First commit
This commit is contained in:
parent
ddda70a258
commit
8e6918ae70
95 changed files with 20766 additions and 1 deletions
BIN
Source/.vs/Rimworld-Animations-Patch/v16/.suo
Normal file
BIN
Source/.vs/Rimworld-Animations-Patch/v16/.suo
Normal file
Binary file not shown.
137
Source/Rimworld-Animations-Patch.csproj
Normal file
137
Source/Rimworld-Animations-Patch.csproj
Normal 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>
|
25
Source/Rimworld-Animations-Patch.sln
Normal file
25
Source/Rimworld-Animations-Patch.sln
Normal 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
|
75
Source/Scripts/Comps/CompApparelVisibility.cs
Normal file
75
Source/Scripts/Comps/CompApparelVisibility.cs
Normal 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());
|
||||
}*/
|
15
Source/Scripts/Comps/CompProperties_ApparelVisibility.cs
Normal file
15
Source/Scripts/Comps/CompProperties_ApparelVisibility.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
29
Source/Scripts/Defs/ActorAnimationData.cs
Normal file
29
Source/Scripts/Defs/ActorAnimationData.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
28
Source/Scripts/Defs/HandAnimationDef.cs
Normal file
28
Source/Scripts/Defs/HandAnimationDef.cs
Normal 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;
|
||||
}
|
||||
}
|
60
Source/Scripts/Defs/RimNudeData.cs
Normal file
60
Source/Scripts/Defs/RimNudeData.cs
Normal 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
15
Source/Scripts/Enums.cs
Normal 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,
|
||||
}
|
||||
}
|
284
Source/Scripts/Extensions/PawnExtension.cs
Normal file
284
Source/Scripts/Extensions/PawnExtension.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
79
Source/Scripts/JobDrivers/JobDriver_JoinInSex.cs
Normal file
79
Source/Scripts/JobDrivers/JobDriver_JoinInSex.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
[HarmonyPatch(typeof(ApparelGraphicRecordGetter), "TryGetGraphicApparel")]
|
||||
public static class HarmonyPatch_ApparelGraphicRecordGetter_TryGetGraphicApparel
|
||||
{
|
||||
public static void Postfix(ref bool __result, ref Apparel apparel, ref BodyTypeDef bodyType, ref ApparelGraphicRecord rec)
|
||||
{
|
||||
if (__result == false || apparel == null || bodyType == null || rec.graphic == null || ApparelSettings.cropApparel == false)
|
||||
{ return; }
|
||||
|
||||
// Get graphic
|
||||
Graphic graphic = rec.graphic;
|
||||
|
||||
// This graphic may need to be masked if the apparel sits on the skin layer and does not cover the legs
|
||||
if (apparel.def.apparel.LastLayer == ApparelLayerDefOf.OnSkin && apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) && !apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs))
|
||||
{
|
||||
Dictionary<GraphicRequest, Graphic> allGraphics = Traverse.Create(typeof(GraphicDatabase)).Field("allGraphics").GetValue() as Dictionary<GraphicRequest, Graphic>;
|
||||
GraphicRequest graphicRequest = new GraphicRequest(typeof(Graphic_Multi), graphic.path, ShaderDatabase.CutoutComplex, apparel.def.graphicData.drawSize, apparel.DrawColor, apparel.DrawColor, null, 0, null, "Masks/apparel_shirt_mask_" + bodyType.defName);
|
||||
|
||||
if (allGraphics.TryGetValue(graphicRequest) == null)
|
||||
{
|
||||
Graphic graphicWithApparelMask = GraphicDatabase.Get<Graphic_Multi>(graphic.path, ShaderDatabase.CutoutComplex, apparel.def.graphicData.drawSize, apparel.DrawColor, apparel.DrawColor, null, "Masks/apparel_shirt_mask_" + bodyType.defName);
|
||||
graphic = GraphicMaskingUtility.ApplyGraphicWithMasks(graphic, graphicWithApparelMask, true);
|
||||
DebugMode.Message("Applying apparel mask: Masks/apparel_shirt_mask_" + bodyType.defName + " to " + apparel.def.defName + " (" + graphic.path + ")");
|
||||
}
|
||||
}
|
||||
|
||||
rec = new ApparelGraphicRecord(graphic, apparel);
|
||||
}
|
||||
}
|
||||
}
|
46
Source/Scripts/Patches/HarmonyPatch_CompBodyAnimator.cs
Normal file
46
Source/Scripts/Patches/HarmonyPatch_CompBodyAnimator.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using UnityEngine;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using AlienRace;
|
||||
using Rimworld_Animations;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
[HarmonyPatch(typeof(CompBodyAnimator), "calculateDrawValues")]
|
||||
public static class HarmonyPatch_CompBodyAnimator_calculateDrawValues
|
||||
{
|
||||
public static void Postfix(CompBodyAnimator __instance)
|
||||
{
|
||||
if (__instance?.pawn == null)
|
||||
{ return; }
|
||||
|
||||
if (BasicSettings.autoscaleDeltaPos)
|
||||
{
|
||||
__instance.deltaPos.x *= __instance.pawn.RaceProps.baseBodySize;
|
||||
__instance.deltaPos.z *= __instance.pawn.RaceProps.baseBodySize;
|
||||
}
|
||||
|
||||
if (__instance.pawn.IsInBed(out Building bed) &&
|
||||
__instance.pawn.GetAnimationData().animationDef.actors[__instance.pawn.GetAnimationData().actorID].requiredGenitals.NullOrEmpty() == false &&
|
||||
__instance.pawn.GetAnimationData().animationDef.actors[__instance.pawn.GetAnimationData().actorID].requiredGenitals.Contains("Bed"))
|
||||
{
|
||||
__instance.bodyAngle += ((float)bed.Rotation.AsInt - 2f) * 90;
|
||||
if (__instance.bodyAngle < 0) __instance.bodyAngle = 360 - ((-1f * __instance.bodyAngle) % 360);
|
||||
if (__instance.bodyAngle > 360) __instance.bodyAngle %= 360;
|
||||
|
||||
__instance.headAngle += ((float)bed.Rotation.AsInt - 2f) * 90;
|
||||
if (__instance.headAngle < 0) __instance.headAngle = 360 - ((-1f * __instance.headAngle) % 360);
|
||||
if (__instance.headAngle > 360) __instance.headAngle %= 360;
|
||||
|
||||
__instance.deltaPos = __instance.deltaPos.RotatedBy(-(float)bed.Rotation.AsAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
Source/Scripts/Patches/HarmonyPatch_DrawGUIOverlay.cs
Normal file
49
Source/Scripts/Patches/HarmonyPatch_DrawGUIOverlay.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using UnityEngine;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using AlienRace;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
[HarmonyPatch(typeof(Building_Bed), "DrawGUIOverlay")]
|
||||
public static class HarmonyPatch_Building_Bed_DrawGUIOverlay
|
||||
{
|
||||
// Patches beds so sleeping spot names are hidden when the owner is having sex on it
|
||||
public static bool Prefix(Building_Bed __instance)
|
||||
{
|
||||
foreach (Pawn pawn in __instance.OwnersForReading)
|
||||
{
|
||||
if (pawn.GetAnimationData() != null && pawn.IsInBed(out Building bed) && bed == __instance)
|
||||
{ return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[StaticConstructorOnStartup]
|
||||
[HarmonyPatch(typeof(PawnUIOverlay), "DrawPawnGUIOverlay")]
|
||||
public static class HarmonyPatch_PawnUIOverlay_DrawPawnGUIOverlay
|
||||
{
|
||||
// Patches pawns so their name is hidden when having sex
|
||||
public static bool Prefix(PawnUIOverlay __instance)
|
||||
{
|
||||
if (BasicSettings.hideNamesForSex)
|
||||
{
|
||||
Pawn pawn = (Pawn)AccessTools.Field(typeof(PawnUIOverlay), "pawn").GetValue(__instance);
|
||||
|
||||
if (pawn.GetAnimationData() != null)
|
||||
{ return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
34
Source/Scripts/Patches/HarmonyPatch_JobDriver.cs
Normal file
34
Source/Scripts/Patches/HarmonyPatch_JobDriver.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using Rimworld_Animations;
|
||||
using rjw;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[HarmonyPatch(typeof(JobDriver), "GetReport")]
|
||||
public static class HarmonyPatch_JobDriver
|
||||
{
|
||||
public static bool Prefix(JobDriver __instance, ref string __result)
|
||||
{
|
||||
JobDriver_Sex jobdriver = __instance as JobDriver_Sex;
|
||||
|
||||
if (jobdriver != null && jobdriver.pawn != null && jobdriver.pawn.GetAnimationData() != null && jobdriver.Sexprops.isRape == false && jobdriver.Sexprops.isWhoring == false)
|
||||
{
|
||||
LocalTargetInfo a = jobdriver.job.targetA.IsValid ? jobdriver.job.targetA : jobdriver.job.targetQueueA.FirstValid();
|
||||
LocalTargetInfo b = jobdriver.job.targetB.IsValid ? jobdriver.job.targetB : jobdriver.job.targetQueueB.FirstValid();
|
||||
LocalTargetInfo targetC = jobdriver.job.targetC;
|
||||
|
||||
__result = JobUtility.GetResolvedJobReport(jobdriver.pawn.GetAnimationData().animationDef.label, a, b, targetC);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
27
Source/Scripts/Patches/HarmonyPatch_PatchAll.cs
Normal file
27
Source/Scripts/Patches/HarmonyPatch_PatchAll.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Verse;
|
||||
using HarmonyLib;
|
||||
using System.Reflection;
|
||||
using rjw;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
public static class Harmony_PatchAll
|
||||
{
|
||||
static Harmony_PatchAll()
|
||||
{
|
||||
Harmony harmony = new Harmony("Rimworld_Animations_Patch");
|
||||
harmony.PatchAll(Assembly.GetExecutingAssembly());
|
||||
|
||||
Quirk voyeur = new Quirk("Voyeur", "VoyeurQuirk", null, null);
|
||||
|
||||
if (Quirk.All.Contains(voyeur) == false)
|
||||
{ Quirk.All.Add(voyeur); }
|
||||
}
|
||||
}
|
||||
}
|
96
Source/Scripts/Patches/HarmonyPatch_PawnRenderer.cs
Normal file
96
Source/Scripts/Patches/HarmonyPatch_PawnRenderer.cs
Normal file
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Rimworld_Animations;
|
||||
using rjw;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
[HarmonyPatch(typeof(PawnRenderer), "RenderPawnInternal", new Type[]
|
||||
{
|
||||
typeof(Vector3),
|
||||
typeof(float),
|
||||
typeof(bool),
|
||||
typeof(Rot4),
|
||||
typeof(RotDrawMode),
|
||||
typeof(PawnRenderFlags)
|
||||
}
|
||||
)]
|
||||
public static class HarmonyPatch_PawnRenderer_RenderPawnInternal
|
||||
{
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
List<CodeInstruction> ins = instructions.ToList();
|
||||
|
||||
for (int i = 0; i < instructions.Count(); i++)
|
||||
{
|
||||
bool runIns = true;
|
||||
|
||||
// Replaces the rotation that gets passed to DrawHeadHair with one that is based the current 'true' head orientation
|
||||
if (i + 8 < instructions.Count() && ins[i + 8].opcode == OpCodes.Call && ins[i + 8].operand != null && ins[i + 8].OperandIs(AccessTools.DeclaredMethod(typeof(PawnRenderer), "DrawHeadHair")))
|
||||
{
|
||||
// Get the true head rotation
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_0);
|
||||
yield return new CodeInstruction(OpCodes.Ldfld, AccessTools.DeclaredField(typeof(PawnRenderer), "pawn"));
|
||||
yield return new CodeInstruction(OpCodes.Ldloc, (object)7); // local body facing
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_S, (object)6); // renderer flags
|
||||
yield return new CodeInstruction(OpCodes.Call, AccessTools.DeclaredMethod(typeof(AnimationPatchUtility), "PawnHeadRotInAnimation"));
|
||||
yield return new CodeInstruction(OpCodes.Stloc_S, (object)7); // set local body facing to true head facing
|
||||
|
||||
// Pass this head rotation to a new DrawHeadHair call
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_0);
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_1);
|
||||
yield return new CodeInstruction(OpCodes.Ldloc_S, (object)6);
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_2);
|
||||
yield return new CodeInstruction(OpCodes.Ldloc_S, (object)7); // local true head facing
|
||||
yield return new CodeInstruction(OpCodes.Ldloc_S, (object)7); // local true head facing
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_S, (object)5); // bodyDrawType
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_S, (object)6); // renderer flags
|
||||
yield return new CodeInstruction(OpCodes.Call, AccessTools.DeclaredMethod(typeof(PawnRenderer), "DrawHeadHair"));
|
||||
|
||||
// Skip the original call to DrawHeadHair
|
||||
i = i + 8;
|
||||
|
||||
runIns = false;
|
||||
}
|
||||
|
||||
if (runIns)
|
||||
{
|
||||
yield return ins[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[StaticConstructorOnStartup]
|
||||
[HarmonyPatch(typeof(PawnGraphicSet), "ResolveAllGraphics")]
|
||||
public static class HarmonyPatch_PawnGraphicSet_ResolveAllGraphics
|
||||
{
|
||||
public static void Postfix(PawnGraphicSet __instance)
|
||||
{
|
||||
if (__instance?.pawn?.apparel == null)
|
||||
{ return; }
|
||||
|
||||
if (__instance.pawn.GetAnimationData() != null)
|
||||
{ return; }
|
||||
|
||||
if (__instance.pawn.apparel.WornApparel.NullOrEmpty() == false)
|
||||
{
|
||||
foreach(Apparel apparel in __instance.pawn.apparel.WornApparel)
|
||||
{
|
||||
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
|
||||
|
||||
if (comp != null)
|
||||
{ comp.isBeingWorn = true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
460
Source/Scripts/Patches/HarmonyPatch_RJW.cs
Normal file
460
Source/Scripts/Patches/HarmonyPatch_RJW.cs
Normal file
|
@ -0,0 +1,460 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using rjw;
|
||||
using Rimworld_Animations;
|
||||
using RJW_ToysAndMasturbation;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[HarmonyPatch(typeof(JobDriver_Sex), "setup_ticks")]
|
||||
public static class HarmonyPatch_JobDriver_Sex_setup_ticks
|
||||
{
|
||||
public static void Postfix(ref JobDriver_Sex __instance)
|
||||
{
|
||||
// Sets ticks so that the orgasm meter starts empty, plus stop any running animations
|
||||
HarmonyPatch_JobDriver_Masturbate_setup_ticks.Postfix(ref __instance);
|
||||
|
||||
// Invite another for a threesome?
|
||||
if (RJWHookupSettings.QuickHookupsEnabled &&
|
||||
__instance is JobDriver_SexBaseInitiator &&
|
||||
__instance.pawn.GetAllSexParticipants().Count == 2 &&
|
||||
(__instance is JobDriver_JoinInSex) == false &&
|
||||
Random.value < BasicSettings.chanceForOtherToJoinInSex)
|
||||
{
|
||||
DebugMode.Message("Find another to join in sex");
|
||||
|
||||
Pawn pawn = __instance.pawn;
|
||||
List<Pawn> candidates = new List<Pawn>();
|
||||
float radius = 4f;
|
||||
|
||||
foreach (Thing thing in GenRadial.RadialDistinctThingsAround(pawn.Position, pawn.Map, radius, true))
|
||||
{
|
||||
Pawn other = thing as Pawn;
|
||||
|
||||
// Find candidates to invite
|
||||
if (other != null && (int)SexInteractionUtility.CheckSexJobAgainstMorals(other, __instance, out Precept precept) <= 0 &&
|
||||
SexInteractionUtility.PawnCanInvitePasserbyForSex(other, pawn.GetAllSexParticipants()))
|
||||
{
|
||||
DebugMode.Message(other.NameShortColored + " is a potential candidate");
|
||||
candidates.Add(other);
|
||||
}
|
||||
}
|
||||
|
||||
// Invite a random candidate (weighted by attraction)
|
||||
if (candidates.Count > 0)
|
||||
{
|
||||
Pawn invitedPawn = candidates.RandomElementByWeight(x => SexAppraiser.would_fuck(pawn, x, false, false, true) + SexAppraiser.would_fuck(pawn.GetSexPartner(), x, false, false, true));
|
||||
pawn.GetSexInitiator().IsInBed(out Building bed);
|
||||
|
||||
DebugMode.Message(invitedPawn.NameShortColored + " was invited to join in sex");
|
||||
|
||||
Job job = new Job(DefDatabase<JobDef>.GetNamed("JoinInSex", false), pawn.GetSexPartner(), bed);
|
||||
invitedPawn.jobs.TryTakeOrderedJob(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(JobDriver_Masturbate), "setup_ticks")]
|
||||
public static class HarmonyPatch_JobDriver_Masturbate_setup_ticks
|
||||
{
|
||||
// Sets ticks so that the orgasm meter starts empty, plus stop any running animations
|
||||
public static void Postfix(ref JobDriver_Sex __instance)
|
||||
{
|
||||
__instance.sex_ticks = __instance.duration;
|
||||
|
||||
CompBodyAnimator comp = __instance.pawn.TryGetComp<CompBodyAnimator>();
|
||||
|
||||
if (comp != null && comp.isAnimating)
|
||||
{ comp.isAnimating = false; }
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(JobDriver_SexBaseInitiator), "Start")]
|
||||
public static class HarmonyPatch_JobDriver_SexBaseInitiator_Start
|
||||
{
|
||||
public static bool MustRerollHumping(Pawn pawn, SexProps sexProps)
|
||||
{
|
||||
if (sexProps?.dictionaryKey?.defName == null || sexProps.dictionaryKey.defName != "Masturbation_Humping")
|
||||
{ return false; }
|
||||
|
||||
if (pawn.IsInBed(out Building bed))
|
||||
{ return false; }
|
||||
|
||||
DebugMode.Message("Not in bed, cannot do requested action");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static float RandomMasturbationWeights(InteractionDef interactionDef, Pawn pawn)
|
||||
{
|
||||
bool hasBed = pawn.IsInBed(out Building bed);
|
||||
|
||||
if (interactionDef.defName == "Masturbation_Breastjob" && Genital_Helper.has_breasts(pawn)) { return BasicSettings.breastsMasturbationChance; }
|
||||
if (interactionDef.defName == "Masturbation_HandjobA" && Genital_Helper.has_anus(pawn)) { return BasicSettings.analMasturbationChance; }
|
||||
if (interactionDef.defName == "Masturbation_HandjobP" && Genital_Helper.has_penis_fertile(pawn)) { return BasicSettings.genitalMasturbationChance; }
|
||||
if (interactionDef.defName == "Masturbation_HandjobV" && Genital_Helper.has_vagina(pawn)) { return BasicSettings.genitalMasturbationChance; }
|
||||
if (interactionDef.defName == "Masturbation_Humping" && hasBed) { return BasicSettings.humpingMasturbationChance; }
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
// Adds weights to masturbation type selection
|
||||
public static void Prefix(ref JobDriver_SexBaseInitiator __instance)
|
||||
{
|
||||
if (__instance.Sexprops == null)
|
||||
{ __instance.Sexprops = __instance.pawn.GetRMBSexPropsCache(); }
|
||||
|
||||
if (__instance is JobDriver_Masturbate && (__instance.Sexprops == null || MustRerollHumping(__instance.pawn, __instance.Sexprops)))
|
||||
{
|
||||
DebugMode.Message("No valid sexprops provided. Generating new interaction...");
|
||||
|
||||
SexProps sexProps = new SexProps();
|
||||
sexProps.pawn = __instance.pawn;
|
||||
sexProps.partner = __instance.pawn;
|
||||
sexProps.sexType = xxx.rjwSextype.Masturbation;
|
||||
|
||||
List<InteractionDef> interactionDefs = DefDatabase<InteractionDef>.AllDefs.Where(x => x.HasModExtension<InteractionExtension>()).ToList();
|
||||
Dictionary<rjw.Modules.Interactions.Objects.InteractionWithExtension, float> interactionsPlusWeights = new Dictionary<rjw.Modules.Interactions.Objects.InteractionWithExtension, float>();
|
||||
|
||||
foreach (InteractionDef interactionDef in interactionDefs)
|
||||
{
|
||||
var interaction = rjw.Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(interactionDef);
|
||||
|
||||
if (interaction.Extension.rjwSextype != xxx.rjwSextype.Masturbation.ToStringSafe())
|
||||
{ continue; }
|
||||
|
||||
interactionsPlusWeights.Add(interaction, RandomMasturbationWeights(interaction.Interaction, sexProps.pawn));
|
||||
}
|
||||
|
||||
var selectedInteraction = interactionsPlusWeights.RandomElementByWeight(x => x.Value).Key;
|
||||
|
||||
sexProps.dictionaryKey = selectedInteraction.Interaction;
|
||||
sexProps.rulePack = selectedInteraction.Extension.rulepack_defs.RandomElement();
|
||||
|
||||
DebugMode.Message("Generated interaction: " + sexProps.dictionaryKey.defName);
|
||||
DebugMode.Message(sexProps.rulePack);
|
||||
|
||||
__instance.Sexprops = sexProps;
|
||||
}
|
||||
}
|
||||
|
||||
// Adds in option for animated masturbation
|
||||
public static void Postfix(ref JobDriver_SexBaseInitiator __instance)
|
||||
{
|
||||
// Allow solo animations to be played
|
||||
if (__instance is JobDriver_Masturbate && __instance.pawn.GetAnimationData() == null)
|
||||
{ PickMasturbationAnimation(__instance.pawn, __instance.Sexprops); }
|
||||
|
||||
// Allow make out animations to be played
|
||||
if (__instance.pawn.GetAnimationData() == null)
|
||||
{ PickMakeOutAnimation(__instance.pawn, __instance.Sexprops); }
|
||||
|
||||
// If there is no animation to play, exit
|
||||
if (__instance.pawn.GetAnimationData() == null)
|
||||
{ return; }
|
||||
|
||||
// Get animation data
|
||||
AnimationDef anim = __instance.pawn.GetAnimationData()?.animationDef;
|
||||
List<Pawn> pawnsToAnimate = __instance.pawn.GetAllSexParticipants();
|
||||
|
||||
// Sync animations across participants
|
||||
foreach (Pawn participant in pawnsToAnimate)
|
||||
{
|
||||
JobDriver_Sex jobdriver = participant.jobs.curDriver as JobDriver_Sex;
|
||||
|
||||
if (jobdriver == null)
|
||||
{ continue; }
|
||||
|
||||
// Animation timing reset
|
||||
jobdriver.orgasms = 0;
|
||||
jobdriver.ticks_left = AnimationPatchUtility.FindTrueAnimationLength(participant, out int orgasmTick);
|
||||
jobdriver.ticksLeftThisToil = jobdriver.ticks_left;
|
||||
jobdriver.sex_ticks = orgasmTick;
|
||||
jobdriver.duration = jobdriver.sex_ticks;
|
||||
jobdriver.orgasmstick = 0;
|
||||
|
||||
// Reset anchor and animation for sex toys
|
||||
CompThingAnimator sexToyCompThingAnimator = ((Thing)jobdriver.job.GetTarget(TargetIndex.A)).TryGetComp<CompThingAnimator>();
|
||||
|
||||
if (sexToyCompThingAnimator != null)
|
||||
{
|
||||
DebugMode.Message("Using sex toy - " + jobdriver.job.GetTarget(TargetIndex.A));
|
||||
|
||||
__instance.pawn.IsInBed(out Building bed);
|
||||
Vector3 anchor = AnimationPatchUtility.GetAnchorPosition(__instance.pawn, bed) - new Vector3(0.5f, 0, 0.5f);
|
||||
AccessTools.Field(typeof(CompThingAnimator), "anchor").SetValue(sexToyCompThingAnimator, anchor);
|
||||
}
|
||||
|
||||
// Determine where pawns are to toss clothes
|
||||
if (participant?.apparel?.WornApparel != null)
|
||||
{
|
||||
IntVec3 apparelCell = MathUtility.FindRandomCellNearPawn(participant, 4);
|
||||
|
||||
foreach (Apparel apparel in participant.apparel.WornApparel)
|
||||
{
|
||||
CompApparelVisibility compApparelVisibility = apparel.TryGetComp<CompApparelVisibility>();
|
||||
|
||||
if (compApparelVisibility != null)
|
||||
{ compApparelVisibility.GenerateFloorPosition(apparelCell, new Vector2(0f, 0.125f)); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void PickMasturbationAnimation(Pawn pawn, SexProps sexProps = null)
|
||||
{
|
||||
if (pawn.TryGetComp<CompBodyAnimator>() == null)
|
||||
{ Log.Error("Error: " + pawn.Name + " of race " + pawn.def.defName + " does not have CompBodyAnimator attached!"); return; }
|
||||
|
||||
pawn.TryGetComp<CompBodyAnimator>().isAnimating = false;
|
||||
|
||||
List<Pawn> pawnsToAnimate = new List<Pawn>() { pawn };
|
||||
AnimationDef anim = null;
|
||||
|
||||
// Get random animation based on interaction type
|
||||
if (sexProps != null)
|
||||
{
|
||||
var interaction = rjw.Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(sexProps.dictionaryKey);
|
||||
InteractionDef interactionDef = interaction.Interaction;
|
||||
|
||||
DebugMode.Message("Finding animations that match " + interactionDef.defName);
|
||||
|
||||
List<AnimationDef> anims = new List<AnimationDef>();
|
||||
|
||||
foreach (AnimationDef _anim in DefDatabase<AnimationDef>.AllDefs)
|
||||
{
|
||||
if (_anim?.actors?.Count == 1 &&
|
||||
_anim.sexTypes != null && _anim.sexTypes.Contains(xxx.rjwSextype.Masturbation) &&
|
||||
_anim.interactionDefTypes != null && _anim.interactionDefTypes.Contains(interactionDef.defName) &&
|
||||
AnimationUtility.GenitalCheckForPawn(_anim.actors[0].requiredGenitals, pawn, out string failReason))
|
||||
{ anims.Add(_anim); }
|
||||
}
|
||||
|
||||
if (anims != null && anims.Any())
|
||||
{ anim = anims.RandomElement(); }
|
||||
}
|
||||
|
||||
// If no animation exists, pick one at random
|
||||
if (anim == null)
|
||||
{ anim = AnimationUtility.tryFindAnimation(ref pawnsToAnimate, xxx.rjwSextype.Masturbation, sexProps); }
|
||||
|
||||
if (anim == null)
|
||||
{ DebugMode.Message("No animation found"); return; }
|
||||
|
||||
// Start animation
|
||||
DebugMode.Message("Playing " + anim.defName);
|
||||
|
||||
pawn.IsInBed(out Building bed);
|
||||
|
||||
if (bed != null)
|
||||
{ pawn.TryGetComp<CompBodyAnimator>().setAnchor(bed); }
|
||||
|
||||
else
|
||||
{ pawn.TryGetComp<CompBodyAnimator>().setAnchor(pawn.Position); }
|
||||
|
||||
pawn.TryGetComp<CompBodyAnimator>().StartAnimation(anim, pawnsToAnimate, 0, GenTicks.TicksGame % 2 == 0, true, bed == null);
|
||||
|
||||
// Hide hearts if necessary
|
||||
if (!AnimationSettings.hearts)
|
||||
{ (pawn.jobs.curDriver as JobDriver_Sex).ticks_between_hearts = System.Int32.MaxValue; }
|
||||
}
|
||||
|
||||
public static void PickMakeOutAnimation(Pawn pawn, SexProps sexProps = null)
|
||||
{
|
||||
if (pawn.TryGetComp<CompBodyAnimator>() == null)
|
||||
{ Log.Error("Error: " + pawn.Name + " of race " + pawn.def.defName + " does not have CompBodyAnimator attached!"); return; }
|
||||
|
||||
List<Pawn> pawnsToAnimate = pawn.GetAllSexParticipants();
|
||||
|
||||
if (sexProps.sexType != xxx.rjwSextype.Oral || pawnsToAnimate.Count != 2)
|
||||
{ return; }
|
||||
|
||||
List<AnimationDef> kissingAnims = DefDatabase<AnimationDef>.AllDefs.Where(x => x.defName.Contains("Kiss")).ToList();
|
||||
AnimationDef anim = kissingAnims[Random.Range(0, kissingAnims.Count)];
|
||||
|
||||
if (anim == null)
|
||||
{ DebugMode.Message("No animation found"); return; }
|
||||
|
||||
bool mirror = GenTicks.TicksGame % 2 == 0;
|
||||
|
||||
// Start animation
|
||||
DebugMode.Message("Playing " + anim.defName);
|
||||
|
||||
foreach (Pawn participant in pawnsToAnimate)
|
||||
{
|
||||
participant.TryGetComp<CompBodyAnimator>().setAnchor(pawnsToAnimate[0].Position);
|
||||
participant.TryGetComp<CompBodyAnimator>().StartAnimation(anim, pawnsToAnimate, pawnsToAnimate.IndexOf(participant), mirror);
|
||||
|
||||
// Hide hearts if necessary
|
||||
if (!AnimationSettings.hearts)
|
||||
{ (participant.jobs.curDriver as JobDriver_Sex).ticks_between_hearts = System.Int32.MaxValue; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(JobDriver_Sex), "SexTick")]
|
||||
public static class HarmonyPatch_JobDriver_Sex_SexTick
|
||||
{
|
||||
// If pawns don't have privacy, they'll stop having sex
|
||||
public static void Postfix(ref JobDriver_Sex __instance, Pawn pawn)
|
||||
{
|
||||
if (pawn.IsHashIntervalTick(90))
|
||||
{
|
||||
if (pawn.IsMasturbating() && pawn.HasPrivacy(8f) == false)
|
||||
{ pawn.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false); }
|
||||
|
||||
else if (pawn.IsHavingSex())
|
||||
{
|
||||
bool havePrivacy = true;
|
||||
List<Pawn> participants = pawn.GetAllSexParticipants();
|
||||
|
||||
foreach (Pawn participant in participants)
|
||||
{
|
||||
if (participant.HasPrivacy(8f) == false)
|
||||
{ havePrivacy = false; }
|
||||
}
|
||||
|
||||
if (__instance.Sexprops != null && (__instance.Sexprops.isRape || __instance.Sexprops.isWhoring))
|
||||
{ return; }
|
||||
|
||||
if (havePrivacy == false)
|
||||
{
|
||||
foreach (Pawn participant in participants)
|
||||
{ participant.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(JobDriver_Sex), "Orgasm")]
|
||||
public static class HarmonyPatch_JobDriver_Sex_Orgasm
|
||||
{
|
||||
// Stops orgasm triggering more than once per animation
|
||||
public static bool Prefix(ref JobDriver_Sex __instance)
|
||||
{
|
||||
if (__instance.orgasms > 0)
|
||||
{ return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ParticipantsDesireMoreSex(JobDriver_Sex jobdriver)
|
||||
{
|
||||
List<Pawn> participants = jobdriver.pawn.GetAllSexParticipants();
|
||||
|
||||
float satisfaction = 0f;
|
||||
|
||||
foreach (Pawn pawn in participants)
|
||||
{
|
||||
Need_Sex sexNeed = pawn?.needs?.TryGetNeed<Need_Sex>();
|
||||
|
||||
if (sexNeed == null)
|
||||
{ satisfaction += 1; continue; }
|
||||
|
||||
satisfaction += sexNeed.CurLevelPercentage;
|
||||
}
|
||||
|
||||
return Rand.Chance(1 - satisfaction / participants.Count);
|
||||
}
|
||||
|
||||
// Alows the starting of a new animation cycle at the end of the current one
|
||||
public static void Postfix(ref JobDriver_Sex __instance)
|
||||
{
|
||||
if (__instance.orgasms > 0)
|
||||
{ __instance.sex_ticks = 0; }
|
||||
|
||||
if (__instance is JobDriver_SexBaseInitiator == false || __instance is JobDriver_JoinInSex)
|
||||
{ return; }
|
||||
|
||||
if (__instance.Sexprops != null && (__instance.Sexprops.isRape || __instance.Sexprops.isWhoring))
|
||||
{ return; }
|
||||
|
||||
if (__instance.ticksLeftThisToil <= 1 && (__instance.neverendingsex || ParticipantsDesireMoreSex(__instance)))
|
||||
{
|
||||
List<Pawn> participants = __instance.pawn.GetAllSexParticipants();
|
||||
|
||||
if (participants.Count == 2)
|
||||
{
|
||||
Job job = JobMaker.MakeJob(participants[0].CurJobDef, participants[0], participants[0].jobs.curJob.targetC);
|
||||
participants[1].jobs.StartJob(job, JobCondition.Succeeded);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(JobDriver_SexBaseInitiator), "End")]
|
||||
public static class HarmonyPatch_JobDriver_Sex_End
|
||||
{
|
||||
// Clear all partners out when sex ends to prevent issues with threesome animations
|
||||
public static void Postfix(ref JobDriver_SexBaseInitiator __instance)
|
||||
{
|
||||
if (__instance.Partner != null && __instance?.Partner?.jobs?.curDriver != null && __instance.Partner.Dead == false && __instance.Partner?.jobs.curDriver is JobDriver_SexBaseReciever)
|
||||
{
|
||||
foreach (Pawn participant in (__instance.Partner?.jobs.curDriver as JobDriver_SexBaseReciever).parteners.ToList())
|
||||
{ participant.jobs.EndCurrentJob(JobCondition.Succeeded, false, true); }
|
||||
|
||||
(__instance.Partner?.jobs.curDriver as JobDriver_SexBaseReciever).parteners.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(SexUtility), "AfterMasturbation")]
|
||||
public static class HarmonyPatch_SexUtility_AfterMasturbation
|
||||
{
|
||||
// Removes excess calls to generate filth
|
||||
public static bool Prefix(SexProps props)
|
||||
{
|
||||
var methodInfo = AccessTools.Method(typeof(SexUtility), "IncreaseTicksToNextLovin", null, null);
|
||||
methodInfo.Invoke(null, new object[] { props.pawn });
|
||||
AfterSexUtility.UpdateRecords(props);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Genital_Helper), "has_mouth")]
|
||||
public static class HarmonyPatch_Genital_Helper_has_mouth
|
||||
{
|
||||
// Fixes mouth check
|
||||
public static bool Prefix(ref bool __result, Pawn pawn)
|
||||
{
|
||||
__result = pawn.health.hediffSet.GetNotMissingParts().Any(x => x.def.defName.ToLower().ContainsAny("mouth", "teeth", "jaw", "beak"));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(SexUtility), "DrawNude")]
|
||||
public static class HarmonyPatch_SexUtility_DrawNude
|
||||
{
|
||||
public static bool Prefix(Pawn pawn, bool keep_hat_on)
|
||||
{
|
||||
if (!xxx.is_human(pawn)) return false;
|
||||
if (pawn.Map != Find.CurrentMap) return false;
|
||||
|
||||
pawn.Drawer.renderer.graphics.ClearCache();
|
||||
pawn.Drawer.renderer.graphics.apparelGraphics.Clear();
|
||||
|
||||
ApparelAnimationUtility.DetermineApparelToKeepOn(pawn);
|
||||
|
||||
foreach (Apparel apparel in pawn.apparel.WornApparel)
|
||||
{
|
||||
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
|
||||
|
||||
if ((comp == null || comp.isBeingWorn) && ApparelGraphicRecordGetter.TryGetGraphicApparel(apparel, pawn.story.bodyType, out ApparelGraphicRecord item))
|
||||
{ pawn.Drawer.renderer.graphics.apparelGraphics.Add(item); }
|
||||
}
|
||||
|
||||
GlobalTextureAtlasManager.TryMarkPawnFrameSetDirty(pawn);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
37
Source/Scripts/Patches/HarmonyPatch_RimNudeWorld.cs
Normal file
37
Source/Scripts/Patches/HarmonyPatch_RimNudeWorld.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Verse;
|
||||
using HarmonyLib;
|
||||
using RimNudeWorld;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
public static class HarmonyPatch_RimNudeWorld
|
||||
{
|
||||
/*static HarmonyPatch_RimNudeWorld()
|
||||
{
|
||||
try
|
||||
{
|
||||
((Action)(() =>
|
||||
{
|
||||
if (LoadedModManager.RunningModsListForReading.Any(x => x.PackageIdPlayerFacing == "shauaputa.rimnudeworld"))
|
||||
{
|
||||
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("RevealingApparel.HarmonyPatch_DrawAddons"), "Postfix"),
|
||||
prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_RimNudeWorld), "Prefix_DrawAddons")));
|
||||
}
|
||||
}))();
|
||||
}
|
||||
catch (TypeLoadException) { }
|
||||
}
|
||||
|
||||
// Patch RimNudeWorld to override the revealing apparel feature; this task is handled by the new apparel settings system
|
||||
public static bool Prefix_DrawAddons()
|
||||
{
|
||||
return false;
|
||||
}*/
|
||||
}
|
||||
}
|
369
Source/Scripts/Patches/HarmonyPatch_Rimworld_Animations.cs
Normal file
369
Source/Scripts/Patches/HarmonyPatch_Rimworld_Animations.cs
Normal file
|
@ -0,0 +1,369 @@
|
|||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using AlienRace;
|
||||
using Rimworld_Animations;
|
||||
using rjw;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
public static class HarmonyPatch_Rimworld_Animations
|
||||
{
|
||||
static HarmonyPatch_Rimworld_Animations()
|
||||
{
|
||||
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.AnimationUtility"), "GenitalCheckForPawn"),
|
||||
postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "PostFix_AnimationUtility_GenitalCheckForPawn")));
|
||||
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.CompBodyAnimator"), "setAnchor", new Type[] { typeof(Thing) }),
|
||||
prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Prefix_HarmonyPatch_CompBodyAnimator_setAnchor")));
|
||||
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.HarmonyPatch_JobDriver_SexBaseInitiator_Start"), "RerollAnimations"),
|
||||
postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Postfix_RerollAnimations")));
|
||||
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.HarmonyPatch_AlienRace"), "Prefix_AnimateHeadAddons"),
|
||||
prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Prefix_DrawAddons")));
|
||||
}
|
||||
|
||||
// Extend the animation selector's body part check to include hands and whether the pawn is in bed or not
|
||||
public static void PostFix_AnimationUtility_GenitalCheckForPawn(ref bool __result, List<string> requiredGenitals, Pawn pawn, ref string failReason)
|
||||
{
|
||||
int handCount = 0;
|
||||
bool pawnInBed = pawn.IsInBed(out Building bed);
|
||||
|
||||
var hands = pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def.defName == "Hand");
|
||||
if (hands != null)
|
||||
{ handCount = hands.Count(); }
|
||||
|
||||
if (requiredGenitals.NullOrEmpty())
|
||||
{ return; }
|
||||
|
||||
if (requiredGenitals.Contains("OneHand") && handCount < 1)
|
||||
{ failReason = "missing hand"; __result = false; }
|
||||
|
||||
if (requiredGenitals.Contains("TwoHands") && handCount < 2)
|
||||
{ failReason = "missing hand(s)"; __result = false; }
|
||||
|
||||
if (requiredGenitals.Contains("Bed") && pawnInBed == false)
|
||||
{ failReason = "pawn is not in bed"; __result = false; }
|
||||
|
||||
if (requiredGenitals.Contains("NoBed") && pawnInBed)
|
||||
{ failReason = "pawn is in bed"; __result = false; }
|
||||
}
|
||||
|
||||
// Override CompBodyAnimator's anchors
|
||||
public static bool Prefix_HarmonyPatch_CompBodyAnimator_setAnchor(CompBodyAnimator __instance, Thing thing)
|
||||
{
|
||||
__instance.anchor = AnimationPatchUtility.GetAnchorPosition(__instance.pawn, thing);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Adds functionality to determine which apparel each actor should discard based on the animation they are running
|
||||
public static void Postfix_RerollAnimations(Pawn pawn)
|
||||
{
|
||||
AnimationDef anim = pawn.GetAnimationData()?.animationDef;
|
||||
|
||||
if (anim != null)
|
||||
{
|
||||
DebugMode.Message("Running animation: " + anim.defName);
|
||||
|
||||
List<Pawn> pawnsToAnimate = pawn.GetAllSexParticipants();
|
||||
Pawn Target = pawn.GetSexReceiver();
|
||||
|
||||
foreach (Pawn participant in pawnsToAnimate)
|
||||
{
|
||||
int actorID = (int)AccessTools.Field(typeof(CompBodyAnimator), "actor").GetValue(participant.TryGetComp<CompBodyAnimator>());
|
||||
DebugMode.Message("Participant " + actorID + ": " + participant.NameShortColored);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if a body addon is covered by apparel
|
||||
/*public static bool BodyAddonCoveredByApparel(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
|
||||
{
|
||||
foreach (ApparelGraphicRecord apparelGraphicRecord in pawn.Drawer.renderer.graphics.apparelGraphics)
|
||||
{
|
||||
Apparel apparel = apparelGraphicRecord.sourceApparel;
|
||||
|
||||
if (apparel.def.apparel.bodyPartGroups.Any(x => bodyAddon.hiddenUnderApparelFor.Contains(x)))
|
||||
{ return true; }
|
||||
}
|
||||
|
||||
return false;
|
||||
}*/
|
||||
|
||||
public static bool BodyAddonCoveredByWornApparel(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
|
||||
{
|
||||
if (bodyAddon?.hiddenUnderApparelFor == null || bodyAddon?.hiddenUnderApparelTag == null)
|
||||
{ return false; }
|
||||
|
||||
foreach (Apparel apparel in pawn.apparel.WornApparel)
|
||||
{
|
||||
if (ApparelAnimationUtility.BodyAddonCoveredByApparel(apparel, bodyAddon))
|
||||
{ return true; }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine if a body addon should be drawn
|
||||
public static bool CanDrawAddon(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
|
||||
{
|
||||
if (bodyAddon == null)
|
||||
{ return false; }
|
||||
|
||||
if (pawn.RaceProps.Animal)
|
||||
{ return true; }
|
||||
|
||||
Building_Bed building_Bed = pawn.CurrentBed();
|
||||
|
||||
if ((building_Bed == null ||
|
||||
building_Bed.def.building.bed_showSleeperBody ||
|
||||
bodyAddon.drawnInBed) && (bodyAddon.backstoryRequirement.NullOrEmpty() || pawn.story.AllBackstories.Any((Backstory x) => x.identifier == bodyAddon.backstoryRequirement)))
|
||||
{
|
||||
if (!bodyAddon.drawnDesiccated)
|
||||
{
|
||||
Corpse corpse = pawn.Corpse;
|
||||
if (corpse != null && corpse.GetRotStage() == RotStage.Dessicated)
|
||||
{ return false; }
|
||||
}
|
||||
|
||||
if (!bodyAddon.bodyPart.NullOrEmpty() &&
|
||||
!pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null).Any((BodyPartRecord bpr) => bpr.untranslatedCustomLabel == bodyAddon.bodyPart || bpr.def.defName == bodyAddon.bodyPart))
|
||||
{
|
||||
List<AlienPartGenerator.BodyAddonHediffGraphic> list = bodyAddon.hediffGraphics;
|
||||
bool flag;
|
||||
if (list == null)
|
||||
{ flag = false; }
|
||||
|
||||
else
|
||||
{ flag = list.Any((AlienPartGenerator.BodyAddonHediffGraphic bahg) => bahg.hediff == HediffDefOf.MissingBodyPart); }
|
||||
|
||||
if (!flag)
|
||||
{ return false; }
|
||||
}
|
||||
|
||||
if ((pawn.gender == Gender.Female) ? bodyAddon.drawForFemale : bodyAddon.drawForMale)
|
||||
{
|
||||
if (bodyAddon.bodyTypeRequirement.NullOrEmpty() || pawn.story.bodyType.ToString() == bodyAddon.bodyTypeRequirement)
|
||||
{
|
||||
bool renderClothes = true;
|
||||
|
||||
if (Find.WindowStack.currentlyDrawnWindow is Page_ConfigureStartingPawns)
|
||||
{ renderClothes = (bool)AccessTools.Field(typeof(Page_ConfigureStartingPawns), "renderClothes").GetValue(Find.WindowStack.currentlyDrawnWindow); }
|
||||
|
||||
else
|
||||
{ renderClothes = pawn.Drawer.renderer.graphics.apparelGraphics.Count > 0; }
|
||||
|
||||
bool conditionA = !BodyAddonCoveredByWornApparel(pawn, bodyAddon);
|
||||
bool conditionB = !renderClothes;
|
||||
bool conditionC = pawn.GetPosture() == PawnPosture.Standing;
|
||||
bool conditionD = (pawn.GetPosture() == PawnPosture.LayingOnGroundNormal || pawn.GetPosture() == PawnPosture.LayingOnGroundFaceUp) && bodyAddon.drawnOnGround;
|
||||
bool conditionE = pawn.GetPosture() == PawnPosture.LayingInBed && bodyAddon.drawnInBed;
|
||||
|
||||
return (conditionA || conditionB) && (conditionC || conditionD || conditionE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replacement patch for AlienRace to draw the body addons
|
||||
public static bool Prefix_DrawAddons(PawnRenderFlags renderFlags, Vector3 vector, Vector3 headOffset, Pawn pawn, Quaternion quat, Rot4 rotation)
|
||||
{
|
||||
if (!(pawn.def is ThingDef_AlienRace alienProps) || renderFlags.FlagSet(PawnRenderFlags.Invisible))
|
||||
{ return false; }
|
||||
|
||||
// Try to draw apparel thrown on ground
|
||||
if (ApparelSettings.clothesThrownOnGround)
|
||||
{ ApparelAnimationUtility.TryToDrawApparelOnFloor(pawn); }
|
||||
|
||||
// Get components
|
||||
List<AlienPartGenerator.BodyAddon> bodyAddons = alienProps.alienRace.generalSettings.alienPartGenerator.bodyAddons.ToList();
|
||||
AlienPartGenerator.AlienComp alienComp = pawn.GetComp<AlienPartGenerator.AlienComp>();
|
||||
CompBodyAnimator pawnAnimator = pawn.TryGetComp<CompBodyAnimator>();
|
||||
|
||||
// Get available hands
|
||||
var hands = pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def.defName == "Hand");
|
||||
int handsAvailableCount = hands.Count();
|
||||
|
||||
// Sort addons by their layer offset, otherwise body parts will actualy be layered according to their position in the list
|
||||
// Note that sorting the addons directly seems to mess up relations between lists need by AlienRace
|
||||
var sortedBodyAddons = bodyAddons.Select((x, i) => new KeyValuePair<AlienPartGenerator.BodyAddon, int>(x, i)).OrderBy(x => x.Key.offsets.GetOffset(rotation).layerOffset).ToList();
|
||||
List<int> idxBodyAddons = sortedBodyAddons.Select(x => x.Value).ToList();
|
||||
|
||||
for (int idx = 0; idx < idxBodyAddons.Count; idx++)
|
||||
{
|
||||
int i = idxBodyAddons[idx];
|
||||
AlienPartGenerator.BodyAddon bodyAddon = bodyAddons[i];
|
||||
BodyPartRecord bodyPartRecord = AnimationPatchUtility.GetBodyPartRecord(pawn, bodyAddon.bodyPart);
|
||||
|
||||
bool alignWithHead = bodyAddon.alignWithHead || (bodyPartRecord != null && bodyPartRecord.IsInGroup(BodyPartGroupDefOf.FullHead));
|
||||
|
||||
Graphic addonGraphic = alienComp.addonGraphics[i];
|
||||
//DebugMode.Message(" Trying to draw " + addonGraphic.path);
|
||||
|
||||
Rot4 apparentRotation = rotation;
|
||||
if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawnAnimator != null && pawnAnimator.isAnimating)
|
||||
{ apparentRotation = alignWithHead ? pawnAnimator.headFacing : pawnAnimator.bodyFacing; }
|
||||
|
||||
AlienPartGenerator.RotationOffset defaultOffsets = bodyAddon.defaultOffsets.GetOffset(apparentRotation);
|
||||
Vector3 bodyTypeOffset = (defaultOffsets != null) ? defaultOffsets.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, alienComp.crownType) : Vector3.zero;
|
||||
AlienPartGenerator.RotationOffset offsets = bodyAddon.offsets.GetOffset(apparentRotation);
|
||||
Vector3 vector2 = bodyTypeOffset + ((offsets != null) ? offsets.GetOffset(renderFlags.FlagSet(PawnRenderFlags.Portrait), pawn.story.bodyType, alienComp.crownType) : Vector3.zero);
|
||||
|
||||
// Offset private parts so that they render over tattoos but under apparel (rendering under tatoos looks weird)
|
||||
if ((bodyPartRecord != null && (bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.GenitalsBPG) || bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.ChestBPG) || bodyPartRecord.IsInGroup(PatchBodyPartGroupDefOf.AnusBPG))) ||
|
||||
addonGraphic.path.ToLower().Contains("belly"))
|
||||
{
|
||||
vector2.y = (vector2.y + 0.40f) / 1000f + 0.012f;
|
||||
|
||||
// Erected penises should be drawn over apparel
|
||||
if (pawn.RaceProps.Humanlike &&
|
||||
addonGraphic.path.ToLower().Contains("penis") &&
|
||||
addonGraphic.path.ToLower().Contains("flaccid") == false &&
|
||||
BodyAddonCoveredByWornApparel(pawn, bodyAddon) == false &&
|
||||
apparentRotation == Rot4.South)
|
||||
{ vector2.y += 0.010f; }
|
||||
}
|
||||
|
||||
// Otherwise use the standard offsets
|
||||
else
|
||||
{ vector2.y = 0.3f + vector2.y; }
|
||||
|
||||
if (!bodyAddon.inFrontOfBody)
|
||||
{ vector2.y *= -1f; }
|
||||
|
||||
float bodyAddonAngle = bodyAddon.angle;
|
||||
|
||||
if (apparentRotation == Rot4.North)
|
||||
{
|
||||
if (bodyAddon.layerInvert)
|
||||
{ vector2.y = -vector2.y; }
|
||||
|
||||
bodyAddonAngle = 0f;
|
||||
}
|
||||
|
||||
if (apparentRotation == Rot4.East)
|
||||
{
|
||||
bodyAddonAngle = -bodyAddonAngle;
|
||||
vector2.x = -vector2.x;
|
||||
}
|
||||
|
||||
Quaternion addonRotation = quat;
|
||||
Quaternion quatAdditional = Quaternion.identity;
|
||||
|
||||
float finalAngle = 0;
|
||||
|
||||
if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawnAnimator != null && pawnAnimator.isAnimating)
|
||||
{
|
||||
if (pawnAnimator.controlGenitalAngle && bodyAddon?.hediffGraphics != null && !bodyAddon.hediffGraphics.NullOrEmpty() && bodyAddon.hediffGraphics[0]?.path != null && (bodyAddon.hediffGraphics[0].path.Contains("Penis") || bodyAddon.hediffGraphics[0].path.Contains("penis")))
|
||||
{
|
||||
float bodyAngle = pawnAnimator.bodyAngle;
|
||||
addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
|
||||
|
||||
float anglePenis = AnimationSettings.controlGenitalRotation ? pawnAnimator.genitalAngle : 0f;
|
||||
anglePenis = anglePenis < 0 ? 360 - (360 % anglePenis) : anglePenis;
|
||||
quatAdditional = Quaternion.AngleAxis(angle: anglePenis, axis: Vector3.up);
|
||||
|
||||
finalAngle = bodyAngle + anglePenis;
|
||||
}
|
||||
|
||||
else if (alignWithHead)
|
||||
{
|
||||
float headAngle = pawnAnimator.headAngle;
|
||||
headAngle = headAngle < 0 ? 360 - (360 % headAngle) : headAngle;
|
||||
addonRotation = Quaternion.AngleAxis(angle: headAngle, axis: Vector3.up);
|
||||
|
||||
finalAngle = pawnAnimator.bodyAngle + headAngle;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
float bodyAngle = pawnAnimator.bodyAngle;
|
||||
addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
|
||||
|
||||
finalAngle = bodyAngle;
|
||||
}
|
||||
}
|
||||
|
||||
// Fixes 'leaning left' issue with Yayo's animations
|
||||
else if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && (pawnAnimator == null || pawnAnimator.isAnimating == false))
|
||||
{
|
||||
float bodyAngle = addonRotation.eulerAngles.y;
|
||||
bodyAngle = bodyAngle < 0 ? 360 - (360 % bodyAngle) : bodyAngle;
|
||||
addonRotation = Quaternion.AngleAxis(angle: bodyAngle, axis: Vector3.up);
|
||||
}
|
||||
|
||||
if (alignWithHead && bodyAddon.alignWithHead == false)
|
||||
{ vector2 -= pawn.Drawer.renderer.BaseHeadOffsetAt(apparentRotation); }
|
||||
|
||||
Vector3 finalPosition = vector + (alignWithHead ? headOffset : Vector3.zero) + vector2.RotatedBy(angle: Mathf.Acos(f: Quaternion.Dot(a: Quaternion.identity, b: addonRotation)) * 2f * 57.29578f);
|
||||
|
||||
// Draw the addon if visible
|
||||
if (CanDrawAddon(pawn, bodyAddon))
|
||||
{
|
||||
GenDraw.DrawMeshNowOrLater(mesh: addonGraphic.MeshAt(rot: apparentRotation),
|
||||
loc: finalPosition,
|
||||
quat: Quaternion.AngleAxis(angle: bodyAddonAngle, axis: Vector3.up) * quatAdditional * addonRotation,
|
||||
mat: addonGraphic.MatAt(rot: apparentRotation), renderFlags.FlagSet(PawnRenderFlags.DrawNow));
|
||||
}
|
||||
|
||||
// Draw hand over the body part if required
|
||||
if (BasicSettings.showHands && !renderFlags.FlagSet(PawnRenderFlags.Portrait) && handsAvailableCount > 0)
|
||||
{
|
||||
if (HandAnimationUtility.TryToDrawHand(pawn, addonGraphic.path, finalPosition, finalAngle, rotation, renderFlags))
|
||||
{ handsAvailableCount--; }
|
||||
}
|
||||
}
|
||||
|
||||
// Body addons are sometimes are not appropriately concealed by long hair, so re-draw the pawn's hair here
|
||||
if (pawn.Drawer.renderer.graphics.headGraphic != null)
|
||||
{
|
||||
var methodInfo = AccessTools.Method(typeof(PawnRenderer), "DrawHeadHair", null, null);
|
||||
|
||||
Rot4 headFacing = pawnAnimator != null && pawnAnimator.isAnimating && !renderFlags.FlagSet(PawnRenderFlags.Portrait) ? pawnAnimator.headFacing : rotation;
|
||||
float headAngle = pawnAnimator != null && pawnAnimator.isAnimating && !renderFlags.FlagSet(PawnRenderFlags.Portrait) ? pawnAnimator.headAngle : quat.eulerAngles.y;
|
||||
RotDrawMode rotDrawMode = (RotDrawMode)AccessTools.Property(typeof(PawnRenderer), "CurRotDrawMode").GetValue(pawn.Drawer.renderer);
|
||||
|
||||
methodInfo.Invoke(pawn.Drawer.renderer, new object[] { vector + new Vector3(0f, YOffset_Head, 0f), headOffset, headAngle, headFacing, headFacing, rotDrawMode, renderFlags });
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// List of potentially useful layer offsets
|
||||
//private const float YOffset_CarriedThingUnder = -0.0028957527f;
|
||||
//private const float YOffset_Align_Behind = 0.0028957527f;
|
||||
//private const float YOffset_Body = 0.008687258f;
|
||||
//private const float YOffset_Interval_Clothes = 0.0028957527f;
|
||||
//private const float YOffset_Shell = 0.02027027f;
|
||||
private const float YOffset_Head = 0.023166021f;
|
||||
private const float YOffset_OnHead = 0.028957527f;
|
||||
//private const float YOffset_PostHead = 0.03185328f;
|
||||
//private const float YOffset_Tattoo = 0.0014478763f;
|
||||
//private const float YOffset_WoundOverlays1 = 0.009687258f;
|
||||
//private const float YOffset_WoundOverlays2 = 0.022166021f;
|
||||
|
||||
/* Details on the above
|
||||
Body = rootLoc + YOffset_Body; (~ 0.009)
|
||||
Tattoo = rootLoc + YOffset_Body + YOffset_Tattoo; (~ 0.010)
|
||||
BodyAddons (not protruding) = rootLoc + 0.011f; (~0.011)
|
||||
Body wounds (under clothes) = rootLoc + YOffset_WoundOverlays1; (~ 0.010)
|
||||
Apparel (not north) = rootLoc + YOffset_Shell; (~ 0.020)
|
||||
BodyAddons (protruding) = rootLoc + 0.011f + 0.010f; (~0.021)
|
||||
Apparel (north) = rootLoc + YOffset_Head; (~ 0.023)
|
||||
Body wounds (over clothes) = rootLoc + YOffset_WoundOverlays1 + YOffset_WoundOverlays2; (~ 0.03)
|
||||
|
||||
Head (not north) = rootLoc + YOffset_Head (~ 0.023);
|
||||
Head (north) = rootLoc + YOffset_Shell; (~ 0.020)
|
||||
Face tattoo = rootLoc + YOffset_OnHead - YOffset_Tattoo; (~ 0.028)
|
||||
Head wounds (under clothes) = rootLoc + YOffset_OnHead; (~ 0.029)
|
||||
Hair = rootLoc + YOffset_OnHead; (~ 0.029)
|
||||
Hat (over hair) = rootLoc + YOffset_PostHead; (~ 0.031)
|
||||
*/
|
||||
}
|
||||
}
|
76
Source/Scripts/Patches/HarmonyPatch_VisiblePants.cs
Normal file
76
Source/Scripts/Patches/HarmonyPatch_VisiblePants.cs
Normal file
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[StaticConstructorOnStartup]
|
||||
public static class HarmonyPatch_VisiblePants
|
||||
{
|
||||
static HarmonyPatch_VisiblePants()
|
||||
{
|
||||
try
|
||||
{
|
||||
((Action)(() =>
|
||||
{
|
||||
if (LoadedModManager.RunningModsListForReading.Any(x => x.PackageIdPlayerFacing == "XeoNovaDan.VisiblePants"))
|
||||
{
|
||||
(new Harmony("HeyLover")).Patch(AccessTools.Method(typeof(ApparelGraphicRecordGetter), "TryGetGraphicApparel"),
|
||||
prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_VisiblePants), "Prefix_ApparelGraphicRecordGetter_TryGetGraphicApparel")));
|
||||
}
|
||||
}))();
|
||||
}
|
||||
catch (TypeLoadException) { }
|
||||
}
|
||||
|
||||
public static bool Prefix_ApparelGraphicRecordGetter_TryGetGraphicApparel(ref bool __result, ref Apparel apparel, ref BodyTypeDef bodyType, out ApparelGraphicRecord rec)
|
||||
{
|
||||
rec = new ApparelGraphicRecord(null, null);
|
||||
|
||||
if (bodyType == null)
|
||||
{
|
||||
Log.Error("Found apparel graphic with undefined body type.");
|
||||
bodyType = BodyTypeDefOf.Male;
|
||||
}
|
||||
|
||||
if (apparel == null || apparel.WornGraphicPath.NullOrEmpty())
|
||||
{
|
||||
rec = new ApparelGraphicRecord(null, null);
|
||||
__result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
string path;
|
||||
|
||||
if (apparel.def.apparel.LastLayer == ApparelLayerDefOf.Overhead || apparel.def.apparel.LastLayer == ApparelLayerDefOf.EyeCover || PawnRenderer.RenderAsPack(apparel) || apparel.WornGraphicPath == BaseContent.PlaceholderImagePath || apparel.WornGraphicPath == BaseContent.PlaceholderGearImagePath)
|
||||
{ path = apparel.WornGraphicPath; }
|
||||
|
||||
else
|
||||
{ path = apparel.WornGraphicPath + "_" + bodyType.defName; }
|
||||
|
||||
Shader shader = ShaderDatabase.Cutout;
|
||||
|
||||
if (apparel.def.apparel.useWornGraphicMask)
|
||||
{ shader = ShaderDatabase.CutoutComplex; }
|
||||
|
||||
// Load the standard apparel graphic
|
||||
Graphic graphic = GraphicDatabase.Get<Graphic_Multi>(path, shader, apparel.def.graphicData.drawSize, apparel.DrawColor);
|
||||
|
||||
// This graphic may need to be masked if the apparel sits on the skin layer and does not cover the legs
|
||||
if (apparel.def.apparel.LastLayer == ApparelLayerDefOf.OnSkin && apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) && !apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs))
|
||||
{
|
||||
Graphic graphicWithApparelMask = GraphicDatabase.Get<Graphic_Multi>(path, ShaderDatabase.CutoutComplex, apparel.def.graphicData.drawSize, apparel.DrawColor, apparel.DrawColor, null, "Masks/apparel_shirt_mask_" + bodyType.defName);
|
||||
graphic = GraphicMaskingUtility.ApplyGraphicWithMasks(graphic, graphicWithApparelMask, true);
|
||||
//Log.Message("Applying apparel mask: Masks/apparel_shirt_mask_" + bodyType.defName + " to " + apparel.def.defName);
|
||||
}
|
||||
|
||||
rec = new ApparelGraphicRecord(graphic, apparel);
|
||||
__result = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
357
Source/Scripts/Settings/ApparelSettings.cs
Normal file
357
Source/Scripts/Settings/ApparelSettings.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
127
Source/Scripts/Settings/BasicSettings.cs
Normal file
127
Source/Scripts/Settings/BasicSettings.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
97
Source/Scripts/Settings/BasicSetttings.cs
Normal file
97
Source/Scripts/Settings/BasicSetttings.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
170
Source/Scripts/Utilities/AnimationPatchUtility.cs
Normal file
170
Source/Scripts/Utilities/AnimationPatchUtility.cs
Normal file
|
@ -0,0 +1,170 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using HarmonyLib;
|
||||
using Verse;
|
||||
using Rimworld_Animations;
|
||||
using rjw;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class AnimationPatchUtility
|
||||
{
|
||||
public static int FindTrueAnimationLength(Pawn pawn, out int orgasmTick)
|
||||
{
|
||||
orgasmTick = int.MaxValue;
|
||||
|
||||
ActorAnimationData actorAnimationData = pawn.GetAnimationData();
|
||||
CompBodyAnimator compBodyAnimator = pawn.TryGetComp<CompBodyAnimator>();
|
||||
|
||||
// No data
|
||||
if (actorAnimationData == null || compBodyAnimator == null)
|
||||
{
|
||||
DebugMode.Message("There is no actor animation data for " + pawn.NameShortColored);
|
||||
orgasmTick = 1500 + (int)(Rand.Value * 1000f);
|
||||
|
||||
return orgasmTick;
|
||||
}
|
||||
|
||||
AnimationDef anim = actorAnimationData.animationDef;
|
||||
int actorId = actorAnimationData.actorID;
|
||||
bool isQuickie = compBodyAnimator.fastAnimForQuickie;
|
||||
|
||||
int ticks = 0;
|
||||
|
||||
foreach (AnimationStage animStage in anim.animationStages)
|
||||
{
|
||||
// Legacy: skip the first stage of quickies if there's no playTimeTicksQuick values declared
|
||||
if (anim.animationStages.IndexOf(animStage) == 0 && isQuickie && anim.animationStages.Any(x => x.playTimeTicksQuick >= 0) == false)
|
||||
{ continue; }
|
||||
|
||||
int curr_tick = 0;
|
||||
|
||||
foreach (PawnKeyframe keyframe in (animStage.animationClips[actorId] as PawnAnimationClip).keyframes)
|
||||
{
|
||||
curr_tick += keyframe.tickDuration;
|
||||
|
||||
if (keyframe.soundEffect != null && keyframe.soundEffect == "Cum" && orgasmTick > (ticks + curr_tick))
|
||||
{ orgasmTick = ticks + curr_tick; }
|
||||
|
||||
if (isQuickie && animStage.playTimeTicksQuick > 0 && curr_tick >= animStage.playTimeTicksQuick)
|
||||
{ break; }
|
||||
}
|
||||
|
||||
ticks += isQuickie && animStage.playTimeTicksQuick > 0 && animStage.playTimeTicksQuick < animStage.playTimeTicks ? animStage.playTimeTicksQuick : animStage.playTimeTicks;
|
||||
}
|
||||
|
||||
// Orgasm tick not found
|
||||
if (orgasmTick > ticks)
|
||||
{
|
||||
// Safeguard for penial, vaginal and anal sex
|
||||
if (anim.actors[actorId].isFucked || anim.actors[actorId].isFucking || anim.actors[actorId].requiredGenitals.Any(x => x.ToLower().ContainsAny("penis", "vagina", "anus")))
|
||||
{ orgasmTick = Mathf.Clamp(ticks - 5, 0, int.MaxValue); }
|
||||
|
||||
// Actor does not orgasm
|
||||
else
|
||||
{ orgasmTick = (int)(ticks * (2f + Rand.Value)); }
|
||||
}
|
||||
|
||||
return ticks;
|
||||
|
||||
}
|
||||
|
||||
// Extended version of PawnHeadRotInAnimation (prevents pawn hair from getting messed up when draw in portraits)
|
||||
public static Rot4 PawnHeadRotInAnimation(Pawn pawn, Rot4 regularPos, PawnRenderFlags renderFlags)
|
||||
{
|
||||
if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawn?.TryGetComp<CompBodyAnimator>() != null && pawn.TryGetComp<CompBodyAnimator>().isAnimating)
|
||||
{
|
||||
return pawn.TryGetComp<CompBodyAnimator>().headFacing;
|
||||
}
|
||||
|
||||
return regularPos;
|
||||
}
|
||||
|
||||
public static BodyPartRecord GetBodyPartRecord(Pawn pawn, string bodyPart)
|
||||
{
|
||||
if (bodyPart.NullOrEmpty())
|
||||
{ return null; }
|
||||
|
||||
return pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null).FirstOrDefault(x => x.untranslatedCustomLabel == bodyPart || x.def.defName == bodyPart);
|
||||
}
|
||||
|
||||
public static Vector3 GetAnchorPosition(Pawn pawn, Thing thing = null)
|
||||
{
|
||||
Vector3 anchor;
|
||||
|
||||
if (thing == null)
|
||||
{ return pawn.Position.ToVector3Shifted(); }
|
||||
|
||||
int numOfSleepingSlots = 0;
|
||||
|
||||
if (thing is Building_Bed)
|
||||
{ numOfSleepingSlots = BedUtility.GetSleepingSlotsCount(thing.def.size); }
|
||||
|
||||
// Anchor to the pawn's sleeping slot when masturbating in own bed
|
||||
if (thing is Building_Bed && (pawn.ownership.OwnedBed == thing || pawn.CurrentBed() == thing) && pawn.IsMasturbating())
|
||||
{
|
||||
anchor = RestUtility.GetBedSleepingSlotPosFor(pawn, thing as Building_Bed).ToVector3();
|
||||
|
||||
if (thing.Rotation.AsInt == 0)
|
||||
{
|
||||
anchor.x += 0.5f;
|
||||
anchor.z += 1f;
|
||||
}
|
||||
else if (thing.Rotation.AsInt == 1)
|
||||
{
|
||||
anchor.x += 1f;
|
||||
anchor.z += 0.5f;
|
||||
}
|
||||
else if (thing.Rotation.AsInt == 2)
|
||||
{
|
||||
anchor.x += 0.5f;
|
||||
anchor.z += 0.5f;
|
||||
}
|
||||
else if (thing.Rotation.AsInt == 3)
|
||||
{
|
||||
anchor.x += 0f;
|
||||
anchor.z += 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
// Anchor to the center of the bed (should work for beds of any size?)
|
||||
else if (thing is Building_Bed && numOfSleepingSlots > 0)
|
||||
{
|
||||
anchor = thing.Position.ToVector3();
|
||||
float halfSlots = numOfSleepingSlots / 2f;
|
||||
|
||||
if (thing.Rotation.AsInt == 0)
|
||||
{
|
||||
anchor.x += halfSlots;
|
||||
anchor.z += 1f;
|
||||
}
|
||||
else if (thing.Rotation.AsInt == 1)
|
||||
{
|
||||
anchor.x += 1f;
|
||||
anchor.z += (1.0f - halfSlots);
|
||||
}
|
||||
else if (thing.Rotation.AsInt == 2)
|
||||
{
|
||||
anchor.x += (1f - halfSlots);
|
||||
anchor.z += 0f;
|
||||
}
|
||||
else if (thing.Rotation.AsInt == 3)
|
||||
{
|
||||
anchor.x += 0f;
|
||||
anchor.z += halfSlots;
|
||||
}
|
||||
}
|
||||
|
||||
// Anchor to the centre of the thing
|
||||
else
|
||||
{
|
||||
anchor = thing.Position.ToVector3Shifted();
|
||||
}
|
||||
|
||||
return anchor;
|
||||
}
|
||||
}
|
||||
}
|
197
Source/Scripts/Utilities/ApparelAnimationUtility.cs
Normal file
197
Source/Scripts/Utilities/ApparelAnimationUtility.cs
Normal file
|
@ -0,0 +1,197 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
using Rimworld_Animations;
|
||||
using UnityEngine;
|
||||
using AlienRace;
|
||||
using rjw;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class ApparelAnimationUtility
|
||||
{
|
||||
public static float apparelScale = 0.75f;
|
||||
|
||||
public static void TryToDrawApparelOnFloor(Pawn pawn)
|
||||
{
|
||||
if (pawn?.apparel?.WornApparel != null)
|
||||
{
|
||||
CompBodyAnimator compBodyAnimator = pawn.TryGetComp<CompBodyAnimator>();
|
||||
|
||||
if (ApparelSettings.clothesThrownOnGround == false || Find.CurrentMap != pawn.Map || compBodyAnimator == null || compBodyAnimator.isAnimating == false)
|
||||
{ return; }
|
||||
|
||||
foreach (Apparel apparel in pawn.apparel.WornApparel)
|
||||
{
|
||||
CompApparelVisibility compApparelVisibility = apparel.TryGetComp<CompApparelVisibility>();
|
||||
|
||||
if (compApparelVisibility != null && compApparelVisibility.position != default && compApparelVisibility.isBeingWorn == false)
|
||||
{
|
||||
Graphic apparelGraphic = apparel.Graphic;
|
||||
apparelGraphic.drawSize.x *= apparelScale;
|
||||
apparelGraphic.drawSize.y *= apparelScale;
|
||||
|
||||
GenDraw.DrawMeshNowOrLater(mesh: apparelGraphic.MeshAt(rot: apparel.Rotation),
|
||||
loc: compApparelVisibility.position,
|
||||
quat: Quaternion.AngleAxis(angle: compApparelVisibility.rotation, axis: Vector3.up),
|
||||
mat: apparelGraphic.MatAt(rot: apparel.Rotation),
|
||||
false);
|
||||
|
||||
apparelGraphic.drawSize.x *= 1f / apparelScale;
|
||||
apparelGraphic.drawSize.y *= 1f / apparelScale;
|
||||
DebugMode.Message(compApparelVisibility.rotation.ToString());
|
||||
//DebugMode.Message("Drawing " + apparel.def.defName + " on ground");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool BodyAddonCoveredByApparel(Apparel apparel, AlienPartGenerator.BodyAddon bodyAddon)
|
||||
{
|
||||
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
|
||||
|
||||
if (comp != null && comp.isBeingWorn == false)
|
||||
{ return false; }
|
||||
|
||||
RimNudeData rimNudeData = ApparelSettings.GetRimNudeData(apparel);
|
||||
|
||||
if (rimNudeData != null && bodyAddon?.bodyPart != null)
|
||||
{
|
||||
if (bodyAddon.bodyPart == "Genitals" && rimNudeData.coversGroin == false)
|
||||
{ return false; }
|
||||
|
||||
if (bodyAddon.bodyPart == "Chest" && rimNudeData.coversChest == false)
|
||||
{ return false; }
|
||||
|
||||
if (bodyAddon.bodyPart == "Torso" && rimNudeData.coversBelly == false)
|
||||
{ return false; }
|
||||
}
|
||||
|
||||
if (apparel.def.apparel.bodyPartGroups.Any(x => bodyAddon.hiddenUnderApparelFor.Contains(x)) ||
|
||||
apparel.def.apparel.tags.Any(x => bodyAddon.hiddenUnderApparelTag.Contains(x)))
|
||||
{ return true; }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool BodyPartCoveredByApparel(Apparel apparel, BodyPartRecord bodyPart)
|
||||
{
|
||||
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
|
||||
|
||||
if (comp != null && comp.isBeingWorn == false)
|
||||
{ return false; }
|
||||
|
||||
RimNudeData rimNudeData = ApparelSettings.GetRimNudeData(apparel);
|
||||
|
||||
if (rimNudeData != null)
|
||||
{
|
||||
if (bodyPart.def.defName == "Genitals" && rimNudeData.coversGroin == false)
|
||||
{ return false; }
|
||||
|
||||
if (bodyPart.def.defName == "Chest" && rimNudeData.coversChest == false)
|
||||
{ return false; }
|
||||
|
||||
if (bodyPart.def.defName == "Torso" && rimNudeData.coversBelly == false)
|
||||
{ return false; }
|
||||
}
|
||||
|
||||
if (apparel.def.apparel.CoversBodyPart(bodyPart))
|
||||
{ return true; }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void DetermineApparelToKeepOn(Pawn pawn)
|
||||
{
|
||||
JobDriver_Sex jobdriver = pawn.jobs.curDriver as JobDriver_Sex;
|
||||
|
||||
if (pawn.RaceProps.Humanlike == false || pawn?.apparel?.WornApparel == null || jobdriver == null)
|
||||
{ return; }
|
||||
|
||||
foreach (Apparel apparel in pawn.apparel?.WornApparel)
|
||||
{
|
||||
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
|
||||
if (comp != null)
|
||||
{ comp.isBeingWorn = true; }
|
||||
}
|
||||
|
||||
ActorAnimationData animData = pawn.GetAnimationData();
|
||||
|
||||
if (animData == null)
|
||||
{ return; }
|
||||
|
||||
AnimationDef anim = animData.animationDef;
|
||||
int actorID = animData.actorID;
|
||||
|
||||
var clothingPreference = pawn.IsInBed(out Building bed) ? RJWPreferenceSettings.sex_wear : ApparelSettings.apparelWornForQuickies;
|
||||
|
||||
if (xxx.has_quirk(pawn, "Endytophile"))
|
||||
{ clothingPreference = RJWPreferenceSettings.Clothing.Clothed; }
|
||||
|
||||
// Determine any obstructing apparel that must be removed
|
||||
foreach (Apparel apparel in pawn.apparel.WornApparel)
|
||||
{
|
||||
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
|
||||
|
||||
if (comp == null)
|
||||
{ continue; }
|
||||
|
||||
if (apparel.def is bondage_gear_def)
|
||||
{ continue; }
|
||||
|
||||
if (ApparelSettings.GetRimNudeData(apparel) != null && ApparelSettings.GetRimNudeData(apparel).sexWear)
|
||||
{ continue; }
|
||||
|
||||
if (clothingPreference == RJWPreferenceSettings.Clothing.Nude)
|
||||
{
|
||||
comp.isBeingWorn = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isHat = apparel.def.apparel.bodyPartGroups.NullOrEmpty() == false && (apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.FullHead) || apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.UpperHead));
|
||||
|
||||
if (clothingPreference == RJWPreferenceSettings.Clothing.Headgear && isHat == false)
|
||||
{
|
||||
comp.isBeingWorn = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ApparelCoversPawnRequiredBodyParts(pawn, apparel, anim, actorID))
|
||||
{
|
||||
comp.isBeingWorn = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ApparelCoversPawnRequiredBodyParts(Pawn pawn, Apparel apparel, AnimationDef anim, int actorID)
|
||||
{
|
||||
bool bodyPartCovered = false;
|
||||
|
||||
IEnumerable<BodyPartRecord> bodyParts = pawn.RaceProps.body.AllParts;
|
||||
|
||||
var requiredGenitals = anim.actors[actorID].requiredGenitals;
|
||||
|
||||
if (requiredGenitals.NullOrEmpty())
|
||||
{ requiredGenitals = new List<string>(); }
|
||||
|
||||
if (anim.actors[actorID].isFucking || requiredGenitals.Contains("Penis"))
|
||||
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.genitalsDef)); }
|
||||
|
||||
if (anim.actors[actorID].isFucked || requiredGenitals.Contains("Vagina"))
|
||||
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.genitalsDef)); }
|
||||
|
||||
if (anim.actors[actorID].isFucked || requiredGenitals.Contains("Anus"))
|
||||
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.anusDef)); }
|
||||
|
||||
if (requiredGenitals.Contains("Breasts"))
|
||||
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.breastsDef)); }
|
||||
|
||||
if (requiredGenitals.Contains("Mouth"))
|
||||
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def.defName.ToLower().ContainsAny("mouth", "teeth", "jaw", "beak"))); }
|
||||
|
||||
return bodyPartCovered;
|
||||
}
|
||||
}
|
||||
}
|
199
Source/Scripts/Utilities/ApparelSettingsUtility.cs
Normal file
199
Source/Scripts/Utilities/ApparelSettingsUtility.cs
Normal file
|
@ -0,0 +1,199 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class ApparelSettingsUtility
|
||||
{
|
||||
public static List<ThingDef> GetApparelOfInterest()
|
||||
{
|
||||
List<ThingDef> thingDefs = new List<ThingDef>();
|
||||
|
||||
foreach (ThingDef thingDef in DefDatabase<ThingDef>.AllDefs)
|
||||
{
|
||||
if (thingDef.IsApparel && thingDef.apparel.layers.Count == 1 && thingDef.apparel.layers[0] == ApparelLayerDefOf.Belt)
|
||||
{ continue; }
|
||||
|
||||
if (thingDef.IsApparel &&
|
||||
(thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) ||
|
||||
thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs) ||
|
||||
thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.GenitalsBPG) ||
|
||||
thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.ChestBPG)))
|
||||
{ thingDefs.Add(thingDef); }
|
||||
}
|
||||
|
||||
return thingDefs;
|
||||
}
|
||||
|
||||
// Resets all data
|
||||
public static void ResetRimNudeData(List<RimNudeData> rimNudeData)
|
||||
{
|
||||
rimNudeData.Clear();
|
||||
List<ThingDef> thingDefs = GetApparelOfInterest();
|
||||
|
||||
foreach (ThingDef thingDef in thingDefs)
|
||||
{
|
||||
for (int i = 0; i < 5; i++)
|
||||
{ rimNudeData.Add(new RimNudeData(thingDef)); }
|
||||
}
|
||||
|
||||
GetApparelDefaults(rimNudeData);
|
||||
}
|
||||
|
||||
// Update apparel data
|
||||
public static void UpdateRimNudeData(List<RimNudeData> rimNudeData, string thingDef, bool coversGroin, bool coversBelly, bool coversChest, bool sexWear)
|
||||
{
|
||||
for (int i = 0; i < rimNudeData.Count; i++)
|
||||
{
|
||||
RimNudeData apparelData = rimNudeData[i];
|
||||
|
||||
if (apparelData.thingDef == thingDef)
|
||||
{
|
||||
rimNudeData[i] = new RimNudeData(thingDef, coversGroin, coversBelly, coversChest, sexWear);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetAllCoversGroin(List<RimNudeData> rimNudeData, bool value)
|
||||
{
|
||||
foreach (RimNudeData rimNudeApparel in rimNudeData)
|
||||
{ rimNudeApparel.coversGroin = value; }
|
||||
}
|
||||
|
||||
public static void SetAllCoversBelly(List<RimNudeData> rimNudeData, bool value)
|
||||
{
|
||||
foreach (RimNudeData rimNudeApparel in rimNudeData)
|
||||
{ rimNudeApparel.coversBelly = value; }
|
||||
}
|
||||
|
||||
public static void SetAllCoversChest(List<RimNudeData> rimNudeData, bool value)
|
||||
{
|
||||
foreach (RimNudeData rimNudeApparel in rimNudeData)
|
||||
{ rimNudeApparel.coversChest = value; }
|
||||
}
|
||||
|
||||
public static void SetAllSexWear(List<RimNudeData> rimNudeData, bool value)
|
||||
{
|
||||
foreach (RimNudeData rimNudeApparel in rimNudeData)
|
||||
{ rimNudeApparel.sexWear = value; }
|
||||
}
|
||||
|
||||
public static void GetApparelDefaults(List<RimNudeData> rimNudeData)
|
||||
{
|
||||
//Apparel_BasicShirt
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_BasicShirt", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
|
||||
|
||||
//Apparel_CollarShirt
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_CollarShirt", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
|
||||
|
||||
//Apparel_FlakVest
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_FlakVest", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
|
||||
|
||||
//Apparel_Duster
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_Duster", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//Apparel_Jacket
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_Jacket", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//Apparel_TribalA
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_TribalA", coversGroin: true, coversBelly: true, coversChest: true, sexWear: false);
|
||||
|
||||
//Apparel_BodyStrap
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_BodyStrap", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//Apparel_PsyfocusRobe
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_PsyfocusRobe", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//Apparel_Cape
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_Cape", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
|
||||
|
||||
//Apparel_RobeRoyal
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_RobeRoyal", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//Apparel_Corset
|
||||
UpdateRimNudeData(rimNudeData, "Apparel_Corset", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
|
||||
|
||||
//VAE_Apparel_Overalls
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_Overalls", coversGroin: true, coversBelly: true, coversChest: false, sexWear: false);
|
||||
|
||||
//VAE_Apparel_LabCoat
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_LabCoat", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//VAE_Apparel_BuildersJacket
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_BuildersJacket", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//VAE_Apparel_Apron
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_Apron", coversGroin: true, coversBelly: true, coversChest: false, sexWear: false);
|
||||
|
||||
//VAE_Apparel_Tunic
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_Tunic", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
|
||||
|
||||
//VAE_Apparel_PeltCoat
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_PeltCoat", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//VAE_Apparel_WoodenArmor
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_WoodenArmor", coversGroin: false, coversBelly: true, coversChest: false, sexWear: false);
|
||||
|
||||
//VAE_Apparel_AdvancedVest
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_AdvancedVest", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
|
||||
|
||||
//VAE_Apparel_BulletproofVest
|
||||
UpdateRimNudeData(rimNudeData, "VAE_Apparel_BulletproofVest", coversGroin: false, coversBelly: true, coversChest: false, sexWear: false);
|
||||
|
||||
//VWE_Apparel_Exoframe
|
||||
UpdateRimNudeData(rimNudeData, "VWE_Apparel_Exoframe", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
|
||||
|
||||
//VFEM_Apparel_Tabard
|
||||
UpdateRimNudeData(rimNudeData, "VFEM_Apparel_Tabard", coversGroin: true, coversBelly: true, coversChest: true, sexWear: false);
|
||||
|
||||
//VFEV_Apparel_JarlCape
|
||||
UpdateRimNudeData(rimNudeData, "VFEV_Apparel_JarlCape", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//VFEV_Apparel_RoyalFurCoat
|
||||
UpdateRimNudeData(rimNudeData, "VFEV_Apparel_RoyalFurCoat", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
|
||||
|
||||
//PrisonerChains
|
||||
UpdateRimNudeData(rimNudeData, "PrisonerChains", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_ChainHarnessA
|
||||
UpdateRimNudeData(rimNudeData, "S16_ChainHarnessA", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_NippleWristCuffs
|
||||
UpdateRimNudeData(rimNudeData, "S16_NippleWristCuffs", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_ServantGirlDress
|
||||
UpdateRimNudeData(rimNudeData, "S16_ServantGirlDress", coversGroin: true, coversBelly: true, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_ZDress
|
||||
UpdateRimNudeData(rimNudeData, "S16_ZDress", coversGroin: false, coversBelly: true, coversChest: true, sexWear: true);
|
||||
|
||||
//S16_MaidA
|
||||
UpdateRimNudeData(rimNudeData, "S16_MaidA", coversGroin: false, coversBelly: true, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_DiscoTop
|
||||
UpdateRimNudeData(rimNudeData, "S16_DiscoTop", coversGroin: false, coversBelly: false, coversChest: true, sexWear: true);
|
||||
|
||||
//S16_TransparentSkirt
|
||||
UpdateRimNudeData(rimNudeData, "S16_TransparentSkirt", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_Vibrator
|
||||
UpdateRimNudeData(rimNudeData, "S16_Vibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_VibratorDouble
|
||||
UpdateRimNudeData(rimNudeData, "S16_VibratorDouble", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_WiredVibrator
|
||||
UpdateRimNudeData(rimNudeData, "S16_WiredVibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_DoubleWiredVibrator
|
||||
UpdateRimNudeData(rimNudeData, "S16_DoubleWiredVibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
|
||||
//S16_WiredAnalVibrator
|
||||
UpdateRimNudeData(rimNudeData, "S16_WiredAnalVibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
|
||||
}
|
||||
}
|
||||
}
|
16
Source/Scripts/Utilities/DebugMode.cs
Normal file
16
Source/Scripts/Utilities/DebugMode.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class DebugMode
|
||||
{
|
||||
public static void Message(string text)
|
||||
{
|
||||
if (BasicSettings.debugMode)
|
||||
{ Log.Message("[DEBUG] " + text); }
|
||||
}
|
||||
}
|
||||
}
|
144
Source/Scripts/Utilities/GraphicMaskingUtility.cs
Normal file
144
Source/Scripts/Utilities/GraphicMaskingUtility.cs
Normal file
|
@ -0,0 +1,144 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class GraphicMaskingUtility
|
||||
{
|
||||
public static Texture2D GetReadableTexture2D(Texture2D source, int newWidth, int newHeight, Material mat = null) //rescales texture to newWidth and newHeight
|
||||
{
|
||||
source.filterMode = FilterMode.Trilinear;
|
||||
|
||||
RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight);
|
||||
rt.filterMode = FilterMode.Trilinear;
|
||||
|
||||
RenderTexture.active = rt;
|
||||
if (mat != null)
|
||||
{ Graphics.Blit(source, rt, mat); }
|
||||
|
||||
else
|
||||
{ Graphics.Blit(source, rt); }
|
||||
|
||||
Texture2D nTex = new Texture2D(newWidth, newHeight, TextureFormat.RGBA32, mipChain: true);
|
||||
nTex.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0);
|
||||
nTex.name = source.name;
|
||||
nTex.filterMode = FilterMode.Trilinear;
|
||||
nTex.anisoLevel = 2;
|
||||
nTex.Apply(updateMipmaps: true);
|
||||
|
||||
GL.Clear(true, true, Color.clear);
|
||||
RenderTexture.active = null;
|
||||
RenderTexture.ReleaseTemporary(rt);
|
||||
|
||||
return nTex;
|
||||
}
|
||||
|
||||
public static Texture2D GetReadableTexture2D(Texture2D source, Material mat = null)
|
||||
{
|
||||
return GetReadableTexture2D(source, source.width, source.height, mat);
|
||||
}
|
||||
|
||||
public static Texture2D ApplyMaskToTexture2D(Texture2D mainTex, Texture2D maskTex, bool writeOverMainTex)
|
||||
{
|
||||
if (mainTex == null || maskTex == null)
|
||||
{
|
||||
DebugMode.Message("mainTex or maskTex is missing!");
|
||||
return mainTex;
|
||||
}
|
||||
|
||||
Color[] mainArray = GetReadableTexture2D(mainTex).GetPixels();
|
||||
Color[] maskArray = GetReadableTexture2D(maskTex, mainTex.width, mainTex.height).GetPixels();
|
||||
|
||||
for (int j = 0; j < mainArray.Length; j++)
|
||||
{
|
||||
if (maskArray[j] == Color.white)
|
||||
{ /*null*/ }
|
||||
|
||||
else if (maskArray[j].a == 0)
|
||||
{ mainArray[j].a = 0; }
|
||||
|
||||
else if (mainArray[j].a > 0 && maskArray[j].a > 0 && writeOverMainTex)
|
||||
{ mainArray[j] = new Color(Mathf.Min(mainArray[j].r, maskArray[j].r), Mathf.Min(mainArray[j].g, maskArray[j].g), Mathf.Min(mainArray[j].b, maskArray[j].b), Mathf.Min(mainArray[j].a, maskArray[j].a)); }
|
||||
}
|
||||
|
||||
Texture2D newTex = new Texture2D(mainTex.width, mainTex.height, TextureFormat.RGBA32, mipChain: true);
|
||||
newTex.SetPixels(mainArray);
|
||||
newTex.filterMode = FilterMode.Trilinear;
|
||||
newTex.anisoLevel = 2;
|
||||
newTex.Apply(updateMipmaps: true);
|
||||
|
||||
return newTex;
|
||||
}
|
||||
|
||||
public static Graphic ApplyGraphicWithMasks(Graphic graphic, Graphic graphicWithMask, bool writeOverMainTex)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Texture2D mainTex = (Texture2D)graphic.MatAt(new Rot4(i)).mainTexture;
|
||||
Texture2D maskTex = graphicWithMask.MatAt(new Rot4(i)).GetMaskTexture();
|
||||
graphic.MatAt(new Rot4(i)).mainTexture = ApplyMaskToTexture2D(mainTex, maskTex, writeOverMainTex);
|
||||
}
|
||||
|
||||
return graphic;
|
||||
}
|
||||
|
||||
public static Graphic ApplyGraphicWithMasks(Graphic graphic, string mask, bool writeOverMainTex)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Texture2D mainTex = (Texture2D)graphic.MatAt(new Rot4(i)).mainTexture;
|
||||
|
||||
if (mainTex == null)
|
||||
{ DebugMode.Message("Main Texture2D not found for " + graphic.path + ". Rotation: " + i.ToString()); continue; }
|
||||
|
||||
string suffix = string.Empty;
|
||||
switch (i)
|
||||
{
|
||||
case 0: suffix = "_north"; break;
|
||||
case 1: suffix = "_east"; break;
|
||||
case 2: suffix = "_south"; break;
|
||||
case 3: suffix = "_west"; break;
|
||||
}
|
||||
|
||||
Texture2D maskTex = ContentFinder<Texture2D>.Get(mask + suffix, false);
|
||||
|
||||
if (maskTex == null)
|
||||
{ DebugMode.Message("Mask Texture2D not found for " + mask + ". Rotation: " + i.ToString()); continue; }
|
||||
|
||||
graphic.MatAt(new Rot4(i)).mainTexture = ApplyMaskToTexture2D(mainTex, maskTex, writeOverMainTex);
|
||||
}
|
||||
|
||||
return graphic;
|
||||
}
|
||||
|
||||
public static void ResetGraphic(Graphic graphic)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
string suffix = string.Empty;
|
||||
switch (i)
|
||||
{
|
||||
case 0: suffix = "_north"; break;
|
||||
case 1: suffix = "_east"; break;
|
||||
case 2: suffix = "_south"; break;
|
||||
case 3: suffix = "_west"; break;
|
||||
}
|
||||
|
||||
Texture2D texture2D = ContentFinder<Texture2D>.Get(graphic.path + suffix, false);
|
||||
|
||||
if (texture2D == null && i == 3)
|
||||
{ texture2D = ContentFinder<Texture2D>.Get(graphic.path + "_east", false); }
|
||||
|
||||
if (texture2D == null)
|
||||
{ texture2D = ContentFinder<Texture2D>.Get(graphic.path + "_north", false); }
|
||||
|
||||
if (texture2D != null)
|
||||
{ graphic.MatAt(new Rot4(i)).mainTexture = texture2D; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
219
Source/Scripts/Utilities/HandAnimationUtility.cs
Normal file
219
Source/Scripts/Utilities/HandAnimationUtility.cs
Normal file
|
@ -0,0 +1,219 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
using HarmonyLib;
|
||||
using Rimworld_Animations;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class HandAnimationUtility
|
||||
{
|
||||
public static BodyPartDef handDef;
|
||||
|
||||
public static bool BodyPartIsBeingTouched(Pawn pawn, string bodypartFilePath, out List<HandAnimationData> handAnimationData)
|
||||
{
|
||||
handAnimationData = new List<HandAnimationData>();
|
||||
|
||||
ActorAnimationData actorAnimationData = pawn.GetAnimationData();
|
||||
HandAnimationDef handAnimationDef = DefDatabase<HandAnimationDef>.AllDefs.FirstOrDefault(x => x.animationDefName == actorAnimationData.animationDef.defName);
|
||||
|
||||
if (handAnimationDef == null)
|
||||
{ return false; }
|
||||
|
||||
foreach (HandAnimationData datum in handAnimationDef.handAnimationData)
|
||||
{
|
||||
if (datum.stageID != actorAnimationData.currentStage || datum.actorID != actorAnimationData.actorID)
|
||||
{ continue; }
|
||||
|
||||
if (datum.bodySide.NullOrEmpty() == false && bodypartFilePath.ToLower().Contains(datum.bodySide) == false)
|
||||
{ continue; }
|
||||
|
||||
if (datum.targetBodyPart.NullOrEmpty() == false && bodypartFilePath.ToLower().Contains(datum.targetBodyPart.ToLower()))
|
||||
{ handAnimationData.Add(datum); }
|
||||
|
||||
else if (datum.targetBodyParts.Any(x => bodypartFilePath.ToLower().Contains(x.ToLower())))
|
||||
{ handAnimationData.Add(datum); }
|
||||
}
|
||||
|
||||
return handAnimationData.NullOrEmpty() == false;
|
||||
}
|
||||
|
||||
public static Vector3 GetHandPosition(Pawn pawn, HandAnimationData handAnimationData, Vector3 basePosition, float baseAngle)
|
||||
{
|
||||
var methodInfo = AccessTools.Method(typeof(HandAnimationUtility), handAnimationData.motion, null, null);
|
||||
|
||||
if (methodInfo == null)
|
||||
{
|
||||
Debug.LogWarning("Hand anaimation motion '" + handAnimationData.motion + "' was not found");
|
||||
return default;
|
||||
}
|
||||
|
||||
Vector3 handPosition = (Vector3)methodInfo.Invoke(null, new object[] { pawn, handAnimationData, baseAngle });
|
||||
|
||||
return handPosition * pawn.RaceProps.baseBodySize + basePosition;
|
||||
}
|
||||
|
||||
public static float GetGenitalSize(Pawn pawn, string genitalName)
|
||||
{
|
||||
switch(genitalName.ToLower())
|
||||
{
|
||||
case "penis": return pawn.health.hediffSet.hediffs.First(x => x.def.defName.ToLower().Contains("penis")).Severity;
|
||||
case "breasts": return pawn.health.hediffSet.hediffs.First(x => x.def.defName.ToLower().Contains("breasts")).Severity;
|
||||
case "vagina": return 0.1f;
|
||||
case "anus": return 0.1f;
|
||||
}
|
||||
|
||||
return 0.1f;
|
||||
}
|
||||
|
||||
public static Vector3 Motion_StrokeGenitalsUpAndDownShort_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
|
||||
{
|
||||
Vector3 handPosition = new Vector3();
|
||||
ActorAnimationData data = pawn.GetAnimationData();
|
||||
|
||||
float p = (Mathf.PingPong(data.stageTicks, handAnimationData.cycleTime) / handAnimationData.cycleTime);
|
||||
float length = 0.035f;
|
||||
|
||||
handPosition.x = 0;
|
||||
handPosition.z = length * p;
|
||||
|
||||
handPosition = handPosition.RotatedBy(baseAngle);
|
||||
|
||||
return handPosition;
|
||||
}
|
||||
|
||||
public static Vector3 Motion_StrokeGenitalsUpAndDown_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
|
||||
{
|
||||
Vector3 handPosition = new Vector3();
|
||||
ActorAnimationData data = pawn.GetAnimationData();
|
||||
|
||||
float p = (Mathf.PingPong(data.stageTicks, handAnimationData.cycleTime) / handAnimationData.cycleTime);
|
||||
float size = GetGenitalSize(pawn, handAnimationData.targetBodyPart) * 0.2f;
|
||||
float m = (data.actorFacing == Rot4.North ? -1f : 1f) * (handAnimationData.mirror ? -1f : 1f) * (pawn.TryGetComp<CompBodyAnimator>().Mirror ? -1f : 1f);
|
||||
|
||||
handPosition.x = 0.025f * m;
|
||||
handPosition.z = size * p;
|
||||
|
||||
handPosition = handPosition.RotatedBy(baseAngle);
|
||||
|
||||
return handPosition;
|
||||
}
|
||||
|
||||
public static Vector3 Motion_StrokeGenitalsUpAndDown_FacingEW(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
|
||||
{
|
||||
Vector3 handPosition = new Vector3();
|
||||
ActorAnimationData data = pawn.GetAnimationData();
|
||||
|
||||
float p = Mathf.PingPong(data.stageTicks, handAnimationData.cycleTime) / handAnimationData.cycleTime;
|
||||
float size = GetGenitalSize(pawn, handAnimationData.targetBodyPart) * 0.2f;
|
||||
float m = (data.actorFacing == Rot4.West ? -1f : 1f) * (handAnimationData.mirror ? -1f : 1f);
|
||||
|
||||
handPosition.x = Mathf.Sin(m * (baseAngle + 45f) / 180f * Mathf.PI) * size * p;
|
||||
handPosition.z = Mathf.Cos(m * (baseAngle + 45f) / 180f * Mathf.PI) * size * p;
|
||||
|
||||
return handPosition;
|
||||
}
|
||||
|
||||
public static Vector3 Motion_RubGenitals_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
|
||||
{
|
||||
Vector3 handPosition = new Vector3();
|
||||
ActorAnimationData data = pawn.GetAnimationData();
|
||||
|
||||
float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
|
||||
float m = (data.actorFacing == Rot4.North ? 1f : -1f) * (handAnimationData.mirror ? -1f : 1f) * (pawn.TryGetComp<CompBodyAnimator>().Mirror ? -1f : 1f);
|
||||
|
||||
handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.05f - 0.025f) * m;
|
||||
handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f + 0.03f;
|
||||
|
||||
handPosition = handPosition.RotatedBy(baseAngle);
|
||||
|
||||
return handPosition;
|
||||
}
|
||||
|
||||
public static Vector3 Motion_RubGenitals_FacingEW(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
|
||||
{
|
||||
Vector3 handPosition = new Vector3();
|
||||
ActorAnimationData data = pawn.GetAnimationData();
|
||||
|
||||
float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
|
||||
float m = (data.actorFacing == Rot4.West ? 1f : -1f) * (handAnimationData.mirror ? -1f : 1f);
|
||||
|
||||
handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.005f - 0.05f) * m;
|
||||
handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f;
|
||||
//handPosition.y = -0.1f;
|
||||
|
||||
handPosition = handPosition.RotatedBy(baseAngle);
|
||||
|
||||
return handPosition;
|
||||
}
|
||||
|
||||
public static Vector3 Motion_RubBreasts_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
|
||||
{
|
||||
Vector3 handPosition = new Vector3();
|
||||
ActorAnimationData data = pawn.GetAnimationData();
|
||||
|
||||
float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
|
||||
float m = (data.actorFacing == Rot4.North ? -1f : 1f) * (handAnimationData.mirror ? -1f : 1f) * (pawn.TryGetComp<CompBodyAnimator>().Mirror ? -1f : 1f);
|
||||
float size = GetGenitalSize(pawn, "breasts");
|
||||
|
||||
handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.05f * size - size * 0.25f) * m;
|
||||
handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f - size * 0.125f;
|
||||
|
||||
handPosition = handPosition.RotatedBy(baseAngle);
|
||||
|
||||
return handPosition;
|
||||
}
|
||||
|
||||
public static Vector3 Motion_RubBreasts_FacingEW(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
|
||||
{
|
||||
Vector3 handPosition = new Vector3();
|
||||
ActorAnimationData data = pawn.GetAnimationData();
|
||||
|
||||
float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
|
||||
float m = (data.actorFacing == Rot4.West ? 1f : -1f) * (handAnimationData.mirror ? -1f : 1f);
|
||||
float size = GetGenitalSize(pawn, "breasts");
|
||||
|
||||
handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.005f - size * 0.25f) * m;
|
||||
handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f - size * 0.125f;
|
||||
|
||||
handPosition = handPosition.RotatedBy(baseAngle);
|
||||
|
||||
return handPosition;
|
||||
}
|
||||
|
||||
public static Graphic GetHandGraphic(Pawn touchingPawn, string touchedBodyAddonName, HandAnimationData handAnimationData)
|
||||
{
|
||||
string handGraphicPath = "Hands/HandClean";
|
||||
Color skinColour = touchingPawn.story.SkinColor;
|
||||
float handSize = 0.6667f * touchingPawn.RaceProps.baseBodySize;
|
||||
|
||||
return GraphicDatabase.Get<Graphic_Single>(handGraphicPath, ShaderDatabase.Cutout, new Vector2(handSize, handSize), skinColour);
|
||||
}
|
||||
|
||||
public static bool TryToDrawHand(Pawn pawn, string bodyAddonName, Vector3 bodyAddonPosition, float bodyAddonAngle, Rot4 bodyAddonRotation, PawnRenderFlags renderFlags)
|
||||
{
|
||||
if (pawn.TryGetComp<CompBodyAnimator>() != null && pawn.TryGetComp<CompBodyAnimator>().isAnimating && BodyPartIsBeingTouched(pawn, bodyAddonName, out List<HandAnimationData> handAnimationData))
|
||||
{
|
||||
foreach (HandAnimationData datum in handAnimationData)
|
||||
{
|
||||
Pawn touchingPawn = datum.touchingActorID >= 0 && pawn.GetAllSexParticipants().Count > datum.touchingActorID ? pawn.GetAllSexParticipants()[datum.touchingActorID] : pawn;
|
||||
|
||||
Graphic handgraphic = GetHandGraphic(touchingPawn, bodyAddonName, datum);
|
||||
Vector3 handPosition = GetHandPosition(pawn, datum, bodyAddonPosition, bodyAddonAngle);
|
||||
|
||||
GenDraw.DrawMeshNowOrLater(mesh: handgraphic.MeshAt(rot: bodyAddonRotation),
|
||||
loc: handPosition + new Vector3(0f, 0.022f, 0f),
|
||||
quat: Quaternion.identity,
|
||||
mat: handgraphic.MatAt(rot: bodyAddonRotation), renderFlags.FlagSet(PawnRenderFlags.DrawNow));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
46
Source/Scripts/Utilities/MathUtility.cs
Normal file
46
Source/Scripts/Utilities/MathUtility.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class MathUtility
|
||||
{
|
||||
public static float Repeat(float value, float min, float max)
|
||||
{
|
||||
if (Mathf.Abs(max) < Mathf.Abs(min))
|
||||
{
|
||||
Log.Error("RepeatDual: min value must be greater than max value");
|
||||
return -1;
|
||||
}
|
||||
|
||||
float range = max - min;
|
||||
float m = value % range;
|
||||
|
||||
if (m < 0)
|
||||
{ m = range + m; }
|
||||
|
||||
return min + m;
|
||||
}
|
||||
|
||||
public static IntVec3 FindRandomCellNearPawn(Pawn pawn, int maxRadius)
|
||||
{
|
||||
if (maxRadius > 0)
|
||||
{
|
||||
for (int radius = 1; radius < maxRadius; radius++)
|
||||
{
|
||||
List<IntVec3> cells = GenRadial.RadialCellsAround(pawn.Position, radius + 0.75f, false).Where(x => x.Standable(pawn.Map) && x.GetRoom(pawn.Map) == pawn.GetRoom())?.ToList();
|
||||
|
||||
if (cells.NullOrEmpty() == false && cells.Count > 0)
|
||||
{ return cells.RandomElement(); }
|
||||
}
|
||||
}
|
||||
|
||||
return GenAdj.RandomAdjacentCellCardinal(pawn);
|
||||
}
|
||||
}
|
||||
}
|
14
Source/Scripts/Utilities/PatchDefOf.cs
Normal file
14
Source/Scripts/Utilities/PatchDefOf.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using Verse;
|
||||
using RimWorld;
|
||||
using AlienRace;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
[DefOf]
|
||||
public static class PatchBodyPartGroupDefOf
|
||||
{
|
||||
public static BodyPartGroupDef GenitalsBPG;
|
||||
public static BodyPartGroupDef AnusBPG;
|
||||
public static BodyPartGroupDef ChestBPG;
|
||||
}
|
||||
}
|
16
Source/Scripts/Utilities/SettingsUtility.cs
Normal file
16
Source/Scripts/Utilities/SettingsUtility.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class SettingsUtility
|
||||
{
|
||||
public static float Align(float objectDim, float columnDim)
|
||||
{
|
||||
return (columnDim - objectDim) * 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
241
Source/Scripts/Utilities/SexInteractionUtility.cs
Normal file
241
Source/Scripts/Utilities/SexInteractionUtility.cs
Normal file
|
@ -0,0 +1,241 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using Verse.AI.Group;
|
||||
using RimWorld;
|
||||
using rjw;
|
||||
using RJWSexperience.Ideology;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Rimworld_Animations_Patch
|
||||
{
|
||||
public static class SexInteractionUtility
|
||||
{
|
||||
public static bool PawnCaughtLovinByWitness(Pawn pawn, Pawn witness)
|
||||
{
|
||||
if (witness == null || pawn == witness || witness.AnimalOrWildMan() || witness.RaceProps.IsMechanoid || witness.Awake() == false || witness.CanSee(pawn) == false)
|
||||
{ return false; }
|
||||
|
||||
if (pawn.IsHavingSex() == false && pawn.IsMasturbating() == false)
|
||||
{ return false; }
|
||||
|
||||
List<Pawn> sexParticipants = pawn.GetAllSexParticipants();
|
||||
bool witnessIsCourtingSexParticipant = witness.jobs.curDriver is JobDriver_SexBaseInitiator && sexParticipants.Contains((witness.jobs.curDriver as JobDriver_SexBaseInitiator).Partner);
|
||||
|
||||
if (sexParticipants.Contains(witness) || witnessIsCourtingSexParticipant)
|
||||
{ return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool PawnIsCheatingOnPartner(Pawn pawn, Pawn partner)
|
||||
{
|
||||
if (BasicSettings.worryAboutInfidelity == false || pawn.IsMasturbating() || pawn.IsHavingSex() == false || pawn.GetAllSexParticipants().Contains(partner))
|
||||
{ return false; }
|
||||
|
||||
if (pawn.GetAllSexParticipants().Any(x => pawn.GetSpouseCount(false) > 0 && pawn.GetSpouses(false).Contains(x)))
|
||||
{ return false; }
|
||||
|
||||
return partner.IsLoverOfOther(pawn) && pawn.HasTrait("Polygamist") == false && partner.HasTrait("Polygamist") == false;
|
||||
}
|
||||
|
||||
public static bool PawnCanInvitePasserbyForSex(Pawn passerby, List<Pawn> participants)
|
||||
{
|
||||
if (passerby == null || participants.NullOrEmpty() || participants.Contains(passerby) || passerby.AnimalOrWildMan() || passerby.RaceProps.IsMechanoid || passerby.Awake() == false || participants.All(x => x.CanSee(passerby) == false))
|
||||
{ return false; }
|
||||
|
||||
if (participants.Any(x => x.IsForbidden(passerby) || x.HostileTo(passerby) || PawnIsCheatingOnPartner(x, passerby)) || CasualSex_Helper.CanHaveSex(passerby) == false || xxx.IsTargetPawnOkay(passerby) == false || participants.Count > 2)
|
||||
{ return false; }
|
||||
|
||||
if (SexUtility.ReadyForHookup(passerby) &&
|
||||
(passerby?.jobs?.curJob == null || (passerby.jobs.curJob.playerForced == false && CasualSex_Helper.quickieAllowedJobs.Contains(passerby.jobs.curJob.def))) &&
|
||||
participants.Any(x => SexAppraiser.would_fuck(x, passerby) > 0.1f && SexAppraiser.would_fuck(passerby, x) > 0.1f) &&
|
||||
participants.All(x => SexAppraiser.would_fuck(x, passerby, false, false, true) > 0.1f && SexAppraiser.would_fuck(passerby, x, false, false, true) > 0.1f))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static TabooStatus CheckSexJobAgainstMorals(Pawn pawn, JobDriver_Sex jobDriver, out Precept precept)
|
||||
{
|
||||
bool sexIsNecro = jobDriver.Partner != null && jobDriver.Partner.Dead;
|
||||
bool sexIsBeastial = jobDriver.Partner != null && jobDriver.Partner.RaceProps.Animal;
|
||||
bool sexIsRape = sexIsBeastial == false && sexIsNecro == false &&
|
||||
(jobDriver is JobDriver_Rape || jobDriver is JobDriver_RapeEnemy || jobDriver is JobDriver_SexBaseRecieverRaped) &&
|
||||
jobDriver.Partner.IsPrisoner == false && jobDriver.Partner.IsSlave == false;
|
||||
bool sexIsSlaveRape = sexIsBeastial == false && sexIsNecro == false &&
|
||||
(jobDriver is JobDriver_Rape || jobDriver is JobDriver_RapeEnemy || jobDriver is JobDriver_SexBaseRecieverRaped) &&
|
||||
(jobDriver.Partner.IsPrisoner || jobDriver.Partner.IsSlave);
|
||||
bool sexIsXeno = jobDriver.Partner != null && jobDriver.Partner.def.defName != jobDriver.pawn.def.defName;
|
||||
|
||||
TabooStatus tabooStatus = TabooStatus.NotTaboo;
|
||||
precept = null;
|
||||
|
||||
if (BasicSettings.worryAboutNecro && sexIsNecro && xxx.is_necrophiliac(pawn) == false)
|
||||
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Necrophilia"), TabooStatus.MajorTaboo, out precept); }
|
||||
|
||||
else if (BasicSettings.worryAboutBeastiality && sexIsBeastial && xxx.is_zoophile(pawn) == false)
|
||||
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Beastility"), TabooStatus.MajorTaboo, out precept); }
|
||||
|
||||
else if (BasicSettings.worryAboutRape && sexIsRape && xxx.is_rapist(pawn) == false)
|
||||
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Rape"), TabooStatus.MajorTaboo, out precept); }
|
||||
|
||||
else if (BasicSettings.worryAboutRape && BasicSettings.ignoreSlaveRape == false && sexIsSlaveRape && xxx.is_rapist(pawn) == false)
|
||||
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Rape"), TabooStatus.MajorTaboo, out precept); }
|
||||
|
||||
else if (BasicSettings.worryAboutXeno && sexIsXeno && pawn.HasTrait("Xenophobia") && pawn.story.traits.DegreeOfTrait(DefDatabase<TraitDef>.GetNamedSilentFail("Xenophobia")) > 0)
|
||||
{ tabooStatus = TabooStatus.MajorTaboo; }
|
||||
|
||||
else if (BasicSettings.worryAboutXeno && sexIsXeno)
|
||||
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("HAR_AlienDating"), TabooStatus.NotTaboo, out precept); }
|
||||
|
||||
//DebugMode.Message("Sex job is: " + jobDriver + " Issue is: " + (precept?.def?.issue?.defName).ToStringSafe() + " Opinion is: " + (precept?.def?.defName).ToStringSafe() + " Judgement is: " + tabooStatus.ToString());
|
||||
|
||||
return tabooStatus;
|
||||
}
|
||||
|
||||
public static TabooStatus GetTabooStatusOfIssue(Pawn pawn, IssueDef issueDef, TabooStatus defaultTabboStatus, out Precept precept)
|
||||
{
|
||||
if (pawn.IssueIsMajorTaboo(issueDef, out precept))
|
||||
{ return TabooStatus.MajorTaboo; }
|
||||
|
||||
if (pawn.IssueIsMinorTaboo(issueDef, out precept))
|
||||
{ return TabooStatus.MinorTaboo; }
|
||||
|
||||
return defaultTabboStatus;
|
||||
}
|
||||
|
||||
public static bool ResolveThoughtsForWhenSexIsWitnessed(Pawn pawn, Pawn witness, out bool witnessJoiningSex)
|
||||
{
|
||||
witnessJoiningSex = false;
|
||||
|
||||
if (pawn.IsAnimal() || pawn.RaceProps.IsMechanoid || pawn.Dead)
|
||||
{ return false; }
|
||||
|
||||
if (witness.IsAnimal() || witness.RaceProps.IsMechanoid || witness.Dead)
|
||||
{ return false; }
|
||||
|
||||
JobDriver_Sex jobDriver = pawn.jobs.curDriver as JobDriver_Sex;
|
||||
|
||||
string pawnThoughtDefName = pawn.IsMasturbating() ? "SeenMasturbating" : "SeenHavingSex";
|
||||
string witnessThoughtDefName = pawn.IsMasturbating() ? "SawMasturbation" : "SawSex";
|
||||
|
||||
bool pawnIsExhibitionist = pawn.HasTrait("Exhibitionist") || xxx.has_quirk(pawn, "Exhibitionist");
|
||||
if (pawnIsExhibitionist)
|
||||
{ pawnThoughtDefName += "Exhibitionist"; }
|
||||
|
||||
bool witnessIsVoyeur = witness.HasTrait("Voyeur") || xxx.has_quirk(witness, "Voyeur");
|
||||
if (witnessIsVoyeur)
|
||||
{ witnessThoughtDefName += "Voyeur"; }
|
||||
|
||||
bool sexIsRitual = pawn.GetLord() != null && pawn.GetLord().LordJob is LordJob_Ritual && witness?.Ideo == pawn?.Ideo;
|
||||
bool pawnIsVictim = pawn.CurJob.def == xxx.gettin_raped || pawn.Dead;
|
||||
bool pawnIsCheating = pawnIsVictim == false && PawnIsCheatingOnPartner(pawn, witness);
|
||||
|
||||
witnessJoiningSex = Random.value < BasicSettings.chanceForOtherToJoinInSex && PawnCanInvitePasserbyForSex(witness, pawn.GetAllSexParticipants());
|
||||
|
||||
// Determine if there are any issues with the witness' morals
|
||||
TabooStatus tabooStatus = CheckSexJobAgainstMorals(witness, jobDriver, out Precept precept);
|
||||
|
||||
if (tabooStatus == TabooStatus.MajorTaboo)
|
||||
{ witnessThoughtDefName = "SawMajorTaboo"; witnessJoiningSex = false; }
|
||||
|
||||
else if (tabooStatus == TabooStatus.MinorTaboo)
|
||||
{ witnessThoughtDefName = "SawTaboo"; witnessJoiningSex = false; }
|
||||
|
||||
else if (pawnIsCheating)
|
||||
{ witnessThoughtDefName = "CheatedOnMe"; witnessJoiningSex = false; }
|
||||
|
||||
else if (BasicSettings.needPrivacy == false)
|
||||
{ witnessThoughtDefName = ""; }
|
||||
|
||||
// Apply thoughts to witness
|
||||
ThoughtDef witnessThoughtDef = DefDatabase<ThoughtDef>.GetNamedSilentFail(witnessThoughtDefName);
|
||||
|
||||
if (witnessThoughtDef != null && pawnIsVictim == false && witnessJoiningSex == false && sexIsRitual == false)
|
||||
{
|
||||
witness.needs.mood.thoughts.memories.TryGainMemory(witnessThoughtDef, pawn, precept);
|
||||
|
||||
if (witnessThoughtDef.stages[0].baseMoodEffect < 0)
|
||||
{ FleckMaker.ThrowMetaIcon(witness.Position, witness.Map, FleckDefOf.IncapIcon); }
|
||||
|
||||
// Fight or flight reaction
|
||||
if (BasicSettings.majorTabooCanStartFights &&
|
||||
(tabooStatus == TabooStatus.MajorTaboo || pawnIsCheating) &&
|
||||
witness.Drafted == false &&
|
||||
witness.jobs.curDriver is JobDriver_Flee == false &&
|
||||
witness.jobs.curDriver is JobDriver_AttackMelee == false &&
|
||||
witness.jobs.curDriver is JobDriver_Vomit == false)
|
||||
{
|
||||
// Fight
|
||||
if (pawn.RaceProps.Humanlike && witness.RaceProps.Humanlike && witness.DislikesViolence() == false && (Random.value < 0.2f || witness.EnjoysViolence()) && witness.HostileTo(pawn) == false && InteractionUtility.TryGetRandomVerbForSocialFight(witness, out Verb verbToUse))
|
||||
{
|
||||
if (witness.LastAttackedTarget.Pawn != pawn || (pawn.mindState.lastAttackTargetTick < 0 && pawn.mindState.lastAttackTargetTick + Find.TickManager.TicksGame > 180))
|
||||
{
|
||||
pawn.mindState.lastAttackTargetTick = Find.TickManager.TicksGame;
|
||||
string message = witness.LabelShort + " is going to punish " + pawn.LabelShort + " for " + GenderUtility.GetPossessive(pawn.gender) + " transgression.";
|
||||
Messages.Message(message, pawn, MessageTypeDefOf.NegativeEvent);
|
||||
}
|
||||
|
||||
Job job = JobMaker.MakeJob(JobDefOf.SocialFight, pawn);
|
||||
job.maxNumMeleeAttacks = 1;
|
||||
job.verbToUse = verbToUse;
|
||||
|
||||
witness.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
|
||||
witness.jobs.StartJob(job);
|
||||
}
|
||||
|
||||
// Vomit
|
||||
else if (jobDriver.Partner != null && jobDriver.Partner.Dead)
|
||||
{
|
||||
Job jobVomit = JobMaker.MakeJob(JobDefOf.Vomit);
|
||||
Job jobFlee = JobMaker.MakeJob(JobDefOf.FleeAndCower, CellFinderLoose.GetFleeDest(witness, new List<Thing>() { pawn }, 24f), pawn);
|
||||
|
||||
witness.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
|
||||
witness.jobs.StartJob(jobVomit);
|
||||
witness.jobs.jobQueue.EnqueueFirst(jobFlee);
|
||||
}
|
||||
|
||||
// Flight
|
||||
else
|
||||
{
|
||||
Job job = JobMaker.MakeJob(JobDefOf.FleeAndCower, CellFinderLoose.GetFleeDest(witness, new List<Thing>() { pawn }, 24f), pawn);
|
||||
witness.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
|
||||
witness.jobs.StartJob(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check issue against pawn precepts
|
||||
tabooStatus = CheckSexJobAgainstMorals(pawn, jobDriver, out precept);
|
||||
|
||||
if (tabooStatus == TabooStatus.MajorTaboo)
|
||||
{ pawnThoughtDefName = "SeenCommittingMajorTaboo"; witnessJoiningSex = false; }
|
||||
|
||||
else if (tabooStatus == TabooStatus.MinorTaboo)
|
||||
{ pawnThoughtDefName = "SeenCommittingTaboo"; witnessJoiningSex = false; }
|
||||
|
||||
else if (pawnIsCheating)
|
||||
{ pawnThoughtDefName = "CaughtCheating"; witnessJoiningSex = false; }
|
||||
|
||||
else if (BasicSettings.needPrivacy == false)
|
||||
{ pawnThoughtDefName = ""; }
|
||||
|
||||
// Apply thoughts to pawn
|
||||
ThoughtDef pawnThoughtDef = DefDatabase<ThoughtDef>.GetNamedSilentFail(pawnThoughtDefName);
|
||||
|
||||
if (pawnThoughtDef != null && pawnIsVictim == false && witnessJoiningSex == false && sexIsRitual == false)
|
||||
{
|
||||
pawn.needs.mood.thoughts.memories.TryGainMemory(pawnThoughtDef, witness, precept);
|
||||
|
||||
if (pawnThoughtDef.stages[0].baseMoodEffect < 0)
|
||||
{ FleckMaker.ThrowMetaIcon(pawn.Position, pawn.Map, FleckDefOf.IncapIcon); }
|
||||
}
|
||||
|
||||
return witnessJoiningSex;
|
||||
}
|
||||
}
|
||||
}
|
BIN
Source/obj/Debug/DesignTimeResolveAssemblyReferences.cache
Normal file
BIN
Source/obj/Debug/DesignTimeResolveAssemblyReferences.cache
Normal file
Binary file not shown.
BIN
Source/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache
Normal file
BIN
Source/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache
Normal file
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
d5a3e9402b3033575abd239e1b750dc876117ae0
|
|
@ -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.
BIN
Source/obj/Debug/Rimworld-Animations-Patch.dll
Normal file
BIN
Source/obj/Debug/Rimworld-Animations-Patch.dll
Normal file
Binary file not shown.
BIN
Source/obj/Debug/Rimworld-Animations-Patch.pdb
Normal file
BIN
Source/obj/Debug/Rimworld-Animations-Patch.pdb
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue