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