diff --git a/Rimworld-Animations.csproj b/Rimworld-Animations.csproj
index 3f03b70..98397bc 100644
--- a/Rimworld-Animations.csproj
+++ b/Rimworld-Animations.csproj
@@ -62,33 +62,33 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Source/Actors/Actor.cs b/Source/Actors/Actor.cs
new file mode 100644
index 0000000..5dbb57e
--- /dev/null
+++ b/Source/Actors/Actor.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace Rimworld_Animations {
+ public class Actor {
+ public List defNames;
+ public List requiredGenitals;
+ public bool activeRole = false;
+ public string gender;
+ public bool isFucking = false;
+ public bool isFucked = false;
+ public Vector3 offset = new Vector3(0, 0, 0);
+ }
+}
diff --git a/Source/AnimationUtility.cs b/Source/AnimationUtility.cs
new file mode 100644
index 0000000..81d997e
--- /dev/null
+++ b/Source/AnimationUtility.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using RimWorld;
+using UnityEngine;
+using Verse;
+using Verse.AI;
+
+namespace Rimworld_Animations {
+ public static class AnimationUtility {
+ /*
+ Note: always make the list in this order:
+ Female pawns, animal female pawns, male pawns, animal male pawns
+ */
+ public static AnimationDef tryFindAnimation(ref List participants) {
+
+ //aggressors first
+ participants = participants.OrderByDescending(p => p.jobs.curDriver is rjw.JobDriver_SexBaseInitiator).ToList();
+
+ //fucked first, fucking second
+ participants = participants.OrderBy(p => rjw.xxx.can_fuck(p)).ToList();
+
+ List localParticipants = new List(participants);
+
+ IEnumerable options = DefDatabase.AllDefs.Where((AnimationDef x) => {
+
+ if (x.actors.Count != localParticipants.Count) {
+ return false;
+ }
+ for (int i = 0; i < x.actors.Count; i++) {
+
+
+ if(x.actors[i].defNames.Contains("Human")) {
+ if (!rjw.xxx.is_human(localParticipants[i])) {
+ if(rjw.RJWSettings.DevMode) {
+ Log.Message(x.defName.ToStringSafe() + " not selected -- " + localParticipants[i].def.defName.ToStringSafe() + " " + localParticipants[i].Name.ToStringSafe() + " is not human");
+ }
+
+ return false;
+ }
+
+ } else {
+
+ if (!x.actors[i].defNames.Contains(localParticipants[i].def.defName)) {
+
+ if (rjw.RJWSettings.DevMode) {
+ Log.Message(x.defName.ToStringSafe() + " not selected -- " + localParticipants[i].def.defName.ToStringSafe() + " " + localParticipants[i].Name.ToStringSafe() + " is not ");
+ foreach(String defname in x.actors[i].defNames) {
+ Log.Message(defname + ", ");
+ }
+ }
+
+ return false;
+ }
+ }
+
+ if(x.actors[i].requiredGenitals != null && x.actors[i].requiredGenitals.Contains("Vagina")) {
+
+ if (!rjw.Genital_Helper.has_vagina(localParticipants[i])) {
+ Log.Message(x.defName.ToStringSafe() + " not selected -- " + localParticipants[i].def.defName.ToStringSafe() + " " + localParticipants[i].Name.ToStringSafe() + " doesn't have vagina");
+ return false;
+ }
+
+ }
+
+ //TESTING ANIMATIONS ONLY REMEMBER TO COMMENT OUT BEFORE PUSH
+ /*
+ if (x.defName != "Tribadism")
+ return false;
+ */
+
+ if (x.actors[i].isFucking && !rjw.xxx.can_fuck(localParticipants[i])) {
+ Log.Message(x.defName.ToStringSafe() + " not selected -- " + localParticipants[i].def.defName.ToStringSafe() + " " + localParticipants[i].Name.ToStringSafe() + " can't fuck");
+ return false;
+ }
+
+ if (x.actors[i].isFucked && !rjw.xxx.can_be_fucked(localParticipants[i])) {
+ Log.Message(x.defName.ToStringSafe() + " not selected -- " + localParticipants[i].def.defName.ToStringSafe() + " " + localParticipants[i].Name.ToStringSafe() + " can't be fucked");
+ return false;
+ }
+ }
+ return true;
+ });
+
+ if (options != null && options.Any()) {
+ Log.Message("Randomly selecting animation...");
+ return options.RandomElement();
+ }
+
+ else
+ return null;
+ }
+
+ public static void RenderPawnHeadMeshInAnimation(Mesh mesh, Vector3 loc, Quaternion quaternion, Material material, bool portrait, Pawn pawn) {
+
+ if(pawn == null) {
+ GenDraw.DrawMeshNowOrLater(mesh, loc, quaternion, material, portrait);
+ return;
+ }
+
+ CompBodyAnimator pawnAnimator = pawn.TryGetComp();
+
+ if (pawnAnimator == null || !pawnAnimator.isAnimating || portrait) {
+ GenDraw.DrawMeshNowOrLater(mesh, loc, quaternion, material, portrait);
+ } else {
+ Vector3 pawnHeadPosition = pawnAnimator.getPawnHeadPosition();
+ pawnHeadPosition.y = loc.y;
+ GenDraw.DrawMeshNowOrLater(mesh, pawnHeadPosition, Quaternion.AngleAxis(pawnAnimator.headAngle, Vector3.up), material, portrait);
+ }
+ }
+ }
+}
diff --git a/Source/Animations/AnimationStage.cs b/Source/Animations/AnimationStage.cs
new file mode 100644
index 0000000..475c079
--- /dev/null
+++ b/Source/Animations/AnimationStage.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Rimworld_Animations {
+ public class AnimationStage
+ {
+ public string stageName;
+ public int stageIndex;
+ public int playTimeTicks = 0;
+ public bool isLooping;
+ public List animationClips;
+
+
+ public void initialize() {
+ foreach (BaseAnimationClip clip in animationClips) {
+ clip.buildSimpleCurves();
+ //select playTimeTicks as longest playtime of all the animations
+ if(clip.duration > playTimeTicks) {
+ playTimeTicks = clip.duration;
+ }
+ }
+ }
+ }
+}
diff --git a/Source/Animations/Clips/BaseAnimationClip.cs b/Source/Animations/Clips/BaseAnimationClip.cs
new file mode 100644
index 0000000..1aaf03f
--- /dev/null
+++ b/Source/Animations/Clips/BaseAnimationClip.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using RimWorld;
+using Verse;
+
+namespace Rimworld_Animations {
+ public abstract class BaseAnimationClip
+ {
+ public List types; //types of participants
+ public int duration;
+ public abstract void buildSimpleCurves();
+ public string soundDef = null; //for playing sounds
+ public int actor;
+
+ }
+}
diff --git a/Source/Animations/Clips/PawnAnimationClip.cs b/Source/Animations/Clips/PawnAnimationClip.cs
new file mode 100644
index 0000000..cf9db9d
--- /dev/null
+++ b/Source/Animations/Clips/PawnAnimationClip.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using RimWorld;
+using Verse;
+
+namespace Rimworld_Animations {
+ public class PawnAnimationClip : BaseAnimationClip {
+
+ public List keyframes;
+ public AltitudeLayer layer = AltitudeLayer.Pawn;
+
+ public Dictionary SoundEffects = new Dictionary();
+ public SimpleCurve BodyAngle = new SimpleCurve();
+ public SimpleCurve HeadAngle = new SimpleCurve();
+ public SimpleCurve HeadBob = new SimpleCurve();
+ public SimpleCurve BodyOffsetX = new SimpleCurve();
+ public SimpleCurve BodyOffsetZ = new SimpleCurve();
+ public SimpleCurve HeadFacing = new SimpleCurve();
+ public SimpleCurve BodyFacing = new SimpleCurve();
+
+
+ public override void buildSimpleCurves() {
+
+
+ int duration = 0;
+ //getting the length of the whole clip
+ foreach(PawnKeyframe frame in keyframes) {
+ duration += frame.tickDuration;
+ }
+
+ //guarantees loops don't get cut off mid-anim
+ this.duration = duration;
+
+ int keyframePosition = 0;
+ foreach (PawnKeyframe frame in keyframes) {
+
+ if (frame.atTick.HasValue) {
+ if (frame.bodyAngle.HasValue)
+ BodyAngle.Add((float)frame.atTick / (float)duration, frame.bodyAngle.Value, true);
+
+ if (frame.headAngle.HasValue)
+ HeadAngle.Add((float)frame.atTick / (float)duration, frame.headAngle.Value, true);
+
+ if (frame.bodyOffsetX.HasValue)
+ BodyOffsetX.Add((float)frame.atTick / (float)duration, frame.bodyOffsetX.Value, true);
+
+ if (frame.bodyOffsetZ.HasValue)
+ BodyOffsetZ.Add((float)frame.atTick / (float)duration, frame.bodyOffsetZ.Value, true);
+
+ if (frame.headFacing.HasValue)
+ HeadFacing.Add((float)frame.atTick / (float)duration, frame.headFacing.Value, true);
+
+ if (frame.bodyFacing.HasValue)
+ BodyFacing.Add((float)frame.atTick / (float)duration, frame.bodyFacing.Value, true);
+
+ if (frame.headBob.HasValue)
+ HeadBob.Add((float)frame.atTick / (float)duration, frame.headBob.Value, true);
+
+ if (frame.soundEffect != null) {
+ SoundEffects.Add((int)frame.atTick, frame.soundEffect);
+ }
+ }
+ else {
+ if (frame.bodyAngle.HasValue)
+ BodyAngle.Add((float)keyframePosition / (float)duration, frame.bodyAngle.Value, true);
+
+ if (frame.headAngle.HasValue)
+ HeadAngle.Add((float)keyframePosition / (float)duration, frame.headAngle.Value, true);
+
+ if (frame.bodyOffsetX.HasValue)
+ BodyOffsetX.Add((float)keyframePosition / (float)duration, frame.bodyOffsetX.Value, true);
+
+ if (frame.bodyOffsetZ.HasValue)
+ BodyOffsetZ.Add((float)keyframePosition / (float)duration, frame.bodyOffsetZ.Value, true);
+
+ if (frame.headFacing.HasValue)
+ HeadFacing.Add((float)keyframePosition / (float)duration, frame.headFacing.Value, true);
+
+ if (frame.bodyFacing.HasValue)
+ BodyFacing.Add((float)keyframePosition / (float)duration, frame.bodyFacing.Value, true);
+
+ if (frame.headBob.HasValue)
+ HeadBob.Add((float)keyframePosition / (float)duration, frame.headBob.Value, true);
+
+ if (frame.soundEffect != null) {
+ SoundEffects.Add(keyframePosition, frame.soundEffect);
+ }
+
+ keyframePosition += frame.tickDuration;
+
+ }
+
+ }
+
+ }
+
+ }
+}
diff --git a/Source/Animations/Clips/ThingAnimationClip.cs b/Source/Animations/Clips/ThingAnimationClip.cs
new file mode 100644
index 0000000..4742140
--- /dev/null
+++ b/Source/Animations/Clips/ThingAnimationClip.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Rimworld_Animations {
+ public class ThingAnimationClip : BaseAnimationClip
+ {
+ public List keyframes;
+
+ public override void buildSimpleCurves() {
+ }
+ }
+}
diff --git a/Source/Animations/Keyframes/Keyframe.cs b/Source/Animations/Keyframes/Keyframe.cs
new file mode 100644
index 0000000..8b49841
--- /dev/null
+++ b/Source/Animations/Keyframes/Keyframe.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Rimworld_Animations {
+ public abstract class Keyframe
+ {
+ public int tickDuration = 1;
+
+
+ }
+}
diff --git a/Source/Animations/Keyframes/PawnKeyframe.cs b/Source/Animations/Keyframes/PawnKeyframe.cs
new file mode 100644
index 0000000..5ee8f8d
--- /dev/null
+++ b/Source/Animations/Keyframes/PawnKeyframe.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace Rimworld_Animations {
+ public class PawnKeyframe : Keyframe
+ {
+ public float? bodyAngle;
+ public float? headAngle;
+
+ public float? bodyOffsetZ;
+ public float? bodyOffsetX;
+
+ public float? headBob;
+ //todo: add headOffsets l/r?
+
+ public int? bodyFacing;
+ public int? headFacing;
+
+ public string soundEffect;
+
+ public float? atTick;
+ }
+}
diff --git a/Source/Animations/Keyframes/ThingKeyframe.cs b/Source/Animations/Keyframes/ThingKeyframe.cs
new file mode 100644
index 0000000..9d35a96
--- /dev/null
+++ b/Source/Animations/Keyframes/ThingKeyframe.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Rimworld_Animations {
+ public class ThingKeyframe : Keyframe
+ {
+ }
+}
diff --git a/Source/Comps/CompBodyAnimator.cs b/Source/Comps/CompBodyAnimator.cs
new file mode 100644
index 0000000..1dc58c5
--- /dev/null
+++ b/Source/Comps/CompBodyAnimator.cs
@@ -0,0 +1,238 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using RimWorld;
+using rjw;
+using UnityEngine;
+using Verse;
+using Verse.Sound;
+
+namespace Rimworld_Animations {
+ class CompBodyAnimator : ThingComp
+ {
+ public Pawn pawn => base.parent as Pawn;
+ public PawnGraphicSet Graphics;
+
+ public CompProperties_BodyAnimator Props => (CompProperties_BodyAnimator)(object)base.props;
+
+ public bool isAnimating {
+ get {
+ return Animating;
+ }
+ set {
+ Animating = value;
+
+ if(value == true) {
+ xxx.DrawNude(pawn);
+ } else {
+ pawn.Drawer.renderer.graphics.ResolveAllGraphics();
+ }
+
+ PortraitsCache.SetDirty(pawn);
+ }
+ }
+ private bool Animating;
+ private bool mirror = false;
+ private int actor;
+
+ private int animTicks = 0, stageTicks = 0, clipTicks = 0;
+ private int curStage = 0;
+ private float clipPercent = 0;
+
+ public Vector3 anchor, deltaPos, headBob;
+ public float bodyAngle, headAngle;
+ public Rot4 headFacing, bodyFacing;
+
+ private AnimationDef anim;
+ private AnimationStage stage => anim.animationStages[curStage];
+ private PawnAnimationClip clip => (PawnAnimationClip)stage.animationClips[actor];
+
+ public void setAnchor(IntVec3 pos)
+ {
+ anchor = pos.ToVector3();
+ }
+ public void setAnchor(Thing thing) {
+ anchor = thing.Position.ToVector3();
+ //center on bed
+ if(thing is Building_Bed) {
+ if(((Building_Bed)thing).SleepingSlotsCount == 2) {
+ if (thing.Rotation.AsInt == 0) {
+ anchor.x += 1;
+ anchor.z += 1;
+ }
+ else if (thing.Rotation.AsInt == 1) {
+ anchor.x += 1;
+ }
+ else if(thing.Rotation.AsInt == 3) {
+ anchor.z += 1;
+ }
+
+ }
+ else {
+ 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;
+ } else {
+ anchor.z += 0.5f;
+ }
+ }
+ }
+ }
+ public void StartAnimation(AnimationDef anim, int actor, bool mirror = false) {
+
+ isAnimating = true;
+
+ pawn.jobs.posture = PawnPosture.Standing;
+
+ this.actor = actor;
+ this.anim = anim;
+ this.mirror = mirror;
+
+ curStage = 0;
+ animTicks = 0;
+ stageTicks = 0;
+ clipTicks = 0;
+
+ //tick once for initialization
+ tickAnim();
+
+ }
+ public override void CompTick() {
+ base.CompTick();
+
+ //maybe this is causing anim to stop mid?
+
+
+ if(Animating) {
+
+ tickAnim();
+
+ if (pawn?.jobs?.curDriver == null || (pawn?.jobs?.curDriver != null && !(pawn?.jobs?.curDriver is rjw.JobDriver_Sex))) {
+ Animating = false;
+ }
+ }
+ }
+ public void animatePawn(ref Vector3 rootLoc, ref float angle, ref Rot4 bodyFacing, ref Rot4 headFacing) {
+
+ if(!Animating) {
+ return;
+ }
+ rootLoc = anchor + deltaPos;
+ angle = bodyAngle;
+ bodyFacing = this.bodyFacing;
+ headFacing = this.headFacing;
+
+ }
+
+ public void tickGraphics(PawnGraphicSet graphics) {
+ this.Graphics = graphics;
+ }
+
+ public void tickAnim() {
+
+ if (!Animating) return;
+
+ animTicks++;
+ if (animTicks < anim.animationTimeTicks) {
+ tickStage();
+ } else {
+ Animating = false;
+ }
+ }
+
+ public void tickStage()
+ {
+ stageTicks++;
+
+ if(stageTicks >= stage.playTimeTicks) {
+ curStage++;
+ stageTicks = 0;
+ clipTicks = 0;
+ clipPercent = 0;
+ }
+
+ tickClip();
+
+ }
+
+ public void tickClip() {
+
+ clipTicks++;
+
+ //play sound effect
+ if(rjw.RJWSettings.sounds_enabled && clip.SoundEffects.ContainsKey(clipTicks)) {
+ SoundDef.Named(clip.SoundEffects[clipTicks]).PlayOneShot(new TargetInfo(pawn.Position, pawn.Map));
+ }
+
+ //loop animation if possible
+ if (clipPercent >= 1 && stage.isLooping) {
+ clipTicks = 1;//warning: don't set to zero or else calculations go wrong
+ }
+ clipPercent = (float)clipTicks / (float)clip.duration;
+
+ calculateDrawValues();
+ }
+
+ public void calculateDrawValues() {
+
+ deltaPos = new Vector3(clip.BodyOffsetX.Evaluate(clipPercent) * (mirror ? -1 : 1), clip.layer.AltitudeFor(), clip.BodyOffsetZ.Evaluate(clipPercent));
+ bodyAngle = clip.BodyAngle.Evaluate(clipPercent) * (mirror ? -1 : 1);
+ headAngle = clip.HeadAngle.Evaluate(clipPercent) * (mirror ? -1 : 1);
+ bodyFacing = mirror ? new Rot4((int)clip.BodyFacing.Evaluate(clipPercent)).Opposite : new Rot4((int)clip.BodyFacing.Evaluate(clipPercent));
+
+ bodyFacing = new Rot4((int)clip.BodyFacing.Evaluate(clipPercent));
+ if(bodyFacing.IsHorizontal && mirror) {
+ bodyFacing = bodyFacing.Opposite;
+ }
+
+ headFacing = new Rot4((int)clip.HeadFacing.Evaluate(clipPercent));
+ if(headFacing.IsHorizontal && mirror) {
+ headFacing = headFacing.Opposite;
+ }
+ headBob = new Vector3(0, 0, clip.HeadBob.Evaluate(clipPercent));
+ }
+
+ public Vector3 getPawnHeadPosition() {
+
+ return anchor + deltaPos + Quaternion.AngleAxis(bodyAngle, Vector3.up) * (pawn.Drawer.renderer.BaseHeadOffsetAt(headFacing) + headBob);
+
+ }
+
+ public override void PostExposeData() {
+ base.PostExposeData();
+
+ Scribe_Defs.Look(ref anim, "anim");
+
+ Scribe_Values.Look(ref animTicks, "animTicks", 1);
+ Scribe_Values.Look(ref stageTicks, "stageTicks", 1);
+ Scribe_Values.Look(ref clipTicks, "clipTicks", 1);
+ Scribe_Values.Look(ref clipPercent, "clipPercent", 1);
+
+ Scribe_Values.Look(ref mirror, "mirror");
+
+ Scribe_Values.Look(ref curStage, "curStage", 0);
+ Scribe_Values.Look(ref actor, "actor");
+
+ Scribe_Values.Look(ref Animating, "Animating");
+ Scribe_Values.Look(ref anchor, "anchor");
+ Scribe_Values.Look(ref deltaPos, "deltaPos");
+ Scribe_Values.Look(ref headBob, "headBob");
+ Scribe_Values.Look(ref bodyAngle, "bodyAngle");
+ Scribe_Values.Look(ref headAngle, "headAngle");
+
+ Scribe_Values.Look(ref headFacing, "headFacing");
+ Scribe_Values.Look(ref headFacing, "bodyFacing");
+ }
+
+ }
+}
diff --git a/Source/Comps/CompProperties_BodyAnimator.cs b/Source/Comps/CompProperties_BodyAnimator.cs
new file mode 100644
index 0000000..cc40925
--- /dev/null
+++ b/Source/Comps/CompProperties_BodyAnimator.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+using RimWorld;
+
+namespace Rimworld_Animations {
+ class CompProperties_BodyAnimator : CompProperties
+ {
+ public CompProperties_BodyAnimator() {
+
+ base.compClass = typeof(CompBodyAnimator);
+ }
+ }
+}
diff --git a/Source/Comps/CompProperties_ThingAnimator.cs b/Source/Comps/CompProperties_ThingAnimator.cs
new file mode 100644
index 0000000..f9b76bd
--- /dev/null
+++ b/Source/Comps/CompProperties_ThingAnimator.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Rimworld_Animations {
+ class CompProperties_ThingAnimator
+ {
+ }
+}
diff --git a/Source/Comps/CompThingAnimator.cs b/Source/Comps/CompThingAnimator.cs
new file mode 100644
index 0000000..bb3c944
--- /dev/null
+++ b/Source/Comps/CompThingAnimator.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Rimworld_Animations {
+ class CompThingAnimator
+ {
+ }
+}
diff --git a/Source/Defs/AnimationDef.cs b/Source/Defs/AnimationDef.cs
new file mode 100644
index 0000000..808c9cf
--- /dev/null
+++ b/Source/Defs/AnimationDef.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using RimWorld;
+using Verse;
+
+namespace Rimworld_Animations {
+ public class AnimationDef : Def
+ {
+ public List animationStages;
+ public List actors;
+ public int animationTimeTicks = 0; //do not set manually
+ public bool sounds = false;
+
+ public override void PostLoad() {
+ base.PostLoad();
+ foreach(AnimationStage stage in animationStages) {
+ stage.initialize();
+ animationTimeTicks += stage.playTimeTicks;
+ }
+ }
+ }
+}
diff --git a/Source/JobDrivers/JobDriver_SexBaseRecieverLovedForAnimation.cs b/Source/JobDrivers/JobDriver_SexBaseRecieverLovedForAnimation.cs
new file mode 100644
index 0000000..3305488
--- /dev/null
+++ b/Source/JobDrivers/JobDriver_SexBaseRecieverLovedForAnimation.cs
@@ -0,0 +1,62 @@
+using System.Collections.Generic;
+using RimWorld;
+using Verse;
+using Verse.AI;
+using System;
+using rjw;
+
+namespace Rimworld_Animations {
+ public class JobDriver_SexBaseRecieverLovedForAnimation : JobDriver_SexBaseReciever {
+
+ public readonly TargetIndex ipartner = TargetIndex.A;
+ public readonly TargetIndex ibed = TargetIndex.B;
+
+ public Pawn Partner => (Pawn)(job.GetTarget(ipartner));
+ public new Building_Bed Bed => (Building_Bed)(job.GetTarget(ibed));
+
+ protected override IEnumerable MakeNewToils() {
+ setup_ticks();
+ parteners.Add(Partner);// add job starter, so this wont fail, before Initiator starts his job
+ //--Log.Message("[RJW]JobDriver_GettinLoved::MakeNewToils is called");
+
+ float partner_ability = xxx.get_sex_ability(Partner);
+
+ // More/less hearts based on partner ability.
+ if (partner_ability < 0.8f)
+ ticks_between_thrusts += 100;
+ else if (partner_ability > 2.0f)
+ ticks_between_thrusts -= 25;
+
+ // More/less hearts based on opinion.
+ if (pawn.relations.OpinionOf(Partner) < 0)
+ ticks_between_hearts += 50;
+ else if (pawn.relations.OpinionOf(Partner) > 60)
+ ticks_between_hearts -= 25;
+
+ this.FailOnDespawnedOrNull(ipartner);
+ this.FailOn(() => !Partner.health.capacities.CanBeAwake);
+ this.FailOn(() => pawn.Drafted);
+ this.KeepLyingDown(ibed);
+ yield return Toils_Reserve.Reserve(ipartner, 1, 0);
+ yield return Toils_Reserve.Reserve(ibed, Bed.SleepingSlotsCount, 0);
+
+ Toil get_loved = Toils_LayDown.LayDown(ibed, true, false, false, false);
+ get_loved.FailOn(() => Partner.CurJobDef != DefDatabase.GetNamed("JoinInBedAnimation", true));
+ get_loved.defaultCompleteMode = ToilCompleteMode.Never;
+ get_loved.socialMode = RandomSocialMode.Off;
+ get_loved.AddPreTickAction(delegate {
+ if (pawn.IsHashIntervalTick(ticks_between_hearts))
+ MoteMaker.ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
+ if (pawn.IsHashIntervalTick(ticks_between_thrusts))
+ xxx.sexTick(pawn, Partner, false);
+ });
+ get_loved.AddFinishAction(delegate {
+ if (xxx.is_human(pawn))
+ pawn.Drawer.renderer.graphics.ResolveApparelGraphics();
+ });
+ yield return get_loved;
+
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/JobDrivers/JobDriver_SexCasualForAnimation.cs b/Source/JobDrivers/JobDriver_SexCasualForAnimation.cs
new file mode 100644
index 0000000..0100f96
--- /dev/null
+++ b/Source/JobDrivers/JobDriver_SexCasualForAnimation.cs
@@ -0,0 +1,92 @@
+using RimWorld;
+using rjw;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+using Verse.AI;
+
+namespace Rimworld_Animations {
+ class JobDriver_SexCasualForAnimation : JobDriver_SexBaseInitiator {
+
+ public readonly TargetIndex ipartner = TargetIndex.A;
+ public readonly TargetIndex ibed = TargetIndex.B;
+
+ public Pawn Partner => (Pawn)job.GetTarget(ipartner);
+ public new Building_Bed Bed => (Building_Bed)job.GetTarget(ibed);
+
+ public override bool TryMakePreToilReservations(bool errorOnFailed) {
+ return ReservationUtility.Reserve(base.pawn, Partner, job, xxx.max_rapists_per_prisoner, 0, null, errorOnFailed);
+ }
+
+ protected override IEnumerable MakeNewToils() {
+ setup_ticks();
+ this.FailOnDespawnedOrNull(ipartner);
+ this.FailOnDespawnedOrNull(ibed);
+ this.FailOn(() => !Partner.health.capacities.CanBeAwake);
+
+ yield return Toils_Reserve.Reserve(ipartner, xxx.max_rapists_per_prisoner, 0, null);
+
+ Toil goToPawnInBed = Toils_Goto.GotoThing(ipartner, PathEndMode.OnCell);
+ goToPawnInBed.FailOn(() => !RestUtility.InBed(Partner) && !xxx.in_same_bed(Partner, pawn));
+
+ yield return goToPawnInBed;
+
+
+ Toil startPartnerSex = new Toil();
+ startPartnerSex.initAction = delegate {
+
+ Log.Message("Attempting to start job...");
+ Job gettinLovedJob = JobMaker.MakeJob(DefDatabase.GetNamed("GettinLovedAnimation"), pawn, Bed); // new gettin loved toil that wakes up the pawn goes here
+
+ Partner.jobs.jobQueue.EnqueueFirst(gettinLovedJob);
+ Partner.jobs.EndCurrentJob(JobCondition.InterruptForced);
+ };
+ yield return startPartnerSex;
+
+ Toil sexToil = new Toil();
+ sexToil.FailOn(() => (Partner.CurJobDef == null) || Partner.CurJobDef != DefDatabase.GetNamed("GettinLovedAnimation", true)); //partner jobdriver is not sexbaserecieverlovedforanim
+ sexToil.socialMode = RandomSocialMode.Off;
+ sexToil.defaultCompleteMode = ToilCompleteMode.Never;
+ sexToil.initAction = delegate {
+
+ usedCondom = (CondomUtility.TryUseCondom(base.pawn) || CondomUtility.TryUseCondom(Partner));
+ Start();
+ };
+
+ sexToil.AddPreTickAction(delegate {
+
+ ticks_left--;
+ if(Gen.IsHashIntervalTick(pawn, ticks_between_hearts)) {
+ MoteMaker.ThrowMetaIcon(pawn.Position, pawn.Map, ThingDefOf.Mote_Heart);
+ }
+ PawnUtility.GainComfortFromCellIfPossible(pawn, false);
+ PawnUtility.GainComfortFromCellIfPossible(Partner, false);
+ xxx.reduce_rest(Partner);
+ xxx.reduce_rest(pawn, 2);
+ if (ticks_left <= 0)
+ ReadyForNextToil();
+
+ });
+ sexToil.AddFinishAction(delegate {
+
+ End();
+ if(xxx.is_human(pawn)) {
+ pawn.Drawer.renderer.graphics.ResolveApparelGraphics();
+ }
+
+ });
+ yield return sexToil;
+
+ Toil finish = new Toil();
+ finish.initAction = delegate {
+ SexUtility.ProcessSex(pawn, Partner, usedCondom);
+ };
+ finish.defaultCompleteMode = ToilCompleteMode.Instant;
+ yield return finish;
+
+ }
+ }
+}
diff --git a/Source/MainTabWindows/MainTabWindow_SexAnimator.cs b/Source/MainTabWindows/MainTabWindow_SexAnimator.cs
new file mode 100644
index 0000000..5889510
--- /dev/null
+++ b/Source/MainTabWindows/MainTabWindow_SexAnimator.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+using RimWorld;
+
+namespace Rimworld_Animations {
+ class MainTabWindow_SexAnimator : MainTabWindow
+ {
+ //todo: add animation maker window
+ }
+}
diff --git a/Source/Patches/HarmonyPatch_AlienRace.cs b/Source/Patches/HarmonyPatch_AlienRace.cs
new file mode 100644
index 0000000..b2949b9
--- /dev/null
+++ b/Source/Patches/HarmonyPatch_AlienRace.cs
@@ -0,0 +1,35 @@
+using HarmonyLib;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace Rimworld_Animations {
+ [StaticConstructorOnStartup]
+ public static class HarmonyPatch_AlienRace {
+ static HarmonyPatch_AlienRace () {
+ try {
+ ((Action)(() => {
+ if (LoadedModManager.RunningModsListForReading.Any(x => x.Name == "Humanoid Alien Races 2.0")) {
+ (new Harmony("rjw")).Patch(AccessTools.Method(typeof(PawnGraphicSet), "ResolveApparelGraphics"),
+ prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_AlienRace), "Prefix_StopResolveAllGraphicsWhileSex")));
+ }
+ }))();
+ }
+ catch (TypeLoadException ex) {
+
+ }
+ }
+
+ public static bool Prefix_StopResolveAllGraphicsWhileSex(ref Pawn ___pawn) {
+
+ if(___pawn.TryGetComp() != null && ___pawn.TryGetComp().isAnimating) {
+ return false;
+ }
+ return true;
+ }
+
+ }
+}
diff --git a/Source/Patches/HarmonyPatch_FacialAnimation.cs b/Source/Patches/HarmonyPatch_FacialAnimation.cs
new file mode 100644
index 0000000..9ebe1aa
--- /dev/null
+++ b/Source/Patches/HarmonyPatch_FacialAnimation.cs
@@ -0,0 +1,44 @@
+using HarmonyLib;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+using Verse;
+
+namespace Rimworld_Animations {
+ [StaticConstructorOnStartup]
+ public static class Patch_FacialAnimation {
+
+ static Patch_FacialAnimation() {
+ try {
+ ((Action)(() => {
+ if (LoadedModManager.RunningModsListForReading.Any(x => x.Name == "[NL] Facial Animation - WIP")) {
+ (new Harmony("rjw")).Patch(AccessTools.Method(AccessTools.TypeByName("FacialAnimation.DrawFaceGraphicsComp"), "DrawGraphics"),
+ prefix: new HarmonyMethod(AccessTools.Method(typeof(Patch_FacialAnimation), "Prefix")));
+ }
+ }))();
+ }
+ catch (TypeLoadException ex) {
+
+ }
+ }
+
+ public static bool Prefix(ref Pawn ___pawn, ref Rot4 headFacing, ref Vector3 headOrigin, ref Quaternion quaternion, ref bool portrait) {
+
+ CompBodyAnimator bodyAnim = ___pawn.TryGetComp();
+
+ if (bodyAnim.isAnimating && !portrait) {
+
+ headFacing = bodyAnim.headFacing;
+ headOrigin = new Vector3(bodyAnim.getPawnHeadPosition().x, headOrigin.y, bodyAnim.getPawnHeadPosition().z);
+ quaternion = Quaternion.AngleAxis(bodyAnim.headAngle, Vector3.up);
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/Source/Patches/HarmonyPatch_PawnRenderer.cs b/Source/Patches/HarmonyPatch_PawnRenderer.cs
new file mode 100644
index 0000000..24da16c
--- /dev/null
+++ b/Source/Patches/HarmonyPatch_PawnRenderer.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using HarmonyLib;
+using RimWorld;
+using Verse;
+using UnityEngine;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace Rimworld_Animations {
+
+ [HarmonyPatch(typeof(PawnRenderer), "RenderPawnInternal", new Type[]
+ {
+ typeof(Vector3),
+ typeof(float),
+ typeof(bool),
+ typeof(Rot4),
+ typeof(Rot4),
+ typeof(RotDrawMode),
+ typeof(bool),
+ typeof(bool),
+ typeof(bool)
+ })]
+
+ public static class HarmonyPatch_PawnRenderer
+ {
+
+ [HarmonyBefore(new string[] { "showhair.kv.rw", "erdelf.HumanoidAlienRaces", "Nals.FacialAnimation" })]
+ public static void Prefix(PawnRenderer __instance, ref Vector3 rootLoc, ref float angle, bool renderBody, ref Rot4 bodyFacing, ref Rot4 headFacing, RotDrawMode bodyDrawType, bool portrait, bool headStump, bool invisible)
+ {
+ PawnGraphicSet graphics = __instance.graphics;
+ Pawn pawn = graphics.pawn;
+ CompBodyAnimator bodyAnim = pawn.TryGetComp();
+
+ if (!graphics.AllResolved) {
+ graphics.ResolveAllGraphics();
+ }
+
+
+ if (bodyAnim != null && bodyAnim.isAnimating && !portrait) {
+ bodyAnim.tickGraphics(graphics);
+ pawn.TryGetComp().animatePawn(ref rootLoc, ref angle, ref bodyFacing, ref headFacing);
+
+ }
+ }
+
+ [HarmonyAfter(new string[] { "showhair.kv.rw", "erdelf.HumanoidAlienRaces", "Nals.FacialAnimation" })]
+ [HarmonyReversePatch(HarmonyReversePatchType.Snapshot)]
+ public static IEnumerable Transpiler(IEnumerable instructions) {
+
+ MethodInfo drawMeshNowOrLater = AccessTools.Method(typeof(GenDraw), "DrawMeshNowOrLater");//typeof(GenDraw).GetMethod("DrawMeshNowOrLater", BindingFlags.Static | BindingFlags.Public);
+ FieldInfo headGraphic = AccessTools.Field(typeof(PawnGraphicSet), "headGraphic");
+
+
+ List codes = instructions.ToList();
+ bool forHead = true;
+ for(int i = 0; i < codes.Count(); i++) {
+
+ //Instead of calling drawmeshnoworlater, add pawn to the stack and call my special static method
+ if (codes[i].OperandIs(drawMeshNowOrLater) && forHead) {
+
+ yield return new CodeInstruction(OpCodes.Ldarg_0);
+ yield return new CodeInstruction(OpCodes.Ldfld, AccessTools.DeclaredField(typeof(PawnRenderer), "pawn"));
+ yield return new CodeInstruction(OpCodes.Call, AccessTools.DeclaredMethod(typeof(AnimationUtility), nameof(AnimationUtility.RenderPawnHeadMeshInAnimation)));
+
+ }
+ //checking for if(graphics.headGraphic != null)
+ else if (codes[i].opcode == OpCodes.Ldfld && codes[i].OperandIs(headGraphic)) {
+ forHead = true;
+ yield return codes[i];
+ }
+ //checking for if(renderbody)
+ else if(codes[i].opcode == OpCodes.Ldarg_3) {
+ forHead = false;
+ yield return codes[i];
+ }
+ else {
+ yield return codes[i];
+ }
+ }
+ }
+ }
+}
diff --git a/Source/Patches/HarmonyPatch_Pawn_DrawTracker.cs b/Source/Patches/HarmonyPatch_Pawn_DrawTracker.cs
new file mode 100644
index 0000000..73856ae
--- /dev/null
+++ b/Source/Patches/HarmonyPatch_Pawn_DrawTracker.cs
@@ -0,0 +1,25 @@
+using HarmonyLib;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+using Verse;
+
+namespace Rimworld_Animations {
+
+ [HarmonyPatch(typeof(Pawn_DrawTracker), "DrawPos", MethodType.Getter)]
+ public static class HarmonyPatch_Pawn_DrawTracker {
+ public static bool Prefix(ref Pawn ___pawn, ref Vector3 __result) {
+
+ if(___pawn.TryGetComp() != null && ___pawn.TryGetComp().isAnimating) {
+ __result = ___pawn.TryGetComp().anchor + ___pawn.TryGetComp().deltaPos;
+ ___pawn.Position = __result.ToIntVec3();
+ return false;
+ }
+ return true;
+
+ }
+ }
+}
diff --git a/Source/Patches/HarmonyPatch_ShowHairWithHats.cs b/Source/Patches/HarmonyPatch_ShowHairWithHats.cs
new file mode 100644
index 0000000..87e59d2
--- /dev/null
+++ b/Source/Patches/HarmonyPatch_ShowHairWithHats.cs
@@ -0,0 +1,50 @@
+using HarmonyLib;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+
+namespace Rimworld_Animations {
+ [StaticConstructorOnStartup]
+ public static class Patch_ShowHairWithHats {
+
+ static Patch_ShowHairWithHats() {
+ try {
+ ((Action)(() =>
+ {
+ if (LoadedModManager.RunningModsListForReading.Any(x => x.Name == "[KV] Show Hair With Hats or Hide All Hats - 1.1")) {
+ (new Harmony("rjw")).Patch(AccessTools.Method(AccessTools.TypeByName("ShowHair.Patch_PawnRenderer_RenderPawnInternal"), "Postfix"), //typeof(ShowHair.Patch_PawnRenderer_RenderPawnInternal), nameof(ShowHair.Patch_PawnRenderer_RenderPawnInternal.Postfix)),
+ transpiler: new HarmonyMethod(AccessTools.Method(typeof(Patch_ShowHairWithHats), "Transpiler")));
+ }
+ }))();
+ }
+ catch (TypeLoadException ex) { }
+ }
+
+
+ public static IEnumerable Transpiler(IEnumerable instructions) {
+
+ MethodInfo drawMeshNowOrLater = AccessTools.Method(typeof(GenDraw), "DrawMeshNowOrLater");
+
+ List codes = instructions.ToList();
+ for (int i = 0; i < codes.Count(); i++) {
+
+ //Instead of calling drawmeshnoworlater, add pawn to the stack and call my special static method
+ if (codes[i].OperandIs(drawMeshNowOrLater)) {
+
+ yield return new CodeInstruction(OpCodes.Ldarg_0);
+ yield return new CodeInstruction(OpCodes.Ldfld, AccessTools.DeclaredField(typeof(PawnRenderer), "pawn"));
+ yield return new CodeInstruction(OpCodes.Call, AccessTools.DeclaredMethod(typeof(AnimationUtility), nameof(AnimationUtility.RenderPawnHeadMeshInAnimation)));
+
+ }
+ else {
+ yield return codes[i];
+ }
+ }
+ }
+ }
+}
diff --git a/Source/Patches/Harmony_PatchAll.cs b/Source/Patches/Harmony_PatchAll.cs
new file mode 100644
index 0000000..3a0a7bb
--- /dev/null
+++ b/Source/Patches/Harmony_PatchAll.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Verse;
+using HarmonyLib;
+using System.Reflection;
+
+namespace Rimworld_Animations {
+
+ [StaticConstructorOnStartup]
+ public static class Harmony_PatchAll {
+
+ static Harmony_PatchAll() {
+
+ Harmony val = new Harmony("rimworldanim");
+ val.PatchAll(Assembly.GetExecutingAssembly());
+
+ }
+ }
+}
diff --git a/Source/Patches/rjwPatches/HarmonyPatch_JobDriver_SexBaseInitiator.cs b/Source/Patches/rjwPatches/HarmonyPatch_JobDriver_SexBaseInitiator.cs
new file mode 100644
index 0000000..14f6402
--- /dev/null
+++ b/Source/Patches/rjwPatches/HarmonyPatch_JobDriver_SexBaseInitiator.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using HarmonyLib;
+using RimWorld;
+using Verse;
+using rjw;
+
+namespace Rimworld_Animations {
+
+ [HarmonyPatch(typeof(JobDriver_SexBaseInitiator), "Start")]
+ static class HarmonyPatch_JobDriver_SexBaseInitiator_Start {
+ public static void Postfix(ref JobDriver_SexBaseInitiator __instance) {
+
+ if(__instance is JobDriver_JoinInBed) {
+ Log.Warning("Tried to start wrong JobDriver with Rimworld-Animations installed. If you see this warning soon after installing this mod, it's fine and animated sex will start soon. If you see this a long time after installing, that's a problem.");
+ return;
+ }
+
+ Pawn Target = __instance.Target;
+ Pawn pawn = __instance.pawn;
+
+ Building_Bed bed = __instance.Bed;
+
+ if (__instance is JobDriver_BestialityForFemale)
+ bed = (__instance as JobDriver_BestialityForFemale).Bed;
+ else if (__instance is JobDriver_WhoreIsServingVisitors) {
+ bed = (__instance as JobDriver_WhoreIsServingVisitors).Bed;
+ }
+ else if(__instance is JobDriver_SexCasualForAnimation) {
+ bed = (__instance as JobDriver_SexCasualForAnimation).Bed;
+ }
+
+ if (__instance.Target.jobs?.curDriver is JobDriver_SexBaseReciever) {
+
+ if (!(Target.jobs.curDriver as JobDriver_SexBaseReciever).parteners.Contains(pawn)) {
+ (Target.jobs.curDriver as JobDriver_SexBaseReciever).parteners.Add(pawn);
+ }
+
+ if (bed != null) {
+ RerollAnimations(Target, __instance.duration, bed as Thing);
+ }
+ else {
+ RerollAnimations(Target, __instance.duration);
+ }
+ }
+ }
+
+ public static void RerollAnimations(Pawn pawn, int duration, Thing bed = null) {
+
+ if(pawn == null || !(pawn.jobs?.curDriver is JobDriver_SexBaseReciever)) {
+ Log.Message("Error: Tried to reroll animations when pawn isn't sexing");
+ return;
+ }
+
+ List pawnsToAnimate = (pawn.jobs.curDriver as JobDriver_SexBaseReciever).parteners.ToList();
+
+ if (!pawnsToAnimate.Contains(pawn)) {
+ pawnsToAnimate = pawnsToAnimate.Append(pawn).ToList();
+ }
+
+ AnimationDef anim = AnimationUtility.tryFindAnimation(ref pawnsToAnimate);
+
+ if (anim != null) {
+
+ Log.Message("Now playing " + anim.defName);
+
+ bool mirror = GenTicks.TicksGame % 2 == 0;
+ for (int i = 0; i < pawnsToAnimate.Count; i++) {
+
+ if (bed != null)
+ pawnsToAnimate[i].TryGetComp().setAnchor(bed);
+ else
+ pawnsToAnimate[i].TryGetComp().setAnchor(pawn);
+
+ pawnsToAnimate[i].TryGetComp().StartAnimation(anim, i, mirror);
+ (pawnsToAnimate[i].jobs.curDriver as JobDriver_Sex).ticks_left = anim.animationTimeTicks;
+ (pawnsToAnimate[i].jobs.curDriver as JobDriver_Sex).ticksLeftThisToil = anim.animationTimeTicks;
+ (pawnsToAnimate[i].jobs.curDriver as JobDriver_Sex).duration = anim.animationTimeTicks;
+ (pawnsToAnimate[i].jobs.curDriver as JobDriver_Sex).ticks_remaining = anim.animationTimeTicks;
+
+ }
+ }
+ else {
+ Log.Message("Anim not found");
+ //if pawn isn't already animating,
+ if (!pawn.TryGetComp().isAnimating) {
+ (pawn.jobs.curDriver as JobDriver_SexBaseReciever).increase_time(duration);
+ //they'll just do the thrusting anim
+ }
+ }
+
+
+ }
+ }
+
+ [HarmonyPatch(typeof(JobDriver_SexBaseInitiator), "End")]
+ static class HarmonyPatch_JobDriver_SexBaseInitiator_End {
+
+ public static void Postfix(ref JobDriver_SexBaseInitiator __instance) {
+
+ if (__instance.Target.jobs?.curDriver is JobDriver_SexBaseReciever) {
+ if (__instance.pawn.TryGetComp().isAnimating) {
+
+ List parteners = (__instance.Target.jobs.curDriver as JobDriver_SexBaseReciever).parteners;
+
+ for (int i = 0; i < parteners.Count; i++) {
+
+ //prevents pawns who started a new anim from stopping their new anim
+ if (!((parteners[i].jobs.curDriver as JobDriver_SexBaseInitiator) != null && (parteners[i].jobs.curDriver as JobDriver_SexBaseInitiator).Target != __instance.pawn))
+ parteners[i].TryGetComp().isAnimating = false;
+
+ if (xxx.is_human(parteners[i])) {
+ parteners[i].Drawer.renderer.graphics.ResolveApparelGraphics();
+ PortraitsCache.SetDirty(parteners[i]);
+ }
+
+ }
+
+ __instance.Target.TryGetComp().isAnimating = false;
+
+ if (xxx.is_human(__instance.Target)) {
+ __instance.Target.Drawer.renderer.graphics.ResolveApparelGraphics();
+ PortraitsCache.SetDirty(__instance.Target);
+ }
+ }
+
+ (__instance.Target.jobs.curDriver as JobDriver_SexBaseReciever).parteners.Remove(__instance.pawn);
+
+ }
+
+ if (xxx.is_human(__instance.pawn)) {
+ __instance.pawn.Drawer.renderer.graphics.ResolveApparelGraphics();
+ PortraitsCache.SetDirty(__instance.pawn);
+ }
+ }
+ }
+}
diff --git a/Source/Patches/rjwPatches/HarmonyPatch_JoinInBedGiveJob.cs b/Source/Patches/rjwPatches/HarmonyPatch_JoinInBedGiveJob.cs
new file mode 100644
index 0000000..7a8b573
--- /dev/null
+++ b/Source/Patches/rjwPatches/HarmonyPatch_JoinInBedGiveJob.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using rjw;
+using Verse;
+using Verse.AI;
+using RimWorld;
+using HarmonyLib;
+
+namespace Rimworld_Animations {
+ [HarmonyPatch(typeof(JobGiver_JoinInBed), "TryGiveJob")]
+ public static class HarmonyPatch_JoinInBedGiveJob {
+
+ public static bool Prefix(ref Job __result, ref Pawn pawn) {
+
+ __result = null;
+
+ if (!RJWHookupSettings.HookupsEnabled)
+ return false;
+
+ if (pawn.Drafted)
+ return false;
+
+ if (!SexUtility.ReadyForHookup(pawn))
+ return false;
+
+ // We increase the time right away to prevent the fairly expensive check from happening too frequently
+ SexUtility.IncreaseTicksToNextHookup(pawn);
+
+ // If the pawn is a whore, or recently had sex, skip the job unless they're really horny
+ if (!xxx.is_frustrated(pawn) && (xxx.is_whore(pawn) || !SexUtility.ReadyForLovin(pawn)))
+ return false;
+
+ // This check attempts to keep groups leaving the map, like guests or traders, from turning around to hook up
+ if (pawn.mindState?.duty?.def == DutyDefOf.TravelOrLeave) {
+ // TODO: Some guest pawns keep the TravelOrLeave duty the whole time, I think the ones assigned to guard the pack animals.
+ // That's probably ok, though it wasn't the intention.
+ if (RJWSettings.DebugLogJoinInBed) Log.Message($"[RJW] JoinInBed.TryGiveJob:({xxx.get_pawnname(pawn)}): has TravelOrLeave, no time for lovin!");
+ return false;
+ }
+
+ if (pawn.CurJob == null || pawn.CurJob.def == JobDefOf.LayDown) {
+ //--Log.Message(" checking pawn and abilities");
+ if (xxx.can_fuck(pawn) || xxx.can_be_fucked(pawn)) {
+ //--Log.Message(" finding partner");
+ Pawn partner = JobGiver_JoinInBed.find_pawn_to_fuck(pawn, pawn.Map);
+
+ //--Log.Message(" checking partner");
+ if (partner == null)
+ return false;
+
+ // Can never be null, since find checks for bed.
+ Building_Bed bed = partner.CurrentBed();
+
+ // Interrupt current job.
+ if (pawn.CurJob != null && pawn.jobs.curDriver != null)
+ pawn.jobs.curDriver.EndJobWith(JobCondition.InterruptForced);
+
+ __result = JobMaker.MakeJob(DefDatabase.GetNamed("JoinInBedAnimation", true), partner, bed);
+ return false;
+ }
+ }
+
+ return false;
+
+ }
+
+ }
+}
diff --git a/Source/Patches/rjwPatches/HarmonyPatch_SexTick.cs b/Source/Patches/rjwPatches/HarmonyPatch_SexTick.cs
new file mode 100644
index 0000000..a1ca804
--- /dev/null
+++ b/Source/Patches/rjwPatches/HarmonyPatch_SexTick.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using HarmonyLib;
+using RimWorld;
+using Verse;
+using rjw;
+using Verse.Sound;
+
+namespace Rimworld_Animations {
+
+ [HarmonyPatch(typeof(xxx), "sexTick")]
+ public static class HarmonyPatch_SexTick {
+
+ public static bool Prefix(ref Pawn pawn, ref Pawn partner, ref bool enablerotation, ref bool pawnnude, ref bool partnernude) {
+
+
+ if (enablerotation) {
+ pawn.rotationTracker.Face(((Thing)partner).DrawPos);
+ partner.rotationTracker.Face(((Thing)pawn).DrawPos);
+ }
+ if (RJWSettings.sounds_enabled && !pawn.TryGetComp().isAnimating) {
+ SoundDef.Named("Sex").PlayOneShot(new TargetInfo(pawn.Position, pawn.Map));
+ }
+ pawn.Drawer.Notify_MeleeAttackOn((Thing)(object)partner);
+ if (enablerotation) {
+ pawn.rotationTracker.FaceCell(partner.Position);
+ }
+ if (pawnnude && !xxx.has_quirk(pawn, "Endytophile")) {
+ xxx.DrawNude(pawn);
+ }
+ if (partnernude && !xxx.has_quirk(pawn, "Endytophile")) {
+ xxx.DrawNude(partner);
+ }
+
+ return false;
+ }
+
+ }
+}
diff --git a/Source/Patches/rjwPatches/HarmonyPatch_WorkGiverSex.cs b/Source/Patches/rjwPatches/HarmonyPatch_WorkGiverSex.cs
new file mode 100644
index 0000000..5b7479f
--- /dev/null
+++ b/Source/Patches/rjwPatches/HarmonyPatch_WorkGiverSex.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using rjw;
+using HarmonyLib;
+using Verse;
+using RimWorld;
+using Verse.AI;
+
+namespace Rimworld_Animations {
+
+ [HarmonyPatch(typeof(WorkGiver_Sex), "JobOnThing")]
+ public static class HarmonyPatch_WorkGiverSex {
+
+ public static bool Prefix(ref Job __result, ref Thing t) {
+
+ Building_Bed bed = RestUtility.CurrentBed(t as Pawn);
+ if (bed == null) {
+ return false;
+ }
+ __result = JobMaker.MakeJob(DefDatabase.GetNamed("JoinInBedAnimation", true), t as Pawn, bed);
+ return false;
+
+ }
+
+ }
+}
diff --git a/Source/Rimworld-Animations b/Source/Rimworld-Animations
deleted file mode 160000
index ab9a1d9..0000000
--- a/Source/Rimworld-Animations
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit ab9a1d98883cb78d0d40a6bd93d85de7e34b132b