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
				
			
		
							
								
								
									
										75
									
								
								Source/Scripts/Comps/CompApparelVisibility.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								Source/Scripts/Comps/CompApparelVisibility.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,75 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using Verse;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using Rimworld_Animations;
 | 
			
		||||
using HarmonyLib;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    public class CompApparelVisibility : ThingComp
 | 
			
		||||
    {
 | 
			
		||||
        public Apparel apparel => base.parent as Apparel;
 | 
			
		||||
        public Vector3 position;
 | 
			
		||||
        public float rotation = 0f;
 | 
			
		||||
        public bool isBeingWorn = true;
 | 
			
		||||
 | 
			
		||||
        private IntVec3 cellPosition;
 | 
			
		||||
 | 
			
		||||
        public override void PostExposeData()
 | 
			
		||||
        {
 | 
			
		||||
            base.PostExposeData();
 | 
			
		||||
            Scribe_Values.Look(ref position, "position", default);
 | 
			
		||||
            Scribe_Values.Look(ref cellPosition, "cellPosition", default);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void GenerateFloorPosition(IntVec3 apparelCell, Vector2 apparelOffset = default)
 | 
			
		||||
        {
 | 
			
		||||
            Pawn pawn = apparel.Wearer;
 | 
			
		||||
 | 
			
		||||
            // Reuse an old location for thrown clothes if the wearer is not too far away from it
 | 
			
		||||
            if ((cellPosition - pawn.Position).LengthManhattan <= 2 && cellPosition.GetRoom(pawn.Map) == pawn.GetRoom())
 | 
			
		||||
            { return; }
 | 
			
		||||
 | 
			
		||||
            CompBodyAnimator comp = pawn.TryGetComp<CompBodyAnimator>();
 | 
			
		||||
            if (comp == null || comp.isAnimating == false)
 | 
			
		||||
            { return; }
 | 
			
		||||
 | 
			
		||||
            cellPosition = apparelCell;
 | 
			
		||||
            apparel.Rotation = Rot4.Random;
 | 
			
		||||
 | 
			
		||||
            Vector3 offset = new Vector3(Rand.Gaussian(apparelOffset.x, apparelOffset.y), 0f, Rand.Gaussian(apparelOffset.x, apparelOffset.y));
 | 
			
		||||
            position = cellPosition.ToVector3() + offset + new Vector3(0.5f, AltitudeLayer.ItemImportant.AltitudeFor() - Mathf.Clamp(apparel.def.apparel.LastLayer.drawOrder/100000f, 0f, 1f), 0.5f);
 | 
			
		||||
            rotation = 120 * (-1f + 2f * Rand.Value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /*public bool IsBeingWorn()
 | 
			
		||||
        {
 | 
			
		||||
            Pawn pawn = apparel.Wearer;
 | 
			
		||||
 | 
			
		||||
            if (apparel.def.apparel.wornGraphicPath.NullOrEmpty())
 | 
			
		||||
            { return true; }
 | 
			
		||||
 | 
			
		||||
            foreach (ApparelGraphicRecord record in pawn.Drawer.renderer.graphics.apparelGraphics)
 | 
			
		||||
            {
 | 
			
		||||
                if (record.sourceApparel == apparel)
 | 
			
		||||
                { return true; }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }*/
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//var methodInfo = AccessTools.Method(typeof(GenPlace), "TryFindPlaceSpotNear", null, null);
 | 
			
		||||
//object[] parameters = new object[] { apparel.Wearer.Position, default(Rot4), apparel.Wearer.Map, apparel.Wearer, false, null, null };
 | 
			
		||||
//object result = methodInfo.Invoke(null, parameters);
 | 
			
		||||
 | 
			
		||||
/*bool _result = (bool)result;
 | 
			
		||||
if (_result)
 | 
			
		||||
{
 | 
			
		||||
    bestSpot = (IntVec3)parameters[5];
 | 
			
		||||
    DebugMode.Message("Best position: " + bestSpot.ToString());
 | 
			
		||||
}*/
 | 
			
		||||
							
								
								
									
										15
									
								
								Source/Scripts/Comps/CompProperties_ApparelVisibility.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Source/Scripts/Comps/CompProperties_ApparelVisibility.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using Verse;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    public class CompProperties_ApparelVisibility : CompProperties
 | 
			
		||||
    {
 | 
			
		||||
        public CompProperties_ApparelVisibility()
 | 
			
		||||
        {
 | 
			
		||||
            base.compClass = typeof(CompApparelVisibility);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								Source/Scripts/Defs/ActorAnimationData.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								Source/Scripts/Defs/ActorAnimationData.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using HarmonyLib;
 | 
			
		||||
using Rimworld_Animations;
 | 
			
		||||
using Verse;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    public class ActorAnimationData
 | 
			
		||||
    {
 | 
			
		||||
        public AnimationDef animationDef = null;
 | 
			
		||||
        public int actorID = 0;
 | 
			
		||||
        public int currentStage = 0;
 | 
			
		||||
        public int stageTicks = 0;
 | 
			
		||||
        public Rot4 actorFacing = Rot4.South;
 | 
			
		||||
 | 
			
		||||
        public ActorAnimationData(AnimationDef animationDef, int actorID, int currentStage, int stageTicks, Rot4 actorFacing)
 | 
			
		||||
        {
 | 
			
		||||
            this.animationDef = animationDef;
 | 
			
		||||
            this.actorID = actorID;
 | 
			
		||||
            this.currentStage = currentStage;
 | 
			
		||||
            this.stageTicks = stageTicks;
 | 
			
		||||
            this.actorFacing = actorFacing;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								Source/Scripts/Defs/HandAnimationDef.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Source/Scripts/Defs/HandAnimationDef.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Verse;
 | 
			
		||||
using Rimworld_Animations;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    public class HandAnimationDef : Def
 | 
			
		||||
    {
 | 
			
		||||
        public string animationDefName;      
 | 
			
		||||
        
 | 
			
		||||
        public List<HandAnimationData> handAnimationData = new List<HandAnimationData>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class HandAnimationData
 | 
			
		||||
    {
 | 
			
		||||
        public int stageID = 0;
 | 
			
		||||
        public int actorID = 0;
 | 
			
		||||
        public int touchingActorID = -1;
 | 
			
		||||
        public string targetBodyPart;
 | 
			
		||||
        public string bodySide = "";
 | 
			
		||||
        public List<string> targetBodyParts = new List<string>();
 | 
			
		||||
        public string motion;
 | 
			
		||||
        public int cycleTime = 0;
 | 
			
		||||
        public bool mirror = false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								Source/Scripts/Defs/RimNudeData.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								Source/Scripts/Defs/RimNudeData.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,60 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Verse;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	public class RimNudeData : IExposable
 | 
			
		||||
	{
 | 
			
		||||
		public string thingDef = "Invalid";
 | 
			
		||||
		public bool coversGroin = false;
 | 
			
		||||
		public bool coversBelly = false;
 | 
			
		||||
		public bool coversChest = false;
 | 
			
		||||
		public bool sexWear = false;
 | 
			
		||||
 | 
			
		||||
		public RimNudeData() { }
 | 
			
		||||
 | 
			
		||||
		public RimNudeData(ThingDef thingDef)
 | 
			
		||||
		{
 | 
			
		||||
			this.thingDef = thingDef.defName;
 | 
			
		||||
 | 
			
		||||
			if (thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs) || thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.GenitalsBPG))
 | 
			
		||||
			{ coversGroin = true; }
 | 
			
		||||
 | 
			
		||||
			if (thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso))
 | 
			
		||||
			{ coversBelly = true; }
 | 
			
		||||
 | 
			
		||||
			if (thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) || thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.ChestBPG))
 | 
			
		||||
			{ coversChest = true; }
 | 
			
		||||
 | 
			
		||||
			this.sexWear = false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public RimNudeData(string thingDef, bool coversGroin, bool coversBelly, bool coversChest, bool sexWear)
 | 
			
		||||
		{
 | 
			
		||||
			this.thingDef = thingDef;
 | 
			
		||||
			this.coversGroin = coversGroin;
 | 
			
		||||
			this.coversBelly = coversBelly;
 | 
			
		||||
			this.coversChest = coversChest;
 | 
			
		||||
			this.sexWear = sexWear;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public bool EquivalentTo(RimNudeData other)
 | 
			
		||||
		{
 | 
			
		||||
			return (thingDef == other.thingDef);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public void ExposeData()
 | 
			
		||||
		{
 | 
			
		||||
			Scribe_Values.Look(ref this.thingDef, "thingDef", "Invalid");
 | 
			
		||||
			Scribe_Values.Look(ref this.coversGroin, "coversGroin", false);
 | 
			
		||||
			Scribe_Values.Look(ref this.coversBelly, "coversBelly", false);
 | 
			
		||||
			Scribe_Values.Look(ref this.coversChest, "coversChest", false);
 | 
			
		||||
			Scribe_Values.Look(ref this.sexWear, "sexWear", false);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								Source/Scripts/Enums.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Source/Scripts/Enums.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    public enum TabooStatus
 | 
			
		||||
    {
 | 
			
		||||
        NotTaboo = 0,
 | 
			
		||||
        MinorTaboo = 1,
 | 
			
		||||
        MajorTaboo = 2,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										284
									
								
								Source/Scripts/Extensions/PawnExtension.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										284
									
								
								Source/Scripts/Extensions/PawnExtension.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,284 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using Verse;
 | 
			
		||||
using Verse.AI;
 | 
			
		||||
using Verse.AI.Group;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using rjw;
 | 
			
		||||
using Rimworld_Animations;
 | 
			
		||||
using HarmonyLib;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    public static class PawnExtension
 | 
			
		||||
    {
 | 
			
		||||
        public static bool IsInBed(this Pawn pawn, out Building bed)
 | 
			
		||||
        {
 | 
			
		||||
			bed = pawn.Position.GetThingList(pawn.Map).FirstOrDefault(x => x is Building_Bed) as Building;
 | 
			
		||||
			return bed != null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        public static bool IsSeated(this Pawn pawn, out Building seat)
 | 
			
		||||
        {
 | 
			
		||||
			seat = pawn.Position.GetThingList(pawn.Map).FirstOrDefault(x => x is Building && x.def.building.isSittable) as Building;
 | 
			
		||||
			return seat != null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		public static bool IsHavingSex(this Pawn pawn)
 | 
			
		||||
		{
 | 
			
		||||
			if (pawn?.jobs?.curDriver == null || pawn.Dead || pawn.jobs.curDriver is JobDriver_Sex == false)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			JobDriver_Sex jobdriver = pawn.jobs.curDriver as JobDriver_Sex;
 | 
			
		||||
 | 
			
		||||
			return jobdriver.Partner != null && jobdriver.Partner != pawn;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool IsMasturbating(this Pawn pawn)
 | 
			
		||||
		{
 | 
			
		||||
			if (pawn?.jobs?.curDriver == null || pawn.Dead || pawn.jobs.curDriver is JobDriver_Sex == false)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			JobDriver_Sex jobdriver = pawn.jobs.curDriver as JobDriver_Sex;
 | 
			
		||||
 | 
			
		||||
			return jobdriver.Partner == null || jobdriver.Partner == pawn || (jobdriver.Partner is Pawn) == false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Pawn GetSexInitiator(this Pawn pawn)
 | 
			
		||||
		{
 | 
			
		||||
			if (pawn?.jobs?.curDriver != null && pawn.Dead == false && pawn.jobs.curDriver is JobDriver_SexBaseInitiator)
 | 
			
		||||
			{ return pawn; }
 | 
			
		||||
 | 
			
		||||
			JobDriver_SexBaseReciever jobDriver = pawn.jobs.curDriver as JobDriver_SexBaseReciever;
 | 
			
		||||
 | 
			
		||||
			if (jobDriver?.Partner?.jobs?.curDriver != null && jobDriver.Partner.Dead == false && jobDriver.Partner.jobs.curDriver is JobDriver_SexBaseInitiator)
 | 
			
		||||
			{ return jobDriver.Partner; }
 | 
			
		||||
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Pawn GetSexReceiver(this Pawn pawn)
 | 
			
		||||
		{
 | 
			
		||||
			if (pawn.jobs.curDriver is JobDriver_SexBaseReciever)
 | 
			
		||||
			{ return pawn; }
 | 
			
		||||
 | 
			
		||||
			JobDriver_SexBaseInitiator jobDriver = pawn.jobs.curDriver as JobDriver_SexBaseInitiator;
 | 
			
		||||
 | 
			
		||||
			if (jobDriver?.Partner?.jobs?.curDriver != null && jobDriver.Partner.Dead == false && jobDriver.Partner.jobs.curDriver is JobDriver_SexBaseReciever)
 | 
			
		||||
			{ return jobDriver.Partner; }
 | 
			
		||||
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Pawn GetSexPartner(this Pawn pawn)
 | 
			
		||||
		{
 | 
			
		||||
			return (pawn.jobs.curDriver as JobDriver_Sex)?.Partner;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static List<Pawn> GetAllSexParticipants(this Pawn pawn)
 | 
			
		||||
		{
 | 
			
		||||
			List<Pawn> participants = new List<Pawn>();
 | 
			
		||||
 | 
			
		||||
			if (pawn?.jobs?.curDriver == null || (pawn.jobs.curDriver is JobDriver_Sex) == false)
 | 
			
		||||
			{ return participants; }
 | 
			
		||||
		
 | 
			
		||||
			if (pawn.GetSexReceiver() != null)
 | 
			
		||||
			{ 
 | 
			
		||||
				List<Pawn> partners = (pawn.GetSexReceiver().jobs.curDriver as JobDriver_SexBaseReciever).parteners.ToList();
 | 
			
		||||
 | 
			
		||||
				if (partners != null)
 | 
			
		||||
				{
 | 
			
		||||
					foreach (Pawn partner in partners)
 | 
			
		||||
					{
 | 
			
		||||
						if (partner != null)
 | 
			
		||||
						{ participants = partners; }
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			if (pawn.GetSexInitiator() != null)
 | 
			
		||||
			{
 | 
			
		||||
				Pawn partner = (pawn.GetSexInitiator().jobs.curDriver as JobDriver_SexBaseInitiator).Partner;
 | 
			
		||||
				
 | 
			
		||||
				if (partner != null && partner.Dead == false)
 | 
			
		||||
				{ participants.AddDistinct(partner); }
 | 
			
		||||
			}
 | 
			
		||||
		
 | 
			
		||||
			participants.AddDistinct(pawn);	
 | 
			
		||||
			participants.SortBy(x => x.GetAnimationData() != null ? x.GetAnimationData().actorID : participants.IndexOf(x));
 | 
			
		||||
		
 | 
			
		||||
			return participants;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool IsLoverOfOther(this Pawn pawn, Pawn other)
 | 
			
		||||
		{
 | 
			
		||||
			if (pawn == null || other == null)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			List<DirectPawnRelation> lovers = SpouseRelationUtility.GetLoveRelations(pawn, false);
 | 
			
		||||
			return lovers.Any(x => x.otherPawn == other);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool HasPrivacy(this Pawn pawn, float radius)
 | 
			
		||||
		{
 | 
			
		||||
			if (pawn.AnimalOrWildMan() || pawn.RaceProps.Humanlike == false)
 | 
			
		||||
			{ return true; }
 | 
			
		||||
 | 
			
		||||
			if (pawn.IsHavingSex() == false && pawn.IsMasturbating() == false)
 | 
			
		||||
			{ return true; }
 | 
			
		||||
 | 
			
		||||
			bool hasPrivacy = true;
 | 
			
		||||
			bool isExhibitionist = pawn.HasTrait("Exhibitionist") || xxx.has_quirk(pawn, "Exhibitionist");
 | 
			
		||||
 | 
			
		||||
			pawn.IsInBed(out Building bed);
 | 
			
		||||
 | 
			
		||||
			foreach (Thing thing in GenRadial.RadialDistinctThingsAround(pawn.Position, pawn.Map, radius, true))
 | 
			
		||||
			{
 | 
			
		||||
				Pawn witness = thing as Pawn;
 | 
			
		||||
 | 
			
		||||
				// Caught having sex
 | 
			
		||||
				if (SexInteractionUtility.PawnCaughtLovinByWitness(pawn, witness))
 | 
			
		||||
				{
 | 
			
		||||
					SexInteractionUtility.ResolveThoughtsForWhenSexIsWitnessed(pawn, witness, out bool witnessJoiningSex);
 | 
			
		||||
 | 
			
		||||
					// Try to invite intruder to join in
 | 
			
		||||
					if (witnessJoiningSex)
 | 
			
		||||
					{
 | 
			
		||||
						if (pawn.IsMasturbating())
 | 
			
		||||
						{
 | 
			
		||||
							if (bed == null)
 | 
			
		||||
							{ 
 | 
			
		||||
								Job job = new Job(xxx.quick_sex, pawn);
 | 
			
		||||
								witness.jobs.TryTakeOrderedJob(job);
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							else
 | 
			
		||||
							{
 | 
			
		||||
								Job job = new Job(xxx.casual_sex, pawn, bed);
 | 
			
		||||
								witness.jobs.TryTakeOrderedJob(job);
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						else if (pawn.GetSexReceiver() != null)
 | 
			
		||||
						{
 | 
			
		||||
							Job job = new Job(DefDatabase<JobDef>.GetNamed("JoinInSex", false), pawn.GetSexReceiver(), bed);
 | 
			
		||||
							witness.jobs.TryTakeOrderedJob(job);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// The invitation failed
 | 
			
		||||
					else
 | 
			
		||||
					{ hasPrivacy = false; }
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return hasPrivacy || isExhibitionist || BasicSettings.needPrivacy == false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static ActorAnimationData GetAnimationData(this Pawn pawn)
 | 
			
		||||
		{
 | 
			
		||||
			if (pawn.TryGetComp<CompBodyAnimator>() == null) return null;
 | 
			
		||||
			if (pawn.TryGetComp<CompBodyAnimator>().isAnimating == false) return null;
 | 
			
		||||
 | 
			
		||||
			AnimationDef animationDef = (AnimationDef)AccessTools.Field(typeof(CompBodyAnimator), "anim").GetValue(pawn.TryGetComp<CompBodyAnimator>());
 | 
			
		||||
			int actorID = (int)AccessTools.Field(typeof(CompBodyAnimator), "actor").GetValue(pawn.TryGetComp<CompBodyAnimator>());
 | 
			
		||||
			int currentStage = (int)AccessTools.Field(typeof(CompBodyAnimator), "curStage").GetValue(pawn.TryGetComp<CompBodyAnimator>());
 | 
			
		||||
			int stageTicks = (int)AccessTools.Field(typeof(CompBodyAnimator), "stageTicks").GetValue(pawn.TryGetComp<CompBodyAnimator>());
 | 
			
		||||
			Rot4 actorFacing = (Rot4)AccessTools.Field(typeof(CompBodyAnimator), "bodyFacing").GetValue(pawn.TryGetComp<CompBodyAnimator>());
 | 
			
		||||
 | 
			
		||||
			return new ActorAnimationData(animationDef, actorID, currentStage, stageTicks, actorFacing);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static List<BodyPartRecord> GetHands(this Pawn pawn)
 | 
			
		||||
		{
 | 
			
		||||
			if (HandAnimationUtility.handDef == null)
 | 
			
		||||
			{ HandAnimationUtility.handDef = DefDatabase<BodyPartDef>.GetNamed("Hand", false); }
 | 
			
		||||
 | 
			
		||||
			return pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def == HandAnimationUtility.handDef)?.ToList();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool HasPreceptForIssue(this Pawn pawn, IssueDef issueDef, out Precept precept)
 | 
			
		||||
		{
 | 
			
		||||
			precept = null;
 | 
			
		||||
 | 
			
		||||
			if (pawn?.Ideo == null || issueDef == null)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			foreach (Precept _precept in pawn.Ideo.PreceptsListForReading)
 | 
			
		||||
			{
 | 
			
		||||
				if (_precept.def.issue == issueDef)
 | 
			
		||||
				{
 | 
			
		||||
					precept = _precept;
 | 
			
		||||
					return true; 
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool IssueIsMajorTaboo(this Pawn pawn, IssueDef issueDef, out Precept precept)
 | 
			
		||||
		{
 | 
			
		||||
			if (HasPreceptForIssue(pawn, issueDef, out precept))
 | 
			
		||||
			{
 | 
			
		||||
				if (precept.def.defName.Contains("Forbidden") || precept.def.defName.Contains("Prohibited") || precept.def.defName.Contains("Abhorrent"))
 | 
			
		||||
				{ return true; }
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool IssueIsMinorTaboo(this Pawn pawn, IssueDef issueDef, out Precept precept)
 | 
			
		||||
		{
 | 
			
		||||
			if (HasPreceptForIssue(pawn, issueDef, out precept))
 | 
			
		||||
			{
 | 
			
		||||
				if (precept.def.defName.Contains("Horrible") || precept.def.defName.Contains("Despised") || precept.def.defName.Contains("Disapproved"))
 | 
			
		||||
				{ return true; }
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool EnjoysViolence(this Pawn pawn)
 | 
			
		||||
		{
 | 
			
		||||
			if (pawn.IsAnimal() || pawn.RaceProps.IsMechanoid)
 | 
			
		||||
			{ return true; }
 | 
			
		||||
 | 
			
		||||
			if (pawn?.story?.traits?.allTraits == null || pawn?.story?.traits?.allTraits.NullOrEmpty() == true)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			List<string> traits = new List<string>() { "Brawler", "Psychopath", "Bloodlust" };
 | 
			
		||||
 | 
			
		||||
			return pawn.story.traits.allTraits.Any(x => traits.Contains(x.def.defName));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool DislikesViolence(this Pawn pawn)
 | 
			
		||||
		{
 | 
			
		||||
			if (pawn.IsAnimal() || pawn.RaceProps.IsMechanoid)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			if (pawn?.story?.traits?.allTraits == null || pawn?.story?.traits?.allTraits.NullOrEmpty() == true)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			List<string> traits = new List<string>() { "Kind", "Wimp" };
 | 
			
		||||
 | 
			
		||||
			return pawn.WorkTagIsDisabled(WorkTags.Violent) || pawn.story.traits.allTraits.Any(x => traits.Contains(x.def.defName));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool HasTrait(this Pawn pawn, string trait)
 | 
			
		||||
		{
 | 
			
		||||
			if (pawn?.story?.traits?.allTraits == null || pawn.story.traits.allTraits.NullOrEmpty())
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			TraitDef traitDef = DefDatabase<TraitDef>.GetNamedSilentFail(trait);
 | 
			
		||||
 | 
			
		||||
			if (traitDef == null)
 | 
			
		||||
			{ traitDef = DefDatabase<TraitDef>.GetNamedSilentFail(trait.ToLower()); }
 | 
			
		||||
 | 
			
		||||
			if (traitDef == null)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			return pawn.story.traits.HasTrait(traitDef);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										79
									
								
								Source/Scripts/JobDrivers/JobDriver_JoinInSex.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								Source/Scripts/JobDrivers/JobDriver_JoinInSex.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,79 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using Verse;
 | 
			
		||||
using Verse.AI;
 | 
			
		||||
using rjw;
 | 
			
		||||
using Rimworld_Animations;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	public class JobDriver_JoinInSex : JobDriver_SexBaseInitiator
 | 
			
		||||
	{
 | 
			
		||||
		public override bool TryMakePreToilReservations(bool errorOnFailed)
 | 
			
		||||
		{
 | 
			
		||||
			return true; // pawn.Reserve(Target, job, 3, 0, null, errorOnFailed);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		protected override IEnumerable<Toil> MakeNewToils()
 | 
			
		||||
		{
 | 
			
		||||
			setup_ticks();
 | 
			
		||||
 | 
			
		||||
			this.FailOnDespawnedNullOrForbidden(iTarget);
 | 
			
		||||
			this.FailOn(() => !Partner.health.capacities.CanBeAwake);
 | 
			
		||||
			this.FailOn(() => pawn.Drafted);
 | 
			
		||||
			this.FailOn(() => Partner.Drafted);
 | 
			
		||||
 | 
			
		||||
			Toil FollowToil = new Toil();
 | 
			
		||||
			FollowToil.defaultCompleteMode = ToilCompleteMode.Delay;
 | 
			
		||||
			FollowToil.socialMode = RandomSocialMode.Off;
 | 
			
		||||
			FollowToil.defaultDuration = 1200;
 | 
			
		||||
			FollowToil.tickAction = delegate
 | 
			
		||||
			{
 | 
			
		||||
				pawn.pather.StartPath(Partner, PathEndMode.Touch);
 | 
			
		||||
				
 | 
			
		||||
				if (pawn.pather.Moving == false && Partner.pather.Moving == false && Partner.jobs.curDriver is JobDriver_SexBaseReciever)
 | 
			
		||||
				{ ReadyForNextToil(); }
 | 
			
		||||
			};
 | 
			
		||||
			yield return FollowToil;
 | 
			
		||||
 | 
			
		||||
			Toil SexToil = new Toil();
 | 
			
		||||
			SexToil.defaultCompleteMode = ToilCompleteMode.Never;
 | 
			
		||||
			SexToil.socialMode = RandomSocialMode.Off;
 | 
			
		||||
			SexToil.defaultDuration = duration;
 | 
			
		||||
			SexToil.handlingFacing = true;
 | 
			
		||||
			SexToil.FailOn(() => (Partner.jobs.curDriver is JobDriver_SexBaseReciever) == false);
 | 
			
		||||
			SexToil.initAction = delegate
 | 
			
		||||
			{
 | 
			
		||||
				Start();
 | 
			
		||||
				Sexprops.usedCondom = CondomUtility.TryUseCondom(pawn) || CondomUtility.TryUseCondom(Partner);
 | 
			
		||||
			};
 | 
			
		||||
			SexToil.AddPreTickAction(delegate
 | 
			
		||||
			{
 | 
			
		||||
				if (pawn.IsHashIntervalTick(ticks_between_hearts))
 | 
			
		||||
					ThrowMetaIconF(pawn.Position, pawn.Map, FleckDefOf.Heart);
 | 
			
		||||
				SexTick(pawn, Partner);
 | 
			
		||||
				SexUtility.reduce_rest(pawn, 1);
 | 
			
		||||
				if (ticks_left <= 0)
 | 
			
		||||
					ReadyForNextToil();
 | 
			
		||||
			});
 | 
			
		||||
			SexToil.AddFinishAction(delegate
 | 
			
		||||
			{
 | 
			
		||||
				End();
 | 
			
		||||
			});
 | 
			
		||||
			yield return SexToil;
 | 
			
		||||
 | 
			
		||||
			yield return new Toil
 | 
			
		||||
			{
 | 
			
		||||
				initAction = delegate
 | 
			
		||||
				{
 | 
			
		||||
					//// Trying to add some interactions and social logs
 | 
			
		||||
					SexUtility.ProcessSex(Sexprops);
 | 
			
		||||
				},
 | 
			
		||||
				defaultCompleteMode = ToilCompleteMode.Instant
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										357
									
								
								Source/Scripts/Settings/ApparelSettings.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										357
									
								
								Source/Scripts/Settings/ApparelSettings.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,357 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using Verse;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using rjw;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	public class ApparelSettings : ModSettings
 | 
			
		||||
	{
 | 
			
		||||
		public static List<RimNudeData> rimNudeData = new List<RimNudeData>();
 | 
			
		||||
 | 
			
		||||
		public static bool cropApparel = false;
 | 
			
		||||
		public static bool clothesThrownOnGround = true;
 | 
			
		||||
		public static RJWPreferenceSettings.Clothing apparelWornForQuickies = RJWPreferenceSettings.Clothing.Clothed;
 | 
			
		||||
 | 
			
		||||
		public override void ExposeData()
 | 
			
		||||
		{
 | 
			
		||||
			base.ExposeData();
 | 
			
		||||
 | 
			
		||||
			Scribe_Values.Look(ref cropApparel, "cropApparel", false);
 | 
			
		||||
			Scribe_Values.Look(ref clothesThrownOnGround, "clothesThrownOnGround", true);
 | 
			
		||||
			Scribe_Values.Look(ref apparelWornForQuickies, "apparelWornForQuickies", RJWPreferenceSettings.Clothing.Clothed);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static RimNudeData GetRimNudeData(Apparel apparel)
 | 
			
		||||
		{
 | 
			
		||||
			if (rimNudeData.NullOrEmpty())
 | 
			
		||||
			{ ApparelSettingsUtility.ResetRimNudeData(rimNudeData); }
 | 
			
		||||
 | 
			
		||||
			foreach (RimNudeData apparelData in rimNudeData)
 | 
			
		||||
			{
 | 
			
		||||
				if (apparelData.EquivalentTo(new RimNudeData(apparel.def)))
 | 
			
		||||
				{ return apparelData; }
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public class ApparelSettingsDisplay : Mod
 | 
			
		||||
	{
 | 
			
		||||
		private const float windowY = 250f;
 | 
			
		||||
		private const float windowHeight = 360f;
 | 
			
		||||
 | 
			
		||||
		private Vector2 scrollPosition;
 | 
			
		||||
		private const float scrollBarWidthMargin = 18f;
 | 
			
		||||
 | 
			
		||||
		private const float headerHeight = 48f;
 | 
			
		||||
		private const float widgetWidth = 32f;
 | 
			
		||||
		private const float widgetHeight = 32f;
 | 
			
		||||
		private const float buttonWidth = 90f;
 | 
			
		||||
		private const float buttonHeight = 32f;
 | 
			
		||||
		private const float checkboxSize = 24f;
 | 
			
		||||
		private const float labelWidth = 170f;
 | 
			
		||||
		private const float labelHeight = 40f;
 | 
			
		||||
 | 
			
		||||
		private const float rowHeight = 40f;
 | 
			
		||||
		private const float halfColumnWidth = 40f;
 | 
			
		||||
		private const float singleColumnWidth = 100f;
 | 
			
		||||
		private const float doubleColumnWidth = 180f;
 | 
			
		||||
 | 
			
		||||
		private static List<ThingDef> thingDefs = new List<ThingDef>();
 | 
			
		||||
		
 | 
			
		||||
		public ApparelSettingsDisplay(ModContentPack content) : base(content)
 | 
			
		||||
		{
 | 
			
		||||
			GetSettings<ApparelSettings>();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void WriteSettings()
 | 
			
		||||
		{
 | 
			
		||||
			base.WriteSettings();
 | 
			
		||||
			ApplySettings();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Update all humanlike pawn graphics when settings window is closed
 | 
			
		||||
		public void ApplySettings()
 | 
			
		||||
		{
 | 
			
		||||
			if (Current.ProgramState == ProgramState.Playing)
 | 
			
		||||
			{
 | 
			
		||||
				foreach (Pawn pawn in Current.Game.CurrentMap.mapPawns.AllPawns)
 | 
			
		||||
				{
 | 
			
		||||
					if (pawn.RaceProps.Humanlike && pawn.apparel.WornApparel.NullOrEmpty() == false)
 | 
			
		||||
					{ pawn.Drawer.renderer.graphics.ResolveAllGraphics(); }
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void DoSettingsWindowContents(Rect inRect)
 | 
			
		||||
		{
 | 
			
		||||
			// Settings list
 | 
			
		||||
			Listing_Standard listingStandard;
 | 
			
		||||
			listingStandard = new Listing_Standard();
 | 
			
		||||
			listingStandard.Begin(inRect);
 | 
			
		||||
 | 
			
		||||
			listingStandard.Gap(10f);
 | 
			
		||||
			listingStandard.Label("rimworld_animation_patch_clothing".Translate());
 | 
			
		||||
			listingStandard.Gap(5f);
 | 
			
		||||
 | 
			
		||||
			listingStandard.Label("wearing_clothes_in_bed".Translate(), -1, "wearing_clothes_in_bed_desc".Translate());
 | 
			
		||||
 | 
			
		||||
			if (Widgets.ButtonText(new Rect(inRect.width - 128f, 36f, 128f, 24f), RJWPreferenceSettings.sex_wear.ToString()))
 | 
			
		||||
			{
 | 
			
		||||
				List<FloatMenuOption> options = new List<FloatMenuOption>
 | 
			
		||||
				{
 | 
			
		||||
					new FloatMenuOption(RJWPreferenceSettings.Clothing.Clothed.ToString(), delegate()
 | 
			
		||||
					{ RJWPreferenceSettings.sex_wear =  RJWPreferenceSettings.Clothing.Clothed;
 | 
			
		||||
					}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
					new FloatMenuOption(RJWPreferenceSettings.Clothing.Headgear.ToString(), delegate()
 | 
			
		||||
					{ RJWPreferenceSettings.sex_wear =  RJWPreferenceSettings.Clothing.Headgear;
 | 
			
		||||
					}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
					new FloatMenuOption(RJWPreferenceSettings.Clothing.Nude.ToString(), delegate()
 | 
			
		||||
					{ RJWPreferenceSettings.sex_wear =  RJWPreferenceSettings.Clothing.Nude;
 | 
			
		||||
					}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
				}; Find.WindowStack.Add(new FloatMenu(options));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			listingStandard.Label("wearing_clothes_for_quickies".Translate(), -1, "wearing_clothes_for_quickies_desc".Translate());
 | 
			
		||||
 | 
			
		||||
			if (Widgets.ButtonText(new Rect(inRect.width - 128f, 60f, 128f, 24f), ApparelSettings.apparelWornForQuickies.ToString()))
 | 
			
		||||
			{
 | 
			
		||||
				List<FloatMenuOption> options = new List<FloatMenuOption>
 | 
			
		||||
				{
 | 
			
		||||
					new FloatMenuOption(RJWPreferenceSettings.Clothing.Clothed.ToString(), delegate()
 | 
			
		||||
					{ ApparelSettings.apparelWornForQuickies =  RJWPreferenceSettings.Clothing.Clothed;
 | 
			
		||||
					}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
					new FloatMenuOption(RJWPreferenceSettings.Clothing.Headgear.ToString(), delegate()
 | 
			
		||||
					{ ApparelSettings.apparelWornForQuickies =  RJWPreferenceSettings.Clothing.Headgear;
 | 
			
		||||
					}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
					new FloatMenuOption(RJWPreferenceSettings.Clothing.Nude.ToString(), delegate()
 | 
			
		||||
					{ ApparelSettings.apparelWornForQuickies =  RJWPreferenceSettings.Clothing.Nude;
 | 
			
		||||
					}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),					
 | 
			
		||||
				}; Find.WindowStack.Add(new FloatMenu(options));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			listingStandard.CheckboxLabeled("clothes_thrown_on_ground".Translate(), ref ApparelSettings.clothesThrownOnGround, "clothes_thrown_on_ground_desc".Translate());
 | 
			
		||||
			listingStandard.CheckboxLabeled("crop_apparel".Translate(), ref ApparelSettings.cropApparel, "crop_apparel_desc".Translate());
 | 
			
		||||
 | 
			
		||||
			listingStandard.End();
 | 
			
		||||
			base.DoSettingsWindowContents(inRect);
 | 
			
		||||
 | 
			
		||||
			// Local variables
 | 
			
		||||
			Rect rect = Find.WindowStack.currentlyDrawnWindow.windowRect.AtZero();
 | 
			
		||||
			Rect tempRect = new Rect(0, 0, 0, 0);
 | 
			
		||||
 | 
			
		||||
			float innerY = 0f;
 | 
			
		||||
			float innerX = 0;
 | 
			
		||||
			int num = 0;
 | 
			
		||||
 | 
			
		||||
			bool isEnabled = false;
 | 
			
		||||
			bool linkChangesChanged = false;
 | 
			
		||||
 | 
			
		||||
			// Get a list of apparel of interest
 | 
			
		||||
			if (thingDefs.NullOrEmpty())
 | 
			
		||||
			{ thingDefs = ApparelSettingsUtility.GetApparelOfInterest(); }
 | 
			
		||||
 | 
			
		||||
			// Ensure that all apparel has associated RimNudeData
 | 
			
		||||
			if (ApparelSettings.rimNudeData.NullOrEmpty())
 | 
			
		||||
			{ ApparelSettingsUtility.ResetRimNudeData(ApparelSettings.rimNudeData); }
 | 
			
		||||
 | 
			
		||||
			// Add buttons to the top of the main window
 | 
			
		||||
			innerX = halfColumnWidth;
 | 
			
		||||
 | 
			
		||||
			// Apparel
 | 
			
		||||
			tempRect = new Rect(innerX + SettingsUtility.Align(labelWidth, doubleColumnWidth), windowY - headerHeight - 5, labelWidth, headerHeight);
 | 
			
		||||
			Widgets.DrawHighlightIfMouseover(tempRect);
 | 
			
		||||
			TooltipHandler.TipRegion(tempRect, "List of apparel that covers the legs and/or torso. This list can be sorted alphabetically or by the mod that added them.");
 | 
			
		||||
 | 
			
		||||
			if (Widgets.ButtonText(tempRect, "Apparel"))
 | 
			
		||||
			{
 | 
			
		||||
				List<FloatMenuOption> options = new List<FloatMenuOption>
 | 
			
		||||
			{
 | 
			
		||||
				new FloatMenuOption("Sort by name", delegate()
 | 
			
		||||
				{ thingDefs = thingDefs.OrderBy(x => x.label).ToList();
 | 
			
		||||
				}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
				new FloatMenuOption("Sort by mod", delegate()
 | 
			
		||||
				{ thingDefs = ApparelSettingsUtility.GetApparelOfInterest();
 | 
			
		||||
				}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
			}; Find.WindowStack.Add(new FloatMenu(options));
 | 
			
		||||
			}; innerX += doubleColumnWidth;
 | 
			
		||||
 | 
			
		||||
			// Covers groin
 | 
			
		||||
			tempRect = new Rect(innerX + SettingsUtility.Align(buttonWidth, singleColumnWidth), windowY - headerHeight - 5, buttonWidth, headerHeight);
 | 
			
		||||
			Widgets.DrawHighlightIfMouseover(tempRect);
 | 
			
		||||
			TooltipHandler.TipRegion(tempRect, "Toggles whether genitials should be hidden when wearing this apparel.");
 | 
			
		||||
 | 
			
		||||
			if (Widgets.ButtonText(tempRect, "Covers\ngroin"))
 | 
			
		||||
			{
 | 
			
		||||
				List<FloatMenuOption> options = new List<FloatMenuOption>
 | 
			
		||||
			{
 | 
			
		||||
				new FloatMenuOption("Set all 'true'", delegate()
 | 
			
		||||
				{ ApparelSettingsUtility.SetAllCoversGroin(ApparelSettings.rimNudeData, true);
 | 
			
		||||
				}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
				new FloatMenuOption("Set all 'false'", delegate()
 | 
			
		||||
				{ ApparelSettingsUtility.SetAllCoversGroin(ApparelSettings.rimNudeData, false);
 | 
			
		||||
				}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
			}; Find.WindowStack.Add(new FloatMenu(options));
 | 
			
		||||
			}; innerX += singleColumnWidth;
 | 
			
		||||
 | 
			
		||||
			// Covers belly
 | 
			
		||||
			tempRect = new Rect(innerX + SettingsUtility.Align(buttonWidth, singleColumnWidth), windowY - headerHeight - 5, buttonWidth, headerHeight);
 | 
			
		||||
			Widgets.DrawHighlightIfMouseover(tempRect);
 | 
			
		||||
			TooltipHandler.TipRegion(tempRect, "Toggles whether an enlarged belly should be hidden when wearing this apparel.");
 | 
			
		||||
 | 
			
		||||
			if (Widgets.ButtonText(tempRect, "Covers\nbelly"))
 | 
			
		||||
			{
 | 
			
		||||
				List<FloatMenuOption> options = new List<FloatMenuOption>
 | 
			
		||||
			{
 | 
			
		||||
				new FloatMenuOption("Set all 'true'", delegate()
 | 
			
		||||
				{ ApparelSettingsUtility.SetAllCoversBelly(ApparelSettings.rimNudeData, true);
 | 
			
		||||
				}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
				new FloatMenuOption("Set all 'false'", delegate()
 | 
			
		||||
				{ ApparelSettingsUtility.SetAllCoversBelly(ApparelSettings.rimNudeData, false);
 | 
			
		||||
				}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
			}; Find.WindowStack.Add(new FloatMenu(options));
 | 
			
		||||
			}; innerX += singleColumnWidth;
 | 
			
		||||
 | 
			
		||||
			// Covers belly
 | 
			
		||||
			tempRect = new Rect(innerX + SettingsUtility.Align(buttonWidth, singleColumnWidth), windowY - headerHeight - 5, buttonWidth, headerHeight);
 | 
			
		||||
			Widgets.DrawHighlightIfMouseover(tempRect);
 | 
			
		||||
			TooltipHandler.TipRegion(tempRect, "Toggles whether this apparel conceals breasts.");
 | 
			
		||||
 | 
			
		||||
			if (Widgets.ButtonText(tempRect, "Covers\nbreasts"))
 | 
			
		||||
			{
 | 
			
		||||
				List<FloatMenuOption> options = new List<FloatMenuOption>
 | 
			
		||||
			{
 | 
			
		||||
				new FloatMenuOption("Set all 'true'", delegate()
 | 
			
		||||
				{ ApparelSettingsUtility.SetAllCoversChest(ApparelSettings.rimNudeData, true);
 | 
			
		||||
				}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
				new FloatMenuOption("Set all 'false'", delegate()
 | 
			
		||||
				{ ApparelSettingsUtility.SetAllCoversChest(ApparelSettings.rimNudeData, false);
 | 
			
		||||
				}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
			}; Find.WindowStack.Add(new FloatMenu(options));
 | 
			
		||||
			}; innerX += singleColumnWidth;
 | 
			
		||||
 | 
			
		||||
			// Sex wear
 | 
			
		||||
			tempRect = new Rect(innerX + SettingsUtility.Align(buttonWidth, singleColumnWidth), windowY - headerHeight - 5, buttonWidth, headerHeight);
 | 
			
		||||
			Widgets.DrawHighlightIfMouseover(tempRect);
 | 
			
		||||
			TooltipHandler.TipRegion(tempRect, "Toggles whether this piece of apparel should always be kept on during lovin'");
 | 
			
		||||
 | 
			
		||||
			if (Widgets.ButtonText(tempRect, "Sex-wear"))
 | 
			
		||||
			{
 | 
			
		||||
				List<FloatMenuOption> options = new List<FloatMenuOption>
 | 
			
		||||
			{
 | 
			
		||||
				new FloatMenuOption("Set all 'true'", delegate()
 | 
			
		||||
				{ ApparelSettingsUtility.SetAllSexWear(ApparelSettings.rimNudeData, true);
 | 
			
		||||
				}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
				new FloatMenuOption("Set all 'false'", delegate()
 | 
			
		||||
				{ ApparelSettingsUtility.SetAllSexWear(ApparelSettings.rimNudeData, false);
 | 
			
		||||
				}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
 | 
			
		||||
			}; Find.WindowStack.Add(new FloatMenu(options));
 | 
			
		||||
			}; innerX += singleColumnWidth;
 | 
			
		||||
 | 
			
		||||
			// Reset button
 | 
			
		||||
			tempRect = new Rect(innerX + SettingsUtility.Align(buttonWidth, singleColumnWidth), windowY - headerHeight - 5, buttonWidth, headerHeight);
 | 
			
		||||
			Widgets.DrawHighlightIfMouseover(tempRect);
 | 
			
		||||
			TooltipHandler.TipRegion(tempRect, "Returns all values in this table to their default state.");
 | 
			
		||||
 | 
			
		||||
			if (Widgets.ButtonText(tempRect, "Reset to\ndefaults"))
 | 
			
		||||
			{ ApparelSettingsUtility.ResetRimNudeData(ApparelSettings.rimNudeData); }; innerX += singleColumnWidth + scrollBarWidthMargin;
 | 
			
		||||
 | 
			
		||||
			// Determine the height of the scrollable area
 | 
			
		||||
			int apparelCount = thingDefs.Count;
 | 
			
		||||
			float totalContentHeight = rowHeight * (float)apparelCount;
 | 
			
		||||
 | 
			
		||||
			// Create a rect for the scroll window
 | 
			
		||||
			var contentRect = new Rect(0f, windowY, innerX, windowHeight);
 | 
			
		||||
 | 
			
		||||
			// Determine if the scroll will be visible
 | 
			
		||||
			bool scrollBarVisible = totalContentHeight > contentRect.height;
 | 
			
		||||
 | 
			
		||||
			// Create a rect for the scrollable area
 | 
			
		||||
			var scrollViewTotal = new Rect(0f, 0f, innerX - (scrollBarVisible ? scrollBarWidthMargin : 0), totalContentHeight);
 | 
			
		||||
 | 
			
		||||
			// Start of content for scrollable area
 | 
			
		||||
			Widgets.DrawHighlight(contentRect);
 | 
			
		||||
			Widgets.BeginScrollView(contentRect, ref scrollPosition, scrollViewTotal);
 | 
			
		||||
 | 
			
		||||
			foreach (ThingDef thingDef in thingDefs)
 | 
			
		||||
			{
 | 
			
		||||
				isEnabled = false;
 | 
			
		||||
				bool changeHappened = false;
 | 
			
		||||
 | 
			
		||||
				innerX = 0;
 | 
			
		||||
				innerY = (float)num * (rowHeight);
 | 
			
		||||
				num++;
 | 
			
		||||
 | 
			
		||||
				RimNudeData rimNudeApparel = ApparelSettings.rimNudeData.First(x => x.EquivalentTo(new RimNudeData(thingDef)));
 | 
			
		||||
 | 
			
		||||
				// Apparel symbol
 | 
			
		||||
				Widgets.ThingIcon(new Rect(innerX + SettingsUtility.Align(widgetWidth, halfColumnWidth), innerY + SettingsUtility.Align(widgetHeight, rowHeight), widgetWidth, widgetHeight), thingDef, null, null, 1f, null);
 | 
			
		||||
				innerX += halfColumnWidth;
 | 
			
		||||
 | 
			
		||||
				// Apparel name
 | 
			
		||||
				Text.Anchor = TextAnchor.MiddleLeft;
 | 
			
		||||
				Widgets.Label(new Rect(innerX + 10f, innerY + SettingsUtility.Align(labelHeight, rowHeight), labelWidth, labelHeight), thingDef.label.CapitalizeFirst()); innerX += doubleColumnWidth;
 | 
			
		||||
				Text.Anchor = TextAnchor.UpperLeft;
 | 
			
		||||
 | 
			
		||||
				// Hide groin checkbox
 | 
			
		||||
				if (thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs) || thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.GenitalsBPG))
 | 
			
		||||
				{
 | 
			
		||||
					isEnabled = rimNudeApparel.coversGroin;
 | 
			
		||||
					Widgets.Checkbox(innerX + SettingsUtility.Align(checkboxSize, singleColumnWidth), innerY + SettingsUtility.Align(checkboxSize, rowHeight), ref isEnabled, checkboxSize, false, true, null, null);
 | 
			
		||||
					if (isEnabled != rimNudeApparel.coversGroin) { changeHappened = true; }
 | 
			
		||||
					rimNudeApparel.coversGroin = isEnabled;
 | 
			
		||||
				}; innerX += singleColumnWidth;
 | 
			
		||||
 | 
			
		||||
				// Hide belly checkbox
 | 
			
		||||
				if (thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso))
 | 
			
		||||
				{
 | 
			
		||||
					isEnabled = rimNudeApparel.coversBelly;
 | 
			
		||||
					Widgets.Checkbox(innerX + SettingsUtility.Align(checkboxSize, singleColumnWidth), innerY + SettingsUtility.Align(checkboxSize, rowHeight), ref isEnabled, checkboxSize, false, true, null, null);
 | 
			
		||||
					if (isEnabled != rimNudeApparel.coversBelly) { changeHappened = true; }
 | 
			
		||||
					rimNudeApparel.coversBelly = isEnabled;
 | 
			
		||||
				}; innerX += singleColumnWidth;
 | 
			
		||||
 | 
			
		||||
				// Covers bust checkbox
 | 
			
		||||
				if (thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) || thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.ChestBPG))
 | 
			
		||||
				{
 | 
			
		||||
					isEnabled = rimNudeApparel.coversChest;
 | 
			
		||||
					Widgets.Checkbox(innerX + SettingsUtility.Align(checkboxSize, singleColumnWidth), innerY + SettingsUtility.Align(checkboxSize, rowHeight), ref isEnabled, checkboxSize, false, true, null, null);
 | 
			
		||||
					if (isEnabled != rimNudeApparel.coversChest) { changeHappened = true; }
 | 
			
		||||
					rimNudeApparel.coversChest = isEnabled;
 | 
			
		||||
				}; innerX += singleColumnWidth;
 | 
			
		||||
 | 
			
		||||
				// Is sex-wear checkbox
 | 
			
		||||
				isEnabled = rimNudeApparel.sexWear;
 | 
			
		||||
				Widgets.Checkbox(innerX + SettingsUtility.Align(checkboxSize, singleColumnWidth), innerY + SettingsUtility.Align(checkboxSize, rowHeight), ref isEnabled, checkboxSize, false, true, null, null);
 | 
			
		||||
				if (isEnabled != rimNudeApparel.sexWear) { changeHappened = true; }
 | 
			
		||||
				rimNudeApparel.sexWear = isEnabled;
 | 
			
		||||
				innerX += singleColumnWidth;
 | 
			
		||||
 | 
			
		||||
				// Update other body types if linked changed are enabled
 | 
			
		||||
				if (BasicSettings.linkChanges && (changeHappened || linkChangesChanged))
 | 
			
		||||
				{
 | 
			
		||||
					for (int i = 0; i < 5; i++)
 | 
			
		||||
					{
 | 
			
		||||
						RimNudeData _rimNudeApparel = ApparelSettings.rimNudeData.First(x => x.EquivalentTo(new RimNudeData(thingDef)));
 | 
			
		||||
						_rimNudeApparel.coversGroin = rimNudeApparel.coversGroin;
 | 
			
		||||
						_rimNudeApparel.coversBelly = rimNudeApparel.coversBelly;
 | 
			
		||||
						_rimNudeApparel.coversChest = rimNudeApparel.coversChest;
 | 
			
		||||
						_rimNudeApparel.sexWear = rimNudeApparel.sexWear;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Widgets.EndScrollView();		
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public sealed override string SettingsCategory()
 | 
			
		||||
		{
 | 
			
		||||
			return "rimworld_animation_patch_apparelsettings".Translate();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										127
									
								
								Source/Scripts/Settings/BasicSettings.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								Source/Scripts/Settings/BasicSettings.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,127 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using Verse;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	public class BasicSettings : ModSettings
 | 
			
		||||
	{
 | 
			
		||||
		public static bool autoscaleDeltaPos = true;
 | 
			
		||||
		
 | 
			
		||||
		public static bool linkChanges = false;
 | 
			
		||||
		public static bool needPrivacy = true;
 | 
			
		||||
		public static float chanceForOtherToJoinInSex = 0.25f;
 | 
			
		||||
		public static bool hideNamesForSex = false;
 | 
			
		||||
		public static bool debugMode = false;
 | 
			
		||||
		public static bool showHands = true;
 | 
			
		||||
		
 | 
			
		||||
		public static bool worryAboutInfidelity = true;
 | 
			
		||||
		public static bool worryAboutBeastiality = true;
 | 
			
		||||
		public static bool worryAboutRape = true;
 | 
			
		||||
		public static bool worryAboutNecro = true;
 | 
			
		||||
		public static bool worryAboutXeno = true;
 | 
			
		||||
		public static bool ignoreSlaveRape = false;
 | 
			
		||||
		public static bool majorTabooCanStartFights = false;
 | 
			
		||||
 | 
			
		||||
		public static float genitalMasturbationChance = 1.0f;
 | 
			
		||||
		public static float analMasturbationChance = 0.25f;
 | 
			
		||||
		public static float breastsMasturbationChance = 0.5f;
 | 
			
		||||
		public static float humpingMasturbationChance = 0.25f;
 | 
			
		||||
		public static float otherMasturbationChance = 0.2f;
 | 
			
		||||
 | 
			
		||||
		public override void ExposeData()
 | 
			
		||||
		{
 | 
			
		||||
			base.ExposeData();
 | 
			
		||||
 | 
			
		||||
			Scribe_Values.Look(ref autoscaleDeltaPos, "autoscaleDeltaPos", true);
 | 
			
		||||
			Scribe_Values.Look(ref linkChanges, "linkChanges", false);
 | 
			
		||||
			Scribe_Values.Look(ref needPrivacy, "needPrivacy", true);
 | 
			
		||||
			Scribe_Values.Look(ref chanceForOtherToJoinInSex, "chanceForSexExtra", 0.25f);
 | 
			
		||||
			Scribe_Values.Look(ref hideNamesForSex, "hideNamesForSex", false);
 | 
			
		||||
			Scribe_Values.Look(ref debugMode, "debugMode", false);
 | 
			
		||||
			Scribe_Values.Look(ref showHands, "showHands", true);
 | 
			
		||||
			Scribe_Values.Look(ref worryAboutInfidelity, "worryAboutInfidelity", true);
 | 
			
		||||
			Scribe_Values.Look(ref worryAboutBeastiality, "worryAboutBeastiality", true);
 | 
			
		||||
			Scribe_Values.Look(ref worryAboutRape, "worryAboutRape", true);
 | 
			
		||||
			Scribe_Values.Look(ref worryAboutNecro, "worryAboutNecro", true);
 | 
			
		||||
			Scribe_Values.Look(ref worryAboutXeno, "worryAboutXeno", true);
 | 
			
		||||
			Scribe_Values.Look(ref ignoreSlaveRape, "ignoreSlaveRape", false);
 | 
			
		||||
			Scribe_Values.Look(ref majorTabooCanStartFights, "majorTabooCanStartFights", false);
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public class BasicSettingsDisplay : Mod
 | 
			
		||||
	{
 | 
			
		||||
		public BasicSettingsDisplay(ModContentPack content) : base(content)
 | 
			
		||||
		{
 | 
			
		||||
			GetSettings<BasicSettings>();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void WriteSettings()
 | 
			
		||||
		{
 | 
			
		||||
			base.WriteSettings();
 | 
			
		||||
			ApplySettings();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Update all humanlike pawn graphics when settings window is closed
 | 
			
		||||
		public void ApplySettings()
 | 
			
		||||
		{
 | 
			
		||||
			if (Current.ProgramState == ProgramState.Playing)
 | 
			
		||||
			{
 | 
			
		||||
				foreach (Pawn pawn in Current.Game.CurrentMap.mapPawns.AllPawns)
 | 
			
		||||
				{
 | 
			
		||||
					if (pawn.RaceProps.Humanlike && pawn.apparel.WornApparel.NullOrEmpty() == false)
 | 
			
		||||
					{ pawn.Drawer.renderer.graphics.ResolveAllGraphics(); }
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void DoSettingsWindowContents(Rect inRect)
 | 
			
		||||
		{
 | 
			
		||||
			Listing_Standard listingStandard;
 | 
			
		||||
			listingStandard = new Listing_Standard();
 | 
			
		||||
			listingStandard.Begin(inRect);
 | 
			
		||||
 | 
			
		||||
			listingStandard.Gap(10f);
 | 
			
		||||
			listingStandard.Label("rimworld_animation_patch_general".Translate());
 | 
			
		||||
			listingStandard.Gap(5f);
 | 
			
		||||
 | 
			
		||||
			listingStandard.CheckboxLabeled("need_privacy".Translate(), ref BasicSettings.needPrivacy, "need_privacy_desc".Translate());
 | 
			
		||||
			listingStandard.CheckboxLabeled("worry_about_infidelity".Translate(), ref BasicSettings.worryAboutInfidelity, "worry_about_infidelity_desc".Translate());
 | 
			
		||||
			listingStandard.CheckboxLabeled("worry_about_beastiality".Translate(), ref BasicSettings.worryAboutBeastiality, "worry_about_beastiality_desc".Translate());
 | 
			
		||||
			listingStandard.CheckboxLabeled("worry_about_rape".Translate(), ref BasicSettings.worryAboutRape, "worry_about_rape_desc".Translate());
 | 
			
		||||
 | 
			
		||||
			if (BasicSettings.worryAboutRape)
 | 
			
		||||
			{
 | 
			
		||||
				listingStandard.CheckboxLabeled("ignore_slave_rape".Translate(), ref BasicSettings.ignoreSlaveRape);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			listingStandard.CheckboxLabeled("worry_about_necro".Translate(), ref BasicSettings.worryAboutNecro, "worry_about_necro_desc".Translate());
 | 
			
		||||
			listingStandard.CheckboxLabeled("worry_about_xeno".Translate(), ref BasicSettings.worryAboutXeno, "worry_about_xeno_desc".Translate());
 | 
			
		||||
			listingStandard.CheckboxLabeled("major_taboo_can_start_fights".Translate(), ref BasicSettings.majorTabooCanStartFights, "major_taboo_can_start_fights_desc".Translate());
 | 
			
		||||
			
 | 
			
		||||
			listingStandard.Label("chance_for_other_to_join_in_sex".Translate() + ": " + BasicSettings.chanceForOtherToJoinInSex.ToString("F"), -1f, "chance_for_other_to_join_in_sex_desc".Translate());
 | 
			
		||||
			BasicSettings.chanceForOtherToJoinInSex = listingStandard.Slider(BasicSettings.chanceForOtherToJoinInSex, 0f, 1f);
 | 
			
		||||
			listingStandard.CheckboxLabeled("hide_names_for_sex".Translate(), ref BasicSettings.hideNamesForSex, "hide_names_for_sex_desc".Translate());
 | 
			
		||||
			listingStandard.CheckboxLabeled("debug_mode".Translate(), ref BasicSettings.debugMode, "debug_mode_desc".Translate());
 | 
			
		||||
					
 | 
			
		||||
			listingStandard.Gap(10f);
 | 
			
		||||
			listingStandard.Label("rimworld_animation_patch_animation".Translate());
 | 
			
		||||
			listingStandard.Gap(5f);
 | 
			
		||||
 | 
			
		||||
			listingStandard.CheckboxLabeled("autoscale_delta_pos".Translate(), ref BasicSettings.autoscaleDeltaPos, "autoscale_delta_pos_desc".Translate());
 | 
			
		||||
			listingStandard.CheckboxLabeled("show_hands".Translate(), ref BasicSettings.showHands, "show_hands_desc".Translate());
 | 
			
		||||
 | 
			
		||||
			listingStandard.End();
 | 
			
		||||
			base.DoSettingsWindowContents(inRect);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public sealed override string SettingsCategory()
 | 
			
		||||
		{
 | 
			
		||||
			return "rimworld_animation_patch_basicsettings".Translate();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										97
									
								
								Source/Scripts/Settings/BasicSetttings.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								Source/Scripts/Settings/BasicSetttings.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,97 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using Verse;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	public class BasicSettings : ModSettings
 | 
			
		||||
	{
 | 
			
		||||
		public static bool autoscaleDeltaPos = true;
 | 
			
		||||
 | 
			
		||||
		public static bool allowUndressing = true;
 | 
			
		||||
		public static bool clothesThrownOnGround = true;
 | 
			
		||||
 | 
			
		||||
		public static float undressingInPrivateDegree = 0.8f;
 | 
			
		||||
		public static float undressingInPublicDegree = 0.2f;
 | 
			
		||||
 | 
			
		||||
		public override void ExposeData()
 | 
			
		||||
		{
 | 
			
		||||
			base.ExposeData();
 | 
			
		||||
			Scribe_Values.Look(ref autoscaleDeltaPos, "autoscaleDeltaPos", true);
 | 
			
		||||
			Scribe_Values.Look(ref clothesThrownOnGround, "clothesThrownOnGround", true);
 | 
			
		||||
			Scribe_Values.Look(ref allowUndressing, "allowUndressing", true);
 | 
			
		||||
			Scribe_Values.Look(ref undressingInPrivateDegree, "undressingInPrivateDegree", 0.8f);
 | 
			
		||||
			Scribe_Values.Look(ref undressingInPublicDegree, "undressingInPublicDegree", 0.2f);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public class BasicSettingsDisplay : Mod
 | 
			
		||||
	{
 | 
			
		||||
 | 
			
		||||
		public BasicSettingsDisplay(ModContentPack content) : base(content)
 | 
			
		||||
		{
 | 
			
		||||
			GetSettings<BasicSettings>();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override void DoSettingsWindowContents(Rect inRect)
 | 
			
		||||
		{
 | 
			
		||||
			Listing_Standard listingStandard;
 | 
			
		||||
 | 
			
		||||
			listingStandard = new Listing_Standard();
 | 
			
		||||
			listingStandard.Begin(inRect);
 | 
			
		||||
 | 
			
		||||
			listingStandard.Gap(10f);
 | 
			
		||||
			listingStandard.Label("Misc Options");
 | 
			
		||||
			listingStandard.Gap(5f);
 | 
			
		||||
			listingStandard.CheckboxLabeled("      Auto-scale animations based on pawn body size", ref BasicSettings.autoscaleDeltaPos);
 | 
			
		||||
 | 
			
		||||
			listingStandard.Gap(10f);
 | 
			
		||||
			listingStandard.Label("Disrobing options");
 | 
			
		||||
			listingStandard.Gap(5f);
 | 
			
		||||
			listingStandard.CheckboxLabeled("      Animate pawn undressing", ref BasicSettings.allowUndressing);
 | 
			
		||||
			if (BasicSettings.allowUndressing)
 | 
			
		||||
			{
 | 
			
		||||
				listingStandard.CheckboxLabeled("      Show discarded clothes on the floor", ref BasicSettings.clothesThrownOnGround);
 | 
			
		||||
				listingStandard.Label("      Degree of disrobing when in bed (default is 0.80): " + BasicSettings.undressingInPrivateDegree.ToString("F"));
 | 
			
		||||
				listingStandard.Label("        " + ReportOnDisrobingInPrivate());
 | 
			
		||||
				BasicSettings.undressingInPrivateDegree = listingStandard.Slider(BasicSettings.undressingInPrivateDegree, 0f, 1f);
 | 
			
		||||
				listingStandard.Label("      Degree of disrobing when out of bed (default is 0.20): " + BasicSettings.undressingInPublicDegree.ToString("F"));
 | 
			
		||||
				listingStandard.Label("        " + ReportOnDisrobingInPublic());
 | 
			
		||||
				BasicSettings.undressingInPublicDegree = listingStandard.Slider(BasicSettings.undressingInPublicDegree, 0f, 1f);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			listingStandard.End();
 | 
			
		||||
 | 
			
		||||
			base.DoSettingsWindowContents(inRect);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public string ReportOnDisrobing(float val)
 | 
			
		||||
		{
 | 
			
		||||
			string report;
 | 
			
		||||
 | 
			
		||||
			if (val == 1) { report = "(Pawns will always fully disrobe)"; }
 | 
			
		||||
			else if (val >= 0.6667) { report = "(Pawns prefer to fully disrobe)"; }
 | 
			
		||||
			else if (val >= 0.3333) { report = "(Pawns like to keep some clothes on)"; }
 | 
			
		||||
			else if (val > 0) { report = "(Pawns will keep most of their clothes on)"; }
 | 
			
		||||
			else { report = "(Pawns will try to keep all of their clothes on)"; }
 | 
			
		||||
 | 
			
		||||
			return report;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public string ReportOnDisrobingInPrivate()
 | 
			
		||||
		{
 | 
			
		||||
			return ReportOnDisrobing(BasicSettings.undressingInPrivateDegree);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public string ReportOnDisrobingInPublic()
 | 
			
		||||
		{
 | 
			
		||||
			return ReportOnDisrobing(BasicSettings.undressingInPublicDegree);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override string SettingsCategory()
 | 
			
		||||
		{
 | 
			
		||||
			return "Rimworld Animations Patch - Basic settings";
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										170
									
								
								Source/Scripts/Utilities/AnimationPatchUtility.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								Source/Scripts/Utilities/AnimationPatchUtility.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,170 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using HarmonyLib;
 | 
			
		||||
using Verse;
 | 
			
		||||
using Rimworld_Animations;
 | 
			
		||||
using rjw;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    public static class AnimationPatchUtility
 | 
			
		||||
    {
 | 
			
		||||
        public static int FindTrueAnimationLength(Pawn pawn, out int orgasmTick)
 | 
			
		||||
        {
 | 
			
		||||
			orgasmTick = int.MaxValue;
 | 
			
		||||
 | 
			
		||||
			ActorAnimationData actorAnimationData = pawn.GetAnimationData();
 | 
			
		||||
			CompBodyAnimator compBodyAnimator = pawn.TryGetComp<CompBodyAnimator>();
 | 
			
		||||
 | 
			
		||||
			// No data
 | 
			
		||||
			if (actorAnimationData == null || compBodyAnimator == null)
 | 
			
		||||
			{
 | 
			
		||||
				DebugMode.Message("There is no actor animation data for " + pawn.NameShortColored);
 | 
			
		||||
				orgasmTick = 1500 + (int)(Rand.Value * 1000f);
 | 
			
		||||
 | 
			
		||||
				return orgasmTick;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			AnimationDef anim = actorAnimationData.animationDef;
 | 
			
		||||
			int actorId = actorAnimationData.actorID;
 | 
			
		||||
			bool isQuickie = compBodyAnimator.fastAnimForQuickie;
 | 
			
		||||
 | 
			
		||||
			int ticks = 0;
 | 
			
		||||
 | 
			
		||||
            foreach (AnimationStage animStage in anim.animationStages)
 | 
			
		||||
            {
 | 
			
		||||
				// Legacy: skip the first stage of quickies if there's no playTimeTicksQuick values declared 
 | 
			
		||||
				if (anim.animationStages.IndexOf(animStage) == 0 && isQuickie && anim.animationStages.Any(x => x.playTimeTicksQuick >= 0) == false)
 | 
			
		||||
				{ continue; }
 | 
			
		||||
 | 
			
		||||
                int curr_tick = 0;
 | 
			
		||||
 | 
			
		||||
                foreach (PawnKeyframe keyframe in (animStage.animationClips[actorId] as PawnAnimationClip).keyframes)
 | 
			
		||||
                {
 | 
			
		||||
                    curr_tick += keyframe.tickDuration;
 | 
			
		||||
 | 
			
		||||
                    if (keyframe.soundEffect != null && keyframe.soundEffect == "Cum" && orgasmTick > (ticks + curr_tick))
 | 
			
		||||
                    { orgasmTick = ticks + curr_tick; }
 | 
			
		||||
 | 
			
		||||
					if (isQuickie && animStage.playTimeTicksQuick > 0 && curr_tick >= animStage.playTimeTicksQuick)
 | 
			
		||||
					{ break; }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                ticks += isQuickie && animStage.playTimeTicksQuick > 0 && animStage.playTimeTicksQuick < animStage.playTimeTicks ? animStage.playTimeTicksQuick : animStage.playTimeTicks;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
			// Orgasm tick not found
 | 
			
		||||
			if (orgasmTick > ticks)
 | 
			
		||||
			{
 | 
			
		||||
				// Safeguard for penial, vaginal and anal sex
 | 
			
		||||
				if (anim.actors[actorId].isFucked || anim.actors[actorId].isFucking || anim.actors[actorId].requiredGenitals.Any(x => x.ToLower().ContainsAny("penis", "vagina", "anus")))
 | 
			
		||||
				{ orgasmTick = Mathf.Clamp(ticks - 5, 0, int.MaxValue); }
 | 
			
		||||
 | 
			
		||||
				// Actor does not orgasm
 | 
			
		||||
				else
 | 
			
		||||
				{ orgasmTick = (int)(ticks * (2f + Rand.Value)); }
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return ticks;
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        // Extended version of PawnHeadRotInAnimation (prevents pawn hair from getting messed up when draw in portraits)
 | 
			
		||||
        public static Rot4 PawnHeadRotInAnimation(Pawn pawn, Rot4 regularPos, PawnRenderFlags renderFlags)
 | 
			
		||||
        {
 | 
			
		||||
            if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawn?.TryGetComp<CompBodyAnimator>() != null && pawn.TryGetComp<CompBodyAnimator>().isAnimating)
 | 
			
		||||
            {
 | 
			
		||||
                return pawn.TryGetComp<CompBodyAnimator>().headFacing;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return regularPos;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static BodyPartRecord GetBodyPartRecord(Pawn pawn, string bodyPart)
 | 
			
		||||
        {
 | 
			
		||||
            if (bodyPart.NullOrEmpty())
 | 
			
		||||
            { return null; }
 | 
			
		||||
 | 
			
		||||
             return pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null).FirstOrDefault(x => x.untranslatedCustomLabel == bodyPart || x.def.defName == bodyPart);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Vector3 GetAnchorPosition(Pawn pawn, Thing thing = null)
 | 
			
		||||
        {
 | 
			
		||||
			Vector3 anchor;
 | 
			
		||||
 | 
			
		||||
			if (thing == null)
 | 
			
		||||
			{ return pawn.Position.ToVector3Shifted(); }
 | 
			
		||||
 | 
			
		||||
			int numOfSleepingSlots = 0;
 | 
			
		||||
 | 
			
		||||
			if (thing is Building_Bed)
 | 
			
		||||
			{ numOfSleepingSlots = BedUtility.GetSleepingSlotsCount(thing.def.size); }
 | 
			
		||||
 | 
			
		||||
			// Anchor to the pawn's sleeping slot when masturbating in own bed
 | 
			
		||||
			if (thing is Building_Bed && (pawn.ownership.OwnedBed == thing || pawn.CurrentBed() == thing) && pawn.IsMasturbating())
 | 
			
		||||
			{
 | 
			
		||||
				anchor = RestUtility.GetBedSleepingSlotPosFor(pawn, thing as Building_Bed).ToVector3();
 | 
			
		||||
 | 
			
		||||
				if (thing.Rotation.AsInt == 0)
 | 
			
		||||
				{
 | 
			
		||||
					anchor.x += 0.5f;
 | 
			
		||||
					anchor.z += 1f;
 | 
			
		||||
				}
 | 
			
		||||
				else if (thing.Rotation.AsInt == 1)
 | 
			
		||||
				{
 | 
			
		||||
					anchor.x += 1f;
 | 
			
		||||
					anchor.z += 0.5f;
 | 
			
		||||
				}
 | 
			
		||||
				else if (thing.Rotation.AsInt == 2)
 | 
			
		||||
				{
 | 
			
		||||
					anchor.x += 0.5f;
 | 
			
		||||
					anchor.z += 0.5f;
 | 
			
		||||
				}
 | 
			
		||||
				else if (thing.Rotation.AsInt == 3)
 | 
			
		||||
				{
 | 
			
		||||
					anchor.x += 0f;
 | 
			
		||||
					anchor.z += 0.5f;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Anchor to the center of the bed (should work for beds of any size?)
 | 
			
		||||
			else if (thing is Building_Bed && numOfSleepingSlots > 0)
 | 
			
		||||
			{
 | 
			
		||||
				anchor = thing.Position.ToVector3();
 | 
			
		||||
				float halfSlots = numOfSleepingSlots / 2f;
 | 
			
		||||
 | 
			
		||||
				if (thing.Rotation.AsInt == 0)
 | 
			
		||||
				{
 | 
			
		||||
					anchor.x += halfSlots;
 | 
			
		||||
					anchor.z += 1f;
 | 
			
		||||
				}
 | 
			
		||||
				else if (thing.Rotation.AsInt == 1)
 | 
			
		||||
				{
 | 
			
		||||
					anchor.x += 1f;
 | 
			
		||||
					anchor.z += (1.0f - halfSlots);
 | 
			
		||||
				}
 | 
			
		||||
				else if (thing.Rotation.AsInt == 2)
 | 
			
		||||
				{
 | 
			
		||||
					anchor.x += (1f - halfSlots);
 | 
			
		||||
					anchor.z += 0f;
 | 
			
		||||
				}
 | 
			
		||||
				else if (thing.Rotation.AsInt == 3)
 | 
			
		||||
				{
 | 
			
		||||
					anchor.x += 0f;
 | 
			
		||||
					anchor.z += halfSlots;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Anchor to the centre of the thing
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				anchor = thing.Position.ToVector3Shifted();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return anchor;
 | 
			
		||||
		}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										197
									
								
								Source/Scripts/Utilities/ApparelAnimationUtility.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								Source/Scripts/Utilities/ApparelAnimationUtility.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,197 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Verse;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using Rimworld_Animations;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using AlienRace;
 | 
			
		||||
using rjw;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    public static class ApparelAnimationUtility
 | 
			
		||||
    {
 | 
			
		||||
		public static float apparelScale = 0.75f;
 | 
			
		||||
 | 
			
		||||
		public static void TryToDrawApparelOnFloor(Pawn pawn)
 | 
			
		||||
        {
 | 
			
		||||
			if (pawn?.apparel?.WornApparel != null)
 | 
			
		||||
			{
 | 
			
		||||
				CompBodyAnimator compBodyAnimator = pawn.TryGetComp<CompBodyAnimator>();
 | 
			
		||||
 | 
			
		||||
				if (ApparelSettings.clothesThrownOnGround == false || Find.CurrentMap != pawn.Map || compBodyAnimator == null || compBodyAnimator.isAnimating == false)
 | 
			
		||||
				{ return; }
 | 
			
		||||
 | 
			
		||||
				foreach (Apparel apparel in pawn.apparel.WornApparel)
 | 
			
		||||
				{
 | 
			
		||||
					CompApparelVisibility compApparelVisibility = apparel.TryGetComp<CompApparelVisibility>();
 | 
			
		||||
 | 
			
		||||
                    if (compApparelVisibility != null && compApparelVisibility.position != default && compApparelVisibility.isBeingWorn == false)
 | 
			
		||||
					{
 | 
			
		||||
						Graphic apparelGraphic = apparel.Graphic;
 | 
			
		||||
						apparelGraphic.drawSize.x *= apparelScale;
 | 
			
		||||
						apparelGraphic.drawSize.y *= apparelScale;
 | 
			
		||||
 | 
			
		||||
						GenDraw.DrawMeshNowOrLater(mesh: apparelGraphic.MeshAt(rot: apparel.Rotation),
 | 
			
		||||
							loc: compApparelVisibility.position,
 | 
			
		||||
							quat: Quaternion.AngleAxis(angle: compApparelVisibility.rotation, axis: Vector3.up),
 | 
			
		||||
							mat: apparelGraphic.MatAt(rot: apparel.Rotation),
 | 
			
		||||
							false);
 | 
			
		||||
 | 
			
		||||
						apparelGraphic.drawSize.x *= 1f / apparelScale;
 | 
			
		||||
						apparelGraphic.drawSize.y *= 1f / apparelScale;
 | 
			
		||||
                        DebugMode.Message(compApparelVisibility.rotation.ToString());
 | 
			
		||||
                        //DebugMode.Message("Drawing " + apparel.def.defName + " on ground");
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        public static bool BodyAddonCoveredByApparel(Apparel apparel, AlienPartGenerator.BodyAddon bodyAddon)
 | 
			
		||||
        {
 | 
			
		||||
            CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
 | 
			
		||||
 | 
			
		||||
            if (comp != null && comp.isBeingWorn == false)
 | 
			
		||||
            { return false; }
 | 
			
		||||
 | 
			
		||||
            RimNudeData rimNudeData = ApparelSettings.GetRimNudeData(apparel);
 | 
			
		||||
 | 
			
		||||
            if (rimNudeData != null && bodyAddon?.bodyPart != null)
 | 
			
		||||
            {
 | 
			
		||||
                if (bodyAddon.bodyPart == "Genitals" && rimNudeData.coversGroin == false)
 | 
			
		||||
                { return false; }
 | 
			
		||||
 | 
			
		||||
                if (bodyAddon.bodyPart == "Chest" && rimNudeData.coversChest == false)
 | 
			
		||||
                { return false; }
 | 
			
		||||
 | 
			
		||||
                if (bodyAddon.bodyPart == "Torso" && rimNudeData.coversBelly == false)
 | 
			
		||||
                { return false; }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (apparel.def.apparel.bodyPartGroups.Any(x => bodyAddon.hiddenUnderApparelFor.Contains(x)) ||
 | 
			
		||||
                apparel.def.apparel.tags.Any(x => bodyAddon.hiddenUnderApparelTag.Contains(x)))
 | 
			
		||||
            { return true; }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool BodyPartCoveredByApparel(Apparel apparel, BodyPartRecord bodyPart)
 | 
			
		||||
        {
 | 
			
		||||
            CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
 | 
			
		||||
 | 
			
		||||
            if (comp != null && comp.isBeingWorn == false)
 | 
			
		||||
            { return false; }
 | 
			
		||||
 | 
			
		||||
            RimNudeData rimNudeData = ApparelSettings.GetRimNudeData(apparel);
 | 
			
		||||
 | 
			
		||||
            if (rimNudeData != null)
 | 
			
		||||
            {
 | 
			
		||||
                if (bodyPart.def.defName == "Genitals" && rimNudeData.coversGroin == false)
 | 
			
		||||
                { return false; }
 | 
			
		||||
 | 
			
		||||
                if (bodyPart.def.defName == "Chest" && rimNudeData.coversChest == false)
 | 
			
		||||
                { return false; }
 | 
			
		||||
 | 
			
		||||
                if (bodyPart.def.defName == "Torso" && rimNudeData.coversBelly == false)
 | 
			
		||||
                { return false; }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (apparel.def.apparel.CoversBodyPart(bodyPart))
 | 
			
		||||
            { return true; }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void DetermineApparelToKeepOn(Pawn pawn)
 | 
			
		||||
        {
 | 
			
		||||
            JobDriver_Sex jobdriver = pawn.jobs.curDriver as JobDriver_Sex;
 | 
			
		||||
 | 
			
		||||
            if (pawn.RaceProps.Humanlike == false || pawn?.apparel?.WornApparel == null || jobdriver == null)
 | 
			
		||||
            { return; }
 | 
			
		||||
 | 
			
		||||
            foreach (Apparel apparel in pawn.apparel?.WornApparel)
 | 
			
		||||
            {
 | 
			
		||||
                CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
 | 
			
		||||
                if (comp != null)
 | 
			
		||||
                { comp.isBeingWorn = true; }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ActorAnimationData animData = pawn.GetAnimationData();
 | 
			
		||||
 | 
			
		||||
            if (animData == null)
 | 
			
		||||
            { return; }
 | 
			
		||||
 | 
			
		||||
            AnimationDef anim = animData.animationDef;
 | 
			
		||||
            int actorID = animData.actorID;
 | 
			
		||||
 | 
			
		||||
            var clothingPreference = pawn.IsInBed(out Building bed) ? RJWPreferenceSettings.sex_wear : ApparelSettings.apparelWornForQuickies;
 | 
			
		||||
 | 
			
		||||
            if (xxx.has_quirk(pawn, "Endytophile"))
 | 
			
		||||
            { clothingPreference = RJWPreferenceSettings.Clothing.Clothed; }
 | 
			
		||||
 | 
			
		||||
            // Determine any obstructing apparel that must be removed
 | 
			
		||||
            foreach (Apparel apparel in pawn.apparel.WornApparel)
 | 
			
		||||
            {
 | 
			
		||||
                CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
 | 
			
		||||
               
 | 
			
		||||
                if (comp == null)
 | 
			
		||||
                { continue; }
 | 
			
		||||
 | 
			
		||||
                if (apparel.def is bondage_gear_def)
 | 
			
		||||
                { continue; }
 | 
			
		||||
 | 
			
		||||
                if (ApparelSettings.GetRimNudeData(apparel) != null && ApparelSettings.GetRimNudeData(apparel).sexWear)
 | 
			
		||||
                { continue; }
 | 
			
		||||
 | 
			
		||||
                if (clothingPreference == RJWPreferenceSettings.Clothing.Nude)
 | 
			
		||||
                { 
 | 
			
		||||
                    comp.isBeingWorn = false;
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                bool isHat = apparel.def.apparel.bodyPartGroups.NullOrEmpty() == false && (apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.FullHead) || apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.UpperHead));
 | 
			
		||||
                
 | 
			
		||||
                if (clothingPreference == RJWPreferenceSettings.Clothing.Headgear && isHat == false)
 | 
			
		||||
                {
 | 
			
		||||
                    comp.isBeingWorn = false;
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (ApparelCoversPawnRequiredBodyParts(pawn, apparel, anim, actorID))
 | 
			
		||||
                { 
 | 
			
		||||
                    comp.isBeingWorn = false;
 | 
			
		||||
                    continue; 
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool ApparelCoversPawnRequiredBodyParts(Pawn pawn, Apparel apparel, AnimationDef anim, int actorID)
 | 
			
		||||
        {
 | 
			
		||||
            bool bodyPartCovered = false;
 | 
			
		||||
 | 
			
		||||
            IEnumerable<BodyPartRecord> bodyParts = pawn.RaceProps.body.AllParts;
 | 
			
		||||
 | 
			
		||||
            var requiredGenitals = anim.actors[actorID].requiredGenitals;
 | 
			
		||||
 | 
			
		||||
            if (requiredGenitals.NullOrEmpty())
 | 
			
		||||
            { requiredGenitals = new List<string>(); }
 | 
			
		||||
 | 
			
		||||
            if (anim.actors[actorID].isFucking || requiredGenitals.Contains("Penis"))
 | 
			
		||||
            { bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.genitalsDef)); }
 | 
			
		||||
 | 
			
		||||
            if (anim.actors[actorID].isFucked || requiredGenitals.Contains("Vagina"))
 | 
			
		||||
            { bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.genitalsDef)); }
 | 
			
		||||
 | 
			
		||||
            if (anim.actors[actorID].isFucked || requiredGenitals.Contains("Anus"))
 | 
			
		||||
            { bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.anusDef)); }
 | 
			
		||||
 | 
			
		||||
            if (requiredGenitals.Contains("Breasts"))
 | 
			
		||||
            { bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.breastsDef)); }
 | 
			
		||||
 | 
			
		||||
            if (requiredGenitals.Contains("Mouth"))
 | 
			
		||||
            { bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def.defName.ToLower().ContainsAny("mouth", "teeth", "jaw", "beak"))); }
 | 
			
		||||
 | 
			
		||||
            return bodyPartCovered;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										199
									
								
								Source/Scripts/Utilities/ApparelSettingsUtility.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								Source/Scripts/Utilities/ApparelSettingsUtility.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,199 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Verse;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	public static class ApparelSettingsUtility
 | 
			
		||||
	{
 | 
			
		||||
		public static List<ThingDef> GetApparelOfInterest()
 | 
			
		||||
		{
 | 
			
		||||
			List<ThingDef> thingDefs = new List<ThingDef>();
 | 
			
		||||
 | 
			
		||||
			foreach (ThingDef thingDef in DefDatabase<ThingDef>.AllDefs)
 | 
			
		||||
			{
 | 
			
		||||
				if (thingDef.IsApparel && thingDef.apparel.layers.Count == 1 && thingDef.apparel.layers[0] == ApparelLayerDefOf.Belt)
 | 
			
		||||
				{ continue; }
 | 
			
		||||
 | 
			
		||||
				if (thingDef.IsApparel &&
 | 
			
		||||
					(thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) ||
 | 
			
		||||
					thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs) ||
 | 
			
		||||
					thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.GenitalsBPG) ||
 | 
			
		||||
					thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.ChestBPG)))
 | 
			
		||||
				{ thingDefs.Add(thingDef); }
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return thingDefs;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Resets all data
 | 
			
		||||
		public static void ResetRimNudeData(List<RimNudeData> rimNudeData)
 | 
			
		||||
		{
 | 
			
		||||
			rimNudeData.Clear();
 | 
			
		||||
			List<ThingDef> thingDefs = GetApparelOfInterest();
 | 
			
		||||
 | 
			
		||||
			foreach (ThingDef thingDef in thingDefs)
 | 
			
		||||
			{
 | 
			
		||||
				for (int i = 0; i < 5; i++)
 | 
			
		||||
				{ rimNudeData.Add(new RimNudeData(thingDef)); }
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			GetApparelDefaults(rimNudeData);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Update apparel data
 | 
			
		||||
		public static void UpdateRimNudeData(List<RimNudeData> rimNudeData, string thingDef, bool coversGroin, bool coversBelly, bool coversChest, bool sexWear)
 | 
			
		||||
		{
 | 
			
		||||
			for (int i = 0; i < rimNudeData.Count; i++)
 | 
			
		||||
			{
 | 
			
		||||
				RimNudeData apparelData = rimNudeData[i];
 | 
			
		||||
 | 
			
		||||
				if (apparelData.thingDef == thingDef)
 | 
			
		||||
				{
 | 
			
		||||
					rimNudeData[i] = new RimNudeData(thingDef, coversGroin, coversBelly, coversChest, sexWear);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static void SetAllCoversGroin(List<RimNudeData> rimNudeData, bool value)
 | 
			
		||||
		{
 | 
			
		||||
			foreach (RimNudeData rimNudeApparel in rimNudeData)
 | 
			
		||||
			{ rimNudeApparel.coversGroin = value; }
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static void SetAllCoversBelly(List<RimNudeData> rimNudeData, bool value)
 | 
			
		||||
		{
 | 
			
		||||
			foreach (RimNudeData rimNudeApparel in rimNudeData)
 | 
			
		||||
			{ rimNudeApparel.coversBelly = value; }
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static void SetAllCoversChest(List<RimNudeData> rimNudeData, bool value)
 | 
			
		||||
		{
 | 
			
		||||
			foreach (RimNudeData rimNudeApparel in rimNudeData)
 | 
			
		||||
			{ rimNudeApparel.coversChest = value; }
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static void SetAllSexWear(List<RimNudeData> rimNudeData, bool value)
 | 
			
		||||
		{
 | 
			
		||||
			foreach (RimNudeData rimNudeApparel in rimNudeData)
 | 
			
		||||
			{ rimNudeApparel.sexWear = value; }
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static void GetApparelDefaults(List<RimNudeData> rimNudeData)
 | 
			
		||||
		{
 | 
			
		||||
			//Apparel_BasicShirt
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "Apparel_BasicShirt", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//Apparel_CollarShirt
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "Apparel_CollarShirt", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//Apparel_FlakVest
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "Apparel_FlakVest", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//Apparel_Duster
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "Apparel_Duster", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//Apparel_Jacket
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "Apparel_Jacket", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//Apparel_TribalA
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "Apparel_TribalA", coversGroin: true, coversBelly: true, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//Apparel_BodyStrap
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "Apparel_BodyStrap", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
 | 
			
		||||
 | 
			
		||||
			//Apparel_PsyfocusRobe
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "Apparel_PsyfocusRobe", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//Apparel_Cape
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "Apparel_Cape", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//Apparel_RobeRoyal
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "Apparel_RobeRoyal",  coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//Apparel_Corset
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "Apparel_Corset", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//VAE_Apparel_Overalls
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "VAE_Apparel_Overalls", coversGroin: true, coversBelly: true, coversChest: false, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//VAE_Apparel_LabCoat
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "VAE_Apparel_LabCoat",  coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//VAE_Apparel_BuildersJacket
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "VAE_Apparel_BuildersJacket",  coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//VAE_Apparel_Apron
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "VAE_Apparel_Apron", coversGroin: true, coversBelly: true, coversChest: false, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//VAE_Apparel_Tunic
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "VAE_Apparel_Tunic", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//VAE_Apparel_PeltCoat
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "VAE_Apparel_PeltCoat", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//VAE_Apparel_WoodenArmor
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "VAE_Apparel_WoodenArmor", coversGroin: false, coversBelly: true, coversChest: false, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//VAE_Apparel_AdvancedVest
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "VAE_Apparel_AdvancedVest", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//VAE_Apparel_BulletproofVest
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "VAE_Apparel_BulletproofVest", coversGroin: false, coversBelly: true, coversChest: false, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//VWE_Apparel_Exoframe
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "VWE_Apparel_Exoframe", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//VFEM_Apparel_Tabard
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "VFEM_Apparel_Tabard", coversGroin: true, coversBelly: true, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//VFEV_Apparel_JarlCape
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "VFEV_Apparel_JarlCape", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//VFEV_Apparel_RoyalFurCoat
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "VFEV_Apparel_RoyalFurCoat",  coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
 | 
			
		||||
 | 
			
		||||
			//PrisonerChains
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "PrisonerChains", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
 | 
			
		||||
 | 
			
		||||
			//S16_ChainHarnessA
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "S16_ChainHarnessA", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
 | 
			
		||||
 | 
			
		||||
			//S16_NippleWristCuffs
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "S16_NippleWristCuffs", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
 | 
			
		||||
 | 
			
		||||
			//S16_ServantGirlDress
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "S16_ServantGirlDress", coversGroin: true, coversBelly: true, coversChest: false, sexWear: true);
 | 
			
		||||
 | 
			
		||||
			//S16_ZDress
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "S16_ZDress", coversGroin: false, coversBelly: true, coversChest: true, sexWear: true);
 | 
			
		||||
 | 
			
		||||
			//S16_MaidA
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "S16_MaidA", coversGroin: false, coversBelly: true, coversChest: false, sexWear: true);
 | 
			
		||||
 | 
			
		||||
			//S16_DiscoTop
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "S16_DiscoTop", coversGroin: false, coversBelly: false, coversChest: true, sexWear: true);
 | 
			
		||||
 | 
			
		||||
			//S16_TransparentSkirt
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "S16_TransparentSkirt", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
 | 
			
		||||
 | 
			
		||||
			//S16_Vibrator
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "S16_Vibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
 | 
			
		||||
 | 
			
		||||
			//S16_VibratorDouble
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "S16_VibratorDouble", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
 | 
			
		||||
 | 
			
		||||
			//S16_WiredVibrator
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "S16_WiredVibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
 | 
			
		||||
 | 
			
		||||
			//S16_DoubleWiredVibrator
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "S16_DoubleWiredVibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
 | 
			
		||||
 | 
			
		||||
			//S16_WiredAnalVibrator
 | 
			
		||||
			UpdateRimNudeData(rimNudeData, "S16_WiredAnalVibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								Source/Scripts/Utilities/DebugMode.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Source/Scripts/Utilities/DebugMode.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using Verse;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    public static class DebugMode
 | 
			
		||||
    {
 | 
			
		||||
        public static void Message(string text)
 | 
			
		||||
        {
 | 
			
		||||
            if (BasicSettings.debugMode)
 | 
			
		||||
            { Log.Message("[DEBUG] " + text); }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										144
									
								
								Source/Scripts/Utilities/GraphicMaskingUtility.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								Source/Scripts/Utilities/GraphicMaskingUtility.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,144 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using HarmonyLib;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using Verse;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
	public static class GraphicMaskingUtility
 | 
			
		||||
	{
 | 
			
		||||
		public static Texture2D GetReadableTexture2D(Texture2D source, int newWidth, int newHeight, Material mat = null) //rescales texture to newWidth and newHeight
 | 
			
		||||
		{
 | 
			
		||||
			source.filterMode = FilterMode.Trilinear;
 | 
			
		||||
 | 
			
		||||
			RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight);
 | 
			
		||||
			rt.filterMode = FilterMode.Trilinear;
 | 
			
		||||
 | 
			
		||||
			RenderTexture.active = rt;
 | 
			
		||||
			if (mat != null)
 | 
			
		||||
			{ Graphics.Blit(source, rt, mat); }
 | 
			
		||||
			
 | 
			
		||||
			else
 | 
			
		||||
			{ Graphics.Blit(source, rt); }
 | 
			
		||||
 | 
			
		||||
			Texture2D nTex = new Texture2D(newWidth, newHeight, TextureFormat.RGBA32, mipChain: true);
 | 
			
		||||
			nTex.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0);
 | 
			
		||||
			nTex.name = source.name;
 | 
			
		||||
			nTex.filterMode = FilterMode.Trilinear;
 | 
			
		||||
			nTex.anisoLevel = 2;
 | 
			
		||||
			nTex.Apply(updateMipmaps: true);
 | 
			
		||||
 | 
			
		||||
			GL.Clear(true, true, Color.clear);
 | 
			
		||||
			RenderTexture.active = null;
 | 
			
		||||
			RenderTexture.ReleaseTemporary(rt);
 | 
			
		||||
 | 
			
		||||
			return nTex;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Texture2D GetReadableTexture2D(Texture2D source, Material mat = null)
 | 
			
		||||
		{
 | 
			
		||||
			return GetReadableTexture2D(source, source.width, source.height, mat);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Texture2D ApplyMaskToTexture2D(Texture2D mainTex, Texture2D maskTex, bool writeOverMainTex)
 | 
			
		||||
		{
 | 
			
		||||
			if (mainTex == null || maskTex == null)
 | 
			
		||||
			{
 | 
			
		||||
				DebugMode.Message("mainTex or maskTex is missing!");
 | 
			
		||||
				return mainTex;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Color[] mainArray = GetReadableTexture2D(mainTex).GetPixels();
 | 
			
		||||
			Color[] maskArray = GetReadableTexture2D(maskTex, mainTex.width, mainTex.height).GetPixels();
 | 
			
		||||
 | 
			
		||||
			for (int j = 0; j < mainArray.Length; j++)
 | 
			
		||||
			{
 | 
			
		||||
				if (maskArray[j] == Color.white)
 | 
			
		||||
				{ /*null*/ }
 | 
			
		||||
 | 
			
		||||
				else if (maskArray[j].a == 0)
 | 
			
		||||
				{ mainArray[j].a = 0; }
 | 
			
		||||
 | 
			
		||||
				else if (mainArray[j].a > 0 && maskArray[j].a > 0 && writeOverMainTex)
 | 
			
		||||
				{ mainArray[j] = new Color(Mathf.Min(mainArray[j].r, maskArray[j].r), Mathf.Min(mainArray[j].g, maskArray[j].g), Mathf.Min(mainArray[j].b, maskArray[j].b), Mathf.Min(mainArray[j].a, maskArray[j].a)); }
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			Texture2D newTex = new Texture2D(mainTex.width, mainTex.height, TextureFormat.RGBA32, mipChain: true);
 | 
			
		||||
			newTex.SetPixels(mainArray);
 | 
			
		||||
			newTex.filterMode = FilterMode.Trilinear;
 | 
			
		||||
			newTex.anisoLevel = 2;
 | 
			
		||||
			newTex.Apply(updateMipmaps: true);
 | 
			
		||||
 | 
			
		||||
			return newTex;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Graphic ApplyGraphicWithMasks(Graphic graphic, Graphic graphicWithMask, bool writeOverMainTex)
 | 
			
		||||
		{
 | 
			
		||||
			for (int i = 0; i < 4; i++)
 | 
			
		||||
			{
 | 
			
		||||
				Texture2D mainTex = (Texture2D)graphic.MatAt(new Rot4(i)).mainTexture;
 | 
			
		||||
				Texture2D maskTex = graphicWithMask.MatAt(new Rot4(i)).GetMaskTexture();
 | 
			
		||||
				graphic.MatAt(new Rot4(i)).mainTexture = ApplyMaskToTexture2D(mainTex, maskTex, writeOverMainTex);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return graphic;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static Graphic ApplyGraphicWithMasks(Graphic graphic, string mask, bool writeOverMainTex)
 | 
			
		||||
		{
 | 
			
		||||
			for (int i = 0; i < 4; i++)
 | 
			
		||||
			{
 | 
			
		||||
				Texture2D mainTex = (Texture2D)graphic.MatAt(new Rot4(i)).mainTexture;
 | 
			
		||||
 | 
			
		||||
				if (mainTex == null)
 | 
			
		||||
				{ DebugMode.Message("Main Texture2D not found for " + graphic.path + ". Rotation: " + i.ToString()); continue; }
 | 
			
		||||
 | 
			
		||||
				string suffix = string.Empty;
 | 
			
		||||
				switch (i)
 | 
			
		||||
				{
 | 
			
		||||
					case 0: suffix = "_north"; break;
 | 
			
		||||
					case 1: suffix = "_east"; break;
 | 
			
		||||
					case 2: suffix = "_south"; break;
 | 
			
		||||
					case 3: suffix = "_west"; break;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				Texture2D maskTex = ContentFinder<Texture2D>.Get(mask + suffix, false);
 | 
			
		||||
 | 
			
		||||
				if (maskTex == null)
 | 
			
		||||
				{ DebugMode.Message("Mask Texture2D not found for " + mask + ". Rotation: " + i.ToString()); continue; }
 | 
			
		||||
 | 
			
		||||
				graphic.MatAt(new Rot4(i)).mainTexture = ApplyMaskToTexture2D(mainTex, maskTex, writeOverMainTex);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return graphic;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static void ResetGraphic(Graphic graphic)
 | 
			
		||||
		{
 | 
			
		||||
			for (int i = 0; i < 4; i++)
 | 
			
		||||
			{
 | 
			
		||||
				string suffix = string.Empty;
 | 
			
		||||
				switch (i)
 | 
			
		||||
				{
 | 
			
		||||
					case 0: suffix = "_north"; break;
 | 
			
		||||
					case 1: suffix = "_east"; break;
 | 
			
		||||
					case 2: suffix = "_south"; break;
 | 
			
		||||
					case 3: suffix = "_west"; break;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				Texture2D texture2D = ContentFinder<Texture2D>.Get(graphic.path + suffix, false);
 | 
			
		||||
 | 
			
		||||
				if (texture2D == null && i == 3)
 | 
			
		||||
				{ texture2D = ContentFinder<Texture2D>.Get(graphic.path + "_east", false); }
 | 
			
		||||
		
 | 
			
		||||
				if (texture2D == null)
 | 
			
		||||
				{ texture2D = ContentFinder<Texture2D>.Get(graphic.path + "_north", false); }
 | 
			
		||||
 | 
			
		||||
				if (texture2D != null)
 | 
			
		||||
				{ graphic.MatAt(new Rot4(i)).mainTexture = texture2D; }
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										219
									
								
								Source/Scripts/Utilities/HandAnimationUtility.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								Source/Scripts/Utilities/HandAnimationUtility.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,219 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using Verse;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using HarmonyLib;
 | 
			
		||||
using Rimworld_Animations;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    public static class HandAnimationUtility
 | 
			
		||||
    {
 | 
			
		||||
        public static BodyPartDef handDef;
 | 
			
		||||
 | 
			
		||||
        public static bool BodyPartIsBeingTouched(Pawn pawn, string bodypartFilePath, out List<HandAnimationData> handAnimationData)
 | 
			
		||||
        {
 | 
			
		||||
            handAnimationData = new List<HandAnimationData>();
 | 
			
		||||
 | 
			
		||||
            ActorAnimationData actorAnimationData = pawn.GetAnimationData();
 | 
			
		||||
            HandAnimationDef handAnimationDef = DefDatabase<HandAnimationDef>.AllDefs.FirstOrDefault(x => x.animationDefName == actorAnimationData.animationDef.defName);
 | 
			
		||||
 | 
			
		||||
            if (handAnimationDef == null)
 | 
			
		||||
            { return false; }
 | 
			
		||||
 | 
			
		||||
            foreach (HandAnimationData datum in handAnimationDef.handAnimationData)
 | 
			
		||||
            {
 | 
			
		||||
                if (datum.stageID != actorAnimationData.currentStage || datum.actorID != actorAnimationData.actorID)
 | 
			
		||||
                { continue; }
 | 
			
		||||
 | 
			
		||||
                if (datum.bodySide.NullOrEmpty() == false && bodypartFilePath.ToLower().Contains(datum.bodySide) == false)
 | 
			
		||||
                { continue; }
 | 
			
		||||
                    
 | 
			
		||||
                if (datum.targetBodyPart.NullOrEmpty() == false && bodypartFilePath.ToLower().Contains(datum.targetBodyPart.ToLower()))
 | 
			
		||||
                { handAnimationData.Add(datum); }
 | 
			
		||||
 | 
			
		||||
                else if (datum.targetBodyParts.Any(x => bodypartFilePath.ToLower().Contains(x.ToLower())))
 | 
			
		||||
                { handAnimationData.Add(datum); }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return handAnimationData.NullOrEmpty() == false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Vector3 GetHandPosition(Pawn pawn, HandAnimationData handAnimationData, Vector3 basePosition, float baseAngle)
 | 
			
		||||
        {
 | 
			
		||||
            var methodInfo = AccessTools.Method(typeof(HandAnimationUtility), handAnimationData.motion, null, null);
 | 
			
		||||
 | 
			
		||||
            if (methodInfo == null)
 | 
			
		||||
            {
 | 
			
		||||
                Debug.LogWarning("Hand anaimation motion '" + handAnimationData.motion + "' was not found");
 | 
			
		||||
                return default; 
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Vector3 handPosition = (Vector3)methodInfo.Invoke(null, new object[] { pawn, handAnimationData, baseAngle });
 | 
			
		||||
  
 | 
			
		||||
            return handPosition * pawn.RaceProps.baseBodySize + basePosition;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static float GetGenitalSize(Pawn pawn, string genitalName)
 | 
			
		||||
        {
 | 
			
		||||
            switch(genitalName.ToLower())
 | 
			
		||||
            {
 | 
			
		||||
                case "penis": return pawn.health.hediffSet.hediffs.First(x => x.def.defName.ToLower().Contains("penis")).Severity;
 | 
			
		||||
                case "breasts": return pawn.health.hediffSet.hediffs.First(x => x.def.defName.ToLower().Contains("breasts")).Severity;
 | 
			
		||||
                case "vagina": return 0.1f;
 | 
			
		||||
                case "anus": return 0.1f;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return 0.1f;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Vector3 Motion_StrokeGenitalsUpAndDownShort_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
 | 
			
		||||
        {
 | 
			
		||||
            Vector3 handPosition = new Vector3();
 | 
			
		||||
            ActorAnimationData data = pawn.GetAnimationData();
 | 
			
		||||
 | 
			
		||||
            float p = (Mathf.PingPong(data.stageTicks, handAnimationData.cycleTime) / handAnimationData.cycleTime);
 | 
			
		||||
            float length = 0.035f;
 | 
			
		||||
 | 
			
		||||
            handPosition.x = 0;
 | 
			
		||||
            handPosition.z = length * p;
 | 
			
		||||
 | 
			
		||||
            handPosition = handPosition.RotatedBy(baseAngle);
 | 
			
		||||
 | 
			
		||||
            return handPosition;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Vector3 Motion_StrokeGenitalsUpAndDown_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
 | 
			
		||||
        {
 | 
			
		||||
            Vector3 handPosition = new Vector3();
 | 
			
		||||
            ActorAnimationData data = pawn.GetAnimationData();
 | 
			
		||||
 | 
			
		||||
            float p = (Mathf.PingPong(data.stageTicks, handAnimationData.cycleTime) / handAnimationData.cycleTime);
 | 
			
		||||
            float size = GetGenitalSize(pawn, handAnimationData.targetBodyPart) * 0.2f;
 | 
			
		||||
            float m = (data.actorFacing == Rot4.North ? -1f : 1f) * (handAnimationData.mirror ? -1f : 1f) * (pawn.TryGetComp<CompBodyAnimator>().Mirror ? -1f : 1f);
 | 
			
		||||
 | 
			
		||||
            handPosition.x = 0.025f * m;
 | 
			
		||||
            handPosition.z = size * p;
 | 
			
		||||
 | 
			
		||||
            handPosition = handPosition.RotatedBy(baseAngle);
 | 
			
		||||
 | 
			
		||||
            return handPosition;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Vector3 Motion_StrokeGenitalsUpAndDown_FacingEW(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
 | 
			
		||||
        {
 | 
			
		||||
            Vector3 handPosition = new Vector3();
 | 
			
		||||
            ActorAnimationData data = pawn.GetAnimationData();
 | 
			
		||||
 | 
			
		||||
            float p = Mathf.PingPong(data.stageTicks, handAnimationData.cycleTime) / handAnimationData.cycleTime;
 | 
			
		||||
            float size = GetGenitalSize(pawn, handAnimationData.targetBodyPart) * 0.2f;
 | 
			
		||||
            float m = (data.actorFacing == Rot4.West ? -1f : 1f) * (handAnimationData.mirror ? -1f : 1f);
 | 
			
		||||
 | 
			
		||||
            handPosition.x = Mathf.Sin(m * (baseAngle + 45f) / 180f * Mathf.PI) * size * p;
 | 
			
		||||
            handPosition.z = Mathf.Cos(m * (baseAngle + 45f) / 180f * Mathf.PI) * size * p;
 | 
			
		||||
 | 
			
		||||
            return handPosition;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Vector3 Motion_RubGenitals_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
 | 
			
		||||
        {
 | 
			
		||||
            Vector3 handPosition = new Vector3();
 | 
			
		||||
            ActorAnimationData data = pawn.GetAnimationData();
 | 
			
		||||
 | 
			
		||||
            float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
 | 
			
		||||
            float m = (data.actorFacing == Rot4.North ? 1f : -1f) * (handAnimationData.mirror ? -1f : 1f) * (pawn.TryGetComp<CompBodyAnimator>().Mirror ? -1f : 1f);
 | 
			
		||||
 | 
			
		||||
            handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.05f - 0.025f) * m;
 | 
			
		||||
            handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f + 0.03f;
 | 
			
		||||
 | 
			
		||||
            handPosition = handPosition.RotatedBy(baseAngle);
 | 
			
		||||
 | 
			
		||||
            return handPosition;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Vector3 Motion_RubGenitals_FacingEW(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
 | 
			
		||||
        {
 | 
			
		||||
            Vector3 handPosition = new Vector3();
 | 
			
		||||
            ActorAnimationData data = pawn.GetAnimationData();
 | 
			
		||||
 | 
			
		||||
            float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
 | 
			
		||||
            float m = (data.actorFacing == Rot4.West ? 1f : -1f) * (handAnimationData.mirror ? -1f : 1f);
 | 
			
		||||
 | 
			
		||||
            handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.005f - 0.05f) * m;
 | 
			
		||||
            handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f;
 | 
			
		||||
            //handPosition.y = -0.1f;
 | 
			
		||||
 | 
			
		||||
            handPosition = handPosition.RotatedBy(baseAngle);
 | 
			
		||||
 | 
			
		||||
            return handPosition;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Vector3 Motion_RubBreasts_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
 | 
			
		||||
        {
 | 
			
		||||
            Vector3 handPosition = new Vector3();
 | 
			
		||||
            ActorAnimationData data = pawn.GetAnimationData();
 | 
			
		||||
 | 
			
		||||
            float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
 | 
			
		||||
            float m = (data.actorFacing == Rot4.North ? -1f : 1f) * (handAnimationData.mirror ? -1f : 1f) * (pawn.TryGetComp<CompBodyAnimator>().Mirror ? -1f : 1f);
 | 
			
		||||
            float size = GetGenitalSize(pawn, "breasts");
 | 
			
		||||
 | 
			
		||||
            handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.05f * size - size * 0.25f) * m;
 | 
			
		||||
            handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f - size * 0.125f;
 | 
			
		||||
 | 
			
		||||
            handPosition = handPosition.RotatedBy(baseAngle);
 | 
			
		||||
 | 
			
		||||
            return handPosition;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Vector3 Motion_RubBreasts_FacingEW(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
 | 
			
		||||
        {
 | 
			
		||||
            Vector3 handPosition = new Vector3();
 | 
			
		||||
            ActorAnimationData data = pawn.GetAnimationData();
 | 
			
		||||
 | 
			
		||||
            float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
 | 
			
		||||
            float m = (data.actorFacing == Rot4.West ? 1f : -1f) * (handAnimationData.mirror ? -1f : 1f);
 | 
			
		||||
            float size = GetGenitalSize(pawn, "breasts");
 | 
			
		||||
 | 
			
		||||
            handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.005f - size * 0.25f) * m;
 | 
			
		||||
            handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f - size * 0.125f;
 | 
			
		||||
 | 
			
		||||
            handPosition = handPosition.RotatedBy(baseAngle);
 | 
			
		||||
 | 
			
		||||
            return handPosition;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Graphic GetHandGraphic(Pawn touchingPawn, string touchedBodyAddonName, HandAnimationData handAnimationData)
 | 
			
		||||
        {
 | 
			
		||||
            string handGraphicPath = "Hands/HandClean";
 | 
			
		||||
            Color skinColour = touchingPawn.story.SkinColor;
 | 
			
		||||
            float handSize = 0.6667f * touchingPawn.RaceProps.baseBodySize;
 | 
			
		||||
 | 
			
		||||
            return GraphicDatabase.Get<Graphic_Single>(handGraphicPath, ShaderDatabase.Cutout, new Vector2(handSize, handSize), skinColour);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool TryToDrawHand(Pawn pawn, string bodyAddonName, Vector3 bodyAddonPosition, float bodyAddonAngle, Rot4 bodyAddonRotation, PawnRenderFlags renderFlags)
 | 
			
		||||
        {
 | 
			
		||||
            if (pawn.TryGetComp<CompBodyAnimator>() != null && pawn.TryGetComp<CompBodyAnimator>().isAnimating && BodyPartIsBeingTouched(pawn, bodyAddonName, out List<HandAnimationData> handAnimationData))
 | 
			
		||||
            {
 | 
			
		||||
                foreach (HandAnimationData datum in handAnimationData)
 | 
			
		||||
                {
 | 
			
		||||
                    Pawn touchingPawn = datum.touchingActorID >= 0 && pawn.GetAllSexParticipants().Count > datum.touchingActorID ? pawn.GetAllSexParticipants()[datum.touchingActorID] : pawn;
 | 
			
		||||
 | 
			
		||||
                    Graphic handgraphic = GetHandGraphic(touchingPawn, bodyAddonName, datum);
 | 
			
		||||
                    Vector3 handPosition = GetHandPosition(pawn, datum, bodyAddonPosition, bodyAddonAngle);
 | 
			
		||||
 | 
			
		||||
                    GenDraw.DrawMeshNowOrLater(mesh: handgraphic.MeshAt(rot: bodyAddonRotation),
 | 
			
		||||
                        loc: handPosition + new Vector3(0f, 0.022f, 0f),
 | 
			
		||||
                        quat: Quaternion.identity,
 | 
			
		||||
                        mat: handgraphic.MatAt(rot: bodyAddonRotation), renderFlags.FlagSet(PawnRenderFlags.DrawNow));
 | 
			
		||||
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								Source/Scripts/Utilities/MathUtility.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								Source/Scripts/Utilities/MathUtility.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using Verse;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    public static class MathUtility
 | 
			
		||||
    {
 | 
			
		||||
        public static float Repeat(float value, float min, float max)
 | 
			
		||||
        {
 | 
			
		||||
            if (Mathf.Abs(max) < Mathf.Abs(min))
 | 
			
		||||
            {
 | 
			
		||||
                Log.Error("RepeatDual: min value must be greater than max value");
 | 
			
		||||
                return -1;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            float range = max - min;
 | 
			
		||||
            float m = value % range;
 | 
			
		||||
 | 
			
		||||
            if (m < 0)
 | 
			
		||||
            { m = range + m; }
 | 
			
		||||
 | 
			
		||||
            return min + m;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static IntVec3 FindRandomCellNearPawn(Pawn pawn, int maxRadius)
 | 
			
		||||
        {
 | 
			
		||||
            if (maxRadius > 0)
 | 
			
		||||
            {
 | 
			
		||||
                for (int radius = 1; radius < maxRadius; radius++)
 | 
			
		||||
                {
 | 
			
		||||
                    List<IntVec3> cells = GenRadial.RadialCellsAround(pawn.Position, radius + 0.75f, false).Where(x => x.Standable(pawn.Map) && x.GetRoom(pawn.Map) == pawn.GetRoom())?.ToList();
 | 
			
		||||
 | 
			
		||||
                    if (cells.NullOrEmpty() == false && cells.Count > 0)
 | 
			
		||||
                    { return cells.RandomElement(); }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return GenAdj.RandomAdjacentCellCardinal(pawn);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								Source/Scripts/Utilities/PatchDefOf.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Source/Scripts/Utilities/PatchDefOf.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
using Verse;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using AlienRace;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    [DefOf]
 | 
			
		||||
    public static class PatchBodyPartGroupDefOf
 | 
			
		||||
    {
 | 
			
		||||
        public static BodyPartGroupDef GenitalsBPG;
 | 
			
		||||
        public static BodyPartGroupDef AnusBPG;
 | 
			
		||||
        public static BodyPartGroupDef ChestBPG;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								Source/Scripts/Utilities/SettingsUtility.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Source/Scripts/Utilities/SettingsUtility.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    public static class SettingsUtility
 | 
			
		||||
    {
 | 
			
		||||
		public static float Align(float objectDim, float columnDim)
 | 
			
		||||
		{
 | 
			
		||||
			return (columnDim - objectDim) * 0.5f;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										241
									
								
								Source/Scripts/Utilities/SexInteractionUtility.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								Source/Scripts/Utilities/SexInteractionUtility.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,241 @@
 | 
			
		|||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Verse;
 | 
			
		||||
using Verse.AI;
 | 
			
		||||
using Verse.AI.Group;
 | 
			
		||||
using RimWorld;
 | 
			
		||||
using rjw;
 | 
			
		||||
using RJWSexperience.Ideology;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
 | 
			
		||||
namespace Rimworld_Animations_Patch
 | 
			
		||||
{
 | 
			
		||||
    public static class SexInteractionUtility
 | 
			
		||||
    {
 | 
			
		||||
		public static bool PawnCaughtLovinByWitness(Pawn pawn, Pawn witness)
 | 
			
		||||
		{
 | 
			
		||||
			if (witness == null || pawn == witness || witness.AnimalOrWildMan() || witness.RaceProps.IsMechanoid || witness.Awake() == false || witness.CanSee(pawn) == false)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			if (pawn.IsHavingSex() == false && pawn.IsMasturbating() == false)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			List<Pawn> sexParticipants = pawn.GetAllSexParticipants();
 | 
			
		||||
			bool witnessIsCourtingSexParticipant = witness.jobs.curDriver is JobDriver_SexBaseInitiator && sexParticipants.Contains((witness.jobs.curDriver as JobDriver_SexBaseInitiator).Partner);
 | 
			
		||||
 | 
			
		||||
			if (sexParticipants.Contains(witness) || witnessIsCourtingSexParticipant)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool PawnIsCheatingOnPartner(Pawn pawn, Pawn partner)
 | 
			
		||||
		{
 | 
			
		||||
			if (BasicSettings.worryAboutInfidelity == false || pawn.IsMasturbating() || pawn.IsHavingSex() == false || pawn.GetAllSexParticipants().Contains(partner))
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			if (pawn.GetAllSexParticipants().Any(x => pawn.GetSpouseCount(false) > 0 && pawn.GetSpouses(false).Contains(x)))
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			return partner.IsLoverOfOther(pawn) && pawn.HasTrait("Polygamist") == false && partner.HasTrait("Polygamist") == false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool PawnCanInvitePasserbyForSex(Pawn passerby, List<Pawn> participants)
 | 
			
		||||
		{
 | 
			
		||||
			if (passerby == null || participants.NullOrEmpty() || participants.Contains(passerby) || passerby.AnimalOrWildMan() || passerby.RaceProps.IsMechanoid || passerby.Awake() == false || participants.All(x => x.CanSee(passerby) == false))
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			if (participants.Any(x => x.IsForbidden(passerby) || x.HostileTo(passerby) || PawnIsCheatingOnPartner(x, passerby)) || CasualSex_Helper.CanHaveSex(passerby) == false || xxx.IsTargetPawnOkay(passerby) == false || participants.Count > 2)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			if (SexUtility.ReadyForHookup(passerby) &&
 | 
			
		||||
				(passerby?.jobs?.curJob == null || (passerby.jobs.curJob.playerForced == false && CasualSex_Helper.quickieAllowedJobs.Contains(passerby.jobs.curJob.def))) &&
 | 
			
		||||
				participants.Any(x => SexAppraiser.would_fuck(x, passerby) > 0.1f && SexAppraiser.would_fuck(passerby, x) > 0.1f) &&
 | 
			
		||||
				participants.All(x => SexAppraiser.would_fuck(x, passerby, false, false, true) > 0.1f && SexAppraiser.would_fuck(passerby, x, false, false, true) > 0.1f))
 | 
			
		||||
			{
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static TabooStatus CheckSexJobAgainstMorals(Pawn pawn, JobDriver_Sex jobDriver, out Precept precept)
 | 
			
		||||
		{		
 | 
			
		||||
			bool sexIsNecro = jobDriver.Partner != null && jobDriver.Partner.Dead;
 | 
			
		||||
			bool sexIsBeastial = jobDriver.Partner != null && jobDriver.Partner.RaceProps.Animal;
 | 
			
		||||
			bool sexIsRape = sexIsBeastial == false && sexIsNecro == false && 
 | 
			
		||||
				(jobDriver is JobDriver_Rape || jobDriver is JobDriver_RapeEnemy || jobDriver is JobDriver_SexBaseRecieverRaped) && 
 | 
			
		||||
				jobDriver.Partner.IsPrisoner == false && jobDriver.Partner.IsSlave == false;
 | 
			
		||||
			bool sexIsSlaveRape = sexIsBeastial == false && sexIsNecro == false &&
 | 
			
		||||
				(jobDriver is JobDriver_Rape || jobDriver is JobDriver_RapeEnemy || jobDriver is JobDriver_SexBaseRecieverRaped) &&
 | 
			
		||||
				(jobDriver.Partner.IsPrisoner || jobDriver.Partner.IsSlave);
 | 
			
		||||
			bool sexIsXeno = jobDriver.Partner != null && jobDriver.Partner.def.defName != jobDriver.pawn.def.defName;
 | 
			
		||||
 | 
			
		||||
			TabooStatus tabooStatus = TabooStatus.NotTaboo;
 | 
			
		||||
			precept = null;
 | 
			
		||||
 | 
			
		||||
			if (BasicSettings.worryAboutNecro && sexIsNecro && xxx.is_necrophiliac(pawn) == false)
 | 
			
		||||
			{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Necrophilia"), TabooStatus.MajorTaboo, out precept); }
 | 
			
		||||
 | 
			
		||||
			else if (BasicSettings.worryAboutBeastiality && sexIsBeastial && xxx.is_zoophile(pawn) == false)
 | 
			
		||||
			{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Beastility"), TabooStatus.MajorTaboo, out precept); }
 | 
			
		||||
 | 
			
		||||
			else if (BasicSettings.worryAboutRape && sexIsRape && xxx.is_rapist(pawn) == false)
 | 
			
		||||
			{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Rape"), TabooStatus.MajorTaboo, out precept); }
 | 
			
		||||
 | 
			
		||||
			else if (BasicSettings.worryAboutRape && BasicSettings.ignoreSlaveRape == false && sexIsSlaveRape && xxx.is_rapist(pawn) == false)
 | 
			
		||||
			{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Rape"), TabooStatus.MajorTaboo, out precept); }
 | 
			
		||||
 | 
			
		||||
			else if (BasicSettings.worryAboutXeno && sexIsXeno && pawn.HasTrait("Xenophobia") && pawn.story.traits.DegreeOfTrait(DefDatabase<TraitDef>.GetNamedSilentFail("Xenophobia")) > 0)
 | 
			
		||||
			{ tabooStatus = TabooStatus.MajorTaboo; }
 | 
			
		||||
 | 
			
		||||
			else if (BasicSettings.worryAboutXeno && sexIsXeno)
 | 
			
		||||
			{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("HAR_AlienDating"), TabooStatus.NotTaboo, out precept); }
 | 
			
		||||
	
 | 
			
		||||
			//DebugMode.Message("Sex job is: " + jobDriver + " Issue is: " + (precept?.def?.issue?.defName).ToStringSafe() + " Opinion is: " + (precept?.def?.defName).ToStringSafe() + " Judgement is: " + tabooStatus.ToString());
 | 
			
		||||
 | 
			
		||||
			return tabooStatus;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static TabooStatus GetTabooStatusOfIssue(Pawn pawn, IssueDef issueDef, TabooStatus defaultTabboStatus, out Precept precept)
 | 
			
		||||
		{
 | 
			
		||||
			if (pawn.IssueIsMajorTaboo(issueDef, out precept))
 | 
			
		||||
			{ return TabooStatus.MajorTaboo; }
 | 
			
		||||
 | 
			
		||||
			if (pawn.IssueIsMinorTaboo(issueDef, out precept))
 | 
			
		||||
			{ return TabooStatus.MinorTaboo; }
 | 
			
		||||
 | 
			
		||||
			return defaultTabboStatus;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public static bool ResolveThoughtsForWhenSexIsWitnessed(Pawn pawn, Pawn witness, out bool witnessJoiningSex)
 | 
			
		||||
		{
 | 
			
		||||
			witnessJoiningSex = false;
 | 
			
		||||
 | 
			
		||||
			if (pawn.IsAnimal() || pawn.RaceProps.IsMechanoid || pawn.Dead)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			if (witness.IsAnimal() || witness.RaceProps.IsMechanoid || witness.Dead)
 | 
			
		||||
			{ return false; }
 | 
			
		||||
 | 
			
		||||
			JobDriver_Sex jobDriver = pawn.jobs.curDriver as JobDriver_Sex;
 | 
			
		||||
 | 
			
		||||
			string pawnThoughtDefName = pawn.IsMasturbating() ? "SeenMasturbating" : "SeenHavingSex";
 | 
			
		||||
			string witnessThoughtDefName = pawn.IsMasturbating() ? "SawMasturbation" : "SawSex";
 | 
			
		||||
 | 
			
		||||
			bool pawnIsExhibitionist = pawn.HasTrait("Exhibitionist") || xxx.has_quirk(pawn, "Exhibitionist");
 | 
			
		||||
			if (pawnIsExhibitionist)
 | 
			
		||||
			{ pawnThoughtDefName += "Exhibitionist"; }
 | 
			
		||||
 | 
			
		||||
			bool witnessIsVoyeur = witness.HasTrait("Voyeur") || xxx.has_quirk(witness, "Voyeur");
 | 
			
		||||
			if (witnessIsVoyeur)
 | 
			
		||||
			{ witnessThoughtDefName += "Voyeur"; }
 | 
			
		||||
 | 
			
		||||
			bool sexIsRitual = pawn.GetLord() != null && pawn.GetLord().LordJob is LordJob_Ritual && witness?.Ideo == pawn?.Ideo;
 | 
			
		||||
			bool pawnIsVictim = pawn.CurJob.def == xxx.gettin_raped || pawn.Dead;
 | 
			
		||||
			bool pawnIsCheating = pawnIsVictim == false && PawnIsCheatingOnPartner(pawn, witness);
 | 
			
		||||
 | 
			
		||||
			witnessJoiningSex = Random.value < BasicSettings.chanceForOtherToJoinInSex && PawnCanInvitePasserbyForSex(witness, pawn.GetAllSexParticipants());
 | 
			
		||||
 | 
			
		||||
			// Determine if there are any issues with the witness' morals
 | 
			
		||||
			TabooStatus tabooStatus = CheckSexJobAgainstMorals(witness, jobDriver, out Precept precept);
 | 
			
		||||
 | 
			
		||||
			if (tabooStatus == TabooStatus.MajorTaboo)
 | 
			
		||||
			{ witnessThoughtDefName = "SawMajorTaboo"; witnessJoiningSex = false; }
 | 
			
		||||
			
 | 
			
		||||
			else if (tabooStatus == TabooStatus.MinorTaboo)
 | 
			
		||||
			{ witnessThoughtDefName = "SawTaboo"; witnessJoiningSex = false; }
 | 
			
		||||
 | 
			
		||||
			else if (pawnIsCheating)
 | 
			
		||||
			{ witnessThoughtDefName = "CheatedOnMe"; witnessJoiningSex = false; }
 | 
			
		||||
 | 
			
		||||
			else if (BasicSettings.needPrivacy == false)
 | 
			
		||||
			{ witnessThoughtDefName = ""; }
 | 
			
		||||
 | 
			
		||||
			// Apply thoughts to witness
 | 
			
		||||
			ThoughtDef witnessThoughtDef = DefDatabase<ThoughtDef>.GetNamedSilentFail(witnessThoughtDefName);
 | 
			
		||||
 | 
			
		||||
			if (witnessThoughtDef != null && pawnIsVictim == false && witnessJoiningSex == false && sexIsRitual == false)
 | 
			
		||||
			{
 | 
			
		||||
				witness.needs.mood.thoughts.memories.TryGainMemory(witnessThoughtDef, pawn, precept);
 | 
			
		||||
 | 
			
		||||
				if (witnessThoughtDef.stages[0].baseMoodEffect < 0)
 | 
			
		||||
				{ FleckMaker.ThrowMetaIcon(witness.Position, witness.Map, FleckDefOf.IncapIcon); }
 | 
			
		||||
 | 
			
		||||
				// Fight or flight reaction
 | 
			
		||||
				if (BasicSettings.majorTabooCanStartFights &&
 | 
			
		||||
					(tabooStatus == TabooStatus.MajorTaboo || pawnIsCheating) &&
 | 
			
		||||
					witness.Drafted == false && 
 | 
			
		||||
					witness.jobs.curDriver is JobDriver_Flee == false &&
 | 
			
		||||
					witness.jobs.curDriver is JobDriver_AttackMelee == false &&
 | 
			
		||||
					witness.jobs.curDriver is JobDriver_Vomit == false)
 | 
			
		||||
				{
 | 
			
		||||
					// Fight
 | 
			
		||||
					if (pawn.RaceProps.Humanlike && witness.RaceProps.Humanlike && witness.DislikesViolence() == false && (Random.value < 0.2f || witness.EnjoysViolence()) && witness.HostileTo(pawn) == false && InteractionUtility.TryGetRandomVerbForSocialFight(witness, out Verb verbToUse))
 | 
			
		||||
					{
 | 
			
		||||
						if (witness.LastAttackedTarget.Pawn != pawn || (pawn.mindState.lastAttackTargetTick < 0 && pawn.mindState.lastAttackTargetTick + Find.TickManager.TicksGame > 180))
 | 
			
		||||
						{
 | 
			
		||||
							pawn.mindState.lastAttackTargetTick = Find.TickManager.TicksGame;
 | 
			
		||||
							string message = witness.LabelShort + " is going to punish " + pawn.LabelShort + " for " + GenderUtility.GetPossessive(pawn.gender) + " transgression.";
 | 
			
		||||
							Messages.Message(message, pawn, MessageTypeDefOf.NegativeEvent);
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						Job job = JobMaker.MakeJob(JobDefOf.SocialFight, pawn);
 | 
			
		||||
						job.maxNumMeleeAttacks = 1;
 | 
			
		||||
						job.verbToUse = verbToUse;
 | 
			
		||||
 | 
			
		||||
						witness.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
 | 
			
		||||
						witness.jobs.StartJob(job);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// Vomit
 | 
			
		||||
					else if (jobDriver.Partner != null && jobDriver.Partner.Dead)
 | 
			
		||||
					{
 | 
			
		||||
						Job jobVomit = JobMaker.MakeJob(JobDefOf.Vomit);
 | 
			
		||||
						Job jobFlee = JobMaker.MakeJob(JobDefOf.FleeAndCower, CellFinderLoose.GetFleeDest(witness, new List<Thing>() { pawn }, 24f), pawn);
 | 
			
		||||
 | 
			
		||||
						witness.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
 | 
			
		||||
						witness.jobs.StartJob(jobVomit);
 | 
			
		||||
						witness.jobs.jobQueue.EnqueueFirst(jobFlee);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// Flight
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						Job job = JobMaker.MakeJob(JobDefOf.FleeAndCower, CellFinderLoose.GetFleeDest(witness, new List<Thing>() { pawn }, 24f), pawn);
 | 
			
		||||
						witness.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
 | 
			
		||||
						witness.jobs.StartJob(job);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Check issue against pawn precepts
 | 
			
		||||
			tabooStatus = CheckSexJobAgainstMorals(pawn, jobDriver, out precept);
 | 
			
		||||
 | 
			
		||||
			if (tabooStatus == TabooStatus.MajorTaboo)
 | 
			
		||||
			{ pawnThoughtDefName = "SeenCommittingMajorTaboo"; witnessJoiningSex = false; }
 | 
			
		||||
 | 
			
		||||
			else if (tabooStatus == TabooStatus.MinorTaboo)
 | 
			
		||||
			{ pawnThoughtDefName = "SeenCommittingTaboo"; witnessJoiningSex = false; }
 | 
			
		||||
 | 
			
		||||
			else if (pawnIsCheating)
 | 
			
		||||
			{ pawnThoughtDefName = "CaughtCheating"; witnessJoiningSex = false; }
 | 
			
		||||
 | 
			
		||||
			else if (BasicSettings.needPrivacy == false)
 | 
			
		||||
			{ pawnThoughtDefName = ""; }
 | 
			
		||||
 | 
			
		||||
			// Apply thoughts to pawn
 | 
			
		||||
			ThoughtDef pawnThoughtDef = DefDatabase<ThoughtDef>.GetNamedSilentFail(pawnThoughtDefName);
 | 
			
		||||
 | 
			
		||||
			if (pawnThoughtDef != null && pawnIsVictim == false && witnessJoiningSex == false && sexIsRitual == false)
 | 
			
		||||
			{
 | 
			
		||||
				pawn.needs.mood.thoughts.memories.TryGainMemory(pawnThoughtDef, witness, precept);
 | 
			
		||||
 | 
			
		||||
				if (pawnThoughtDef.stages[0].baseMoodEffect < 0)
 | 
			
		||||
				{ FleckMaker.ThrowMetaIcon(pawn.Position, pawn.Map, FleckDefOf.IncapIcon); }
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return witnessJoiningSex;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue