First commit

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

Binary file not shown.

View file

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B36224DC-E481-44EF-8279-BF0CBE580D20}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>Rimworld_Animations_Patch</RootNamespace>
<AssemblyName>Rimworld-Animations-Patch</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\1.3\Assemblies\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\..\..\..\..\workshop\content\294100\2009463077\Current\Assemblies\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="AlienRace">
<HintPath>..\..\..\..\..\workshop\content\294100\839005762\1.3\Assemblies\AlienRace.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\..\..\RimWorldWin64_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="BadHygiene">
<HintPath>..\..\..\..\..\workshop\content\294100\836308268\1.3\Assemblies\BadHygiene.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="HugsLib">
<HintPath>..\..\..\..\..\workshop\content\294100\818773962\Assemblies\HugsLib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Patch_SexToysMasturbation">
<HintPath>..\..\rimworld-animations-master\Patch_SexToysMasturbation\1.3\Assemblies\Patch_SexToysMasturbation.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="RimNudeWorld">
<HintPath>..\..\rimnude-unofficial-master\1.3 Assembly\Assemblies\RimNudeWorld.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Rimworld-Animations, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\rimworld-animations-master\1.3\Assemblies\Rimworld-Animations.dll</HintPath>
</Reference>
<Reference Include="RJW, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\rjw-master\1.3\Assemblies\RJW.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="RJW-ToysAndMasturbation">
<HintPath>..\..\rjw-toys-and-masturbation-master\Assemblies\RJW-ToysAndMasturbation.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="RJWSexperience.Ideology">
<HintPath>..\..\rjw-sexperience-ideology-master\1.3\Assemblies\RJWSexperience.Ideology.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="UnityEngine">
<HintPath>..\..\..\RimWorldWin64_Data\Managed\UnityEngine.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\..\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.IMGUIModule">
<HintPath>..\..\..\RimWorldWin64_Data\Managed\UnityEngine.IMGUIModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule">
<HintPath>..\..\..\RimWorldWin64_Data\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Compile Include="Scripts\Comps\CompApparelVisibility.cs" />
<Compile Include="Scripts\Comps\CompProperties_ApparelVisibility.cs" />
<Compile Include="Scripts\Defs\ActorAnimationData.cs" />
<Compile Include="Scripts\Defs\HandAnimationDef.cs" />
<Compile Include="Scripts\Defs\RimNudeData.cs" />
<Compile Include="Scripts\Enums.cs" />
<Compile Include="Scripts\Patches\HarmonyPatch_ApparelGraphicRecordGetter.cs" />
<Compile Include="Scripts\Patches\HarmonyPatch_DrawGUIOverlay.cs" />
<Compile Include="Scripts\Patches\HarmonyPatch_JobDriver.cs" />
<Compile Include="Scripts\Settings\ApparelSettings.cs" />
<Compile Include="Scripts\Utilities\ApparelAnimationUtility.cs" />
<Compile Include="Scripts\Utilities\ApparelSettingsUtility.cs" />
<Compile Include="Scripts\Utilities\DebugMode.cs" />
<Compile Include="Scripts\JobDrivers\JobDriver_JoinInSex.cs" />
<Compile Include="Scripts\Patches\HarmonyPatch_RimNudeWorld.cs" />
<Compile Include="Scripts\Utilities\HandAnimationUtility.cs" />
<Compile Include="Scripts\Utilities\PatchDefOf.cs" />
<Compile Include="Scripts\Utilities\SettingsUtility.cs" />
<Compile Include="Scripts\Utilities\AnimationPatchUtility.cs" />
<Compile Include="Scripts\Extensions\PawnExtension.cs" />
<Compile Include="Scripts\Patches\HarmonyPatch_Rimworld_Animations.cs" />
<Compile Include="Scripts\Patches\HarmonyPatch_CompBodyAnimator.cs" />
<Compile Include="Scripts\Patches\HarmonyPatch_PatchAll.cs" />
<Compile Include="Scripts\Patches\HarmonyPatch_PawnRenderer.cs" />
<Compile Include="Scripts\Patches\HarmonyPatch_RJW.cs" />
<Compile Include="Scripts\Settings\BasicSettings.cs" />
<Compile Include="Scripts\Utilities\GraphicMaskingUtility.cs" />
<Compile Include="Scripts\Utilities\MathUtility.cs" />
<Compile Include="Scripts\Utilities\SexInteractionUtility.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30011.22
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rimworld-Animations-Patch", "Rimworld-Animations-Patch.csproj", "{B36224DC-E481-44EF-8279-BF0CBE580D20}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B36224DC-E481-44EF-8279-BF0CBE580D20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B36224DC-E481-44EF-8279-BF0CBE580D20}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B36224DC-E481-44EF-8279-BF0CBE580D20}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B36224DC-E481-44EF-8279-BF0CBE580D20}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F0E9A1B9-1B2B-423B-8CF0-D0A4121558D4}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using Verse;
using UnityEngine;
using Rimworld_Animations;
using HarmonyLib;
namespace Rimworld_Animations_Patch
{
public class CompApparelVisibility : ThingComp
{
public Apparel apparel => base.parent as Apparel;
public Vector3 position;
public float rotation = 0f;
public bool isBeingWorn = true;
private IntVec3 cellPosition;
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref position, "position", default);
Scribe_Values.Look(ref cellPosition, "cellPosition", default);
}
public void GenerateFloorPosition(IntVec3 apparelCell, Vector2 apparelOffset = default)
{
Pawn pawn = apparel.Wearer;
// Reuse an old location for thrown clothes if the wearer is not too far away from it
if ((cellPosition - pawn.Position).LengthManhattan <= 2 && cellPosition.GetRoom(pawn.Map) == pawn.GetRoom())
{ return; }
CompBodyAnimator comp = pawn.TryGetComp<CompBodyAnimator>();
if (comp == null || comp.isAnimating == false)
{ return; }
cellPosition = apparelCell;
apparel.Rotation = Rot4.Random;
Vector3 offset = new Vector3(Rand.Gaussian(apparelOffset.x, apparelOffset.y), 0f, Rand.Gaussian(apparelOffset.x, apparelOffset.y));
position = cellPosition.ToVector3() + offset + new Vector3(0.5f, AltitudeLayer.ItemImportant.AltitudeFor() - Mathf.Clamp(apparel.def.apparel.LastLayer.drawOrder/100000f, 0f, 1f), 0.5f);
rotation = 120 * (-1f + 2f * Rand.Value);
}
/*public bool IsBeingWorn()
{
Pawn pawn = apparel.Wearer;
if (apparel.def.apparel.wornGraphicPath.NullOrEmpty())
{ return true; }
foreach (ApparelGraphicRecord record in pawn.Drawer.renderer.graphics.apparelGraphics)
{
if (record.sourceApparel == apparel)
{ return true; }
}
return false;
}*/
}
}
//var methodInfo = AccessTools.Method(typeof(GenPlace), "TryFindPlaceSpotNear", null, null);
//object[] parameters = new object[] { apparel.Wearer.Position, default(Rot4), apparel.Wearer.Map, apparel.Wearer, false, null, null };
//object result = methodInfo.Invoke(null, parameters);
/*bool _result = (bool)result;
if (_result)
{
bestSpot = (IntVec3)parameters[5];
DebugMode.Message("Best position: " + bestSpot.ToString());
}*/

View file

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using Verse;
using RimWorld;
namespace Rimworld_Animations_Patch
{
public class CompProperties_ApparelVisibility : CompProperties
{
public CompProperties_ApparelVisibility()
{
base.compClass = typeof(CompApparelVisibility);
}
}
}

View file

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HarmonyLib;
using Rimworld_Animations;
using Verse;
namespace Rimworld_Animations_Patch
{
public class ActorAnimationData
{
public AnimationDef animationDef = null;
public int actorID = 0;
public int currentStage = 0;
public int stageTicks = 0;
public Rot4 actorFacing = Rot4.South;
public ActorAnimationData(AnimationDef animationDef, int actorID, int currentStage, int stageTicks, Rot4 actorFacing)
{
this.animationDef = animationDef;
this.actorID = actorID;
this.currentStage = currentStage;
this.stageTicks = stageTicks;
this.actorFacing = actorFacing;
}
}
}

View file

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Verse;
using Rimworld_Animations;
namespace Rimworld_Animations_Patch
{
public class HandAnimationDef : Def
{
public string animationDefName;
public List<HandAnimationData> handAnimationData = new List<HandAnimationData>();
}
public class HandAnimationData
{
public int stageID = 0;
public int actorID = 0;
public int touchingActorID = -1;
public string targetBodyPart;
public string bodySide = "";
public List<string> targetBodyParts = new List<string>();
public string motion;
public int cycleTime = 0;
public bool mirror = false;
}
}

View file

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
using RimWorld;
namespace Rimworld_Animations_Patch
{
public class RimNudeData : IExposable
{
public string thingDef = "Invalid";
public bool coversGroin = false;
public bool coversBelly = false;
public bool coversChest = false;
public bool sexWear = false;
public RimNudeData() { }
public RimNudeData(ThingDef thingDef)
{
this.thingDef = thingDef.defName;
if (thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs) || thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.GenitalsBPG))
{ coversGroin = true; }
if (thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso))
{ coversBelly = true; }
if (thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) || thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.ChestBPG))
{ coversChest = true; }
this.sexWear = false;
}
public RimNudeData(string thingDef, bool coversGroin, bool coversBelly, bool coversChest, bool sexWear)
{
this.thingDef = thingDef;
this.coversGroin = coversGroin;
this.coversBelly = coversBelly;
this.coversChest = coversChest;
this.sexWear = sexWear;
}
public bool EquivalentTo(RimNudeData other)
{
return (thingDef == other.thingDef);
}
public void ExposeData()
{
Scribe_Values.Look(ref this.thingDef, "thingDef", "Invalid");
Scribe_Values.Look(ref this.coversGroin, "coversGroin", false);
Scribe_Values.Look(ref this.coversBelly, "coversBelly", false);
Scribe_Values.Look(ref this.coversChest, "coversChest", false);
Scribe_Values.Look(ref this.sexWear, "sexWear", false);
}
}
}

15
Source/Scripts/Enums.cs Normal file
View file

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Rimworld_Animations_Patch
{
public enum TabooStatus
{
NotTaboo = 0,
MinorTaboo = 1,
MajorTaboo = 2,
}
}

View file

@ -0,0 +1,284 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Verse;
using Verse.AI;
using Verse.AI.Group;
using RimWorld;
using rjw;
using Rimworld_Animations;
using HarmonyLib;
namespace Rimworld_Animations_Patch
{
public static class PawnExtension
{
public static bool IsInBed(this Pawn pawn, out Building bed)
{
bed = pawn.Position.GetThingList(pawn.Map).FirstOrDefault(x => x is Building_Bed) as Building;
return bed != null;
}
public static bool IsSeated(this Pawn pawn, out Building seat)
{
seat = pawn.Position.GetThingList(pawn.Map).FirstOrDefault(x => x is Building && x.def.building.isSittable) as Building;
return seat != null;
}
public static bool IsHavingSex(this Pawn pawn)
{
if (pawn?.jobs?.curDriver == null || pawn.Dead || pawn.jobs.curDriver is JobDriver_Sex == false)
{ return false; }
JobDriver_Sex jobdriver = pawn.jobs.curDriver as JobDriver_Sex;
return jobdriver.Partner != null && jobdriver.Partner != pawn;
}
public static bool IsMasturbating(this Pawn pawn)
{
if (pawn?.jobs?.curDriver == null || pawn.Dead || pawn.jobs.curDriver is JobDriver_Sex == false)
{ return false; }
JobDriver_Sex jobdriver = pawn.jobs.curDriver as JobDriver_Sex;
return jobdriver.Partner == null || jobdriver.Partner == pawn || (jobdriver.Partner is Pawn) == false;
}
public static Pawn GetSexInitiator(this Pawn pawn)
{
if (pawn?.jobs?.curDriver != null && pawn.Dead == false && pawn.jobs.curDriver is JobDriver_SexBaseInitiator)
{ return pawn; }
JobDriver_SexBaseReciever jobDriver = pawn.jobs.curDriver as JobDriver_SexBaseReciever;
if (jobDriver?.Partner?.jobs?.curDriver != null && jobDriver.Partner.Dead == false && jobDriver.Partner.jobs.curDriver is JobDriver_SexBaseInitiator)
{ return jobDriver.Partner; }
return null;
}
public static Pawn GetSexReceiver(this Pawn pawn)
{
if (pawn.jobs.curDriver is JobDriver_SexBaseReciever)
{ return pawn; }
JobDriver_SexBaseInitiator jobDriver = pawn.jobs.curDriver as JobDriver_SexBaseInitiator;
if (jobDriver?.Partner?.jobs?.curDriver != null && jobDriver.Partner.Dead == false && jobDriver.Partner.jobs.curDriver is JobDriver_SexBaseReciever)
{ return jobDriver.Partner; }
return null;
}
public static Pawn GetSexPartner(this Pawn pawn)
{
return (pawn.jobs.curDriver as JobDriver_Sex)?.Partner;
}
public static List<Pawn> GetAllSexParticipants(this Pawn pawn)
{
List<Pawn> participants = new List<Pawn>();
if (pawn?.jobs?.curDriver == null || (pawn.jobs.curDriver is JobDriver_Sex) == false)
{ return participants; }
if (pawn.GetSexReceiver() != null)
{
List<Pawn> partners = (pawn.GetSexReceiver().jobs.curDriver as JobDriver_SexBaseReciever).parteners.ToList();
if (partners != null)
{
foreach (Pawn partner in partners)
{
if (partner != null)
{ participants = partners; }
}
}
}
if (pawn.GetSexInitiator() != null)
{
Pawn partner = (pawn.GetSexInitiator().jobs.curDriver as JobDriver_SexBaseInitiator).Partner;
if (partner != null && partner.Dead == false)
{ participants.AddDistinct(partner); }
}
participants.AddDistinct(pawn);
participants.SortBy(x => x.GetAnimationData() != null ? x.GetAnimationData().actorID : participants.IndexOf(x));
return participants;
}
public static bool IsLoverOfOther(this Pawn pawn, Pawn other)
{
if (pawn == null || other == null)
{ return false; }
List<DirectPawnRelation> lovers = SpouseRelationUtility.GetLoveRelations(pawn, false);
return lovers.Any(x => x.otherPawn == other);
}
public static bool HasPrivacy(this Pawn pawn, float radius)
{
if (pawn.AnimalOrWildMan() || pawn.RaceProps.Humanlike == false)
{ return true; }
if (pawn.IsHavingSex() == false && pawn.IsMasturbating() == false)
{ return true; }
bool hasPrivacy = true;
bool isExhibitionist = pawn.HasTrait("Exhibitionist") || xxx.has_quirk(pawn, "Exhibitionist");
pawn.IsInBed(out Building bed);
foreach (Thing thing in GenRadial.RadialDistinctThingsAround(pawn.Position, pawn.Map, radius, true))
{
Pawn witness = thing as Pawn;
// Caught having sex
if (SexInteractionUtility.PawnCaughtLovinByWitness(pawn, witness))
{
SexInteractionUtility.ResolveThoughtsForWhenSexIsWitnessed(pawn, witness, out bool witnessJoiningSex);
// Try to invite intruder to join in
if (witnessJoiningSex)
{
if (pawn.IsMasturbating())
{
if (bed == null)
{
Job job = new Job(xxx.quick_sex, pawn);
witness.jobs.TryTakeOrderedJob(job);
}
else
{
Job job = new Job(xxx.casual_sex, pawn, bed);
witness.jobs.TryTakeOrderedJob(job);
}
}
else if (pawn.GetSexReceiver() != null)
{
Job job = new Job(DefDatabase<JobDef>.GetNamed("JoinInSex", false), pawn.GetSexReceiver(), bed);
witness.jobs.TryTakeOrderedJob(job);
}
}
// The invitation failed
else
{ hasPrivacy = false; }
}
}
return hasPrivacy || isExhibitionist || BasicSettings.needPrivacy == false;
}
public static ActorAnimationData GetAnimationData(this Pawn pawn)
{
if (pawn.TryGetComp<CompBodyAnimator>() == null) return null;
if (pawn.TryGetComp<CompBodyAnimator>().isAnimating == false) return null;
AnimationDef animationDef = (AnimationDef)AccessTools.Field(typeof(CompBodyAnimator), "anim").GetValue(pawn.TryGetComp<CompBodyAnimator>());
int actorID = (int)AccessTools.Field(typeof(CompBodyAnimator), "actor").GetValue(pawn.TryGetComp<CompBodyAnimator>());
int currentStage = (int)AccessTools.Field(typeof(CompBodyAnimator), "curStage").GetValue(pawn.TryGetComp<CompBodyAnimator>());
int stageTicks = (int)AccessTools.Field(typeof(CompBodyAnimator), "stageTicks").GetValue(pawn.TryGetComp<CompBodyAnimator>());
Rot4 actorFacing = (Rot4)AccessTools.Field(typeof(CompBodyAnimator), "bodyFacing").GetValue(pawn.TryGetComp<CompBodyAnimator>());
return new ActorAnimationData(animationDef, actorID, currentStage, stageTicks, actorFacing);
}
public static List<BodyPartRecord> GetHands(this Pawn pawn)
{
if (HandAnimationUtility.handDef == null)
{ HandAnimationUtility.handDef = DefDatabase<BodyPartDef>.GetNamed("Hand", false); }
return pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def == HandAnimationUtility.handDef)?.ToList();
}
public static bool HasPreceptForIssue(this Pawn pawn, IssueDef issueDef, out Precept precept)
{
precept = null;
if (pawn?.Ideo == null || issueDef == null)
{ return false; }
foreach (Precept _precept in pawn.Ideo.PreceptsListForReading)
{
if (_precept.def.issue == issueDef)
{
precept = _precept;
return true;
}
}
return false;
}
public static bool IssueIsMajorTaboo(this Pawn pawn, IssueDef issueDef, out Precept precept)
{
if (HasPreceptForIssue(pawn, issueDef, out precept))
{
if (precept.def.defName.Contains("Forbidden") || precept.def.defName.Contains("Prohibited") || precept.def.defName.Contains("Abhorrent"))
{ return true; }
}
return false;
}
public static bool IssueIsMinorTaboo(this Pawn pawn, IssueDef issueDef, out Precept precept)
{
if (HasPreceptForIssue(pawn, issueDef, out precept))
{
if (precept.def.defName.Contains("Horrible") || precept.def.defName.Contains("Despised") || precept.def.defName.Contains("Disapproved"))
{ return true; }
}
return false;
}
public static bool EnjoysViolence(this Pawn pawn)
{
if (pawn.IsAnimal() || pawn.RaceProps.IsMechanoid)
{ return true; }
if (pawn?.story?.traits?.allTraits == null || pawn?.story?.traits?.allTraits.NullOrEmpty() == true)
{ return false; }
List<string> traits = new List<string>() { "Brawler", "Psychopath", "Bloodlust" };
return pawn.story.traits.allTraits.Any(x => traits.Contains(x.def.defName));
}
public static bool DislikesViolence(this Pawn pawn)
{
if (pawn.IsAnimal() || pawn.RaceProps.IsMechanoid)
{ return false; }
if (pawn?.story?.traits?.allTraits == null || pawn?.story?.traits?.allTraits.NullOrEmpty() == true)
{ return false; }
List<string> traits = new List<string>() { "Kind", "Wimp" };
return pawn.WorkTagIsDisabled(WorkTags.Violent) || pawn.story.traits.allTraits.Any(x => traits.Contains(x.def.defName));
}
public static bool HasTrait(this Pawn pawn, string trait)
{
if (pawn?.story?.traits?.allTraits == null || pawn.story.traits.allTraits.NullOrEmpty())
{ return false; }
TraitDef traitDef = DefDatabase<TraitDef>.GetNamedSilentFail(trait);
if (traitDef == null)
{ traitDef = DefDatabase<TraitDef>.GetNamedSilentFail(trait.ToLower()); }
if (traitDef == null)
{ return false; }
return pawn.story.traits.HasTrait(traitDef);
}
}
}

View file

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;
using rjw;
using Rimworld_Animations;
namespace Rimworld_Animations_Patch
{
public class JobDriver_JoinInSex : JobDriver_SexBaseInitiator
{
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
return true; // pawn.Reserve(Target, job, 3, 0, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
setup_ticks();
this.FailOnDespawnedNullOrForbidden(iTarget);
this.FailOn(() => !Partner.health.capacities.CanBeAwake);
this.FailOn(() => pawn.Drafted);
this.FailOn(() => Partner.Drafted);
Toil FollowToil = new Toil();
FollowToil.defaultCompleteMode = ToilCompleteMode.Delay;
FollowToil.socialMode = RandomSocialMode.Off;
FollowToil.defaultDuration = 1200;
FollowToil.tickAction = delegate
{
pawn.pather.StartPath(Partner, PathEndMode.Touch);
if (pawn.pather.Moving == false && Partner.pather.Moving == false && Partner.jobs.curDriver is JobDriver_SexBaseReciever)
{ ReadyForNextToil(); }
};
yield return FollowToil;
Toil SexToil = new Toil();
SexToil.defaultCompleteMode = ToilCompleteMode.Never;
SexToil.socialMode = RandomSocialMode.Off;
SexToil.defaultDuration = duration;
SexToil.handlingFacing = true;
SexToil.FailOn(() => (Partner.jobs.curDriver is JobDriver_SexBaseReciever) == false);
SexToil.initAction = delegate
{
Start();
Sexprops.usedCondom = CondomUtility.TryUseCondom(pawn) || CondomUtility.TryUseCondom(Partner);
};
SexToil.AddPreTickAction(delegate
{
if (pawn.IsHashIntervalTick(ticks_between_hearts))
ThrowMetaIconF(pawn.Position, pawn.Map, FleckDefOf.Heart);
SexTick(pawn, Partner);
SexUtility.reduce_rest(pawn, 1);
if (ticks_left <= 0)
ReadyForNextToil();
});
SexToil.AddFinishAction(delegate
{
End();
});
yield return SexToil;
yield return new Toil
{
initAction = delegate
{
//// Trying to add some interactions and social logs
SexUtility.ProcessSex(Sexprops);
},
defaultCompleteMode = ToilCompleteMode.Instant
};
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,357 @@
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using Verse;
using RimWorld;
using rjw;
namespace Rimworld_Animations_Patch
{
public class ApparelSettings : ModSettings
{
public static List<RimNudeData> rimNudeData = new List<RimNudeData>();
public static bool cropApparel = false;
public static bool clothesThrownOnGround = true;
public static RJWPreferenceSettings.Clothing apparelWornForQuickies = RJWPreferenceSettings.Clothing.Clothed;
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look(ref cropApparel, "cropApparel", false);
Scribe_Values.Look(ref clothesThrownOnGround, "clothesThrownOnGround", true);
Scribe_Values.Look(ref apparelWornForQuickies, "apparelWornForQuickies", RJWPreferenceSettings.Clothing.Clothed);
}
public static RimNudeData GetRimNudeData(Apparel apparel)
{
if (rimNudeData.NullOrEmpty())
{ ApparelSettingsUtility.ResetRimNudeData(rimNudeData); }
foreach (RimNudeData apparelData in rimNudeData)
{
if (apparelData.EquivalentTo(new RimNudeData(apparel.def)))
{ return apparelData; }
}
return null;
}
}
public class ApparelSettingsDisplay : Mod
{
private const float windowY = 250f;
private const float windowHeight = 360f;
private Vector2 scrollPosition;
private const float scrollBarWidthMargin = 18f;
private const float headerHeight = 48f;
private const float widgetWidth = 32f;
private const float widgetHeight = 32f;
private const float buttonWidth = 90f;
private const float buttonHeight = 32f;
private const float checkboxSize = 24f;
private const float labelWidth = 170f;
private const float labelHeight = 40f;
private const float rowHeight = 40f;
private const float halfColumnWidth = 40f;
private const float singleColumnWidth = 100f;
private const float doubleColumnWidth = 180f;
private static List<ThingDef> thingDefs = new List<ThingDef>();
public ApparelSettingsDisplay(ModContentPack content) : base(content)
{
GetSettings<ApparelSettings>();
}
public override void WriteSettings()
{
base.WriteSettings();
ApplySettings();
}
// Update all humanlike pawn graphics when settings window is closed
public void ApplySettings()
{
if (Current.ProgramState == ProgramState.Playing)
{
foreach (Pawn pawn in Current.Game.CurrentMap.mapPawns.AllPawns)
{
if (pawn.RaceProps.Humanlike && pawn.apparel.WornApparel.NullOrEmpty() == false)
{ pawn.Drawer.renderer.graphics.ResolveAllGraphics(); }
}
}
}
public override void DoSettingsWindowContents(Rect inRect)
{
// Settings list
Listing_Standard listingStandard;
listingStandard = new Listing_Standard();
listingStandard.Begin(inRect);
listingStandard.Gap(10f);
listingStandard.Label("rimworld_animation_patch_clothing".Translate());
listingStandard.Gap(5f);
listingStandard.Label("wearing_clothes_in_bed".Translate(), -1, "wearing_clothes_in_bed_desc".Translate());
if (Widgets.ButtonText(new Rect(inRect.width - 128f, 36f, 128f, 24f), RJWPreferenceSettings.sex_wear.ToString()))
{
List<FloatMenuOption> options = new List<FloatMenuOption>
{
new FloatMenuOption(RJWPreferenceSettings.Clothing.Clothed.ToString(), delegate()
{ RJWPreferenceSettings.sex_wear = RJWPreferenceSettings.Clothing.Clothed;
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
new FloatMenuOption(RJWPreferenceSettings.Clothing.Headgear.ToString(), delegate()
{ RJWPreferenceSettings.sex_wear = RJWPreferenceSettings.Clothing.Headgear;
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
new FloatMenuOption(RJWPreferenceSettings.Clothing.Nude.ToString(), delegate()
{ RJWPreferenceSettings.sex_wear = RJWPreferenceSettings.Clothing.Nude;
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
}; Find.WindowStack.Add(new FloatMenu(options));
}
listingStandard.Label("wearing_clothes_for_quickies".Translate(), -1, "wearing_clothes_for_quickies_desc".Translate());
if (Widgets.ButtonText(new Rect(inRect.width - 128f, 60f, 128f, 24f), ApparelSettings.apparelWornForQuickies.ToString()))
{
List<FloatMenuOption> options = new List<FloatMenuOption>
{
new FloatMenuOption(RJWPreferenceSettings.Clothing.Clothed.ToString(), delegate()
{ ApparelSettings.apparelWornForQuickies = RJWPreferenceSettings.Clothing.Clothed;
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
new FloatMenuOption(RJWPreferenceSettings.Clothing.Headgear.ToString(), delegate()
{ ApparelSettings.apparelWornForQuickies = RJWPreferenceSettings.Clothing.Headgear;
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
new FloatMenuOption(RJWPreferenceSettings.Clothing.Nude.ToString(), delegate()
{ ApparelSettings.apparelWornForQuickies = RJWPreferenceSettings.Clothing.Nude;
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
}; Find.WindowStack.Add(new FloatMenu(options));
}
listingStandard.CheckboxLabeled("clothes_thrown_on_ground".Translate(), ref ApparelSettings.clothesThrownOnGround, "clothes_thrown_on_ground_desc".Translate());
listingStandard.CheckboxLabeled("crop_apparel".Translate(), ref ApparelSettings.cropApparel, "crop_apparel_desc".Translate());
listingStandard.End();
base.DoSettingsWindowContents(inRect);
// Local variables
Rect rect = Find.WindowStack.currentlyDrawnWindow.windowRect.AtZero();
Rect tempRect = new Rect(0, 0, 0, 0);
float innerY = 0f;
float innerX = 0;
int num = 0;
bool isEnabled = false;
bool linkChangesChanged = false;
// Get a list of apparel of interest
if (thingDefs.NullOrEmpty())
{ thingDefs = ApparelSettingsUtility.GetApparelOfInterest(); }
// Ensure that all apparel has associated RimNudeData
if (ApparelSettings.rimNudeData.NullOrEmpty())
{ ApparelSettingsUtility.ResetRimNudeData(ApparelSettings.rimNudeData); }
// Add buttons to the top of the main window
innerX = halfColumnWidth;
// Apparel
tempRect = new Rect(innerX + SettingsUtility.Align(labelWidth, doubleColumnWidth), windowY - headerHeight - 5, labelWidth, headerHeight);
Widgets.DrawHighlightIfMouseover(tempRect);
TooltipHandler.TipRegion(tempRect, "List of apparel that covers the legs and/or torso. This list can be sorted alphabetically or by the mod that added them.");
if (Widgets.ButtonText(tempRect, "Apparel"))
{
List<FloatMenuOption> options = new List<FloatMenuOption>
{
new FloatMenuOption("Sort by name", delegate()
{ thingDefs = thingDefs.OrderBy(x => x.label).ToList();
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
new FloatMenuOption("Sort by mod", delegate()
{ thingDefs = ApparelSettingsUtility.GetApparelOfInterest();
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
}; Find.WindowStack.Add(new FloatMenu(options));
}; innerX += doubleColumnWidth;
// Covers groin
tempRect = new Rect(innerX + SettingsUtility.Align(buttonWidth, singleColumnWidth), windowY - headerHeight - 5, buttonWidth, headerHeight);
Widgets.DrawHighlightIfMouseover(tempRect);
TooltipHandler.TipRegion(tempRect, "Toggles whether genitials should be hidden when wearing this apparel.");
if (Widgets.ButtonText(tempRect, "Covers\ngroin"))
{
List<FloatMenuOption> options = new List<FloatMenuOption>
{
new FloatMenuOption("Set all 'true'", delegate()
{ ApparelSettingsUtility.SetAllCoversGroin(ApparelSettings.rimNudeData, true);
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
new FloatMenuOption("Set all 'false'", delegate()
{ ApparelSettingsUtility.SetAllCoversGroin(ApparelSettings.rimNudeData, false);
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
}; Find.WindowStack.Add(new FloatMenu(options));
}; innerX += singleColumnWidth;
// Covers belly
tempRect = new Rect(innerX + SettingsUtility.Align(buttonWidth, singleColumnWidth), windowY - headerHeight - 5, buttonWidth, headerHeight);
Widgets.DrawHighlightIfMouseover(tempRect);
TooltipHandler.TipRegion(tempRect, "Toggles whether an enlarged belly should be hidden when wearing this apparel.");
if (Widgets.ButtonText(tempRect, "Covers\nbelly"))
{
List<FloatMenuOption> options = new List<FloatMenuOption>
{
new FloatMenuOption("Set all 'true'", delegate()
{ ApparelSettingsUtility.SetAllCoversBelly(ApparelSettings.rimNudeData, true);
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
new FloatMenuOption("Set all 'false'", delegate()
{ ApparelSettingsUtility.SetAllCoversBelly(ApparelSettings.rimNudeData, false);
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
}; Find.WindowStack.Add(new FloatMenu(options));
}; innerX += singleColumnWidth;
// Covers belly
tempRect = new Rect(innerX + SettingsUtility.Align(buttonWidth, singleColumnWidth), windowY - headerHeight - 5, buttonWidth, headerHeight);
Widgets.DrawHighlightIfMouseover(tempRect);
TooltipHandler.TipRegion(tempRect, "Toggles whether this apparel conceals breasts.");
if (Widgets.ButtonText(tempRect, "Covers\nbreasts"))
{
List<FloatMenuOption> options = new List<FloatMenuOption>
{
new FloatMenuOption("Set all 'true'", delegate()
{ ApparelSettingsUtility.SetAllCoversChest(ApparelSettings.rimNudeData, true);
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
new FloatMenuOption("Set all 'false'", delegate()
{ ApparelSettingsUtility.SetAllCoversChest(ApparelSettings.rimNudeData, false);
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
}; Find.WindowStack.Add(new FloatMenu(options));
}; innerX += singleColumnWidth;
// Sex wear
tempRect = new Rect(innerX + SettingsUtility.Align(buttonWidth, singleColumnWidth), windowY - headerHeight - 5, buttonWidth, headerHeight);
Widgets.DrawHighlightIfMouseover(tempRect);
TooltipHandler.TipRegion(tempRect, "Toggles whether this piece of apparel should always be kept on during lovin'");
if (Widgets.ButtonText(tempRect, "Sex-wear"))
{
List<FloatMenuOption> options = new List<FloatMenuOption>
{
new FloatMenuOption("Set all 'true'", delegate()
{ ApparelSettingsUtility.SetAllSexWear(ApparelSettings.rimNudeData, true);
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
new FloatMenuOption("Set all 'false'", delegate()
{ ApparelSettingsUtility.SetAllSexWear(ApparelSettings.rimNudeData, false);
}, MenuOptionPriority.Default, null, null, 0f, null, null, true, 0),
}; Find.WindowStack.Add(new FloatMenu(options));
}; innerX += singleColumnWidth;
// Reset button
tempRect = new Rect(innerX + SettingsUtility.Align(buttonWidth, singleColumnWidth), windowY - headerHeight - 5, buttonWidth, headerHeight);
Widgets.DrawHighlightIfMouseover(tempRect);
TooltipHandler.TipRegion(tempRect, "Returns all values in this table to their default state.");
if (Widgets.ButtonText(tempRect, "Reset to\ndefaults"))
{ ApparelSettingsUtility.ResetRimNudeData(ApparelSettings.rimNudeData); }; innerX += singleColumnWidth + scrollBarWidthMargin;
// Determine the height of the scrollable area
int apparelCount = thingDefs.Count;
float totalContentHeight = rowHeight * (float)apparelCount;
// Create a rect for the scroll window
var contentRect = new Rect(0f, windowY, innerX, windowHeight);
// Determine if the scroll will be visible
bool scrollBarVisible = totalContentHeight > contentRect.height;
// Create a rect for the scrollable area
var scrollViewTotal = new Rect(0f, 0f, innerX - (scrollBarVisible ? scrollBarWidthMargin : 0), totalContentHeight);
// Start of content for scrollable area
Widgets.DrawHighlight(contentRect);
Widgets.BeginScrollView(contentRect, ref scrollPosition, scrollViewTotal);
foreach (ThingDef thingDef in thingDefs)
{
isEnabled = false;
bool changeHappened = false;
innerX = 0;
innerY = (float)num * (rowHeight);
num++;
RimNudeData rimNudeApparel = ApparelSettings.rimNudeData.First(x => x.EquivalentTo(new RimNudeData(thingDef)));
// Apparel symbol
Widgets.ThingIcon(new Rect(innerX + SettingsUtility.Align(widgetWidth, halfColumnWidth), innerY + SettingsUtility.Align(widgetHeight, rowHeight), widgetWidth, widgetHeight), thingDef, null, null, 1f, null);
innerX += halfColumnWidth;
// Apparel name
Text.Anchor = TextAnchor.MiddleLeft;
Widgets.Label(new Rect(innerX + 10f, innerY + SettingsUtility.Align(labelHeight, rowHeight), labelWidth, labelHeight), thingDef.label.CapitalizeFirst()); innerX += doubleColumnWidth;
Text.Anchor = TextAnchor.UpperLeft;
// Hide groin checkbox
if (thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs) || thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.GenitalsBPG))
{
isEnabled = rimNudeApparel.coversGroin;
Widgets.Checkbox(innerX + SettingsUtility.Align(checkboxSize, singleColumnWidth), innerY + SettingsUtility.Align(checkboxSize, rowHeight), ref isEnabled, checkboxSize, false, true, null, null);
if (isEnabled != rimNudeApparel.coversGroin) { changeHappened = true; }
rimNudeApparel.coversGroin = isEnabled;
}; innerX += singleColumnWidth;
// Hide belly checkbox
if (thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso))
{
isEnabled = rimNudeApparel.coversBelly;
Widgets.Checkbox(innerX + SettingsUtility.Align(checkboxSize, singleColumnWidth), innerY + SettingsUtility.Align(checkboxSize, rowHeight), ref isEnabled, checkboxSize, false, true, null, null);
if (isEnabled != rimNudeApparel.coversBelly) { changeHappened = true; }
rimNudeApparel.coversBelly = isEnabled;
}; innerX += singleColumnWidth;
// Covers bust checkbox
if (thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) || thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.ChestBPG))
{
isEnabled = rimNudeApparel.coversChest;
Widgets.Checkbox(innerX + SettingsUtility.Align(checkboxSize, singleColumnWidth), innerY + SettingsUtility.Align(checkboxSize, rowHeight), ref isEnabled, checkboxSize, false, true, null, null);
if (isEnabled != rimNudeApparel.coversChest) { changeHappened = true; }
rimNudeApparel.coversChest = isEnabled;
}; innerX += singleColumnWidth;
// Is sex-wear checkbox
isEnabled = rimNudeApparel.sexWear;
Widgets.Checkbox(innerX + SettingsUtility.Align(checkboxSize, singleColumnWidth), innerY + SettingsUtility.Align(checkboxSize, rowHeight), ref isEnabled, checkboxSize, false, true, null, null);
if (isEnabled != rimNudeApparel.sexWear) { changeHappened = true; }
rimNudeApparel.sexWear = isEnabled;
innerX += singleColumnWidth;
// Update other body types if linked changed are enabled
if (BasicSettings.linkChanges && (changeHappened || linkChangesChanged))
{
for (int i = 0; i < 5; i++)
{
RimNudeData _rimNudeApparel = ApparelSettings.rimNudeData.First(x => x.EquivalentTo(new RimNudeData(thingDef)));
_rimNudeApparel.coversGroin = rimNudeApparel.coversGroin;
_rimNudeApparel.coversBelly = rimNudeApparel.coversBelly;
_rimNudeApparel.coversChest = rimNudeApparel.coversChest;
_rimNudeApparel.sexWear = rimNudeApparel.sexWear;
}
}
}
Widgets.EndScrollView();
}
public sealed override string SettingsCategory()
{
return "rimworld_animation_patch_apparelsettings".Translate();
}
}
}

View file

@ -0,0 +1,127 @@
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using Verse;
namespace Rimworld_Animations_Patch
{
public class BasicSettings : ModSettings
{
public static bool autoscaleDeltaPos = true;
public static bool linkChanges = false;
public static bool needPrivacy = true;
public static float chanceForOtherToJoinInSex = 0.25f;
public static bool hideNamesForSex = false;
public static bool debugMode = false;
public static bool showHands = true;
public static bool worryAboutInfidelity = true;
public static bool worryAboutBeastiality = true;
public static bool worryAboutRape = true;
public static bool worryAboutNecro = true;
public static bool worryAboutXeno = true;
public static bool ignoreSlaveRape = false;
public static bool majorTabooCanStartFights = false;
public static float genitalMasturbationChance = 1.0f;
public static float analMasturbationChance = 0.25f;
public static float breastsMasturbationChance = 0.5f;
public static float humpingMasturbationChance = 0.25f;
public static float otherMasturbationChance = 0.2f;
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look(ref autoscaleDeltaPos, "autoscaleDeltaPos", true);
Scribe_Values.Look(ref linkChanges, "linkChanges", false);
Scribe_Values.Look(ref needPrivacy, "needPrivacy", true);
Scribe_Values.Look(ref chanceForOtherToJoinInSex, "chanceForSexExtra", 0.25f);
Scribe_Values.Look(ref hideNamesForSex, "hideNamesForSex", false);
Scribe_Values.Look(ref debugMode, "debugMode", false);
Scribe_Values.Look(ref showHands, "showHands", true);
Scribe_Values.Look(ref worryAboutInfidelity, "worryAboutInfidelity", true);
Scribe_Values.Look(ref worryAboutBeastiality, "worryAboutBeastiality", true);
Scribe_Values.Look(ref worryAboutRape, "worryAboutRape", true);
Scribe_Values.Look(ref worryAboutNecro, "worryAboutNecro", true);
Scribe_Values.Look(ref worryAboutXeno, "worryAboutXeno", true);
Scribe_Values.Look(ref ignoreSlaveRape, "ignoreSlaveRape", false);
Scribe_Values.Look(ref majorTabooCanStartFights, "majorTabooCanStartFights", false);
}
}
public class BasicSettingsDisplay : Mod
{
public BasicSettingsDisplay(ModContentPack content) : base(content)
{
GetSettings<BasicSettings>();
}
public override void WriteSettings()
{
base.WriteSettings();
ApplySettings();
}
// Update all humanlike pawn graphics when settings window is closed
public void ApplySettings()
{
if (Current.ProgramState == ProgramState.Playing)
{
foreach (Pawn pawn in Current.Game.CurrentMap.mapPawns.AllPawns)
{
if (pawn.RaceProps.Humanlike && pawn.apparel.WornApparel.NullOrEmpty() == false)
{ pawn.Drawer.renderer.graphics.ResolveAllGraphics(); }
}
}
}
public override void DoSettingsWindowContents(Rect inRect)
{
Listing_Standard listingStandard;
listingStandard = new Listing_Standard();
listingStandard.Begin(inRect);
listingStandard.Gap(10f);
listingStandard.Label("rimworld_animation_patch_general".Translate());
listingStandard.Gap(5f);
listingStandard.CheckboxLabeled("need_privacy".Translate(), ref BasicSettings.needPrivacy, "need_privacy_desc".Translate());
listingStandard.CheckboxLabeled("worry_about_infidelity".Translate(), ref BasicSettings.worryAboutInfidelity, "worry_about_infidelity_desc".Translate());
listingStandard.CheckboxLabeled("worry_about_beastiality".Translate(), ref BasicSettings.worryAboutBeastiality, "worry_about_beastiality_desc".Translate());
listingStandard.CheckboxLabeled("worry_about_rape".Translate(), ref BasicSettings.worryAboutRape, "worry_about_rape_desc".Translate());
if (BasicSettings.worryAboutRape)
{
listingStandard.CheckboxLabeled("ignore_slave_rape".Translate(), ref BasicSettings.ignoreSlaveRape);
}
listingStandard.CheckboxLabeled("worry_about_necro".Translate(), ref BasicSettings.worryAboutNecro, "worry_about_necro_desc".Translate());
listingStandard.CheckboxLabeled("worry_about_xeno".Translate(), ref BasicSettings.worryAboutXeno, "worry_about_xeno_desc".Translate());
listingStandard.CheckboxLabeled("major_taboo_can_start_fights".Translate(), ref BasicSettings.majorTabooCanStartFights, "major_taboo_can_start_fights_desc".Translate());
listingStandard.Label("chance_for_other_to_join_in_sex".Translate() + ": " + BasicSettings.chanceForOtherToJoinInSex.ToString("F"), -1f, "chance_for_other_to_join_in_sex_desc".Translate());
BasicSettings.chanceForOtherToJoinInSex = listingStandard.Slider(BasicSettings.chanceForOtherToJoinInSex, 0f, 1f);
listingStandard.CheckboxLabeled("hide_names_for_sex".Translate(), ref BasicSettings.hideNamesForSex, "hide_names_for_sex_desc".Translate());
listingStandard.CheckboxLabeled("debug_mode".Translate(), ref BasicSettings.debugMode, "debug_mode_desc".Translate());
listingStandard.Gap(10f);
listingStandard.Label("rimworld_animation_patch_animation".Translate());
listingStandard.Gap(5f);
listingStandard.CheckboxLabeled("autoscale_delta_pos".Translate(), ref BasicSettings.autoscaleDeltaPos, "autoscale_delta_pos_desc".Translate());
listingStandard.CheckboxLabeled("show_hands".Translate(), ref BasicSettings.showHands, "show_hands_desc".Translate());
listingStandard.End();
base.DoSettingsWindowContents(inRect);
}
public sealed override string SettingsCategory()
{
return "rimworld_animation_patch_basicsettings".Translate();
}
}
}

View file

@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Verse;
namespace Rimworld_Animations_Patch
{
public class BasicSettings : ModSettings
{
public static bool autoscaleDeltaPos = true;
public static bool allowUndressing = true;
public static bool clothesThrownOnGround = true;
public static float undressingInPrivateDegree = 0.8f;
public static float undressingInPublicDegree = 0.2f;
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look(ref autoscaleDeltaPos, "autoscaleDeltaPos", true);
Scribe_Values.Look(ref clothesThrownOnGround, "clothesThrownOnGround", true);
Scribe_Values.Look(ref allowUndressing, "allowUndressing", true);
Scribe_Values.Look(ref undressingInPrivateDegree, "undressingInPrivateDegree", 0.8f);
Scribe_Values.Look(ref undressingInPublicDegree, "undressingInPublicDegree", 0.2f);
}
}
public class BasicSettingsDisplay : Mod
{
public BasicSettingsDisplay(ModContentPack content) : base(content)
{
GetSettings<BasicSettings>();
}
public override void DoSettingsWindowContents(Rect inRect)
{
Listing_Standard listingStandard;
listingStandard = new Listing_Standard();
listingStandard.Begin(inRect);
listingStandard.Gap(10f);
listingStandard.Label("Misc Options");
listingStandard.Gap(5f);
listingStandard.CheckboxLabeled(" Auto-scale animations based on pawn body size", ref BasicSettings.autoscaleDeltaPos);
listingStandard.Gap(10f);
listingStandard.Label("Disrobing options");
listingStandard.Gap(5f);
listingStandard.CheckboxLabeled(" Animate pawn undressing", ref BasicSettings.allowUndressing);
if (BasicSettings.allowUndressing)
{
listingStandard.CheckboxLabeled(" Show discarded clothes on the floor", ref BasicSettings.clothesThrownOnGround);
listingStandard.Label(" Degree of disrobing when in bed (default is 0.80): " + BasicSettings.undressingInPrivateDegree.ToString("F"));
listingStandard.Label(" " + ReportOnDisrobingInPrivate());
BasicSettings.undressingInPrivateDegree = listingStandard.Slider(BasicSettings.undressingInPrivateDegree, 0f, 1f);
listingStandard.Label(" Degree of disrobing when out of bed (default is 0.20): " + BasicSettings.undressingInPublicDegree.ToString("F"));
listingStandard.Label(" " + ReportOnDisrobingInPublic());
BasicSettings.undressingInPublicDegree = listingStandard.Slider(BasicSettings.undressingInPublicDegree, 0f, 1f);
}
listingStandard.End();
base.DoSettingsWindowContents(inRect);
}
public string ReportOnDisrobing(float val)
{
string report;
if (val == 1) { report = "(Pawns will always fully disrobe)"; }
else if (val >= 0.6667) { report = "(Pawns prefer to fully disrobe)"; }
else if (val >= 0.3333) { report = "(Pawns like to keep some clothes on)"; }
else if (val > 0) { report = "(Pawns will keep most of their clothes on)"; }
else { report = "(Pawns will try to keep all of their clothes on)"; }
return report;
}
public string ReportOnDisrobingInPrivate()
{
return ReportOnDisrobing(BasicSettings.undressingInPrivateDegree);
}
public string ReportOnDisrobingInPublic()
{
return ReportOnDisrobing(BasicSettings.undressingInPublicDegree);
}
public override string SettingsCategory()
{
return "Rimworld Animations Patch - Basic settings";
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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); }
}
}
}

View 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; }
}
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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;
}
}

View 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;
}
}
}

View 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;
}
}
}

View file

@ -0,0 +1 @@
d5a3e9402b3033575abd239e1b750dc876117ae0

View file

@ -0,0 +1,7 @@
C:\Program Files (x86)\Steam\SteamApps\common\RimWorld\Mods\rimworld-animations-patch-abscon\Source\obj\Debug\Rimworld-Animations-Patch.csproj.CoreCompileInputs.cache
C:\Program Files (x86)\Steam\SteamApps\common\RimWorld\Mods\rimworld-animations-patch-abscon\1.3\Assemblies\Rimworld-Animations-Patch.dll
C:\Program Files (x86)\Steam\SteamApps\common\RimWorld\Mods\rimworld-animations-patch-abscon\1.3\Assemblies\Rimworld-Animations-Patch.pdb
C:\Program Files (x86)\Steam\SteamApps\common\RimWorld\Mods\rimworld-animations-patch-abscon\Source\obj\Debug\Rimworld-Animations-Patch.dll
C:\Program Files (x86)\Steam\SteamApps\common\RimWorld\Mods\rimworld-animations-patch-abscon\Source\obj\Debug\Rimworld-Animations-Patch.pdb
C:\Program Files (x86)\Steam\SteamApps\common\RimWorld\Mods\rimworld-animations-patch-abscon\Source\obj\Debug\Rimworld-Animations-Patch.csproj.CopyComplete
C:\Program Files (x86)\Steam\SteamApps\common\RimWorld\Mods\rimworld-animations-patch-abscon\Source\obj\Debug\Rimworld-Animations-Patch.csprojAssemblyReference.cache

Binary file not shown.

Binary file not shown.