rimworld-animations-patch_m.../Source/Scripts/Patches/HarmonyPatch_RJW.cs

461 lines
16 KiB
C#

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using HarmonyLib;
using RimWorld;
using Verse;
using Verse.AI;
using rjw;
using Rimworld_Animations;
using RJW_ToysAndMasturbation;
namespace Rimworld_Animations_Patch
{
[HarmonyPatch(typeof(JobDriver_Sex), "setup_ticks")]
public static class HarmonyPatch_JobDriver_Sex_setup_ticks
{
public static void Postfix(ref JobDriver_Sex __instance)
{
// Sets ticks so that the orgasm meter starts empty, plus stop any running animations
HarmonyPatch_JobDriver_Masturbate_setup_ticks.Postfix(ref __instance);
// Invite another for a threesome?
if (RJWHookupSettings.QuickHookupsEnabled &&
__instance is JobDriver_SexBaseInitiator &&
__instance.pawn.GetAllSexParticipants().Count == 2 &&
(__instance is JobDriver_JoinInSex) == false &&
Random.value < BasicSettings.chanceForOtherToJoinInSex)
{
DebugMode.Message("Find another to join in sex");
Pawn pawn = __instance.pawn;
List<Pawn> candidates = new List<Pawn>();
float radius = 4f;
foreach (Thing thing in GenRadial.RadialDistinctThingsAround(pawn.Position, pawn.Map, radius, true))
{
Pawn other = thing as Pawn;
// Find candidates to invite
if (other != null && (int)SexInteractionUtility.CheckSexJobAgainstMorals(other, __instance, out Precept precept) <= 0 &&
SexInteractionUtility.PawnCanInvitePasserbyForSex(other, pawn.GetAllSexParticipants()))
{
DebugMode.Message(other.NameShortColored + " is a potential candidate");
candidates.Add(other);
}
}
// Invite a random candidate (weighted by attraction)
if (candidates.Count > 0)
{
Pawn invitedPawn = candidates.RandomElementByWeight(x => SexAppraiser.would_fuck(pawn, x, false, false, true) + SexAppraiser.would_fuck(pawn.GetSexPartner(), x, false, false, true));
pawn.GetSexInitiator().IsInBed(out Building bed);
DebugMode.Message(invitedPawn.NameShortColored + " was invited to join in sex");
Job job = new Job(DefDatabase<JobDef>.GetNamed("JoinInSex", false), pawn.GetSexPartner(), bed);
invitedPawn.jobs.TryTakeOrderedJob(job);
}
}
}
}
[HarmonyPatch(typeof(JobDriver_Masturbate), "setup_ticks")]
public static class HarmonyPatch_JobDriver_Masturbate_setup_ticks
{
// Sets ticks so that the orgasm meter starts empty, plus stop any running animations
public static void Postfix(ref JobDriver_Sex __instance)
{
__instance.sex_ticks = __instance.duration;
CompBodyAnimator comp = __instance.pawn.TryGetComp<CompBodyAnimator>();
if (comp != null && comp.isAnimating)
{ comp.isAnimating = false; }
}
}
[HarmonyPatch(typeof(JobDriver_SexBaseInitiator), "Start")]
public static class HarmonyPatch_JobDriver_SexBaseInitiator_Start
{
public static bool MustRerollHumping(Pawn pawn, SexProps sexProps)
{
if (sexProps?.dictionaryKey?.defName == null || sexProps.dictionaryKey.defName != "Masturbation_Humping")
{ return false; }
if (pawn.IsInBed(out Building bed))
{ return false; }
DebugMode.Message("Not in bed, cannot do requested action");
return true;
}
public static float RandomMasturbationWeights(InteractionDef interactionDef, Pawn pawn)
{
bool hasBed = pawn.IsInBed(out Building bed);
if (interactionDef.defName == "Masturbation_Breastjob" && Genital_Helper.has_breasts(pawn)) { return BasicSettings.breastsMasturbationChance; }
if (interactionDef.defName == "Masturbation_HandjobA" && Genital_Helper.has_anus(pawn)) { return BasicSettings.analMasturbationChance; }
if (interactionDef.defName == "Masturbation_HandjobP" && Genital_Helper.has_penis_fertile(pawn)) { return BasicSettings.genitalMasturbationChance; }
if (interactionDef.defName == "Masturbation_HandjobV" && Genital_Helper.has_vagina(pawn)) { return BasicSettings.genitalMasturbationChance; }
if (interactionDef.defName == "Masturbation_Humping" && hasBed) { return BasicSettings.humpingMasturbationChance; }
return 0f;
}
// Adds weights to masturbation type selection
public static void Prefix(ref JobDriver_SexBaseInitiator __instance)
{
if (__instance.Sexprops == null)
{ __instance.Sexprops = __instance.pawn.GetRMBSexPropsCache(); }
if (__instance is JobDriver_Masturbate && (__instance.Sexprops == null || MustRerollHumping(__instance.pawn, __instance.Sexprops)))
{
DebugMode.Message("No valid sexprops provided. Generating new interaction...");
SexProps sexProps = new SexProps();
sexProps.pawn = __instance.pawn;
sexProps.partner = __instance.pawn;
sexProps.sexType = xxx.rjwSextype.Masturbation;
List<InteractionDef> interactionDefs = DefDatabase<InteractionDef>.AllDefs.Where(x => x.HasModExtension<InteractionExtension>()).ToList();
Dictionary<rjw.Modules.Interactions.Objects.InteractionWithExtension, float> interactionsPlusWeights = new Dictionary<rjw.Modules.Interactions.Objects.InteractionWithExtension, float>();
foreach (InteractionDef interactionDef in interactionDefs)
{
var interaction = rjw.Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(interactionDef);
if (interaction.Extension.rjwSextype != xxx.rjwSextype.Masturbation.ToStringSafe())
{ continue; }
interactionsPlusWeights.Add(interaction, RandomMasturbationWeights(interaction.Interaction, sexProps.pawn));
}
var selectedInteraction = interactionsPlusWeights.RandomElementByWeight(x => x.Value).Key;
sexProps.dictionaryKey = selectedInteraction.Interaction;
sexProps.rulePack = selectedInteraction.Extension.rulepack_defs.RandomElement();
DebugMode.Message("Generated interaction: " + sexProps.dictionaryKey.defName);
DebugMode.Message(sexProps.rulePack);
__instance.Sexprops = sexProps;
}
}
// Adds in option for animated masturbation
public static void Postfix(ref JobDriver_SexBaseInitiator __instance)
{
// Allow solo animations to be played
if (__instance is JobDriver_Masturbate && __instance.pawn.GetAnimationData() == null)
{ PickMasturbationAnimation(__instance.pawn, __instance.Sexprops); }
// Allow make out animations to be played
if (__instance.pawn.GetAnimationData() == null)
{ PickMakeOutAnimation(__instance.pawn, __instance.Sexprops); }
// If there is no animation to play, exit
if (__instance.pawn.GetAnimationData() == null)
{ return; }
// Get animation data
AnimationDef anim = __instance.pawn.GetAnimationData()?.animationDef;
List<Pawn> pawnsToAnimate = __instance.pawn.GetAllSexParticipants();
// Sync animations across participants
foreach (Pawn participant in pawnsToAnimate)
{
JobDriver_Sex jobdriver = participant.jobs.curDriver as JobDriver_Sex;
if (jobdriver == null)
{ continue; }
// Animation timing reset
jobdriver.orgasms = 0;
jobdriver.ticks_left = AnimationPatchUtility.FindTrueAnimationLength(participant, out int orgasmTick);
jobdriver.ticksLeftThisToil = jobdriver.ticks_left;
jobdriver.sex_ticks = orgasmTick;
jobdriver.duration = jobdriver.sex_ticks;
jobdriver.orgasmstick = 0;
// Reset anchor and animation for sex toys
CompThingAnimator sexToyCompThingAnimator = ((Thing)jobdriver.job.GetTarget(TargetIndex.A)).TryGetComp<CompThingAnimator>();
if (sexToyCompThingAnimator != null)
{
DebugMode.Message("Using sex toy - " + jobdriver.job.GetTarget(TargetIndex.A));
__instance.pawn.IsInBed(out Building bed);
Vector3 anchor = AnimationPatchUtility.GetAnchorPosition(__instance.pawn, bed) - new Vector3(0.5f, 0, 0.5f);
AccessTools.Field(typeof(CompThingAnimator), "anchor").SetValue(sexToyCompThingAnimator, anchor);
}
// Determine where pawns are to toss clothes
if (participant?.apparel?.WornApparel != null)
{
IntVec3 apparelCell = MathUtility.FindRandomCellNearPawn(participant, 4);
foreach (Apparel apparel in participant.apparel.WornApparel)
{
CompApparelVisibility compApparelVisibility = apparel.TryGetComp<CompApparelVisibility>();
if (compApparelVisibility != null)
{ compApparelVisibility.GenerateFloorPosition(apparelCell, new Vector2(0f, 0.125f)); }
}
}
}
}
public static void PickMasturbationAnimation(Pawn pawn, SexProps sexProps = null)
{
if (pawn.TryGetComp<CompBodyAnimator>() == null)
{ Log.Error("Error: " + pawn.Name + " of race " + pawn.def.defName + " does not have CompBodyAnimator attached!"); return; }
pawn.TryGetComp<CompBodyAnimator>().isAnimating = false;
List<Pawn> pawnsToAnimate = new List<Pawn>() { pawn };
AnimationDef anim = null;
// Get random animation based on interaction type
if (sexProps != null)
{
var interaction = rjw.Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(sexProps.dictionaryKey);
InteractionDef interactionDef = interaction.Interaction;
DebugMode.Message("Finding animations that match " + interactionDef.defName);
List<AnimationDef> anims = new List<AnimationDef>();
foreach (AnimationDef _anim in DefDatabase<AnimationDef>.AllDefs)
{
if (_anim?.actors?.Count == 1 &&
_anim.sexTypes != null && _anim.sexTypes.Contains(xxx.rjwSextype.Masturbation) &&
_anim.interactionDefTypes != null && _anim.interactionDefTypes.Contains(interactionDef.defName) &&
AnimationUtility.GenitalCheckForPawn(_anim.actors[0].requiredGenitals, pawn, out string failReason))
{ anims.Add(_anim); }
}
if (anims != null && anims.Any())
{ anim = anims.RandomElement(); }
}
// If no animation exists, pick one at random
if (anim == null)
{ anim = AnimationUtility.tryFindAnimation(ref pawnsToAnimate, xxx.rjwSextype.Masturbation, sexProps); }
if (anim == null)
{ DebugMode.Message("No animation found"); return; }
// Start animation
DebugMode.Message("Playing " + anim.defName);
pawn.IsInBed(out Building bed);
if (bed != null)
{ pawn.TryGetComp<CompBodyAnimator>().setAnchor(bed); }
else
{ pawn.TryGetComp<CompBodyAnimator>().setAnchor(pawn.Position); }
pawn.TryGetComp<CompBodyAnimator>().StartAnimation(anim, pawnsToAnimate, 0, GenTicks.TicksGame % 2 == 0, true, bed == null);
// Hide hearts if necessary
if (!AnimationSettings.hearts)
{ (pawn.jobs.curDriver as JobDriver_Sex).ticks_between_hearts = System.Int32.MaxValue; }
}
public static void PickMakeOutAnimation(Pawn pawn, SexProps sexProps = null)
{
if (pawn.TryGetComp<CompBodyAnimator>() == null)
{ Log.Error("Error: " + pawn.Name + " of race " + pawn.def.defName + " does not have CompBodyAnimator attached!"); return; }
List<Pawn> pawnsToAnimate = pawn.GetAllSexParticipants();
if (sexProps.sexType != xxx.rjwSextype.Oral || pawnsToAnimate.Count != 2)
{ return; }
List<AnimationDef> kissingAnims = DefDatabase<AnimationDef>.AllDefs.Where(x => x.defName.Contains("Kiss")).ToList();
AnimationDef anim = kissingAnims[Random.Range(0, kissingAnims.Count)];
if (anim == null)
{ DebugMode.Message("No animation found"); return; }
bool mirror = GenTicks.TicksGame % 2 == 0;
// Start animation
DebugMode.Message("Playing " + anim.defName);
foreach (Pawn participant in pawnsToAnimate)
{
participant.TryGetComp<CompBodyAnimator>().setAnchor(pawnsToAnimate[0].Position);
participant.TryGetComp<CompBodyAnimator>().StartAnimation(anim, pawnsToAnimate, pawnsToAnimate.IndexOf(participant), mirror);
// Hide hearts if necessary
if (!AnimationSettings.hearts)
{ (participant.jobs.curDriver as JobDriver_Sex).ticks_between_hearts = System.Int32.MaxValue; }
}
}
}
[HarmonyPatch(typeof(JobDriver_Sex), "SexTick")]
public static class HarmonyPatch_JobDriver_Sex_SexTick
{
// If pawns don't have privacy, they'll stop having sex
public static void Postfix(ref JobDriver_Sex __instance, Pawn pawn)
{
if (pawn.IsHashIntervalTick(90))
{
if (pawn.IsMasturbating() && pawn.HasPrivacy(8f) == false)
{ pawn.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false); }
else if (pawn.IsHavingSex())
{
bool havePrivacy = true;
List<Pawn> participants = pawn.GetAllSexParticipants();
foreach (Pawn participant in participants)
{
if (participant.HasPrivacy(8f) == false)
{ havePrivacy = false; }
}
if (__instance.Sexprops != null && (__instance.Sexprops.isRape || __instance.Sexprops.isWhoring))
{ return; }
if (havePrivacy == false)
{
foreach (Pawn participant in participants)
{ participant.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false); }
}
}
}
}
}
[HarmonyPatch(typeof(JobDriver_Sex), "Orgasm")]
public static class HarmonyPatch_JobDriver_Sex_Orgasm
{
// Stops orgasm triggering more than once per animation
public static bool Prefix(ref JobDriver_Sex __instance)
{
if (__instance.orgasms > 0)
{ return false; }
return true;
}
public static bool ParticipantsDesireMoreSex(JobDriver_Sex jobdriver)
{
List<Pawn> participants = jobdriver.pawn.GetAllSexParticipants();
float satisfaction = 0f;
foreach (Pawn pawn in participants)
{
Need_Sex sexNeed = pawn?.needs?.TryGetNeed<Need_Sex>();
if (sexNeed == null)
{ satisfaction += 1; continue; }
satisfaction += sexNeed.CurLevelPercentage;
}
return Rand.Chance(1 - satisfaction / participants.Count);
}
// Alows the starting of a new animation cycle at the end of the current one
public static void Postfix(ref JobDriver_Sex __instance)
{
if (__instance.orgasms > 0)
{ __instance.sex_ticks = 0; }
if (__instance is JobDriver_SexBaseInitiator == false || __instance is JobDriver_JoinInSex)
{ return; }
if (__instance.Sexprops != null && (__instance.Sexprops.isRape || __instance.Sexprops.isWhoring))
{ return; }
if (__instance.ticksLeftThisToil <= 1 && (__instance.neverendingsex || ParticipantsDesireMoreSex(__instance)))
{
List<Pawn> participants = __instance.pawn.GetAllSexParticipants();
if (participants.Count == 2)
{
Job job = JobMaker.MakeJob(participants[0].CurJobDef, participants[0], participants[0].jobs.curJob.targetC);
participants[1].jobs.StartJob(job, JobCondition.Succeeded);
}
}
}
}
[HarmonyPatch(typeof(JobDriver_SexBaseInitiator), "End")]
public static class HarmonyPatch_JobDriver_Sex_End
{
// Clear all partners out when sex ends to prevent issues with threesome animations
public static void Postfix(ref JobDriver_SexBaseInitiator __instance)
{
if (__instance.Partner != null && __instance?.Partner?.jobs?.curDriver != null && __instance.Partner.Dead == false && __instance.Partner?.jobs.curDriver is JobDriver_SexBaseReciever)
{
foreach (Pawn participant in (__instance.Partner?.jobs.curDriver as JobDriver_SexBaseReciever).parteners.ToList())
{ participant.jobs.EndCurrentJob(JobCondition.Succeeded, false, true); }
(__instance.Partner?.jobs.curDriver as JobDriver_SexBaseReciever).parteners.Clear();
}
}
}
[HarmonyPatch(typeof(SexUtility), "AfterMasturbation")]
public static class HarmonyPatch_SexUtility_AfterMasturbation
{
// Removes excess calls to generate filth
public static bool Prefix(SexProps props)
{
var methodInfo = AccessTools.Method(typeof(SexUtility), "IncreaseTicksToNextLovin", null, null);
methodInfo.Invoke(null, new object[] { props.pawn });
AfterSexUtility.UpdateRecords(props);
return false;
}
}
[HarmonyPatch(typeof(Genital_Helper), "has_mouth")]
public static class HarmonyPatch_Genital_Helper_has_mouth
{
// Fixes mouth check
public static bool Prefix(ref bool __result, Pawn pawn)
{
__result = pawn.health.hediffSet.GetNotMissingParts().Any(x => x.def.defName.ToLower().ContainsAny("mouth", "teeth", "jaw", "beak"));
return false;
}
}
[HarmonyPatch(typeof(SexUtility), "DrawNude")]
public static class HarmonyPatch_SexUtility_DrawNude
{
public static bool Prefix(Pawn pawn, bool keep_hat_on)
{
if (!xxx.is_human(pawn)) return false;
if (pawn.Map != Find.CurrentMap) return false;
pawn.Drawer.renderer.graphics.ClearCache();
pawn.Drawer.renderer.graphics.apparelGraphics.Clear();
ApparelAnimationUtility.DetermineApparelToKeepOn(pawn);
foreach (Apparel apparel in pawn.apparel.WornApparel)
{
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
if ((comp == null || comp.isBeingWorn) && ApparelGraphicRecordGetter.TryGetGraphicApparel(apparel, pawn.story.bodyType, out ApparelGraphicRecord item))
{ pawn.Drawer.renderer.graphics.apparelGraphics.Add(item); }
}
GlobalTextureAtlasManager.TryMarkPawnFrameSetDirty(pawn);
return false;
}
}
}