mirror of
				https://gitgud.io/AbstractConcept/rimworld-animations-patch.git
				synced 2024-08-15 00:43:27 +00:00 
			
		
		
		
	First commit
This commit is contained in:
		
							parent
							
								
									ddda70a258
								
							
						
					
					
						commit
						8e6918ae70
					
				
					 95 changed files with 20766 additions and 1 deletions
				
			
		| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using HarmonyLib;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using Verse;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	[StaticConstructorOnStartup]
 | 
			
		||||
	[HarmonyPatch(typeof(ApparelGraphicRecordGetter), "TryGetGraphicApparel")]
 | 
			
		||||
	public static class HarmonyPatch_ApparelGraphicRecordGetter_TryGetGraphicApparel
 | 
			
		||||
	{
 | 
			
		||||
		public static void Postfix(ref bool __result, ref Apparel apparel, ref BodyTypeDef bodyType, ref ApparelGraphicRecord rec)
 | 
			
		||||
		{
 | 
			
		||||
			if (__result == false || apparel == null || bodyType == null || rec.graphic == null || ApparelSettings.cropApparel == false)
 | 
			
		||||
			{ return; }
 | 
			
		||||
 | 
			
		||||
			// Get graphic
 | 
			
		||||
			Graphic graphic = rec.graphic;
 | 
			
		||||
 | 
			
		||||
			// This graphic may need to be masked if the apparel sits on the skin layer and does not cover the legs
 | 
			
		||||
			if (apparel.def.apparel.LastLayer == ApparelLayerDefOf.OnSkin && apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) && !apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs))
 | 
			
		||||
			{
 | 
			
		||||
				Dictionary<GraphicRequest, Graphic> allGraphics = Traverse.Create(typeof(GraphicDatabase)).Field("allGraphics").GetValue() as Dictionary<GraphicRequest, Graphic>;
 | 
			
		||||
				GraphicRequest graphicRequest = new GraphicRequest(typeof(Graphic_Multi), graphic.path, ShaderDatabase.CutoutComplex, apparel.def.graphicData.drawSize, apparel.DrawColor, apparel.DrawColor, null, 0, null, "Masks/apparel_shirt_mask_" + bodyType.defName);
 | 
			
		||||
 | 
			
		||||
				if (allGraphics.TryGetValue(graphicRequest) == null)
 | 
			
		||||
				{
 | 
			
		||||
					Graphic graphicWithApparelMask = GraphicDatabase.Get<Graphic_Multi>(graphic.path, ShaderDatabase.CutoutComplex, apparel.def.graphicData.drawSize, apparel.DrawColor, apparel.DrawColor, null, "Masks/apparel_shirt_mask_" + bodyType.defName);
 | 
			
		||||
					graphic = GraphicMaskingUtility.ApplyGraphicWithMasks(graphic, graphicWithApparelMask, true);
 | 
			
		||||
					DebugMode.Message("Applying apparel mask: Masks/apparel_shirt_mask_" + bodyType.defName + " to " + apparel.def.defName + " (" + graphic.path + ")");
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			rec = new ApparelGraphicRecord(graphic, apparel);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								Source/Scripts/Patches/HarmonyPatch_CompBodyAnimator.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								Source/Scripts/Patches/HarmonyPatch_CompBodyAnimator.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
using HarmonyLib;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Reflection.Emit;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using Verse;
 | 
			
		||||
using AlienRace;
 | 
			
		||||
using Rimworld_Animations;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	[StaticConstructorOnStartup]
 | 
			
		||||
	[HarmonyPatch(typeof(CompBodyAnimator), "calculateDrawValues")]
 | 
			
		||||
	public static class HarmonyPatch_CompBodyAnimator_calculateDrawValues
 | 
			
		||||
	{
 | 
			
		||||
		public static void Postfix(CompBodyAnimator __instance)
 | 
			
		||||
		{
 | 
			
		||||
			if (__instance?.pawn == null)
 | 
			
		||||
			{ return; }
 | 
			
		||||
 | 
			
		||||
			if (BasicSettings.autoscaleDeltaPos)
 | 
			
		||||
			{
 | 
			
		||||
				__instance.deltaPos.x *= __instance.pawn.RaceProps.baseBodySize;
 | 
			
		||||
				__instance.deltaPos.z *= __instance.pawn.RaceProps.baseBodySize;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (__instance.pawn.IsInBed(out Building bed) &&
 | 
			
		||||
				__instance.pawn.GetAnimationData().animationDef.actors[__instance.pawn.GetAnimationData().actorID].requiredGenitals.NullOrEmpty() == false &&
 | 
			
		||||
				__instance.pawn.GetAnimationData().animationDef.actors[__instance.pawn.GetAnimationData().actorID].requiredGenitals.Contains("Bed"))
 | 
			
		||||
			{
 | 
			
		||||
				__instance.bodyAngle += ((float)bed.Rotation.AsInt - 2f) * 90;
 | 
			
		||||
				if (__instance.bodyAngle < 0) __instance.bodyAngle = 360 - ((-1f * __instance.bodyAngle) % 360);
 | 
			
		||||
				if (__instance.bodyAngle > 360) __instance.bodyAngle %= 360;
 | 
			
		||||
 | 
			
		||||
				__instance.headAngle += ((float)bed.Rotation.AsInt - 2f) * 90;
 | 
			
		||||
				if (__instance.headAngle < 0) __instance.headAngle = 360 - ((-1f * __instance.headAngle) % 360);
 | 
			
		||||
				if (__instance.headAngle > 360) __instance.headAngle %= 360;
 | 
			
		||||
 | 
			
		||||
				__instance.deltaPos = __instance.deltaPos.RotatedBy(-(float)bed.Rotation.AsAngle);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										49
									
								
								Source/Scripts/Patches/HarmonyPatch_DrawGUIOverlay.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								Source/Scripts/Patches/HarmonyPatch_DrawGUIOverlay.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,49 @@
 | 
			
		|||
using HarmonyLib;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Reflection.Emit;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using Verse;
 | 
			
		||||
using AlienRace;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	[StaticConstructorOnStartup]
 | 
			
		||||
	[HarmonyPatch(typeof(Building_Bed), "DrawGUIOverlay")]
 | 
			
		||||
	public static class HarmonyPatch_Building_Bed_DrawGUIOverlay
 | 
			
		||||
	{
 | 
			
		||||
		// Patches beds so sleeping spot names are hidden when the owner is having sex on it
 | 
			
		||||
		public static bool Prefix(Building_Bed __instance)
 | 
			
		||||
		{
 | 
			
		||||
			foreach (Pawn pawn in __instance.OwnersForReading)
 | 
			
		||||
			{
 | 
			
		||||
				if (pawn.GetAnimationData() != null && pawn.IsInBed(out Building bed) && bed == __instance)
 | 
			
		||||
				{ return false; }
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[StaticConstructorOnStartup]
 | 
			
		||||
	[HarmonyPatch(typeof(PawnUIOverlay), "DrawPawnGUIOverlay")]
 | 
			
		||||
	public static class HarmonyPatch_PawnUIOverlay_DrawPawnGUIOverlay
 | 
			
		||||
	{
 | 
			
		||||
		// Patches pawns so their name is hidden when having sex
 | 
			
		||||
		public static bool Prefix(PawnUIOverlay __instance)
 | 
			
		||||
		{
 | 
			
		||||
			if (BasicSettings.hideNamesForSex)
 | 
			
		||||
			{
 | 
			
		||||
				Pawn pawn = (Pawn)AccessTools.Field(typeof(PawnUIOverlay), "pawn").GetValue(__instance);
 | 
			
		||||
 | 
			
		||||
				if (pawn.GetAnimationData() != null)
 | 
			
		||||
				{ return false; }
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								Source/Scripts/Patches/HarmonyPatch_JobDriver.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Source/Scripts/Patches/HarmonyPatch_JobDriver.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using HarmonyLib;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using Verse;
 | 
			
		||||
using Verse.AI;
 | 
			
		||||
using Rimworld_Animations;
 | 
			
		||||
using rjw;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	[HarmonyPatch(typeof(JobDriver), "GetReport")]
 | 
			
		||||
	public static class HarmonyPatch_JobDriver
 | 
			
		||||
	{
 | 
			
		||||
		public static bool Prefix(JobDriver __instance, ref string __result)
 | 
			
		||||
		{
 | 
			
		||||
			JobDriver_Sex jobdriver = __instance as JobDriver_Sex;
 | 
			
		||||
 | 
			
		||||
			if (jobdriver != null && jobdriver.pawn != null && jobdriver.pawn.GetAnimationData() != null && jobdriver.Sexprops.isRape == false && jobdriver.Sexprops.isWhoring == false)
 | 
			
		||||
			{
 | 
			
		||||
				LocalTargetInfo a = jobdriver.job.targetA.IsValid ? jobdriver.job.targetA : jobdriver.job.targetQueueA.FirstValid();
 | 
			
		||||
				LocalTargetInfo b = jobdriver.job.targetB.IsValid ? jobdriver.job.targetB : jobdriver.job.targetQueueB.FirstValid();
 | 
			
		||||
				LocalTargetInfo targetC = jobdriver.job.targetC;
 | 
			
		||||
 | 
			
		||||
				__result = JobUtility.GetResolvedJobReport(jobdriver.pawn.GetAnimationData().animationDef.label, a, b, targetC);
 | 
			
		||||
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								Source/Scripts/Patches/HarmonyPatch_PatchAll.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Source/Scripts/Patches/HarmonyPatch_PatchAll.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Verse;
 | 
			
		||||
using HarmonyLib;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using rjw;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    [StaticConstructorOnStartup]
 | 
			
		||||
    public static class Harmony_PatchAll
 | 
			
		||||
    {
 | 
			
		||||
        static Harmony_PatchAll()
 | 
			
		||||
        {
 | 
			
		||||
            Harmony harmony = new Harmony("Rimworld_Animations_Patch");
 | 
			
		||||
            harmony.PatchAll(Assembly.GetExecutingAssembly());
 | 
			
		||||
 | 
			
		||||
            Quirk voyeur = new Quirk("Voyeur", "VoyeurQuirk", null, null);
 | 
			
		||||
 | 
			
		||||
            if (Quirk.All.Contains(voyeur) == false)
 | 
			
		||||
            { Quirk.All.Add(voyeur); }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										96
									
								
								Source/Scripts/Patches/HarmonyPatch_PawnRenderer.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								Source/Scripts/Patches/HarmonyPatch_PawnRenderer.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,96 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using HarmonyLib;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using Verse;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Reflection.Emit;
 | 
			
		||||
using Rimworld_Animations;
 | 
			
		||||
using rjw;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	[StaticConstructorOnStartup]
 | 
			
		||||
	[HarmonyPatch(typeof(PawnRenderer), "RenderPawnInternal", new Type[]
 | 
			
		||||
	{
 | 
			
		||||
				typeof(Vector3),
 | 
			
		||||
				typeof(float),
 | 
			
		||||
				typeof(bool),
 | 
			
		||||
				typeof(Rot4),
 | 
			
		||||
				typeof(RotDrawMode),
 | 
			
		||||
				typeof(PawnRenderFlags)
 | 
			
		||||
	}
 | 
			
		||||
	)]
 | 
			
		||||
	public static class HarmonyPatch_PawnRenderer_RenderPawnInternal
 | 
			
		||||
	{
 | 
			
		||||
		public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
 | 
			
		||||
		{
 | 
			
		||||
			List<CodeInstruction> ins = instructions.ToList();
 | 
			
		||||
 | 
			
		||||
			for (int i = 0; i < instructions.Count(); i++)
 | 
			
		||||
			{
 | 
			
		||||
				bool runIns = true;
 | 
			
		||||
 | 
			
		||||
				// Replaces the rotation that gets passed to DrawHeadHair with one that is based the current 'true' head orientation
 | 
			
		||||
				if (i + 8 < instructions.Count() && ins[i + 8].opcode == OpCodes.Call && ins[i + 8].operand != null && ins[i + 8].OperandIs(AccessTools.DeclaredMethod(typeof(PawnRenderer), "DrawHeadHair")))
 | 
			
		||||
				{
 | 
			
		||||
					// Get the true head rotation
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Ldarg_0);
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Ldfld, AccessTools.DeclaredField(typeof(PawnRenderer), "pawn"));
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Ldloc, (object)7); // local body facing
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Ldarg_S, (object)6); // renderer flags
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Call, AccessTools.DeclaredMethod(typeof(AnimationPatchUtility), "PawnHeadRotInAnimation"));
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Stloc_S, (object)7); // set local body facing to true head facing
 | 
			
		||||
 | 
			
		||||
					// Pass this head rotation to a new DrawHeadHair call
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Ldarg_0);
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Ldarg_1);
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Ldloc_S, (object)6);
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Ldarg_2);
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Ldloc_S, (object)7); // local true head facing
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Ldloc_S, (object)7); // local true head facing
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Ldarg_S, (object)5); // bodyDrawType
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Ldarg_S, (object)6); // renderer flags
 | 
			
		||||
					yield return new CodeInstruction(OpCodes.Call, AccessTools.DeclaredMethod(typeof(PawnRenderer), "DrawHeadHair"));
 | 
			
		||||
 | 
			
		||||
					// Skip the original call to DrawHeadHair
 | 
			
		||||
					i = i + 8;
 | 
			
		||||
 | 
			
		||||
					runIns = false;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (runIns)
 | 
			
		||||
				{
 | 
			
		||||
					yield return ins[i];
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[StaticConstructorOnStartup]
 | 
			
		||||
	[HarmonyPatch(typeof(PawnGraphicSet), "ResolveAllGraphics")]
 | 
			
		||||
	public static class HarmonyPatch_PawnGraphicSet_ResolveAllGraphics
 | 
			
		||||
	{
 | 
			
		||||
		public static void Postfix(PawnGraphicSet __instance)
 | 
			
		||||
		{
 | 
			
		||||
			if (__instance?.pawn?.apparel == null)
 | 
			
		||||
			{ return; }
 | 
			
		||||
 | 
			
		||||
			if (__instance.pawn.GetAnimationData() != null)
 | 
			
		||||
			{ return; }
 | 
			
		||||
 | 
			
		||||
			if (__instance.pawn.apparel.WornApparel.NullOrEmpty() == false)
 | 
			
		||||
			{
 | 
			
		||||
				foreach(Apparel apparel in __instance.pawn.apparel.WornApparel)
 | 
			
		||||
				{
 | 
			
		||||
					CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
 | 
			
		||||
 | 
			
		||||
					if (comp != null)
 | 
			
		||||
					{ comp.isBeingWorn = true; }
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										460
									
								
								Source/Scripts/Patches/HarmonyPatch_RJW.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										460
									
								
								Source/Scripts/Patches/HarmonyPatch_RJW.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,460 @@
 | 
			
		|||
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;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								Source/Scripts/Patches/HarmonyPatch_RimNudeWorld.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								Source/Scripts/Patches/HarmonyPatch_RimNudeWorld.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Verse;
 | 
			
		||||
using HarmonyLib;
 | 
			
		||||
using RimNudeWorld;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	[StaticConstructorOnStartup]
 | 
			
		||||
	public static class HarmonyPatch_RimNudeWorld
 | 
			
		||||
	{
 | 
			
		||||
		/*static HarmonyPatch_RimNudeWorld()
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
			{
 | 
			
		||||
				((Action)(() =>
 | 
			
		||||
				{
 | 
			
		||||
					if (LoadedModManager.RunningModsListForReading.Any(x => x.PackageIdPlayerFacing == "shauaputa.rimnudeworld"))
 | 
			
		||||
					{
 | 
			
		||||
						(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("RevealingApparel.HarmonyPatch_DrawAddons"), "Postfix"),
 | 
			
		||||
							prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_RimNudeWorld), "Prefix_DrawAddons")));
 | 
			
		||||
					}
 | 
			
		||||
				}))();
 | 
			
		||||
			}
 | 
			
		||||
			catch (TypeLoadException) { }
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Patch RimNudeWorld to override the revealing apparel feature; this task is handled by the new apparel settings system
 | 
			
		||||
		public static bool Prefix_DrawAddons()
 | 
			
		||||
		{
 | 
			
		||||
			return false;
 | 
			
		||||
		}*/
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										369
									
								
								Source/Scripts/Patches/HarmonyPatch_Rimworld_Animations.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										369
									
								
								Source/Scripts/Patches/HarmonyPatch_Rimworld_Animations.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,369 @@
 | 
			
		|||
using HarmonyLib;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using Verse;
 | 
			
		||||
using AlienRace;
 | 
			
		||||
using Rimworld_Animations;
 | 
			
		||||
using rjw;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	[StaticConstructorOnStartup]
 | 
			
		||||
	public static class HarmonyPatch_Rimworld_Animations
 | 
			
		||||
	{
 | 
			
		||||
		static HarmonyPatch_Rimworld_Animations()
 | 
			
		||||
		{
 | 
			
		||||
			(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.AnimationUtility"), "GenitalCheckForPawn"),
 | 
			
		||||
				postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "PostFix_AnimationUtility_GenitalCheckForPawn")));
 | 
			
		||||
			(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.CompBodyAnimator"), "setAnchor", new Type[] { typeof(Thing) }),
 | 
			
		||||
				prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Prefix_HarmonyPatch_CompBodyAnimator_setAnchor")));
 | 
			
		||||
			(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.HarmonyPatch_JobDriver_SexBaseInitiator_Start"), "RerollAnimations"),
 | 
			
		||||
				postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Postfix_RerollAnimations")));
 | 
			
		||||
			(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.HarmonyPatch_AlienRace"), "Prefix_AnimateHeadAddons"),
 | 
			
		||||
				prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Prefix_DrawAddons")));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Extend the animation selector's body part check to include hands and whether the pawn is in bed or not
 | 
			
		||||
		public static void PostFix_AnimationUtility_GenitalCheckForPawn(ref bool __result, List<string> requiredGenitals, Pawn pawn, ref string failReason)
 | 
			
		||||
		{
 | 
			
		||||
			int handCount = 0;
 | 
			
		||||
			bool pawnInBed = pawn.IsInBed(out Building bed);
 | 
			
		||||
 | 
			
		||||
			var hands = pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def.defName == "Hand");
 | 
			
		||||
			if (hands != null)
 | 
			
		||||
			{ handCount = hands.Count(); }
 | 
			
		||||
 | 
			
		||||
			if (requiredGenitals.NullOrEmpty())
 | 
			
		||||
			{ return; }
 | 
			
		||||
 | 
			
		||||
			if (requiredGenitals.Contains("OneHand") && handCount < 1)
 | 
			
		||||
			{ failReason = "missing hand"; __result = false; }
 | 
			
		||||
 | 
			
		||||
			if (requiredGenitals.Contains("TwoHands") && handCount < 2)
 | 
			
		||||
			{ failReason = "missing hand(s)"; __result = false; }
 | 
			
		||||
 | 
			
		||||
			if (requiredGenitals.Contains("Bed") && pawnInBed == false)
 | 
			
		||||
			{ failReason = "pawn is not in bed"; __result = false; }
 | 
			
		||||
 | 
			
		||||
			if (requiredGenitals.Contains("NoBed") && pawnInBed)
 | 
			
		||||
			{ failReason = "pawn is in bed"; __result = false; }
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Override CompBodyAnimator's anchors
 | 
			
		||||
		public static bool Prefix_HarmonyPatch_CompBodyAnimator_setAnchor(CompBodyAnimator __instance, Thing thing)
 | 
			
		||||
		{
 | 
			
		||||
			__instance.anchor = AnimationPatchUtility.GetAnchorPosition(__instance.pawn, thing);
 | 
			
		||||
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Adds functionality to determine which apparel each actor should discard based on the animation they are running
 | 
			
		||||
		public static void Postfix_RerollAnimations(Pawn pawn)
 | 
			
		||||
		{
 | 
			
		||||
			AnimationDef anim = pawn.GetAnimationData()?.animationDef;
 | 
			
		||||
 | 
			
		||||
			if (anim != null)
 | 
			
		||||
			{
 | 
			
		||||
				DebugMode.Message("Running animation: " + anim.defName);
 | 
			
		||||
 | 
			
		||||
				List<Pawn> pawnsToAnimate = pawn.GetAllSexParticipants();
 | 
			
		||||
				Pawn Target = pawn.GetSexReceiver();
 | 
			
		||||
 | 
			
		||||
				foreach (Pawn participant in pawnsToAnimate)
 | 
			
		||||
				{
 | 
			
		||||
					int actorID = (int)AccessTools.Field(typeof(CompBodyAnimator), "actor").GetValue(participant.TryGetComp<CompBodyAnimator>());
 | 
			
		||||
					DebugMode.Message("Participant " + actorID + ": " + participant.NameShortColored);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Determine if a body addon is covered by apparel
 | 
			
		||||
		/*public static bool BodyAddonCoveredByApparel(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
 | 
			
		||||
		{
 | 
			
		||||
			foreach (ApparelGraphicRecord apparelGraphicRecord in pawn.Drawer.renderer.graphics.apparelGraphics)
 | 
			
		||||
			{
 | 
			
		||||
				Apparel apparel = apparelGraphicRecord.sourceApparel;
 | 
			
		||||
 | 
			
		||||
				if (apparel.def.apparel.bodyPartGroups.Any(x => bodyAddon.hiddenUnderApparelFor.Contains(x)))
 | 
			
		||||
				{ return true; }
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return false;
 | 
			
		||||
		}*/
 | 
			
		||||
 | 
			
		||||
		public static bool BodyAddonCoveredByWornApparel(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
 | 
			
		||||
		{
 | 
			
		||||
			if (bodyAddon?.hiddenUnderApparelFor == null || bodyAddon?.hiddenUnderApparelTag == null)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			foreach (Apparel apparel in pawn.apparel.WornApparel)
 | 
			
		||||
			{
 | 
			
		||||
				if (ApparelAnimationUtility.BodyAddonCoveredByApparel(apparel, bodyAddon))
 | 
			
		||||
				{ return true; }
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Determine if a body addon should be drawn
 | 
			
		||||
		public static bool CanDrawAddon(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
 | 
			
		||||
		{
 | 
			
		||||
			if (bodyAddon == null)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			if (pawn.RaceProps.Animal)
 | 
			
		||||
			{ return true; }
 | 
			
		||||
 | 
			
		||||
			Building_Bed building_Bed = pawn.CurrentBed();
 | 
			
		||||
 | 
			
		||||
			if ((building_Bed == null || 
 | 
			
		||||
				building_Bed.def.building.bed_showSleeperBody || 
 | 
			
		||||
				bodyAddon.drawnInBed) && (bodyAddon.backstoryRequirement.NullOrEmpty() || pawn.story.AllBackstories.Any((Backstory x) => x.identifier == bodyAddon.backstoryRequirement)))
 | 
			
		||||
			{
 | 
			
		||||
				if (!bodyAddon.drawnDesiccated)
 | 
			
		||||
				{
 | 
			
		||||
					Corpse corpse = pawn.Corpse;
 | 
			
		||||
					if (corpse != null && corpse.GetRotStage() == RotStage.Dessicated)
 | 
			
		||||
					{ return false; }
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (!bodyAddon.bodyPart.NullOrEmpty() && 
 | 
			
		||||
					!pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null).Any((BodyPartRecord bpr) => bpr.untranslatedCustomLabel == bodyAddon.bodyPart || bpr.def.defName == bodyAddon.bodyPart))
 | 
			
		||||
				{
 | 
			
		||||
					List<AlienPartGenerator.BodyAddonHediffGraphic> list = bodyAddon.hediffGraphics;
 | 
			
		||||
					bool flag;
 | 
			
		||||
					if (list == null)
 | 
			
		||||
					{ flag = false; }
 | 
			
		||||
 | 
			
		||||
					else
 | 
			
		||||
					{ flag = list.Any((AlienPartGenerator.BodyAddonHediffGraphic bahg) => bahg.hediff == HediffDefOf.MissingBodyPart); }
 | 
			
		||||
 | 
			
		||||
					if (!flag)
 | 
			
		||||
					{ return false; }
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if ((pawn.gender == Gender.Female) ? bodyAddon.drawForFemale : bodyAddon.drawForMale)
 | 
			
		||||
				{
 | 
			
		||||
					if (bodyAddon.bodyTypeRequirement.NullOrEmpty() || pawn.story.bodyType.ToString() == bodyAddon.bodyTypeRequirement)
 | 
			
		||||
					{
 | 
			
		||||
						bool renderClothes = true;
 | 
			
		||||
 | 
			
		||||
						if (Find.WindowStack.currentlyDrawnWindow is Page_ConfigureStartingPawns)
 | 
			
		||||
						{ renderClothes = (bool)AccessTools.Field(typeof(Page_ConfigureStartingPawns), "renderClothes").GetValue(Find.WindowStack.currentlyDrawnWindow); }
 | 
			
		||||
 | 
			
		||||
						else
 | 
			
		||||
						{ renderClothes = pawn.Drawer.renderer.graphics.apparelGraphics.Count > 0; }
 | 
			
		||||
 | 
			
		||||
						bool conditionA = !BodyAddonCoveredByWornApparel(pawn, bodyAddon);
 | 
			
		||||
						bool conditionB = !renderClothes;
 | 
			
		||||
						bool conditionC = pawn.GetPosture() == PawnPosture.Standing;
 | 
			
		||||
						bool conditionD = (pawn.GetPosture() == PawnPosture.LayingOnGroundNormal || pawn.GetPosture() == PawnPosture.LayingOnGroundFaceUp) && bodyAddon.drawnOnGround;
 | 
			
		||||
						bool conditionE = pawn.GetPosture() == PawnPosture.LayingInBed && bodyAddon.drawnInBed;
 | 
			
		||||
 | 
			
		||||
						return (conditionA || conditionB) && (conditionC || conditionD || conditionE);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Replacement patch for AlienRace to draw the body addons
 | 
			
		||||
		public static bool Prefix_DrawAddons(PawnRenderFlags renderFlags, Vector3 vector, Vector3 headOffset, Pawn pawn, Quaternion quat, Rot4 rotation)
 | 
			
		||||
		{
 | 
			
		||||
			if (!(pawn.def is ThingDef_AlienRace alienProps) || renderFlags.FlagSet(PawnRenderFlags.Invisible))
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			// Try to draw apparel thrown on ground
 | 
			
		||||
			if (ApparelSettings.clothesThrownOnGround)
 | 
			
		||||
			{ ApparelAnimationUtility.TryToDrawApparelOnFloor(pawn); }
 | 
			
		||||
 | 
			
		||||
			// Get components
 | 
			
		||||
			List<AlienPartGenerator.BodyAddon> bodyAddons = alienProps.alienRace.generalSettings.alienPartGenerator.bodyAddons.ToList();
 | 
			
		||||
			AlienPartGenerator.AlienComp alienComp = pawn.GetComp<AlienPartGenerator.AlienComp>();
 | 
			
		||||
			CompBodyAnimator pawnAnimator = pawn.TryGetComp<CompBodyAnimator>();
 | 
			
		||||
 | 
			
		||||
			// Get available hands
 | 
			
		||||
			var hands = pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def.defName == "Hand");
 | 
			
		||||
			int handsAvailableCount = hands.Count();
 | 
			
		||||
 | 
			
		||||
			// Sort addons by their layer offset, otherwise body parts will actualy be layered according to their position in the list
 | 
			
		||||
			// Note that sorting the addons directly seems to mess up relations between lists need by AlienRace
 | 
			
		||||
			var sortedBodyAddons = bodyAddons.Select((x, i) => new KeyValuePair<AlienPartGenerator.BodyAddon, int>(x, i)).OrderBy(x => x.Key.offsets.GetOffset(rotation).layerOffset).ToList();
 | 
			
		||||
			List<int> idxBodyAddons = sortedBodyAddons.Select(x => x.Value).ToList();
 | 
			
		||||
 | 
			
		||||
			for (int idx = 0; idx < idxBodyAddons.Count; idx++)
 | 
			
		||||
			{
 | 
			
		||||
				int i = idxBodyAddons[idx];
 | 
			
		||||
				AlienPartGenerator.BodyAddon bodyAddon = bodyAddons[i];
 | 
			
		||||
				BodyPartRecord bodyPartRecord = AnimationPatchUtility.GetBodyPartRecord(pawn, bodyAddon.bodyPart);
 | 
			
		||||
 | 
			
		||||
				bool alignWithHead = bodyAddon.alignWithHead || (bodyPartRecord != null && bodyPartRecord.IsInGroup(BodyPartGroupDefOf.FullHead));
 | 
			
		||||
 | 
			
		||||
				Graphic addonGraphic = alienComp.addonGraphics[i];
 | 
			
		||||
				//DebugMode.Message("  Trying to draw " + addonGraphic.path);
 | 
			
		||||
				
 | 
			
		||||
				Rot4 apparentRotation = rotation;
 | 
			
		||||
				if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawnAnimator != null && pawnAnimator.isAnimating)
 | 
			
		||||
				{ apparentRotation = alignWithHead ? pawnAnimator.headFacing : pawnAnimator.bodyFacing; }
 | 
			
		||||
 | 
			
		||||
				AlienPartGenerator.RotationOffset defaultOffsets = bodyAddon.defaultOffsets.GetOffset(apparentRotation);
 | 
			
		||||
				Vector3 bodyTypeOffset = (defaultOffsets != null) ? defaultOffsets.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, alienComp.crownType) : Vector3.zero;
 | 
			
		||||
				AlienPartGenerator.RotationOffset offsets = bodyAddon.offsets.GetOffset(apparentRotation);
 | 
			
		||||
				Vector3 vector2 = bodyTypeOffset + ((offsets != null) ? offsets.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, alienComp.crownType) : Vector3.zero);
 | 
			
		||||
 | 
			
		||||
				// Offset private parts so that they render over tattoos but under apparel (rendering under tatoos looks weird)
 | 
			
		||||
				if ((bodyPartRecord != null && (bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.GenitalsBPG) || bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.ChestBPG) || bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.AnusBPG))) ||
 | 
			
		||||
					addonGraphic.path.ToLower().Contains("belly"))
 | 
			
		||||
				{ 
 | 
			
		||||
					vector2.y = (vector2.y + 0.40f) / 1000f + 0.012f;
 | 
			
		||||
 | 
			
		||||
					// Erected penises should be drawn over apparel
 | 
			
		||||
					if (pawn.RaceProps.Humanlike &&
 | 
			
		||||
						addonGraphic.path.ToLower().Contains("penis") &&
 | 
			
		||||
						addonGraphic.path.ToLower().Contains("flaccid") == false &&
 | 
			
		||||
						BodyAddonCoveredByWornApparel(pawn, bodyAddon) == false &&
 | 
			
		||||
						apparentRotation == Rot4.South)
 | 
			
		||||
					{ vector2.y += 0.010f; }
 | 
			
		||||
				}
 | 
			
		||||
				
 | 
			
		||||
				// Otherwise use the standard offsets
 | 
			
		||||
				else
 | 
			
		||||
				{ vector2.y = 0.3f + vector2.y; }
 | 
			
		||||
 | 
			
		||||
				if (!bodyAddon.inFrontOfBody)
 | 
			
		||||
				{ vector2.y *= -1f; }
 | 
			
		||||
			
 | 
			
		||||
				float bodyAddonAngle = bodyAddon.angle;
 | 
			
		||||
 | 
			
		||||
				if (apparentRotation == Rot4.North)
 | 
			
		||||
				{
 | 
			
		||||
					if (bodyAddon.layerInvert)
 | 
			
		||||
					{ vector2.y = -vector2.y; }
 | 
			
		||||
 | 
			
		||||
					bodyAddonAngle = 0f;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (apparentRotation == Rot4.East)
 | 
			
		||||
				{
 | 
			
		||||
					bodyAddonAngle = -bodyAddonAngle;
 | 
			
		||||
					vector2.x = -vector2.x;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				Quaternion addonRotation = quat;
 | 
			
		||||
				Quaternion quatAdditional = Quaternion.identity;
 | 
			
		||||
 | 
			
		||||
				float finalAngle = 0;
 | 
			
		||||
				
 | 
			
		||||
				if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawnAnimator != null && pawnAnimator.isAnimating)
 | 
			
		||||
				{
 | 
			
		||||
					if (pawnAnimator.controlGenitalAngle && bodyAddon?.hediffGraphics != null && !bodyAddon.hediffGraphics.NullOrEmpty() && bodyAddon.hediffGraphics[0]?.path != null && (bodyAddon.hediffGraphics[0].path.Contains("Penis") || bodyAddon.hediffGraphics[0].path.Contains("penis")))
 | 
			
		||||
					{
 | 
			
		||||
						float bodyAngle = pawnAnimator.bodyAngle;
 | 
			
		||||
						addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
 | 
			
		||||
 | 
			
		||||
						float anglePenis = AnimationSettings.controlGenitalRotation ? pawnAnimator.genitalAngle : 0f;
 | 
			
		||||
						anglePenis = anglePenis < 0 ? 360 - (360 % anglePenis) : anglePenis;
 | 
			
		||||
						quatAdditional = Quaternion.AngleAxis(angle: anglePenis, axis: Vector3.up);
 | 
			
		||||
 | 
			
		||||
						finalAngle = bodyAngle + anglePenis;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					else if (alignWithHead)
 | 
			
		||||
					{
 | 
			
		||||
						float headAngle = pawnAnimator.headAngle;
 | 
			
		||||
						headAngle = headAngle < 0 ? 360 - (360 % headAngle) : headAngle;
 | 
			
		||||
						addonRotation = Quaternion.AngleAxis(angle: headAngle, axis: Vector3.up);
 | 
			
		||||
 | 
			
		||||
						finalAngle = pawnAnimator.bodyAngle + headAngle;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						float bodyAngle = pawnAnimator.bodyAngle;
 | 
			
		||||
						addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
 | 
			
		||||
 | 
			
		||||
						finalAngle = bodyAngle;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Fixes 'leaning left' issue with Yayo's animations
 | 
			
		||||
				else if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && (pawnAnimator == null || pawnAnimator.isAnimating == false))
 | 
			
		||||
				{
 | 
			
		||||
					float bodyAngle = addonRotation.eulerAngles.y;
 | 
			
		||||
					bodyAngle = bodyAngle < 0 ? 360 - (360 % bodyAngle) : bodyAngle;
 | 
			
		||||
					addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (alignWithHead && bodyAddon.alignWithHead == false)
 | 
			
		||||
				{ vector2 -= pawn.Drawer.renderer.BaseHeadOffsetAt(apparentRotation); }
 | 
			
		||||
 | 
			
		||||
				Vector3 finalPosition = vector + (alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(angle: Mathf.Acos(f: Quaternion.Dot(a: Quaternion.identity, b: addonRotation)) * 2f * 57.29578f);
 | 
			
		||||
		
 | 
			
		||||
				// Draw the addon if visible
 | 
			
		||||
				if (CanDrawAddon(pawn, bodyAddon))
 | 
			
		||||
				{ 
 | 
			
		||||
					GenDraw.DrawMeshNowOrLater(mesh: addonGraphic.MeshAt(rot: apparentRotation),
 | 
			
		||||
						loc: finalPosition,
 | 
			
		||||
						quat: Quaternion.AngleAxis(angle: bodyAddonAngle, axis: Vector3.up) * quatAdditional * addonRotation,
 | 
			
		||||
						mat: addonGraphic.MatAt(rot: apparentRotation), renderFlags.FlagSet(PawnRenderFlags.DrawNow));
 | 
			
		||||
				}
 | 
			
		||||
				
 | 
			
		||||
				// Draw hand over the body part if required
 | 
			
		||||
				if (BasicSettings.showHands && !renderFlags.FlagSet(PawnRenderFlags.Portrait) && handsAvailableCount > 0)
 | 
			
		||||
				{ 
 | 
			
		||||
					if (HandAnimationUtility.TryToDrawHand(pawn, addonGraphic.path, finalPosition, finalAngle, rotation, renderFlags))
 | 
			
		||||
					{ handsAvailableCount--; }
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Body addons are sometimes are not appropriately concealed by long hair, so re-draw the pawn's hair here
 | 
			
		||||
			if (pawn.Drawer.renderer.graphics.headGraphic != null)
 | 
			
		||||
			{
 | 
			
		||||
				var methodInfo = AccessTools.Method(typeof(PawnRenderer), "DrawHeadHair", null, null);
 | 
			
		||||
 | 
			
		||||
				Rot4 headFacing = pawnAnimator != null && pawnAnimator.isAnimating && !renderFlags.FlagSet(PawnRenderFlags.Portrait) ? pawnAnimator.headFacing : rotation;
 | 
			
		||||
				float headAngle = pawnAnimator != null && pawnAnimator.isAnimating && !renderFlags.FlagSet(PawnRenderFlags.Portrait) ? pawnAnimator.headAngle : quat.eulerAngles.y;
 | 
			
		||||
				RotDrawMode rotDrawMode = (RotDrawMode)AccessTools.Property(typeof(PawnRenderer), "CurRotDrawMode").GetValue(pawn.Drawer.renderer);
 | 
			
		||||
 | 
			
		||||
				methodInfo.Invoke(pawn.Drawer.renderer, new object[] { vector + new Vector3(0f, YOffset_Head, 0f), headOffset, headAngle, headFacing, headFacing, rotDrawMode, renderFlags });
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// List of potentially useful layer offsets
 | 
			
		||||
		//private const float YOffset_CarriedThingUnder = -0.0028957527f;
 | 
			
		||||
		//private const float YOffset_Align_Behind = 0.0028957527f;
 | 
			
		||||
		//private const float YOffset_Body = 0.008687258f;
 | 
			
		||||
		//private const float YOffset_Interval_Clothes = 0.0028957527f;
 | 
			
		||||
		//private const float YOffset_Shell = 0.02027027f;
 | 
			
		||||
		private const float YOffset_Head = 0.023166021f;
 | 
			
		||||
		private const float YOffset_OnHead = 0.028957527f;
 | 
			
		||||
		//private const float YOffset_PostHead = 0.03185328f;
 | 
			
		||||
		//private const float YOffset_Tattoo = 0.0014478763f;
 | 
			
		||||
		//private const float YOffset_WoundOverlays1 = 0.009687258f;
 | 
			
		||||
		//private const float YOffset_WoundOverlays2 = 0.022166021f;
 | 
			
		||||
 | 
			
		||||
		/* Details on the above
 | 
			
		||||
		Body = rootLoc + YOffset_Body; (~ 0.009)
 | 
			
		||||
		Tattoo = rootLoc + YOffset_Body + YOffset_Tattoo; (~ 0.010)
 | 
			
		||||
		BodyAddons (not protruding) = rootLoc + 0.011f; (~0.011)
 | 
			
		||||
		Body wounds (under clothes) = rootLoc + YOffset_WoundOverlays1; (~ 0.010)
 | 
			
		||||
		Apparel (not north) = rootLoc + YOffset_Shell; (~ 0.020)
 | 
			
		||||
		BodyAddons (protruding) = rootLoc + 0.011f + 0.010f; (~0.021)
 | 
			
		||||
		Apparel (north) = rootLoc + YOffset_Head; (~ 0.023)
 | 
			
		||||
		Body wounds (over clothes) = rootLoc + YOffset_WoundOverlays1 + YOffset_WoundOverlays2; (~ 0.03)
 | 
			
		||||
		
 | 
			
		||||
		Head (not north) = rootLoc + YOffset_Head (~ 0.023);
 | 
			
		||||
		Head (north) = rootLoc + YOffset_Shell; (~ 0.020)
 | 
			
		||||
		Face tattoo = rootLoc + YOffset_OnHead - YOffset_Tattoo; (~ 0.028)
 | 
			
		||||
		Head wounds (under clothes) = rootLoc + YOffset_OnHead; (~ 0.029)
 | 
			
		||||
		Hair = rootLoc + YOffset_OnHead; (~ 0.029)
 | 
			
		||||
		Hat (over hair) = rootLoc + YOffset_PostHead; (~ 0.031)
 | 
			
		||||
		*/
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										76
									
								
								Source/Scripts/Patches/HarmonyPatch_VisiblePants.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								Source/Scripts/Patches/HarmonyPatch_VisiblePants.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,76 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using HarmonyLib;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using Verse;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	[StaticConstructorOnStartup]
 | 
			
		||||
	public static class HarmonyPatch_VisiblePants
 | 
			
		||||
	{
 | 
			
		||||
		static HarmonyPatch_VisiblePants()
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
			{
 | 
			
		||||
				((Action)(() =>
 | 
			
		||||
				{
 | 
			
		||||
					if (LoadedModManager.RunningModsListForReading.Any(x => x.PackageIdPlayerFacing == "XeoNovaDan.VisiblePants"))
 | 
			
		||||
					{
 | 
			
		||||
						(new Harmony("HeyLover")).Patch(AccessTools.Method(typeof(ApparelGraphicRecordGetter), "TryGetGraphicApparel"),
 | 
			
		||||
							prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_VisiblePants), "Prefix_ApparelGraphicRecordGetter_TryGetGraphicApparel")));
 | 
			
		||||
					}
 | 
			
		||||
				}))();
 | 
			
		||||
			}
 | 
			
		||||
			catch (TypeLoadException) { }
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool Prefix_ApparelGraphicRecordGetter_TryGetGraphicApparel(ref bool __result, ref Apparel apparel, ref BodyTypeDef bodyType, out ApparelGraphicRecord rec)
 | 
			
		||||
		{
 | 
			
		||||
			rec = new ApparelGraphicRecord(null, null);
 | 
			
		||||
 | 
			
		||||
			if (bodyType == null)
 | 
			
		||||
			{
 | 
			
		||||
				Log.Error("Found apparel graphic with undefined body type.");
 | 
			
		||||
				bodyType = BodyTypeDefOf.Male;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (apparel == null || apparel.WornGraphicPath.NullOrEmpty())
 | 
			
		||||
			{
 | 
			
		||||
				rec = new ApparelGraphicRecord(null, null);
 | 
			
		||||
				__result = false;
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			string path;
 | 
			
		||||
 | 
			
		||||
			if (apparel.def.apparel.LastLayer == ApparelLayerDefOf.Overhead || apparel.def.apparel.LastLayer == ApparelLayerDefOf.EyeCover || PawnRenderer.RenderAsPack(apparel) || apparel.WornGraphicPath == BaseContent.PlaceholderImagePath || apparel.WornGraphicPath == BaseContent.PlaceholderGearImagePath)
 | 
			
		||||
			{ path = apparel.WornGraphicPath; }
 | 
			
		||||
 | 
			
		||||
			else
 | 
			
		||||
			{ path = apparel.WornGraphicPath + "_" + bodyType.defName; }
 | 
			
		||||
 | 
			
		||||
			Shader shader = ShaderDatabase.Cutout;
 | 
			
		||||
 | 
			
		||||
			if (apparel.def.apparel.useWornGraphicMask)
 | 
			
		||||
			{ shader = ShaderDatabase.CutoutComplex; }
 | 
			
		||||
 | 
			
		||||
			// Load the standard apparel graphic
 | 
			
		||||
			Graphic graphic = GraphicDatabase.Get<Graphic_Multi>(path, shader, apparel.def.graphicData.drawSize, apparel.DrawColor);
 | 
			
		||||
 | 
			
		||||
			// This graphic may need to be masked if the apparel sits on the skin layer and does not cover the legs
 | 
			
		||||
			if (apparel.def.apparel.LastLayer == ApparelLayerDefOf.OnSkin && apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) && !apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs))
 | 
			
		||||
			{
 | 
			
		||||
				Graphic graphicWithApparelMask = GraphicDatabase.Get<Graphic_Multi>(path, ShaderDatabase.CutoutComplex, apparel.def.graphicData.drawSize, apparel.DrawColor, apparel.DrawColor, null, "Masks/apparel_shirt_mask_" + bodyType.defName);
 | 
			
		||||
				graphic = GraphicMaskingUtility.ApplyGraphicWithMasks(graphic, graphicWithApparelMask, true);
 | 
			
		||||
				//Log.Message("Applying apparel mask: Masks/apparel_shirt_mask_" + bodyType.defName + " to " + apparel.def.defName);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			rec = new ApparelGraphicRecord(graphic, apparel);
 | 
			
		||||
			__result = true;
 | 
			
		||||
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue