First commit
This commit is contained in:
parent
ddda70a258
commit
8e6918ae70
95 changed files with 20766 additions and 1 deletions
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
[HarmonyPatch(typeof(ApparelGraphicRecordGetter), "TryGetGraphicApparel")]
|
||||
public static class HarmonyPatch_ApparelGraphicRecordGetter_TryGetGraphicApparel
|
||||
{
|
||||
public static void Postfix(ref bool __result, ref Apparel apparel, ref BodyTypeDef bodyType, ref ApparelGraphicRecord rec)
|
||||
{
|
||||
if (__result == false || apparel == null || bodyType == null || rec.graphic == null || ApparelSettings.cropApparel == false)
|
||||
{ return; }
|
||||
|
||||
// Get graphic
|
||||
Graphic graphic = rec.graphic;
|
||||
|
||||
// This graphic may need to be masked if the apparel sits on the skin layer and does not cover the legs
|
||||
if (apparel.def.apparel.LastLayer == ApparelLayerDefOf.OnSkin && apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) && !apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs))
|
||||
{
|
||||
Dictionary<GraphicRequest, Graphic> allGraphics = Traverse.Create(typeof(GraphicDatabase)).Field("allGraphics").GetValue() as Dictionary<GraphicRequest, Graphic>;
|
||||
GraphicRequest graphicRequest = new GraphicRequest(typeof(Graphic_Multi), graphic.path, ShaderDatabase.CutoutComplex, apparel.def.graphicData.drawSize, apparel.DrawColor, apparel.DrawColor, null, 0, null, "Masks/apparel_shirt_mask_" + bodyType.defName);
|
||||
|
||||
if (allGraphics.TryGetValue(graphicRequest) == null)
|
||||
{
|
||||
Graphic graphicWithApparelMask = GraphicDatabase.Get<Graphic_Multi>(graphic.path, ShaderDatabase.CutoutComplex, apparel.def.graphicData.drawSize, apparel.DrawColor, apparel.DrawColor, null, "Masks/apparel_shirt_mask_" + bodyType.defName);
|
||||
graphic = GraphicMaskingUtility.ApplyGraphicWithMasks(graphic, graphicWithApparelMask, true);
|
||||
DebugMode.Message("Applying apparel mask: Masks/apparel_shirt_mask_" + bodyType.defName + " to " + apparel.def.defName + " (" + graphic.path + ")");
|
||||
}
|
||||
}
|
||||
|
||||
rec = new ApparelGraphicRecord(graphic, apparel);
|
||||
}
|
||||
}
|
||||
}
|
46
Source/Scripts/Patches/HarmonyPatch_CompBodyAnimator.cs
Normal file
46
Source/Scripts/Patches/HarmonyPatch_CompBodyAnimator.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using UnityEngine;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using AlienRace;
|
||||
using Rimworld_Animations;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
[HarmonyPatch(typeof(CompBodyAnimator), "calculateDrawValues")]
|
||||
public static class HarmonyPatch_CompBodyAnimator_calculateDrawValues
|
||||
{
|
||||
public static void Postfix(CompBodyAnimator __instance)
|
||||
{
|
||||
if (__instance?.pawn == null)
|
||||
{ return; }
|
||||
|
||||
if (BasicSettings.autoscaleDeltaPos)
|
||||
{
|
||||
__instance.deltaPos.x *= __instance.pawn.RaceProps.baseBodySize;
|
||||
__instance.deltaPos.z *= __instance.pawn.RaceProps.baseBodySize;
|
||||
}
|
||||
|
||||
if (__instance.pawn.IsInBed(out Building bed) &&
|
||||
__instance.pawn.GetAnimationData().animationDef.actors[__instance.pawn.GetAnimationData().actorID].requiredGenitals.NullOrEmpty() == false &&
|
||||
__instance.pawn.GetAnimationData().animationDef.actors[__instance.pawn.GetAnimationData().actorID].requiredGenitals.Contains("Bed"))
|
||||
{
|
||||
__instance.bodyAngle += ((float)bed.Rotation.AsInt - 2f) * 90;
|
||||
if (__instance.bodyAngle < 0) __instance.bodyAngle = 360 - ((-1f * __instance.bodyAngle) % 360);
|
||||
if (__instance.bodyAngle > 360) __instance.bodyAngle %= 360;
|
||||
|
||||
__instance.headAngle += ((float)bed.Rotation.AsInt - 2f) * 90;
|
||||
if (__instance.headAngle < 0) __instance.headAngle = 360 - ((-1f * __instance.headAngle) % 360);
|
||||
if (__instance.headAngle > 360) __instance.headAngle %= 360;
|
||||
|
||||
__instance.deltaPos = __instance.deltaPos.RotatedBy(-(float)bed.Rotation.AsAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
Source/Scripts/Patches/HarmonyPatch_DrawGUIOverlay.cs
Normal file
49
Source/Scripts/Patches/HarmonyPatch_DrawGUIOverlay.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using UnityEngine;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using AlienRace;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
[HarmonyPatch(typeof(Building_Bed), "DrawGUIOverlay")]
|
||||
public static class HarmonyPatch_Building_Bed_DrawGUIOverlay
|
||||
{
|
||||
// Patches beds so sleeping spot names are hidden when the owner is having sex on it
|
||||
public static bool Prefix(Building_Bed __instance)
|
||||
{
|
||||
foreach (Pawn pawn in __instance.OwnersForReading)
|
||||
{
|
||||
if (pawn.GetAnimationData() != null && pawn.IsInBed(out Building bed) && bed == __instance)
|
||||
{ return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[StaticConstructorOnStartup]
|
||||
[HarmonyPatch(typeof(PawnUIOverlay), "DrawPawnGUIOverlay")]
|
||||
public static class HarmonyPatch_PawnUIOverlay_DrawPawnGUIOverlay
|
||||
{
|
||||
// Patches pawns so their name is hidden when having sex
|
||||
public static bool Prefix(PawnUIOverlay __instance)
|
||||
{
|
||||
if (BasicSettings.hideNamesForSex)
|
||||
{
|
||||
Pawn pawn = (Pawn)AccessTools.Field(typeof(PawnUIOverlay), "pawn").GetValue(__instance);
|
||||
|
||||
if (pawn.GetAnimationData() != null)
|
||||
{ return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
34
Source/Scripts/Patches/HarmonyPatch_JobDriver.cs
Normal file
34
Source/Scripts/Patches/HarmonyPatch_JobDriver.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using Rimworld_Animations;
|
||||
using rjw;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[HarmonyPatch(typeof(JobDriver), "GetReport")]
|
||||
public static class HarmonyPatch_JobDriver
|
||||
{
|
||||
public static bool Prefix(JobDriver __instance, ref string __result)
|
||||
{
|
||||
JobDriver_Sex jobdriver = __instance as JobDriver_Sex;
|
||||
|
||||
if (jobdriver != null && jobdriver.pawn != null && jobdriver.pawn.GetAnimationData() != null && jobdriver.Sexprops.isRape == false && jobdriver.Sexprops.isWhoring == false)
|
||||
{
|
||||
LocalTargetInfo a = jobdriver.job.targetA.IsValid ? jobdriver.job.targetA : jobdriver.job.targetQueueA.FirstValid();
|
||||
LocalTargetInfo b = jobdriver.job.targetB.IsValid ? jobdriver.job.targetB : jobdriver.job.targetQueueB.FirstValid();
|
||||
LocalTargetInfo targetC = jobdriver.job.targetC;
|
||||
|
||||
__result = JobUtility.GetResolvedJobReport(jobdriver.pawn.GetAnimationData().animationDef.label, a, b, targetC);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
27
Source/Scripts/Patches/HarmonyPatch_PatchAll.cs
Normal file
27
Source/Scripts/Patches/HarmonyPatch_PatchAll.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Verse;
|
||||
using HarmonyLib;
|
||||
using System.Reflection;
|
||||
using rjw;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
public static class Harmony_PatchAll
|
||||
{
|
||||
static Harmony_PatchAll()
|
||||
{
|
||||
Harmony harmony = new Harmony("Rimworld_Animations_Patch");
|
||||
harmony.PatchAll(Assembly.GetExecutingAssembly());
|
||||
|
||||
Quirk voyeur = new Quirk("Voyeur", "VoyeurQuirk", null, null);
|
||||
|
||||
if (Quirk.All.Contains(voyeur) == false)
|
||||
{ Quirk.All.Add(voyeur); }
|
||||
}
|
||||
}
|
||||
}
|
96
Source/Scripts/Patches/HarmonyPatch_PawnRenderer.cs
Normal file
96
Source/Scripts/Patches/HarmonyPatch_PawnRenderer.cs
Normal file
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Rimworld_Animations;
|
||||
using rjw;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
[HarmonyPatch(typeof(PawnRenderer), "RenderPawnInternal", new Type[]
|
||||
{
|
||||
typeof(Vector3),
|
||||
typeof(float),
|
||||
typeof(bool),
|
||||
typeof(Rot4),
|
||||
typeof(RotDrawMode),
|
||||
typeof(PawnRenderFlags)
|
||||
}
|
||||
)]
|
||||
public static class HarmonyPatch_PawnRenderer_RenderPawnInternal
|
||||
{
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
List<CodeInstruction> ins = instructions.ToList();
|
||||
|
||||
for (int i = 0; i < instructions.Count(); i++)
|
||||
{
|
||||
bool runIns = true;
|
||||
|
||||
// Replaces the rotation that gets passed to DrawHeadHair with one that is based the current 'true' head orientation
|
||||
if (i + 8 < instructions.Count() && ins[i + 8].opcode == OpCodes.Call && ins[i + 8].operand != null && ins[i + 8].OperandIs(AccessTools.DeclaredMethod(typeof(PawnRenderer), "DrawHeadHair")))
|
||||
{
|
||||
// Get the true head rotation
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_0);
|
||||
yield return new CodeInstruction(OpCodes.Ldfld, AccessTools.DeclaredField(typeof(PawnRenderer), "pawn"));
|
||||
yield return new CodeInstruction(OpCodes.Ldloc, (object)7); // local body facing
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_S, (object)6); // renderer flags
|
||||
yield return new CodeInstruction(OpCodes.Call, AccessTools.DeclaredMethod(typeof(AnimationPatchUtility), "PawnHeadRotInAnimation"));
|
||||
yield return new CodeInstruction(OpCodes.Stloc_S, (object)7); // set local body facing to true head facing
|
||||
|
||||
// Pass this head rotation to a new DrawHeadHair call
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_0);
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_1);
|
||||
yield return new CodeInstruction(OpCodes.Ldloc_S, (object)6);
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_2);
|
||||
yield return new CodeInstruction(OpCodes.Ldloc_S, (object)7); // local true head facing
|
||||
yield return new CodeInstruction(OpCodes.Ldloc_S, (object)7); // local true head facing
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_S, (object)5); // bodyDrawType
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_S, (object)6); // renderer flags
|
||||
yield return new CodeInstruction(OpCodes.Call, AccessTools.DeclaredMethod(typeof(PawnRenderer), "DrawHeadHair"));
|
||||
|
||||
// Skip the original call to DrawHeadHair
|
||||
i = i + 8;
|
||||
|
||||
runIns = false;
|
||||
}
|
||||
|
||||
if (runIns)
|
||||
{
|
||||
yield return ins[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[StaticConstructorOnStartup]
|
||||
[HarmonyPatch(typeof(PawnGraphicSet), "ResolveAllGraphics")]
|
||||
public static class HarmonyPatch_PawnGraphicSet_ResolveAllGraphics
|
||||
{
|
||||
public static void Postfix(PawnGraphicSet __instance)
|
||||
{
|
||||
if (__instance?.pawn?.apparel == null)
|
||||
{ return; }
|
||||
|
||||
if (__instance.pawn.GetAnimationData() != null)
|
||||
{ return; }
|
||||
|
||||
if (__instance.pawn.apparel.WornApparel.NullOrEmpty() == false)
|
||||
{
|
||||
foreach(Apparel apparel in __instance.pawn.apparel.WornApparel)
|
||||
{
|
||||
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
|
||||
|
||||
if (comp != null)
|
||||
{ comp.isBeingWorn = true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
460
Source/Scripts/Patches/HarmonyPatch_RJW.cs
Normal file
460
Source/Scripts/Patches/HarmonyPatch_RJW.cs
Normal file
|
@ -0,0 +1,460 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using rjw;
|
||||
using Rimworld_Animations;
|
||||
using RJW_ToysAndMasturbation;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[HarmonyPatch(typeof(JobDriver_Sex), "setup_ticks")]
|
||||
public static class HarmonyPatch_JobDriver_Sex_setup_ticks
|
||||
{
|
||||
public static void Postfix(ref JobDriver_Sex __instance)
|
||||
{
|
||||
// Sets ticks so that the orgasm meter starts empty, plus stop any running animations
|
||||
HarmonyPatch_JobDriver_Masturbate_setup_ticks.Postfix(ref __instance);
|
||||
|
||||
// Invite another for a threesome?
|
||||
if (RJWHookupSettings.QuickHookupsEnabled &&
|
||||
__instance is JobDriver_SexBaseInitiator &&
|
||||
__instance.pawn.GetAllSexParticipants().Count == 2 &&
|
||||
(__instance is JobDriver_JoinInSex) == false &&
|
||||
Random.value < BasicSettings.chanceForOtherToJoinInSex)
|
||||
{
|
||||
DebugMode.Message("Find another to join in sex");
|
||||
|
||||
Pawn pawn = __instance.pawn;
|
||||
List<Pawn> candidates = new List<Pawn>();
|
||||
float radius = 4f;
|
||||
|
||||
foreach (Thing thing in GenRadial.RadialDistinctThingsAround(pawn.Position, pawn.Map, radius, true))
|
||||
{
|
||||
Pawn other = thing as Pawn;
|
||||
|
||||
// Find candidates to invite
|
||||
if (other != null && (int)SexInteractionUtility.CheckSexJobAgainstMorals(other, __instance, out Precept precept) <= 0 &&
|
||||
SexInteractionUtility.PawnCanInvitePasserbyForSex(other, pawn.GetAllSexParticipants()))
|
||||
{
|
||||
DebugMode.Message(other.NameShortColored + " is a potential candidate");
|
||||
candidates.Add(other);
|
||||
}
|
||||
}
|
||||
|
||||
// Invite a random candidate (weighted by attraction)
|
||||
if (candidates.Count > 0)
|
||||
{
|
||||
Pawn invitedPawn = candidates.RandomElementByWeight(x => SexAppraiser.would_fuck(pawn, x, false, false, true) + SexAppraiser.would_fuck(pawn.GetSexPartner(), x, false, false, true));
|
||||
pawn.GetSexInitiator().IsInBed(out Building bed);
|
||||
|
||||
DebugMode.Message(invitedPawn.NameShortColored + " was invited to join in sex");
|
||||
|
||||
Job job = new Job(DefDatabase<JobDef>.GetNamed("JoinInSex", false), pawn.GetSexPartner(), bed);
|
||||
invitedPawn.jobs.TryTakeOrderedJob(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(JobDriver_Masturbate), "setup_ticks")]
|
||||
public static class HarmonyPatch_JobDriver_Masturbate_setup_ticks
|
||||
{
|
||||
// Sets ticks so that the orgasm meter starts empty, plus stop any running animations
|
||||
public static void Postfix(ref JobDriver_Sex __instance)
|
||||
{
|
||||
__instance.sex_ticks = __instance.duration;
|
||||
|
||||
CompBodyAnimator comp = __instance.pawn.TryGetComp<CompBodyAnimator>();
|
||||
|
||||
if (comp != null && comp.isAnimating)
|
||||
{ comp.isAnimating = false; }
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(JobDriver_SexBaseInitiator), "Start")]
|
||||
public static class HarmonyPatch_JobDriver_SexBaseInitiator_Start
|
||||
{
|
||||
public static bool MustRerollHumping(Pawn pawn, SexProps sexProps)
|
||||
{
|
||||
if (sexProps?.dictionaryKey?.defName == null || sexProps.dictionaryKey.defName != "Masturbation_Humping")
|
||||
{ return false; }
|
||||
|
||||
if (pawn.IsInBed(out Building bed))
|
||||
{ return false; }
|
||||
|
||||
DebugMode.Message("Not in bed, cannot do requested action");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static float RandomMasturbationWeights(InteractionDef interactionDef, Pawn pawn)
|
||||
{
|
||||
bool hasBed = pawn.IsInBed(out Building bed);
|
||||
|
||||
if (interactionDef.defName == "Masturbation_Breastjob" && Genital_Helper.has_breasts(pawn)) { return BasicSettings.breastsMasturbationChance; }
|
||||
if (interactionDef.defName == "Masturbation_HandjobA" && Genital_Helper.has_anus(pawn)) { return BasicSettings.analMasturbationChance; }
|
||||
if (interactionDef.defName == "Masturbation_HandjobP" && Genital_Helper.has_penis_fertile(pawn)) { return BasicSettings.genitalMasturbationChance; }
|
||||
if (interactionDef.defName == "Masturbation_HandjobV" && Genital_Helper.has_vagina(pawn)) { return BasicSettings.genitalMasturbationChance; }
|
||||
if (interactionDef.defName == "Masturbation_Humping" && hasBed) { return BasicSettings.humpingMasturbationChance; }
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
// Adds weights to masturbation type selection
|
||||
public static void Prefix(ref JobDriver_SexBaseInitiator __instance)
|
||||
{
|
||||
if (__instance.Sexprops == null)
|
||||
{ __instance.Sexprops = __instance.pawn.GetRMBSexPropsCache(); }
|
||||
|
||||
if (__instance is JobDriver_Masturbate && (__instance.Sexprops == null || MustRerollHumping(__instance.pawn, __instance.Sexprops)))
|
||||
{
|
||||
DebugMode.Message("No valid sexprops provided. Generating new interaction...");
|
||||
|
||||
SexProps sexProps = new SexProps();
|
||||
sexProps.pawn = __instance.pawn;
|
||||
sexProps.partner = __instance.pawn;
|
||||
sexProps.sexType = xxx.rjwSextype.Masturbation;
|
||||
|
||||
List<InteractionDef> interactionDefs = DefDatabase<InteractionDef>.AllDefs.Where(x => x.HasModExtension<InteractionExtension>()).ToList();
|
||||
Dictionary<rjw.Modules.Interactions.Objects.InteractionWithExtension, float> interactionsPlusWeights = new Dictionary<rjw.Modules.Interactions.Objects.InteractionWithExtension, float>();
|
||||
|
||||
foreach (InteractionDef interactionDef in interactionDefs)
|
||||
{
|
||||
var interaction = rjw.Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(interactionDef);
|
||||
|
||||
if (interaction.Extension.rjwSextype != xxx.rjwSextype.Masturbation.ToStringSafe())
|
||||
{ continue; }
|
||||
|
||||
interactionsPlusWeights.Add(interaction, RandomMasturbationWeights(interaction.Interaction, sexProps.pawn));
|
||||
}
|
||||
|
||||
var selectedInteraction = interactionsPlusWeights.RandomElementByWeight(x => x.Value).Key;
|
||||
|
||||
sexProps.dictionaryKey = selectedInteraction.Interaction;
|
||||
sexProps.rulePack = selectedInteraction.Extension.rulepack_defs.RandomElement();
|
||||
|
||||
DebugMode.Message("Generated interaction: " + sexProps.dictionaryKey.defName);
|
||||
DebugMode.Message(sexProps.rulePack);
|
||||
|
||||
__instance.Sexprops = sexProps;
|
||||
}
|
||||
}
|
||||
|
||||
// Adds in option for animated masturbation
|
||||
public static void Postfix(ref JobDriver_SexBaseInitiator __instance)
|
||||
{
|
||||
// Allow solo animations to be played
|
||||
if (__instance is JobDriver_Masturbate && __instance.pawn.GetAnimationData() == null)
|
||||
{ PickMasturbationAnimation(__instance.pawn, __instance.Sexprops); }
|
||||
|
||||
// Allow make out animations to be played
|
||||
if (__instance.pawn.GetAnimationData() == null)
|
||||
{ PickMakeOutAnimation(__instance.pawn, __instance.Sexprops); }
|
||||
|
||||
// If there is no animation to play, exit
|
||||
if (__instance.pawn.GetAnimationData() == null)
|
||||
{ return; }
|
||||
|
||||
// Get animation data
|
||||
AnimationDef anim = __instance.pawn.GetAnimationData()?.animationDef;
|
||||
List<Pawn> pawnsToAnimate = __instance.pawn.GetAllSexParticipants();
|
||||
|
||||
// Sync animations across participants
|
||||
foreach (Pawn participant in pawnsToAnimate)
|
||||
{
|
||||
JobDriver_Sex jobdriver = participant.jobs.curDriver as JobDriver_Sex;
|
||||
|
||||
if (jobdriver == null)
|
||||
{ continue; }
|
||||
|
||||
// Animation timing reset
|
||||
jobdriver.orgasms = 0;
|
||||
jobdriver.ticks_left = AnimationPatchUtility.FindTrueAnimationLength(participant, out int orgasmTick);
|
||||
jobdriver.ticksLeftThisToil = jobdriver.ticks_left;
|
||||
jobdriver.sex_ticks = orgasmTick;
|
||||
jobdriver.duration = jobdriver.sex_ticks;
|
||||
jobdriver.orgasmstick = 0;
|
||||
|
||||
// Reset anchor and animation for sex toys
|
||||
CompThingAnimator sexToyCompThingAnimator = ((Thing)jobdriver.job.GetTarget(TargetIndex.A)).TryGetComp<CompThingAnimator>();
|
||||
|
||||
if (sexToyCompThingAnimator != null)
|
||||
{
|
||||
DebugMode.Message("Using sex toy - " + jobdriver.job.GetTarget(TargetIndex.A));
|
||||
|
||||
__instance.pawn.IsInBed(out Building bed);
|
||||
Vector3 anchor = AnimationPatchUtility.GetAnchorPosition(__instance.pawn, bed) - new Vector3(0.5f, 0, 0.5f);
|
||||
AccessTools.Field(typeof(CompThingAnimator), "anchor").SetValue(sexToyCompThingAnimator, anchor);
|
||||
}
|
||||
|
||||
// Determine where pawns are to toss clothes
|
||||
if (participant?.apparel?.WornApparel != null)
|
||||
{
|
||||
IntVec3 apparelCell = MathUtility.FindRandomCellNearPawn(participant, 4);
|
||||
|
||||
foreach (Apparel apparel in participant.apparel.WornApparel)
|
||||
{
|
||||
CompApparelVisibility compApparelVisibility = apparel.TryGetComp<CompApparelVisibility>();
|
||||
|
||||
if (compApparelVisibility != null)
|
||||
{ compApparelVisibility.GenerateFloorPosition(apparelCell, new Vector2(0f, 0.125f)); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void PickMasturbationAnimation(Pawn pawn, SexProps sexProps = null)
|
||||
{
|
||||
if (pawn.TryGetComp<CompBodyAnimator>() == null)
|
||||
{ Log.Error("Error: " + pawn.Name + " of race " + pawn.def.defName + " does not have CompBodyAnimator attached!"); return; }
|
||||
|
||||
pawn.TryGetComp<CompBodyAnimator>().isAnimating = false;
|
||||
|
||||
List<Pawn> pawnsToAnimate = new List<Pawn>() { pawn };
|
||||
AnimationDef anim = null;
|
||||
|
||||
// Get random animation based on interaction type
|
||||
if (sexProps != null)
|
||||
{
|
||||
var interaction = rjw.Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(sexProps.dictionaryKey);
|
||||
InteractionDef interactionDef = interaction.Interaction;
|
||||
|
||||
DebugMode.Message("Finding animations that match " + interactionDef.defName);
|
||||
|
||||
List<AnimationDef> anims = new List<AnimationDef>();
|
||||
|
||||
foreach (AnimationDef _anim in DefDatabase<AnimationDef>.AllDefs)
|
||||
{
|
||||
if (_anim?.actors?.Count == 1 &&
|
||||
_anim.sexTypes != null && _anim.sexTypes.Contains(xxx.rjwSextype.Masturbation) &&
|
||||
_anim.interactionDefTypes != null && _anim.interactionDefTypes.Contains(interactionDef.defName) &&
|
||||
AnimationUtility.GenitalCheckForPawn(_anim.actors[0].requiredGenitals, pawn, out string failReason))
|
||||
{ anims.Add(_anim); }
|
||||
}
|
||||
|
||||
if (anims != null && anims.Any())
|
||||
{ anim = anims.RandomElement(); }
|
||||
}
|
||||
|
||||
// If no animation exists, pick one at random
|
||||
if (anim == null)
|
||||
{ anim = AnimationUtility.tryFindAnimation(ref pawnsToAnimate, xxx.rjwSextype.Masturbation, sexProps); }
|
||||
|
||||
if (anim == null)
|
||||
{ DebugMode.Message("No animation found"); return; }
|
||||
|
||||
// Start animation
|
||||
DebugMode.Message("Playing " + anim.defName);
|
||||
|
||||
pawn.IsInBed(out Building bed);
|
||||
|
||||
if (bed != null)
|
||||
{ pawn.TryGetComp<CompBodyAnimator>().setAnchor(bed); }
|
||||
|
||||
else
|
||||
{ pawn.TryGetComp<CompBodyAnimator>().setAnchor(pawn.Position); }
|
||||
|
||||
pawn.TryGetComp<CompBodyAnimator>().StartAnimation(anim, pawnsToAnimate, 0, GenTicks.TicksGame % 2 == 0, true, bed == null);
|
||||
|
||||
// Hide hearts if necessary
|
||||
if (!AnimationSettings.hearts)
|
||||
{ (pawn.jobs.curDriver as JobDriver_Sex).ticks_between_hearts = System.Int32.MaxValue; }
|
||||
}
|
||||
|
||||
public static void PickMakeOutAnimation(Pawn pawn, SexProps sexProps = null)
|
||||
{
|
||||
if (pawn.TryGetComp<CompBodyAnimator>() == null)
|
||||
{ Log.Error("Error: " + pawn.Name + " of race " + pawn.def.defName + " does not have CompBodyAnimator attached!"); return; }
|
||||
|
||||
List<Pawn> pawnsToAnimate = pawn.GetAllSexParticipants();
|
||||
|
||||
if (sexProps.sexType != xxx.rjwSextype.Oral || pawnsToAnimate.Count != 2)
|
||||
{ return; }
|
||||
|
||||
List<AnimationDef> kissingAnims = DefDatabase<AnimationDef>.AllDefs.Where(x => x.defName.Contains("Kiss")).ToList();
|
||||
AnimationDef anim = kissingAnims[Random.Range(0, kissingAnims.Count)];
|
||||
|
||||
if (anim == null)
|
||||
{ DebugMode.Message("No animation found"); return; }
|
||||
|
||||
bool mirror = GenTicks.TicksGame % 2 == 0;
|
||||
|
||||
// Start animation
|
||||
DebugMode.Message("Playing " + anim.defName);
|
||||
|
||||
foreach (Pawn participant in pawnsToAnimate)
|
||||
{
|
||||
participant.TryGetComp<CompBodyAnimator>().setAnchor(pawnsToAnimate[0].Position);
|
||||
participant.TryGetComp<CompBodyAnimator>().StartAnimation(anim, pawnsToAnimate, pawnsToAnimate.IndexOf(participant), mirror);
|
||||
|
||||
// Hide hearts if necessary
|
||||
if (!AnimationSettings.hearts)
|
||||
{ (participant.jobs.curDriver as JobDriver_Sex).ticks_between_hearts = System.Int32.MaxValue; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(JobDriver_Sex), "SexTick")]
|
||||
public static class HarmonyPatch_JobDriver_Sex_SexTick
|
||||
{
|
||||
// If pawns don't have privacy, they'll stop having sex
|
||||
public static void Postfix(ref JobDriver_Sex __instance, Pawn pawn)
|
||||
{
|
||||
if (pawn.IsHashIntervalTick(90))
|
||||
{
|
||||
if (pawn.IsMasturbating() && pawn.HasPrivacy(8f) == false)
|
||||
{ pawn.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false); }
|
||||
|
||||
else if (pawn.IsHavingSex())
|
||||
{
|
||||
bool havePrivacy = true;
|
||||
List<Pawn> participants = pawn.GetAllSexParticipants();
|
||||
|
||||
foreach (Pawn participant in participants)
|
||||
{
|
||||
if (participant.HasPrivacy(8f) == false)
|
||||
{ havePrivacy = false; }
|
||||
}
|
||||
|
||||
if (__instance.Sexprops != null && (__instance.Sexprops.isRape || __instance.Sexprops.isWhoring))
|
||||
{ return; }
|
||||
|
||||
if (havePrivacy == false)
|
||||
{
|
||||
foreach (Pawn participant in participants)
|
||||
{ participant.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(JobDriver_Sex), "Orgasm")]
|
||||
public static class HarmonyPatch_JobDriver_Sex_Orgasm
|
||||
{
|
||||
// Stops orgasm triggering more than once per animation
|
||||
public static bool Prefix(ref JobDriver_Sex __instance)
|
||||
{
|
||||
if (__instance.orgasms > 0)
|
||||
{ return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ParticipantsDesireMoreSex(JobDriver_Sex jobdriver)
|
||||
{
|
||||
List<Pawn> participants = jobdriver.pawn.GetAllSexParticipants();
|
||||
|
||||
float satisfaction = 0f;
|
||||
|
||||
foreach (Pawn pawn in participants)
|
||||
{
|
||||
Need_Sex sexNeed = pawn?.needs?.TryGetNeed<Need_Sex>();
|
||||
|
||||
if (sexNeed == null)
|
||||
{ satisfaction += 1; continue; }
|
||||
|
||||
satisfaction += sexNeed.CurLevelPercentage;
|
||||
}
|
||||
|
||||
return Rand.Chance(1 - satisfaction / participants.Count);
|
||||
}
|
||||
|
||||
// Alows the starting of a new animation cycle at the end of the current one
|
||||
public static void Postfix(ref JobDriver_Sex __instance)
|
||||
{
|
||||
if (__instance.orgasms > 0)
|
||||
{ __instance.sex_ticks = 0; }
|
||||
|
||||
if (__instance is JobDriver_SexBaseInitiator == false || __instance is JobDriver_JoinInSex)
|
||||
{ return; }
|
||||
|
||||
if (__instance.Sexprops != null && (__instance.Sexprops.isRape || __instance.Sexprops.isWhoring))
|
||||
{ return; }
|
||||
|
||||
if (__instance.ticksLeftThisToil <= 1 && (__instance.neverendingsex || ParticipantsDesireMoreSex(__instance)))
|
||||
{
|
||||
List<Pawn> participants = __instance.pawn.GetAllSexParticipants();
|
||||
|
||||
if (participants.Count == 2)
|
||||
{
|
||||
Job job = JobMaker.MakeJob(participants[0].CurJobDef, participants[0], participants[0].jobs.curJob.targetC);
|
||||
participants[1].jobs.StartJob(job, JobCondition.Succeeded);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(JobDriver_SexBaseInitiator), "End")]
|
||||
public static class HarmonyPatch_JobDriver_Sex_End
|
||||
{
|
||||
// Clear all partners out when sex ends to prevent issues with threesome animations
|
||||
public static void Postfix(ref JobDriver_SexBaseInitiator __instance)
|
||||
{
|
||||
if (__instance.Partner != null && __instance?.Partner?.jobs?.curDriver != null && __instance.Partner.Dead == false && __instance.Partner?.jobs.curDriver is JobDriver_SexBaseReciever)
|
||||
{
|
||||
foreach (Pawn participant in (__instance.Partner?.jobs.curDriver as JobDriver_SexBaseReciever).parteners.ToList())
|
||||
{ participant.jobs.EndCurrentJob(JobCondition.Succeeded, false, true); }
|
||||
|
||||
(__instance.Partner?.jobs.curDriver as JobDriver_SexBaseReciever).parteners.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(SexUtility), "AfterMasturbation")]
|
||||
public static class HarmonyPatch_SexUtility_AfterMasturbation
|
||||
{
|
||||
// Removes excess calls to generate filth
|
||||
public static bool Prefix(SexProps props)
|
||||
{
|
||||
var methodInfo = AccessTools.Method(typeof(SexUtility), "IncreaseTicksToNextLovin", null, null);
|
||||
methodInfo.Invoke(null, new object[] { props.pawn });
|
||||
AfterSexUtility.UpdateRecords(props);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Genital_Helper), "has_mouth")]
|
||||
public static class HarmonyPatch_Genital_Helper_has_mouth
|
||||
{
|
||||
// Fixes mouth check
|
||||
public static bool Prefix(ref bool __result, Pawn pawn)
|
||||
{
|
||||
__result = pawn.health.hediffSet.GetNotMissingParts().Any(x => x.def.defName.ToLower().ContainsAny("mouth", "teeth", "jaw", "beak"));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(SexUtility), "DrawNude")]
|
||||
public static class HarmonyPatch_SexUtility_DrawNude
|
||||
{
|
||||
public static bool Prefix(Pawn pawn, bool keep_hat_on)
|
||||
{
|
||||
if (!xxx.is_human(pawn)) return false;
|
||||
if (pawn.Map != Find.CurrentMap) return false;
|
||||
|
||||
pawn.Drawer.renderer.graphics.ClearCache();
|
||||
pawn.Drawer.renderer.graphics.apparelGraphics.Clear();
|
||||
|
||||
ApparelAnimationUtility.DetermineApparelToKeepOn(pawn);
|
||||
|
||||
foreach (Apparel apparel in pawn.apparel.WornApparel)
|
||||
{
|
||||
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
|
||||
|
||||
if ((comp == null || comp.isBeingWorn) && ApparelGraphicRecordGetter.TryGetGraphicApparel(apparel, pawn.story.bodyType, out ApparelGraphicRecord item))
|
||||
{ pawn.Drawer.renderer.graphics.apparelGraphics.Add(item); }
|
||||
}
|
||||
|
||||
GlobalTextureAtlasManager.TryMarkPawnFrameSetDirty(pawn);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
37
Source/Scripts/Patches/HarmonyPatch_RimNudeWorld.cs
Normal file
37
Source/Scripts/Patches/HarmonyPatch_RimNudeWorld.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Verse;
|
||||
using HarmonyLib;
|
||||
using RimNudeWorld;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
public static class HarmonyPatch_RimNudeWorld
|
||||
{
|
||||
/*static HarmonyPatch_RimNudeWorld()
|
||||
{
|
||||
try
|
||||
{
|
||||
((Action)(() =>
|
||||
{
|
||||
if (LoadedModManager.RunningModsListForReading.Any(x => x.PackageIdPlayerFacing == "shauaputa.rimnudeworld"))
|
||||
{
|
||||
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("RevealingApparel.HarmonyPatch_DrawAddons"), "Postfix"),
|
||||
prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_RimNudeWorld), "Prefix_DrawAddons")));
|
||||
}
|
||||
}))();
|
||||
}
|
||||
catch (TypeLoadException) { }
|
||||
}
|
||||
|
||||
// Patch RimNudeWorld to override the revealing apparel feature; this task is handled by the new apparel settings system
|
||||
public static bool Prefix_DrawAddons()
|
||||
{
|
||||
return false;
|
||||
}*/
|
||||
}
|
||||
}
|
369
Source/Scripts/Patches/HarmonyPatch_Rimworld_Animations.cs
Normal file
369
Source/Scripts/Patches/HarmonyPatch_Rimworld_Animations.cs
Normal file
|
@ -0,0 +1,369 @@
|
|||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using AlienRace;
|
||||
using Rimworld_Animations;
|
||||
using rjw;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
public static class HarmonyPatch_Rimworld_Animations
|
||||
{
|
||||
static HarmonyPatch_Rimworld_Animations()
|
||||
{
|
||||
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.AnimationUtility"), "GenitalCheckForPawn"),
|
||||
postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "PostFix_AnimationUtility_GenitalCheckForPawn")));
|
||||
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.CompBodyAnimator"), "setAnchor", new Type[] { typeof(Thing) }),
|
||||
prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Prefix_HarmonyPatch_CompBodyAnimator_setAnchor")));
|
||||
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.HarmonyPatch_JobDriver_SexBaseInitiator_Start"), "RerollAnimations"),
|
||||
postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Postfix_RerollAnimations")));
|
||||
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.HarmonyPatch_AlienRace"), "Prefix_AnimateHeadAddons"),
|
||||
prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Prefix_DrawAddons")));
|
||||
}
|
||||
|
||||
// Extend the animation selector's body part check to include hands and whether the pawn is in bed or not
|
||||
public static void PostFix_AnimationUtility_GenitalCheckForPawn(ref bool __result, List<string> requiredGenitals, Pawn pawn, ref string failReason)
|
||||
{
|
||||
int handCount = 0;
|
||||
bool pawnInBed = pawn.IsInBed(out Building bed);
|
||||
|
||||
var hands = pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def.defName == "Hand");
|
||||
if (hands != null)
|
||||
{ handCount = hands.Count(); }
|
||||
|
||||
if (requiredGenitals.NullOrEmpty())
|
||||
{ return; }
|
||||
|
||||
if (requiredGenitals.Contains("OneHand") && handCount < 1)
|
||||
{ failReason = "missing hand"; __result = false; }
|
||||
|
||||
if (requiredGenitals.Contains("TwoHands") && handCount < 2)
|
||||
{ failReason = "missing hand(s)"; __result = false; }
|
||||
|
||||
if (requiredGenitals.Contains("Bed") && pawnInBed == false)
|
||||
{ failReason = "pawn is not in bed"; __result = false; }
|
||||
|
||||
if (requiredGenitals.Contains("NoBed") && pawnInBed)
|
||||
{ failReason = "pawn is in bed"; __result = false; }
|
||||
}
|
||||
|
||||
// Override CompBodyAnimator's anchors
|
||||
public static bool Prefix_HarmonyPatch_CompBodyAnimator_setAnchor(CompBodyAnimator __instance, Thing thing)
|
||||
{
|
||||
__instance.anchor = AnimationPatchUtility.GetAnchorPosition(__instance.pawn, thing);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Adds functionality to determine which apparel each actor should discard based on the animation they are running
|
||||
public static void Postfix_RerollAnimations(Pawn pawn)
|
||||
{
|
||||
AnimationDef anim = pawn.GetAnimationData()?.animationDef;
|
||||
|
||||
if (anim != null)
|
||||
{
|
||||
DebugMode.Message("Running animation: " + anim.defName);
|
||||
|
||||
List<Pawn> pawnsToAnimate = pawn.GetAllSexParticipants();
|
||||
Pawn Target = pawn.GetSexReceiver();
|
||||
|
||||
foreach (Pawn participant in pawnsToAnimate)
|
||||
{
|
||||
int actorID = (int)AccessTools.Field(typeof(CompBodyAnimator), "actor").GetValue(participant.TryGetComp<CompBodyAnimator>());
|
||||
DebugMode.Message("Participant " + actorID + ": " + participant.NameShortColored);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if a body addon is covered by apparel
|
||||
/*public static bool BodyAddonCoveredByApparel(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
|
||||
{
|
||||
foreach (ApparelGraphicRecord apparelGraphicRecord in pawn.Drawer.renderer.graphics.apparelGraphics)
|
||||
{
|
||||
Apparel apparel = apparelGraphicRecord.sourceApparel;
|
||||
|
||||
if (apparel.def.apparel.bodyPartGroups.Any(x => bodyAddon.hiddenUnderApparelFor.Contains(x)))
|
||||
{ return true; }
|
||||
}
|
||||
|
||||
return false;
|
||||
}*/
|
||||
|
||||
public static bool BodyAddonCoveredByWornApparel(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
|
||||
{
|
||||
if (bodyAddon?.hiddenUnderApparelFor == null || bodyAddon?.hiddenUnderApparelTag == null)
|
||||
{ return false; }
|
||||
|
||||
foreach (Apparel apparel in pawn.apparel.WornApparel)
|
||||
{
|
||||
if (ApparelAnimationUtility.BodyAddonCoveredByApparel(apparel, bodyAddon))
|
||||
{ return true; }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine if a body addon should be drawn
|
||||
public static bool CanDrawAddon(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
|
||||
{
|
||||
if (bodyAddon == null)
|
||||
{ return false; }
|
||||
|
||||
if (pawn.RaceProps.Animal)
|
||||
{ return true; }
|
||||
|
||||
Building_Bed building_Bed = pawn.CurrentBed();
|
||||
|
||||
if ((building_Bed == null ||
|
||||
building_Bed.def.building.bed_showSleeperBody ||
|
||||
bodyAddon.drawnInBed) && (bodyAddon.backstoryRequirement.NullOrEmpty() || pawn.story.AllBackstories.Any((Backstory x) => x.identifier == bodyAddon.backstoryRequirement)))
|
||||
{
|
||||
if (!bodyAddon.drawnDesiccated)
|
||||
{
|
||||
Corpse corpse = pawn.Corpse;
|
||||
if (corpse != null && corpse.GetRotStage() == RotStage.Dessicated)
|
||||
{ return false; }
|
||||
}
|
||||
|
||||
if (!bodyAddon.bodyPart.NullOrEmpty() &&
|
||||
!pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null).Any((BodyPartRecord bpr) => bpr.untranslatedCustomLabel == bodyAddon.bodyPart || bpr.def.defName == bodyAddon.bodyPart))
|
||||
{
|
||||
List<AlienPartGenerator.BodyAddonHediffGraphic> list = bodyAddon.hediffGraphics;
|
||||
bool flag;
|
||||
if (list == null)
|
||||
{ flag = false; }
|
||||
|
||||
else
|
||||
{ flag = list.Any((AlienPartGenerator.BodyAddonHediffGraphic bahg) => bahg.hediff == HediffDefOf.MissingBodyPart); }
|
||||
|
||||
if (!flag)
|
||||
{ return false; }
|
||||
}
|
||||
|
||||
if ((pawn.gender == Gender.Female) ? bodyAddon.drawForFemale : bodyAddon.drawForMale)
|
||||
{
|
||||
if (bodyAddon.bodyTypeRequirement.NullOrEmpty() || pawn.story.bodyType.ToString() == bodyAddon.bodyTypeRequirement)
|
||||
{
|
||||
bool renderClothes = true;
|
||||
|
||||
if (Find.WindowStack.currentlyDrawnWindow is Page_ConfigureStartingPawns)
|
||||
{ renderClothes = (bool)AccessTools.Field(typeof(Page_ConfigureStartingPawns), "renderClothes").GetValue(Find.WindowStack.currentlyDrawnWindow); }
|
||||
|
||||
else
|
||||
{ renderClothes = pawn.Drawer.renderer.graphics.apparelGraphics.Count > 0; }
|
||||
|
||||
bool conditionA = !BodyAddonCoveredByWornApparel(pawn, bodyAddon);
|
||||
bool conditionB = !renderClothes;
|
||||
bool conditionC = pawn.GetPosture() == PawnPosture.Standing;
|
||||
bool conditionD = (pawn.GetPosture() == PawnPosture.LayingOnGroundNormal || pawn.GetPosture() == PawnPosture.LayingOnGroundFaceUp) && bodyAddon.drawnOnGround;
|
||||
bool conditionE = pawn.GetPosture() == PawnPosture.LayingInBed && bodyAddon.drawnInBed;
|
||||
|
||||
return (conditionA || conditionB) && (conditionC || conditionD || conditionE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replacement patch for AlienRace to draw the body addons
|
||||
public static bool Prefix_DrawAddons(PawnRenderFlags renderFlags, Vector3 vector, Vector3 headOffset, Pawn pawn, Quaternion quat, Rot4 rotation)
|
||||
{
|
||||
if (!(pawn.def is ThingDef_AlienRace alienProps) || renderFlags.FlagSet(PawnRenderFlags.Invisible))
|
||||
{ return false; }
|
||||
|
||||
// Try to draw apparel thrown on ground
|
||||
if (ApparelSettings.clothesThrownOnGround)
|
||||
{ ApparelAnimationUtility.TryToDrawApparelOnFloor(pawn); }
|
||||
|
||||
// Get components
|
||||
List<AlienPartGenerator.BodyAddon> bodyAddons = alienProps.alienRace.generalSettings.alienPartGenerator.bodyAddons.ToList();
|
||||
AlienPartGenerator.AlienComp alienComp = pawn.GetComp<AlienPartGenerator.AlienComp>();
|
||||
CompBodyAnimator pawnAnimator = pawn.TryGetComp<CompBodyAnimator>();
|
||||
|
||||
// Get available hands
|
||||
var hands = pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def.defName == "Hand");
|
||||
int handsAvailableCount = hands.Count();
|
||||
|
||||
// Sort addons by their layer offset, otherwise body parts will actualy be layered according to their position in the list
|
||||
// Note that sorting the addons directly seems to mess up relations between lists need by AlienRace
|
||||
var sortedBodyAddons = bodyAddons.Select((x, i) => new KeyValuePair<AlienPartGenerator.BodyAddon, int>(x, i)).OrderBy(x => x.Key.offsets.GetOffset(rotation).layerOffset).ToList();
|
||||
List<int> idxBodyAddons = sortedBodyAddons.Select(x => x.Value).ToList();
|
||||
|
||||
for (int idx = 0; idx < idxBodyAddons.Count; idx++)
|
||||
{
|
||||
int i = idxBodyAddons[idx];
|
||||
AlienPartGenerator.BodyAddon bodyAddon = bodyAddons[i];
|
||||
BodyPartRecord bodyPartRecord = AnimationPatchUtility.GetBodyPartRecord(pawn, bodyAddon.bodyPart);
|
||||
|
||||
bool alignWithHead = bodyAddon.alignWithHead || (bodyPartRecord != null && bodyPartRecord.IsInGroup(BodyPartGroupDefOf.FullHead));
|
||||
|
||||
Graphic addonGraphic = alienComp.addonGraphics[i];
|
||||
//DebugMode.Message(" Trying to draw " + addonGraphic.path);
|
||||
|
||||
Rot4 apparentRotation = rotation;
|
||||
if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawnAnimator != null && pawnAnimator.isAnimating)
|
||||
{ apparentRotation = alignWithHead ? pawnAnimator.headFacing : pawnAnimator.bodyFacing; }
|
||||
|
||||
AlienPartGenerator.RotationOffset defaultOffsets = bodyAddon.defaultOffsets.GetOffset(apparentRotation);
|
||||
Vector3 bodyTypeOffset = (defaultOffsets != null) ? defaultOffsets.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, alienComp.crownType) : Vector3.zero;
|
||||
AlienPartGenerator.RotationOffset offsets = bodyAddon.offsets.GetOffset(apparentRotation);
|
||||
Vector3 vector2 = bodyTypeOffset + ((offsets != null) ? offsets.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, alienComp.crownType) : Vector3.zero);
|
||||
|
||||
// Offset private parts so that they render over tattoos but under apparel (rendering under tatoos looks weird)
|
||||
if ((bodyPartRecord != null && (bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.GenitalsBPG) || bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.ChestBPG) || bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.AnusBPG))) ||
|
||||
addonGraphic.path.ToLower().Contains("belly"))
|
||||
{
|
||||
vector2.y = (vector2.y + 0.40f) / 1000f + 0.012f;
|
||||
|
||||
// Erected penises should be drawn over apparel
|
||||
if (pawn.RaceProps.Humanlike &&
|
||||
addonGraphic.path.ToLower().Contains("penis") &&
|
||||
addonGraphic.path.ToLower().Contains("flaccid") == false &&
|
||||
BodyAddonCoveredByWornApparel(pawn, bodyAddon) == false &&
|
||||
apparentRotation == Rot4.South)
|
||||
{ vector2.y += 0.010f; }
|
||||
}
|
||||
|
||||
// Otherwise use the standard offsets
|
||||
else
|
||||
{ vector2.y = 0.3f + vector2.y; }
|
||||
|
||||
if (!bodyAddon.inFrontOfBody)
|
||||
{ vector2.y *= -1f; }
|
||||
|
||||
float bodyAddonAngle = bodyAddon.angle;
|
||||
|
||||
if (apparentRotation == Rot4.North)
|
||||
{
|
||||
if (bodyAddon.layerInvert)
|
||||
{ vector2.y = -vector2.y; }
|
||||
|
||||
bodyAddonAngle = 0f;
|
||||
}
|
||||
|
||||
if (apparentRotation == Rot4.East)
|
||||
{
|
||||
bodyAddonAngle = -bodyAddonAngle;
|
||||
vector2.x = -vector2.x;
|
||||
}
|
||||
|
||||
Quaternion addonRotation = quat;
|
||||
Quaternion quatAdditional = Quaternion.identity;
|
||||
|
||||
float finalAngle = 0;
|
||||
|
||||
if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawnAnimator != null && pawnAnimator.isAnimating)
|
||||
{
|
||||
if (pawnAnimator.controlGenitalAngle && bodyAddon?.hediffGraphics != null && !bodyAddon.hediffGraphics.NullOrEmpty() && bodyAddon.hediffGraphics[0]?.path != null && (bodyAddon.hediffGraphics[0].path.Contains("Penis") || bodyAddon.hediffGraphics[0].path.Contains("penis")))
|
||||
{
|
||||
float bodyAngle = pawnAnimator.bodyAngle;
|
||||
addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
|
||||
|
||||
float anglePenis = AnimationSettings.controlGenitalRotation ? pawnAnimator.genitalAngle : 0f;
|
||||
anglePenis = anglePenis < 0 ? 360 - (360 % anglePenis) : anglePenis;
|
||||
quatAdditional = Quaternion.AngleAxis(angle: anglePenis, axis: Vector3.up);
|
||||
|
||||
finalAngle = bodyAngle + anglePenis;
|
||||
}
|
||||
|
||||
else if (alignWithHead)
|
||||
{
|
||||
float headAngle = pawnAnimator.headAngle;
|
||||
headAngle = headAngle < 0 ? 360 - (360 % headAngle) : headAngle;
|
||||
addonRotation = Quaternion.AngleAxis(angle: headAngle, axis: Vector3.up);
|
||||
|
||||
finalAngle = pawnAnimator.bodyAngle + headAngle;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
float bodyAngle = pawnAnimator.bodyAngle;
|
||||
addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
|
||||
|
||||
finalAngle = bodyAngle;
|
||||
}
|
||||
}
|
||||
|
||||
// Fixes 'leaning left' issue with Yayo's animations
|
||||
else if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && (pawnAnimator == null || pawnAnimator.isAnimating == false))
|
||||
{
|
||||
float bodyAngle = addonRotation.eulerAngles.y;
|
||||
bodyAngle = bodyAngle < 0 ? 360 - (360 % bodyAngle) : bodyAngle;
|
||||
addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
|
||||
}
|
||||
|
||||
if (alignWithHead && bodyAddon.alignWithHead == false)
|
||||
{ vector2 -= pawn.Drawer.renderer.BaseHeadOffsetAt(apparentRotation); }
|
||||
|
||||
Vector3 finalPosition = vector + (alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(angle: Mathf.Acos(f: Quaternion.Dot(a: Quaternion.identity, b: addonRotation)) * 2f * 57.29578f);
|
||||
|
||||
// Draw the addon if visible
|
||||
if (CanDrawAddon(pawn, bodyAddon))
|
||||
{
|
||||
GenDraw.DrawMeshNowOrLater(mesh: addonGraphic.MeshAt(rot: apparentRotation),
|
||||
loc: finalPosition,
|
||||
quat: Quaternion.AngleAxis(angle: bodyAddonAngle, axis: Vector3.up) * quatAdditional * addonRotation,
|
||||
mat: addonGraphic.MatAt(rot: apparentRotation), renderFlags.FlagSet(PawnRenderFlags.DrawNow));
|
||||
}
|
||||
|
||||
// Draw hand over the body part if required
|
||||
if (BasicSettings.showHands && !renderFlags.FlagSet(PawnRenderFlags.Portrait) && handsAvailableCount > 0)
|
||||
{
|
||||
if (HandAnimationUtility.TryToDrawHand(pawn, addonGraphic.path, finalPosition, finalAngle, rotation, renderFlags))
|
||||
{ handsAvailableCount--; }
|
||||
}
|
||||
}
|
||||
|
||||
// Body addons are sometimes are not appropriately concealed by long hair, so re-draw the pawn's hair here
|
||||
if (pawn.Drawer.renderer.graphics.headGraphic != null)
|
||||
{
|
||||
var methodInfo = AccessTools.Method(typeof(PawnRenderer), "DrawHeadHair", null, null);
|
||||
|
||||
Rot4 headFacing = pawnAnimator != null && pawnAnimator.isAnimating && !renderFlags.FlagSet(PawnRenderFlags.Portrait) ? pawnAnimator.headFacing : rotation;
|
||||
float headAngle = pawnAnimator != null && pawnAnimator.isAnimating && !renderFlags.FlagSet(PawnRenderFlags.Portrait) ? pawnAnimator.headAngle : quat.eulerAngles.y;
|
||||
RotDrawMode rotDrawMode = (RotDrawMode)AccessTools.Property(typeof(PawnRenderer), "CurRotDrawMode").GetValue(pawn.Drawer.renderer);
|
||||
|
||||
methodInfo.Invoke(pawn.Drawer.renderer, new object[] { vector + new Vector3(0f, YOffset_Head, 0f), headOffset, headAngle, headFacing, headFacing, rotDrawMode, renderFlags });
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// List of potentially useful layer offsets
|
||||
//private const float YOffset_CarriedThingUnder = -0.0028957527f;
|
||||
//private const float YOffset_Align_Behind = 0.0028957527f;
|
||||
//private const float YOffset_Body = 0.008687258f;
|
||||
//private const float YOffset_Interval_Clothes = 0.0028957527f;
|
||||
//private const float YOffset_Shell = 0.02027027f;
|
||||
private const float YOffset_Head = 0.023166021f;
|
||||
private const float YOffset_OnHead = 0.028957527f;
|
||||
//private const float YOffset_PostHead = 0.03185328f;
|
||||
//private const float YOffset_Tattoo = 0.0014478763f;
|
||||
//private const float YOffset_WoundOverlays1 = 0.009687258f;
|
||||
//private const float YOffset_WoundOverlays2 = 0.022166021f;
|
||||
|
||||
/* Details on the above
|
||||
Body = rootLoc + YOffset_Body; (~ 0.009)
|
||||
Tattoo = rootLoc + YOffset_Body + YOffset_Tattoo; (~ 0.010)
|
||||
BodyAddons (not protruding) = rootLoc + 0.011f; (~0.011)
|
||||
Body wounds (under clothes) = rootLoc + YOffset_WoundOverlays1; (~ 0.010)
|
||||
Apparel (not north) = rootLoc + YOffset_Shell; (~ 0.020)
|
||||
BodyAddons (protruding) = rootLoc + 0.011f + 0.010f; (~0.021)
|
||||
Apparel (north) = rootLoc + YOffset_Head; (~ 0.023)
|
||||
Body wounds (over clothes) = rootLoc + YOffset_WoundOverlays1 + YOffset_WoundOverlays2; (~ 0.03)
|
||||
|
||||
Head (not north) = rootLoc + YOffset_Head (~ 0.023);
|
||||
Head (north) = rootLoc + YOffset_Shell; (~ 0.020)
|
||||
Face tattoo = rootLoc + YOffset_OnHead - YOffset_Tattoo; (~ 0.028)
|
||||
Head wounds (under clothes) = rootLoc + YOffset_OnHead; (~ 0.029)
|
||||
Hair = rootLoc + YOffset_OnHead; (~ 0.029)
|
||||
Hat (over hair) = rootLoc + YOffset_PostHead; (~ 0.031)
|
||||
*/
|
||||
}
|
||||
}
|
76
Source/Scripts/Patches/HarmonyPatch_VisiblePants.cs
Normal file
76
Source/Scripts/Patches/HarmonyPatch_VisiblePants.cs
Normal file
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
public static class HarmonyPatch_VisiblePants
|
||||
{
|
||||
static HarmonyPatch_VisiblePants()
|
||||
{
|
||||
try
|
||||
{
|
||||
((Action)(() =>
|
||||
{
|
||||
if (LoadedModManager.RunningModsListForReading.Any(x => x.PackageIdPlayerFacing == "XeoNovaDan.VisiblePants"))
|
||||
{
|
||||
(new Harmony("HeyLover")).Patch(AccessTools.Method(typeof(ApparelGraphicRecordGetter), "TryGetGraphicApparel"),
|
||||
prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_VisiblePants), "Prefix_ApparelGraphicRecordGetter_TryGetGraphicApparel")));
|
||||
}
|
||||
}))();
|
||||
}
|
||||
catch (TypeLoadException) { }
|
||||
}
|
||||
|
||||
public static bool Prefix_ApparelGraphicRecordGetter_TryGetGraphicApparel(ref bool __result, ref Apparel apparel, ref BodyTypeDef bodyType, out ApparelGraphicRecord rec)
|
||||
{
|
||||
rec = new ApparelGraphicRecord(null, null);
|
||||
|
||||
if (bodyType == null)
|
||||
{
|
||||
Log.Error("Found apparel graphic with undefined body type.");
|
||||
bodyType = BodyTypeDefOf.Male;
|
||||
}
|
||||
|
||||
if (apparel == null || apparel.WornGraphicPath.NullOrEmpty())
|
||||
{
|
||||
rec = new ApparelGraphicRecord(null, null);
|
||||
__result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
string path;
|
||||
|
||||
if (apparel.def.apparel.LastLayer == ApparelLayerDefOf.Overhead || apparel.def.apparel.LastLayer == ApparelLayerDefOf.EyeCover || PawnRenderer.RenderAsPack(apparel) || apparel.WornGraphicPath == BaseContent.PlaceholderImagePath || apparel.WornGraphicPath == BaseContent.PlaceholderGearImagePath)
|
||||
{ path = apparel.WornGraphicPath; }
|
||||
|
||||
else
|
||||
{ path = apparel.WornGraphicPath + "_" + bodyType.defName; }
|
||||
|
||||
Shader shader = ShaderDatabase.Cutout;
|
||||
|
||||
if (apparel.def.apparel.useWornGraphicMask)
|
||||
{ shader = ShaderDatabase.CutoutComplex; }
|
||||
|
||||
// Load the standard apparel graphic
|
||||
Graphic graphic = GraphicDatabase.Get<Graphic_Multi>(path, shader, apparel.def.graphicData.drawSize, apparel.DrawColor);
|
||||
|
||||
// This graphic may need to be masked if the apparel sits on the skin layer and does not cover the legs
|
||||
if (apparel.def.apparel.LastLayer == ApparelLayerDefOf.OnSkin && apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) && !apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs))
|
||||
{
|
||||
Graphic graphicWithApparelMask = GraphicDatabase.Get<Graphic_Multi>(path, ShaderDatabase.CutoutComplex, apparel.def.graphicData.drawSize, apparel.DrawColor, apparel.DrawColor, null, "Masks/apparel_shirt_mask_" + bodyType.defName);
|
||||
graphic = GraphicMaskingUtility.ApplyGraphicWithMasks(graphic, graphicWithApparelMask, true);
|
||||
//Log.Message("Applying apparel mask: Masks/apparel_shirt_mask_" + bodyType.defName + " to " + apparel.def.defName);
|
||||
}
|
||||
|
||||
rec = new ApparelGraphicRecord(graphic, apparel);
|
||||
__result = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue