code:
using RimWorld;
using Verse;
using Verse.AI;
using rjw;
using UnityEngine;
using System.Collections.Generic;
using LudeonTK;
using System.Linq;
using HarmonyLib;

namespace ElToro_BAddon
{
    public static class DebugActions_NuzzleSex
    {
        [DebugAction("ElToro Bestiality", "Trigger NuzzleSex", actionType = DebugActionType.Action, allowedGameStates = AllowedGameStates.PlayingOnMap)]
        public static void TriggerNuzzleSex()
        {
            List<Pawn> selectedPawns = Find.Selector.SelectedObjects.OfType<Pawn>().ToList();
            if (selectedPawns.Count != 2)
            {
                Messages.Message("Select exactly 2 pawns: animal initiator and human recipient.", MessageTypeDefOf.RejectInput, false);
                return;
            }

            Pawn initiator = selectedPawns[0];
            Pawn recipient = selectedPawns[1];

            if (!initiator.RaceProps.Animal)
            {
                Messages.Message("First selected pawn must be an animal.", MessageTypeDefOf.RejectInput, false);
                return;
            }

            if (!recipient.RaceProps.Humanlike)
            {
                Messages.Message("Second selected pawn must be a human.", MessageTypeDefOf.RejectInput, false);
                return;
            }

            InteractionDef interaction = (recipient.gender == Gender.Female)
                ? defs.InterspiciesHumanMatingFemale
                : defs.InterspiciesHumanMatingMale;

            bool result = initiator.interactions.TryInteractWith(recipient, interaction);
            Messages.Message($"[DEBUG] NuzzleSex interaction triggered: {initiator.LabelShortCap} → {recipient.LabelShortCap}. Success: {result}", MessageTypeDefOf.NeutralEvent, false);
        }
    }
    public class DebugActions_Nuzzle
    {
        [DebugAction("ElToro Bestiality", "Force Nuzzle Job", actionType = DebugActionType.Action, allowedGameStates = AllowedGameStates.PlayingOnMap)]
        public static void ForceNuzzleJob()
        {
            List<Pawn> selectedPawns = Find.Selector.SelectedObjects.FindAll(obj => obj is Pawn).ConvertAll(obj => obj as Pawn);
            if (selectedPawns.Count != 2)
            {
                Messages.Message("Select exactly two pawns: one animal (initiator) and one human (target).", MessageTypeDefOf.RejectInput, false);
                return;
            }

            Pawn initiator = selectedPawns[0];
            Pawn recipient = selectedPawns[1];

            if (!initiator.RaceProps.Animal)
            {
                Messages.Message("The first selected pawn must be an animal.", MessageTypeDefOf.RejectInput, false);
                return;
            }

            if (recipient.RaceProps.Animal || recipient.NonHumanlikeOrWildMan())
            {
                Messages.Message("The second selected pawn must be a humanlike target.", MessageTypeDefOf.RejectInput, false);
                return;
            }

            Job nuzzleJob = JobMaker.MakeJob(JobDefOf.Nuzzle, recipient);
            initiator.jobs.TryTakeOrderedJob(nuzzleJob);

            Messages.Message($"{initiator.LabelShortCap} is now trying to nuzzle {recipient.LabelShortCap}.", MessageTypeDefOf.NeutralEvent, false);
        }
        [DebugAction("ElToro Bestiality", "Reroll Animal Sex Trait", actionType = DebugActionType.Action, allowedGameStates = AllowedGameStates.PlayingOnMap)]
        public static void RerollSexTrait()
        {
            List<Pawn> selectedPawns = Find.Selector.SelectedObjects.OfType<Pawn>().ToList();
            if (selectedPawns.Count != 1)
            {
                Messages.Message("Select exactly 1 animal pawn to reroll trait.", MessageTypeDefOf.RejectInput, false);
                return;
            }

            Pawn animal = selectedPawns[0];

            if (!animal.RaceProps.Animal)
            {
                Messages.Message("Selected pawn must be an animal.", MessageTypeDefOf.RejectInput, false);
                return;
            }

            var comp = animal.TryGetComp<CompAnimalSexMemory>();
            if (comp == null || !comp.ShouldUseMemory)
            {
                Messages.Message("Selected animal does not have a valid CompAnimalSexMemory or memory is disabled.", MessageTypeDefOf.RejectInput, false);
                return;
            }

            AccessTools.Field(typeof(CompAnimalSexMemory), "traits").SetValue(comp, SexTrait.None); // forcibly reset
            bool success = comp.GiveSexTrait();

            string newTraits = comp.GetTraits().ToString();
            Messages.Message($"[DEBUG] Rerolled trait for {animal.LabelShortCap}: {(success ? newTraits : "No trait assigned")}", MessageTypeDefOf.NeutralEvent, false);
        }
    }
}


code:
using RimWorld;
using rjw;
using rjw.Modules.Attraction;
using System.Collections.Generic;
using UnityEngine;
using Verse;
using Verse.AI;

namespace ElToro_BAddon
{
    public class InteractionWorker_NuzzleSex : InteractionWorker
    {
        public override float RandomSelectionWeight(Pawn initiator, Pawn recipient)
        {
            return 0f;
        }

        public override void Interacted(Pawn initiator, Pawn recipient, List<RulePackDef> extraSentencePacks,
            out string letterText, out string letterLabel, out LetterDef letterDef, out LookTargets lookTargets)
        {
            letterText = null;
            letterLabel = null;
            letterDef = null;
            lookTargets = null;

            if (!xxx.is_healthy(initiator) || !CasualSex_Helper.CanHaveSex(initiator) || !SexUtility.ReadyForLovin(initiator))
                return;

            var comp = initiator.TryGetComp<CompAnimalSexMemory>();
            float willingnessScore = AttractionUtility.Evaluate(recipient, initiator, AttractionPurpose.ForFucking);
            float acceptanceChance;

            if (willingnessScore < 0.1f)
            {
                acceptanceChance = 0f;
            }
            else if (willingnessScore >= 0.5f)
            {
                acceptanceChance = 0.9f;
            }
            else
            {
                acceptanceChance = Mathf.Lerp(0.3f, 0.9f, Mathf.InverseLerp(0.1f, 0.5f, willingnessScore));
            }

            if (xxx.is_satisfied(recipient))
            {
                acceptanceChance *= 0.75f;
            }

            if (comp.HasTrait(SexTrait.Affectionate))
            {
                acceptanceChance *= 1.25f;
            }

            acceptanceChance = Mathf.Clamp01(acceptanceChance * Settings.InterspiciesHumanModifier);

            bool accepted = Rand.Chance(acceptanceChance);
            bool forced = false;

            if (!accepted)
            {
                float initiatorV = xxx.get_vulnerability(initiator);
                float recipientV = xxx.get_vulnerability(recipient);

                float forceChance = 0.05f;

                float baseBoost = (recipientV - initiatorV) / 2f;
                baseBoost = Mathf.Clamp01(baseBoost);

                float maxBoost = 0f;
                if (comp != null)
                {
                    if (comp.HasTrait(SexTrait.Affectionate))
                        maxBoost = 0f;
                    else if (comp.HasTrait(SexTrait.Aggressive))
                        maxBoost = 0.30f;
                    else
                        maxBoost = 0.15f;
                }

                // Apply boost
                forceChance += maxBoost * baseBoost;

                forceChance = Mathf.Clamp01(forceChance * Settings.InterspiciesRapeModifier);

                forced = Rand.Chance(forceChance) && xxx.can_get_raped(recipient) && xxx.can_rape(initiator);
            }

            RulePackDef initiatorPack = initiator.gender == Gender.Female
                ? DefDatabase<RulePackDef>.GetNamedSilentFail("BestialityNuzzle_Initiator_Female")
                : DefDatabase<RulePackDef>.GetNamedSilentFail("BestialityNuzzle_Initiator_Male");

            if (initiatorPack != null)
            {
                if (extraSentencePacks == null)
                    extraSentencePacks = new List<RulePackDef>();
                extraSentencePacks.Add(initiatorPack);
            }

            if (accepted)
            {
                Job jobAccept = JobMaker.MakeJob(defs.InterspiciesMatingInteractionAccept, recipient);
                initiator.jobs.TryTakeOrderedJob(jobAccept);
            }
            else if (forced)
            {
                Job jobForced = JobMaker.MakeJob(defs.ForcedBreeding, recipient);
                initiator.jobs.TryTakeOrderedJob(jobForced);
            }
            else
            {
                Job jobDecline = JobMaker.MakeJob(defs.InterspiciesMatingInteractionDecline, recipient);
                initiator.jobs.TryTakeOrderedJob(jobDecline);
            }
        }
    }
}

repo_name: ElToro/rjw

license: unknown


code:
using HarmonyLib;
using Verse;
using Verse.AI;
using RimWorld;
using rjw;
using System.Collections.Generic;
using ElToro_BAddon;
using System.Linq;
using UnityEngine;
using Rimworld_Animations;

[HarmonyPatch(typeof(JobDriver_Nuzzle), "MakeNewToils")]
public static class Patch_NuzzleSex_JobDriver_Nuzzle_Escalation
{
    [HarmonyPostfix]
    public static void Postfix(JobDriver_Nuzzle __instance, ref IEnumerable<Toil> __result)
    {
        var toils = __result.ToList();
        if (toils.Count < 3) return;

        Pawn initiator = __instance.pawn;
        Pawn recipient = __instance.job.targetA.Thing as Pawn;

        var comp = initiator.TryGetComp<CompAnimalSexMemory>();

        if (initiator == null || recipient == null)
            return;
        if (!CasualSex_Helper.CanHaveSex(initiator) || !CasualSex_Helper.CanHaveSex(recipient))
            return;

        float baseChance = 0.1f;

        Hediff paraphilia = initiator.health.hediffSet.GetFirstHediffOfDef(xxx.humanlikeParaphilia);
        if (paraphilia != null)
        {
            baseChance = Mathf.Lerp(0.1f, 0.9f, paraphilia.Severity);
        }

        if (initiator.health.hediffSet.HasHediff(defs.HumanHybrid))
        {
            baseChance = Mathf.Clamp(baseChance + 0.1f, 0.1f, 0.9f);
        }

        if (comp.HasTrait(SexTrait.Affectionate) || comp.HasTrait(SexTrait.Aggressive))
        {
            baseChance = Mathf.Clamp(baseChance + 0.3f, 0.1f, 0.9f);
        }

        if (comp.HasTrait(SexTrait.LikesHumans))
        {
            baseChance = Mathf.Clamp(baseChance + 0.2f, 0.1f, 0.9f);
        }

        if (comp.HasTrait(SexTrait.LikesAnimals))
        {
            baseChance = Mathf.Clamp(baseChance - 0.2f, 0.1f, 0.9f);
        }

        bool paraphiliaOverride = paraphilia != null && Rand.Value < Mathf.Lerp(0f, 0.1f, paraphilia.Severity);
        bool hybridOverride = initiator.health.hediffSet.HasHediff(defs.HumanHybrid) && Rand.Chance(0.05f);

        if (!SexUtility.ReadyForLovin(initiator))
        {
            return;
        }

        if (!paraphiliaOverride && !hybridOverride && !Rand.Chance(baseChance))
        {
            return;
        }

        Toil WaitForInteraction = new Toil();
        WaitForInteraction.defaultCompleteMode = ToilCompleteMode.Delay;
        WaitForInteraction.defaultDuration = 130; // Should be 121, but i don't like such values =)
        WaitForInteraction.socialMode = RandomSocialMode.Off;

        WaitForInteraction.tickAction = delegate
        {
            if (initiator.interactions.CanInteractNowWith(recipient))
            {
                initiator.jobs.curDriver.ReadyForNextToil();
            }

        };

        Toil escalateToil = new Toil();
        escalateToil.initAction = () =>
        {
            bool success = initiator.interactions.TryInteractWith(recipient, defs.NuzzleSex);
        };
        escalateToil.defaultCompleteMode = ToilCompleteMode.Instant;

        toils.Add(WaitForInteraction);
        toils.Add(escalateToil);

        __result = toils;
    }
}


code:
using ElToro_BAddon;
using rjw;
using rjw.Modules.Interactions.Preferences;
using System.Collections.Generic;
using Verse;

public class AnimalInitiatedHighPawnPreferences : PawnPreferences
{
    public override bool IsActive(Pawn pawn, Pawn partner, SexProps props)
    {
        if (!xxx.is_human(partner) || !xxx.is_animal(pawn) || !props.isRape)
            return false;

        float acceptance = BestialityAcceptanceUtility.GetBestialityAcceptanceSeverity(partner);
        return acceptance > 0.5f;
    }

    public override IEnumerable<SexPreference> GetPreferences(Pawn pawn, Pawn partner, SexProps props)
    {
        yield return new PenetrativePreference(Preference.Love);
        yield return new PenetratedPreference(Preference.Love);
    }
}


code:
using ElToro_BAddon;
using rjw;
using rjw.Modules.Interactions;
using rjw.Modules.Interactions.Preferences;
using System.Collections.Generic;
using Verse;

public class AnimalInitiatedLowPawnPreferences : PawnPreferences
{
    public override bool IsActive(Pawn pawn, Pawn partner, SexProps props)
    {
        if (!xxx.is_human(partner) || !xxx.is_animal(pawn) || !props.isRape)
            return false;

        float acceptance = BestialityAcceptanceUtility.GetBestialityAcceptanceSeverity(partner);
        return acceptance < 0.25f;
    }

    public override IEnumerable<SexPreference> GetPreferences(Pawn pawn, Pawn partner, SexProps props)
    {
        yield return new TypePreference(xxx.rjwSextype.Vaginal, Preference.LastResort);
        yield return new TypePreference(xxx.rjwSextype.Anal, Preference.LastResort);
        yield return new TypePreference(xxx.rjwSextype.DoublePenetration, Preference.LastResort);
        yield return new PenetrativePreference(Preference.LastResort);
        yield return new PenetratedPreference(Preference.LastResort);

        yield return new SelfPartPreference(LewdablePartFamily.Mouth, Preference.Love);
        yield return new PartnerPartPreference(LewdablePartFamily.Mouth, Preference.LastResort);
    }
}


code:
using ElToro_BAddon;
using rjw;
using rjw.Modules.Interactions;
using rjw.Modules.Interactions.Preferences;
using System.Collections.Generic;
using Verse;

public class AnimalInitiatedMediumPawnPreferences : PawnPreferences
{
    public override bool IsActive(Pawn pawn, Pawn partner, SexProps props)
    {
        if (!xxx.is_human(partner) || !xxx.is_animal(pawn) || !props.isRape)
            return false;

        float acceptance = BestialityAcceptanceUtility.GetBestialityAcceptanceSeverity(partner);
        return acceptance > 0.25f && acceptance < 0.5f;
    }

    public override IEnumerable<SexPreference> GetPreferences(Pawn pawn, Pawn partner, SexProps props)
    {
        yield return new SelfPartPreference(LewdablePartFamily.Mouth, Preference.Love);

        if (Genital_Helper.has_vagina(partner))
        {
            yield return new TypePreference(xxx.rjwSextype.Vaginal, Preference.LastResort);
            yield return new TypePreference(xxx.rjwSextype.Anal, Preference.Dislike);
            yield return new TypePreference(xxx.rjwSextype.DoublePenetration, Preference.LastResort);
            yield return new PartnerPartPreference(LewdablePartFamily.Mouth, Preference.Love);
        }
        if (Genital_Helper.has_male_bits(partner))
        {
            yield return new PenetrativePreference(Preference.Dislike);
        }
    }
}


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

namespace ElToro_BAddon
{
	public class JobDriver_SexInitiatorAnimalSex : JobDriver_SexBaseInitiator
	{
        public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
            return true;
		}

        protected override IEnumerable<Toil> MakeNewToils()
        {
            setup_ticks();
            var PartnerJob = defs.SexRecieverAnimalSex;
            var comp = pawn.TryGetComp<CompAnimalSexMemory>();
            float initiatorV = xxx.get_vulnerability(pawn);
            float recipientV = xxx.get_vulnerability(Partner);

            float forceChance = 0.05f;

            float baseBoost = (recipientV - initiatorV) / 2f;
            baseBoost = Mathf.Clamp01(baseBoost);

            float maxBoost = 0f;
            if (comp != null)
            {
                if (comp.HasTrait(SexTrait.Affectionate))
                    maxBoost = 0f;
                else if (comp.HasTrait(SexTrait.Aggressive))
                    maxBoost = 0.30f;
                else
                    maxBoost = 0.15f;
            }

            // Apply boost
            forceChance += maxBoost * baseBoost;

            forceChance = Mathf.Clamp01(forceChance * Settings.InterspiciesRapeModifier);

            bool shouldInterrupt = Rand.Chance(forceChance) && xxx.can_get_raped(Partner) && xxx.can_rape(pawn);

            this.FailOnDespawnedOrNull(iTarget);
            this.FailOn(() => !pawn.CanReach(Partner, PathEndMode.Touch, Danger.Some));
            this.FailOn(() => pawn.Drafted);
            this.FailOn(() => Partner.Drafted);
            this.FailOn(() => pawn.IsFighting());
            this.FailOn(() => Partner.IsFighting());

            yield return Toils_Goto.GotoThing(iTarget, PathEndMode.Touch);

            var stopPartnerJob = new Toil();
            stopPartnerJob.defaultCompleteMode = ToilCompleteMode.Delay;
            stopPartnerJob.defaultDuration = 1000;

            stopPartnerJob.initAction = () =>
            {
                if (!pawn.CanReach(Partner, PathEndMode.Touch, Danger.Some))
                {
                    pawn.interactions.TryInteractWith(Partner, defs.NuzzleSexCancel);

                    pawn.jobs.EndCurrentJob(JobCondition.Incompletable);
                    return;
                }

                if (Partner.CurJob != null && Partner.jobs.curDriver != null && (Partner.jobs.curDriver is JobDriver_SexBaseInitiator || Partner.jobs.curDriver is JobDriver_SexBaseReciever))
                {
                    pawn.interactions.TryInteractWith(Partner, defs.NuzzleSexCancel);

                    pawn.jobs.EndCurrentJob(JobCondition.Incompletable);
                    return;
                }

                if (Partner.CurJob != null && Partner.CurJob.def.casualInterruptible && (Partner.carryTracker == null || !(Partner.carryTracker.CarriedThing is Pawn)))
                {
                    Partner.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
                    Partner?.jobs.StopAll();
                    Partner.pather?.StopDead();

                    if (!pawn.Reserve(Partner, job, ignoreOtherReservations: true))
                    {
                        pawn.jobs.EndCurrentJob(JobCondition.Incompletable);
                        return;
                    }

                    // Start blocking job on partner to prevent reassignments
                    Job waitJob = JobMaker.MakeJob(JobDefOf.Wait, 10);
                    Partner.jobs.StartJob(waitJob, JobCondition.InterruptForced);

                    ReadyForNextToil();
                }

                if (Partner.CurJob == null)
                {
                    Partner.pather?.StopDead();

                    if (!pawn.Reserve(Partner, job, ignoreOtherReservations: true))
                    {
                        pawn.jobs.EndCurrentJob(JobCondition.Incompletable);
                        return;
                    }

                    // Start blocking job on partner to prevent reassignments
                    Job waitJob = JobMaker.MakeJob(JobDefOf.Wait, 10);
                    Partner.jobs.StartJob(waitJob, JobCondition.InterruptForced);

                    ReadyForNextToil();
                }
            };

            stopPartnerJob.tickAction = () =>
            {
                if (Partner.CurJob != null && Partner.CurJob.def.casualInterruptible && (Partner.carryTracker == null || !(Partner.carryTracker.CarriedThing is Pawn)))
                {
                    Partner.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
                    Partner?.jobs.StopAll();
                    Partner.pather?.StopDead();

                    if (!pawn.Reserve(Partner, job, ignoreOtherReservations: true))
                    {
                        pawn.jobs.EndCurrentJob(JobCondition.Incompletable);
                        return;
                    }

                    // Start blocking job on partner to prevent reassignments
                    Job waitJob = JobMaker.MakeJob(JobDefOf.Wait, 10);
                    Partner.jobs.StartJob(waitJob, JobCondition.InterruptForced);

                    ReadyForNextToil();
                }

                if (Partner.CurJob == null)
                {
                    Partner.pather?.StopDead();

                    if (!pawn.Reserve(Partner, job, ignoreOtherReservations: true))
                    {
                        pawn.jobs.EndCurrentJob(JobCondition.Incompletable);
                        return;
                    }

                    // Start blocking job on partner to prevent reassignments
                    Job waitJob = JobMaker.MakeJob(JobDefOf.Wait, 10);
                    Partner.jobs.StartJob(waitJob, JobCondition.InterruptForced);

                    ReadyForNextToil();
                }

                if (!pawn.Position.InHorDistOf(Partner.Position, 5f))
                {
                    if (!pawn.pather.Moving || pawn.pather.Destination != Partner.Position)
                    {
                        if (!pawn.CanReach(Partner, PathEndMode.OnCell, Danger.Some))
                        {
                            pawn.interactions.TryInteractWith(Partner, defs.NuzzleSexCancel);

                            pawn.jobs.EndCurrentJob(JobCondition.Incompletable);
                            return;
                        }

                        pawn.pather.StartPath(Partner.Position, PathEndMode.OnCell);
                    }
                }
            };

            yield return stopPartnerJob;

            var finalInterruptCheck = new Toil();
            finalInterruptCheck.initAction = () =>
            {
                if (Partner.CurJob != null && !Partner.CurJob.def.casualInterruptible)
                {
                    if (shouldInterrupt)
                    {

                        Job rapeJob = JobMaker.MakeJob(defs.ForcedBreedingWaiting, Partner);
                        pawn.jobs.StartJob(rapeJob, JobCondition.InterruptForced);

                        return;
                    }
                }

                if (Partner.CurJob != null && !Partner.CurJob.def.casualInterruptible)
                {
                    pawn.interactions.TryInteractWith(Partner, defs.NuzzleSexCancel);

                    pawn.jobs.EndCurrentJob(JobCondition.Incompletable);
                    return;
                }

                if (Partner.CurJob != null && Partner.carryTracker != null && (Partner.carryTracker.CarriedThing is Pawn))
                {
                    pawn.interactions.TryInteractWith(Partner, defs.NuzzleSexCancel);

                    pawn.jobs.EndCurrentJob(JobCondition.Incompletable);
                    return;
                }
            };
            yield return finalInterruptCheck;

            Toil findQuickieSpot = new Toil();
            findQuickieSpot.defaultCompleteMode = ToilCompleteMode.PatherArrival;
            findQuickieSpot.initAction = delegate
            {
                if (Partner == null || Partner.Destroyed || !Partner.Spawned)
                {
                    pawn.jobs.EndCurrentJob(JobCondition.Incompletable);
                    return;
                }

                var all_pawns = pawn.Map.mapPawns.AllPawnsSpawned.Where(x =>
                    x.Position.DistanceTo(pawn.Position) < 100 &&
                    xxx.is_human(x) &&
                    x != pawn &&
                    x != Partner
                ).ToList();

                FloatRange temperature = pawn.ComfortableTemperatureRange();
                float cellTemp = pawn.Position.GetTemperature(pawn.Map);

                if (Partner.IsPrisonerInPrisonCell() ||
                    (!CasualSex_Helper.MightBeSeen(all_pawns, pawn.Position, pawn, Partner) &&
                     (cellTemp > temperature.min && cellTemp < temperature.max)))
                {
                    ReadyForNextToil();
                    return;
                }

                var spot = MatingHelper.FindSexLocation(pawn, Partner);

                if (!spot.IsValid || !pawn.CanReach(spot, PathEndMode.OnCell, Danger.Some) || !Partner.CanReach(spot, PathEndMode.OnCell, Danger.Some))
                {
                    spot = Partner.Position;
                    ReadyForNextToil();
                    return;
                }

                pawn.pather?.StartPath(spot, PathEndMode.OnCell);

                var job = JobMaker.MakeJob(JobDefOf.GotoMindControlled, spot);
                Partner?.jobs?.StartJob(job, JobCondition.InterruptForced);
            };
            yield return findQuickieSpot;

            Toil WaitForPartner = new Toil();
            WaitForPartner.defaultCompleteMode = ToilCompleteMode.Delay;
            WaitForPartner.initAction = delegate
            {
                ticksLeftThisToil = 5000;
            };
            WaitForPartner.tickAction = delegate
            {
                if (pawn.Position.DistanceTo(Partner.Position) <= 1f)
                {
                    ReadyForNextToil();
                }
            };
            yield return WaitForPartner;

            var startPartnerJob = new Toil();
            startPartnerJob.defaultCompleteMode = ToilCompleteMode.Instant;
            startPartnerJob.socialMode = RandomSocialMode.Off;
            startPartnerJob.initAction = () =>
            {
                Partner.pather?.StopDead();
                Partner.jobs.StartJob(JobMaker.MakeJob(PartnerJob, pawn), JobCondition.InterruptForced);
            };
            yield return startPartnerJob;

            var sexToil = new Toil();
            sexToil.defaultCompleteMode = ToilCompleteMode.Never;
            sexToil.defaultDuration = duration;
            sexToil.handlingFacing = true;
            sexToil.initAction = () =>
            {
                Partner.pather?.StopDead();
                Partner.jobs.curDriver.asleep = false;
                Start();
            };
            sexToil.tickAction = () =>
            {
                if (pawn.IsHashIntervalTick(ticks_between_hearts))
                    ThrowMetaIconF(pawn.Position, pawn.Map, FleckDefOf.Heart);
                SexTick(pawn, Partner);
                if (!Partner.Dead)
                    SexUtility.reduce_rest(Partner, 1);
                SexUtility.reduce_rest(pawn, 2);
                if (ticks_left <= 0)
                    ReadyForNextToil();
            };
            sexToil.FailOn(() => Partner.CurJob == null || Partner.CurJob.def != PartnerJob);
            sexToil.AddFinishAction(End);
            yield return sexToil;

            yield return new Toil
            {
                initAction = () =>
                {
                    SexUtility.ProcessSex(Sexprops);

                    if (comp != null && comp.HasTrait(SexTrait.Vigorous))
                        {
                            MatingHelper.ShortenLovinCooldown(pawn);
                        }

                },
                defaultCompleteMode = ToilCompleteMode.Instant
            };
        }
    }
}


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

namespace ElToro_BAddon
{
    public class JobDriver_SexRecieverAnimalSex : JobDriver_SexBaseReciever
    {
        protected override IEnumerable<Toil> MakeNewToils()
        {
            DoSetup();

            this.FailOn(() => pawn.Drafted);
            this.FailOn(() => pawn.IsFighting());

            var get_loved = new Toil
            {
                defaultCompleteMode = ToilCompleteMode.Never,
                handlingFacing = true,
                socialMode = RandomSocialMode.Off,
                initAction = () =>
                {
                    pawn.pather.StopDead();
                    pawn.jobs.curDriver.asleep = false;
                },
                tickAction = () =>
                {
                    if (parteners.Count <= 0) return;
                    if (pawn.IsHashIntervalTick(ticks_between_hearts))
                        ThrowMetaIconF(pawn.Position, pawn.Map, FleckDefOf.Heart);
                }
            };
            get_loved.AddFinishAction(() =>
            {
                if (xxx.is_human(pawn))
                {
                    var comp = pawn.GetCompRJW();
                    if (comp != null)
                    {
                        comp.drawNude = false;
                        pawn.Drawer.renderer.SetAllGraphicsDirty();
                    }
                }
                GlobalTextureAtlasManager.TryMarkPawnFrameSetDirty(pawn);
            });

            yield return get_loved;
        }
    }
}


code:
using rjw;
using rjw.Modules.Attraction;
using rjw.Modules.Attraction.StandardPreferences;
using UnityEngine;
using Verse;

namespace ElToro_BAddon
{
    public class BestialityAcceptancePref : BasePreference
    {
        [StandardPreference]
        public static void ApplyTo(ref AttractionRequest request)
        {
            if (xxx.is_human(request.Pawn) && xxx.is_animal(request.Target))
                request.SetPreference(new BestialityAcceptancePref());
        }

        private BestialityAcceptancePref() : base(
            AttractionMode.Physical,
            nameof(P_HumanToBodyType),
            FactorOperation.Multiply)
        {
            Priority = 150;
        }

        protected override float GetOperand(ref AttractionRequest request, float factor)
        {
            float acceptance = BestialityAcceptanceUtility.GetBestialityAcceptanceSeverity(request.Pawn);
            float normalized = Mathf.Clamp01((acceptance - 0.25f) / 0.75f);
            float scaled = Mathf.Lerp(0.45f, 1.0f, normalized * normalized);
            return scaled;
        }
    }
}


code:
using RimWorld;
using Verse;
using UnityEngine;
using rjw;

namespace ElToro_BAddon
{
    public static class BestialityAcceptanceUtility
    {
        public static float GetBestialityAcceptanceSeverity(Pawn pawn)
        {
            if (pawn == null || pawn.Dead) return 0f;

            if (xxx.is_zoophile(pawn))
                return 1.0f;

            var hediff = pawn.health.hediffSet.GetFirstHediffOfDef(defs.BestialityAcceptance);
            return hediff?.Severity ?? 0f;
        }

        public static void ProcessBestialityAcceptance(Pawn pawn, float severityChange = 0.05f, bool canReach100 = false, bool isNegative = false)
        {
            if (pawn == null || pawn.Dead) return;

            if (xxx.is_zoophile(pawn)) return;

            var hediff = pawn.health.hediffSet.GetFirstHediffOfDef(defs.BestialityAcceptance);
            if (hediff == null)
            {
                hediff = HediffMaker.MakeHediff(defs.BestialityAcceptance, pawn);
                hediff.Severity = GetInitialAcceptance(pawn);
                pawn.health.AddHediff(hediff);
            }

            float traitModifier = GetTraitModifier(pawn);
            severityChange *= traitModifier;

            severityChange *= Rand.Range(0.7f, 1.3f);

            if (isNegative)
            {
                hediff.Severity = Mathf.Max(hediff.Severity - severityChange, 0f);
            }
            else
            {
                if (!canReach100)
                {
                    if (hediff.Severity >= 0.75f)
                        return;

                    hediff.Severity = Mathf.Min(hediff.Severity + severityChange, 0.75f);
                }
                else
                {
                    hediff.Severity = Mathf.Clamp(hediff.Severity + severityChange, 0f, 1f);
                }
            }

            if (hediff.Severity >= 1.0f)
            {
                int requiredSexActs = Rand.Range(1, 6);
                int actualSexActs = (int)(pawn.records.GetValue(xxx.CountOfSexWithAnimals) + pawn.records.GetValue(xxx.CountOfSexWithInsects));

                if (actualSexActs >= requiredSexActs)
                {
                    pawn.story.traits.GainTrait(new Trait(xxx.zoophile));
                    pawn.health.RemoveHediff(hediff);
                }
            }
        }

        private static float GetInitialAcceptance(Pawn pawn)
        {
            float baseAcceptance = Rand.Range(0f, 0.2f);
            baseAcceptance += GetTraitInitialModifier(pawn);
            return Mathf.Clamp(baseAcceptance, 0f, 1f);
        }

        private static float GetTraitInitialModifier(Pawn pawn)
        {
            if (pawn.story?.traits == null) return 0f;

            float modifier = 0f;

            if (xxx.is_nympho(pawn)) modifier += 0.1f;
            if (xxx.is_lecher(pawn)) modifier += 0.1f;
            if (pawn.story?.traits?.HasTrait(TraitDefOf.Psychopath) == true) modifier += Rand.Range(-0.2f, 0.2f);
            if (xxx.is_prude(pawn)) modifier -= 0.2f;
            if (pawn.story?.traits?.HasTrait(TraitDefOf.BodyPurist) == true) modifier -= 0.2f;
            if (xxx.is_kind(pawn)) modifier += 0.1f;

            return modifier;
        }

        private static float GetTraitModifier(Pawn pawn)
        {
            if (pawn.story?.traits == null) return 1f;

            float modifier = 1f;

            if (xxx.is_nympho(pawn)) modifier *= 1.2f;
            if (xxx.is_lecher(pawn)) modifier *= 1.2f;
            if (xxx.is_prude(pawn)) modifier *= 0.8f;
            if (pawn.story?.traits?.HasTrait(TraitDefOf.BodyPurist) == true) modifier *= 0.8f;

            return modifier;
        }
    }
}


code:
using ElToro_BAddon;
using RimWorld;
using rjw;
using UnityEngine;
using Verse;

public static class BestialityTalkUtility
{
    public static void ProcessBestialityTalk(Pawn initiator, Pawn recipient, bool isNegative)
    {
        if (initiator == null || recipient == null || initiator.Dead || recipient.Dead) return;

        float baseSeverityChange = 0.025f;
        float socialInfluence = GetSocialInfluence(initiator);
        float severityChange = baseSeverityChange * (1f + socialInfluence);

        BestialityAcceptanceUtility.ProcessBestialityAcceptance(recipient, severityChange, false, isNegative);

        if (xxx.is_zoophile(initiator))
        {
            initiator.needs.mood.thoughts.memories.TryGainMemory(DefDatabase<ThoughtDef>.GetNamed("BestialityTalkMood"));
        }

        if (xxx.is_zoophile(recipient))
        {
            recipient.needs.mood.thoughts.memories.TryGainMemory(DefDatabase<ThoughtDef>.GetNamed("BestialityTalkMood"));
        }

        if (!isNegative)
        {
            if (Rand.Chance(0.6f))
                initiator.needs.mood.thoughts.memories.TryGainMemory(DefDatabase<ThoughtDef>.GetNamed("BestialityTalkSocial"), recipient);
            else if (!Rand.Chance(0.9f))
                initiator.needs.mood.thoughts.memories.TryGainMemory(DefDatabase<ThoughtDef>.GetNamed("BestialityTalkSocial_Negative"), recipient);
        }
        else
        {
            if (Rand.Chance(0.5f))
                recipient.needs.mood.thoughts.memories.TryGainMemory(DefDatabase<ThoughtDef>.GetNamed("BestialityTalkSocial_Negative"), initiator);
            else if (!Rand.Chance(0.9f))
                recipient.needs.mood.thoughts.memories.TryGainMemory(DefDatabase<ThoughtDef>.GetNamed("BestialityTalkSocial"), initiator);
        }
    }
    public static float GetSocialInfluence(Pawn pawn)
    {
        float socialImpact = pawn.GetStatValue(StatDefOf.SocialImpact);
        return Mathf.Clamp((socialImpact - 1f) * 0.3f, -0.3f, 0.3f);
    }
}


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

namespace ElToro_BAddon
{
    public class InteractionWorker_BestialityTalk : InteractionWorker
    {
        private const float BASE_CHANCE = 0.2f;
        private const float MIN_ACCEPTANCE = 0.25f;
        private const float HIGH_ACCEPTANCE_THRESHOLD = 0.75f;
        private const float NEGATIVITY_BASE_CHANCE = 0.5f;

        private static readonly HediffDef AlcoholHigh = HediffDefOf.AlcoholHigh;
        private static readonly HediffDef SmokeleafHigh = DefDatabase<HediffDef>.GetNamedSilentFail("SmokeleafHigh");
        private static readonly HediffDef PsychiteTeaHigh = DefDatabase<HediffDef>.GetNamedSilentFail("PsychiteTeaHigh");
        private static readonly HediffDef FlakeHigh = DefDatabase<HediffDef>.GetNamedSilentFail("FlakeHigh");
        private static readonly HediffDef YayoHigh = DefDatabase<HediffDef>.GetNamedSilentFail("YayoHigh");

        public override float RandomSelectionWeight(Pawn initiator, Pawn recipient)
        {
            if (!Settings.BestialityAcceptance || initiator == null || recipient == null || initiator.Dead || recipient.Dead)
                return 0f;

            float baseChance = BASE_CHANCE * Settings.BestialityTalkChances;
            float acceptance = BestialityAcceptanceUtility.GetBestialityAcceptanceSeverity(initiator);

            if (acceptance < MIN_ACCEPTANCE)
                return 0f;
            if (acceptance >= HIGH_ACCEPTANCE_THRESHOLD)
            {
                float t = Mathf.InverseLerp(HIGH_ACCEPTANCE_THRESHOLD, 1f, acceptance);
                baseChance *= 1f + (0.5f * t) + (0.5f * t * t); // Quadratic scaling to ~2x
            }

            baseChance *= GetDrugMultiplier(initiator);
            baseChance *= GetTraitMultiplier(initiator);
            if (initiator.needs.mood?.CurLevelPercentage < 0.3f)
                baseChance *= 0.8f;
            baseChance *= GetIdeologyMultiplier(initiator);
            float relationModifier = (initiator.relations.OpinionOf(recipient) / 100f) * 0.3f;
            baseChance *= (1f + relationModifier);

            return Mathf.Max(baseChance, 0f);
        }

        public override void Interacted(Pawn initiator, Pawn recipient, List<RulePackDef> extraSentencePacks,
            out string letterText, out string letterLabel, out LetterDef letterDef, out LookTargets lookTargets)
        {
            letterText = null;
            letterLabel = null;
            letterDef = null;
            lookTargets = null;

            if (initiator == null || recipient == null || initiator.Dead || recipient.Dead)
                return;

            float negativityChance = NEGATIVITY_BASE_CHANCE;
            negativityChance += GetInitiatorAcceptanceModifier(initiator);              // +0.1 to -0.1
            negativityChance += -GetRecipientExperienceModifier(recipient);             // -0.2 to 0
            negativityChance += GetRecipientTraitModifier(recipient);                   // -0.1 to +0.3 (or ±0.2 for Psychopath)
            negativityChance += (recipient.needs.mood?.CurLevelPercentage < 0.3f ? 0.1f : 0f); // +0.1 or 0
            negativityChance += GetRecipientDrugModifier(recipient);                    // -0.15 to 0
            negativityChance += GetRelationModifier(recipient, initiator);              // -0.3 to +0.3
            negativityChance += -GetSocialImpactModifier(initiator);                    // -0.06 to +0.06
            negativityChance += GetRecipientIdeologyModifier(recipient);                // -0.1 to +0.3
            negativityChance = Mathf.Clamp01(negativityChance);

            bool isNegative = Rand.Chance(negativityChance);
            RulePackDef responseRulePack = isNegative
                ? DefDatabase<RulePackDef>.GetNamed("BestialityTalk_Recipient_Negative")
                : DefDatabase<RulePackDef>.GetNamed("BestialityTalk_Recipient_Positive");

            if (extraSentencePacks == null)
                extraSentencePacks = new List<RulePackDef>();
            extraSentencePacks.Add(responseRulePack);

            BestialityTalkUtility.ProcessBestialityTalk(initiator, recipient, isNegative);

            if (isNegative && Rand.Chance(0.01f))
            {
                initiator.interactions.StartSocialFight(recipient);
                letterLabel = "BestialityTalk_TurnedViolent_Label".Translate();

                if (Rand.Bool)
                    letterText = "BestialityTalk_TurnedViolent_Text1".Translate(initiator.NameShortColored);
                else
                    letterText = "BestialityTalk_TurnedViolent_Text2".Translate(recipient.NameShortColored);

                letterDef = LetterDefOf.NegativeEvent;
                lookTargets = new LookTargets(initiator, recipient);
            }
        }

        private float GetDrugMultiplier(Pawn pawn)
        {
            var hediffs = pawn.health.hediffSet;
            if (hediffs.HasHediff(AlcoholHigh) || hediffs.HasHediff(SmokeleafHigh) || hediffs.HasHediff(PsychiteTeaHigh))
                return 1.5f;
            if (hediffs.HasHediff(FlakeHigh) || hediffs.HasHediff(YayoHigh))
                return 1.25f;
            return 1f;
        }

        private float GetTraitMultiplier(Pawn pawn)
        {
            float multiplier = 1f;
            var traits = pawn.story?.traits;
            if (traits == null) return multiplier;

            if (xxx.is_nympho(pawn) || xxx.is_lecher(pawn)) multiplier *= 1.2f;
            if (traits.HasTrait(TraitDefOf.Psychopath)) multiplier *= Rand.Range(0.8f, 1.2f);
            if (xxx.is_kind(pawn)) multiplier *= 1.2f;
            if (xxx.is_prude(pawn)) multiplier *= 0.5f;
            if (traits.HasTrait(TraitDefOf.BodyPurist)) multiplier *= 0.5f;
            return multiplier;
        }

        private float GetIdeologyMultiplier(Pawn pawn)
        {
            if (!ModsConfig.IdeologyActive || pawn.Ideo == null) return 1f;
            float multiplier = 1f;

            if (pawn.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("Proselytizing_Occasionally"))) multiplier *= 1.1f;
            else if (pawn.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("Proselytizing_Sometimes"))) multiplier *= 1.2f;
            else if (pawn.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("Proselytizing_Frequently"))) multiplier *= 1.3f;

            if (pawn.Ideo.HasMeme(DefDatabase<MemeDef>.GetNamedSilentFail("AnimalPersonhood")) ||
                pawn.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("AnimalPersonhood_Strong"))) multiplier *= 1.1f;
            if (pawn.Ideo.HasMeme(DefDatabase<MemeDef>.GetNamedSilentFail("Rancher")) ||
                pawn.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("Ranching_Central"))) multiplier *= 1.1f;
            if (pawn.Ideo.HasMeme(DefDatabase<MemeDef>.GetNamedSilentFail("FleshPurity")) ||
                pawn.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("BodyModification_Prohibited"))) multiplier *= 0.8f;
            if (pawn.Ideo.HasMeme(DefDatabase<MemeDef>.GetNamedSilentFail("HumanPrimacy")) ||
                pawn.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("Bonding_Disapproved"))) multiplier *= 0.8f;

            if (ModsConfig.IsActive("rjw.sexperience.ideology"))
            {
                if (pawn.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("Bestiality_Disapproved"))) multiplier *= 0.7f;
                else if (pawn.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("Bestiality_Horrible"))) multiplier *= 0.5f;
                else if (pawn.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("Bestiality_Abhorrent"))) multiplier *= 0.3f;
            }
            return multiplier;
        }

        private float GetInitiatorAcceptanceModifier(Pawn initiator)
        {
            float severity = BestialityAcceptanceUtility.GetBestialityAcceptanceSeverity(initiator);
            return Mathf.Lerp(0.1f, -0.1f, Mathf.InverseLerp(0.25f, 1f, severity));
        }

        private float GetRecipientExperienceModifier(Pawn recipient)
        {
            float acts = (recipient.records?.GetValue(xxx.CountOfSexWithAnimals) ?? 0f) +
                         (recipient.records?.GetValue(xxx.CountOfSexWithInsects) ?? 0f);
            return Mathf.Clamp(acts * 0.02f, 0f, 0.2f); // Positive value, negated in main calc
        }

        private float GetRecipientTraitModifier(Pawn recipient)
        {
            float modifier = 0f;
            var traits = recipient.story?.traits;
            if (traits == null) return modifier;

            if (xxx.is_prude(recipient) || traits.HasTrait(TraitDefOf.BodyPurist)) modifier += 0.3f;
            if (xxx.is_kind(recipient)) modifier += -0.1f;
            if (traits.HasTrait(TraitDefOf.Psychopath)) modifier += Rand.Range(-0.2f, 0.2f);
            if (xxx.is_nympho(recipient) || xxx.is_lecher(recipient)) modifier += -0.1f;
            return modifier;
        }

        private float GetRecipientDrugModifier(Pawn recipient)
        {
            var hediffs = recipient.health.hediffSet;
            if (hediffs.HasHediff(AlcoholHigh) || hediffs.HasHediff(SmokeleafHigh) || hediffs.HasHediff(PsychiteTeaHigh))
                return -0.15f;
            if (hediffs.HasHediff(FlakeHigh) || hediffs.HasHediff(YayoHigh))
                return -0.05f;
            return 0f;
        }

        private float GetRelationModifier(Pawn recipient, Pawn initiator)
        {
            return (recipient.relations.OpinionOf(initiator) / 100f) * -0.3f; // Positive opinion decreases chance
        }

        private float GetSocialImpactModifier(Pawn initiator)
        {
            return BestialityTalkUtility.GetSocialInfluence(initiator) * 0.2f; // Positive influence decreases chance
        }

        private float GetRecipientIdeologyModifier(Pawn recipient)
        {
            if (!ModsConfig.IdeologyActive || recipient.Ideo == null) return 0f;
            float modifier = 0f;

            if (recipient.Ideo.HasMeme(DefDatabase<MemeDef>.GetNamedSilentFail("AnimalPersonhood")) ||
                recipient.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("AnimalPersonhood_Strong"))) modifier += -0.1f;
            if (recipient.Ideo.HasMeme(DefDatabase<MemeDef>.GetNamedSilentFail("Rancher")) ||
                recipient.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("Ranching_Central"))) modifier += -0.1f;
            if (recipient.Ideo.HasMeme(DefDatabase<MemeDef>.GetNamedSilentFail("FleshPurity")) ||
                recipient.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("BodyModification_Prohibited"))) modifier += 0.2f;
            if (recipient.Ideo.HasMeme(DefDatabase<MemeDef>.GetNamedSilentFail("HumanPrimacy")) ||
                recipient.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("Bonding_Disapproved"))) modifier += 0.2f;

            if (ModsConfig.IsActive("rjw.sexperience.ideology"))
            {
                if (recipient.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("Bestiality_Disapproved"))) modifier += 0.1f;
                else if (recipient.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("Bestiality_Horrible"))) modifier += 0.2f;
                else if (recipient.Ideo.HasPrecept(DefDatabase<PreceptDef>.GetNamedSilentFail("Bestiality_Abhorrent"))) modifier += 0.3f;
            }
            return modifier;
        }
    }
}


code:
using HarmonyLib;
using RimWorld;
using Verse;

namespace ElToro_BAddon
{
    [HarmonyPatch(typeof(RelationsUtility), "TryDevelopBondRelation")]
    public static class Patch_BestialityAcceptanceBonding
    {
        [HarmonyPostfix]
        public static void Postfix(Pawn humanlike, Pawn animal, float baseChance, bool __result)
        {
            if (!__result || humanlike == null || animal == null || humanlike.Dead || animal.Dead || !animal.RaceProps.Animal)
                return;

            BestialityAcceptanceUtility.ProcessBestialityAcceptance(humanlike, 0.02f, false, false);
        }
    }
}


code:
using HarmonyLib;
using System.Collections.Generic;
using System.Reflection.Emit;
using Verse;
using rjw;
using System.Reflection;

namespace ElToro_BAddon
{
    [HarmonyPatch(typeof(SexUtility), "GiveTraits")]
    public static class Patch_BestialityAcceptanceGiveTraits
    {
        [HarmonyTranspiler]
        public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
        {
            List<CodeInstruction> newInstructions = new List<CodeInstruction>(instructions);

            FieldInfo bestialityAcceptanceField = AccessTools.Field(typeof(Settings), "BestialityAcceptance");
            Label skipLabel = generator.DefineLabel(); // Generate Label to jump over

            // Insert our check at the beginning:
            newInstructions.Insert(0, new CodeInstruction(OpCodes.Ldsfld, bestialityAcceptanceField)); // Load Settings.BestialityAcceptance field
            newInstructions.Insert(1, new CodeInstruction(OpCodes.Brtrue_S, skipLabel)); // If field is true, skip

            for (int i = 0; i < newInstructions.Count; i++)
            {
                if (newInstructions[i].opcode == OpCodes.Ret) // Find the return statement
                {
                    newInstructions[i].labels.Add(skipLabel); // Attach skip label to it
                    break;
                }
            }

            return newInstructions;
        }
    }
}


code:
using HarmonyLib;
using RimWorld;
using Verse;
using rjw;
using UnityEngine;

namespace ElToro_BAddon
{
    [HarmonyPatch(typeof(ThinkNode_ChancePerHour_Bestiality), "MtbHours")]
    public static class Patch_BestialityAcceptanceMtbHours
    {
        public static void Postfix(Pawn pawn, ref float __result)
        {
            if (!xxx.is_zoophile(pawn))
            {
                float acceptanceSeverity = BestialityAcceptanceUtility.GetBestialityAcceptanceSeverity(pawn);

                float normalizedSeverity = (acceptanceSeverity - 0.25f) / 0.75f;
                normalizedSeverity = Mathf.Clamp01(normalizedSeverity);

                float mtbMultiplier = Mathf.Lerp(1.0f, 8.0f, normalizedSeverity * normalizedSeverity);

                __result /= mtbMultiplier;
            }
        }
    }
}


code:
using RimWorld;
using Verse;
using HarmonyLib;
using rjw;

namespace ElToro_BAddon
{
    [HarmonyPatch(typeof(InteractionWorker_Nuzzle), "Interacted")]
    public static class Patch_BestialityAcceptanceNuzzle
    {
        public static void Postfix(Pawn initiator, Pawn recipient)
        {
            BestialityAcceptanceUtility.ProcessBestialityAcceptance(recipient, 0.01f, false, false);
        }
    }
}


code:
using RimWorld;
using Verse;
using HarmonyLib;
using rjw;
using System;
using Rimworld_Animations;
using UnityEngine;

namespace ElToro_BAddon
{
    [HarmonyPatch(typeof(AfterSexUtility), "UpdateRecords")]
    public static class Patch_BestialityAcceptanceUpdateRecords
    {
        public static void Postfix(SexProps props)
        {
            if (props.pawn == null || props.partner == null) return;

            bool isBestiality = (xxx.is_human(props.pawn) && xxx.is_animal(props.partner)) ||
                                (xxx.is_human(props.partner) && xxx.is_animal(props.pawn));

            if (!isBestiality) return;

            Pawn humanPawn = xxx.is_human(props.pawn) ? props.pawn : props.partner;

            bool NegativeChance = !Rand.Chance(0.8f);

            if (props.isRape && !props.isRapist && !xxx.is_masochist(humanPawn))
            {
                NegativeChance = Rand.Chance(0.8f);
            }

            BestialityAcceptanceUtility.ProcessBestialityAcceptance(humanPawn, canReach100: true, isNegative: NegativeChance);
        }
    }
}


code:
using HarmonyLib;
using RimWorld;
using rjw;
using Verse;
using System.Reflection;

namespace ElToro_BAddon
{
    [HarmonyPatch(typeof(InteractionWorker_RecruitAttempt))]
    public static class Patch_BestialityAcceptance_DoRecruit
    {
        static MethodBase TargetMethod()
        {
            return AccessTools.Method(
                typeof(InteractionWorker_RecruitAttempt),
                "DoRecruit",
                new[] { typeof(Pawn), typeof(Pawn), typeof(bool) }
            );
        }

        public static void Postfix(Pawn recruiter, Pawn recruitee)
        {
            if (xxx.is_animal(recruitee))
            {
                float acceptanceGain = 0.01f;
                BestialityAcceptanceUtility.ProcessBestialityAcceptance(recruiter, acceptanceGain, false, false);
            }
        }
    }
}


code:
using HarmonyLib;
using RimWorld;
using Verse;
using Verse.AI;

namespace ElToro_BAddon
{
    [HarmonyPatch(typeof(Toils_Interpersonal), "TryTrain")]
    public static class Patch_BestialityAcceptance_TryTrain
    {
        [HarmonyPostfix]
        public static void Postfix(Toil __result, TargetIndex traineeInd)
        {
            __result.AddFinishAction(() =>
            {
                Pawn trainer = __result.actor;

                Pawn animal = trainer.jobs.curJob.GetTarget(traineeInd).Thing as Pawn;

                float severityChange = 0.005f;
                BestialityAcceptanceUtility.ProcessBestialityAcceptance(trainer, severityChange, false, false);
            });
        }
    }
}


code:
using ElToro_BAddon;
using rjw;
using rjw.Modules.Interactions.Preferences;
using System.Collections.Generic;
using Verse;

public class BestialityAcceptanceHighPawnPreferences : PawnPreferences
{
    public override bool IsActive(Pawn pawn, Pawn partner, SexProps props)
    {
        if (!xxx.is_human(pawn) || !xxx.is_animal(partner) || !props.isRape)
            return false;

        float acceptance = BestialityAcceptanceUtility.GetBestialityAcceptanceSeverity(pawn);
        return acceptance > 0.5f;
    }

    public override IEnumerable<SexPreference> GetPreferences(Pawn pawn, Pawn partner, SexProps props)
    {
        yield return new PenetrativePreference(Preference.Love);
        yield return new PenetratedPreference(Preference.Love);
    }
}


code:
using ElToro_BAddon;
using rjw;
using rjw.Modules.Interactions;
using rjw.Modules.Interactions.Preferences;
using System.Collections.Generic;
using Verse;

public class BestialityAcceptanceLowPawnPreferences : PawnPreferences
{
    public override bool IsActive(Pawn pawn, Pawn partner, SexProps props)
    {
        if (!xxx.is_human(pawn) || !xxx.is_animal(partner) || !props.isRape)
            return false;

        float acceptance = BestialityAcceptanceUtility.GetBestialityAcceptanceSeverity(pawn);
        return acceptance < 0.25f;
    }

    public override IEnumerable<SexPreference> GetPreferences(Pawn pawn, Pawn partner, SexProps props)
    {
        yield return new TypePreference(xxx.rjwSextype.Vaginal, Preference.LastResort);
        yield return new TypePreference(xxx.rjwSextype.Anal, Preference.LastResort);
        yield return new TypePreference(xxx.rjwSextype.DoublePenetration, Preference.LastResort);
        yield return new PenetrativePreference(Preference.LastResort);
        yield return new PenetratedPreference(Preference.LastResort);

        yield return new SelfPartPreference(LewdablePartFamily.Mouth, Preference.LastResort);
        yield return new PartnerPartPreference(LewdablePartFamily.Mouth, Preference.Love);
    }
}


code:
using ElToro_BAddon;
using rjw;
using rjw.Modules.Interactions;
using rjw.Modules.Interactions.Preferences;
using System.Collections.Generic;
using Verse;

public class BestialityAcceptanceMediumPawnPreferences : PawnPreferences
{
    public override bool IsActive(Pawn pawn, Pawn partner, SexProps props)
    {
        if (!xxx.is_human(pawn) || !xxx.is_animal(partner) || !props.isRape)
            return false;

        float acceptance = BestialityAcceptanceUtility.GetBestialityAcceptanceSeverity(pawn);
        return acceptance > 0.25f && acceptance < 0.5f;
    }

    public override IEnumerable<SexPreference> GetPreferences(Pawn pawn, Pawn partner, SexProps props)
    {
        yield return new PartnerPartPreference(LewdablePartFamily.Mouth, Preference.Love);

        if (!Genital_Helper.has_male_bits(pawn))
        {
            yield return new TypePreference(xxx.rjwSextype.Vaginal, Preference.LastResort);
            yield return new TypePreference(xxx.rjwSextype.Anal, Preference.Dislike);
            yield return new TypePreference(xxx.rjwSextype.DoublePenetration, Preference.LastResort);
            yield return new SelfPartPreference(LewdablePartFamily.Mouth, Preference.Love);
        }
        if (Genital_Helper.has_male_bits(pawn))
        {
            yield return new PenetrativePreference(Preference.Dislike);
        }
    }
}


code:
using HarmonyLib;

namespace ElToro_BAddon
{
    [HarmonyPatch(typeof(rjw.Modules.Interactions.Preferences.BestialityPreferences), nameof(rjw.Modules.Interactions.Preferences.BestialityPreferences.IsActive))]
    public static class Patch_DisableDefaultBestialityPreferences
    {
        public static void Postfix(ref bool __result)
        {
            __result = false;
        }
    }
}


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

namespace ElToro_BAddon
{
    public static class BreedingWorkHelper
    {
        public static void ApplyBreedingThought(Pawn pawn, bool forced)
        {
            if (pawn == null || pawn.Dead || pawn.needs?.mood == null)
                return;

            if (!forced)
            {
                TryApplyPositiveThought(pawn);
                return;
            }

            TryApplyNegativeThoughtAndConditioning(pawn);
        }

        private static void TryApplyPositiveThought(Pawn pawn)
        {
            bool isSlave = pawn.IsSlave;
            bool isFemale = pawn.gender == Gender.Female;

            ThoughtDef positiveThought = isFemale
                ? (isSlave ? defs.BreedingWork_FemaleSlave_Pos : defs.BreedingWork_FemaleColonist_Pos)
                : (isSlave ? defs.BreedingWork_MaleSlave_Pos : defs.BreedingWork_MaleColonist_Pos);

            pawn.needs.mood.thoughts.memories.TryGainMemory(positiveThought);
        }

        private static void TryApplyNegativeThoughtAndConditioning(Pawn pawn)
        {
            bool isSlave = pawn.IsSlave;
            bool isFemale = pawn.gender == Gender.Female;

            ThoughtDef negativeThought = isFemale
                ? (isSlave ? defs.BreedingWork_FemaleSlave_Neg : defs.BreedingWork_FemaleColonist_Neg)
                : (isSlave ? defs.BreedingWork_MaleSlave_Neg : defs.BreedingWork_MaleColonist_Neg);

            ThoughtDef conditionedMemory = isFemale
                ? defs.BreedingWork_BreedingConditionedFemale
                : defs.BreedingWork_BreedingConditionedMale;

            var memories = pawn.needs.mood.thoughts.memories;
            var allMemories = memories.Memories;

            var existingConditioning = allMemories.FirstOrDefault(m => m.def == conditionedMemory) as Thought_Memory;
            bool isConditioned = existingConditioning != null;

            int stageIndex = GetNegativeThoughtStageIndex(existingConditioning);

            Thought_Memory negThought = ThoughtMaker.MakeThought(negativeThought, stageIndex) as Thought_Memory;
            if (negThought != null)
                memories.TryGainMemory(negThought);

            ProcessConditioning(pawn, memories, allMemories, negativeThought, conditionedMemory, existingConditioning);
        }

        private static int GetNegativeThoughtStageIndex(Thought_Memory conditioning)
        {
            // Stages: 0 = negative, 1 = neutral, 2 = broken/positive
            if (conditioning == null)
                return 0;

            return conditioning.CurStageIndex == 1 ? 2 : 1;
        }

        private static void ProcessConditioning(
            Pawn pawn,
            MemoryThoughtHandler memories,
            List<Thought_Memory> allMemories,
            ThoughtDef negativeThought,
            ThoughtDef conditionedMemory,
            Thought_Memory existingConditioning)
        {
            int requiredStacks = Rand.RangeInclusive(15, 25);

            if (existingConditioning == null)
            {
                int totalNeg = allMemories.Count(m => m.def == negativeThought);
                if (totalNeg >= requiredStacks)
                {
                    Thought_Memory cond = ThoughtMaker.MakeThought(conditionedMemory, 0) as Thought_Memory;
                    if (cond != null)
                        memories.TryGainMemory(cond);
                }
            }
            else
            {
                int stage1Negs = allMemories
                    .OfType<Thought_Memory>()
                    .Count(m => m.def == negativeThought && m.CurStageIndex == 1);

                bool canUpgrade =
                    stage1Negs >= requiredStacks &&
                    existingConditioning.CurStageIndex == 0 &&
                    HasAnyBreedingPrecepts(Faction.OfPlayer?.ideos?.PrimaryIdeo);

                if (canUpgrade)
                {
                    existingConditioning.SetForcedStage(1);
                    existingConditioning.Renew();
                    memories.RemoveMemoriesOfDef(negativeThought);
                }
            }
        }
        public static bool IsConditionedLikes(Pawn pawn)
        {
            if (pawn == null || pawn.Dead || pawn.needs?.mood == null)
                return false;

            ThoughtDef conditionedMemory = pawn.gender == Gender.Female
                ? defs.BreedingWork_BreedingConditionedFemale
                : defs.BreedingWork_BreedingConditionedMale;

            var memory = pawn.needs.mood.thoughts.memories.GetFirstMemoryOfDef(conditionedMemory) as Thought_Memory;
            return memory != null && memory.CurStageIndex == 1;
        }
        public static bool CanBeMarkedForBreedingAnimal(Pawn animal)
        {
            if (!xxx.is_animal(animal))
                return false;

            if (animal.TryGetComp<CompForcedBreedingAnimal>() == null)
                return false;

            if (animal.Dead)
                return false;

            if (animal.Faction != Faction.OfPlayer)
                return false;

            if (!CasualSex_Helper.CanHaveSex(animal))
                return false;

            if (Genital_Helper.has_vagina(animal))
                return true;

            if (Genital_Helper.has_penis_fertile(animal))
                return true;

            return false;
        }
        public static bool IdeoOverridesManualDesignationAnimal(Ideo ideo)
        {
            if (ideo == null)
                return false;

            return ideo.HasPrecept(defs.ForcedBreed_AllAnimals) ||
                   ideo.HasPrecept(defs.ForcedBreed_PenAnimals) ||
                   ideo.HasPrecept(defs.ForcedBreed_NonPenAnimals) ||
                   ideo.HasPrecept(defs.ForcedBreed_Venerated) ||
                   ideo.HasPrecept(defs.ForcedBreed_NonVenerated) ||
                   ideo.HasPrecept(defs.ForcedBreed_NonBonded);
        }
        public static bool CanBeMarkedForBreedingHuman(Pawn pawn)
        {
            if (pawn == null || pawn.Dead || !pawn.Spawned)
                return false;

            if (!pawn.RaceProps.Humanlike || pawn.Faction != Faction.OfPlayer)
                return false;

            if (!CasualSex_Helper.CanHaveSex(pawn))
                return false;

            if (!Genital_Helper.has_vagina(pawn) && !Genital_Helper.has_penis_fertile(pawn))
                return false;

            return true;
        }
        public static bool IdeoOverridesManualDesignationHuman(Pawn pawn)
        {
            if (pawn == null)
                return false;

            Ideo ideo = Faction.OfPlayer?.ideos?.PrimaryIdeo;
            if (ideo == null)
                return false;

            bool isMale = Genital_Helper.has_penis_fertile(pawn);
            bool isFemale = Genital_Helper.has_vagina(pawn);
            bool isSlave = xxx.is_slave(pawn);
            bool isColonist = pawn.IsColonist;

            if (isMale && isSlave)
                return ideo.HasPrecept(defs.BreedingWorkMaleSlaves) || ideo.HasPrecept(defs.BreedingWorkMaleForcedSlaves);

            if (isFemale && isSlave)
                return ideo.HasPrecept(defs.BreedingWorkFemaleSlaves) || ideo.HasPrecept(defs.BreedingWorkFemaleForcedSlaves);

            if (isMale && isColonist)
                return ideo.HasPrecept(defs.BreedingWorkMaleColonists) || ideo.HasPrecept(defs.BreedingWorkMaleForcedColonists);

            if (isFemale && isColonist)
                return ideo.HasPrecept(defs.BreedingWorkFemaleColonists) || ideo.HasPrecept(defs.BreedingWorkFemaleForcedColonists);

            return false;
        }
        public static bool IsDesignatedForForcedBreeding(Pawn pawn)
        {
            return pawn.TryGetComp<CompForcedBreedingAnimal>()?.IsDesignated == true;
        }
        public static bool HasAnyBreedingPrecepts(Ideo ideo)
        {
            if (ideo == null) return false;

            return ideo.HasPrecept(defs.BreedingWorkMaleColonists) ||
                   ideo.HasPrecept(defs.BreedingWorkMaleSlaves) ||
                   ideo.HasPrecept(defs.BreedingWorkFemaleColonists) ||
                   ideo.HasPrecept(defs.BreedingWorkFemaleSlaves) ||
                   ideo.HasPrecept(defs.BreedingWorkMaleForcedColonists) ||
                   ideo.HasPrecept(defs.BreedingWorkMaleForcedSlaves) ||
                   ideo.HasPrecept(defs.BreedingWorkFemaleForcedColonists) ||
                   ideo.HasPrecept(defs.BreedingWorkFemaleForcedSlaves);
        }
    }
}


code:
using Verse;

namespace ElToro_BAddon
{
    public class CompProperties_ForcedBreedingAnimal : CompProperties
    {
        public CompProperties_ForcedBreedingAnimal()
        {
            compClass = typeof(CompForcedBreedingAnimal);
        }
    }

    public class CompForcedBreedingAnimal : ThingComp
    {
        private bool isDesignated;
        public bool IsDesignated
        {
            get => isDesignated;
            set
            {
                if (isDesignated == value)
                    return;

                isDesignated = value;

                if (parent is Pawn pawn && pawn.Map != null)
                {
                    var comp = pawn.Map.GetComponent<MapComponent_ForcedBreedingAnimalRegistry>();
                    if (isDesignated)
                        comp.Add(pawn);
                    else
                        comp.Remove(pawn);
                }
            }
        }

        public override void PostExposeData()
        {
            Scribe_Values.Look(ref isDesignated, "IsDesignated", false);

            if (Scribe.mode == LoadSaveMode.PostLoadInit && isDesignated && parent is Pawn pawn && pawn.Map != null)
            {
                var comp = pawn.Map.GetComponent<MapComponent_ForcedBreedingAnimalRegistry>();
                comp.Add(pawn);
            }
        }
    }
}


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

namespace ElToro_BAddon
{
    [StaticConstructorOnStartup]
    public static class InjectForcedBreedingAnimalComp
    {
        static InjectForcedBreedingAnimalComp()
        {
            foreach (ThingDef def in DefDatabase<ThingDef>.AllDefs)
            {
                if (def.race != null && def.race.Animal)
                {
                    if (def.comps == null)
                        def.comps = new List<CompProperties>();

                    bool alreadyExists = false;
                    foreach (CompProperties c in def.comps)
                    {
                        if (c is CompProperties_ForcedBreedingAnimal)
                        {
                            alreadyExists = true;
                            break;
                        }
                    }

                    if (!alreadyExists)
                        def.comps.Add(new CompProperties_ForcedBreedingAnimal());
                }
            }
        }
    }
}


code:
using Verse;
using RimWorld;
using rjw;
using System.Linq;

namespace ElToro_BAddon
{
    public class MapComponent_ForcedBreedingAnimal : MapComponent
    {
        public MapComponent_ForcedBreedingAnimal(Map map) : base(map) { }

        public int lastCheckTick = 0;
        public const int CheckInterval = 1800;

        public override void MapComponentTick()
        {
            if (!map.IsPlayerHome || map != Find.CurrentMap)
                return;

            if (Find.TickManager.TicksGame - lastCheckTick < CheckInterval)
                return;

            lastCheckTick = Find.TickManager.TicksGame;

            Ideo ideo = Faction.OfPlayer?.ideos?.PrimaryIdeo;
            if (ideo == null || !BreedingWorkHelper.IdeoOverridesManualDesignationAnimal(ideo))
                return;

            DesignateAnimalsIfNeeded(ideo);
        }

        public void DesignateAnimalsIfNeeded(Ideo ideo)
        {
            foreach (Pawn pawn in map.mapPawns.PawnsInFaction(Faction.OfPlayer).Where(xxx.is_animal))
            {
                if (!IsAutoDesignationMatch(pawn, ideo))
                    continue;

                var comp = pawn.TryGetComp<CompForcedBreedingAnimal>();
                if (comp != null && !comp.IsDesignated)
                    comp.IsDesignated = true;
            }
        }

        public bool IsAutoDesignationMatch(Pawn pawn, Ideo ideo)
        {
            if (ideo.HasPrecept(defs.ForcedBreed_AllAnimals))
                return true;

            if (ideo.HasPrecept(defs.ForcedBreed_PenAnimals) && AnimalPenUtility.NeedsToBeManagedByRope(pawn))
                return true;

            if (ideo.HasPrecept(defs.ForcedBreed_NonPenAnimals) && !AnimalPenUtility.NeedsToBeManagedByRope(pawn))
                return true;

            if (ideo.HasPrecept(defs.ForcedBreed_Venerated) && ideo.IsVeneratedAnimal(pawn))
                return true;

            if (ideo.HasPrecept(defs.ForcedBreed_NonVenerated) && !ideo.IsVeneratedAnimal(pawn))
                return true;

            if (ideo.HasPrecept(defs.ForcedBreed_NonBonded) && !xxx.HasBond(pawn))
                return true;

            return false;
        }
    }
}


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

namespace ElToro_BAddon
{
    public class MapComponent_ForcedBreedingAnimalRegistry : MapComponent
    {
        public HashSet<Pawn> designatedAnimals = new HashSet<Pawn>();

        public MapComponent_ForcedBreedingAnimalRegistry(Map map) : base(map) { }

        public void Add(Pawn pawn)
        {
            if (pawn != null)
                designatedAnimals.Add(pawn);
        }

        public void Remove(Pawn pawn)
        {
            designatedAnimals.Remove(pawn);
        }

        public bool IsDesignated(Pawn pawn)
        {
            return designatedAnimals.Contains(pawn);
        }

        public IEnumerable<Pawn> GetAll()
        {
            return designatedAnimals.Where(p => p.Spawned && !p.Dead);
        }
    }
}


code:
using RimWorld;
using UnityEngine;
using Verse;
using rjw;

namespace ElToro_BAddon
{
    [StaticConstructorOnStartup]
    public class PawnColumnWorker_ForcedBreedingAnimal : PawnColumnWorker_Checkbox
    {
        protected override bool GetValue(Pawn pawn)
        {
            return pawn.TryGetComp<CompForcedBreedingAnimal>()?.IsDesignated == true;
        }

        protected override void SetValue(Pawn pawn, bool value, PawnTable table)
        {
            var comp = pawn.TryGetComp<CompForcedBreedingAnimal>();
            if (comp != null)
                comp.IsDesignated = value;
        }

        protected override bool HasCheckbox(Pawn pawn)
        {
            if (!Settings.BreedingWork)
                return false;
            return true;
        }

        public override void DoCell(Rect rect, Pawn pawn, PawnTable table)
        {
            if (!HasCheckbox(pawn)) return;

            int xOffset = (int)((rect.width - 24f) / 2f);
            int yOffset = Mathf.Max(3, 0);
            Vector2 topLeft = new Vector2(rect.x + xOffset, rect.y + yOffset);
            Rect checkboxRect = new Rect(topLeft.x, topLeft.y, 24f, 24f);
            Ideo ideo = Faction.OfPlayer?.ideos?.PrimaryIdeo;

            bool disabled = !BreedingWorkHelper.CanBeMarkedForBreedingAnimal(pawn) || BreedingWorkHelper.IdeoOverridesManualDesignationAnimal(ideo);
            bool value = !disabled && GetValue(pawn);
            bool original = value;

            Widgets.Checkbox(topLeft, ref value, 24f, disabled);

            if (Mouse.IsOver(checkboxRect))
            {
                TooltipHandler.TipRegion(checkboxRect, GetTip(pawn));
            }

            if (value != original && !disabled)
            {
                SetValue(pawn, value, table);
            }
        }

        protected override string GetTip(Pawn pawn)
        {
            if (!xxx.is_animal(pawn))
                return "ForcedBreeding_Tip_NotAnimal".Translate();

            if (pawn.TryGetComp<CompForcedBreedingAnimal>() == null)
                return "ForcedBreeding_Tip_MissingComp".Translate();

            if (pawn.Dead)
                return "ForcedBreeding_Tip_Dead".Translate();

            if (pawn.Faction != Faction.OfPlayer)
                return "ForcedBreeding_Tip_NotColony".Translate();

            if (!CasualSex_Helper.CanHaveSex(pawn))
                return "ForcedBreeding_Tip_NotMature".Translate();

            if (!Genital_Helper.has_vagina(pawn) && !Genital_Helper.has_penis_fertile(pawn))
                return "ForcedBreeding_Tip_NoGenitals".Translate();

            Ideo ideo = Faction.OfPlayer?.ideos?.PrimaryIdeo;
            if (ideo != null && BreedingWorkHelper.IdeoOverridesManualDesignationAnimal(ideo))
                return "ForcedBreeding_Tip_Ideo".Translate();

            return "ForcedBreeding_Tip_CanDesignate".Translate();
        }

        public override int Compare(Pawn a, Pawn b)
        {
            bool aVal = a.TryGetComp<CompForcedBreedingAnimal>()?.IsDesignated == true;
            bool bVal = b.TryGetComp<CompForcedBreedingAnimal>()?.IsDesignated == true;
            return aVal.CompareTo(bVal);
        }
    }
}


code:
using Verse;

namespace ElToro_BAddon
{
    public class CompProperties_ForcedBreedingHuman : CompProperties
    {
        public CompProperties_ForcedBreedingHuman()
        {
            compClass = typeof(CompForcedBreedingHuman);
        }
    }

    public class CompForcedBreedingHuman : ThingComp
    {
        private bool isDesignated;
        public bool IsDesignated
        {
            get => isDesignated;
            set
            {
                if (isDesignated == value)
                    return;

                isDesignated = value;

                if (parent is Pawn pawn && pawn.Map != null)
                {
                    var comp = pawn.Map.GetComponent<MapComponent_ForcedBreedingHumanRegistry>();
                    if (isDesignated)
                        comp.Add(pawn);
                    else
                        comp.Remove(pawn);
                }
            }
        }

        public override void PostExposeData()
        {
            Scribe_Values.Look(ref isDesignated, "IsDesignated", false);

            if (Scribe.mode == LoadSaveMode.PostLoadInit && isDesignated && parent is Pawn pawn && pawn.Map != null)
            {
                var comp = pawn.Map.GetComponent<MapComponent_ForcedBreedingHumanRegistry>();
                comp.Add(pawn);
            }
        }
    }
}


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

namespace ElToro_BAddon
{
    [StaticConstructorOnStartup]
    public static class InjectForcedBreedingHumanComp
    {
        static InjectForcedBreedingHumanComp()
        {
            foreach (ThingDef def in DefDatabase<ThingDef>.AllDefs)
            {
                if (def.race != null && def.race.Humanlike)
                {
                    if (def.comps == null)
                        def.comps = new List<CompProperties>();

                    bool alreadyExists = false;
                    foreach (CompProperties c in def.comps)
                    {
                        if (c is CompProperties_ForcedBreedingHuman)
                        {
                            alreadyExists = true;
                            break;
                        }
                    }

                    if (!alreadyExists)
                        def.comps.Add(new CompProperties_ForcedBreedingHuman());
                }
            }
        }
    }
}


code:
using Verse;
using RimWorld;
using rjw;
using System.Linq;

namespace ElToro_BAddon
{
    public class MapComponent_ForcedBreedingHuman : MapComponent
    {
        public MapComponent_ForcedBreedingHuman(Map map) : base(map) { }

        public int lastCheckTick = 0;
        public const int CheckInterval = 1800;

        public override void MapComponentTick()
        {
            if (!map.IsPlayerHome || map != Find.CurrentMap)
                return;

            if (Find.TickManager.TicksGame - lastCheckTick < CheckInterval)
                return;

            lastCheckTick = Find.TickManager.TicksGame;

            Ideo ideo = Faction.OfPlayer?.ideos?.PrimaryIdeo;
            if (ideo == null)
                return;

            DesignateHumansIfNeeded(ideo);
        }

        public void DesignateHumansIfNeeded(Ideo ideo)
        {
            foreach (Pawn pawn in map.mapPawns.PawnsInFaction(Faction.OfPlayer).Where(p => p.RaceProps.Humanlike))
            {
                if (!IsAutoDesignationMatch(pawn, ideo))
                    continue;

                var comp = pawn.TryGetComp<CompForcedBreedingHuman>();
                if (comp != null && !comp.IsDesignated)
                    comp.IsDesignated = true;
            }
        }

        public bool IsAutoDesignationMatch(Pawn pawn, Ideo ideo)
        {
            if (!CasualSex_Helper.CanHaveSex(pawn))
                return false;

            bool isMale = Genital_Helper.has_penis_fertile(pawn);
            bool isFemale = Genital_Helper.has_vagina(pawn);
            bool isColonist = pawn.IsColonist;
            bool isSlave = pawn.IsSlave;

            if (isMale)
            {
                if (isColonist && ideo.HasPrecept(defs.BreedingWorkMaleColonists))
                    return true;

                if (isSlave && ideo.HasPrecept(defs.BreedingWorkMaleSlaves))
                    return true;
            }
            else if (isFemale && !pawn.IsPregnant())
            {
                if (isColonist && ideo.HasPrecept(defs.BreedingWorkFemaleColonists))
                    return true;

                if (isSlave && ideo.HasPrecept(defs.BreedingWorkFemaleSlaves))
                    return true;
            }

            return false;
        }
    }
}


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

namespace ElToro_BAddon
{
    public class MapComponent_ForcedBreedingHumanRegistry : MapComponent
    {
        public HashSet<Pawn> designatedHumans = new HashSet<Pawn>();

        public MapComponent_ForcedBreedingHumanRegistry(Map map) : base(map) { }

        public void Add(Pawn pawn)
        {
            if (pawn != null)
                designatedHumans.Add(pawn);
        }

        public void Remove(Pawn pawn)
        {
            designatedHumans.Remove(pawn);
        }

        public bool IsDesignated(Pawn pawn)
        {
            return designatedHumans.Contains(pawn);
        }

        public IEnumerable<Pawn> GetAll()
        {
            return designatedHumans.Where(p => p.Spawned && !p.Dead);
        }
    }
}


code:
using RimWorld;
using UnityEngine;
using Verse;
using rjw;

namespace ElToro_BAddon
{
    [StaticConstructorOnStartup]
    public class PawnColumnWorker_ForcedBreedingHuman : PawnColumnWorker_Checkbox
    {
        protected override bool GetValue(Pawn pawn)
        {
            return pawn.TryGetComp<CompForcedBreedingHuman>()?.IsDesignated == true;
        }

        protected override void SetValue(Pawn pawn, bool value, PawnTable table)
        {
            var comp = pawn.TryGetComp<CompForcedBreedingHuman>();
            if (comp != null)
                comp.IsDesignated = value;
        }

        protected override bool HasCheckbox(Pawn pawn)
        {
            if (!Settings.BreedingWork)
                return false;

            return true;
        }

        public override void DoCell(Rect rect, Pawn pawn, PawnTable table)
        {
            if (!HasCheckbox(pawn)) return;

            int xOffset = (int)((rect.width - 24f) / 2f);
            int yOffset = Mathf.Max(3, 0);
            Vector2 topLeft = new Vector2(rect.x + xOffset, rect.y + yOffset);
            Rect checkboxRect = new Rect(topLeft.x, topLeft.y, 24f, 24f);

            bool disabled = !BreedingWorkHelper.CanBeMarkedForBreedingHuman(pawn) || BreedingWorkHelper.IdeoOverridesManualDesignationHuman(pawn);
            bool value = !disabled && GetValue(pawn);
            bool original = value;

            Widgets.Checkbox(topLeft, ref value, 24f, disabled);

            if (Mouse.IsOver(checkboxRect))
                TooltipHandler.TipRegion(checkboxRect, GetTip(pawn));

            if (value != original && !disabled)
                SetValue(pawn, value, table);
        }

        protected override string GetTip(Pawn pawn)
        {
            if (!pawn.RaceProps.Humanlike)
                return "ForcedBreeding_Tip_NotHuman".Translate();

            if (pawn.TryGetComp<CompForcedBreedingHuman>() == null)
                return "ForcedBreeding_Tip_MissingComp".Translate();

            if (pawn.Dead)
                return "ForcedBreeding_Tip_Dead".Translate();

            if (pawn.Faction != Faction.OfPlayer)
                return "ForcedBreeding_Tip_NotColony".Translate();

            if (!CasualSex_Helper.CanHaveSex(pawn))
                return "ForcedBreeding_Tip_NotMature".Translate();

            if (!Genital_Helper.has_vagina(pawn) && !Genital_Helper.has_penis_fertile(pawn))
                return "ForcedBreeding_Tip_NoGenitals".Translate();

            if (BreedingWorkHelper.IdeoOverridesManualDesignationHuman(pawn))
                return "ForcedBreeding_Tip_Ideo".Translate();

            return "ForcedBreeding_Tip_CanDesignate".Translate();
        }

        public override int Compare(Pawn a, Pawn b)
        {
            bool aVal = a.TryGetComp<CompForcedBreedingHuman>()?.IsDesignated == true;
            bool bVal = b.TryGetComp<CompForcedBreedingHuman>()?.IsDesignated == true;
            return aVal.CompareTo(bVal);
        }
    }
}


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

namespace ElToro_BAddon
{
    public class JobDriver_AnimalBreedInvite : JobDriver
    {
        public override bool TryMakePreToilReservations(bool errorOnFailed)
        {
            return true;
        }

        protected override IEnumerable<Toil> MakeNewToils()
        {
            this.FailOnDespawnedOrNull(TargetIndex.A);
            this.FailOn(() => !pawn.CanReach(TargetA, PathEndMode.Touch, Danger.Some));
            this.FailOn(() => pawn.Drafted);
            this.FailOn(() => TargetA.Pawn.Drafted);
            Pawn partner = TargetA.Pawn;

            var WaitForInteraction = new Toil();
            WaitForInteraction.FailOn(() => partner.CurJob != null && partner.jobs.curDriver != null && (partner.jobs.curDriver is JobDriver_SexBaseInitiator || partner.jobs.curDriver is JobDriver_SexBaseReciever));
            WaitForInteraction.defaultCompleteMode = ToilCompleteMode.Delay;
            WaitForInteraction.defaultDuration = 130; // Should be 121, but i don't like such values =)
            WaitForInteraction.socialMode = RandomSocialMode.Off;

            WaitForInteraction.tickAction = delegate
            {
                if (pawn.interactions.CanInteractNowWith(partner))
                {
                    ReadyForNextToil();
                }

            };
            yield return WaitForInteraction;

            // Go to the human
            yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.ClosestTouch);

            // Try to interact
            var interactToil = new Toil();
            interactToil.initAction = () =>
            {
                InteractionDef interaction = null;
                if (pawn.gender == Gender.Female)
                {
                    interaction = defs.BreedingWorkInviteFemale;

                }

                else
                {
                    interaction = defs.BreedingWorkInviteMale;
                }
                pawn.interactions?.TryInteractWith(partner, interaction);
            };
            interactToil.defaultCompleteMode = ToilCompleteMode.Instant;
            yield return interactToil;

            var WaitForInteraction2 = new Toil();
            WaitForInteraction2.defaultCompleteMode = ToilCompleteMode.Delay;
            WaitForInteraction2.defaultDuration = 130; // Should be 121, but i don't like such values =)
            WaitForInteraction2.socialMode = RandomSocialMode.Off;

            WaitForInteraction2.tickAction = delegate
            {
                if (pawn.interactions.CanInteractNowWith(partner))
                {
                    ReadyForNextToil();
                }

            };
            yield return WaitForInteraction2;

            var sexToil = new Toil();
            sexToil.defaultCompleteMode = ToilCompleteMode.Delay;
            sexToil.defaultDuration = 1000;
            sexToil.socialMode = RandomSocialMode.Off;
            sexToil.initAction = () =>
            {
                Job SexJob = JobMaker.MakeJob(defs.BreedingWorkSexAnimalInitiate, pawn);
                partner.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
                partner?.jobs.StopAll();
                partner.pather?.StopDead();

                InteractionDef interaction = null;
                if (pawn.gender == Gender.Female)
                {
                    interaction = defs.BreedingWorkAnimalResponseFemale;

                }

                else
                {
                    interaction = defs.BreedingWorkAnimalResponseMale;
                }

                partner.interactions?.TryInteractWith(pawn, interaction);

                partner.jobs.StartJob(SexJob, lastJobEndCondition: JobCondition.InterruptForced);
            };
            yield return sexToil;
        }
    }
}


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

namespace ElToro_BAddon
{
    public class JobDriver_AnimalBreedInviteForced : JobDriver
    {
        public override bool TryMakePreToilReservations(bool errorOnFailed)
        {
            return true;
        }

        protected override IEnumerable<Toil> MakeNewToils()
        {
            this.FailOnDespawnedOrNull(TargetIndex.A);
            this.FailOn(() => !pawn.CanReach(TargetA, PathEndMode.Touch, Danger.Some));
            this.FailOn(() => pawn.Drafted);
            this.FailOn(() => TargetA.Pawn.Drafted);
            Pawn partner = TargetA.Pawn;

            var WaitForInteraction = new Toil();
            WaitForInteraction.FailOn(() => partner.CurJob != null && partner.jobs.curDriver != null && (partner.jobs.curDriver is JobDriver_SexBaseInitiator || partner.jobs.curDriver is JobDriver_SexBaseReciever));
            WaitForInteraction.defaultCompleteMode = ToilCompleteMode.Delay;
            WaitForInteraction.defaultDuration = 130; // Should be 121, but i don't like such values =)
            WaitForInteraction.socialMode = RandomSocialMode.Off;

            WaitForInteraction.tickAction = delegate
            {
                if (pawn.interactions.CanInteractNowWith(partner))
                {
                    ReadyForNextToil();
                }

            };
            yield return WaitForInteraction;

            // Go to the human
            yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.ClosestTouch);

            // Try to interact
            var interactToil = new Toil();
            interactToil.initAction = () =>
            {
                InteractionDef interaction = null;
                if (pawn.gender == Gender.Female)
                {
                    interaction = defs.BreedingWorkInviteForcedFemale;

                }

                else
                {
                    interaction = defs.BreedingWorkInviteForcedMale;
                }
                pawn.interactions?.TryInteractWith(partner, interaction);
            };
            interactToil.defaultCompleteMode = ToilCompleteMode.Instant;
            yield return interactToil;

            var WaitForInteraction2 = new Toil();
            WaitForInteraction2.defaultCompleteMode = ToilCompleteMode.Delay;
            WaitForInteraction2.defaultDuration = 130; // Should be 121, but i don't like such values =)
            WaitForInteraction2.socialMode = RandomSocialMode.Off;

            WaitForInteraction2.tickAction = delegate
            {
                if (pawn.interactions.CanInteractNowWith(partner))
                {
                    ReadyForNextToil();
                }

            };
            yield return WaitForInteraction2;

            var sexToil = new Toil();
            sexToil.defaultCompleteMode = ToilCompleteMode.Delay;
            sexToil.defaultDuration = 1000;
            sexToil.socialMode = RandomSocialMode.Off;
            sexToil.initAction = () =>
            {
                Job SexJob = JobMaker.MakeJob(defs.BreedingWorkRapeAnimalInitiate, pawn);

                partner.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
                partner?.jobs.StopAll();
                partner.pather?.StopDead();

                InteractionDef interaction = null;
                if (pawn.gender == Gender.Female)
                {
                    interaction = defs.BreedingWorkAnimalResponseFemale;

                }

                else
                {
                    interaction = defs.BreedingWorkAnimalResponseMale;
                }

                partner.interactions?.TryInteractWith(pawn, interaction);
                partner.jobs.StartJob(SexJob, lastJobEndCondition: JobCondition.InterruptForced);
            };
            yield return sexToil;
        }
    }
}


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

namespace ElToro_BAddon
{
	/// <summary>
	/// This is the driver for animals mounting breeders.
	/// </summary>
	public class JobDriver_BreedingWorkRapeAnimalInitiate : JobDriver_Rape
	{
		public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
			return true;
        }

		protected override IEnumerable<Toil> MakeNewToils()
		{
			setup_ticks();
			var PartnerJob = defs.ForcedBreedingReciever;
            var comp = pawn.TryGetComp<CompAnimalSexMemory>();

            this.FailOnDespawnedNullOrForbidden(iTarget);
			this.FailOn(() => !pawn.CanReach(Partner, PathEndMode.Touch, Danger.Some));
            this.FailOn(() => pawn.Drafted);
            this.FailOn(() => Partner.Drafted);

            yield return Toils_Goto.GotoThing(iTarget, PathEndMode.OnCell);

            var StartPartnerJob = new Toil();
			StartPartnerJob.defaultCompleteMode = ToilCompleteMode.Instant;
			StartPartnerJob.socialMode = RandomSocialMode.Off;
			StartPartnerJob.initAction = delegate
			{
				if (Partner.jobs.curDriver is JobDriver_SexBaseRecieverRaped) return;

                Partner.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
                Partner?.jobs.StopAll();
                Partner.pather?.StopDead();

                if (!pawn.Reserve(Partner, job, ignoreOtherReservations: true))
                {
                    pawn.jobs.EndCurrentJob(JobCondition.Incompletable);
                    return;
                }

                Partner.jobs.StartJob(
					JobMaker.MakeJob(PartnerJob, pawn),
					lastJobEndCondition: JobCondition.InterruptForced
				);

			};
			yield return StartPartnerJob;

			// Use happy heart instead of bad-touch heart when accepting of breeding.
			var fleckDef = xxx.mote_noheart;

			// Breed target
			var SexToil = new Toil();
			SexToil.defaultCompleteMode = ToilCompleteMode.Never;
			SexToil.defaultDuration = duration;
			SexToil.handlingFacing = true;
			SexToil.initAction = delegate
			{
				Partner.pather.StopDead();
				Partner.jobs.curDriver.asleep = false;
				Start();
            };
			SexToil.tickAction = delegate
			{
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
					ThrowMetaIconF(pawn.Position, pawn.Map, fleckDef);
				SexTick(pawn, Partner);
				if (!Partner.Dead)
					SexUtility.reduce_rest(Partner, 1);
				SexUtility.reduce_rest(pawn, 2);
				if (ticks_left <= 0)
					ReadyForNextToil();
			};
			SexToil.FailOn(() => Partner.CurJob?.def != PartnerJob);
			SexToil.AddFinishAction(End);
			yield return SexToil;

			yield return new Toil
			{
				initAction = delegate
				{
					//Log.Message("JobDriver_Breeding::MakeNewToils() - Calling aftersex");
					//// Trying to add some interactions and social logs
					Sexprops.isRape = true;
					SexUtility.ProcessSex(Sexprops);
                    BreedingWorkHelper.ApplyBreedingThought(Partner, true);

                    if (comp != null && comp.HasTrait(SexTrait.Vigorous))
                    {
                        MatingHelper.ShortenLovinCooldown(pawn);
                    }
                },
				defaultCompleteMode = ToilCompleteMode.Instant
			};
		}
	}
}


code:
using RimWorld;
using rjw;
using rjw.Modules.Attraction;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using UnityEngine;
using Verse;
using Verse.AI;

namespace ElToro_BAddon
{
	public class JobDriver_BreedingWorkSexAnimalInitiate : JobDriver_SexBaseInitiator
	{
        public override bool TryMakePreToilReservations(bool errorOnFailed)
		{
            return true;
		}

        protected override IEnumerable<Toil> MakeNewToils()
        {
            setup_ticks();
            var PartnerJob = defs.SexRecieverAnimalSex;
            var comp = pawn.TryGetComp<CompAnimalSexMemory>();

            this.FailOnDespawnedOrNull(iTarget);
            this.FailOn(() => !pawn.CanReach(Partner, PathEndMode.Touch, Danger.Some));
            this.FailOn(() => pawn.Drafted);
            this.FailOn(() => Partner.Drafted);
            this.FailOn(() => pawn.IsFighting());
            this.FailOn(() => Partner.IsFighting());

            yield return Toils_Goto.GotoThing(iTarget, PathEndMode.Touch);

            var startPartnerJob = new Toil();
            startPartnerJob.defaultCompleteMode = ToilCompleteMode.Instant;
            startPartnerJob.socialMode = RandomSocialMode.Off;
            startPartnerJob.initAction = () =>
            {
                Partner.pather?.StopDead();
                Partner.jobs.StartJob(JobMaker.MakeJob(PartnerJob, pawn), JobCondition.InterruptForced);
            };
            yield return startPartnerJob;

            var sexToil = new Toil();
            sexToil.defaultCompleteMode = ToilCompleteMode.Never;
            sexToil.defaultDuration = duration;
            sexToil.handlingFacing = true;
            sexToil.initAction = () =>
            {
                Partner.pather?.StopDead();
                Partner.jobs.curDriver.asleep = false;
                Start();
            };
            sexToil.tickAction = () =>
            {
                if (pawn.IsHashIntervalTick(ticks_between_hearts))
                    ThrowMetaIconF(pawn.Position, pawn.Map, FleckDefOf.Heart);
                SexTick(pawn, Partner);
                if (!Partner.Dead)
                    SexUtility.reduce_rest(Partner, 1);
                SexUtility.reduce_rest(pawn, 2);
                if (ticks_left <= 0)
                    ReadyForNextToil();
            };
            sexToil.FailOn(() => Partner.CurJob == null || Partner.CurJob.def != PartnerJob);
            sexToil.AddFinishAction(End);
            yield return sexToil;

            yield return new Toil
            {
                initAction = () =>
                {
                    SexUtility.ProcessSex(Sexprops);
                    if (AttractionUtility.Evaluate(Partner, pawn, AttractionPurpose.ForFucking) < 0.1f)
                    {
                        BreedingWorkHelper.ApplyBreedingThought(Partner, true);
                    }
                    else
                    {
                        BreedingWorkHelper.ApplyBreedingThought(Partner, false);
                    }

                    if (comp != null && comp.HasTrait(SexTrait.Vigorous))
                    {
                        MatingHelper.ShortenLovinCooldown(pawn);
                    }
                },
                defaultCompleteMode = ToilCompleteMode.Instant
            };
        }
    }
}


code:
using ElToro_BAddon;
using rjw;
using rjw.Modules.Interactions.Preferences;
using System.Collections.Generic;
using Verse;

public class BreedingWorkPawnPreferences : PawnPreferences
{
    public override bool IsActive(Pawn pawn, Pawn partner, SexProps props)
    {
        if (xxx.is_animal(pawn) && xxx.is_human(partner) && pawn.CurJobDef == defs.BreedingWorkRapeAnimalInitiate)
        {
            return true;
        }
        else return false;
    }

    public override IEnumerable<SexPreference> GetPreferences(Pawn pawn, Pawn partner, SexProps props)
    {
        yield return new TypePreference(xxx.rjwSextype.Vaginal, Preference.Like);
    }
}