mirror of
https://gitgud.io/AbstractConcept/rimworld-animations-patch.git
synced 2024-08-15 00:43:27 +00:00
First commit
This commit is contained in:
parent
ddda70a258
commit
8e6918ae70
95 changed files with 20766 additions and 1 deletions
170
Source/Scripts/Utilities/AnimationPatchUtility.cs
Normal file
170
Source/Scripts/Utilities/AnimationPatchUtility.cs
Normal file
|
@ -0,0 +1,170 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using HarmonyLib;
|
||||
using Verse;
|
||||
using Rimworld_Animations;
|
||||
using rjw;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class AnimationPatchUtility
|
||||
{
|
||||
public static int FindTrueAnimationLength(Pawn pawn, out int orgasmTick)
|
||||
{
|
||||
orgasmTick = int.MaxValue;
|
||||
|
||||
ActorAnimationData actorAnimationData = pawn.GetAnimationData();
|
||||
CompBodyAnimator compBodyAnimator = pawn.TryGetComp<CompBodyAnimator>();
|
||||
|
||||
// No data
|
||||
if (actorAnimationData == null || compBodyAnimator == null)
|
||||
{
|
||||
DebugMode.Message("There is no actor animation data for " + pawn.NameShortColored);
|
||||
orgasmTick = 1500 + (int)(Rand.Value * 1000f);
|
||||
|
||||
return orgasmTick;
|
||||
}
|
||||
|
||||
AnimationDef anim = actorAnimationData.animationDef;
|
||||
int actorId = actorAnimationData.actorID;
|
||||
bool isQuickie = compBodyAnimator.fastAnimForQuickie;
|
||||
|
||||
int ticks = 0;
|
||||
|
||||
foreach (AnimationStage animStage in anim.animationStages)
|
||||
{
|
||||
// Legacy: skip the first stage of quickies if there's no playTimeTicksQuick values declared
|
||||
if (anim.animationStages.IndexOf(animStage) == 0 && isQuickie && anim.animationStages.Any(x => x.playTimeTicksQuick >= 0) == false)
|
||||
{ continue; }
|
||||
|
||||
int curr_tick = 0;
|
||||
|
||||
foreach (PawnKeyframe keyframe in (animStage.animationClips[actorId] as PawnAnimationClip).keyframes)
|
||||
{
|
||||
curr_tick += keyframe.tickDuration;
|
||||
|
||||
if (keyframe.soundEffect != null && keyframe.soundEffect == "Cum" && orgasmTick > (ticks + curr_tick))
|
||||
{ orgasmTick = ticks + curr_tick; }
|
||||
|
||||
if (isQuickie && animStage.playTimeTicksQuick > 0 && curr_tick >= animStage.playTimeTicksQuick)
|
||||
{ break; }
|
||||
}
|
||||
|
||||
ticks += isQuickie && animStage.playTimeTicksQuick > 0 && animStage.playTimeTicksQuick < animStage.playTimeTicks ? animStage.playTimeTicksQuick : animStage.playTimeTicks;
|
||||
}
|
||||
|
||||
// Orgasm tick not found
|
||||
if (orgasmTick > ticks)
|
||||
{
|
||||
// Safeguard for penial, vaginal and anal sex
|
||||
if (anim.actors[actorId].isFucked || anim.actors[actorId].isFucking || anim.actors[actorId].requiredGenitals.Any(x => x.ToLower().ContainsAny("penis", "vagina", "anus")))
|
||||
{ orgasmTick = Mathf.Clamp(ticks - 5, 0, int.MaxValue); }
|
||||
|
||||
// Actor does not orgasm
|
||||
else
|
||||
{ orgasmTick = (int)(ticks * (2f + Rand.Value)); }
|
||||
}
|
||||
|
||||
return ticks;
|
||||
|
||||
}
|
||||
|
||||
// Extended version of PawnHeadRotInAnimation (prevents pawn hair from getting messed up when draw in portraits)
|
||||
public static Rot4 PawnHeadRotInAnimation(Pawn pawn, Rot4 regularPos, PawnRenderFlags renderFlags)
|
||||
{
|
||||
if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawn?.TryGetComp<CompBodyAnimator>() != null && pawn.TryGetComp<CompBodyAnimator>().isAnimating)
|
||||
{
|
||||
return pawn.TryGetComp<CompBodyAnimator>().headFacing;
|
||||
}
|
||||
|
||||
return regularPos;
|
||||
}
|
||||
|
||||
public static BodyPartRecord GetBodyPartRecord(Pawn pawn, string bodyPart)
|
||||
{
|
||||
if (bodyPart.NullOrEmpty())
|
||||
{ return null; }
|
||||
|
||||
return pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null).FirstOrDefault(x => x.untranslatedCustomLabel == bodyPart || x.def.defName == bodyPart);
|
||||
}
|
||||
|
||||
public static Vector3 GetAnchorPosition(Pawn pawn, Thing thing = null)
|
||||
{
|
||||
Vector3 anchor;
|
||||
|
||||
if (thing == null)
|
||||
{ return pawn.Position.ToVector3Shifted(); }
|
||||
|
||||
int numOfSleepingSlots = 0;
|
||||
|
||||
if (thing is Building_Bed)
|
||||
{ numOfSleepingSlots = BedUtility.GetSleepingSlotsCount(thing.def.size); }
|
||||
|
||||
// Anchor to the pawn's sleeping slot when masturbating in own bed
|
||||
if (thing is Building_Bed && (pawn.ownership.OwnedBed == thing || pawn.CurrentBed() == thing) && pawn.IsMasturbating())
|
||||
{
|
||||
anchor = RestUtility.GetBedSleepingSlotPosFor(pawn, thing as Building_Bed).ToVector3();
|
||||
|
||||
if (thing.Rotation.AsInt == 0)
|
||||
{
|
||||
anchor.x += 0.5f;
|
||||
anchor.z += 1f;
|
||||
}
|
||||
else if (thing.Rotation.AsInt == 1)
|
||||
{
|
||||
anchor.x += 1f;
|
||||
anchor.z += 0.5f;
|
||||
}
|
||||
else if (thing.Rotation.AsInt == 2)
|
||||
{
|
||||
anchor.x += 0.5f;
|
||||
anchor.z += 0.5f;
|
||||
}
|
||||
else if (thing.Rotation.AsInt == 3)
|
||||
{
|
||||
anchor.x += 0f;
|
||||
anchor.z += 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
// Anchor to the center of the bed (should work for beds of any size?)
|
||||
else if (thing is Building_Bed && numOfSleepingSlots > 0)
|
||||
{
|
||||
anchor = thing.Position.ToVector3();
|
||||
float halfSlots = numOfSleepingSlots / 2f;
|
||||
|
||||
if (thing.Rotation.AsInt == 0)
|
||||
{
|
||||
anchor.x += halfSlots;
|
||||
anchor.z += 1f;
|
||||
}
|
||||
else if (thing.Rotation.AsInt == 1)
|
||||
{
|
||||
anchor.x += 1f;
|
||||
anchor.z += (1.0f - halfSlots);
|
||||
}
|
||||
else if (thing.Rotation.AsInt == 2)
|
||||
{
|
||||
anchor.x += (1f - halfSlots);
|
||||
anchor.z += 0f;
|
||||
}
|
||||
else if (thing.Rotation.AsInt == 3)
|
||||
{
|
||||
anchor.x += 0f;
|
||||
anchor.z += halfSlots;
|
||||
}
|
||||
}
|
||||
|
||||
// Anchor to the centre of the thing
|
||||
else
|
||||
{
|
||||
anchor = thing.Position.ToVector3Shifted();
|
||||
}
|
||||
|
||||
return anchor;
|
||||
}
|
||||
}
|
||||
}
|
197
Source/Scripts/Utilities/ApparelAnimationUtility.cs
Normal file
197
Source/Scripts/Utilities/ApparelAnimationUtility.cs
Normal file
|
@ -0,0 +1,197 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
using Rimworld_Animations;
|
||||
using UnityEngine;
|
||||
using AlienRace;
|
||||
using rjw;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class ApparelAnimationUtility
|
||||
{
|
||||
public static float apparelScale = 0.75f;
|
||||
|
||||
public static void TryToDrawApparelOnFloor(Pawn pawn)
|
||||
{
|
||||
if (pawn?.apparel?.WornApparel != null)
|
||||
{
|
||||
CompBodyAnimator compBodyAnimator = pawn.TryGetComp<CompBodyAnimator>();
|
||||
|
||||
if (ApparelSettings.clothesThrownOnGround == false || Find.CurrentMap != pawn.Map || compBodyAnimator == null || compBodyAnimator.isAnimating == false)
|
||||
{ return; }
|
||||
|
||||
foreach (Apparel apparel in pawn.apparel.WornApparel)
|
||||
{
|
||||
CompApparelVisibility compApparelVisibility = apparel.TryGetComp<CompApparelVisibility>();
|
||||
|
||||
if (compApparelVisibility != null && compApparelVisibility.position != default && compApparelVisibility.isBeingWorn == false)
|
||||
{
|
||||
Graphic apparelGraphic = apparel.Graphic;
|
||||
apparelGraphic.drawSize.x *= apparelScale;
|
||||
apparelGraphic.drawSize.y *= apparelScale;
|
||||
|
||||
GenDraw.DrawMeshNowOrLater(mesh: apparelGraphic.MeshAt(rot: apparel.Rotation),
|
||||
loc: compApparelVisibility.position,
|
||||
quat: Quaternion.AngleAxis(angle: compApparelVisibility.rotation, axis: Vector3.up),
|
||||
mat: apparelGraphic.MatAt(rot: apparel.Rotation),
|
||||
false);
|
||||
|
||||
apparelGraphic.drawSize.x *= 1f / apparelScale;
|
||||
apparelGraphic.drawSize.y *= 1f / apparelScale;
|
||||
DebugMode.Message(compApparelVisibility.rotation.ToString());
|
||||
//DebugMode.Message("Drawing " + apparel.def.defName + " on ground");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool BodyAddonCoveredByApparel(Apparel apparel, AlienPartGenerator.BodyAddon bodyAddon)
|
||||
{
|
||||
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
|
||||
|
||||
if (comp != null && comp.isBeingWorn == false)
|
||||
{ return false; }
|
||||
|
||||
RimNudeData rimNudeData = ApparelSettings.GetRimNudeData(apparel);
|
||||
|
||||
if (rimNudeData != null && bodyAddon?.bodyPart != null)
|
||||
{
|
||||
if (bodyAddon.bodyPart == "Genitals" && rimNudeData.coversGroin == false)
|
||||
{ return false; }
|
||||
|
||||
if (bodyAddon.bodyPart == "Chest" && rimNudeData.coversChest == false)
|
||||
{ return false; }
|
||||
|
||||
if (bodyAddon.bodyPart == "Torso" && rimNudeData.coversBelly == false)
|
||||
{ return false; }
|
||||
}
|
||||
|
||||
if (apparel.def.apparel.bodyPartGroups.Any(x => bodyAddon.hiddenUnderApparelFor.Contains(x)) ||
|
||||
apparel.def.apparel.tags.Any(x => bodyAddon.hiddenUnderApparelTag.Contains(x)))
|
||||
{ return true; }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool BodyPartCoveredByApparel(Apparel apparel, BodyPartRecord bodyPart)
|
||||
{
|
||||
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
|
||||
|
||||
if (comp != null && comp.isBeingWorn == false)
|
||||
{ return false; }
|
||||
|
||||
RimNudeData rimNudeData = ApparelSettings.GetRimNudeData(apparel);
|
||||
|
||||
if (rimNudeData != null)
|
||||
{
|
||||
if (bodyPart.def.defName == "Genitals" && rimNudeData.coversGroin == false)
|
||||
{ return false; }
|
||||
|
||||
if (bodyPart.def.defName == "Chest" && rimNudeData.coversChest == false)
|
||||
{ return false; }
|
||||
|
||||
if (bodyPart.def.defName == "Torso" && rimNudeData.coversBelly == false)
|
||||
{ return false; }
|
||||
}
|
||||
|
||||
if (apparel.def.apparel.CoversBodyPart(bodyPart))
|
||||
{ return true; }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void DetermineApparelToKeepOn(Pawn pawn)
|
||||
{
|
||||
JobDriver_Sex jobdriver = pawn.jobs.curDriver as JobDriver_Sex;
|
||||
|
||||
if (pawn.RaceProps.Humanlike == false || pawn?.apparel?.WornApparel == null || jobdriver == null)
|
||||
{ return; }
|
||||
|
||||
foreach (Apparel apparel in pawn.apparel?.WornApparel)
|
||||
{
|
||||
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
|
||||
if (comp != null)
|
||||
{ comp.isBeingWorn = true; }
|
||||
}
|
||||
|
||||
ActorAnimationData animData = pawn.GetAnimationData();
|
||||
|
||||
if (animData == null)
|
||||
{ return; }
|
||||
|
||||
AnimationDef anim = animData.animationDef;
|
||||
int actorID = animData.actorID;
|
||||
|
||||
var clothingPreference = pawn.IsInBed(out Building bed) ? RJWPreferenceSettings.sex_wear : ApparelSettings.apparelWornForQuickies;
|
||||
|
||||
if (xxx.has_quirk(pawn, "Endytophile"))
|
||||
{ clothingPreference = RJWPreferenceSettings.Clothing.Clothed; }
|
||||
|
||||
// Determine any obstructing apparel that must be removed
|
||||
foreach (Apparel apparel in pawn.apparel.WornApparel)
|
||||
{
|
||||
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
|
||||
|
||||
if (comp == null)
|
||||
{ continue; }
|
||||
|
||||
if (apparel.def is bondage_gear_def)
|
||||
{ continue; }
|
||||
|
||||
if (ApparelSettings.GetRimNudeData(apparel) != null && ApparelSettings.GetRimNudeData(apparel).sexWear)
|
||||
{ continue; }
|
||||
|
||||
if (clothingPreference == RJWPreferenceSettings.Clothing.Nude)
|
||||
{
|
||||
comp.isBeingWorn = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isHat = apparel.def.apparel.bodyPartGroups.NullOrEmpty() == false && (apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.FullHead) || apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.UpperHead));
|
||||
|
||||
if (clothingPreference == RJWPreferenceSettings.Clothing.Headgear && isHat == false)
|
||||
{
|
||||
comp.isBeingWorn = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ApparelCoversPawnRequiredBodyParts(pawn, apparel, anim, actorID))
|
||||
{
|
||||
comp.isBeingWorn = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ApparelCoversPawnRequiredBodyParts(Pawn pawn, Apparel apparel, AnimationDef anim, int actorID)
|
||||
{
|
||||
bool bodyPartCovered = false;
|
||||
|
||||
IEnumerable<BodyPartRecord> bodyParts = pawn.RaceProps.body.AllParts;
|
||||
|
||||
var requiredGenitals = anim.actors[actorID].requiredGenitals;
|
||||
|
||||
if (requiredGenitals.NullOrEmpty())
|
||||
{ requiredGenitals = new List<string>(); }
|
||||
|
||||
if (anim.actors[actorID].isFucking || requiredGenitals.Contains("Penis"))
|
||||
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.genitalsDef)); }
|
||||
|
||||
if (anim.actors[actorID].isFucked || requiredGenitals.Contains("Vagina"))
|
||||
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.genitalsDef)); }
|
||||
|
||||
if (anim.actors[actorID].isFucked || requiredGenitals.Contains("Anus"))
|
||||
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.anusDef)); }
|
||||
|
||||
if (requiredGenitals.Contains("Breasts"))
|
||||
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.breastsDef)); }
|
||||
|
||||
if (requiredGenitals.Contains("Mouth"))
|
||||
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def.defName.ToLower().ContainsAny("mouth", "teeth", "jaw", "beak"))); }
|
||||
|
||||
return bodyPartCovered;
|
||||
}
|
||||
}
|
||||
}
|
199
Source/Scripts/Utilities/ApparelSettingsUtility.cs
Normal file
199
Source/Scripts/Utilities/ApparelSettingsUtility.cs
Normal file
|
@ -0,0 +1,199 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class ApparelSettingsUtility
|
||||
{
|
||||
public static List<ThingDef> GetApparelOfInterest()
|
||||
{
|
||||
List<ThingDef> thingDefs = new List<ThingDef>();
|
||||
|
||||
foreach (ThingDef thingDef in DefDatabase<ThingDef>.AllDefs)
|
||||
{
|
||||
if (thingDef.IsApparel && thingDef.apparel.layers.Count == 1 && thingDef.apparel.layers[0] == ApparelLayerDefOf.Belt)
|
||||
{ continue; }
|
||||
|
||||
if (thingDef.IsApparel &&
|
||||
(thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) ||
|
||||
thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs) ||
|
||||
thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.GenitalsBPG) ||
|
||||
thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.ChestBPG)))
|
||||
{ thingDefs.Add(thingDef); }
|
||||
}
|
||||
|
||||
return thingDefs;
|
||||
}
|
||||
|
||||
// Resets all data
|
||||
public static void ResetRimNudeData(List<RimNudeData> rimNudeData)
|
||||
{
|
||||
rimNudeData.Clear();
|
||||
List<ThingDef> thingDefs = GetApparelOfInterest();
|
||||
|
||||
foreach (ThingDef thingDef in thingDefs)
|
||||
{
|
||||
for (int i = 0; i < 5; i++)
|
||||
{ rimNudeData.Add(new RimNudeData(thingDef)); }
|
||||
}
|
||||
|
||||
GetApparelDefaults(rimNudeData);
|
||||
}
|
||||
|
||||
// Update apparel data
|
||||
public static void UpdateRimNudeData(List<RimNudeData> rimNudeData, string thingDef, bool coversGroin, bool coversBelly, bool coversChest, bool sexWear)
|
||||
{
|
||||
for (int i = 0; i < rimNudeData.Count; i++)
|
||||
{
|
||||
RimNudeData apparelData = rimNudeData[i];
|
||||
|
||||
if (apparelData.thingDef == thingDef)
|
||||
{
|
||||
rimNudeData[i] = new RimNudeData(thingDef, coversGroin, coversBelly, coversChest, sexWear);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetAllCoversGroin(List<RimNudeData> rimNudeData, bool value)
|
||||
{
|
||||
foreach (RimNudeData rimNudeApparel in rimNudeData)
|
||||
{ rimNudeApparel.coversGroin = value; }
|
||||
}
|
||||
|
||||
public static void SetAllCoversBelly(List<RimNudeData> rimNudeData, bool value)
|
||||
{
|
||||
foreach (RimNudeData rimNudeApparel in rimNudeData)
|
||||
{ rimNudeApparel.coversBelly = value; }
|
||||
}
|
||||
|
||||
public static void SetAllCoversChest(List<RimNudeData> rimNudeData, bool value)
|
||||
{
|
||||
foreach (RimNudeData rimNudeApparel in rimNudeData)
|
||||
{ rimNudeApparel.coversChest = value; }
|
||||
}
|
||||
|
||||
public static void SetAllSexWear(List<RimNudeData> rimNudeData, bool value)
|
||||
{
|
||||
foreach (RimNudeData rimNudeApparel in rimNudeData)
|
||||
{ rimNudeApparel.sexWear = value; }
|
||||
}
|
||||
|
||||
public static void GetApparelDefaults(List<RimNudeData> rimNudeData)
|
||||
{
|
||||
//Apparel_BasicShirt
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_BasicShirt", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
|
||||
|
||||
//Apparel_CollarShirt
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_CollarShirt", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
|
||||
|
||||
//Apparel_FlakVest
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_FlakVest", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
|
||||
|
||||
//Apparel_Duster
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_Duster", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//Apparel_Jacket
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_Jacket", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//Apparel_TribalA
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_TribalA", coversGroin: true, coversBelly: true, coversChest: true, sexWear: false);
|
||||
|
||||
//Apparel_BodyStrap
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_BodyStrap", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//Apparel_PsyfocusRobe
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_PsyfocusRobe", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//Apparel_Cape
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_Cape", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
|
||||
|
||||
//Apparel_RobeRoyal
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_RobeRoyal", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//Apparel_Corset
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_Corset", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
|
||||
|
||||
//VAE_Apparel_Overalls
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_Overalls", coversGroin: true, coversBelly: true, coversChest: false, sexWear: false);
|
||||
|
||||
//VAE_Apparel_LabCoat
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_LabCoat", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//VAE_Apparel_BuildersJacket
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_BuildersJacket", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//VAE_Apparel_Apron
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_Apron", coversGroin: true, coversBelly: true, coversChest: false, sexWear: false);
|
||||
|
||||
//VAE_Apparel_Tunic
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_Tunic", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
|
||||
|
||||
//VAE_Apparel_PeltCoat
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_PeltCoat", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//VAE_Apparel_WoodenArmor
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_WoodenArmor", coversGroin: false, coversBelly: true, coversChest: false, sexWear: false);
|
||||
|
||||
//VAE_Apparel_AdvancedVest
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_AdvancedVest", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
|
||||
|
||||
//VAE_Apparel_BulletproofVest
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_BulletproofVest", coversGroin: false, coversBelly: true, coversChest: false, sexWear: false);
|
||||
|
||||
//VWE_Apparel_Exoframe
|
||||
UpdateRimNudeData(rimNudeData, "VWE_Apparel_Exoframe", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
|
||||
|
||||
//VFEM_Apparel_Tabard
|
||||
UpdateRimNudeData(rimNudeData, "VFEM_Apparel_Tabard", coversGroin: true, coversBelly: true, coversChest: true, sexWear: false);
|
||||
|
||||
//VFEV_Apparel_JarlCape
|
||||
UpdateRimNudeData(rimNudeData, "VFEV_Apparel_JarlCape", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//VFEV_Apparel_RoyalFurCoat
|
||||
UpdateRimNudeData(rimNudeData, "VFEV_Apparel_RoyalFurCoat", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//PrisonerChains
|
||||
UpdateRimNudeData(rimNudeData, "PrisonerChains", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_ChainHarnessA
|
||||
UpdateRimNudeData(rimNudeData, "S16_ChainHarnessA", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_NippleWristCuffs
|
||||
UpdateRimNudeData(rimNudeData, "S16_NippleWristCuffs", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_ServantGirlDress
|
||||
UpdateRimNudeData(rimNudeData, "S16_ServantGirlDress", coversGroin: true, coversBelly: true, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_ZDress
|
||||
UpdateRimNudeData(rimNudeData, "S16_ZDress", coversGroin: false, coversBelly: true, coversChest: true, sexWear: true);
|
||||
|
||||
//S16_MaidA
|
||||
UpdateRimNudeData(rimNudeData, "S16_MaidA", coversGroin: false, coversBelly: true, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_DiscoTop
|
||||
UpdateRimNudeData(rimNudeData, "S16_DiscoTop", coversGroin: false, coversBelly: false, coversChest: true, sexWear: true);
|
||||
|
||||
//S16_TransparentSkirt
|
||||
UpdateRimNudeData(rimNudeData, "S16_TransparentSkirt", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_Vibrator
|
||||
UpdateRimNudeData(rimNudeData, "S16_Vibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_VibratorDouble
|
||||
UpdateRimNudeData(rimNudeData, "S16_VibratorDouble", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_WiredVibrator
|
||||
UpdateRimNudeData(rimNudeData, "S16_WiredVibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_DoubleWiredVibrator
|
||||
UpdateRimNudeData(rimNudeData, "S16_DoubleWiredVibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_WiredAnalVibrator
|
||||
UpdateRimNudeData(rimNudeData, "S16_WiredAnalVibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
}
|
||||
}
|
||||
}
|
16
Source/Scripts/Utilities/DebugMode.cs
Normal file
16
Source/Scripts/Utilities/DebugMode.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class DebugMode
|
||||
{
|
||||
public static void Message(string text)
|
||||
{
|
||||
if (BasicSettings.debugMode)
|
||||
{ Log.Message("[DEBUG] " + text); }
|
||||
}
|
||||
}
|
||||
}
|
144
Source/Scripts/Utilities/GraphicMaskingUtility.cs
Normal file
144
Source/Scripts/Utilities/GraphicMaskingUtility.cs
Normal file
|
@ -0,0 +1,144 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class GraphicMaskingUtility
|
||||
{
|
||||
public static Texture2D GetReadableTexture2D(Texture2D source, int newWidth, int newHeight, Material mat = null) //rescales texture to newWidth and newHeight
|
||||
{
|
||||
source.filterMode = FilterMode.Trilinear;
|
||||
|
||||
RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight);
|
||||
rt.filterMode = FilterMode.Trilinear;
|
||||
|
||||
RenderTexture.active = rt;
|
||||
if (mat != null)
|
||||
{ Graphics.Blit(source, rt, mat); }
|
||||
|
||||
else
|
||||
{ Graphics.Blit(source, rt); }
|
||||
|
||||
Texture2D nTex = new Texture2D(newWidth, newHeight, TextureFormat.RGBA32, mipChain: true);
|
||||
nTex.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0);
|
||||
nTex.name = source.name;
|
||||
nTex.filterMode = FilterMode.Trilinear;
|
||||
nTex.anisoLevel = 2;
|
||||
nTex.Apply(updateMipmaps: true);
|
||||
|
||||
GL.Clear(true, true, Color.clear);
|
||||
RenderTexture.active = null;
|
||||
RenderTexture.ReleaseTemporary(rt);
|
||||
|
||||
return nTex;
|
||||
}
|
||||
|
||||
public static Texture2D GetReadableTexture2D(Texture2D source, Material mat = null)
|
||||
{
|
||||
return GetReadableTexture2D(source, source.width, source.height, mat);
|
||||
}
|
||||
|
||||
public static Texture2D ApplyMaskToTexture2D(Texture2D mainTex, Texture2D maskTex, bool writeOverMainTex)
|
||||
{
|
||||
if (mainTex == null || maskTex == null)
|
||||
{
|
||||
DebugMode.Message("mainTex or maskTex is missing!");
|
||||
return mainTex;
|
||||
}
|
||||
|
||||
Color[] mainArray = GetReadableTexture2D(mainTex).GetPixels();
|
||||
Color[] maskArray = GetReadableTexture2D(maskTex, mainTex.width, mainTex.height).GetPixels();
|
||||
|
||||
for (int j = 0; j < mainArray.Length; j++)
|
||||
{
|
||||
if (maskArray[j] == Color.white)
|
||||
{ /*null*/ }
|
||||
|
||||
else if (maskArray[j].a == 0)
|
||||
{ mainArray[j].a = 0; }
|
||||
|
||||
else if (mainArray[j].a > 0 && maskArray[j].a > 0 && writeOverMainTex)
|
||||
{ mainArray[j] = new Color(Mathf.Min(mainArray[j].r, maskArray[j].r), Mathf.Min(mainArray[j].g, maskArray[j].g), Mathf.Min(mainArray[j].b, maskArray[j].b), Mathf.Min(mainArray[j].a, maskArray[j].a)); }
|
||||
}
|
||||
|
||||
Texture2D newTex = new Texture2D(mainTex.width, mainTex.height, TextureFormat.RGBA32, mipChain: true);
|
||||
newTex.SetPixels(mainArray);
|
||||
newTex.filterMode = FilterMode.Trilinear;
|
||||
newTex.anisoLevel = 2;
|
||||
newTex.Apply(updateMipmaps: true);
|
||||
|
||||
return newTex;
|
||||
}
|
||||
|
||||
public static Graphic ApplyGraphicWithMasks(Graphic graphic, Graphic graphicWithMask, bool writeOverMainTex)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Texture2D mainTex = (Texture2D)graphic.MatAt(new Rot4(i)).mainTexture;
|
||||
Texture2D maskTex = graphicWithMask.MatAt(new Rot4(i)).GetMaskTexture();
|
||||
graphic.MatAt(new Rot4(i)).mainTexture = ApplyMaskToTexture2D(mainTex, maskTex, writeOverMainTex);
|
||||
}
|
||||
|
||||
return graphic;
|
||||
}
|
||||
|
||||
public static Graphic ApplyGraphicWithMasks(Graphic graphic, string mask, bool writeOverMainTex)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Texture2D mainTex = (Texture2D)graphic.MatAt(new Rot4(i)).mainTexture;
|
||||
|
||||
if (mainTex == null)
|
||||
{ DebugMode.Message("Main Texture2D not found for " + graphic.path + ". Rotation: " + i.ToString()); continue; }
|
||||
|
||||
string suffix = string.Empty;
|
||||
switch (i)
|
||||
{
|
||||
case 0: suffix = "_north"; break;
|
||||
case 1: suffix = "_east"; break;
|
||||
case 2: suffix = "_south"; break;
|
||||
case 3: suffix = "_west"; break;
|
||||
}
|
||||
|
||||
Texture2D maskTex = ContentFinder<Texture2D>.Get(mask + suffix, false);
|
||||
|
||||
if (maskTex == null)
|
||||
{ DebugMode.Message("Mask Texture2D not found for " + mask + ". Rotation: " + i.ToString()); continue; }
|
||||
|
||||
graphic.MatAt(new Rot4(i)).mainTexture = ApplyMaskToTexture2D(mainTex, maskTex, writeOverMainTex);
|
||||
}
|
||||
|
||||
return graphic;
|
||||
}
|
||||
|
||||
public static void ResetGraphic(Graphic graphic)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
string suffix = string.Empty;
|
||||
switch (i)
|
||||
{
|
||||
case 0: suffix = "_north"; break;
|
||||
case 1: suffix = "_east"; break;
|
||||
case 2: suffix = "_south"; break;
|
||||
case 3: suffix = "_west"; break;
|
||||
}
|
||||
|
||||
Texture2D texture2D = ContentFinder<Texture2D>.Get(graphic.path + suffix, false);
|
||||
|
||||
if (texture2D == null && i == 3)
|
||||
{ texture2D = ContentFinder<Texture2D>.Get(graphic.path + "_east", false); }
|
||||
|
||||
if (texture2D == null)
|
||||
{ texture2D = ContentFinder<Texture2D>.Get(graphic.path + "_north", false); }
|
||||
|
||||
if (texture2D != null)
|
||||
{ graphic.MatAt(new Rot4(i)).mainTexture = texture2D; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
219
Source/Scripts/Utilities/HandAnimationUtility.cs
Normal file
219
Source/Scripts/Utilities/HandAnimationUtility.cs
Normal file
|
@ -0,0 +1,219 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
using HarmonyLib;
|
||||
using Rimworld_Animations;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class HandAnimationUtility
|
||||
{
|
||||
public static BodyPartDef handDef;
|
||||
|
||||
public static bool BodyPartIsBeingTouched(Pawn pawn, string bodypartFilePath, out List<HandAnimationData> handAnimationData)
|
||||
{
|
||||
handAnimationData = new List<HandAnimationData>();
|
||||
|
||||
ActorAnimationData actorAnimationData = pawn.GetAnimationData();
|
||||
HandAnimationDef handAnimationDef = DefDatabase<HandAnimationDef>.AllDefs.FirstOrDefault(x => x.animationDefName == actorAnimationData.animationDef.defName);
|
||||
|
||||
if (handAnimationDef == null)
|
||||
{ return false; }
|
||||
|
||||
foreach (HandAnimationData datum in handAnimationDef.handAnimationData)
|
||||
{
|
||||
if (datum.stageID != actorAnimationData.currentStage || datum.actorID != actorAnimationData.actorID)
|
||||
{ continue; }
|
||||
|
||||
if (datum.bodySide.NullOrEmpty() == false && bodypartFilePath.ToLower().Contains(datum.bodySide) == false)
|
||||
{ continue; }
|
||||
|
||||
if (datum.targetBodyPart.NullOrEmpty() == false && bodypartFilePath.ToLower().Contains(datum.targetBodyPart.ToLower()))
|
||||
{ handAnimationData.Add(datum); }
|
||||
|
||||
else if (datum.targetBodyParts.Any(x => bodypartFilePath.ToLower().Contains(x.ToLower())))
|
||||
{ handAnimationData.Add(datum); }
|
||||
}
|
||||
|
||||
return handAnimationData.NullOrEmpty() == false;
|
||||
}
|
||||
|
||||
public static Vector3 GetHandPosition(Pawn pawn, HandAnimationData handAnimationData, Vector3 basePosition, float baseAngle)
|
||||
{
|
||||
var methodInfo = AccessTools.Method(typeof(HandAnimationUtility), handAnimationData.motion, null, null);
|
||||
|
||||
if (methodInfo == null)
|
||||
{
|
||||
Debug.LogWarning("Hand anaimation motion '" + handAnimationData.motion + "' was not found");
|
||||
return default;
|
||||
}
|
||||
|
||||
Vector3 handPosition = (Vector3)methodInfo.Invoke(null, new object[] { pawn, handAnimationData, baseAngle });
|
||||
|
||||
return handPosition * pawn.RaceProps.baseBodySize + basePosition;
|
||||
}
|
||||
|
||||
public static float GetGenitalSize(Pawn pawn, string genitalName)
|
||||
{
|
||||
switch(genitalName.ToLower())
|
||||
{
|
||||
case "penis": return pawn.health.hediffSet.hediffs.First(x => x.def.defName.ToLower().Contains("penis")).Severity;
|
||||
case "breasts": return pawn.health.hediffSet.hediffs.First(x => x.def.defName.ToLower().Contains("breasts")).Severity;
|
||||
case "vagina": return 0.1f;
|
||||
case "anus": return 0.1f;
|
||||
}
|
||||
|
||||
return 0.1f;
|
||||
}
|
||||
|
||||
public static Vector3 Motion_StrokeGenitalsUpAndDownShort_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
|
||||
{
|
||||
Vector3 handPosition = new Vector3();
|
||||
ActorAnimationData data = pawn.GetAnimationData();
|
||||
|
||||
float p = (Mathf.PingPong(data.stageTicks, handAnimationData.cycleTime) / handAnimationData.cycleTime);
|
||||
float length = 0.035f;
|
||||
|
||||
handPosition.x = 0;
|
||||
handPosition.z = length * p;
|
||||
|
||||
handPosition = handPosition.RotatedBy(baseAngle);
|
||||
|
||||
return handPosition;
|
||||
}
|
||||
|
||||
public static Vector3 Motion_StrokeGenitalsUpAndDown_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
|
||||
{
|
||||
Vector3 handPosition = new Vector3();
|
||||
ActorAnimationData data = pawn.GetAnimationData();
|
||||
|
||||
float p = (Mathf.PingPong(data.stageTicks, handAnimationData.cycleTime) / handAnimationData.cycleTime);
|
||||
float size = GetGenitalSize(pawn, handAnimationData.targetBodyPart) * 0.2f;
|
||||
float m = (data.actorFacing == Rot4.North ? -1f : 1f) * (handAnimationData.mirror ? -1f : 1f) * (pawn.TryGetComp<CompBodyAnimator>().Mirror ? -1f : 1f);
|
||||
|
||||
handPosition.x = 0.025f * m;
|
||||
handPosition.z = size * p;
|
||||
|
||||
handPosition = handPosition.RotatedBy(baseAngle);
|
||||
|
||||
return handPosition;
|
||||
}
|
||||
|
||||
public static Vector3 Motion_StrokeGenitalsUpAndDown_FacingEW(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
|
||||
{
|
||||
Vector3 handPosition = new Vector3();
|
||||
ActorAnimationData data = pawn.GetAnimationData();
|
||||
|
||||
float p = Mathf.PingPong(data.stageTicks, handAnimationData.cycleTime) / handAnimationData.cycleTime;
|
||||
float size = GetGenitalSize(pawn, handAnimationData.targetBodyPart) * 0.2f;
|
||||
float m = (data.actorFacing == Rot4.West ? -1f : 1f) * (handAnimationData.mirror ? -1f : 1f);
|
||||
|
||||
handPosition.x = Mathf.Sin(m * (baseAngle + 45f) / 180f * Mathf.PI) * size * p;
|
||||
handPosition.z = Mathf.Cos(m * (baseAngle + 45f) / 180f * Mathf.PI) * size * p;
|
||||
|
||||
return handPosition;
|
||||
}
|
||||
|
||||
public static Vector3 Motion_RubGenitals_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
|
||||
{
|
||||
Vector3 handPosition = new Vector3();
|
||||
ActorAnimationData data = pawn.GetAnimationData();
|
||||
|
||||
float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
|
||||
float m = (data.actorFacing == Rot4.North ? 1f : -1f) * (handAnimationData.mirror ? -1f : 1f) * (pawn.TryGetComp<CompBodyAnimator>().Mirror ? -1f : 1f);
|
||||
|
||||
handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.05f - 0.025f) * m;
|
||||
handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f + 0.03f;
|
||||
|
||||
handPosition = handPosition.RotatedBy(baseAngle);
|
||||
|
||||
return handPosition;
|
||||
}
|
||||
|
||||
public static Vector3 Motion_RubGenitals_FacingEW(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
|
||||
{
|
||||
Vector3 handPosition = new Vector3();
|
||||
ActorAnimationData data = pawn.GetAnimationData();
|
||||
|
||||
float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
|
||||
float m = (data.actorFacing == Rot4.West ? 1f : -1f) * (handAnimationData.mirror ? -1f : 1f);
|
||||
|
||||
handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.005f - 0.05f) * m;
|
||||
handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f;
|
||||
//handPosition.y = -0.1f;
|
||||
|
||||
handPosition = handPosition.RotatedBy(baseAngle);
|
||||
|
||||
return handPosition;
|
||||
}
|
||||
|
||||
public static Vector3 Motion_RubBreasts_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
|
||||
{
|
||||
Vector3 handPosition = new Vector3();
|
||||
ActorAnimationData data = pawn.GetAnimationData();
|
||||
|
||||
float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
|
||||
float m = (data.actorFacing == Rot4.North ? -1f : 1f) * (handAnimationData.mirror ? -1f : 1f) * (pawn.TryGetComp<CompBodyAnimator>().Mirror ? -1f : 1f);
|
||||
float size = GetGenitalSize(pawn, "breasts");
|
||||
|
||||
handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.05f * size - size * 0.25f) * m;
|
||||
handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f - size * 0.125f;
|
||||
|
||||
handPosition = handPosition.RotatedBy(baseAngle);
|
||||
|
||||
return handPosition;
|
||||
}
|
||||
|
||||
public static Vector3 Motion_RubBreasts_FacingEW(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
|
||||
{
|
||||
Vector3 handPosition = new Vector3();
|
||||
ActorAnimationData data = pawn.GetAnimationData();
|
||||
|
||||
float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
|
||||
float m = (data.actorFacing == Rot4.West ? 1f : -1f) * (handAnimationData.mirror ? -1f : 1f);
|
||||
float size = GetGenitalSize(pawn, "breasts");
|
||||
|
||||
handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.005f - size * 0.25f) * m;
|
||||
handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f - size * 0.125f;
|
||||
|
||||
handPosition = handPosition.RotatedBy(baseAngle);
|
||||
|
||||
return handPosition;
|
||||
}
|
||||
|
||||
public static Graphic GetHandGraphic(Pawn touchingPawn, string touchedBodyAddonName, HandAnimationData handAnimationData)
|
||||
{
|
||||
string handGraphicPath = "Hands/HandClean";
|
||||
Color skinColour = touchingPawn.story.SkinColor;
|
||||
float handSize = 0.6667f * touchingPawn.RaceProps.baseBodySize;
|
||||
|
||||
return GraphicDatabase.Get<Graphic_Single>(handGraphicPath, ShaderDatabase.Cutout, new Vector2(handSize, handSize), skinColour);
|
||||
}
|
||||
|
||||
public static bool TryToDrawHand(Pawn pawn, string bodyAddonName, Vector3 bodyAddonPosition, float bodyAddonAngle, Rot4 bodyAddonRotation, PawnRenderFlags renderFlags)
|
||||
{
|
||||
if (pawn.TryGetComp<CompBodyAnimator>() != null && pawn.TryGetComp<CompBodyAnimator>().isAnimating && BodyPartIsBeingTouched(pawn, bodyAddonName, out List<HandAnimationData> handAnimationData))
|
||||
{
|
||||
foreach (HandAnimationData datum in handAnimationData)
|
||||
{
|
||||
Pawn touchingPawn = datum.touchingActorID >= 0 && pawn.GetAllSexParticipants().Count > datum.touchingActorID ? pawn.GetAllSexParticipants()[datum.touchingActorID] : pawn;
|
||||
|
||||
Graphic handgraphic = GetHandGraphic(touchingPawn, bodyAddonName, datum);
|
||||
Vector3 handPosition = GetHandPosition(pawn, datum, bodyAddonPosition, bodyAddonAngle);
|
||||
|
||||
GenDraw.DrawMeshNowOrLater(mesh: handgraphic.MeshAt(rot: bodyAddonRotation),
|
||||
loc: handPosition + new Vector3(0f, 0.022f, 0f),
|
||||
quat: Quaternion.identity,
|
||||
mat: handgraphic.MatAt(rot: bodyAddonRotation), renderFlags.FlagSet(PawnRenderFlags.DrawNow));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
46
Source/Scripts/Utilities/MathUtility.cs
Normal file
46
Source/Scripts/Utilities/MathUtility.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class MathUtility
|
||||
{
|
||||
public static float Repeat(float value, float min, float max)
|
||||
{
|
||||
if (Mathf.Abs(max) < Mathf.Abs(min))
|
||||
{
|
||||
Log.Error("RepeatDual: min value must be greater than max value");
|
||||
return -1;
|
||||
}
|
||||
|
||||
float range = max - min;
|
||||
float m = value % range;
|
||||
|
||||
if (m < 0)
|
||||
{ m = range + m; }
|
||||
|
||||
return min + m;
|
||||
}
|
||||
|
||||
public static IntVec3 FindRandomCellNearPawn(Pawn pawn, int maxRadius)
|
||||
{
|
||||
if (maxRadius > 0)
|
||||
{
|
||||
for (int radius = 1; radius < maxRadius; radius++)
|
||||
{
|
||||
List<IntVec3> cells = GenRadial.RadialCellsAround(pawn.Position, radius + 0.75f, false).Where(x => x.Standable(pawn.Map) && x.GetRoom(pawn.Map) == pawn.GetRoom())?.ToList();
|
||||
|
||||
if (cells.NullOrEmpty() == false && cells.Count > 0)
|
||||
{ return cells.RandomElement(); }
|
||||
}
|
||||
}
|
||||
|
||||
return GenAdj.RandomAdjacentCellCardinal(pawn);
|
||||
}
|
||||
}
|
||||
}
|
14
Source/Scripts/Utilities/PatchDefOf.cs
Normal file
14
Source/Scripts/Utilities/PatchDefOf.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using Verse;
|
||||
using RimWorld;
|
||||
using AlienRace;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[DefOf]
|
||||
public static class PatchBodyPartGroupDefOf
|
||||
{
|
||||
public static BodyPartGroupDef GenitalsBPG;
|
||||
public static BodyPartGroupDef AnusBPG;
|
||||
public static BodyPartGroupDef ChestBPG;
|
||||
}
|
||||
}
|
16
Source/Scripts/Utilities/SettingsUtility.cs
Normal file
16
Source/Scripts/Utilities/SettingsUtility.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class SettingsUtility
|
||||
{
|
||||
public static float Align(float objectDim, float columnDim)
|
||||
{
|
||||
return (columnDim - objectDim) * 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
241
Source/Scripts/Utilities/SexInteractionUtility.cs
Normal file
241
Source/Scripts/Utilities/SexInteractionUtility.cs
Normal file
|
@ -0,0 +1,241 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using Verse.AI.Group;
|
||||
using RimWorld;
|
||||
using rjw;
|
||||
using RJWSexperience.Ideology;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class SexInteractionUtility
|
||||
{
|
||||
public static bool PawnCaughtLovinByWitness(Pawn pawn, Pawn witness)
|
||||
{
|
||||
if (witness == null || pawn == witness || witness.AnimalOrWildMan() || witness.RaceProps.IsMechanoid || witness.Awake() == false || witness.CanSee(pawn) == false)
|
||||
{ return false; }
|
||||
|
||||
if (pawn.IsHavingSex() == false && pawn.IsMasturbating() == false)
|
||||
{ return false; }
|
||||
|
||||
List<Pawn> sexParticipants = pawn.GetAllSexParticipants();
|
||||
bool witnessIsCourtingSexParticipant = witness.jobs.curDriver is JobDriver_SexBaseInitiator && sexParticipants.Contains((witness.jobs.curDriver as JobDriver_SexBaseInitiator).Partner);
|
||||
|
||||
if (sexParticipants.Contains(witness) || witnessIsCourtingSexParticipant)
|
||||
{ return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool PawnIsCheatingOnPartner(Pawn pawn, Pawn partner)
|
||||
{
|
||||
if (BasicSettings.worryAboutInfidelity == false || pawn.IsMasturbating() || pawn.IsHavingSex() == false || pawn.GetAllSexParticipants().Contains(partner))
|
||||
{ return false; }
|
||||
|
||||
if (pawn.GetAllSexParticipants().Any(x => pawn.GetSpouseCount(false) > 0 && pawn.GetSpouses(false).Contains(x)))
|
||||
{ return false; }
|
||||
|
||||
return partner.IsLoverOfOther(pawn) && pawn.HasTrait("Polygamist") == false && partner.HasTrait("Polygamist") == false;
|
||||
}
|
||||
|
||||
public static bool PawnCanInvitePasserbyForSex(Pawn passerby, List<Pawn> participants)
|
||||
{
|
||||
if (passerby == null || participants.NullOrEmpty() || participants.Contains(passerby) || passerby.AnimalOrWildMan() || passerby.RaceProps.IsMechanoid || passerby.Awake() == false || participants.All(x => x.CanSee(passerby) == false))
|
||||
{ return false; }
|
||||
|
||||
if (participants.Any(x => x.IsForbidden(passerby) || x.HostileTo(passerby) || PawnIsCheatingOnPartner(x, passerby)) || CasualSex_Helper.CanHaveSex(passerby) == false || xxx.IsTargetPawnOkay(passerby) == false || participants.Count > 2)
|
||||
{ return false; }
|
||||
|
||||
if (SexUtility.ReadyForHookup(passerby) &&
|
||||
(passerby?.jobs?.curJob == null || (passerby.jobs.curJob.playerForced == false && CasualSex_Helper.quickieAllowedJobs.Contains(passerby.jobs.curJob.def))) &&
|
||||
participants.Any(x => SexAppraiser.would_fuck(x, passerby) > 0.1f && SexAppraiser.would_fuck(passerby, x) > 0.1f) &&
|
||||
participants.All(x => SexAppraiser.would_fuck(x, passerby, false, false, true) > 0.1f && SexAppraiser.would_fuck(passerby, x, false, false, true) > 0.1f))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static TabooStatus CheckSexJobAgainstMorals(Pawn pawn, JobDriver_Sex jobDriver, out Precept precept)
|
||||
{
|
||||
bool sexIsNecro = jobDriver.Partner != null && jobDriver.Partner.Dead;
|
||||
bool sexIsBeastial = jobDriver.Partner != null && jobDriver.Partner.RaceProps.Animal;
|
||||
bool sexIsRape = sexIsBeastial == false && sexIsNecro == false &&
|
||||
(jobDriver is JobDriver_Rape || jobDriver is JobDriver_RapeEnemy || jobDriver is JobDriver_SexBaseRecieverRaped) &&
|
||||
jobDriver.Partner.IsPrisoner == false && jobDriver.Partner.IsSlave == false;
|
||||
bool sexIsSlaveRape = sexIsBeastial == false && sexIsNecro == false &&
|
||||
(jobDriver is JobDriver_Rape || jobDriver is JobDriver_RapeEnemy || jobDriver is JobDriver_SexBaseRecieverRaped) &&
|
||||
(jobDriver.Partner.IsPrisoner || jobDriver.Partner.IsSlave);
|
||||
bool sexIsXeno = jobDriver.Partner != null && jobDriver.Partner.def.defName != jobDriver.pawn.def.defName;
|
||||
|
||||
TabooStatus tabooStatus = TabooStatus.NotTaboo;
|
||||
precept = null;
|
||||
|
||||
if (BasicSettings.worryAboutNecro && sexIsNecro && xxx.is_necrophiliac(pawn) == false)
|
||||
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Necrophilia"), TabooStatus.MajorTaboo, out precept); }
|
||||
|
||||
else if (BasicSettings.worryAboutBeastiality && sexIsBeastial && xxx.is_zoophile(pawn) == false)
|
||||
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Beastility"), TabooStatus.MajorTaboo, out precept); }
|
||||
|
||||
else if (BasicSettings.worryAboutRape && sexIsRape && xxx.is_rapist(pawn) == false)
|
||||
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Rape"), TabooStatus.MajorTaboo, out precept); }
|
||||
|
||||
else if (BasicSettings.worryAboutRape && BasicSettings.ignoreSlaveRape == false && sexIsSlaveRape && xxx.is_rapist(pawn) == false)
|
||||
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Rape"), TabooStatus.MajorTaboo, out precept); }
|
||||
|
||||
else if (BasicSettings.worryAboutXeno && sexIsXeno && pawn.HasTrait("Xenophobia") && pawn.story.traits.DegreeOfTrait(DefDatabase<TraitDef>.GetNamedSilentFail("Xenophobia")) > 0)
|
||||
{ tabooStatus = TabooStatus.MajorTaboo; }
|
||||
|
||||
else if (BasicSettings.worryAboutXeno && sexIsXeno)
|
||||
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("HAR_AlienDating"), TabooStatus.NotTaboo, out precept); }
|
||||
|
||||
//DebugMode.Message("Sex job is: " + jobDriver + " Issue is: " + (precept?.def?.issue?.defName).ToStringSafe() + " Opinion is: " + (precept?.def?.defName).ToStringSafe() + " Judgement is: " + tabooStatus.ToString());
|
||||
|
||||
return tabooStatus;
|
||||
}
|
||||
|
||||
public static TabooStatus GetTabooStatusOfIssue(Pawn pawn, IssueDef issueDef, TabooStatus defaultTabboStatus, out Precept precept)
|
||||
{
|
||||
if (pawn.IssueIsMajorTaboo(issueDef, out precept))
|
||||
{ return TabooStatus.MajorTaboo; }
|
||||
|
||||
if (pawn.IssueIsMinorTaboo(issueDef, out precept))
|
||||
{ return TabooStatus.MinorTaboo; }
|
||||
|
||||
return defaultTabboStatus;
|
||||
}
|
||||
|
||||
public static bool ResolveThoughtsForWhenSexIsWitnessed(Pawn pawn, Pawn witness, out bool witnessJoiningSex)
|
||||
{
|
||||
witnessJoiningSex = false;
|
||||
|
||||
if (pawn.IsAnimal() || pawn.RaceProps.IsMechanoid || pawn.Dead)
|
||||
{ return false; }
|
||||
|
||||
if (witness.IsAnimal() || witness.RaceProps.IsMechanoid || witness.Dead)
|
||||
{ return false; }
|
||||
|
||||
JobDriver_Sex jobDriver = pawn.jobs.curDriver as JobDriver_Sex;
|
||||
|
||||
string pawnThoughtDefName = pawn.IsMasturbating() ? "SeenMasturbating" : "SeenHavingSex";
|
||||
string witnessThoughtDefName = pawn.IsMasturbating() ? "SawMasturbation" : "SawSex";
|
||||
|
||||
bool pawnIsExhibitionist = pawn.HasTrait("Exhibitionist") || xxx.has_quirk(pawn, "Exhibitionist");
|
||||
if (pawnIsExhibitionist)
|
||||
{ pawnThoughtDefName += "Exhibitionist"; }
|
||||
|
||||
bool witnessIsVoyeur = witness.HasTrait("Voyeur") || xxx.has_quirk(witness, "Voyeur");
|
||||
if (witnessIsVoyeur)
|
||||
{ witnessThoughtDefName += "Voyeur"; }
|
||||
|
||||
bool sexIsRitual = pawn.GetLord() != null && pawn.GetLord().LordJob is LordJob_Ritual && witness?.Ideo == pawn?.Ideo;
|
||||
bool pawnIsVictim = pawn.CurJob.def == xxx.gettin_raped || pawn.Dead;
|
||||
bool pawnIsCheating = pawnIsVictim == false && PawnIsCheatingOnPartner(pawn, witness);
|
||||
|
||||
witnessJoiningSex = Random.value < BasicSettings.chanceForOtherToJoinInSex && PawnCanInvitePasserbyForSex(witness, pawn.GetAllSexParticipants());
|
||||
|
||||
// Determine if there are any issues with the witness' morals
|
||||
TabooStatus tabooStatus = CheckSexJobAgainstMorals(witness, jobDriver, out Precept precept);
|
||||
|
||||
if (tabooStatus == TabooStatus.MajorTaboo)
|
||||
{ witnessThoughtDefName = "SawMajorTaboo"; witnessJoiningSex = false; }
|
||||
|
||||
else if (tabooStatus == TabooStatus.MinorTaboo)
|
||||
{ witnessThoughtDefName = "SawTaboo"; witnessJoiningSex = false; }
|
||||
|
||||
else if (pawnIsCheating)
|
||||
{ witnessThoughtDefName = "CheatedOnMe"; witnessJoiningSex = false; }
|
||||
|
||||
else if (BasicSettings.needPrivacy == false)
|
||||
{ witnessThoughtDefName = ""; }
|
||||
|
||||
// Apply thoughts to witness
|
||||
ThoughtDef witnessThoughtDef = DefDatabase<ThoughtDef>.GetNamedSilentFail(witnessThoughtDefName);
|
||||
|
||||
if (witnessThoughtDef != null && pawnIsVictim == false && witnessJoiningSex == false && sexIsRitual == false)
|
||||
{
|
||||
witness.needs.mood.thoughts.memories.TryGainMemory(witnessThoughtDef, pawn, precept);
|
||||
|
||||
if (witnessThoughtDef.stages[0].baseMoodEffect < 0)
|
||||
{ FleckMaker.ThrowMetaIcon(witness.Position, witness.Map, FleckDefOf.IncapIcon); }
|
||||
|
||||
// Fight or flight reaction
|
||||
if (BasicSettings.majorTabooCanStartFights &&
|
||||
(tabooStatus == TabooStatus.MajorTaboo || pawnIsCheating) &&
|
||||
witness.Drafted == false &&
|
||||
witness.jobs.curDriver is JobDriver_Flee == false &&
|
||||
witness.jobs.curDriver is JobDriver_AttackMelee == false &&
|
||||
witness.jobs.curDriver is JobDriver_Vomit == false)
|
||||
{
|
||||
// Fight
|
||||
if (pawn.RaceProps.Humanlike && witness.RaceProps.Humanlike && witness.DislikesViolence() == false && (Random.value < 0.2f || witness.EnjoysViolence()) && witness.HostileTo(pawn) == false && InteractionUtility.TryGetRandomVerbForSocialFight(witness, out Verb verbToUse))
|
||||
{
|
||||
if (witness.LastAttackedTarget.Pawn != pawn || (pawn.mindState.lastAttackTargetTick < 0 && pawn.mindState.lastAttackTargetTick + Find.TickManager.TicksGame > 180))
|
||||
{
|
||||
pawn.mindState.lastAttackTargetTick = Find.TickManager.TicksGame;
|
||||
string message = witness.LabelShort + " is going to punish " + pawn.LabelShort + " for " + GenderUtility.GetPossessive(pawn.gender) + " transgression.";
|
||||
Messages.Message(message, pawn, MessageTypeDefOf.NegativeEvent);
|
||||
}
|
||||
|
||||
Job job = JobMaker.MakeJob(JobDefOf.SocialFight, pawn);
|
||||
job.maxNumMeleeAttacks = 1;
|
||||
job.verbToUse = verbToUse;
|
||||
|
||||
witness.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
|
||||
witness.jobs.StartJob(job);
|
||||
}
|
||||
|
||||
// Vomit
|
||||
else if (jobDriver.Partner != null && jobDriver.Partner.Dead)
|
||||
{
|
||||
Job jobVomit = JobMaker.MakeJob(JobDefOf.Vomit);
|
||||
Job jobFlee = JobMaker.MakeJob(JobDefOf.FleeAndCower, CellFinderLoose.GetFleeDest(witness, new List<Thing>() { pawn }, 24f), pawn);
|
||||
|
||||
witness.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
|
||||
witness.jobs.StartJob(jobVomit);
|
||||
witness.jobs.jobQueue.EnqueueFirst(jobFlee);
|
||||
}
|
||||
|
||||
// Flight
|
||||
else
|
||||
{
|
||||
Job job = JobMaker.MakeJob(JobDefOf.FleeAndCower, CellFinderLoose.GetFleeDest(witness, new List<Thing>() { pawn }, 24f), pawn);
|
||||
witness.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
|
||||
witness.jobs.StartJob(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check issue against pawn precepts
|
||||
tabooStatus = CheckSexJobAgainstMorals(pawn, jobDriver, out precept);
|
||||
|
||||
if (tabooStatus == TabooStatus.MajorTaboo)
|
||||
{ pawnThoughtDefName = "SeenCommittingMajorTaboo"; witnessJoiningSex = false; }
|
||||
|
||||
else if (tabooStatus == TabooStatus.MinorTaboo)
|
||||
{ pawnThoughtDefName = "SeenCommittingTaboo"; witnessJoiningSex = false; }
|
||||
|
||||
else if (pawnIsCheating)
|
||||
{ pawnThoughtDefName = "CaughtCheating"; witnessJoiningSex = false; }
|
||||
|
||||
else if (BasicSettings.needPrivacy == false)
|
||||
{ pawnThoughtDefName = ""; }
|
||||
|
||||
// Apply thoughts to pawn
|
||||
ThoughtDef pawnThoughtDef = DefDatabase<ThoughtDef>.GetNamedSilentFail(pawnThoughtDefName);
|
||||
|
||||
if (pawnThoughtDef != null && pawnIsVictim == false && witnessJoiningSex == false && sexIsRitual == false)
|
||||
{
|
||||
pawn.needs.mood.thoughts.memories.TryGainMemory(pawnThoughtDef, witness, precept);
|
||||
|
||||
if (pawnThoughtDef.stages[0].baseMoodEffect < 0)
|
||||
{ FleckMaker.ThrowMetaIcon(pawn.Position, pawn.Map, FleckDefOf.IncapIcon); }
|
||||
}
|
||||
|
||||
return witnessJoiningSex;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue