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(); // 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.NullOrEmpty() == false && anim.actors[actorId].requiredGenitals.Any(x => x.ContainsAny("penis", "vagina", "anus", "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() != null && pawn.TryGetComp().isAnimating) { return pawn.TryGetComp().headFacing; } return regularPos; } public static Rot4 PawnBodyRotInAnimation(Pawn pawn, Rot4 regularPos, PawnRenderFlags renderFlags) { if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawn?.TryGetComp() != null && pawn.TryGetComp().isAnimating) { return pawn.TryGetComp().bodyFacing; } 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; } public static bool ShouldNotAnimatePawn(Pawn pawn) { return pawn.ageTracker.CurLifeStage.developmentalStage == DevelopmentalStage.Baby || pawn.ageTracker.CurLifeStage.developmentalStage == DevelopmentalStage.Child; } public static float GetBodySize(Pawn pawn) { return 1f; } private static Dictionary raceSpecificChildMultipliers = new Dictionary() { { "Alien_Orassan", new Vector3(1.4f, 1.4f, 1.4f) }, { "Alien_Cutebold", new Vector3(1.2f, 1f, 1f) }, { "Rabbie", new Vector3(-0.5f, 1f, 1f) }, }; public static Vector2 GetRaceSpecificOffsetMultipliers(Pawn pawn, BodyPartDef bodypart) { Vector2 multiplierVector = new Vector2(); if (GetBodySize(pawn) == 1f || raceSpecificChildMultipliers.TryGetValue(pawn.def.defName, out Vector3 raceVector) == false) { raceVector = new Vector3(1f, 1f, 1f); } if (bodypart?.defName == "tail" || bodypart?.defName == "Tail") { multiplierVector.x = raceVector.z; multiplierVector.y = raceVector.x; } else { multiplierVector.x = raceVector.y; multiplierVector.y = raceVector.x; } return multiplierVector; } } }