mirror of
https://gitgud.io/AbstractConcept/rimworld-animations-patch.git
synced 2024-08-15 00:43:27 +00:00
003b67fb97
- Fixed an issue with the orgasm not filling in some circumstances - Fixed a bug with sex overdrive not applying correctly - Fixed an issue with 'on orgasm' events not triggering correctly
367 lines
14 KiB
C#
367 lines
14 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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
[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 = null;
|
|
sexProps.sexType = xxx.rjwSextype.Masturbation;
|
|
|
|
IEnumerable<InteractionDef> interactionDefs = DefDatabase<InteractionDef>.AllDefs.Where(x => x.HasModExtension<InteractionExtension>());
|
|
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
|
|
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 = 1;
|
|
|
|
// 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; }
|
|
|
|
IEnumerable<AnimationDef> kissingAnims = DefDatabase<AnimationDef>.AllDefs.Where(x => x.defName.Contains("Kiss"));
|
|
AnimationDef anim = kissingAnims.ElementAt(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), "Orgasm")]
|
|
public static class HarmonyPatch_JobDriver_Sex_Orgasm
|
|
{
|
|
|
|
public static void Prefix(ref JobDriver_Sex __instance, out bool __state)
|
|
{
|
|
__state = __instance.sex_ticks > __instance.orgasmstick;
|
|
}
|
|
|
|
// Alows the starting of a new animation cycle at the end of the current one
|
|
public static void Postfix(ref JobDriver_Sex __instance, bool __state)
|
|
{
|
|
if (__state || __instance.pawn.TryGetComp<CompBodyAnimator>()?.isAnimating != true) return;
|
|
|
|
DebugMode.Message(__instance.pawn.NameShortColored + " reached orgasm");
|
|
|
|
int duration = AnimationPatchUtility.FindTrueAnimationLength(__instance.pawn, out int orgasmTick, true);
|
|
__instance.sex_ticks = duration;
|
|
__instance.duration = duration;
|
|
|
|
if (__instance.neverendingsex)
|
|
{
|
|
__instance.ticks_left = duration + (duration - orgasmTick) + 1;
|
|
__instance.ticksLeftThisToil = __instance.ticks_left + 1;
|
|
|
|
List<Pawn> participants = __instance.pawn.GetAllSexParticipants();
|
|
foreach (Pawn participant in participants)
|
|
{
|
|
JobDriver_Sex jobDriver = participant.jobs.curDriver as JobDriver_Sex;
|
|
|
|
if (participant != __instance.pawn && jobDriver != null)
|
|
{
|
|
jobDriver.ticks_left = duration + (duration - orgasmTick) + 1;
|
|
jobDriver.ticksLeftThisToil = jobDriver.ticks_left;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[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.Dead == false && __instance.Partner?.jobs?.curDriver != null && __instance.Partner?.jobs?.curDriver is JobDriver_SexBaseReciever)
|
|
{
|
|
List<Pawn> participants = (__instance.Partner?.jobs.curDriver as JobDriver_SexBaseReciever).parteners.ToList();
|
|
|
|
foreach (Pawn participant in participants)
|
|
{
|
|
if (__instance.pawn != participant && __instance.Partner?.jobs?.curDriver is JobDriver_Sex)
|
|
{ participant.jobs.EndCurrentJob(JobCondition.Succeeded, false, true); }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[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.ContainsAny("mouth", "teeth", "jaw", "beak", "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 (pawn == null || !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 == true && ApparelGraphicRecordGetter.TryGetGraphicApparel(apparel, pawn.story.bodyType, out ApparelGraphicRecord item))
|
|
{ pawn.Drawer.renderer.graphics.apparelGraphics.Add(item); }
|
|
}
|
|
|
|
pawn?.TryGetComp<CompPawnSexData>()?.UpdateBodyAddonVisibility();
|
|
GlobalTextureAtlasManager.TryMarkPawnFrameSetDirty(pawn);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|