This commit is contained in:
AbstractConcept 2022-09-30 18:34:08 -05:00
parent dab724fb50
commit f089b94044
46 changed files with 2631 additions and 393 deletions

Binary file not shown.

Binary file not shown.

View File

@ -47,11 +47,17 @@ This mod requires RJW and RimWorld-Animation to function. RimNudeWorld isn't req
Here's a brief overview of whats included in this mod
- 12 animations for casual sex
- 7 animations for solo masturbation
- 1 animations for group sex
- 2 animations for group sex
- Animated hands for certain animations
- A need for privacy during sex
- Pawns will react differently to the different types of sex that they witness - their reaction depends on their trait and ideology
- A range of tweaks to how body parts are displayed, particularly when wearing different types of apparel
A full list of changes can be found in the main discussion thread on the forum
Known conflicts
- To avoid certain graphical issues, Dubs Apparel Tweaks should be loaded before this mod while Babies and Children should be loaded after (if you choose to use them). This will mean that you will not be able to run this mod with Dubs Apparel Tweaks and Babies and Children running at the same time
- This mod will result in graphical errors when using Pawnmorpher's 'Pawn scaling' setting
</description>
</ModMetaData>

View File

@ -1,4 +1,21 @@
Chnage log v 1.2.1
Change log v 1.2.2
- Fix: Fix for portraits appearing blank when using the 'crop apparel' feature
- Fix: Underwear counts as covering private parts when determining basic nudity
- Fix: A pawn won't think that they are being cheated on if the catch their partner having sex with an animal or corpse
- Fix: Animals, wild men, mechanoids and factions that are hostile to you no longer have opinions about witnessed sex
- Fix: Fixed an issue which was causing pawns attending orgies to get stuck
- Fix: Fixed an issue where some animations without an orgasm event included would result in an error occurring
- Fix: Pawns no longer have missing body parts while showering
- Fix: Penises no longer not show while wearing pants (unless a pawn is wearing nothing else)
- Change: Significant performance optimisations have been made. You can now host orgies involving 50 or more people. Disable animated hands to further improve performance
- Change: Individualized moodlets are now applied for witnessing different sex acts (currently supports rape, beastiality, xenophilia, necrophilia)
- Change: Pawns might run away and vomit up their lunch if they witness necrophilia
- Change: Pawns who witness a cheating partner or xenophilia might go on an insulting spree
- Addition: Added a setting that allows you toggle whether wearing underwear alone is sufficient to satisfy an ideological need for modesty
- Addition: Added moodlets for pawns that have their underwear showing. These moodlets can be toggled on or off in the settings
- Addition: Added a new threesome animation for one male and two females ('the sandwich')
Change log v 1.2.1
- Change: Optimisation of the body part rendering code - there should be much better frame rates now. Just avoid hosting orgies involving 50 or more people...
- Addition: Added option to the basic settings menu to toggle hair redrawing for portraits (helps make sure that body parts are properly covered by long hair)

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Manifest>
<version>1.2.0</version>
<manifestUri>https://gitgud.io/AbstractConcept/rimworld-animations-patch/-/blob/abscon/About/Manifest.xml</manifestUri>
<version>1.2.2</version>
<downloadUri>https://gitgud.io/AbstractConcept/rimworld-animations-patch</downloadUri>
</Manifest>

File diff suppressed because it is too large Load Diff

53
Defs/HediffDefs.xml Normal file
View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<HediffDef>
<defName>Disquiet</defName>
<label>disquiet</label>
<description>This person saw something that upset them and it's weighting on their thoughts.</description>
<stages>
<li>
<label>present</label>
<becomeVisible>false</becomeVisible>
</li>
</stages>
</HediffDef>
<HediffDef>
<defName>Panicked</defName>
<label>panicked</label>
<description>This person saw something that has shaked them to their core. Will they run or fight?</description>
<stages>
<li>
<label>present</label>
<becomeVisible>false</becomeVisible>
</li>
</stages>
</HediffDef>
<HediffDef>
<defName>Nauseated</defName>
<label>nauseated</label>
<description>This poor soul saw something something so utterly foul that they just might be sick from the thought.</description>
<stages>
<li>
<label>present</label>
<becomeVisible>false</becomeVisible>
</li>
</stages>
</HediffDef>
<HediffDef>
<defName>Indignant</defName>
<label>indignant</label>
<description>This person witnessed something quite distasteful and is going to give the offender a piece of their mind.</description>
<stages>
<li>
<label>present</label>
<becomeVisible>false</becomeVisible>
</li>
</stages>
</HediffDef>
</Defs>

View File

@ -40,7 +40,7 @@
<stages>
<li>
<label>exhibitionist caught masturbatin'</label>
<description>It's OK. You can look!</description>
<description>It's OK, come and have a closer look...</description>
<baseMoodEffect>4</baseMoodEffect>
</li>
</stages>
@ -54,7 +54,7 @@
<stages>
<li>
<label>exhibitionist caught lovin'</label>
<description>Hope they enjoyed the show!</description>
<description>Heheh, hope they enjoyed the show!</description>
<baseMoodEffect>6</baseMoodEffect>
</li>
</stages>
@ -98,7 +98,7 @@
<stages>
<li>
<label>voyeur saw some masturbatin'</label>
<description>Damn, that was hot!</description>
<description>I think that awoke something in me...</description>
<baseMoodEffect>4</baseMoodEffect>
</li>
</stages>
@ -112,7 +112,7 @@
<stages>
<li>
<label>voyeur saw some lovin'</label>
<description>Wow, they were really going at it!</description>
<description>Damn. So hot!</description>
<baseMoodEffect>6</baseMoodEffect>
</li>
</stages>

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<ThoughtDef>
<defName>SawBestiality_Abhorrent</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<nullifyingTraits>
<li>Zoophile</li>
</nullifyingTraits>
<hediff>Panicked</hediff>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>horrified by bestiality</label>
<description>How could that violate that poor animal like that?!</description>
<baseMoodEffect>-20</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawBestiality_Horrible</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<nullifyingTraits>
<li>Zoophile</li>
</nullifyingTraits>
<hediff>Disquiet</hediff>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>disturbed by bestiality</label>
<description>Get away from that poor creature!</description>
<baseMoodEffect>-15</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawBestiality_Disapproved</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<nullifyingTraits>
<li>Zoophile</li>
</nullifyingTraits>
<hediff>Disquiet</hediff>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>witnessed bestiality</label>
<description>Ew. Just. Ew.</description>
<baseMoodEffect>-10</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawBestiality_Acceptable</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<nullifyingTraits>
<li>Zoophile</li>
</nullifyingTraits>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>saw bestiality</label>
<description>Uhhh. Does your friend want some kibble?</description>
<baseMoodEffect>-5</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawBestiality_Honorable</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>observed bestiality</label>
<description>The bond between us and our animals is strong.</description>
<baseMoodEffect>+2</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
</Defs>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<ThoughtDef>
<defName>SawNecrophilia_Abhorrent</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<nullifyingTraits>
<li>Necrophiliac</li>
</nullifyingTraits>
<hediff>Nauseated</hediff>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>horrified by necrophilia</label>
<description>*Urp* I'm gunna hurl!</description>
<baseMoodEffect>-20</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawNecrophilia_Horrible</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<nullifyingTraits>
<li>Necrophiliac</li>
</nullifyingTraits>
<hediff>Disquiet</hediff>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>disturbed by necrophilia</label>
<description>What were they thinking?!</description>
<baseMoodEffect>-15</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawNecrophilia_Disapproved</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<nullifyingTraits>
<li>Necrophiliac</li>
</nullifyingTraits>
<hediff>Disquiet</hediff>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>witnessed necrophilia</label>
<description>Even the dead get no rest here.</description>
<baseMoodEffect>-10</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawNecrophilia_Acceptable</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>saw necrophilia</label>
<description>I just hope they bury it when they're done.</description>
<baseMoodEffect>-5</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawNecrophilia_Honorable</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>observed necrophilia</label>
<description>Passion transcends the grave.</description>
<baseMoodEffect>+2</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
</Defs>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<ThoughtDef>
<defName>SawRape_Abhorrent</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<nullifyingTraits>
<li>Rapist</li>
</nullifyingTraits>
<hediff>Panicked</hediff>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>horrified by rape</label>
<description>Someone please help that poor soul!</description>
<baseMoodEffect>-20</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawRape_Horrible</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<nullifyingTraits>
<li>Rapist</li>
</nullifyingTraits>
<hediff>Disquiet</hediff>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>disturbed by rape</label>
<description>I feel all shaky.</description>
<baseMoodEffect>-15</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawRape_Disapproved</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<nullifyingTraits>
<li>Rapist</li>
</nullifyingTraits>
<hediff>Disquiet</hediff>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>witnessed rape</label>
<description>Is this what life is going to be here?</description>
<baseMoodEffect>-10</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawRape_Acceptable</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>saw rape</label>
<description>Urgh, carry on.</description>
<baseMoodEffect>-5</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawRape_Honorable</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>observed rape</label>
<description>The strong claim their due from the weak.</description>
<baseMoodEffect>+2</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
</Defs>

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<ThoughtDef>
<defName>SawHAR_AlienDating_Prohibited</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<hediff>Indignant</hediff>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>horrified by xenophilia</label>
<description>Filthy degenerates!</description>
<baseMoodEffect>-15</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawHAR_AlienDating_Horrible</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<hediff>Disquiet</hediff>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>disturbed by xenophilia</label>
<description>Oh! That's not right...</description>
<baseMoodEffect>-10</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawHAR_AlienDating_Acceptable</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>saw xenophilia</label>
<description>Oops, beg your pardon!</description>
<baseMoodEffect>-5</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawHAR_AlienDating_Preferred</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>noticed xenophilia</label>
<description>I wonder what their kids would look like?</description>
<baseMoodEffect>+1</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawHAR_AlienDating_Know_Honorable</defName>
<durationDays>3</durationDays>
<stackLimit>3</stackLimit>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>observed xenophilia</label>
<description>A blending of species will make us stronger.</description>
<baseMoodEffect>+2</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
</Defs>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<ThoughtDef>
<defName>ExposeUnderwear</defName>
<workerClass>Rimworld_Animations_Patch.ThoughtWorker_ExposedUnderwear</workerClass>
<validWhileDespawned>true</validWhileDespawned>
<stages>
<li>
<label>exposed underwear</label>
<description>People can see my underwear, it's embarassing.</description>
<baseMoodEffect>-2</baseMoodEffect>
</li>
<li>
<label>exhibitionist flashing underwear</label>
<description>People can see my underwear. It's a bit thrilling, to be honest!</description>
<baseMoodEffect>1</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
</Defs>

View File

@ -43,10 +43,13 @@
<crop_apparel> Crop the bottoms of worn shirts, tank tops, etc. when not wearing pants or a skirt</crop_apparel>
<crop_apparel_desc>Only applies to torso covering apparel that lies directly on the skin and that does not cover the legs. Best used with mods that draw pants graphics, like the Visible Pants mod.\n\nRequires that the game to be reset when toggled off.</crop_apparel_desc>
<clothes_thrown_on_ground> Show discarded clothing on the floor while getting some lovin'</clothes_thrown_on_ground>
<clothes_thrown_on_ground_desc>If a pawn undresses while lovin', these items of clothing will be piled on the floor nearby. This is a purely visual effect.</clothes_thrown_on_ground_desc>
<clothes_thrown_on_ground_desc>If a pawn undresses while lovin', these items of clothing will be piled on the floor nearby.\n\nThis is a purely visual effect.</clothes_thrown_on_ground_desc>
<wearing_clothes_in_bed> Preferred state of dress for people lovin' in a bed</wearing_clothes_in_bed>
<wearing_clothes_in_bed_desc>Changing this will update the clothing preference setting in RJW (and vice versa).</wearing_clothes_in_bed_desc>
<wearing_clothes_for_quickies> Preferred state of dress for people having a quickie</wearing_clothes_for_quickies>
<wearing_clothes_for_quickies_desc>Nothing more to say.</wearing_clothes_for_quickies_desc>
<underwear_sufficent_for_ideos> Underwear satisfies ideological needs for modesty</underwear_sufficent_for_ideos>
<underwear_sufficent_for_ideos_desc>If an ideology requires that certain body parts must be covered, wearing underwear can help fulfill this requirement.\n\nYou may want to turn this setting off if you want ideologies to be more strict about what they consider to be 'modestly clothed'.\n\nIdeologies which prefer to wear fewer clothes than normal are not affected by this setting.</underwear_sufficent_for_ideos_desc>
<exposed_underwear_mood> People feel embarassed if their underwear is showing</exposed_underwear_mood>
<exposed_underwear_mood_desc>It's not as bad as being naked, but your colonists would prefer to wear some additional clothes.\n\nExhibitionists, however, will get a small thrill if their underwear is exposed.</exposed_underwear_mood_desc>
</LanguageData>

View File

@ -1,9 +1,38 @@
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
<Operation Class="PatchOperationSequence">
<success>Always</success>
<success>Normal</success>
<operations>
<li Class="PatchOperationAdd">
<success>Always</success>
<xpath>Defs/ThingDef[race][not(comps)]</xpath>
<value>
<comps />
</value>
</li>
<li Class="PatchOperationAdd">
<success>Always</success>
<xpath>Defs/AlienRace.ThingDef_AlienRace[not(comps)]</xpath>
<value>
<comps />
</value>
</li>
<li Class="PatchOperationAdd">
<xpath>Defs/ThingDef[@Name="BasePawn"]/comps</xpath>
<value>
<li Class="Rimworld_Animations_Patch.CompProperties_PawnSexData" />
</value>
</li>
<li Class="PatchOperationAdd">
<xpath>Defs/AlienRace.ThingDef_AlienRace/comps</xpath>
<value>
<li Class="Rimworld_Animations_Patch.CompProperties_PawnSexData" />
</value>
</li>
<li Class="PatchOperationConditional">
<xpath>/Defs/ThingDef[thingClass="Apparel"]/comps</xpath>
<match Class="PatchOperationAdd">
@ -26,20 +55,23 @@
</nomatch>
</li>
<li Class="PatchOperationAdd">
<xpath>/Defs/ThingDef[thingClass="Apparel"]/apparel/bodyPartGroups[li="Torso"]</xpath>
<value>
<li>ChestBPG</li>
</value>
<!--<li Class="PatchOperationConditional">
<xpath>/Defs/ThingDef[thingClass="Apparel"]/apparel/bodyPartGroups/Torso</xpath>
<match Class="PatchOperationInsert">
<xpath>/Defs/ThingDef[thingClass="Apparel"]/apparel/bodyPartGroups/Torso</xpath>
<value>
<li>ChestBPG</li>
</value>
</match>
</li>
<li Class="PatchOperationAdd">
<xpath>/Defs/ThingDef[thingClass="Apparel"]/apparel/bodyPartGroups[li="Legs"]</xpath>
<li Class="PatchOperationInsert">
<xpath>/Defs/ThingDef[thingClass="Apparel"]/apparel/bodyPartGroups/Legs</xpath>
<value>
<li>GenitalsBPG</li>
</value>
</li>
</value>
</li>-->
</operations>
</Operation>
</Patch>

View File

@ -74,6 +74,9 @@
<HintPath>..\..\rjw-master\1.3\Assemblies\RJW.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="RJW-Events">
<HintPath>..\..\rjw-events-master\1.3\Assemblies\RJW-Events.dll</HintPath>
</Reference>
<Reference Include="RJW-ToysAndMasturbation">
<HintPath>..\..\rjw-toys-and-masturbation-master\Assemblies\RJW-ToysAndMasturbation.dll</HintPath>
<Private>False</Private>
@ -104,16 +107,22 @@
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Compile Include="Scripts\Comps\CompApparelVisibility.cs" />
<Compile Include="Scripts\Comps\CompProperties_PawnSexData.cs" />
<Compile Include="Scripts\Comps\CompProperties_ApparelVisibility.cs" />
<Compile Include="Scripts\Comps\CompApparelVisibility.cs" />
<Compile Include="Scripts\Comps\CompPawnSexData.cs" />
<Compile Include="Scripts\Defs\ActorAnimationData.cs" />
<Compile Include="Scripts\Defs\BodyAddonData.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_Pawn_ApparelTracker.cs" />
<Compile Include="Scripts\Patches\HarmonyPatch_DrawGUIOverlay.cs" />
<Compile Include="Scripts\Patches\HarmonyPatch_JobDriver.cs" />
<Compile Include="Scripts\Patches\HarmonyPatch_ThoughtWorkers.cs" />
<Compile Include="Scripts\Settings\ApparelSettings.cs" />
<Compile Include="Scripts\ThoughtWorkers\ThoughtWorker_ExposedUnderwear.cs" />
<Compile Include="Scripts\Utilities\ApparelAnimationUtility.cs" />
<Compile Include="Scripts\Utilities\ApparelSettingsUtility.cs" />
<Compile Include="Scripts\Utilities\DebugMode.cs" />

View File

@ -15,7 +15,7 @@ namespace Rimworld_Animations_Patch
public Vector3 position;
public float rotation = 0f;
public bool isBeingWorn = true;
public bool? isBeingWorn = null;
public bool coversChest = false;
public bool coversGroin = false;
public bool coversBelly = false;

View File

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RimWorld;
using Verse;
using AlienRace;
using UnityEngine;
namespace Rimworld_Animations_Patch
{
public class CompPawnSexData : ThingComp
{
public HandAnimationDef handAnimationDef = null;
public Graphic handGraphic = null;
public List<BodyPartRecord> hands = new List<BodyPartRecord>();
public Dictionary<AlienPartGenerator.BodyAddon, BodyAddonData> bodyAddonData = new Dictionary<AlienPartGenerator.BodyAddon, BodyAddonData>();
public Dictionary<AlienPartGenerator.BodyAddon, BodyAddonData> bodyAddonDataPortraits = new Dictionary<AlienPartGenerator.BodyAddon, BodyAddonData>();
private Pawn pawn;
private int lastExclaimationTick = -1;
private int exclaimationCoolDown = 90;
public BodyAddonData GetBodyAddonData(AlienPartGenerator.BodyAddon bodyAddon, bool isPortrait)
{
if (pawn == null)
{ pawn = parent as Pawn; }
if (pawn == null || pawn.Map != Find.CurrentMap || bodyAddon == null) return null;
if (isPortrait)
{
if (bodyAddonDataPortraits.TryGetValue(bodyAddon, out BodyAddonData bodyAddonDatum) == false)
{
bodyAddonDatum = new BodyAddonData(pawn, bodyAddon, true);
bodyAddonDataPortraits.Add(bodyAddon, bodyAddonDatum);
}
return bodyAddonDatum;
}
else
{
if (bodyAddonData.TryGetValue(bodyAddon, out BodyAddonData bodyAddonDatum) == false)
{
bodyAddonDatum = new BodyAddonData(pawn, bodyAddon);
bodyAddonData.Add(bodyAddon, bodyAddonDatum);
}
return bodyAddonDatum;
}
}
public void UpdateBodyAddonVisibility()
{
foreach (KeyValuePair<AlienPartGenerator.BodyAddon, BodyAddonData> kvp in bodyAddonData)
{ kvp.Value.UpdateVisibility(); }
foreach (KeyValuePair<AlienPartGenerator.BodyAddon, BodyAddonData> kvp in bodyAddonDataPortraits)
{ kvp.Value.UpdateVisibility(); }
}
public void UpdateHands()
{
hands = pawn?.health?.hediffSet?.GetNotMissingParts()?.Where(x => x.def.tags.Contains(BodyPartTagDefOf.ManipulationLimbCore))?.ToList();
}
public int GetNumberOfHands()
{
if (hands.NullOrEmpty()) return 0;
return hands.Count;
}
public void TryToExclaim()
{
if (Find.TickManager.TicksGame > exclaimationCoolDown + lastExclaimationTick)
{
lastExclaimationTick = Find.TickManager.TicksGame;
FleckMaker.ThrowMetaIcon(pawn.Position, pawn.Map, FleckDefOf.IncapIcon);
}
}
}
}

View File

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using Verse;
using RimWorld;
namespace Rimworld_Animations_Patch
{
@ -12,4 +10,4 @@ namespace Rimworld_Animations_Patch
base.compClass = typeof(CompApparelVisibility);
}
}
}
}

View File

@ -0,0 +1,13 @@
using System;
using Verse;
namespace Rimworld_Animations_Patch
{
public class CompProperties_PawnSexData : CompProperties
{
public CompProperties_PawnSexData()
{
base.compClass = typeof(CompPawnSexData);
}
}
}

View File

@ -0,0 +1,191 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Verse;
using RimWorld;
using AlienRace;
using Rimworld_Animations;
using UnityEngine;
using HarmonyLib;
namespace Rimworld_Animations_Patch
{
public class BodyAddonData
{
public AlienPartGenerator.BodyAddon bodyAddon;
public BodyPartRecord bodyPartRecord;
public List<Vector3> bodyAddonOffsets = new List<Vector3>();
public bool alignsWithHead = false;
private Pawn pawn;
private string bodyType;
private PawnRenderFlags renderFlags;
private bool canDraw = false;
public BodyAddonData(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon, bool isPortrait = false)
{
this.pawn = pawn;
this.bodyAddon = bodyAddon;
if (isPortrait)
{ renderFlags |= PawnRenderFlags.Portrait; }
bodyPartRecord = pawn?.def?.race?.body?.AllParts?.FirstOrDefault(x => x.def.defName == bodyAddon?.bodyPart || x.customLabel == bodyAddon?.bodyPart);
alignsWithHead = bodyAddon.alignWithHead || (bodyPartRecord != null && bodyPartRecord.IsInGroup(BodyPartGroupDefOf.FullHead));
GenerateOffsets();
UpdateVisibility();
}
public void GenerateOffsets()
{
bodyType = pawn.story.bodyType.defName;
bodyAddonOffsets.Clear();
int bodyAddonIndex = (pawn.def as ThingDef_AlienRace).alienRace.generalSettings.alienPartGenerator.bodyAddons.ToList().IndexOf(bodyAddon);
AlienPartGenerator.AlienComp alienComp = pawn.GetComp<AlienPartGenerator.AlienComp>();
Graphic addonGraphic = alienComp.addonGraphics[bodyAddonIndex];
for (int i = 0; i < 4; i++)
{
Rot4 apparentRotation = new Rot4(i);
// Get basic offset for body addon
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 rotationOffsets = bodyAddon.offsets.GetOffset(apparentRotation);
Vector3 bodyAddonOffset = bodyTypeOffset + ((rotationOffsets != null) ? rotationOffsets.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 (bodyAddon.bodyPart == "Genitals" || bodyAddon.bodyPart == "Chest" || bodyAddon.bodyPart == "Anus" || addonGraphic.path.ToLower().Contains("belly"))
{
bodyAddonOffset.y = (bodyAddonOffset.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 &&
apparentRotation == Rot4.South)
{ bodyAddonOffset.y += 0.010f; }
}
// Otherwise use the standard offsets
else
{ bodyAddonOffset.y = 0.3f + bodyAddonOffset.y; }
// Draw addons infront of body
if (!bodyAddon.inFrontOfBody)
{ bodyAddonOffset.y *= -1f; }
// Adjust for facing
if (apparentRotation == Rot4.North)
{
if (bodyAddon.layerInvert)
{ bodyAddonOffset.y = -bodyAddonOffset.y; }
}
if (apparentRotation == Rot4.East)
{ bodyAddonOffset.x = -bodyAddonOffset.x; }
// Adjustment for body addons attached to the head that are not marked as such
if (alignsWithHead && bodyAddon.alignWithHead == false)
{ bodyAddonOffset -= pawn.Drawer.renderer.BaseHeadOffsetAt(apparentRotation); }
// Done
bodyAddonOffsets.Add(bodyAddonOffset);
}
}
public Vector3 GetOffset(Rot4 facing)
{
if (pawn.story.bodyType.defName != bodyType)
{ GenerateOffsets(); }
return bodyAddonOffsets[facing.AsInt];
}
public bool CanDraw()
{
return pawn.Drawer.renderer.graphics.apparelGraphics.Any() == false || canDraw;
}
public void UpdateVisibility()
{
canDraw = true;
if (pawn.CurrentBed()?.def.building.bed_showSleeperBody == false && bodyAddon.drawnInBed == false)
{canDraw = false; return; }
if (bodyAddon.backstoryRequirement.NullOrEmpty() == false && pawn.story.AllBackstories.Any((Backstory x) => x.identifier == bodyAddon.backstoryRequirement) == false)
{ canDraw = false; return; }
if (bodyAddon.drawnDesiccated == false && pawn?.Corpse?.GetRotStage() == RotStage.Dessicated)
{ canDraw = false; return; }
if (pawn.health.hediffSet.GetNotMissingParts().Contains(bodyPartRecord) == false)
{ canDraw = false; return; }
if (pawn.gender == Gender.Female && bodyAddon.drawForFemale == false || pawn.gender == Gender.Male && bodyAddon.drawForMale == false)
{ canDraw = false; return; }
if (bodyAddon.bodyTypeRequirement.NullOrEmpty() == false && pawn.story.bodyType.ToString() != bodyAddon.bodyTypeRequirement)
{ canDraw = false; return; }
if ((pawn.GetPosture() == PawnPosture.LayingOnGroundNormal || pawn.GetPosture() == PawnPosture.LayingOnGroundFaceUp) && bodyAddon.drawnOnGround == false)
{ canDraw = false; return; }
foreach (Apparel apparel in pawn.apparel.WornApparel)
{
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
LoadRimNudeData(comp);
if (comp.isBeingWorn == false) continue;
if (bodyAddon.bodyPart == "Genitals" || bodyAddon.bodyPart == "Anus" || bodyAddon.bodyPart == "Chest" || bodyAddon.hediffGraphics?.Any(x => x.path.ToLower().Contains("belly")) == true)
{
if ((bodyAddon.bodyPart == "Genitals" || bodyAddon.bodyPart == "Anus") && comp.coversGroin)
{ canDraw = false; return; };
if (bodyAddon.bodyPart == "Chest" && comp.coversChest)
{ canDraw = false; return; };
if (bodyAddon.hediffGraphics?.Any(x => x.path.ToLower().Contains("belly")) == true && comp.coversBelly)
{ canDraw = false; return; }
}
else
{
if (bodyAddon.hiddenUnderApparelFor?.Any(x => apparel.def.apparel.hatRenderedFrontOfFace == false && apparel.def.apparel.bodyPartGroups.Contains(x)) == true)
{ canDraw = false; return; };
if (bodyAddon.hiddenUnderApparelTag?.Any(x => apparel.def.apparel.hatRenderedFrontOfFace == false && apparel.def.apparel.tags.Contains(x)) == true)
{ canDraw = false; return; };
}
}
}
public void LoadRimNudeData(CompApparelVisibility comp)
{
if (comp == null || comp.rimNudeDataStatus == RimNudeDataStatus.Unavailable)
{ return; }
if (comp.rimNudeDataStatus == RimNudeDataStatus.NotLoaded)
{
RimNudeData rimNudeData = ApparelSettings.GetRimNudeData(comp.apparel);
if (rimNudeData == null)
{
comp.rimNudeDataStatus = RimNudeDataStatus.Unavailable;
return;
}
comp.coversBelly = rimNudeData.coversBelly;
comp.coversChest = rimNudeData.coversChest;
comp.coversGroin = rimNudeData.coversGroin;
comp.rimNudeDataStatus = RimNudeDataStatus.Loaded;
}
}
}
}

View File

@ -128,8 +128,14 @@ namespace Rimworld_Animations_Patch
if (pawn.IsHavingSex() == false && pawn.IsMasturbating() == false)
{ return true; }
if (pawn.GetLord() != null && pawn.GetLord().LordJob is LordJob_Ritual)
{ return true; }
if (pawn.GetLord() != null && pawn.GetLord().LordJob is LordJob_Joinable_Party)
{ return true; }
bool hasPrivacy = true;
bool isExhibitionist = pawn.HasTrait("Exhibitionist") || xxx.has_quirk(pawn, "Exhibitionist");
bool isExhibitionist = xxx.has_quirk(pawn, "Exhibitionist");
pawn.IsInBed(out Building bed);
@ -192,22 +198,19 @@ namespace Rimworld_Animations_Patch
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();
return pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def == PatchBodyPartDefOf.Hand)?.ToList();
}
public static bool HasPreceptForIssue(this Pawn pawn, IssueDef issueDef, out Precept precept)
public static bool HasPreceptForIssue(this Pawn pawn, string issueDefName, out Precept precept)
{
precept = null;
if (pawn?.Ideo == null || issueDef == null)
if (pawn?.Ideo == null)
{ return false; }
foreach (Precept _precept in pawn.Ideo.PreceptsListForReading)
{
if (_precept.def.issue == issueDef)
if (_precept.def.issue.defName == issueDefName)
{
precept = _precept;
return true;
@ -217,28 +220,6 @@ namespace Rimworld_Animations_Patch
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)
@ -275,6 +256,14 @@ namespace Rimworld_Animations_Patch
if (traitDef == null)
{ traitDef = DefDatabase<TraitDef>.GetNamedSilentFail(trait.ToLower()); }
return HasTrait(pawn, traitDef);
}
public static bool HasTrait(this Pawn pawn, TraitDef traitDef)
{
if (pawn?.story?.traits?.allTraits == null || pawn.story.traits.allTraits.NullOrEmpty())
{ return false; }
if (traitDef == null)
{ return false; }

View File

@ -31,6 +31,9 @@ namespace Rimworld_Animations_Patch
graphic = GraphicMaskingUtility.ApplyGraphicWithMasks(graphic, graphicWithApparelMask, true);
//DebugMode.Message("Applying apparel mask: Masks/apparel_shirt_mask_" + bodyType.defName + " to " + apparel.def.defName + " (" + graphic.path + ")");
if (apparel.Wearer != null)
{ PortraitsCache.SetDirty(apparel.Wearer); }
}
}

View File

@ -89,7 +89,7 @@ namespace Rimworld_Animations_Patch
if (comp != null)
{
comp.isBeingWorn = true;
comp.isBeingWorn = null;
comp.rimNudeDataStatus = RimNudeDataStatus.NotLoaded;
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RimWorld;
using Verse;
using HarmonyLib;
namespace Rimworld_Animations_Patch
{
[HarmonyPatch(typeof(Pawn_ApparelTracker), "HasBasicApparel")]
public static class HarmonyPatch_Pawn_ApparelTracker_HasBasicApparel
{
public static void Postfix(Pawn_ApparelTracker __instance, ref bool hasPants, ref bool hasShirt)
{
if (__instance?.pawn?.apparel?.WornApparel == null || __instance.pawn.apparel.WornApparel.NullOrEmpty()) return;
if (hasPants == false)
{
if (__instance.pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.GenitalsBPG)))
{ hasPants = true; }
}
if (hasShirt == false)
{
if (__instance.pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.ChestBPG)))
{ hasShirt = true; }
}
}
}
[HarmonyPatch(typeof(Pawn_ApparelTracker), "Notify_ApparelChanged")]
public static class HarmonyPatch_Pawn_ApparelTracker_Notify_ApparelChanged
{
public static void Postfix(Pawn_ApparelTracker __instance)
{
__instance?.pawn?.TryGetComp<CompPawnSexData>()?.UpdateBodyAddonVisibility();
}
}
}

View File

@ -16,29 +16,30 @@ namespace Rimworld_Animations_Patch
{
public static void Postfix(ref JobDriver_Sex __instance)
{
Pawn pawn = __instance.pawn;
// 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 &&
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;
ThoughtDef thoughtDef = SexInteractionUtility.GetThoughtsAboutSexAct(other, __instance, out Precept precept);
// Find candidates to invite
if (other != null && (int)SexInteractionUtility.CheckSexJobAgainstMorals(other, __instance, out Precept precept) <= 0 &&
SexInteractionUtility.PawnCanInvitePasserbyForSex(other, pawn.GetAllSexParticipants()))
if (other != null && thoughtDef?.hediff == null && SexInteractionUtility.InvitePasserbyForSex(other, pawn.GetAllSexParticipants()))
{
DebugMode.Message(other.NameShortColored + " is a potential candidate");
candidates.Add(other);
@ -436,7 +437,7 @@ namespace Rimworld_Animations_Patch
{
public static bool Prefix(Pawn pawn, bool keep_hat_on)
{
if (!xxx.is_human(pawn)) return false;
if (pawn == null || !xxx.is_human(pawn)) return false;
if (pawn.Map != Find.CurrentMap) return false;
pawn.Drawer.renderer.graphics.ClearCache();
@ -448,10 +449,11 @@ namespace Rimworld_Animations_Patch
{
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
if ((comp == null || comp.isBeingWorn) && ApparelGraphicRecordGetter.TryGetGraphicApparel(apparel, pawn.story.bodyType, out ApparelGraphicRecord item))
if (comp != null && comp.isBeingWorn == true && ApparelGraphicRecordGetter.TryGetGraphicApparel(apparel, pawn.story.bodyType, out ApparelGraphicRecord item))
{ pawn.Drawer.renderer.graphics.apparelGraphics.Add(item); }
}
pawn?.TryGetComp<CompPawnSexData>()?.UpdateBodyAddonVisibility();
GlobalTextureAtlasManager.TryMarkPawnFrameSetDirty(pawn);
return false;

View File

@ -12,7 +12,7 @@ namespace Rimworld_Animations_Patch
[StaticConstructorOnStartup]
public static class HarmonyPatch_RimNudeWorld
{
/*static HarmonyPatch_RimNudeWorld()
static HarmonyPatch_RimNudeWorld()
{
try
{
@ -21,7 +21,7 @@ namespace Rimworld_Animations_Patch
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")));
prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_RimNudeWorld), "Prefix_HarmonyPatch_DrawAddons")));
}
}))();
}
@ -29,9 +29,9 @@ namespace Rimworld_Animations_Patch
}
// Patch RimNudeWorld to override the revealing apparel feature; this task is handled by the new apparel settings system
public static bool Prefix_DrawAddons()
public static bool Prefix_HarmonyPatch_DrawAddons()
{
return false;
}*/
}
}
}

View File

@ -24,19 +24,42 @@ namespace Rimworld_Animations_Patch
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")));
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("BodyAddon"), "CanDrawAddon"),
postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Postfix_CanDrawAddon")));
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.CompBodyAnimator"), "StartAnimation"),
postfix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Postfix_StartAnimation")));
(new Harmony("Rimworld_Animations_Patch")).Patch(AccessTools.Method(AccessTools.TypeByName("Rimworld_Animations.AnimationUtility"), "tryFindAnimation"),
prefix: new HarmonyMethod(AccessTools.Method(typeof(HarmonyPatch_Rimworld_Animations), "Prefix_tryFindAnimation")));
}
// Update hand count for all participants before finding an animation
public static void Prefix_tryFindAnimation(ref List<Pawn> participants)
{
foreach (Pawn participant in participants)
{
CompPawnSexData comp = participant.TryGetComp<CompPawnSexData>();
if (comp == null) return;
comp.UpdateHands();
}
}
// Update hand animation def on anim start
public static void Postfix_StartAnimation(CompBodyAnimator __instance)
{
CompPawnSexData comp = __instance.pawn.TryGetComp<CompPawnSexData>();
if (comp == null) return;
comp.handAnimationDef = DefDatabase<HandAnimationDef>.AllDefs.FirstOrDefault(x => x.animationDefName == __instance.pawn?.GetAnimationData()?.animationDef?.defName);
}
// 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);
int? handCount = pawn.TryGetComp<CompPawnSexData>()?.GetNumberOfHands();
if (handCount.HasValue == false)
{ handCount = 0; }
var hands = pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def.defName == "Hand");
if (hands != null)
{ handCount = hands.Count(); }
bool pawnInBed = pawn.IsInBed(out Building bed);
if (requiredGenitals.NullOrEmpty())
{ return; }
@ -82,28 +105,6 @@ namespace Rimworld_Animations_Patch
}
}
// Determine if a body addon is covered by apparel
public static bool BodyAddonCoveredByWornApparel(Pawn pawn, AlienPartGenerator.BodyAddon bodyAddon)
{
if (pawn?.apparel?.WornApparel == null || bodyAddon == null)
{ return false; }
foreach (Apparel apparel in pawn.apparel.WornApparel)
{
if (ApparelAnimationUtility.PrivatePartCoveredByApparel(apparel, bodyAddon.bodyPart))
{ return true; }
}
return false;
}
public static void Postfix_CanDrawAddon(AlienPartGenerator.BodyAddon __instance, ref bool __result, Pawn pawn)
{
if (__result == false) return;
__result = BodyAddonCoveredByWornApparel(pawn, __instance) == 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)
{
@ -113,14 +114,15 @@ namespace Rimworld_Animations_Patch
if (ApparelSettings.clothesThrownOnGround)
{ ApparelAnimationUtility.TryToDrawApparelOnFloor(pawn); }
// Get components
// Get actor components and body addons
List<AlienPartGenerator.BodyAddon> bodyAddons = alienProps.alienRace.generalSettings.alienPartGenerator.bodyAddons.ToList();
AlienPartGenerator.AlienComp alienComp = pawn.GetComp<AlienPartGenerator.AlienComp>();
CompBodyAnimator pawnAnimator = pawn.TryGetComp<CompBodyAnimator>();
CompBodyAnimator animatorComp = pawn.TryGetComp<CompBodyAnimator>();
CompPawnSexData sexDataComp = pawn.TryGetComp<CompPawnSexData>();
if (sexDataComp == null) return true;
// Get available hands
var hands = pawn.health.hediffSet.GetNotMissingParts().Where(x => x.def.defName == "Hand");
int handsAvailableCount = hands.Count();
int handsAvailableCount = sexDataComp.GetNumberOfHands();
// 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
@ -131,129 +133,59 @@ namespace Rimworld_Animations_Patch
{
int i = idxBodyAddons[idx];
// Get body addon components
AlienPartGenerator.BodyAddon bodyAddon = bodyAddons[i];
Graphic addonGraphic = alienComp.addonGraphics[i];
BodyAddonData bodyAddonDatum = sexDataComp.GetBodyAddonData(bodyAddon, renderFlags.FlagSet(PawnRenderFlags.Portrait));
if (bodyAddonDatum == null) continue;
bool canDraw = addonGraphic.path.ToLower().Contains("featureless") == false && bodyAddon.CanDrawAddon(pawn);
bool drawHand = !renderFlags.FlagSet(PawnRenderFlags.Portrait) && BasicSettings.showHands && handsAvailableCount > 0 && HandAnimationUtility.BodyPartIsBeingTouched(pawn, addonGraphic.path, out var handData);
// Can draw?
bool canDraw = addonGraphic.path.ToLower().Contains("featureless") == false && bodyAddonDatum.CanDraw();
bool drawHand = BasicSettings.showHands && handsAvailableCount > 0 && renderFlags.FlagSet(PawnRenderFlags.Portrait) == false;
if (canDraw == false && drawHand == false)
{ continue; }
BodyPartRecord bodyPartRecord = AnimationPatchUtility.GetBodyPartRecord(pawn, bodyAddon.bodyPart);
bool alignWithHead = bodyAddon.alignWithHead || (bodyPartRecord != null && bodyPartRecord.IsInGroup(BodyPartGroupDefOf.FullHead));
// Get body angle
float bodyAngle = animatorComp?.isAnimating == true && renderFlags.FlagSet(PawnRenderFlags.Portrait) == false ? animatorComp.bodyAngle : quat.eulerAngles.y;
bodyAngle = bodyAngle < 0f ? 360f + (bodyAngle % 360f) : bodyAngle % 360f;
// Get the apparent rotation and body addon angle
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 &&
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 (renderFlags.FlagSet(PawnRenderFlags.Portrait) == false && animatorComp?.isAnimating == true)
{
if (bodyAddon.layerInvert)
{ vector2.y = -vector2.y; }
apparentRotation = bodyAddonDatum.alignsWithHead ? animatorComp.headFacing : animatorComp.bodyFacing;
bodyAddonAngle = 0f;
if (animatorComp.controlGenitalAngle && addonGraphic.path.ToLower().Contains("penis"))
{ bodyAddonAngle += AnimationSettings.controlGenitalRotation ? animatorComp.genitalAngle : 0f; }
if (bodyAddonDatum.alignsWithHead)
{ bodyAngle = animatorComp.headAngle; }
}
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);
bodyAddonAngle = bodyAddonAngle < 0f ? 360f - (bodyAddonAngle % 360) : bodyAddonAngle % 360f;
float combinedAngle = (bodyAngle + bodyAddonAngle) < 0f ? 360f + ((bodyAngle + bodyAddonAngle) % 360) : (bodyAngle + bodyAddonAngle) % 360f;
Vector3 bodyAddonPosition = vector + (bodyAddonDatum.alignsWithHead ? headOffset : Vector3.zero) + bodyAddonDatum.GetOffset(rotation).RotatedBy(angle: bodyAngle);
// Draw the addon if visible
if (canDraw)
{
//DebugMode.Message("Drawing " + addonGraphic.path);
GenDraw.DrawMeshNowOrLater(mesh: addonGraphic.MeshAt(rot: apparentRotation),
loc: finalPosition,
quat: Quaternion.AngleAxis(angle: bodyAddonAngle, axis: Vector3.up) * quatAdditional * addonRotation,
loc: bodyAddonPosition,
quat: Mathf.Approximately(combinedAngle, 0f) ? quat : Quaternion.AngleAxis(angle: combinedAngle, axis: Vector3.up),
mat: addonGraphic.MatAt(rot: apparentRotation),
drawNow: renderFlags.FlagSet(PawnRenderFlags.DrawNow));
}
// Draw hand over the body part if required
if (drawHand)
{
if (HandAnimationUtility.TryToDrawHand(pawn, addonGraphic.path, finalPosition, finalAngle, rotation, renderFlags))
{
float finalAngle = 0;
if (HandAnimationUtility.TryToDrawHand(pawn, addonGraphic.path, bodyAddonPosition, finalAngle, rotation, renderFlags))
{ handsAvailableCount--; }
}
}
@ -263,8 +195,8 @@ namespace Rimworld_Animations_Patch
{
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;
Rot4 headFacing = animatorComp != null && animatorComp.isAnimating && !renderFlags.FlagSet(PawnRenderFlags.Portrait) ? animatorComp.headFacing : rotation;
float headAngle = animatorComp != null && animatorComp.isAnimating && !renderFlags.FlagSet(PawnRenderFlags.Portrait) ? animatorComp.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 });
@ -303,5 +235,5 @@ namespace Rimworld_Animations_Patch
Hair = rootLoc + YOffset_OnHead; (~ 0.029)
Hat (over hair) = rootLoc + YOffset_PostHead; (~ 0.031)
*/
}
}
}
}

View File

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RimWorld;
using Verse;
using HarmonyLib;
using RJW_Events;
namespace Rimworld_Animations_Patch
{
[HarmonyPatch(typeof(ThinkNode_ConditionalNude), "Satisfied")]
public static class HarmonyPatch_ThinkNode_ConditionalNude
{
public static void Postfix(ref bool __result, Pawn pawn)
{
if (__result == false && pawn?.apparel?.WornApparel != null)
{
// If 'isBeingWorn' has a value, the apparel has already been checked if it should be discarded
if (pawn.apparel.WornApparel.Any(x => x.TryGetComp<CompApparelVisibility>() != null && x.TryGetComp<CompApparelVisibility>().isBeingWorn.HasValue))
{ __result = true; return; }
}
}
}
[HarmonyPatch(typeof(ThoughtWorker_Precept_GroinChestHairOrFaceUncovered), "HasUncoveredGroinChestHairOrFace")]
public static class HarmonyPatch_ThoughtWorker_Precept_GroinChestHairOrFaceUncovered
{
public static void Postfix(ref bool __result, Pawn p)
{
if (__result == false) return;
Pawn pawn = p;
if (ApparelSettings.underwearSufficentForIdeos == false) return;
if (pawn?.apparel == null)
{ __result = false; return; }
if (pawn.apparel.WornApparel.NullOrEmpty())
{ __result = true; return; }
bool fullHeadCovered = pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.FullHead));
bool groinCovered = pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs) || x.def.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.GenitalsBPG));
bool chestCovered = pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) || x.def.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.ChestBPG));
bool faceCovered = fullHeadCovered || pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Eyes));
bool hairCovered = fullHeadCovered || pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.UpperHead));
__result = !(groinCovered && chestCovered && faceCovered && hairCovered);
}
}
[HarmonyPatch(typeof(ThoughtWorker_Precept_GroinChestOrHairUncovered), "HasUncoveredGroinChestOrHair")]
public static class HarmonyPatch_ThoughtWorker_Precept_GroinChestOrHairUncovered
{
public static void Postfix(ref bool __result, Pawn p)
{
if (__result == false) return;
Pawn pawn = p;
if (ApparelSettings.underwearSufficentForIdeos == false) return;
if (pawn?.apparel == null)
{ __result = false; return; }
if (pawn.apparel.WornApparel.NullOrEmpty())
{ __result = true; return; }
bool fullHeadCovered = pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.FullHead));
bool groinCovered = pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs) || x.def.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.GenitalsBPG));
bool chestCovered = pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) || x.def.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.ChestBPG));
bool hairCovered = fullHeadCovered || pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.UpperHead));
__result = !(groinCovered && chestCovered && hairCovered);
}
}
[HarmonyPatch(typeof(ThoughtWorker_Precept_GroinOrChestUncovered), "HasUncoveredGroinOrChest")]
public static class HarmonyPatch_ThoughtWorker_Precept_HasUncoveredGroinOrChest
{
public static void Postfix(ref bool __result, Pawn p)
{
if (__result == false) return;
Pawn pawn = p;
if (ApparelSettings.underwearSufficentForIdeos == false) return;
if (pawn?.apparel == null)
{ __result = false; return; }
if (pawn.apparel.WornApparel.NullOrEmpty())
{ __result = true; return; }
bool groinCovered = pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs) || x.def.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.GenitalsBPG));
bool chestCovered = pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) || x.def.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.ChestBPG));
__result = !(groinCovered && chestCovered);
}
}
[HarmonyPatch(typeof(ThoughtWorker_Precept_GroinUncovered), "HasUncoveredGroin")]
public static class HarmonyPatch_ThoughtWorker_Precept_GroinUncovered
{
public static void Postfix(ref bool __result, Pawn p)
{
if (__result == false) return;
Pawn pawn = p;
if (ApparelSettings.underwearSufficentForIdeos == false) return;
if (pawn?.apparel == null)
{ __result = false; return; }
if (pawn.apparel.WornApparel.NullOrEmpty())
{ __result = true; return; }
bool groinCovered = pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs) || x.def.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.GenitalsBPG));
__result = !groinCovered;
}
}
}

View File

@ -15,6 +15,8 @@ namespace Rimworld_Animations_Patch
public static bool cropApparel = false;
public static bool clothesThrownOnGround = true;
public static RJWPreferenceSettings.Clothing apparelWornForQuickies = RJWPreferenceSettings.Clothing.Clothed;
public static bool underwearSufficentForIdeos = true;
public static bool exposedUnderwearMood = true;
public override void ExposeData()
{
@ -23,6 +25,8 @@ namespace Rimworld_Animations_Patch
Scribe_Values.Look(ref cropApparel, "cropApparel", false);
Scribe_Values.Look(ref clothesThrownOnGround, "clothesThrownOnGround", true);
Scribe_Values.Look(ref apparelWornForQuickies, "apparelWornForQuickies", RJWPreferenceSettings.Clothing.Clothed);
Scribe_Values.Look(ref underwearSufficentForIdeos, "underwearSufficentForIdeos", true);
Scribe_Values.Look(ref underwearSufficentForIdeos, "exposedUnderwearMood", true);
}
public static RimNudeData GetRimNudeData(Apparel apparel)
@ -42,8 +46,8 @@ namespace Rimworld_Animations_Patch
public class ApparelSettingsDisplay : Mod
{
private const float windowY = 250f;
private const float windowHeight = 360f;
private const float windowY = 280f;
private const float windowHeight = 330f;
private Vector2 scrollPosition;
private const float scrollBarWidthMargin = 18f;
@ -83,6 +87,8 @@ namespace Rimworld_Animations_Patch
foreach (Pawn pawn in Current.Game.CurrentMap.mapPawns.AllPawns)
{
pawn.Drawer.renderer.graphics.ResolveAllGraphics();
pawn.TryGetComp<CompPawnSexData>()?.UpdateBodyAddonVisibility();
PortraitsCache.SetDirty(pawn);
GlobalTextureAtlasManager.TryMarkPawnFrameSetDirty(pawn);
}
@ -138,6 +144,8 @@ namespace Rimworld_Animations_Patch
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.CheckboxLabeled("underwear_sufficent_for_ideos".Translate(), ref ApparelSettings.underwearSufficentForIdeos, "underwear_sufficent_for_ideos_desc".Translate());
listingStandard.CheckboxLabeled("exposed_underwear_mood".Translate(), ref ApparelSettings.exposedUnderwearMood, "exposed_underwear_mood_desc".Translate());
listingStandard.End();
base.DoSettingsWindowContents(inRect);

View File

@ -33,6 +33,8 @@ namespace Rimworld_Animations_Patch
public static float humpingMasturbationChance = 0.25f;
public static float otherMasturbationChance = 0.2f;
public static float sliderValue = 0f;
public override void ExposeData()
{
base.ExposeData();
@ -110,6 +112,9 @@ namespace Rimworld_Animations_Patch
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.Label("test slide: " + BasicSettings.sliderValue.ToString("F"), -1f);
//BasicSettings.sliderValue = listingStandard.Slider(BasicSettings.sliderValue, -2f, 2f);
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());

View File

@ -0,0 +1,37 @@
using System;
using RimWorld;
using Verse;
using rjw;
namespace Rimworld_Animations_Patch
{
public class ThoughtWorker_ExposedUnderwear : ThoughtWorker
{
public static ThoughtState CurrentThoughtState(Pawn pawn)
{
if (xxx.has_quirk(pawn, "Exhibitionist"))
{
return ThoughtState.ActiveAtStage(1);
}
return ThoughtState.ActiveAtStage(0);
}
protected override ThoughtState CurrentStateInternal(Pawn pawn)
{
if (ApparelSettings.exposedUnderwearMood == false) return false;
if (pawn?.apparel?.WornApparel == null || pawn.apparel.WornApparel.NullOrEmpty()) return false;
if (pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.GenitalsBPG)) &&
pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs)) == false)
{ return CurrentThoughtState(pawn); }
if (pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.ChestBPG)) &&
pawn.apparel.WornApparel.Any(x => x.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso)) == false)
{ return CurrentThoughtState(pawn); }
return ThoughtState.Inactive;
}
}
}

View File

@ -60,7 +60,7 @@ namespace Rimworld_Animations_Patch
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")))
if (anim.actors[actorId].isFucked || anim.actors[actorId].isFucking || (anim.actors[actorId].requiredGenitals.NullOrEmpty() == false && anim.actors[actorId].requiredGenitals.Any(x => x.ToLower().ContainsAny("penis", "vagina", "anus"))))
{ orgasmTick = Mathf.Clamp(ticks - 5, 0, int.MaxValue); }
// Actor does not orgasm
@ -72,18 +72,24 @@ namespace Rimworld_Animations_Patch
}
// 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;
}
// 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;
}
return regularPos;
}
public static BodyPartRecord GetBodyPartRecord(Pawn pawn, string bodyPart)
public static Rot4 PawnBodyRotInAnimation(Pawn pawn, Rot4 regularPos, PawnRenderFlags renderFlags)
{
if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawn?.TryGetComp<CompBodyAnimator>() != null && pawn.TryGetComp<CompBodyAnimator>().isAnimating)
{ return pawn.TryGetComp<CompBodyAnimator>().bodyFacing; }
return regularPos;
}
public static BodyPartRecord GetBodyPartRecord(Pawn pawn, string bodyPart)
{
if (bodyPart.NullOrEmpty())
{ return null; }

View File

@ -2,6 +2,7 @@
using System.Linq;
using Verse;
using RimWorld;
using Verse.AI.Group;
using Rimworld_Animations;
using UnityEngine;
using AlienRace;
@ -54,23 +55,6 @@ namespace Rimworld_Animations_Patch
if (comp == null || comp.rimNudeDataStatus == RimNudeDataStatus.Unavailable)
{ return false; }
if (comp.rimNudeDataStatus == RimNudeDataStatus.NotLoaded)
{
RimNudeData rimNudeData = ApparelSettings.GetRimNudeData(apparel);
if (rimNudeData == null)
{
comp.rimNudeDataStatus = RimNudeDataStatus.Unavailable;
return false;
}
comp.coversBelly = rimNudeData.coversBelly;
comp.coversChest = rimNudeData.coversChest;
comp.coversGroin = rimNudeData.coversGroin;
comp.rimNudeDataStatus = RimNudeDataStatus.Loaded;
}
if (comp.isBeingWorn == false)
{ return false; }
@ -88,9 +72,7 @@ namespace Rimworld_Animations_Patch
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)
if (pawn?.apparel?.WornApparel == null)
{ return; }
foreach (Apparel apparel in pawn.apparel.WornApparel)
@ -102,23 +84,20 @@ namespace Rimworld_Animations_Patch
}
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; }
// Get naked for rituals and parties
bool undressForRitual = pawn.GetLord() != null && pawn.GetLord().LordJob is LordJob_Ritual;
bool undressForParty = pawn.GetLord() != null && pawn.GetLord().LordJob is LordJob_Joinable_Party;
// Determine any obstructing apparel that must be removed
foreach (Apparel apparel in pawn.apparel.WornApparel)
{
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
if (comp == null)
{ continue; }
@ -128,7 +107,7 @@ namespace Rimworld_Animations_Patch
if (ApparelSettings.GetRimNudeData(apparel) != null && ApparelSettings.GetRimNudeData(apparel).sexWear)
{ continue; }
if (clothingPreference == RJWPreferenceSettings.Clothing.Nude)
if (clothingPreference == RJWPreferenceSettings.Clothing.Nude || undressForRitual || undressForParty)
{
comp.isBeingWorn = false;
continue;
@ -142,7 +121,7 @@ namespace Rimworld_Animations_Patch
continue;
}
if (ApparelCoversPawnRequiredBodyParts(pawn, apparel, anim, actorID))
if (animData != null && ApparelCoversPawnRequiredBodyParts(pawn, apparel, animData.animationDef, animData.actorID))
{
comp.isBeingWorn = false;
continue;

View File

@ -16,14 +16,10 @@ namespace Rimworld_Animations_Patch
public static bool BodyPartIsBeingTouched(Pawn pawn, string bodypartFilePath, out List<HandAnimationData> handAnimationData)
{
handAnimationData = new List<HandAnimationData>();
ActorAnimationData actorAnimationData = pawn.GetAnimationData();
HandAnimationDef handAnimationDef = pawn?.TryGetComp<CompPawnSexData>()?.handAnimationDef;
ActorAnimationData actorAnimationData = pawn?.GetAnimationData();
if (actorAnimationData == null)
{ return false; }
HandAnimationDef handAnimationDef = DefDatabase<HandAnimationDef>.AllDefs.FirstOrDefault(x => x.animationDefName == actorAnimationData.animationDef.defName);
if (handAnimationDef == null)
if (handAnimationDef == null || actorAnimationData == null || bodypartFilePath.NullOrEmpty())
{ return false; }
foreach (HandAnimationData datum in handAnimationDef.handAnimationData)
@ -187,31 +183,39 @@ namespace Rimworld_Animations_Patch
return handPosition;
}
public static Graphic GetHandGraphic(Pawn touchingPawn, string touchedBodyAddonName, HandAnimationData handAnimationData)
public static Graphic GetHandGraphic(Pawn touchingPawn)
{
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);
CompPawnSexData comp = touchingPawn?.TryGetComp<CompPawnSexData>();
if (comp == null) return null;
if (comp.handGraphic == null)
{
string handGraphicPath = "Hands/HandClean";
comp.handGraphic = GraphicDatabase.Get<Graphic_Single>(handGraphicPath, ShaderDatabase.Cutout,
new Vector2(0.6667f * touchingPawn.RaceProps.baseBodySize, 0.6667f * touchingPawn.RaceProps.baseBodySize), touchingPawn.story.SkinColor);
}
return comp.handGraphic;
}
public static bool TryToDrawHand(Pawn pawn, string bodyAddonName, Vector3 bodyAddonPosition, float bodyAddonAngle, Rot4 bodyAddonRotation, PawnRenderFlags renderFlags)
{
{
if (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);
Graphic handGraphic = GetHandGraphic(touchingPawn);
if (handGraphic == null) return false;
Vector3 handPosition = GetHandPosition(pawn, datum, bodyAddonPosition, bodyAddonAngle);
GenDraw.DrawMeshNowOrLater(mesh: handgraphic.MeshAt(rot: bodyAddonRotation),
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));
mat: handGraphic.MatAt(rot: bodyAddonRotation), renderFlags.FlagSet(PawnRenderFlags.DrawNow));
return true;
}
}

View File

@ -11,4 +11,10 @@ namespace Rimworld_Animations_Patch
public static BodyPartGroupDef AnusBPG;
public static BodyPartGroupDef ChestBPG;
}
[DefOf]
public static class PatchBodyPartDefOf
{
public static BodyPartDef Hand;
}
}

View File

@ -37,10 +37,13 @@ namespace Rimworld_Animations_Patch
if (pawn.GetAllSexParticipants().Any(x => pawn.GetSpouseCount(false) > 0 && pawn.GetSpouses(false).Contains(x)))
{ return false; }
if (pawn.GetSexPartner().Dead || pawn.GetSexPartner().IsAnimal())
{ return false; }
return partner.IsLoverOfOther(pawn) && pawn.HasTrait("Polygamist") == false && partner.HasTrait("Polygamist") == false;
}
public static bool PawnCanInvitePasserbyForSex(Pawn passerby, List<Pawn> participants)
public static bool InvitePasserbyForSex(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; }
@ -48,6 +51,12 @@ namespace Rimworld_Animations_Patch
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 (passerby.MentalState != null ||
passerby.jobs.curDriver is JobDriver_Flee ||
passerby.jobs.curDriver is JobDriver_AttackMelee ||
passerby.jobs.curDriver is JobDriver_Vomit)
{ 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) &&
@ -59,182 +68,198 @@ namespace Rimworld_Animations_Patch
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;
public static ThoughtDef GetThoughtsAboutSexAct(Pawn pawn, JobDriver_Sex jobDriver, out Precept precept)
{
ThoughtDef thoughtDef = null;
precept = null;
if (pawn == null || jobDriver == null)
{ return null; }
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);
bool sexIsSlaveRape = sexIsRape && (jobDriver.Partner.IsPrisoner || jobDriver.Partner.IsSlave);
bool sexIsXeno = jobDriver.Partner != null && jobDriver.Partner.def.defName != jobDriver.pawn.def.defName;
bool isXenophobe = pawn.HasTrait("Xenophobia") && pawn.story.traits.DegreeOfTrait(DefDatabase<TraitDef>.GetNamedSilentFail("Xenophobia")) > 0;
bool isXenophile = pawn.HasTrait("Xenophobia") && pawn.story.traits.DegreeOfTrait(DefDatabase<TraitDef>.GetNamedSilentFail("Xenophobia")) < 0;
if (BasicSettings.worryAboutNecro && sexIsNecro && xxx.is_necrophiliac(pawn) == false)
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Necrophilia"), TabooStatus.MajorTaboo, out precept); }
{
thoughtDef = xxx.is_necrophiliac(pawn) ? DefDatabase<ThoughtDef>.GetNamedSilentFail("SawNecrophilia_Honorable") :
pawn.HasPreceptForIssue("Necrophilia", out precept) ? DefDatabase<ThoughtDef>.GetNamedSilentFail("Saw" + precept.def.defName) :
DefDatabase<ThoughtDef>.GetNamedSilentFail("SawNecrophilia_Abhorrent");
}
else if (BasicSettings.worryAboutBeastiality && sexIsBeastial && xxx.is_zoophile(pawn) == false)
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Beastility"), TabooStatus.MajorTaboo, out precept); }
else if (BasicSettings.worryAboutBeastiality && sexIsBeastial)
{
thoughtDef = xxx.is_zoophile(pawn) ? DefDatabase<ThoughtDef>.GetNamedSilentFail("SawBeastility_Honorable") :
pawn.HasPreceptForIssue("Beastility", out precept) ? DefDatabase<ThoughtDef>.GetNamedSilentFail("Saw" + precept.def.defName) :
DefDatabase<ThoughtDef>.GetNamedSilentFail("SawBeastility_Abhorrent");
}
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)
{
thoughtDef = xxx.is_rapist(pawn) ? DefDatabase<ThoughtDef>.GetNamedSilentFail("SawRape_Honorable") :
pawn.HasPreceptForIssue("Rape", out precept) ? DefDatabase<ThoughtDef>.GetNamedSilentFail("Saw" + precept.def.defName) :
DefDatabase<ThoughtDef>.GetNamedSilentFail("SawRape_Abhorrent");
}
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.worryAboutRape && sexIsRape)
{
thoughtDef = xxx.is_rapist(pawn) ? DefDatabase<ThoughtDef>.GetNamedSilentFail("SawRape_Honorable") :
pawn.HasPreceptForIssue("Rape", out precept) ? DefDatabase<ThoughtDef>.GetNamedSilentFail("Saw" + precept.def.defName) :
DefDatabase<ThoughtDef>.GetNamedSilentFail("SawRape_Abhorrent");
}
else if (BasicSettings.worryAboutXeno && sexIsXeno)
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("HAR_AlienDating"), TabooStatus.NotTaboo, out precept); }
{
thoughtDef = isXenophobe ? DefDatabase<ThoughtDef>.GetNamedSilentFail("SawHAR_AlienDating_Prohibited") :
isXenophile ? DefDatabase<ThoughtDef>.GetNamedSilentFail("SawHAR_AlienDating_Honorable") :
pawn.HasPreceptForIssue("HAR_AlienDating", out precept) ? DefDatabase<ThoughtDef>.GetNamedSilentFail("Saw" + precept.def.defName) :
DefDatabase<ThoughtDef>.GetNamedSilentFail("SawHAR_AlienDating_Acceptable");
}
//DebugMode.Message("Sex job is: " + jobDriver + " Issue is: " + (precept?.def?.issue?.defName).ToStringSafe() + " Opinion is: " + (precept?.def?.defName).ToStringSafe() + " Judgement is: " + tabooStatus.ToString());
//DebugMode.Message("Sex job is: " + jobDriver + " Issue is: " + (precept?.def?.issue?.defName).ToStringSafe() + " Opinion is: " + (precept?.def?.defName).ToStringSafe() + " Thought is: " + (thoughtDef?.defName).ToStringSafe());
return tabooStatus;
return thoughtDef;
}
public static TabooStatus GetTabooStatusOfIssue(Pawn pawn, IssueDef issueDef, TabooStatus defaultTabboStatus, out Precept precept)
public static void TriggerReactionInWitness(Pawn witness, Pawn otherPawn, string reaction)
{
if (pawn.IssueIsMajorTaboo(issueDef, out precept))
{ return TabooStatus.MajorTaboo; }
if (BasicSettings.majorTabooCanStartFights == false || reaction.NullOrEmpty())
{ return; }
if (pawn.IssueIsMinorTaboo(issueDef, out precept))
{ return TabooStatus.MinorTaboo; }
if (witness.MentalState != null ||
witness.jobs.curDriver is JobDriver_Flee ||
witness.jobs.curDriver is JobDriver_AttackMelee ||
witness.jobs.curDriver is JobDriver_Vomit)
{ return; }
return defaultTabboStatus;
// Panicked
if (reaction == "Panicked" || (reaction == "Indignant" && Random.value <= 0.5f))
{
// Fight
if (otherPawn.RaceProps.Humanlike && witness.RaceProps.Humanlike && witness.DislikesViolence() == false && (Random.value <= 0.2f || witness.EnjoysViolence()) && witness.HostileTo(otherPawn) == false && InteractionUtility.TryGetRandomVerbForSocialFight(witness, out Verb verbToUse))
{ witness.interactions.StartSocialFight(otherPawn, "MessageSocialFight"); }
// Flight
else
{
Job job = JobMaker.MakeJob(JobDefOf.FleeAndCower, CellFinderLoose.GetFleeDest(witness, new List<Thing>() { otherPawn }, 24f), otherPawn);
witness.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
witness.jobs.StartJob(job);
}
}
// Vomit
else if (reaction == "Nauseated")
{
Job jobVomit = JobMaker.MakeJob(JobDefOf.Vomit);
Job jobFlee = JobMaker.MakeJob(JobDefOf.FleeAndCower, CellFinderLoose.GetFleeDest(witness, new List<Thing>() { otherPawn }, 24f), otherPawn);
witness.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
if (Random.value <= 0.2f)
{
witness.jobs.StartJob(jobVomit);
witness.jobs.jobQueue.EnqueueFirst(jobFlee);
}
else
{ witness.jobs.StartJob(jobFlee); }
}
// Indignant
else if (reaction == "Indignant")
{
witness.mindState.mentalStateHandler.TryStartMentalState(DefDatabase<MentalStateDef>.GetNamedSilentFail("TargetedInsultingSpree"), null, true, false, null, true, false, false);
(witness.mindState.mentalStateHandler.CurState as MentalState_TargetedInsultingSpree).target = otherPawn;
}
}
public static bool ResolveThoughtsForWhenSexIsWitnessed(Pawn pawn, Pawn witness, out bool witnessJoiningSex)
{
witnessJoiningSex = false;
witnessJoiningSex = Random.value < BasicSettings.chanceForOtherToJoinInSex && InvitePasserbyForSex(witness, pawn.GetAllSexParticipants());
if (pawn.IsAnimal() || pawn.RaceProps.IsMechanoid || pawn.Dead)
// Exit clauses
if (witness.AnimalOrWildMan() || witness.RaceProps.IsMechanoid || witness.Dead || witness.Faction == null || witness.Faction.HostileTo(Faction.OfPlayer))
{ return false; }
if (witness.IsAnimal() || witness.RaceProps.IsMechanoid || witness.Dead)
{ return false; }
JobDriver_Sex jobDriver = pawn.jobs.curDriver as JobDriver_Sex;
// Get basic thoughts
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"; }
ThoughtDef pawnThoughtDef = BasicSettings.needPrivacy ? DefDatabase<ThoughtDef>.GetNamedSilentFail(pawnThoughtDefName) : null;
ThoughtDef witnessThoughtDef = BasicSettings.needPrivacy ? DefDatabase<ThoughtDef>.GetNamedSilentFail(witnessThoughtDefName) : null;
bool witnessIsVoyeur = witness.HasTrait("Voyeur") || xxx.has_quirk(witness, "Voyeur");
if (witnessIsVoyeur)
{ witnessThoughtDefName += "Voyeur"; }
// Exhibitionist pawn
if (xxx.has_quirk(pawn, "Exhibitionist"))
{ pawnThoughtDef = DefDatabase<ThoughtDef>.GetNamedSilentFail(pawnThoughtDefName + "Exhibitionist"); }
// Voyeuristic witness
if (xxx.has_quirk(witness, "Voyeur"))
{ witnessThoughtDef = DefDatabase<ThoughtDef>.GetNamedSilentFail(witnessThoughtDefName + "Voyeur"); }
// Mediating cirumstances
bool sexIsRitual = pawn.GetLord() != null && pawn.GetLord().LordJob is LordJob_Ritual && witness?.Ideo == pawn?.Ideo;
bool sexIsParty = pawn.GetLord() != null && pawn.GetLord().LordJob is LordJob_Joinable_Party;
bool pawnIsVictim = pawn.CurJob.def == xxx.gettin_raped || pawn.Dead;
bool pawnIsCheating = pawnIsVictim == false && PawnIsCheatingOnPartner(pawn, witness);
bool pawnIsCheating = sexIsRitual == false && sexIsParty == false && pawnIsVictim == false && PawnIsCheatingOnPartner(pawn, witness);
witnessJoiningSex = Random.value < BasicSettings.chanceForOtherToJoinInSex && PawnCanInvitePasserbyForSex(witness, pawn.GetAllSexParticipants());
// Wipe thoughts if pawn is a victim
if (pawnIsVictim)
{
pawnThoughtDef = null;
witnessThoughtDef = null;
}
// Determine if there are any issues with the witness' morals
TabooStatus tabooStatus = CheckSexJobAgainstMorals(witness, jobDriver, out Precept precept);
// Add thought if pawn is cheating
if (pawnIsCheating)
{
if (pawn.needs.mood.thoughts.memories.GetFirstMemoryOfDef(DefDatabase<ThoughtDef>.GetNamedSilentFail("CaughtCheating")) == null)
{ pawn.needs.mood.thoughts.memories.TryGainMemory(DefDatabase<ThoughtDef>.GetNamedSilentFail("CaughtCheating"), witness); }
if (tabooStatus == TabooStatus.MajorTaboo)
{ witnessThoughtDefName = "SawMajorTaboo"; witnessJoiningSex = false; }
else if (tabooStatus == TabooStatus.MinorTaboo)
{ witnessThoughtDefName = "SawTaboo"; witnessJoiningSex = false; }
if (witness.needs.mood.thoughts.memories.GetFirstMemoryOfDef(ThoughtDefOf.CheatedOnMe) == null)
{ witness.needs.mood.thoughts.memories.TryGainMemory(ThoughtDefOf.CheatedOnMe, pawn); }
else if (pawnIsCheating)
{ witnessThoughtDefName = "CheatedOnMe"; witnessJoiningSex = false; }
witnessJoiningSex = false;
}
else if (BasicSettings.needPrivacy == false)
{ witnessThoughtDefName = ""; }
// Determine if there are any issues with the sex event and the witness' morals
Precept precept = null;
if (sexIsRitual == false && sexIsParty == false && pawnIsVictim == false)
{ witnessThoughtDef = GetThoughtsAboutSexAct(witness, pawn.jobs.curDriver as JobDriver_Sex, out precept); }
// Apply thoughts to witness
ThoughtDef witnessThoughtDef = DefDatabase<ThoughtDef>.GetNamedSilentFail(witnessThoughtDefName);
if (witnessThoughtDef != null && pawnIsVictim == false && witnessJoiningSex == false && sexIsRitual == false)
if (witnessThoughtDef != null)
{
witness.needs.mood.thoughts.memories.TryGainMemory(witnessThoughtDef, pawn, precept);
if (witnessThoughtDef.stages[0].baseMoodEffect < 0)
{ FleckMaker.ThrowMetaIcon(witness.Position, witness.Map, FleckDefOf.IncapIcon); }
{ witness?.TryGetComp<CompPawnSexData>()?.TryToExclaim(); }
// 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);
}
}
witnessJoiningSex = witnessThoughtDef.hediff != null ? false : witnessJoiningSex;
}
// 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; }
// Extreme reaction
if (witnessThoughtDef?.hediff != null)
{ TriggerReactionInWitness(witness, pawn, witnessThoughtDef.hediff.defName); }
else if (pawnIsCheating)
{ pawnThoughtDefName = "CaughtCheating"; witnessJoiningSex = false; }
else if (BasicSettings.needPrivacy == false)
{ pawnThoughtDefName = ""; }
{ TriggerReactionInWitness(witness, pawn, "Indignant"); }
// Apply thoughts to pawn
ThoughtDef pawnThoughtDef = DefDatabase<ThoughtDef>.GetNamedSilentFail(pawnThoughtDefName);
if (pawnThoughtDef != null && pawnIsVictim == false && witnessJoiningSex == false && sexIsRitual == false)
if (pawnThoughtDef != null)
{
pawn.needs.mood.thoughts.memories.TryGainMemory(pawnThoughtDef, witness, precept);
pawn.needs.mood.thoughts.memories.TryGainMemory(pawnThoughtDef, witness, null);
if (pawnThoughtDef.stages[0].baseMoodEffect < 0)
{ FleckMaker.ThrowMetaIcon(pawn.Position, pawn.Map, FleckDefOf.IncapIcon); }
{ pawn?.TryGetComp<CompPawnSexData>()?.TryToExclaim(); }
}
return witnessJoiningSex;
}
}

View File

@ -1 +1 @@
d5a3e9402b3033575abd239e1b750dc876117ae0
3f4f0c89e9095de19b2f487c386783c64b9bb50a

View File

@ -3,4 +3,5 @@ C:\Program Files (x86)\Steam\SteamApps\common\RimWorld\Mods\rimworld-animations-
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