///<summary>For checking normal pregnancy, should not for egg implantion or such.</summary>
        public static bool CanImpregnate(Pawn f****r, Pawn f****d, xxx.rjwSextype sextype = xxx.rjwSextype.Vaginal)
        {
            if (f****r == null || f****d == null)
            {
                return(false);
            }

            if (RJWSettings.DevMode)
            {
                Log.Message("Rimjobworld::CanImpregnate checks (" + sextype + "):: " + xxx.get_pawnname(f****r) + " + " + xxx.get_pawnname(f****d) + ":");
            }

            if (sextype == xxx.rjwSextype.MechImplant && !RJWPregnancySettings.mechanoid_pregnancy_enabled)
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(" mechanoid 'pregnancy' disabled");
                }
                return(false);
            }
            if (!(sextype == xxx.rjwSextype.Vaginal || sextype == xxx.rjwSextype.DoublePenetration))
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(" sextype cannot result in pregnancy");
                }
                return(false);
            }

            if (AndroidsCompatibility.IsAndroid(f****r) && AndroidsCompatibility.IsAndroid(f****d))
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(xxx.get_pawnname(f****d) + " androids cant breed/reproduce androids");
                }
                return(false);
            }

            if ((f****r.IsUnsexyRobot() || f****d.IsUnsexyRobot()) && !(sextype == xxx.rjwSextype.MechImplant))
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(" unsexy robot cant be pregnant");
                }
                return(false);
            }

            if (!f****r.RaceHasPregnancy())
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(xxx.get_pawnname(f****d) + " filtered race that cant be pregnant");
                }
                return(false);
            }

            if (!f****d.RaceHasPregnancy())
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(xxx.get_pawnname(f****r) + " filtered race that cant impregnate");
                }
                return(false);
            }


            if (f****d.IsPregnant())
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(" already pregnant.");
                }
                return(false);
            }

            if ((from x in f****d.health.hediffSet.GetHediffs <Hediff_InsectEgg>() where x.def == DefDatabase <HediffDef_InsectEgg> .GetNamed(x.def.defName) select x).Any())
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(xxx.get_pawnname(f****d) + " cant get pregnant while eggs inside");
                }
                return(false);
            }

            var pawnpartBPR    = Genital_Helper.get_genitalsBPR(f****r);
            var pawnparts      = Genital_Helper.get_PartsHediffList(f****r, pawnpartBPR);
            var partnerpartBPR = Genital_Helper.get_genitalsBPR(f****d);
            var partnerparts   = Genital_Helper.get_PartsHediffList(f****d, partnerpartBPR);

            if (!(Genital_Helper.has_penis_fertile(f****r, pawnparts) && Genital_Helper.has_vagina(f****d, partnerparts)) && !(Genital_Helper.has_penis_fertile(f****d, partnerparts) && Genital_Helper.has_vagina(f****r, pawnparts)))
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(" missing genitals for impregnation");
                }
                return(false);
            }

            if (f****r.health.capacities.GetLevel(xxx.reproduction) <= 0 || f****d.health.capacities.GetLevel(xxx.reproduction) <= 0)
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(" one (or both) pawn(s) infertile");
                }
                return(false);
            }

            if (xxx.is_human(f****d) && xxx.is_human(f****r) && (RJWPregnancySettings.humanlike_impregnation_chance == 0 || !RJWPregnancySettings.humanlike_pregnancy_enabled))
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(" human pregnancy chance set to 0% or pregnancy disabled.");
                }
                return(false);
            }
            else if (((xxx.is_animal(f****r) && xxx.is_human(f****d)) || (xxx.is_human(f****r) && xxx.is_animal(f****d))) && !RJWPregnancySettings.bestial_pregnancy_enabled)
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(" bestiality pregnancy chance set to 0% or pregnancy disabled.");
                }
                return(false);
            }
            else if (xxx.is_animal(f****d) && xxx.is_animal(f****r) && (RJWPregnancySettings.animal_impregnation_chance == 0 || !RJWPregnancySettings.animal_pregnancy_enabled))
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(" animal-animal pregnancy chance set to 0% or pregnancy disabled.");
                }
                return(false);
            }
            else if (f****r.def.defName != f****d.def.defName && (RJWPregnancySettings.interspecies_impregnation_modifier <= 0.0f && !RJWPregnancySettings.complex_interspecies))
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(" interspecies pregnancy disabled.");
                }
                return(false);
            }

            return(true);
        }
        public static bool CheckPreference(Pawn pawn, Pawn partner)
        {
            if (RJWPreferenceSettings.sexuality_distribution == RJWPreferenceSettings.Rjw_sexuality.Vanilla)
            {
                if (xxx.has_traits(pawn))
                {
                    VanillaTraitCheck(pawn);
                }
                if (xxx.has_traits(partner))
                {
                    VanillaTraitCheck(partner);
                }
            }
            if (RJWPreferenceSettings.sexuality_distribution == RJWPreferenceSettings.Rjw_sexuality.SYRIndividuality)
            {
                CopyIndividualitySexuality(pawn);
                CopyIndividualitySexuality(partner);
            }
            if (RJWPreferenceSettings.sexuality_distribution == RJWPreferenceSettings.Rjw_sexuality.Psychology)
            {
                if (RJWPreferenceSettings.sexuality_distribution == RJWPreferenceSettings.Rjw_sexuality.Psychology)
                {
                    CopyPsychologySexuality(pawn);
                }
                CopyPsychologySexuality(partner);
            }

            //if (xxx.is_mechanoid(pawn))
            //	return false;

            Orientation ori = Orientation.None;

            try
            {
                ori = Comp(pawn).orientation;
            }
            catch
            {
                //Log.Message("[RJW]Error, pawn:" + pawn + " doesnt have orientation comp, modded race?");
                return(false);
            }

            if (ori == Orientation.Pansexual || ori == Orientation.Bisexual)
            {
                return(true);
            }

            if (ori == Orientation.Asexual)
            {
                return(false);
            }

            var pawnpartBPR              = Genital_Helper.get_genitalsBPR(pawn);
            var pawnparts                = Genital_Helper.get_PartsHediffList(pawn, pawnpartBPR);
            var pawn_has_vagina          = Genital_Helper.has_vagina(pawn, pawnparts);
            var pawn_has_penis_fertile   = Genital_Helper.has_penis_fertile(pawn, pawnparts);
            var pawn_has_penis_infertile = Genital_Helper.has_penis_infertile(pawn, pawnparts);

            var partnerpartBPR              = Genital_Helper.get_genitalsBPR(partner);
            var partnerparts                = Genital_Helper.get_PartsHediffList(partner, partnerpartBPR);
            var partner_has_vagina          = Genital_Helper.has_vagina(partner, partnerparts);
            var partner_has_penis_fertile   = Genital_Helper.has_penis_fertile(partner, partnerparts);
            var partner_has_penis_infertile = Genital_Helper.has_penis_infertile(partner, partnerparts);


            bool isHetero = (pawn_has_vagina && (partner_has_penis_fertile || partner_has_penis_infertile)) ||
                            (partner_has_vagina && (pawn_has_penis_fertile || pawn_has_penis_infertile));

            bool isHomo = (pawn_has_vagina && partner_has_vagina) ||
                          ((partner_has_penis_fertile || partner_has_penis_infertile) && (pawn_has_penis_fertile || pawn_has_penis_infertile));

            if (isHetero && isHomo)
            {
                // Oh you crazy futas.  We could probably do a check against the pawn's gender, but eh.  They've got so many parts available, they'll find something to do.
                return(true);
            }

            //Rand.PopState();
            //Rand.PushState(RJW_Multiplayer.PredictableSeed());
            switch (ori)
            {
            case Orientation.Heterosexual:
                return(!isHomo);

            case Orientation.MostlyHeterosexual:
                return(!isHomo || Rand.Chance(0.2f));

            case Orientation.LeaningHeterosexual:
                return(!isHomo || Rand.Chance(0.6f));

            case Orientation.LeaningHomosexual:
                return(!isHetero || Rand.Chance(0.6f));

            case Orientation.MostlyHomosexual:
                return(!isHetero || Rand.Chance(0.2f));

            case Orientation.Homosexual:
                return(!isHetero);

            default:
                Log.Error("RJW::ERROR - tried to check preference for undetermined sexuality.");
                return(false);
            }
        }
Beispiel #3
0
        public static Pawn find_breeder_animal(Pawn pawn, Map m)
        {
            DebugText("BreederHelper::find_breeder_animal( " + xxx.get_pawnname(pawn) + " ) called");

            float min_fuckability = 0.10f;                                      // Don't rape pawns with <10% fuckability
            float avg_fuckability = 0f;                                         // Average targets fuckability, choose target higher than that
            var   valid_targets   = new Dictionary <Pawn, float>();             // Valid pawns and their fuckability
            Pawn  chosentarget    = null;                                       // Final target pawn

            //Pruning initial pawn list.
            IEnumerable <Pawn> targets = m.mapPawns.AllPawnsSpawned.Where(x
                                                                          => x != pawn &&
                                                                          xxx.is_animal(x) &&
                                                                          xxx.can_get_raped(x) &&
                                                                          !x.IsForbidden(pawn) &&
                                                                          !x.Suspended &&
                                                                          !x.HostileTo(pawn) &&
                                                                          pawn.CanReserveAndReach(x, PathEndMode.Touch, Danger.Some, max_animals_at_once)
                                                                          //&& SameRace ? pawn.kindDef.race == x.kindDef.race : true
                                                                          );

            if (targets.Any())
            {
                var partBPR = Genital_Helper.get_genitalsBPR(pawn);
                var parts   = Genital_Helper.get_PartsHediffList(pawn, partBPR);

                //filter pawns for female, who can f**k her
                //not sure if faction check should be but w/e
                if (!Genital_Helper.has_penis_fertile(pawn, parts) && !Genital_Helper.has_penis_infertile(pawn, parts) && (Genital_Helper.has_vagina(pawn, parts) || Genital_Helper.has_anus(pawn)))
                {
                    targets = targets.Where(x => xxx.can_fuck(x) && x.Faction == pawn.Faction);
                }

                //for humans, animals dont have need - always = 3f
                //if not horny, seek only targets with safe temp
                if (xxx.need_some_sex(pawn) < 3.0f)
                {
                    targets = targets.Where(x => pawn.CanReach(x, PathEndMode.Touch, Danger.None));
                }

                //Used for interspecies animal-on-animal.
                //Animals will only go for targets they can see.
                if (xxx.is_animal(pawn))
                {
                    targets = targets.Where(x => pawn.CanSee(x) && pawn.def.defName != x.def.defName);
                }
                else
                {
                    // Pickier about the targets if the pawn has no prior experience.
                    if (pawn.records.GetValue(xxx.CountOfSexWithAnimals) < 3 && !xxx.is_zoophile(pawn))
                    {
                        min_fuckability *= 2f;
                    }

                    if (xxx.is_frustrated(pawn))
                    {                       // Less picky when frustrated...
                        min_fuckability *= 0.6f;
                    }
                    else if (!xxx.is_hornyorfrustrated(pawn))
                    {                       // ...and far more picky when satisfied.
                        min_fuckability *= 2.5f;
                    }
                }
            }

            DebugText("[RJW]BreederHelper::find_breeder_animal::" + targets.Count() + " targets found on map.");

            if (!targets.Any())
            {
                return(null);                //None found.
            }

            foreach (Pawn target in targets)
            {
                DebugText("[RJW]BreederHelper::find_breeder_animal::Checking target " + target.kindDef.race.defName.ToLower());

                if (!xxx.can_path_to_target(pawn, target.Position))
                {
                    continue;                                             // too far
                }
                float fuc = SexAppraiser.would_fuck_animal(pawn, target); // 0.0 to ~3.0, orientation checks etc.

                if (!(fuc > min_fuckability))
                {
                    continue;
                }
                DebugText("Adding target" + target.kindDef.race.defName.ToLower());
                valid_targets.Add(target, fuc);
            }

            DebugText(valid_targets.Count() + " valid targets found on map.");
            //Rand.PopState();
            //Rand.PushState(RJW_Multiplayer.PredictableSeed());
            if (valid_targets.Any())
            {
                avg_fuckability = valid_targets.Average(x => x.Value);

                // choose pawns to f**k with above average fuckability
                var valid_targetsFilteredAnimals = valid_targets.Where(x => x.Value >= avg_fuckability);

                if (valid_targetsFilteredAnimals.Any())
                {
                    chosentarget = valid_targetsFilteredAnimals.RandomElement().Key;
                }
            }

            return(chosentarget);
        }
        //called by aftersex (including rape, breed, etc)
        //called by mcevent

        //pawn - "father"; partner = mother
        public static void impregnate(Pawn pawn, Pawn partner, xxx.rjwSextype sextype = xxx.rjwSextype.None)
        {
            if (RJWSettings.DevMode)
            {
                Log.Message("Rimjobworld::impregnate(" + sextype + "):: " + xxx.get_pawnname(pawn) + " + " + xxx.get_pawnname(partner) + ":");
            }

            //"mech" pregnancy
            if (sextype == xxx.rjwSextype.MechImplant)
            {
                if (RJWPregnancySettings.mechanoid_pregnancy_enabled)
                {
                    if (RJWSettings.DevMode)
                    {
                        Log.Message(" mechanoid pregnancy");
                    }

                    // removing old pregnancies
                    if (RJWSettings.DevMode)
                    {
                        Log.Message("[RJW] removing other pregnancies");
                    }
                    var preg = GetPregnancy(partner);
                    if (preg != null)
                    {
                        if (preg is Hediff_BasePregnancy)
                        {
                            (preg as Hediff_BasePregnancy).Kill();
                        }
                        else
                        {
                            partner.health.RemoveHediff(preg);
                        }
                    }

                    // new pregnancy
                    Hediff_MechanoidPregnancy hediff = Hediff_BasePregnancy.Create <Hediff_MechanoidPregnancy>(partner, pawn);

                    return;

                    /*
                     * // Not an actual pregnancy. This implants mechanoid tech into the target.
                     * //may lead to pregnancy
                     * //old "chip pregnancies", maybe integrate them somehow?
                     * //Rand.PopState();
                     * //Rand.PushState(RJW_Multiplayer.PredictableSeed());
                     * HediffDef_MechImplants egg = (from x in DefDatabase<HediffDef_MechImplants>.AllDefs	select x).RandomElement();
                     * if (egg != null)
                     * {
                     *      if (RJWSettings.DevMode) Log.Message(" planting MechImplants:" + egg.ToString());
                     *      PlantSomething(egg, partner, !Genital_Helper.has_vagina(partner), 1);
                     *      return;
                     * }
                     * else
                     * {
                     *      if (RJWSettings.DevMode) Log.Message(" no mech implant found");
                     * }*/
                }
                return;
            }

            // Sextype can result in pregnancy.
            if (!(sextype == xxx.rjwSextype.Vaginal || sextype == xxx.rjwSextype.DoublePenetration))
            {
                return;
            }

            //Log.Message("[RJW] RaceImplantEggs()" + pawn.RaceImplantEggs());
            //"insect" pregnancy
            //straight, female (partner) recives egg insertion from other/sex starter (pawn)
            var pawnpartBPR    = Genital_Helper.get_genitalsBPR(pawn);
            var pawnparts      = Genital_Helper.get_PartsHediffList(pawn, pawnpartBPR);
            var partnerpartBPR = Genital_Helper.get_genitalsBPR(partner);
            var partnerparts   = Genital_Helper.get_PartsHediffList(partner, partnerpartBPR);

            if (Genital_Helper.has_vagina(partner, partnerparts) &&
                (Genital_Helper.has_ovipositorF(pawn, pawnparts) ||
                 (Genital_Helper.has_ovipositorM(pawn, pawnparts) ||
                  (Genital_Helper.has_penis_fertile(pawn, pawnparts) && pawn.RaceImplantEggs())))
                )
            {
                DoEgg(pawn, partner);
                return;
            }
            //reverse, female (pawn) starts sex/passive bestiality and fills herself with eggs - this is likely f****d up and needs fixing at jobdriver, processsex and aftersex levels
            else
            if (Genital_Helper.has_vagina(pawn, pawnparts) &&
                (Genital_Helper.has_ovipositorF(partner, partnerparts) ||
                 (Genital_Helper.has_ovipositorM(partner, partnerparts) ||
                  (Genital_Helper.has_penis_fertile(partner, partnerparts) && pawn.RaceImplantEggs())))
                )
            {
                DoEgg(partner, pawn);
                return;
            }

            //"normal" and "beastial" pregnancy
            if (RJWSettings.DevMode)
            {
                Log.Message(" 'normal' pregnancy checks");
            }

            //futa-futa docking?
            //if (CanImpregnate(partner, pawn, sextype) && CanImpregnate(pawn, partner, sextype))
            //{
            //Log.Message("[RJW] futa-futa docking...");
            //return;
            //Doimpregnate(pawn, partner);
            //Doimpregnate(partner, pawn);
            //}
            //normal, when female is passive/recives interaction
            if (Genital_Helper.has_penis_fertile(pawn, pawnparts) && Genital_Helper.has_vagina(partner, partnerparts) && CanImpregnate(pawn, partner, sextype))
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(" impregnate forward");
                }
                Doimpregnate(pawn, partner);
            }
            //reverse, when female active/starts interaction
            else if (Genital_Helper.has_vagina(pawn, pawnparts) && Genital_Helper.has_penis_fertile(partner, partnerparts) && CanImpregnate(partner, pawn, sextype))
            {
                if (RJWSettings.DevMode)
                {
                    Log.Message(" impregnate reverse");
                }
                Doimpregnate(partner, pawn);
            }
        }
Beispiel #5
0
        public static Pawn find_designated_breeder(Pawn pawn, Map m)
        {
            if (!DesignatorsData.rjwBreeding.Any())
            {
                return(null);
            }

            DebugText("BreederHelper::find_designated_breeder( " + xxx.get_pawnname(pawn) + " ) called");

            float min_fuckability = 0.10f;                          // Don't rape pawns with <10% fuckability
            float avg_fuckability = 0f;                             // Average targets fuckability, choose target higher than that
            var   valid_targets   = new Dictionary <Pawn, float>(); // Valid pawns and their fuckability
            Pawn  chosentarget    = null;                           // Final target pawn

            if (pawn.Faction == null)                               // HostileTo causes error on fresh colony(w/o name)
            {
                return(null);
            }

            IEnumerable <Pawn> targets = DesignatorsData.rjwBreeding.Where(x
                                                                           => x != pawn &&
                                                                           xxx.is_not_dying(x) &&
                                                                           xxx.can_get_raped(x) &&
                                                                           !x.IsForbidden(pawn) &&
                                                                           !x.Suspended &&
                                                                           !x.HostileTo(pawn) &&
                                                                           !(x.IsPregnant() && xxx.is_animal(x)) &&
                                                                           pawn.CanReserveAndReach(x, PathEndMode.Touch, Danger.Some, max_animals_at_once) &&
                                                                           ((RJWSettings.bestiality_enabled && xxx.is_human(x)) || (RJWSettings.animal_on_animal_enabled && xxx.is_animal(x)))
                                                                           );

            foreach (Pawn target in targets)
            {
                if (!xxx.can_path_to_target(pawn, target.Position))
                {
                    continue;                    // too far
                }
                var partBPR = Genital_Helper.get_genitalsBPR(pawn);
                var parts   = Genital_Helper.get_PartsHediffList(pawn, partBPR);

                var fuc = SexAppraiser.would_fuck(pawn, target, invert_opinion: true, ignore_gender: (Genital_Helper.has_penis_fertile(pawn, parts) || Genital_Helper.has_penis_infertile(pawn, parts) || xxx.is_insect(pawn)));
                DebugText("BreederHelper::find_designated_breeder( " + xxx.get_pawnname(pawn) + " -> " + xxx.get_pawnname(target) + " (" + fuc.ToString() + " / " + min_fuckability.ToString() + ")");

                if (fuc > min_fuckability)
                {
                    valid_targets.Add(target, fuc);
                }
            }

            if (valid_targets.Any())
            {
                //avg_fuckability = valid_targets.Average(x => x.Value);

                // choose pawns to f**k with above average fuckability
                var valid_targetsFilteredAnimals = valid_targets.Where(x => x.Value >= avg_fuckability);

                if (valid_targetsFilteredAnimals.Any())
                {
                    chosentarget = valid_targetsFilteredAnimals.RandomElement().Key;
                }
            }

            return(chosentarget);
        }
Beispiel #6
0
        protected override float MtbHours(Pawn pawn)
        {
            var base_mtb = xxx.config.comfort_prisoner_rape_mtbh_mul;             //Default 4.0

            float desire_factor;
            {
                var need_sex = pawn.needs.TryGetNeed <Need_Sex>();
                if (need_sex != null)
                {
                    if (need_sex.CurLevel <= need_sex.thresh_frustrated())
                    {
                        desire_factor = 0.10f;
                    }
                    else if (need_sex.CurLevel <= need_sex.thresh_horny())
                    {
                        desire_factor = 0.50f;
                    }
                    else
                    {
                        desire_factor = 1.00f;
                    }
                }
                else
                {
                    desire_factor = 1.00f;
                }
            }

            float personality_factor;
            {
                personality_factor = 1.0f;
                if (xxx.has_traits(pawn))
                {
                    // Most of the checks are done in the SexAppraiser.would_rape method.

                    personality_factor = 1.0f;

                    if (!RJWSettings.rape_beating)
                    {
                        if (xxx.is_bloodlust(pawn))
                        {
                            personality_factor *= 0.5f;
                        }
                    }

                    if (xxx.is_nympho(pawn))
                    {
                        personality_factor *= 0.5f;
                    }
                    else if (xxx.is_prude(pawn) || pawn.story.traits.HasTrait(TraitDefOf.BodyPurist))
                    {
                        personality_factor *= 2f;
                    }

                    if (xxx.is_rapist(pawn))
                    {
                        personality_factor *= 0.5f;
                    }
                    if (xxx.is_psychopath(pawn))
                    {
                        personality_factor *= 0.75f;
                    }
                    else if (xxx.is_kind(pawn))
                    {
                        personality_factor *= 5.0f;
                    }

                    float rapeCount = pawn.records.GetValue(xxx.CountOfRapedHumanlikes) +
                                      pawn.records.GetValue(xxx.CountOfRapedAnimals) +
                                      pawn.records.GetValue(xxx.CountOfRapedInsects) +
                                      pawn.records.GetValue(xxx.CountOfRapedOthers);

                    // Pawns with few or no rapes are more reluctant to rape CPs.
                    if (rapeCount < 3.0f)
                    {
                        personality_factor *= 1.5f;
                    }
                }
            }

            float fun_factor;
            {
                if ((pawn.needs.joy != null) && (xxx.is_rapist(pawn) || xxx.is_psychopath(pawn)))
                {
                    fun_factor = Mathf.Clamp01(0.50f + pawn.needs.joy.CurLevel);
                }
                else
                {
                    fun_factor = 1.00f;
                }
            }

            float animal_factor = 1.0f;

            if (xxx.is_animal(pawn))
            {
                var partBPR = Genital_Helper.get_genitalsBPR(pawn);
                var parts   = Genital_Helper.get_PartsHediffList(pawn, partBPR);
                // Much slower for female animals.
                animal_factor = (Genital_Helper.has_penis_fertile(pawn, parts) || Genital_Helper.has_penis_infertile(pawn, parts)) ? 2.5f : 6f;
            }

            //if (xxx.is_animal(pawn)) { Log.Message("Chance for " + pawn + " : " + base_mtb * desire_factor * personality_factor * fun_factor * animal_factor); }
            return(base_mtb * desire_factor * personality_factor * fun_factor * animal_factor);
        }
        public static void calculateAndApplySemen(Pawn pawn, Pawn partner, xxx.rjwSextype sextype)
        {
            if (!RJWSettings.cum_on_body)
            {
                return;
            }

            Pawn giver, receiver;
            //Rand.PopState();
            //Rand.PushState(RJW_Multiplayer.PredictableSeed());

            List <Hediff> giverparts;
            var           pawnparts    = Genital_Helper.get_PartsHediffList(pawn, Genital_Helper.get_genitalsBPR(pawn));
            var           partnerparts = Genital_Helper.get_PartsHediffList(partner, Genital_Helper.get_genitalsBPR(partner));

            //dispenser of the seed
            if (Genital_Helper.has_penis_fertile(pawn, pawnparts) || xxx.is_mechanoid(pawn) || xxx.is_insect(pawn))
            {
                giver      = pawn;
                giverparts = pawnparts;
                receiver   = partner;
            }
            else if (partner != null && (Genital_Helper.has_penis_fertile(partner, partnerparts) || xxx.is_mechanoid(partner) || xxx.is_insect(partner)))
            {
                giver      = partner;
                giverparts = partnerparts;
                receiver   = pawn;
            }
            else            //female on female or genderless - no s***n dispensed; maybe add futa support?
            {
                return;
            }

            //slimes do not waste fluids?
            //if (xxx.is_slime(giver)) return;

            //determine entity:
            int entityType = SemenHelper.CUM_NORMAL;

            if (xxx.is_mechanoid(giver))
            {
                entityType = SemenHelper.CUM_MECHA;
            }
            else if (xxx.is_insect(giver))
            {
                entityType = SemenHelper.CUM_INSECT;
            }

            //get pawn genitalia:
            BodyPartRecord genitals;

            if (xxx.is_mechanoid(giver))
            {
                genitals = giver.RaceProps.body.AllParts.Find(x => string.Equals(x.def.defName, "MechGenitals"));
            }
            else            //insects, animals, humans
            {
                genitals = giver.RaceProps.body.AllParts.Find(x => x.def == SemenHelper.genitalsDef);
            }
            //no cum without genitals
            if (genitals == null)
            {
                return;
            }

            float cumAmount = giver.BodySize;                                         //fallback for mechanoinds and w/e without hediffs
            float horniness = 1f;
            float ageScale  = Math.Min(80 / SexUtility.ScaleToHumanAge(giver), 1.0f); //calculation lifted from rjw

            if (xxx.is_mechanoid(giver) && giverparts.NullOrEmpty())
            {
                //use default above
            }
            else if (giverparts.NullOrEmpty())
            {
                return;
            }
            else
            {
                var penisHediff = giverparts.FindAll((Hediff hed) => hed.def.defName.ToLower().Contains("penis")).InRandomOrder().FirstOrDefault();

                if (penisHediff == null)
                {
                    penisHediff = giverparts.FindAll((Hediff hed) => hed.def.defName.ToLower().Contains("ovipositorf")).InRandomOrder().FirstOrDefault();
                }
                if (penisHediff == null)
                {
                    penisHediff = giverparts.FindAll((Hediff hed) => hed.def.defName.ToLower().Contains("ovipositorm")).InRandomOrder().FirstOrDefault();
                }
                if (penisHediff == null)
                {
                    penisHediff = giverparts.FindAll((Hediff hed) => hed.def.defName.ToLower().Contains("tentacle")).InRandomOrder().FirstOrDefault();
                }

                if (penisHediff != null)
                {
                    cumAmount = penisHediff.Severity * giver.BodySize;

                    CompHediffBodyPart chdf = penisHediff.TryGetComp <rjw.CompHediffBodyPart>();
                    if (chdf != null)
                    {
                        cumAmount = chdf.FluidAmmount * chdf.FluidModifier;
                    }

                    Need sexNeed = giver?.needs?.AllNeeds.Find(x => string.Equals(x.def.defName, "Sex"));
                    if (sexNeed != null)                    //non-humans don't have it - therefore just use the default value
                    {
                        horniness = 1f - sexNeed.CurLevel;
                    }
                }
                else
                {
                    //something is wrong... v****a?
                    return;
                }
            }

            cumAmount  = cumAmount * horniness * ageScale * RJWSettings.cum_on_body_amount_adjust;
            cumAmount /= 100;

            //TODO: SemenHelper Autofellatio
            //if no partner -> masturbation, apply some cum on self:
            //if (partner == null && sextype == xxx.rjwSextype.Autofellatio)
            //{
            //	if (!xxx.is_slime(giver))
            //		SemenHelper.cumOn(giver, BodyPartDefOf.Jaw, cumAmount, giver);
            //	return;
            //}
            if (partner == null && sextype == xxx.rjwSextype.Masturbation)
            {
                if (!xxx.is_slime(giver))
                {
                    SemenHelper.cumOn(giver, genitals, cumAmount * 0.3f, giver);                    //pawns are usually not super-messy -> only apply 30%
                }
                return;
            }
            else if (partner != null)
            {
                List <BodyPartRecord>        targetParts    = new List <BodyPartRecord>(); //which to apply s***n on
                IEnumerable <BodyPartRecord> availableParts = SemenHelper.getAvailableBodyParts(receiver);
                BodyPartRecord randomPart;                                                 //not always needed

                switch (sextype)
                {
                case rjw.xxx.rjwSextype.Anal:
                    targetParts.Add(receiver.RaceProps.body.AllParts.Find(x => x.def == SemenHelper.anusDef));
                    break;

                case rjw.xxx.rjwSextype.Boobjob:
                    targetParts.Add(receiver.RaceProps.body.AllParts.Find(x => x.def == SemenHelper.chestDef));
                    break;

                case rjw.xxx.rjwSextype.DoublePenetration:
                    targetParts.Add(receiver.RaceProps.body.AllParts.Find(x => x.def == SemenHelper.anusDef));
                    targetParts.Add(receiver.RaceProps.body.AllParts.Find(x => x.def == SemenHelper.genitalsDef));
                    break;

                case rjw.xxx.rjwSextype.Fingering:
                    cumAmount = 0;
                    break;

                case rjw.xxx.rjwSextype.Fisting:
                    cumAmount = 0;
                    break;

                case rjw.xxx.rjwSextype.Footjob:
                    //random part:
                    availableParts.TryRandomElement <BodyPartRecord>(out randomPart);
                    targetParts.Add(randomPart);
                    break;

                case rjw.xxx.rjwSextype.Handjob:
                    //random part:
                    availableParts.TryRandomElement <BodyPartRecord>(out randomPart);
                    targetParts.Add(randomPart);
                    break;

                case rjw.xxx.rjwSextype.Masturbation:
                    cumAmount *= 2f;
                    break;

                case rjw.xxx.rjwSextype.MechImplant:
                    //one of the openings:
                    int random = Rand.Range(0, 3);
                    if (random == 0)
                    {
                        targetParts.Add(receiver.RaceProps.body.AllParts.Find(x => x.def == SemenHelper.genitalsDef));
                    }
                    else if (random == 1)
                    {
                        targetParts.Add(receiver.RaceProps.body.AllParts.Find(x => x.def == SemenHelper.anusDef));
                    }
                    else if (random == 2)
                    {
                        targetParts.Add(receiver.RaceProps.body.AllParts.Find(x => x.def == BodyPartDefOf.Jaw));
                    }
                    break;

                case rjw.xxx.rjwSextype.MutualMasturbation:
                    //random
                    availableParts.TryRandomElement <BodyPartRecord>(out randomPart);
                    targetParts.Add(randomPart);
                    break;

                case rjw.xxx.rjwSextype.None:
                    cumAmount = 0;
                    break;

                case rjw.xxx.rjwSextype.Oral:
                    targetParts.Add(receiver.RaceProps.body.AllParts.Find(x => x.def == BodyPartDefOf.Jaw));
                    break;

                case rjw.xxx.rjwSextype.Scissoring:
                    //I guess if it came to here, a male must be involved?
                    targetParts.Add(receiver.RaceProps.body.AllParts.Find(x => x.def == SemenHelper.genitalsDef));
                    break;

                case rjw.xxx.rjwSextype.Vaginal:
                    targetParts.Add(receiver.RaceProps.body.AllParts.Find(x => x.def == SemenHelper.genitalsDef));
                    break;
                }

                if (cumAmount > 0)
                {
                    if (xxx.is_slime(receiver))
                    {
                        //slime absorb cum
                        //this needs balancing, since cumamount ranges 0-10(?) which is fine for cum/hentai but not very realistic for feeding
                        //using TransferNutrition for now
                        //Log.Message("cumAmount " + cumAmount);
                        //float nutrition_amount = cumAmount/10;

                        Need_Food need = need = giver.needs.TryGetNeed <Need_Food>();
                        if (need == null)
                        {
                            //Log.Message("xxx::TransferNutrition() " + xxx.get_pawnname(pawn) + " doesn't track nutrition in itself, probably shouldn't feed the others");
                            return;
                        }

                        if (receiver?.needs?.TryGetNeed <Need_Food>() != null)
                        {
                            //Log.Message("xxx::TransferNutrition() " +  xxx.get_pawnname(partner) + " can receive");
                            float nutrition_amount = Math.Min(need.MaxLevel / 15f, need.CurLevel);                             //body size is taken into account implicitly by need.MaxLevel
                            receiver.needs.food.CurLevel += nutrition_amount;
                        }
                    }
                    else
                    {
                        SemenHelper.cumOn(giver, genitals, cumAmount * 0.3f, giver, entityType);                        //cum on self - smaller amount
                        foreach (BodyPartRecord bpr in targetParts)
                        {
                            if (bpr != null)
                            {
                                SemenHelper.cumOn(receiver, bpr, cumAmount, giver, entityType);                                //cum on partner
                            }
                        }
                    }
                }
            }
        }
        public override float CalculateCapacityLevel(HediffSet diffSet, List <PawnCapacityUtility.CapacityImpactor> impactors = null)
        {
            Pawn pawn    = diffSet.pawn;
            var  partBPR = Genital_Helper.get_genitalsBPR(pawn);
            var  parts   = Genital_Helper.get_PartsHediffList(pawn, partBPR);

            if (!Genital_Helper.has_penis_fertile(pawn, parts) && !Genital_Helper.has_vagina(pawn, parts))
            {
                return(0);
            }

            if (Genital_Helper.has_ovipositorF(pawn, parts) || Genital_Helper.has_ovipositorM(pawn, parts))
            {
                return(0);
            }

            //Log.Message("[RJW]PawnCapacityWorker_Fertility::CalculateCapacityLevel is called for: " + xxx.get_pawnname(pawn));
            RaceProperties race = diffSet.pawn.RaceProps;

            if (!pawn.RaceHasFertility())
            {
                //Log.Message(" Fertility_filter, no fertility for : " + pawn.kindDef.defName);
                return(0f);
            }

            //androids only fertile with archotech parts
            if (AndroidsCompatibility.IsAndroid(pawn) && !(AndroidsCompatibility.AndroidPenisFertility(pawn) || AndroidsCompatibility.AndroidVaginaFertility(pawn)))
            {
                //Log.Message(" Android has no archotech genitals set fertility to 0 for: " + pawn.kindDef.defName);
                return(0f);
            }

            //archotech always fertile mode
            if (pawn.health.hediffSet.HasHediff(HediffDef.Named("FertilityEnhancer")))
            {
                //Log.Message(" has archotech FertilityEnhancer set fertility to 100%");
                return(1f);
            }

            float startAge      = 0f;                                                                        //raise fertility
            float startMaxAge   = 0f;                                                                        //max fertility
            float endAge        = race.lifeExpectancy * (RJWPregnancySettings.fertility_endage_male * 0.7f); // Age when males start to lose potency.
            float zeroFertility = race.lifeExpectancy * RJWPregnancySettings.fertility_endage_male;          // Age when fertility hits 0%.

            if (xxx.is_female(pawn))
            {
                if (xxx.is_animal(pawn))
                {
                    endAge        = race.lifeExpectancy * (RJWPregnancySettings.fertility_endage_female_animal * 0.6f);
                    zeroFertility = race.lifeExpectancy * RJWPregnancySettings.fertility_endage_female_animal;
                }
                else
                {
                    endAge        = race.lifeExpectancy * (RJWPregnancySettings.fertility_endage_female_humanlike * 0.6f);            // Age when fertility begins to drop.
                    zeroFertility = race.lifeExpectancy * RJWPregnancySettings.fertility_endage_female_humanlike;                     // Age when fertility hits 0%.
                }
            }

            foreach (LifeStageAge lifestage in race.lifeStageAges)
            {
                if (lifestage.def.reproductive)
                {
                    //presumably teen stage
                    if (startAge == 0f && startMaxAge == 0f)
                    {
                        startAge    = lifestage.minAge;
                        startMaxAge = (Mathf.Max(startAge + (startAge + endAge) * 0.08f, startAge));
                    }
                    //presumably adult stage
                    else
                    {
                        if (startMaxAge > lifestage.minAge)
                        {
                            startMaxAge = lifestage.minAge;
                        }
                    }
                }
            }
            //Log.Message(" Fertility ages for " + pawn.Name + " are: " + startAge + ", " + startMaxAge + ", " + endAge + ", " + endMaxAge);

            float result = PawnCapacityUtility.CalculateTagEfficiency(diffSet, BodyPartTagDefOf.RJW_Fertility, 1f, FloatRange.ZeroToOne, impactors);

            result *= GenMath.FlatHill(startAge, startMaxAge, endAge, zeroFertility, pawn.ageTracker.AgeBiologicalYearsFloat);

            //Log.Message("[RJW]PawnCapacityWorker_Fertility::CalculateCapacityLevel result is: " + result);
            return(result);
        }
        public static float would_fuck_animal(Pawn pawn, Pawn target, bool invert_opinion = false, bool ignore_bleeding = false, bool ignore_gender = false)
        {
            float        wildness_modifier = 1.0f;
            List <float> size_preference   = new List <float>()
            {
                pawn.BodySize * 0.75f, pawn.BodySize * 1.6f
            };
            float fuc = would_fuck(pawn, target, invert_opinion, ignore_bleeding, ignore_gender);             // 0.0 to ~3.0, orientation checks etc.

            if (fuc < 0.1f)
            {               // Would not f**k
                return(0);
            }

            if (xxx.has_quirk(pawn, "Teratophile"))
            {               // Teratophiles prefer more 'monstrous' partners.
                size_preference[0] = pawn.BodySize * 0.8f;
                size_preference[1] = pawn.BodySize * 2.0f;
                wildness_modifier  = 0.3f;
            }
            if (pawn.health.hediffSet.HasHediff(HediffDef.Named("AlcoholHigh")))
            {
                wildness_modifier   = 0.5f;               //Drunk and making poor judgments.
                size_preference[1] *= 1.5f;
            }
            else if (pawn.health.hediffSet.HasHediff(HediffDef.Named("YayoHigh")))
            {
                wildness_modifier   = 0.2f;               //This won't end well.
                size_preference[1] *= 2.5f;
            }
            var partBPR = Genital_Helper.get_genitalsBPR(pawn);
            var parts   = Genital_Helper.get_PartsHediffList(pawn, partBPR);

            if (Genital_Helper.has_vagina(pawn, parts) || Genital_Helper.has_anus(pawn))
            {
                if (!(Genital_Helper.has_penis_fertile(pawn, parts) || Genital_Helper.has_penis_infertile(pawn, parts)))
                {
                    size_preference[1] = pawn.BodySize * 1.3f;
                }
            }
            if (xxx.is_animal(pawn))
            {
                size_preference[1] = pawn.BodySize * 1.3f;
                wildness_modifier  = 0.4f;
            }
            else
            {
                if (pawn.story.traits.HasTrait(TraitDefOf.Tough) || pawn.story.traits.HasTrait(TraitDefOf.Brawler))
                {
                    size_preference[1] += 0.2f;
                    wildness_modifier  -= 0.2f;
                }
                else if (pawn.story.traits.HasTrait(TraitDef.Named("Wimp")))
                {
                    size_preference[0] -= 0.2f;
                    size_preference[1] -= 0.2f;
                    wildness_modifier  += 0.25f;
                }
            }

            float wildness = target.RaceProps.wildness;           // 0.0 to 1.0
            float petness  = target.RaceProps.petness;            // 0.0 to 1.0
            float distance = pawn.Position.DistanceToSquared(target.Position);

            //Log.Message("[RJW]would_fuck_animal:: base: " + fuc + ", wildness: " + wildness + ", petness: " + petness + ", distance: " + distance);

            fuc = fuc + fuc * petness - fuc * wildness * wildness_modifier;

            if (fuc < 0.1f)
            {               // Would not f**k
                return(0);
            }

            // Adjust by distance, nearby targets preferred.
            fuc *= 1.0f - Mathf.Max(distance / 10000, 0.1f);

            // Adjust by size difference.
            if (target.BodySize < size_preference[0])
            {
                fuc *= Mathf.Lerp(0.1f, size_preference[0], target.BodySize);
            }
            else if (target.BodySize > size_preference[1])
            {
                fuc *= Mathf.Lerp(size_preference[1] * 10, size_preference[1], target.BodySize);
            }

            if (target.Faction != pawn.Faction)
            {
                //Log.Message("[RJW]would_fuck_animal(NT):: base: " + fuc + ", bound1: " + fuc * 0.75f);
                //Log.Message("[RJW]would_fuck_animal(NT):: base: " + fuc + ", bound2: " + fuc + 0.25f);
                fuc *= 0.75f;                 // Less likely to target wild animals.
            }
            else if (pawn.relations.DirectRelationExists(PawnRelationDefOf.Bond, target))
            {
                //Log.Message("[RJW]would_fuck_animal(T):: base: " + fuc + ", bound1: " + fuc * 1.25f);
                //Log.Message("[RJW]would_fuck_animal(T):: base: " + fuc + ", bound2: " + fuc + 0.25f);
                fuc *= 1.25f;                 // Bonded animals preferred.
            }

            return(fuc);
        }