using HarmonyLib; using rjw; using rjw.Modules.Shared.Extensions; using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnityEngine; using Verse; using Verse.AI; using static rjw.Dialog_Sexcard; namespace RJW_Genes { [HarmonyPatch(typeof(JobDriver_Sex), "SexTick")] public static class Patch_DangerousGenitalia { const float BASE_DAMAGE = 1.5f; const float BASE_APPLICATION_CHANCE = 1.0f; const float BASE_CHANCE_FOR_INNER_DAMAGE = 0.3f; const float THRESHOLD_FOR_INNER_DAMAGE = 2.0f; public static void Postfix(JobDriver_Sex __instance) { if (__instance == null || __instance.Sexprops == null) return; // Below automatically checks that not both pawns have dangerous genitalia Pawn dangerousGenitaliaPawn = ExactlyOnePawnHasDangerousGenitalia(__instance.Sexprops.pawn, __instance.Sexprops.partner); if (dangerousGenitaliaPawn == null) return; Pawn fuckedPawn = GetPawnThatHasNotTheDangerousGenitalia(__instance.Sexprops.pawn, __instance.Sexprops.partner); if (dangerousGenitaliaPawn.IsHashIntervalTick(__instance.ticks_between_thrusts)) { if ((new System.Random()).NextDouble() < BASE_APPLICATION_CHANCE) { // TODO: Setting only for Rape // TODO: Testing // Open Issues: // Currently, if a "Dangerous Vagina" Pawn "Reverse rapes", the vagina pawn gets damaged. // I think there needs to be a check and logic for isReverse ... ModLog.Message($"Test - Tick {__instance.Sexprops.pawn} <> {__instance.Sexprops.partner} --- Thrust"); bool importantPartDestroyed = ApplyDangerousGenitaliaDamage(dangerousGenitaliaPawn, fuckedPawn, __instance.Sexprops); if (importantPartDestroyed) { ModLog.Message("Genital was destroyed - aborting the SexDriver"); //__instance.EndJobWith(Verse.AI.JobCondition.InterruptForced); __instance.AddFailCondition( () => true); } } } } /// /// /// /// /// /// /// True if a part was destroyed, False otherwise private static bool ApplyDangerousGenitaliaDamage(Pawn damager, Pawn damaged, SexProps props) { DamageWorker DamageWorker = new DamageWorker_AddInjury(); bool damagerIsMaleOrFuta = GenderUtility.IsMale(damager) || Genital_Helper.has_penis_fertile(damager) || Genital_Helper.has_penis_infertile(damager); float penetrator_genitalsize = GetBiggestGenitalSize(damager); float penetrator_bodysize = GetBodySize(damager, GetBiggestGenital(damager, penis: damagerIsMaleOrFuta)); switch (props.sexType) { case xxx.rjwSextype.Oral: case xxx.rjwSextype.Fellatio: case xxx.rjwSextype.Cunnilingus: { float penetrated_bodysize = GetBodySize(damaged); float damage = CalculateSizeRelatedDamage(penetrator_bodysize, penetrator_genitalsize, penetrated_bodysize, 0.0f); bool allow_for_inner_damage = damage >= THRESHOLD_FOR_INNER_DAMAGE; DamageInfo dInfo = new DamageInfo( VariousDefOf.rjw_genes_dangerous_genitalia_damage, damage, instigator: damager, category: DamageInfo.SourceCategory.ThingOrUnknown, hitPart: GetRandomOralBodyPartRecord(damaged, allow_for_inner_damage)); DamageWorker.DamageResult result = DamageWorker.Apply(dInfo, damaged); return CheckIfNeedsToAbortSexDriver(damaged, result); } case xxx.rjwSextype.Vaginal: case xxx.rjwSextype.Scissoring: { Hediff vagina = Genital_Helper.get_AllPartsHediffList(damaged).FirstOrDefault(part => Genital_Helper.is_vagina(part)); if (vagina == null) return false; CompHediffBodyPart comps = vagina.TryGetComp(); float penetrated_bodysize = GetBodySize(damaged, vagina); float damage = CalculateSizeRelatedDamage(penetrator_bodysize, penetrator_genitalsize, penetrated_bodysize, vagina.Severity); bool allow_for_inner_damage = damage >= THRESHOLD_FOR_INNER_DAMAGE; DamageInfo dInfo = new DamageInfo( VariousDefOf.rjw_genes_dangerous_genitalia_damage, damage, instigator: damager, category: DamageInfo.SourceCategory.ThingOrUnknown, hitPart: GetRandomGenitalBodyPartRecord(damaged, allow_for_inner_damage)); DamageWorker.DamageResult result = DamageWorker.Apply(dInfo, damaged); return CheckIfNeedsToAbortSexDriver(damaged, result); } case xxx.rjwSextype.Anal: { Hediff anus = Genital_Helper.get_AllPartsHediffList(damaged).FirstOrDefault(part => Genital_Helper.is_anus(part)); if (anus == null) return false; CompHediffBodyPart comps = anus.TryGetComp(); float penetrated_bodysize = GetBodySize(damaged, anus); float damage = CalculateSizeRelatedDamage(penetrator_bodysize, penetrator_genitalsize, penetrated_bodysize, anus.Severity); bool allow_for_inner_damage = damage >= THRESHOLD_FOR_INNER_DAMAGE; DamageInfo dInfo = new DamageInfo( VariousDefOf.rjw_genes_dangerous_genitalia_damage, damage, instigator: damager, category: DamageInfo.SourceCategory.ThingOrUnknown, hitPart: GetRandomAnalBodyPartRecord(damaged, allow_for_inner_damage)); DamageWorker.DamageResult result = DamageWorker.Apply(dInfo, damaged); return CheckIfNeedsToAbortSexDriver(damaged, result); } default: return false; } } private static bool CheckIfNeedsToAbortSexDriver(Pawn damaged, DamageWorker.DamageResult result) { bool partner_killed = damaged.Dead; bool part_destroyed = result.LastHitPart.IsMissingForPawn(damaged); return partner_killed || part_destroyed; } /// /// Checks both pawns for their genes, /// and returns the pawn that has the rjw_genes_dangerous_genitalia def. /// Returns null if both or none have it. /// private static Pawn ExactlyOnePawnHasDangerousGenitalia(Pawn pawnA, Pawn pawnB) { if (pawnA == null) return null; if (pawnB == null) return null; bool pawnAHasDangerousGen = pawnA.genes != null && pawnA.genes.HasActiveGene(GeneDefOf.rjw_genes_dangerous_genitalia); bool pawnBHasDangerousGen = pawnB.genes != null && pawnB.genes.HasActiveGene(GeneDefOf.rjw_genes_dangerous_genitalia); if (pawnAHasDangerousGen && pawnBHasDangerousGen) return null; else if (!pawnAHasDangerousGen && !pawnBHasDangerousGen) return null; else if (pawnAHasDangerousGen) { return pawnA; } else if(pawnBHasDangerousGen) { return pawnB; } return null; } // This method assumes that the pawn is the penetrator - the biggest penis-severity is returned. private static float GetBiggestGenitalSize(Pawn pawn) { float best = 0.0f; var parts = Genital_Helper.get_AllPartsHediffList(pawn); foreach (var part in parts) { if (Genital_Helper.is_sex_part(part) && Genital_Helper.is_penis(part)) { best = part.Severity > best ? part.Severity : best; } } return best; } /// /// Calculates the damage that will be applied when penetrated based on the genital and body sizes. /// This is done by a simple multiplier on the base damaged, based on the absolute differences in sizes. /// /// The bodysize of the pawn, or, if applicable, the bodysize of the damaging genital. /// The severity of the penetrating body-part /// The bodysize of the penetrated, or, if applicable, the bodysize of the damaged genital. /// The severity of the penetrated body-part, 0 for oral. /// The damage applied to a bodypart-record, 0.0 if there is no damage to be done. private static float CalculateSizeRelatedDamage(float penetrator_bodysize, float penetrator_genitalsize, float penetrated_bodysize, float penetrated_genitalsize=0.0f) { float diff_in_bodysize = penetrator_bodysize - penetrated_bodysize; float diff_in_genital_size = penetrator_genitalsize - penetrated_genitalsize; float damage_multiplier = 1.0f + diff_in_bodysize + diff_in_genital_size; if (damage_multiplier > 0.5f) return BASE_DAMAGE * damage_multiplier; // If the size differences are too big, but the penetrator is the small one, do not deal damage. else return 0.0f; } /// /// Returns the pawn that does not have the gene for dangerous genitalia. /// Returns null if neither or both have it. /// private static Pawn GetPawnThatHasNotTheDangerousGenitalia(Pawn a, Pawn b) { Pawn withGenitalia = ExactlyOnePawnHasDangerousGenitalia(a,b); if (withGenitalia == null) return null; else if (a == withGenitalia) return b; else if (b == withGenitalia) return a; else return null; } /// /// Returns the biggest (by bodysize, then by severity) Genital. /// Biggest mean highest severity for penisses, and lowest for vaginas. /// /// The pawn for whom to look up. /// Whether to look for penisses - this should be true for males and futas. /// Largest Penis or Tightest Vagina. Null in case of errors / non genitals. private static Hediff GetBiggestGenital(Pawn pawn, bool penis = true) { Hediff best = null; var parts = Genital_Helper.get_AllPartsHediffList(pawn); if (penis) { foreach (var part in parts) { if (Genital_Helper.is_penis(part)) { if (best == null) best = part; CompHediffBodyPart CompHediff = part.TryGetComp(); CompHediffBodyPart CompHediffBest = best.TryGetComp(); if (CompHediffBest.SizeOwner > CompHediff.SizeOwner) { best = part; } else if (CompHediffBest.SizeOwner == CompHediff.SizeOwner) { best = part.Severity > best.Severity? part : best; } } } } else { foreach (var part in parts) { if (Genital_Helper.is_vagina(part)) { if (best == null) best = part; CompHediffBodyPart CompHediff = part.TryGetComp(); CompHediffBodyPart CompHediffBest = best.TryGetComp(); if (CompHediffBest.SizeOwner < CompHediff.SizeOwner) { best = part; } else if (CompHediffBest.SizeOwner == CompHediff.SizeOwner) { best = part.Severity < best.Severity ? part : best; } } } } return best; } /// /// Gets the (relevant) bodysize of a pawn or it's genital. /// "Null" as genital input is meant for a fallback, or for oral. It will return the pawns bodysize. /// private static float GetBodySize(Pawn pawn, Hediff genital = null) { if (pawn == null) return 0; if (genital == null) return pawn.BodySize; if(rjw.Genital_Helper.is_sex_part(genital)){ CompHediffBodyPart CompHediff = genital.TryGetComp(); if (CompHediff != null) { return CompHediff.SizeOwner; } } // Fallback! return 0.0f; } private static BodyPartRecord GetRandomAnalBodyPartRecord(Pawn pawn, bool InnerOrgansPossible = false) { if (pawn == null) return null; if (InnerOrgansPossible && (new System.Random()).NextDouble() < 1 - BASE_CHANCE_FOR_INNER_DAMAGE) return rjw.Genital_Helper.get_torsoBPR(pawn); return Genital_Helper.get_anusBPR(pawn); } private static BodyPartRecord GetRandomOralBodyPartRecord(Pawn pawn, bool InnerOrgansPossible = false) { if (pawn == null) return null; if (InnerOrgansPossible && (new System.Random()).NextDouble() < 1 - BASE_CHANCE_FOR_INNER_DAMAGE) return rjw.Genital_Helper.get_torsoBPR(pawn); return Genital_Helper.get_mouthBPR(pawn); } private static BodyPartRecord GetRandomGenitalBodyPartRecord(Pawn pawn, bool InnerOrgansPossible = false) { if (pawn == null) return null; if (InnerOrgansPossible && (new System.Random()).NextDouble() < 1 - BASE_CHANCE_FOR_INNER_DAMAGE) return rjw.Genital_Helper.get_torsoBPR(pawn); return Genital_Helper.get_genitalsBPR(pawn); } } }