mirror of
https://gitgud.io/AbstractConcept/rimworld-animations-patch.git
synced 2024-08-15 00:43:27 +00:00
460 lines
16 KiB
C#
460 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;
|
|
}
|
|
}
|
|
}
|