First commit

This commit is contained in:
AbstractConcept 2022-09-09 20:22:08 -05:00
parent ddda70a258
commit 8e6918ae70
95 changed files with 20766 additions and 1 deletions

View file

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using HarmonyLib;
using RimWorld;
using UnityEngine;
using Verse;
namespace Rimworld_Animations_Patch
{
[StaticConstructorOnStartup]
[HarmonyPatch(typeof(ApparelGraphicRecordGetter), "TryGetGraphicApparel")]
public static class HarmonyPatch_ApparelGraphicRecordGetter_TryGetGraphicApparel
{
public static void Postfix(ref bool __result, ref Apparel apparel, ref BodyTypeDef bodyType, ref ApparelGraphicRecord rec)
{
if (__result == false || apparel == null || bodyType == null || rec.graphic == null || ApparelSettings.cropApparel == false)
{ return; }
// Get graphic
Graphic graphic = rec.graphic;
// This graphic may need to be masked if the apparel sits on the skin layer and does not cover the legs
if (apparel.def.apparel.LastLayer == ApparelLayerDefOf.OnSkin && apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) && !apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs))
{
Dictionary<GraphicRequest, Graphic> allGraphics = Traverse.Create(typeof(GraphicDatabase)).Field("allGraphics").GetValue() as Dictionary<GraphicRequest, Graphic>;
GraphicRequest graphicRequest = new GraphicRequest(typeof(Graphic_Multi), graphic.path, ShaderDatabase.CutoutComplex, apparel.def.graphicData.drawSize, apparel.DrawColor, apparel.DrawColor, null, 0, null, "Masks/apparel_shirt_mask_" + bodyType.defName);
if (allGraphics.TryGetValue(graphicRequest) == null)
{
Graphic graphicWithApparelMask = GraphicDatabase.Get<Graphic_Multi>(graphic.path, ShaderDatabase.CutoutComplex, apparel.def.graphicData.drawSize, apparel.DrawColor, apparel.DrawColor, null, "Masks/apparel_shirt_mask_" + bodyType.defName);
graphic = GraphicMaskingUtility.ApplyGraphicWithMasks(graphic, graphicWithApparelMask, true);
DebugMode.Message("Applying apparel mask: Masks/apparel_shirt_mask_" + bodyType.defName + " to " + apparel.def.defName + " (" + graphic.path + ")");
}
}
rec = new ApparelGraphicRecord(graphic, apparel);
}
}
}

View file

@ -0,0 +1,46 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using UnityEngine;
using RimWorld;
using Verse;
using AlienRace;
using Rimworld_Animations;
namespace Rimworld_Animations_Patch
{
[StaticConstructorOnStartup]
[HarmonyPatch(typeof(CompBodyAnimator), "calculateDrawValues")]
public static class HarmonyPatch_CompBodyAnimator_calculateDrawValues
{
public static void Postfix(CompBodyAnimator __instance)
{
if (__instance?.pawn == null)
{ return; }
if (BasicSettings.autoscaleDeltaPos)
{
__instance.deltaPos.x *= __instance.pawn.RaceProps.baseBodySize;
__instance.deltaPos.z *= __instance.pawn.RaceProps.baseBodySize;
}
if (__instance.pawn.IsInBed(out Building bed) &&
__instance.pawn.GetAnimationData().animationDef.actors[__instance.pawn.GetAnimationData().actorID].requiredGenitals.NullOrEmpty() == false &&
__instance.pawn.GetAnimationData().animationDef.actors[__instance.pawn.GetAnimationData().actorID].requiredGenitals.Contains("Bed"))
{
__instance.bodyAngle += ((float)bed.Rotation.AsInt - 2f) * 90;
if (__instance.bodyAngle < 0) __instance.bodyAngle = 360 - ((-1f * __instance.bodyAngle) % 360);
if (__instance.bodyAngle > 360) __instance.bodyAngle %= 360;
__instance.headAngle += ((float)bed.Rotation.AsInt - 2f) * 90;
if (__instance.headAngle < 0) __instance.headAngle = 360 - ((-1f * __instance.headAngle) % 360);
if (__instance.headAngle > 360) __instance.headAngle %= 360;
__instance.deltaPos = __instance.deltaPos.RotatedBy(-(float)bed.Rotation.AsAngle);
}
}
}
}

View file

@ -0,0 +1,49 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using UnityEngine;
using RimWorld;
using Verse;
using AlienRace;
namespace Rimworld_Animations_Patch
{
[StaticConstructorOnStartup]
[HarmonyPatch(typeof(Building_Bed), "DrawGUIOverlay")]
public static class HarmonyPatch_Building_Bed_DrawGUIOverlay
{
// Patches beds so sleeping spot names are hidden when the owner is having sex on it
public static bool Prefix(Building_Bed __instance)
{
foreach (Pawn pawn in __instance.OwnersForReading)
{
if (pawn.GetAnimationData() != null && pawn.IsInBed(out Building bed) && bed == __instance)
{ return false; }
}
return true;
}
}
[StaticConstructorOnStartup]
[HarmonyPatch(typeof(PawnUIOverlay), "DrawPawnGUIOverlay")]
public static class HarmonyPatch_PawnUIOverlay_DrawPawnGUIOverlay
{
// Patches pawns so their name is hidden when having sex
public static bool Prefix(PawnUIOverlay __instance)
{
if (BasicSettings.hideNamesForSex)
{
Pawn pawn = (Pawn)AccessTools.Field(typeof(PawnUIOverlay), "pawn").GetValue(__instance);
if (pawn.GetAnimationData() != null)
{ return false; }
}
return true;
}
}
}

View file

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using RimWorld;
using Verse;
using Verse.AI;
using Rimworld_Animations;
using rjw;
namespace Rimworld_Animations_Patch
{
[HarmonyPatch(typeof(JobDriver), "GetReport")]
public static class HarmonyPatch_JobDriver
{
public static bool Prefix(JobDriver __instance, ref string __result)
{
JobDriver_Sex jobdriver = __instance as JobDriver_Sex;
if (jobdriver != null && jobdriver.pawn != null && jobdriver.pawn.GetAnimationData() != null && jobdriver.Sexprops.isRape == false && jobdriver.Sexprops.isWhoring == false)
{
LocalTargetInfo a = jobdriver.job.targetA.IsValid ? jobdriver.job.targetA : jobdriver.job.targetQueueA.FirstValid();
LocalTargetInfo b = jobdriver.job.targetB.IsValid ? jobdriver.job.targetB : jobdriver.job.targetQueueB.FirstValid();
LocalTargetInfo targetC = jobdriver.job.targetC;
__result = JobUtility.GetResolvedJobReport(jobdriver.pawn.GetAnimationData().animationDef.label, a, b, targetC);
return false;
}
return true;
}
}
}

View file

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
using HarmonyLib;
using System.Reflection;
using rjw;
namespace Rimworld_Animations_Patch
{
[StaticConstructorOnStartup]
public static class Harmony_PatchAll
{
static Harmony_PatchAll()
{
Harmony harmony = new Harmony("Rimworld_Animations_Patch");
harmony.PatchAll(Assembly.GetExecutingAssembly());
Quirk voyeur = new Quirk("Voyeur", "VoyeurQuirk", null, null);
if (Quirk.All.Contains(voyeur) == false)
{ Quirk.All.Add(voyeur); }
}
}
}

View file

@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using RimWorld;
using Verse;
using UnityEngine;
using System.Reflection;
using System.Reflection.Emit;
using Rimworld_Animations;
using rjw;
namespace Rimworld_Animations_Patch
{
[StaticConstructorOnStartup]
[HarmonyPatch(typeof(PawnRenderer), "RenderPawnInternal", new Type[]
{
typeof(Vector3),
typeof(float),
typeof(bool),
typeof(Rot4),
typeof(RotDrawMode),
typeof(PawnRenderFlags)
}
)]
public static class HarmonyPatch_PawnRenderer_RenderPawnInternal
{
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
List<CodeInstruction> ins = instructions.ToList();
for (int i = 0; i < instructions.Count(); i++)
{
bool runIns = true;
// Replaces the rotation that gets passed to DrawHeadHair with one that is based the current 'true' head orientation
if (i + 8 < instructions.Count() && ins[i + 8].opcode == OpCodes.Call && ins[i + 8].operand != null && ins[i + 8].OperandIs(AccessTools.DeclaredMethod(typeof(PawnRenderer), "DrawHeadHair")))
{
// Get the true head rotation
yield return new CodeInstruction(OpCodes.Ldarg_0);
yield return new CodeInstruction(OpCodes.Ldfld, AccessTools.DeclaredField(typeof(PawnRenderer), "pawn"));
yield return new CodeInstruction(OpCodes.Ldloc, (object)7); // local body facing
yield return new CodeInstruction(OpCodes.Ldarg_S, (object)6); // renderer flags
yield return new CodeInstruction(OpCodes.Call, AccessTools.DeclaredMethod(typeof(AnimationPatchUtility), "PawnHeadRotInAnimation"));
yield return new CodeInstruction(OpCodes.Stloc_S, (object)7); // set local body facing to true head facing
// Pass this head rotation to a new DrawHeadHair call
yield return new CodeInstruction(OpCodes.Ldarg_0);
yield return new CodeInstruction(OpCodes.Ldarg_1);
yield return new CodeInstruction(OpCodes.Ldloc_S, (object)6);
yield return new CodeInstruction(OpCodes.Ldarg_2);
yield return new CodeInstruction(OpCodes.Ldloc_S, (object)7); // local true head facing
yield return new CodeInstruction(OpCodes.Ldloc_S, (object)7); // local true head facing
yield return new CodeInstruction(OpCodes.Ldarg_S, (object)5); // bodyDrawType
yield return new CodeInstruction(OpCodes.Ldarg_S, (object)6); // renderer flags
yield return new CodeInstruction(OpCodes.Call, AccessTools.DeclaredMethod(typeof(PawnRenderer), "DrawHeadHair"));
// Skip the original call to DrawHeadHair
i = i + 8;
runIns = false;
}
if (runIns)
{
yield return ins[i];
}
}
}
}
[StaticConstructorOnStartup]
[HarmonyPatch(typeof(PawnGraphicSet), "ResolveAllGraphics")]
public static class HarmonyPatch_PawnGraphicSet_ResolveAllGraphics
{
public static void Postfix(PawnGraphicSet __instance)
{
if (__instance?.pawn?.apparel == null)
{ return; }
if (__instance.pawn.GetAnimationData() != null)
{ return; }
if (__instance.pawn.apparel.WornApparel.NullOrEmpty() == false)
{
foreach(Apparel apparel in __instance.pawn.apparel.WornApparel)
{
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
if (comp != null)
{ comp.isBeingWorn = true; }
}
}
}
}
}

View file

@ -0,0 +1,460 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using HarmonyLib;
using RimWorld;
using Verse;
using Verse.AI;
using rjw;
using Rimworld_Animations;
using RJW_ToysAndMasturbation;
namespace Rimworld_Animations_Patch
{
[HarmonyPatch(typeof(JobDriver_Sex), "setup_ticks")]
public static class HarmonyPatch_JobDriver_Sex_setup_ticks
{
public static void Postfix(ref JobDriver_Sex __instance)
{
// Sets ticks so that the orgasm meter starts empty, plus stop any running animations
HarmonyPatch_JobDriver_Masturbate_setup_ticks.Postfix(ref __instance);
// Invite another for a threesome?
if (RJWHookupSettings.QuickHookupsEnabled &&
__instance is JobDriver_SexBaseInitiator &&
__instance.pawn.GetAllSexParticipants().Count == 2 &&
(__instance is JobDriver_JoinInSex) == false &&
Random.value < BasicSettings.chanceForOtherToJoinInSex)
{
DebugMode.Message("Find another to join in sex");
Pawn pawn = __instance.pawn;
List<Pawn> candidates = new List<Pawn>();
float radius = 4f;
foreach (Thing thing in GenRadial.RadialDistinctThingsAround(pawn.Position, pawn.Map, radius, true))
{
Pawn other = thing as Pawn;
// Find candidates to invite
if (other != null && (int)SexInteractionUtility.CheckSexJobAgainstMorals(other, __instance, out Precept precept) <= 0 &&
SexInteractionUtility.PawnCanInvitePasserbyForSex(other, pawn.GetAllSexParticipants()))
{
DebugMode.Message(other.NameShortColored + " is a potential candidate");
candidates.Add(other);
}
}
// Invite a random candidate (weighted by attraction)
if (candidates.Count > 0)
{
Pawn invitedPawn = candidates.RandomElementByWeight(x => SexAppraiser.would_fuck(pawn, x, false, false, true) + SexAppraiser.would_fuck(pawn.GetSexPartner(), x, false, false, true));
pawn.GetSexInitiator().IsInBed(out Building bed);
DebugMode.Message(invitedPawn.NameShortColored + " was invited to join in sex");
Job job = new Job(DefDatabase<JobDef>.GetNamed("JoinInSex", false), pawn.GetSexPartner(), bed);
invitedPawn.jobs.TryTakeOrderedJob(job);
}
}
}
}
[HarmonyPatch(typeof(JobDriver_Masturbate), "setup_ticks")]
public static class HarmonyPatch_JobDriver_Masturbate_setup_ticks
{
// Sets ticks so that the orgasm meter starts empty, plus stop any running animations
public static void Postfix(ref JobDriver_Sex __instance)
{
__instance.sex_ticks = __instance.duration;
CompBodyAnimator comp = __instance.pawn.TryGetComp<CompBodyAnimator>();
if (comp != null && comp.isAnimating)
{ comp.isAnimating = false; }
}
}
[HarmonyPatch(typeof(JobDriver_SexBaseInitiator), "Start")]
public static class HarmonyPatch_JobDriver_SexBaseInitiator_Start
{
public static bool MustRerollHumping(Pawn pawn, SexProps sexProps)
{
if (sexProps?.dictionaryKey?.defName == null || sexProps.dictionaryKey.defName != "Masturbation_Humping")
{ return false; }
if (pawn.IsInBed(out Building bed))
{ return false; }
DebugMode.Message("Not in bed, cannot do requested action");
return true;
}
public static float RandomMasturbationWeights(InteractionDef interactionDef, Pawn pawn)
{
bool hasBed = pawn.IsInBed(out Building bed);
if (interactionDef.defName == "Masturbation_Breastjob" && Genital_Helper.has_breasts(pawn)) { return BasicSettings.breastsMasturbationChance; }
if (interactionDef.defName == "Masturbation_HandjobA" && Genital_Helper.has_anus(pawn)) { return BasicSettings.analMasturbationChance; }
if (interactionDef.defName == "Masturbation_HandjobP" && Genital_Helper.has_penis_fertile(pawn)) { return BasicSettings.genitalMasturbationChance; }
if (interactionDef.defName == "Masturbation_HandjobV" && Genital_Helper.has_vagina(pawn)) { return BasicSettings.genitalMasturbationChance; }
if (interactionDef.defName == "Masturbation_Humping" && hasBed) { return BasicSettings.humpingMasturbationChance; }
return 0f;
}
// Adds weights to masturbation type selection
public static void Prefix(ref JobDriver_SexBaseInitiator __instance)
{
if (__instance.Sexprops == null)
{ __instance.Sexprops = __instance.pawn.GetRMBSexPropsCache(); }
if (__instance is JobDriver_Masturbate && (__instance.Sexprops == null || MustRerollHumping(__instance.pawn, __instance.Sexprops)))
{
DebugMode.Message("No valid sexprops provided. Generating new interaction...");
SexProps sexProps = new SexProps();
sexProps.pawn = __instance.pawn;
sexProps.partner = __instance.pawn;
sexProps.sexType = xxx.rjwSextype.Masturbation;
List<InteractionDef> interactionDefs = DefDatabase<InteractionDef>.AllDefs.Where(x => x.HasModExtension<InteractionExtension>()).ToList();
Dictionary<rjw.Modules.Interactions.Objects.InteractionWithExtension, float> interactionsPlusWeights = new Dictionary<rjw.Modules.Interactions.Objects.InteractionWithExtension, float>();
foreach (InteractionDef interactionDef in interactionDefs)
{
var interaction = rjw.Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(interactionDef);
if (interaction.Extension.rjwSextype != xxx.rjwSextype.Masturbation.ToStringSafe())
{ continue; }
interactionsPlusWeights.Add(interaction, RandomMasturbationWeights(interaction.Interaction, sexProps.pawn));
}
var selectedInteraction = interactionsPlusWeights.RandomElementByWeight(x => x.Value).Key;
sexProps.dictionaryKey = selectedInteraction.Interaction;
sexProps.rulePack = selectedInteraction.Extension.rulepack_defs.RandomElement();
DebugMode.Message("Generated interaction: " + sexProps.dictionaryKey.defName);
DebugMode.Message(sexProps.rulePack);
__instance.Sexprops = sexProps;
}
}
// Adds in option for animated masturbation
public static void Postfix(ref JobDriver_SexBaseInitiator __instance)
{
// Allow solo animations to be played
if (__instance is JobDriver_Masturbate && __instance.pawn.GetAnimationData() == null)
{ PickMasturbationAnimation(__instance.pawn, __instance.Sexprops); }
// Allow make out animations to be played
if (__instance.pawn.GetAnimationData() == null)
{ PickMakeOutAnimation(__instance.pawn, __instance.Sexprops); }
// If there is no animation to play, exit
if (__instance.pawn.GetAnimationData() == null)
{ return; }
// Get animation data
AnimationDef anim = __instance.pawn.GetAnimationData()?.animationDef;
List<Pawn> pawnsToAnimate = __instance.pawn.GetAllSexParticipants();
// Sync animations across participants
foreach (Pawn participant in pawnsToAnimate)
{
JobDriver_Sex jobdriver = participant.jobs.curDriver as JobDriver_Sex;
if (jobdriver == null)
{ continue; }
// Animation timing reset
jobdriver.orgasms = 0;
jobdriver.ticks_left = AnimationPatchUtility.FindTrueAnimationLength(participant, out int orgasmTick);
jobdriver.ticksLeftThisToil = jobdriver.ticks_left;
jobdriver.sex_ticks = orgasmTick;
jobdriver.duration = jobdriver.sex_ticks;
jobdriver.orgasmstick = 0;
// Reset anchor and animation for sex toys
CompThingAnimator sexToyCompThingAnimator = ((Thing)jobdriver.job.GetTarget(TargetIndex.A)).TryGetComp<CompThingAnimator>();
if (sexToyCompThingAnimator != null)
{
DebugMode.Message("Using sex toy - " + jobdriver.job.GetTarget(TargetIndex.A));
__instance.pawn.IsInBed(out Building bed);
Vector3 anchor = AnimationPatchUtility.GetAnchorPosition(__instance.pawn, bed) - new Vector3(0.5f, 0, 0.5f);
AccessTools.Field(typeof(CompThingAnimator), "anchor").SetValue(sexToyCompThingAnimator, anchor);
}
// Determine where pawns are to toss clothes
if (participant?.apparel?.WornApparel != null)
{
IntVec3 apparelCell = MathUtility.FindRandomCellNearPawn(participant, 4);
foreach (Apparel apparel in participant.apparel.WornApparel)
{
CompApparelVisibility compApparelVisibility = apparel.TryGetComp<CompApparelVisibility>();
if (compApparelVisibility != null)
{ compApparelVisibility.GenerateFloorPosition(apparelCell, new Vector2(0f, 0.125f)); }
}
}
}
}
public static void PickMasturbationAnimation(Pawn pawn, SexProps sexProps = null)
{
if (pawn.TryGetComp<CompBodyAnimator>() == null)
{ Log.Error("Error: " + pawn.Name + " of race " + pawn.def.defName + " does not have CompBodyAnimator attached!"); return; }
pawn.TryGetComp<CompBodyAnimator>().isAnimating = false;
List<Pawn> pawnsToAnimate = new List<Pawn>() { pawn };
AnimationDef anim = null;
// Get random animation based on interaction type
if (sexProps != null)
{
var interaction = rjw.Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(sexProps.dictionaryKey);
InteractionDef interactionDef = interaction.Interaction;
DebugMode.Message("Finding animations that match " + interactionDef.defName);
List<AnimationDef> anims = new List<AnimationDef>();
foreach (AnimationDef _anim in DefDatabase<AnimationDef>.AllDefs)
{
if (_anim?.actors?.Count == 1 &&
_anim.sexTypes != null && _anim.sexTypes.Contains(xxx.rjwSextype.Masturbation) &&
_anim.interactionDefTypes != null && _anim.interactionDefTypes.Contains(interactionDef.defName) &&
AnimationUtility.GenitalCheckForPawn(_anim.actors[0].requiredGenitals, pawn, out string failReason))
{ anims.Add(_anim); }
}
if (anims != null && anims.Any())
{ anim = anims.RandomElement(); }
}
// If no animation exists, pick one at random
if (anim == null)
{ anim = AnimationUtility.tryFindAnimation(ref pawnsToAnimate, xxx.rjwSextype.Masturbation, sexProps); }
if (anim == null)
{ DebugMode.Message("No animation found"); return; }
// Start animation
DebugMode.Message("Playing " + anim.defName);
pawn.IsInBed(out Building bed);
if (bed != null)
{ pawn.TryGetComp<CompBodyAnimator>().setAnchor(bed); }
else
{ pawn.TryGetComp<CompBodyAnimator>().setAnchor(pawn.Position); }
pawn.TryGetComp<CompBodyAnimator>().StartAnimation(anim, pawnsToAnimate, 0, GenTicks.TicksGame % 2 == 0, true, bed == null);
// Hide hearts if necessary
if (!AnimationSettings.hearts)
{ (pawn.jobs.curDriver as JobDriver_Sex).ticks_between_hearts = System.Int32.MaxValue; }
}
public static void PickMakeOutAnimation(Pawn pawn, SexProps sexProps = null)
{
if (pawn.TryGetComp<CompBodyAnimator>() == null)
{ Log.Error("Error: " + pawn.Name + " of race " + pawn.def.defName + " does not have CompBodyAnimator attached!"); return; }
List<Pawn> pawnsToAnimate = pawn.GetAllSexParticipants();
if (sexProps.sexType != xxx.rjwSextype.Oral || pawnsToAnimate.Count != 2)
{ return; }
List<AnimationDef> kissingAnims = DefDatabase<AnimationDef>.AllDefs.Where(x => x.defName.Contains("Kiss")).ToList();
AnimationDef anim = kissingAnims[Random.Range(0, kissingAnims.Count)];
if (anim == null)
{ DebugMode.Message("No animation found"); return; }
bool mirror = GenTicks.TicksGame % 2 == 0;
// Start animation
DebugMode.Message("Playing " + anim.defName);
foreach (Pawn participant in pawnsToAnimate)
{
participant.TryGetComp<CompBodyAnimator>().setAnchor(pawnsToAnimate[0].Position);
participant.TryGetComp<CompBodyAnimator>().StartAnimation(anim, pawnsToAnimate, pawnsToAnimate.IndexOf(participant), mirror);
// Hide hearts if necessary
if (!AnimationSettings.hearts)
{ (participant.jobs.curDriver as JobDriver_Sex).ticks_between_hearts = System.Int32.MaxValue; }
}
}
}
[HarmonyPatch(typeof(JobDriver_Sex), "SexTick")]
public static class HarmonyPatch_JobDriver_Sex_SexTick
{
// If pawns don't have privacy, they'll stop having sex
public static void Postfix(ref JobDriver_Sex __instance, Pawn pawn)
{
if (pawn.IsHashIntervalTick(90))
{
if (pawn.IsMasturbating() && pawn.HasPrivacy(8f) == false)
{ pawn.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false); }
else if (pawn.IsHavingSex())
{
bool havePrivacy = true;
List<Pawn> participants = pawn.GetAllSexParticipants();
foreach (Pawn participant in participants)
{
if (participant.HasPrivacy(8f) == false)
{ havePrivacy = false; }
}
if (__instance.Sexprops != null && (__instance.Sexprops.isRape || __instance.Sexprops.isWhoring))
{ return; }
if (havePrivacy == false)
{
foreach (Pawn participant in participants)
{ participant.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false); }
}
}
}
}
}
[HarmonyPatch(typeof(JobDriver_Sex), "Orgasm")]
public static class HarmonyPatch_JobDriver_Sex_Orgasm
{
// Stops orgasm triggering more than once per animation
public static bool Prefix(ref JobDriver_Sex __instance)
{
if (__instance.orgasms > 0)
{ return false; }
return true;
}
public static bool ParticipantsDesireMoreSex(JobDriver_Sex jobdriver)
{
List<Pawn> participants = jobdriver.pawn.GetAllSexParticipants();
float satisfaction = 0f;
foreach (Pawn pawn in participants)
{
Need_Sex sexNeed = pawn?.needs?.TryGetNeed<Need_Sex>();
if (sexNeed == null)
{ satisfaction += 1; continue; }
satisfaction += sexNeed.CurLevelPercentage;
}
return Rand.Chance(1 - satisfaction / participants.Count);
}
// Alows the starting of a new animation cycle at the end of the current one
public static void Postfix(ref JobDriver_Sex __instance)
{
if (__instance.orgasms > 0)
{ __instance.sex_ticks = 0; }
if (__instance is JobDriver_SexBaseInitiator == false || __instance is JobDriver_JoinInSex)
{ return; }
if (__instance.Sexprops != null && (__instance.Sexprops.isRape || __instance.Sexprops.isWhoring))
{ return; }
if (__instance.ticksLeftThisToil <= 1 && (__instance.neverendingsex || ParticipantsDesireMoreSex(__instance)))
{
List<Pawn> participants = __instance.pawn.GetAllSexParticipants();
if (participants.Count == 2)
{
Job job = JobMaker.MakeJob(participants[0].CurJobDef, participants[0], participants[0].jobs.curJob.targetC);
participants[1].jobs.StartJob(job, JobCondition.Succeeded);
}
}
}
}
[HarmonyPatch(typeof(JobDriver_SexBaseInitiator), "End")]
public static class HarmonyPatch_JobDriver_Sex_End
{
// Clear all partners out when sex ends to prevent issues with threesome animations
public static void Postfix(ref JobDriver_SexBaseInitiator __instance)
{
if (__instance.Partner != null && __instance?.Partner?.jobs?.curDriver != null && __instance.Partner.Dead == false && __instance.Partner?.jobs.curDriver is JobDriver_SexBaseReciever)
{
foreach (Pawn participant in (__instance.Partner?.jobs.curDriver as JobDriver_SexBaseReciever).parteners.ToList())
{ participant.jobs.EndCurrentJob(JobCondition.Succeeded, false, true); }
(__instance.Partner?.jobs.curDriver as JobDriver_SexBaseReciever).parteners.Clear();
}
}
}
[HarmonyPatch(typeof(SexUtility), "AfterMasturbation")]
public static class HarmonyPatch_SexUtility_AfterMasturbation
{
// Removes excess calls to generate filth
public static bool Prefix(SexProps props)
{
var methodInfo = AccessTools.Method(typeof(SexUtility), "IncreaseTicksToNextLovin", null, null);
methodInfo.Invoke(null, new object[] { props.pawn });
AfterSexUtility.UpdateRecords(props);
return false;
}
}
[HarmonyPatch(typeof(Genital_Helper), "has_mouth")]
public static class HarmonyPatch_Genital_Helper_has_mouth
{
// Fixes mouth check
public static bool Prefix(ref bool __result, Pawn pawn)
{
__result = pawn.health.hediffSet.GetNotMissingParts().Any(x => x.def.defName.ToLower().ContainsAny("mouth", "teeth", "jaw", "beak"));
return false;
}
}
[HarmonyPatch(typeof(SexUtility), "DrawNude")]
public static class HarmonyPatch_SexUtility_DrawNude
{
public static bool Prefix(Pawn pawn, bool keep_hat_on)
{
if (!xxx.is_human(pawn)) return false;
if (pawn.Map != Find.CurrentMap) return false;
pawn.Drawer.renderer.graphics.ClearCache();
pawn.Drawer.renderer.graphics.apparelGraphics.Clear();
ApparelAnimationUtility.DetermineApparelToKeepOn(pawn);
foreach (Apparel apparel in pawn.apparel.WornApparel)
{
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
if ((comp == null || comp.isBeingWorn) && ApparelGraphicRecordGetter.TryGetGraphicApparel(apparel, pawn.story.bodyType, out ApparelGraphicRecord item))
{ pawn.Drawer.renderer.graphics.apparelGraphics.Add(item); }
}
GlobalTextureAtlasManager.TryMarkPawnFrameSetDirty(pawn);
return false;
}
}
}

View file

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
using HarmonyLib;
using RimNudeWorld;
namespace Rimworld_Animations_Patch
{
[StaticConstructorOnStartup]
public static class HarmonyPatch_RimNudeWorld
{
/*static HarmonyPatch_RimNudeWorld()
{
try
{
((Action)(() =>
{
if (LoadedModManager.RunningModsListForReading.Any(x => x.PackageIdPlayerFacing == "shauaputa.rimnudeworld"))
{
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("RevealingApparel.HarmonyPatch_DrawAddons"), "Postfix"),
prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_RimNudeWorld), "Prefix_DrawAddons")));
}
}))();
}
catch (TypeLoadException) { }
}
// Patch RimNudeWorld to override the revealing apparel feature; this task is handled by the new apparel settings system
public static bool Prefix_DrawAddons()
{
return false;
}*/
}
}

View file

@ -0,0 +1,369 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using RimWorld;
using Verse;
using AlienRace;
using Rimworld_Animations;
using rjw;
namespace Rimworld_Animations_Patch
{
[StaticConstructorOnStartup]
public static class HarmonyPatch_Rimworld_Animations
{
static HarmonyPatch_Rimworld_Animations()
{
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.AnimationUtility"), "GenitalCheckForPawn"),
postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "PostFix_AnimationUtility_GenitalCheckForPawn")));
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.CompBodyAnimator"), "setAnchor", new Type[] { typeof(Thing) }),
prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Prefix_HarmonyPatch_CompBodyAnimator_setAnchor")));
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.HarmonyPatch_JobDriver_SexBaseInitiator_Start"), "RerollAnimations"),
postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Postfix_RerollAnimations")));
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.HarmonyPatch_AlienRace"), "Prefix_AnimateHeadAddons"),
prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Prefix_DrawAddons")));
}
// Extend the animation selector's body part check to include hands and whether the pawn is in bed or not
public static void PostFix_AnimationUtility_GenitalCheckForPawn(ref bool __result, List<string> requiredGenitals, Pawn pawn, ref string failReason)
{
int handCount = 0;
bool pawnInBed = pawn.IsInBed(out Building bed);
var hands = pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def.defName == "Hand");
if (hands != null)
{ handCount = hands.Count(); }
if (requiredGenitals.NullOrEmpty())
{ return; }
if (requiredGenitals.Contains("OneHand") && handCount < 1)
{ failReason = "missing hand"; __result = false; }
if (requiredGenitals.Contains("TwoHands") && handCount < 2)
{ failReason = "missing hand(s)"; __result = false; }
if (requiredGenitals.Contains("Bed") && pawnInBed == false)
{ failReason = "pawn is not in bed"; __result = false; }
if (requiredGenitals.Contains("NoBed") && pawnInBed)
{ failReason = "pawn is in bed"; __result = false; }
}
// Override CompBodyAnimator's anchors
public static bool Prefix_HarmonyPatch_CompBodyAnimator_setAnchor(CompBodyAnimator __instance, Thing thing)
{
__instance.anchor = AnimationPatchUtility.GetAnchorPosition(__instance.pawn, thing);
return false;
}
// Adds functionality to determine which apparel each actor should discard based on the animation they are running
public static void Postfix_RerollAnimations(Pawn pawn)
{
AnimationDef anim = pawn.GetAnimationData()?.animationDef;
if (anim != null)
{
DebugMode.Message("Running animation: " + anim.defName);
List<Pawn> pawnsToAnimate = pawn.GetAllSexParticipants();
Pawn Target = pawn.GetSexReceiver();
foreach (Pawn participant in pawnsToAnimate)
{
int actorID = (int)AccessTools.Field(typeof(CompBodyAnimator), "actor").GetValue(participant.TryGetComp<CompBodyAnimator>());
DebugMode.Message("Participant " + actorID + ": " + participant.NameShortColored);
}
}
}
// Determine if a body addon is covered by apparel
/*public static bool BodyAddonCoveredByApparel(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
{
foreach (ApparelGraphicRecord apparelGraphicRecord in pawn.Drawer.renderer.graphics.apparelGraphics)
{
Apparel apparel = apparelGraphicRecord.sourceApparel;
if (apparel.def.apparel.bodyPartGroups.Any(x => bodyAddon.hiddenUnderApparelFor.Contains(x)))
{ return true; }
}
return false;
}*/
public static bool BodyAddonCoveredByWornApparel(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
{
if (bodyAddon?.hiddenUnderApparelFor == null || bodyAddon?.hiddenUnderApparelTag == null)
{ return false; }
foreach (Apparel apparel in pawn.apparel.WornApparel)
{
if (ApparelAnimationUtility.BodyAddonCoveredByApparel(apparel, bodyAddon))
{ return true; }
}
return false;
}
// Determine if a body addon should be drawn
public static bool CanDrawAddon(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
{
if (bodyAddon == null)
{ return false; }
if (pawn.RaceProps.Animal)
{ return true; }
Building_Bed building_Bed = pawn.CurrentBed();
if ((building_Bed == null ||
building_Bed.def.building.bed_showSleeperBody ||
bodyAddon.drawnInBed) && (bodyAddon.backstoryRequirement.NullOrEmpty() || pawn.story.AllBackstories.Any((Backstory x) => x.identifier == bodyAddon.backstoryRequirement)))
{
if (!bodyAddon.drawnDesiccated)
{
Corpse corpse = pawn.Corpse;
if (corpse != null && corpse.GetRotStage() == RotStage.Dessicated)
{ return false; }
}
if (!bodyAddon.bodyPart.NullOrEmpty() &&
!pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null).Any((BodyPartRecord bpr) => bpr.untranslatedCustomLabel == bodyAddon.bodyPart || bpr.def.defName == bodyAddon.bodyPart))
{
List<AlienPartGenerator.BodyAddonHediffGraphic> list = bodyAddon.hediffGraphics;
bool flag;
if (list == null)
{ flag = false; }
else
{ flag = list.Any((AlienPartGenerator.BodyAddonHediffGraphic bahg) => bahg.hediff == HediffDefOf.MissingBodyPart); }
if (!flag)
{ return false; }
}
if ((pawn.gender == Gender.Female) ? bodyAddon.drawForFemale : bodyAddon.drawForMale)
{
if (bodyAddon.bodyTypeRequirement.NullOrEmpty() || pawn.story.bodyType.ToString() == bodyAddon.bodyTypeRequirement)
{
bool renderClothes = true;
if (Find.WindowStack.currentlyDrawnWindow is Page_ConfigureStartingPawns)
{ renderClothes = (bool)AccessTools.Field(typeof(Page_ConfigureStartingPawns), "renderClothes").GetValue(Find.WindowStack.currentlyDrawnWindow); }
else
{ renderClothes = pawn.Drawer.renderer.graphics.apparelGraphics.Count > 0; }
bool conditionA = !BodyAddonCoveredByWornApparel(pawn, bodyAddon);
bool conditionB = !renderClothes;
bool conditionC = pawn.GetPosture() == PawnPosture.Standing;
bool conditionD = (pawn.GetPosture() == PawnPosture.LayingOnGroundNormal || pawn.GetPosture() == PawnPosture.LayingOnGroundFaceUp) && bodyAddon.drawnOnGround;
bool conditionE = pawn.GetPosture() == PawnPosture.LayingInBed && bodyAddon.drawnInBed;
return (conditionA || conditionB) && (conditionC || conditionD || conditionE);
}
}
}
return false;
}
// Replacement patch for AlienRace to draw the body addons
public static bool Prefix_DrawAddons(PawnRenderFlags renderFlags, Vector3 vector, Vector3 headOffset, Pawn pawn, Quaternion quat, Rot4 rotation)
{
if (!(pawn.def is ThingDef_AlienRace alienProps) || renderFlags.FlagSet(PawnRenderFlags.Invisible))
{ return false; }
// Try to draw apparel thrown on ground
if (ApparelSettings.clothesThrownOnGround)
{ ApparelAnimationUtility.TryToDrawApparelOnFloor(pawn); }
// Get components
List<AlienPartGenerator.BodyAddon> bodyAddons = alienProps.alienRace.generalSettings.alienPartGenerator.bodyAddons.ToList();
AlienPartGenerator.AlienComp alienComp = pawn.GetComp<AlienPartGenerator.AlienComp>();
CompBodyAnimator pawnAnimator = pawn.TryGetComp<CompBodyAnimator>();
// Get available hands
var hands = pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def.defName == "Hand");
int handsAvailableCount = hands.Count();
// Sort addons by their layer offset, otherwise body parts will actualy be layered according to their position in the list
// Note that sorting the addons directly seems to mess up relations between lists need by AlienRace
var sortedBodyAddons = bodyAddons.Select((x, i) => new KeyValuePair<AlienPartGenerator.BodyAddon, int>(x, i)).OrderBy(x => x.Key.offsets.GetOffset(rotation).layerOffset).ToList();
List<int> idxBodyAddons = sortedBodyAddons.Select(x => x.Value).ToList();
for (int idx = 0; idx < idxBodyAddons.Count; idx++)
{
int i = idxBodyAddons[idx];
AlienPartGenerator.BodyAddon bodyAddon = bodyAddons[i];
BodyPartRecord bodyPartRecord = AnimationPatchUtility.GetBodyPartRecord(pawn, bodyAddon.bodyPart);
bool alignWithHead = bodyAddon.alignWithHead || (bodyPartRecord != null && bodyPartRecord.IsInGroup(BodyPartGroupDefOf.FullHead));
Graphic addonGraphic = alienComp.addonGraphics[i];
//DebugMode.Message(" Trying to draw " + addonGraphic.path);
Rot4 apparentRotation = rotation;
if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawnAnimator != null && pawnAnimator.isAnimating)
{ apparentRotation = alignWithHead ? pawnAnimator.headFacing : pawnAnimator.bodyFacing; }
AlienPartGenerator.RotationOffset defaultOffsets = bodyAddon.defaultOffsets.GetOffset(apparentRotation);
Vector3 bodyTypeOffset = (defaultOffsets != null) ? defaultOffsets.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, alienComp.crownType) : Vector3.zero;
AlienPartGenerator.RotationOffset offsets = bodyAddon.offsets.GetOffset(apparentRotation);
Vector3 vector2 = bodyTypeOffset + ((offsets != null) ? offsets.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, alienComp.crownType) : Vector3.zero);
// Offset private parts so that they render over tattoos but under apparel (rendering under tatoos looks weird)
if ((bodyPartRecord != null && (bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.GenitalsBPG) || bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.ChestBPG) || bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.AnusBPG))) ||
addonGraphic.path.ToLower().Contains("belly"))
{
vector2.y = (vector2.y + 0.40f) / 1000f + 0.012f;
// Erected penises should be drawn over apparel
if (pawn.RaceProps.Humanlike &&
addonGraphic.path.ToLower().Contains("penis") &&
addonGraphic.path.ToLower().Contains("flaccid") == false &&
BodyAddonCoveredByWornApparel(pawn, bodyAddon) == false &&
apparentRotation == Rot4.South)
{ vector2.y += 0.010f; }
}
// Otherwise use the standard offsets
else
{ vector2.y = 0.3f + vector2.y; }
if (!bodyAddon.inFrontOfBody)
{ vector2.y *= -1f; }
float bodyAddonAngle = bodyAddon.angle;
if (apparentRotation == Rot4.North)
{
if (bodyAddon.layerInvert)
{ vector2.y = -vector2.y; }
bodyAddonAngle = 0f;
}
if (apparentRotation == Rot4.East)
{
bodyAddonAngle = -bodyAddonAngle;
vector2.x = -vector2.x;
}
Quaternion addonRotation = quat;
Quaternion quatAdditional = Quaternion.identity;
float finalAngle = 0;
if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawnAnimator != null && pawnAnimator.isAnimating)
{
if (pawnAnimator.controlGenitalAngle && bodyAddon?.hediffGraphics != null && !bodyAddon.hediffGraphics.NullOrEmpty() && bodyAddon.hediffGraphics[0]?.path != null && (bodyAddon.hediffGraphics[0].path.Contains("Penis") || bodyAddon.hediffGraphics[0].path.Contains("penis")))
{
float bodyAngle = pawnAnimator.bodyAngle;
addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
float anglePenis = AnimationSettings.controlGenitalRotation ? pawnAnimator.genitalAngle : 0f;
anglePenis = anglePenis < 0 ? 360 - (360 % anglePenis) : anglePenis;
quatAdditional = Quaternion.AngleAxis(angle: anglePenis, axis: Vector3.up);
finalAngle = bodyAngle + anglePenis;
}
else if (alignWithHead)
{
float headAngle = pawnAnimator.headAngle;
headAngle = headAngle < 0 ? 360 - (360 % headAngle) : headAngle;
addonRotation = Quaternion.AngleAxis(angle: headAngle, axis: Vector3.up);
finalAngle = pawnAnimator.bodyAngle + headAngle;
}
else
{
float bodyAngle = pawnAnimator.bodyAngle;
addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
finalAngle = bodyAngle;
}
}
// Fixes 'leaning left' issue with Yayo's animations
else if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && (pawnAnimator == null || pawnAnimator.isAnimating == false))
{
float bodyAngle = addonRotation.eulerAngles.y;
bodyAngle = bodyAngle < 0 ? 360 - (360 % bodyAngle) : bodyAngle;
addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
}
if (alignWithHead && bodyAddon.alignWithHead == false)
{ vector2 -= pawn.Drawer.renderer.BaseHeadOffsetAt(apparentRotation); }
Vector3 finalPosition = vector + (alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(angle: Mathf.Acos(f: Quaternion.Dot(a: Quaternion.identity, b: addonRotation)) * 2f * 57.29578f);
// Draw the addon if visible
if (CanDrawAddon(pawn, bodyAddon))
{
GenDraw.DrawMeshNowOrLater(mesh: addonGraphic.MeshAt(rot: apparentRotation),
loc: finalPosition,
quat: Quaternion.AngleAxis(angle: bodyAddonAngle, axis: Vector3.up) * quatAdditional * addonRotation,
mat: addonGraphic.MatAt(rot: apparentRotation), renderFlags.FlagSet(PawnRenderFlags.DrawNow));
}
// Draw hand over the body part if required
if (BasicSettings.showHands && !renderFlags.FlagSet(PawnRenderFlags.Portrait) && handsAvailableCount > 0)
{
if (HandAnimationUtility.TryToDrawHand(pawn, addonGraphic.path, finalPosition, finalAngle, rotation, renderFlags))
{ handsAvailableCount--; }
}
}
// Body addons are sometimes are not appropriately concealed by long hair, so re-draw the pawn's hair here
if (pawn.Drawer.renderer.graphics.headGraphic != null)
{
var methodInfo = AccessTools.Method(typeof(PawnRenderer), "DrawHeadHair", null, null);
Rot4 headFacing = pawnAnimator != null && pawnAnimator.isAnimating && !renderFlags.FlagSet(PawnRenderFlags.Portrait) ? pawnAnimator.headFacing : rotation;
float headAngle = pawnAnimator != null && pawnAnimator.isAnimating && !renderFlags.FlagSet(PawnRenderFlags.Portrait) ? pawnAnimator.headAngle : quat.eulerAngles.y;
RotDrawMode rotDrawMode = (RotDrawMode)AccessTools.Property(typeof(PawnRenderer), "CurRotDrawMode").GetValue(pawn.Drawer.renderer);
methodInfo.Invoke(pawn.Drawer.renderer, new object[] { vector + new Vector3(0f, YOffset_Head, 0f), headOffset, headAngle, headFacing, headFacing, rotDrawMode, renderFlags });
}
return false;
}
// List of potentially useful layer offsets
//private const float YOffset_CarriedThingUnder = -0.0028957527f;
//private const float YOffset_Align_Behind = 0.0028957527f;
//private const float YOffset_Body = 0.008687258f;
//private const float YOffset_Interval_Clothes = 0.0028957527f;
//private const float YOffset_Shell = 0.02027027f;
private const float YOffset_Head = 0.023166021f;
private const float YOffset_OnHead = 0.028957527f;
//private const float YOffset_PostHead = 0.03185328f;
//private const float YOffset_Tattoo = 0.0014478763f;
//private const float YOffset_WoundOverlays1 = 0.009687258f;
//private const float YOffset_WoundOverlays2 = 0.022166021f;
/* Details on the above
Body = rootLoc + YOffset_Body; (~ 0.009)
Tattoo = rootLoc + YOffset_Body + YOffset_Tattoo; (~ 0.010)
BodyAddons (not protruding) = rootLoc + 0.011f; (~0.011)
Body wounds (under clothes) = rootLoc + YOffset_WoundOverlays1; (~ 0.010)
Apparel (not north) = rootLoc + YOffset_Shell; (~ 0.020)
BodyAddons (protruding) = rootLoc + 0.011f + 0.010f; (~0.021)
Apparel (north) = rootLoc + YOffset_Head; (~ 0.023)
Body wounds (over clothes) = rootLoc + YOffset_WoundOverlays1 + YOffset_WoundOverlays2; (~ 0.03)
Head (not north) = rootLoc + YOffset_Head (~ 0.023);
Head (north) = rootLoc + YOffset_Shell; (~ 0.020)
Face tattoo = rootLoc + YOffset_OnHead - YOffset_Tattoo; (~ 0.028)
Head wounds (under clothes) = rootLoc + YOffset_OnHead; (~ 0.029)
Hair = rootLoc + YOffset_OnHead; (~ 0.029)
Hat (over hair) = rootLoc + YOffset_PostHead; (~ 0.031)
*/
}
}

View file

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using HarmonyLib;
using RimWorld;
using UnityEngine;
using Verse;
namespace Rimworld_Animations_Patch
{
[StaticConstructorOnStartup]
public static class HarmonyPatch_VisiblePants
{
static HarmonyPatch_VisiblePants()
{
try
{
((Action)(() =>
{
if (LoadedModManager.RunningModsListForReading.Any(x => x.PackageIdPlayerFacing == "XeoNovaDan.VisiblePants"))
{
(new Harmony("HeyLover")).Patch(AccessTools.Method(typeof(ApparelGraphicRecordGetter), "TryGetGraphicApparel"),
prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_VisiblePants), "Prefix_ApparelGraphicRecordGetter_TryGetGraphicApparel")));
}
}))();
}
catch (TypeLoadException) { }
}
public static bool Prefix_ApparelGraphicRecordGetter_TryGetGraphicApparel(ref bool __result, ref Apparel apparel, ref BodyTypeDef bodyType, out ApparelGraphicRecord rec)
{
rec = new ApparelGraphicRecord(null, null);
if (bodyType == null)
{
Log.Error("Found apparel graphic with undefined body type.");
bodyType = BodyTypeDefOf.Male;
}
if (apparel == null || apparel.WornGraphicPath.NullOrEmpty())
{
rec = new ApparelGraphicRecord(null, null);
__result = false;
return false;
}
string path;
if (apparel.def.apparel.LastLayer == ApparelLayerDefOf.Overhead || apparel.def.apparel.LastLayer == ApparelLayerDefOf.EyeCover || PawnRenderer.RenderAsPack(apparel) || apparel.WornGraphicPath == BaseContent.PlaceholderImagePath || apparel.WornGraphicPath == BaseContent.PlaceholderGearImagePath)
{ path = apparel.WornGraphicPath; }
else
{ path = apparel.WornGraphicPath + "_" + bodyType.defName; }
Shader shader = ShaderDatabase.Cutout;
if (apparel.def.apparel.useWornGraphicMask)
{ shader = ShaderDatabase.CutoutComplex; }
// Load the standard apparel graphic
Graphic graphic = GraphicDatabase.Get<Graphic_Multi>(path, shader, apparel.def.graphicData.drawSize, apparel.DrawColor);
// This graphic may need to be masked if the apparel sits on the skin layer and does not cover the legs
if (apparel.def.apparel.LastLayer == ApparelLayerDefOf.OnSkin && apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) && !apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs))
{
Graphic graphicWithApparelMask = GraphicDatabase.Get<Graphic_Multi>(path, ShaderDatabase.CutoutComplex, apparel.def.graphicData.drawSize, apparel.DrawColor, apparel.DrawColor, null, "Masks/apparel_shirt_mask_" + bodyType.defName);
graphic = GraphicMaskingUtility.ApplyGraphicWithMasks(graphic, graphicWithApparelMask, true);
//Log.Message("Applying apparel mask: Masks/apparel_shirt_mask_" + bodyType.defName + " to " + apparel.def.defName);
}
rec = new ApparelGraphicRecord(graphic, apparel);
__result = true;
return false;
}
}
}