mirror of
https://gitgud.io/AbstractConcept/rimworld-animations-patch.git
synced 2024-08-15 00:43:27 +00:00
767317773b
Change log v 2.0.1 - Fixed issue with a hand animation calling a missing method - Fixed errored that was triggering at the end of sex - Dependency on Humanoid Alien Race is now listed and enforced in the mod load screen - Made XML patching more robust
387 lines
14 KiB
C#
387 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 = 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; }
|
|
|
|
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
|
|
{
|
|
// Stops orgasm triggering more than once per animation
|
|
public static bool Prefix(ref JobDriver_Sex __instance)
|
|
{
|
|
if (__instance.orgasms > 0)
|
|
{ return false; }
|
|
|
|
return true;
|
|
}
|
|
|
|
// Causes too much trouble...
|
|
/*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.Dead == false && __instance.Partner?.jobs?.curDriver != null && __instance.Partner?.jobs?.curDriver is JobDriver_SexBaseReciever)
|
|
{
|
|
foreach (Pawn participant in (__instance.Partner?.jobs.curDriver as JobDriver_SexBaseReciever).parteners)
|
|
{
|
|
if (__instance.pawn != participant)
|
|
{ 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;
|
|
}
|
|
}
|
|
}
|