First commit

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

57
About/About.xml Normal file
View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<ModMetaData>
<name>Rimworld-Animation Patch</name>
<author>AbstractConcept</author>
<supportedVersions>
<li>1.3</li>
</supportedVersions>
<packageId>abscon.rimworld.animations.patch</packageId>
<modDependencies>
<li>
<packageId>brrainz.harmony</packageId>
<displayName>Harmony</displayName>
<steamWorkshopUrl>steam://url/CommunityFilePage/2009463077</steamWorkshopUrl>
<downloadUrl>https://github.com/pardeike/HarmonyRimWorld/releases/latest</downloadUrl>
</li>
<li>
<packageId>rim.job.world</packageId>
<displayName>RimJobWorld</displayName>
<downloadUrl>https://www.loverslab.com/topic/110270-mod-rimjobworld/</downloadUrl>
</li>
<li>
<packageId>c0ffee.rimworld.animations</packageId>
<displayName>Rimworld-Animations</displayName>
<downloadUrl>https://gitgud.io/c0ffeeeeeeee/rimworld-animations</downloadUrl>
</li>
</modDependencies>
<loadBefore>
<li>Dubwise.DubsApparelTweaks</li>
</loadBefore>
<loadAfter>
<li>UnlimitedHugs.HugsLib</li>
<li>brrainz.harmony</li>
<li>rim.job.world</li>
<li>c0ffee.rimworld.animations</li>
<li>shauaputa.rimnudeworld</li>
<li>OTYOTY.NudePatchForRimNudeWorld.UnofficialUpdate</li>
<li>babies.and.children.continued.13</li>
</loadAfter>
<description>
This started as a patch for RimWorld-Animation, but has since grown to patch RJW and RimNudeWorld as well
There's no real theme here. It just fixes a few things, changes some others, as well as making some additions!
This mod requires RJW and RimWorld-Animation to function. RimNudeWorld isn't required, but is recommended. Let me know if you encounter any bugs
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
- Animated hands for certain animations
- A need for privacy during sex
A full list of changes can be found in the main discussion thread on the forum
</description>
</ModMetaData>

103
About/Changelog_v1.2.0.txt Normal file
View File

@ -0,0 +1,103 @@
Change log v 1.2.0
- Fix: Fixed bug where married pawns could be accused of cheating when having sex with another spouse
- Fix: Fixed bug where some animations would not result in an orgasm occuring when they should be occuring
- Fix: Fixed bug where animations would end too quickly when quick sex was enabled, leaving pawns to finish with the default animation provided by RJW
- Fix: Fixed bug where enlarged bellies where being rendered behind pawns
- Fix: Fixed a RJW bug which resulted in all clothing being worn when the prefered clothing level was set to headgear only
- Fix: Fixed issue which resulted in strangely moving body parts when using Yayo's Animations
- Fix: Fixed a layering issue for anuses when using OTY Nude
- Change: This mod must be loaded after the following mods (if you plan on using them) - RimNudeWorld, OTY Nude, Babies and Children
- Change: This mod now requires that you use RimWorld Animations v 1.2.8 or later
- Change: Sex animations have been increased across the board and now last about 35-40 seconds - quick animations will take about 20-25 seconds to finish
- Removal: Fix for eye implants and head wound placement has been removed (a fix for this bug is included in RimWorld Animations as of v 1.2.8)
- Addition: Added option to crop the bottoms of torso covering apparel (e.g., shirts) when not wearing pants - this feature is intended to be used alongside other mods that draw leg covering apparel on pawns (e.g., the Visible Pants mod). If you plan on using this feature and the Sized Apparel mod, you must turn off texture caching or graphical glitches will occur (this can be done by adding Vanilla Expanded Framework to your modlist and enabling its 'Disable Texture Caching' setting)
- Addition: Added a configuration menu which allows the user to customise apparel to either hide / reveal the groin, belly and / or breasts of pawns, plus an option to mark apparel as 'sex-wear' (these items of clothing will always be worn during sex animations)
- Addition: Added option to configure the amount of clothing that should be worn when having a quickie out of bed (pawns can be nude, wear headgear only, or be fully clothed)
- Addition: Individual items of clothing will be hidden if they cover a body part that is needed for a sex animation (e.g., pants will be removed for vaginal or anal sex, but not for oral) - this will not affect apparel marked as sex-wear
Change log v 1.1.1
- Fix: Fixed issue with the violation of corpses
- Fix: Fixed issue with xenophobes not reacting to xenophilic sex
- Fix: Fixed issue with not being able to toggle off reactions to cheating
- Change: Changed how the 'require privacy' toggle in the settings works - disabling this toggle now only stops pawns from feeling shame when being caught having / seeing non-taboo sex
- Removal: Removed unnecessary xpath patch which added a null pregnancy belly graphic to RimNudeWorld
- Addition: Added toggle to the settings which allows pawns to ignore indecent activities against prisoners and slaves
Change log v 1.1.0
- Addition: The reactions of pawns to encountering sex is now more nuanced. A pawn's reaction will now depend on 1) the sex act witnessed and 2) their traits and ideology (the Sexperience Ideology mod is recommended for this but is not required). Pawns can now have specific opinions on
- Rape
- Beastiality
- Necrophilia
- Xenophilia
Specific reactions can be individually toggled on/off. Work on this feature is on going - at the moment, these acts will either treated as a vanilla sex act, as taboo, or as a major taboo. More tailored thoughts are planned to be added
- Addition: Pawns can have extreme reactions to encountered sex acts. If a major taboo is witnessed the pawn will flee in horror, or may even start a fight the perpetrator! This feature can be toggled on/off
Change log v 1.0.5
- Fix: Missing text for the voyeur quirk has been fixed
- Change: Dubs Apparel Tweaks must now be loaded after this mod
- Removal: Test theesome animation removed
Change log v 1.0.4
- Fix: Fixed an issue when masturbating with a dildo
- Fix: Fixed an issue with masturbation animation selection
- Addition: Translation tags have been set up for the settings page - still only available in English however
Change log v 1.0.3
- Fix: fixed incompatibility issue with Vanilla Factions Expanded Ancients
- Fix: non-consensual / paid for sex acts won't be interrupted by people passing by anymore
Change log v 1.0.2
- Fix: fixed an issue with the animation anchors for sex activities outside of bed - pawns should not longer be placed oddly far apart or close together
- Fix: fixed an incompatibility issue with Dub's Apparel Tweaks
- Fix: fixed several body part layering issues for a number of alien races
- Fix: fixed an issue with the sex job driver failing for mechanoids
- Fix: sex animations should now only be played once for non-consensual / paid for sex acts
- Fix: a pawn's age is now considered when sending out an invitation to join in sex
- Fix: pawns won't worry about privacy or infidelity while taking part in a ritual
- Fix: fixed an issue where pawns invited to join a threesome were causing it to stop
Change log v 1.0.1
- Fix: Fixed body parts on animals not being rendered while this mod is active
- Fix: Fixed bug that lead to repeated mating with animals
- Addition: Added a toggle to the settings which will allow the player to choose whether pawns worry about infidelity
Change log v 1.0.0
- Fix: eye implants and head wounds should now line up correctly with a pawn's head during sex animations
- Fix: penis movement has been added back to sex animations (must be enabled under RimWorld-Animations settings)
- Fix: a number of body part layering issues have been fixed for RimNudeWorld
- Fix: the timing of orgasms should now sync up with how they are depicted in sex animations
- Fix: fixed some issues with sex overdrive, the sex override meter, and how sex animations play out
- Fix: fixed issue with animations continuing if a pawn transitions straight from one sex act to another
- Fix: when masturbating in their own bed, pawns will position themselves in their sleeping spot rather than the middle of the bed
- Fix: RimNudeWorld's belly graphics will not appear until a third of the way through a pregnancy and will now be hidden under apparel that covers the torso
- Change: if a pawn doesn't appear to be actively using their own genitals during a sex animation (e.g., giving someone else a handjob) then they will not reach orgasm
- Change: genitals now count as being covered / protected by leg-covering apparel instead than torso-covering apparel
- Change: the scantier underwear from Underwhere and S16's extension mods now only cover / protect the genitals and chest instead of the legs and torso
- Change: added some variety to the log entries / job descriptions for different sex acts
- Change: if sex overdrive is turned on, the next sex act to take place will be chosen at random
- Change: pawns now have a chance to start a second sex animation after the first one ends, depending on how horny the participants still are
- Change: tweaked the lengths of the original animations by c0ffee - animations will now generally finish within 25-30 seconds (~ 40 mins of in game time)
- Addition: included animations for the following sex acts (updated versions of the ones I originally posted on the forum)
- butterfly
- breast job
- hand job
- fisting
- rimming
- sixty-nine
- fingering
- cunnilingus while standing
- reverse cowgirl
- face sitting
- foot job
- mutual masturbation
- Addition: included some new animations!
- kissing / making out (based on an animation originally made by Tory)
- solo masturbation while standing up and lying in a bed (includes separate animations for playing with genitals, breasts, anus and bed humping)
- a threesome with one male and two females ('the cowgirls')
- Addition: certain animations have had animated hands added to them - these include hand jobs, fisting, fingering, solo masturbation and mutual masturbation (can be toggled on or off). This feature requires RimNudeWorld to be active
- Addition: pawn / sleep slot name tags can be hidden during animations so they don't cover up the action (can be toggled on or off)
- Addition: the distance between pawns during sex animations can be set to be scaled depending on the body sizes of the pawns involved (can be toggled on or off)
- Addition: clothing that is not worn during an animation will now appear in a nearby pile on the floor (can be toggled on or off)
- Addition: pawns now have a desire for privacy while having sex / masturbating, and they'll be unhappy and stop what they are doing if they get caught in the act (can be toggled on or off). Exhibitionists won't mind though
- Addition: threesomes can triggered via two new ways - 1) when a pawn sets off to bed their partner a passerby might be invited to follow, and 2) if a pawn stumbles upon a couple having sex, they may just be invited to join in (the chance of a threesome occuring can be configured). In both cases, those involved must have a good opinion of each other and the invited pawn mustn't be doing an important job at the time
- Addition: a voyeur quirk has been added - these pawns get a rush out of catching people in the act

6
About/Manifest.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Manifest>
<version>1.2.0</version>
<manifestUri></manifestUri>
<downloadUri></downloadUri>
</Manifest>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<Rimworld_Animations_Patch.HandAnimationDef>
<defName>Handjob_HandDef</defName>
<animationDefName>Handjob</animationDefName>
<handAnimationData>
<li>
<stageID>0</stageID>
<actorID>1</actorID>
<touchingActorID>0</touchingActorID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingNS</motion>
<cycleTime>30</cycleTime>
</li>
<li>
<stageID>1</stageID>
<actorID>1</actorID>
<touchingActorID>0</touchingActorID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingNS</motion>
<cycleTime>15</cycleTime>
</li>
<li>
<stageID>2</stageID>
<actorID>1</actorID>
<touchingActorID>0</touchingActorID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingNS</motion>
<cycleTime>15</cycleTime>
</li>
</handAnimationData>
</Rimworld_Animations_Patch.HandAnimationDef>
<Rimworld_Animations_Patch.HandAnimationDef>
<defName>Fisting_HandDef</defName>
<animationDefName>Fisting</animationDefName>
<handAnimationData>
<li>
<stageID>0</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDownShort_FacingNS</motion>
<cycleTime>60</cycleTime>
</li>
<li>
<stageID>1</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDownShort_FacingNS</motion>
<cycleTime>15</cycleTime>
</li>
<li>
<stageID>2</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDownShort_FacingNS</motion>
<cycleTime>15</cycleTime>
</li>
<li>
<stageID>0</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDownShort_FacingNS</motion>
<cycleTime>60</cycleTime>
</li>
<li>
<stageID>1</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDownShort_FacingNS</motion>
<cycleTime>15</cycleTime>
</li>
<li>
<stageID>2</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDownShort_FacingNS</motion>
<cycleTime>15</cycleTime>
</li>
</handAnimationData>
</Rimworld_Animations_Patch.HandAnimationDef>
<Rimworld_Animations_Patch.HandAnimationDef>
<defName>Fingering_HandDef</defName>
<animationDefName>Fingering</animationDefName>
<handAnimationData>
<li>
<stageID>0</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDownShort_FacingNS</motion>
<cycleTime>60</cycleTime>
</li>
<li>
<stageID>1</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDownShort_FacingNS</motion>
<cycleTime>15</cycleTime>
</li>
<li>
<stageID>2</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDownShort_FacingNS</motion>
<cycleTime>15</cycleTime>
</li>
</handAnimationData>
</Rimworld_Animations_Patch.HandAnimationDef>
</Defs>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,510 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<Rimworld_Animations_Patch.HandAnimationDef>
<defName>MasturbationWithPenis_Standing_HandDef</defName>
<animationDefName>MasturbationWithPenis_Standing</animationDefName>
<handAnimationData>
<li>
<stageID>0</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingEW</motion>
<cycleTime>31</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingEW</motion>
<cycleTime>16</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingEW</motion>
<cycleTime>16</cycleTime>
</li>
</handAnimationData>
</Rimworld_Animations_Patch.HandAnimationDef>
<Rimworld_Animations_Patch.HandAnimationDef>
<defName>MasturbationWithPenis_InBed_HandDef</defName>
<animationDefName>MasturbationWithPenis_InBed</animationDefName>
<handAnimationData>
<li>
<stageID>0</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingNS</motion>
<cycleTime>31</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingNS</motion>
<cycleTime>16</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingNS</motion>
<cycleTime>16</cycleTime>
</li>
</handAnimationData>
</Rimworld_Animations_Patch.HandAnimationDef>
<Rimworld_Animations_Patch.HandAnimationDef>
<defName>MasturbationWithVagina_Standing_HandDef</defName>
<animationDefName>MasturbationWithVagina_Standing</animationDefName>
<handAnimationData>
<li>
<stageID>0</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingEW</motion>
<cycleTime>31</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingEW</motion>
<cycleTime>16</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingEW</motion>
<cycleTime>16</cycleTime>
</li>
</handAnimationData>
</Rimworld_Animations_Patch.HandAnimationDef>
<Rimworld_Animations_Patch.HandAnimationDef>
<defName>MasturbationWithVagina_InBed_HandDef</defName>
<animationDefName>MasturbationWithVagina_InBed</animationDefName>
<handAnimationData>
<li>
<stageID>0</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>62</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>32</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>32</cycleTime>
</li>
</handAnimationData>
</Rimworld_Animations_Patch.HandAnimationDef>
<Rimworld_Animations_Patch.HandAnimationDef>
<defName>MasturbationWithAnus_Standing_HandDef</defName>
<animationDefName>MasturbationWithAnus_Standing</animationDefName>
<handAnimationData>
<li>
<stageID>0</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>31</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>16</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>16</cycleTime>
</li>
<li>
<stageID>0</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>31</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>16</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>16</cycleTime>
</li>
</handAnimationData>
</Rimworld_Animations_Patch.HandAnimationDef>
<Rimworld_Animations_Patch.HandAnimationDef>
<defName>MasturbationWithAnus_InBed_HandDef</defName>
<animationDefName>MasturbationWithAnus_InBed</animationDefName>
<handAnimationData>
<li>
<stageID>0</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>31</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>16</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>16</cycleTime>
</li>
<li>
<stageID>0</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>31</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>16</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>16</cycleTime>
</li>
</handAnimationData>
</Rimworld_Animations_Patch.HandAnimationDef>
<Rimworld_Animations_Patch.HandAnimationDef>
<defName>MasturbationWithBreasts_Standing_HandDef</defName>
<animationDefName>MasturbationWithBreasts_Standing</animationDefName>
<handAnimationData>
<li>
<stageID>0</stageID>
<targetBodyPart>Breasts</targetBodyPart>
<bodySide>left</bodySide>
<motion>Motion_RubBreasts_FacingEW</motion>
<cycleTime>93</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Breasts</targetBodyPart>
<bodySide>left</bodySide>
<motion>Motion_RubBreasts_FacingEW</motion>
<cycleTime>64</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingEW</motion>
<cycleTime>32</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Breasts</targetBodyPart>
<bodySide>left</bodySide>
<motion>Motion_RubBreasts_FacingEW</motion>
<cycleTime>64</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingEW</motion>
<cycleTime>32</cycleTime>
</li>
</handAnimationData>
</Rimworld_Animations_Patch.HandAnimationDef>
<Rimworld_Animations_Patch.HandAnimationDef>
<defName>MasturbationWithBreasts_InBed_HandDef</defName>
<animationDefName>MasturbationWithBreasts_InBed</animationDefName>
<handAnimationData>
<li>
<stageID>0</stageID>
<targetBodyPart>Breasts</targetBodyPart>
<bodySide>left</bodySide>
<motion>Motion_RubBreasts_FacingNS</motion>
<cycleTime>93</cycleTime>
</li>
<li>
<stageID>0</stageID>
<targetBodyPart>Breasts</targetBodyPart>
<bodySide>right</bodySide>
<mirror>true</mirror>
<motion>Motion_RubBreasts_FacingNS</motion>
<cycleTime>93</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Breasts</targetBodyPart>
<bodySide>left</bodySide>
<motion>Motion_RubBreasts_FacingNS</motion>
<cycleTime>64</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>32</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Breasts</targetBodyPart>
<bodySide>left</bodySide>
<motion>Motion_RubBreasts_FacingNS</motion>
<cycleTime>64</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingNS</motion>
<cycleTime>32</cycleTime>
</li>
</handAnimationData>
</Rimworld_Animations_Patch.HandAnimationDef>
<Rimworld_Animations_Patch.HandAnimationDef>
<defName>MasturbationWithBreastsPlusPenis_Standing_HandDef</defName>
<animationDefName>MasturbationWithBreastsPlusPenis_Standing</animationDefName>
<handAnimationData>
<li>
<stageID>0</stageID>
<targetBodyPart>Breasts</targetBodyPart>
<bodySide>left</bodySide>
<motion>Motion_RubBreasts_FacingEW</motion>
<cycleTime>93</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Breasts</targetBodyPart>
<bodySide>left</bodySide>
<motion>Motion_RubBreasts_FacingEW</motion>
<cycleTime>64</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingEW</motion>
<cycleTime>16</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Breasts</targetBodyPart>
<bodySide>left</bodySide>
<motion>Motion_RubBreasts_FacingEW</motion>
<cycleTime>64</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingEW</motion>
<cycleTime>16</cycleTime>
</li>
</handAnimationData>
</Rimworld_Animations_Patch.HandAnimationDef>
<Rimworld_Animations_Patch.HandAnimationDef>
<defName>MasturbationWithBreastsPlusPenis_InBed_HandDef</defName>
<animationDefName>MasturbationWithBreastsPlusPenis_InBed</animationDefName>
<handAnimationData>
<li>
<stageID>0</stageID>
<targetBodyPart>Breasts</targetBodyPart>
<bodySide>left</bodySide>
<motion>Motion_RubBreasts_FacingNS</motion>
<cycleTime>93</cycleTime>
</li>
<li>
<stageID>0</stageID>
<targetBodyPart>Breasts</targetBodyPart>
<bodySide>right</bodySide>
<mirror>true</mirror>
<motion>Motion_RubBreasts_FacingNS</motion>
<cycleTime>93</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Breasts</targetBodyPart>
<bodySide>left</bodySide>
<motion>Motion_RubBreasts_FacingNS</motion>
<cycleTime>64</cycleTime>
</li>
<li>
<stageID>1</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingNS</motion>
<cycleTime>16</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Breasts</targetBodyPart>
<bodySide>left</bodySide>
<motion>Motion_RubBreasts_FacingNS</motion>
<cycleTime>64</cycleTime>
</li>
<li>
<stageID>2</stageID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingNS</motion>
<cycleTime>16</cycleTime>
</li>
</handAnimationData>
</Rimworld_Animations_Patch.HandAnimationDef>
<Rimworld_Animations_Patch.HandAnimationDef>
<defName>MutualMasturbation_HandDef</defName>
<animationDefName>MutualMasturbation</animationDefName>
<handAnimationData>
<!-- Actor 0 (which has penis) -->
<li>
<stageID>2</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingEW</motion>
<cycleTime>25</cycleTime>
</li>
<li>
<stageID>3</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingEW</motion>
<cycleTime>30</cycleTime>
</li>
<li>
<stageID>4</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingEW</motion>
<cycleTime>15</cycleTime>
</li>
<li>
<stageID>5</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingEW</motion>
<cycleTime>15</cycleTime>
</li>
<!-- Actor 0 (which has vagina) -->
<li>
<stageID>2</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingEW</motion>
<cycleTime>25</cycleTime>
</li>
<li>
<stageID>3</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingEW</motion>
<cycleTime>30</cycleTime>
</li>
<li>
<stageID>4</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingEW</motion>
<cycleTime>15</cycleTime>
</li>
<li>
<stageID>5</stageID>
<actorID>0</actorID>
<touchingActorID>1</touchingActorID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingEW</motion>
<cycleTime>15</cycleTime>
</li>
<!-- Actor 1 (which has penis) -->
<li>
<stageID>2</stageID>
<actorID>1</actorID>
<touchingActorID>0</touchingActorID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingEW</motion>
<cycleTime>25</cycleTime>
</li>
<li>
<stageID>3</stageID>
<actorID>1</actorID>
<touchingActorID>0</touchingActorID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingEW</motion>
<cycleTime>30</cycleTime>
</li>
<li>
<stageID>4</stageID>
<actorID>1</actorID>
<touchingActorID>0</touchingActorID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingEW</motion>
<cycleTime>15</cycleTime>
</li>
<li>
<stageID>5</stageID>
<actorID>1</actorID>
<touchingActorID>0</touchingActorID>
<targetBodyPart>Penis</targetBodyPart>
<motion>Motion_StrokeGenitalsUpAndDown_FacingEW</motion>
<cycleTime>15</cycleTime>
</li>
<!-- Actor 1 (which has vagina) -->
<li>
<stageID>2</stageID>
<actorID>1</actorID>
<touchingActorID>0</touchingActorID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingEW</motion>
<cycleTime>25</cycleTime>
</li>
<li>
<stageID>3</stageID>
<actorID>1</actorID>
<touchingActorID>0</touchingActorID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingEW</motion>
<cycleTime>30</cycleTime>
</li>
<li>
<stageID>4</stageID>
<actorID>1</actorID>
<touchingActorID>0</touchingActorID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingEW</motion>
<cycleTime>15</cycleTime>
</li>
<li>
<stageID>5</stageID>
<actorID>1</actorID>
<touchingActorID>0</touchingActorID>
<targetBodyPart>Vagina</targetBodyPart>
<motion>Motion_RubGenitals_FacingEW</motion>
<cycleTime>15</cycleTime>
</li>
</handAnimationData>
</Rimworld_Animations_Patch.HandAnimationDef>
</Defs>

34
Defs/InteractionDefs.xml Normal file
View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<InteractionDef ParentName="RJW_InteractionDefBaseSex">
<defName>Masturbation_Humping</defName>
<label>masturbation_humping</label>
<logRulesInitiator>
<rulesStrings>
<li>r_logentry->Masturbated.</li>
</rulesStrings>
</logRulesInitiator>
<logRulesRecipient>
<rulesStrings>
<li>r_logentry->Masturbated.</li>
</rulesStrings>
</logRulesRecipient>
<modExtensions>
<li Class="rjw.InteractionExtension">
<RMBLabel>Hump bed</RMBLabel>
<rjwSextype>Masturbation</rjwSextype>
<rulepack_defs>
<li>Masturbation_HumpingRP</li>
</rulepack_defs>
</li>
<li Class="rjw.Modules.Interactions.DefModExtensions.InteractionSelectorExtension">
<tags>
<li>Masturbation</li>
</tags>
<dominantRequirement/>
<submissiveRequirement/>
</li>
</modExtensions>
</InteractionDef>
</Defs>

12
Defs/JobDefs.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<JobDef>
<defName>JoinInSex</defName>
<driverClass>Rimworld_Animations_Patch.JobDriver_JoinInSex</driverClass>
<reportString>joining in on some fun.</reportString>
<casualInterruptible>false</casualInterruptible>
</JobDef>
</Defs>

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<RulePackDef>
<defName>Masturbation_HandjobPRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] stroked [INITIATOR_possessive] penis.</li>
<li>sent-> [INITIATOR_nameDef] jerked [INITIATOR_objective]self off.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>Masturbation_HandjobVRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] teased [INITIATOR_possessive] vagina.</li>
<li>sent-> [INITIATOR_nameDef] rubbed one out.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>Masturbation_HandjobARP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] rubbed [INITIATOR_possessive] anus.</li>
<li>sent-> [INITIATOR_nameDef] gingerly probed [INITIATOR_possessive] anus.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>Masturbation_HandjobBRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] played with [INITIATOR_possessive] breasts.</li>
<li>sent-> [INITIATOR_nameDef] tweaked [INITIATOR_possessive] nipples.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>Masturbation_AutoFellatioRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] autofellated [INITIATOR_possessive] own penis.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>Masturbation_AutoBreastjobRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] stroked [INITIATOR_objective] penis with [INITIATOR_possessive] own breasts.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>Masturbation_HumpingRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] rubbed [INITIATOR_objective]self against the bed.</li>
<li>sent-> [INITIATOR_nameDef] humped [INITIATOR_possessive] bed.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
</Defs>

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<RulePackDef>
<defName>RimmingRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] rimmed [INITIATOR_nameDef].</li>
<li>sent-> [RECIPIENT_nameDef] surprised [INITIATOR_nameDef] with some vigorous tongue-play.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>CunnilingusRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] went down on [INITIATOR_nameDef].</li>
<li>sent-> [INITIATOR_nameDef] moaned as [RECIPIENT_nameDef] tasted [INITIATOR_objective].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>FellatioRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] sucked off [INITIATOR_nameDef].</li>
<li>sent-> [INITIATOR_nameDef] groaned as [RECIPIENT_nameDef] tasted [INITIATOR_possessive] member.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>BeakjobRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] fellated [INITIATOR_nameDef] with [RECIPIENT_possessive] beak.</li>
<li>sent-> [RECIPIENT_nameDef] gingerly nibbled [INITIATOR_nameDef]'s member with [RECIPIENT_possessive] beak.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>SixtynineRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] and [INITIATOR_nameDef] went down on each other.</li>
<li>sent-> [RECIPIENT_nameDef] and [INITIATOR_nameDef] teased each other with their tongues.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>MutualMakeoutRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] made out with [INITIATOR_nameDef].</li>
<li>sent-> [RECIPIENT_nameDef] and [INITIATOR_nameDef] shared in a passionate kiss.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>DeepthroatRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] deepthroated [INITIATOR_nameDef].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
</Defs>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<RulePackDef>
<defName>RimmingReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] rimmed [RECIPIENT_nameDef].</li>
<li>sent-> [INITIATOR_nameDef] surprised [RECIPIENT_nameDef] with some vigorous tongue-play.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>CunnilingusReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] went down on [RECIPIENT_nameDef].</li>
<li>sent-> [RECIPIENT_nameDef] moaned as [INITIATOR_nameDef] tasted [RECIPIENT_objective].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>FellatioReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] sucked off [RECIPIENT_nameDef].</li>
<li>sent-> [RECIPIENT_nameDef] groaned as [INITIATOR_nameDef] tasted [RECIPIENT_possessive] member.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>BeakjobReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] fellated [RECIPIENT_nameDef] with [INITIATOR_possessive] beak.</li>
<li>sent-> [INITIATOR_nameDef] gingerly nibbled [RECIPIENT_nameDef]'s member with [INITIATOR_possessive] beak.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>SixtynineReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] 69'd with [RECIPIENT_nameDef].</li>
<li>sent-> [INITIATOR_nameDef] and [RECIPIENT_nameDef] teased each other with their tongues.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>DeepthroatReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] deepthroated [RECIPIENT_nameDef].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
</Defs>

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<RulePackDef>
<defName>MainReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] copulated with [INITIATOR_nameDef].</li>
<li>sent-> [RECIPIENT_nameDef] made love to [INITIATOR_nameDef].</li>
<li>sent-> [INITIATOR_nameDef] panted as [RECIPIENT_nameDef] thrust into [INITIATORobjective].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>DefaultSexReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] had sex with [INITIATOR_nameDef].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>AnalSexReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] took [INITIATOR_nameDef] from behind.</li>
<li>sent-> [INITIATOR_nameDef] gasped as [RECIPIENT_nameDef] entered [INITIATOR_objective] from behind.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>VaginalSexReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] copulated with [INITIATOR_nameDef].</li>
<li>sent-> [RECIPIENT_nameDef] made love to [INITIATOR_nameDef].</li>
<li>sent-> [INITIATOR_nameDef] panted as [RECIPIENT_nameDef] thrust into [INITIATOR_objective].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>HandjobReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] received a handjob from [INITIATOR_nameDef].</li>
<li>sent-> [INITIATOR_nameDef] deftly worked [RECIPIENT_nameDef]'s shaft with [INITIATOR_possessive] hand.</li>
<li>sent-> [RECIPIENT_nameDef] was jerked off by [INITIATOR_nameDef].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>FootjobReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] stroked [RECIPIENT_nameDef] with [INITIATOR_possessive] feet.</li>
<li>sent-> [INITIATOR_nameDef] massaged [RECIPIENT_nameDef] with [INITIATOR_possessive] feet.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>FingeringReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] teased [RECIPIENT_nameDef] with [INITIATOR_possessive] fingers.</li>
<li>sent-> [RECIPIENT_nameDef] gasped as [INITIATOR_nameDef] fondled [RECIPIENT_objective].</li>
<li>sent-> [RECIPIENT_nameDef] groaned as [INITIATOR_nameDef] inserted [INITIATOR_possessive] fingers inside [RECIPIENT_objective].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>FistingReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] gasped as [INITIATOR_nameDef] worked [INITIATOR_possessive]'s hand inside [RECIPIENT_objective].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>DoublePenetrationReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] shuddered at the feeling of having both [INITIATOR_possessive] holes being filled.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>BreastjobReverseRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] used [INITIATOR_possessive] breasts to pleasure [RECIPIENT_nameDef].</li>
<li>sent-> [INITIATOR_nameDef] rubbed [INITIATOR_possessive] breasts along [RECIPIENT_nameDef]'s shaft.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
</Defs>

View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<RulePackDef>
<defName>AnalSexRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] took [RECIPIENT_nameDef] from behind.</li>
<li>sent-> [RECIPIENT_nameDef] gasped as [INITIATOR_nameDef] entered [RECIPIENT_objective] from behind.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>VaginalSexRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] copulated with [RECIPIENT_nameDef].</li>
<li>sent-> [INITIATOR_nameDef] made love to [RECIPIENT_nameDef].</li>
<li>sent-> [RECIPIENT_nameDef] panted as [INITIATOR_nameDef] thrust into [RECIPIENT_objective].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>OtherSexRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] had sex with [RECIPIENT_nameDef].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>DoublePenetrationRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] shuddered at the feeling of having both [RECIPIENT_possessive] holes being filled.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>BreastjobRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] used [RECIPIENT_possessive] breasts to pleasure [INITIATOR_nameDef].</li>
<li>sent-> [RECIPIENT_nameDef] rubbed [RECIPIENT_possessive] breasts along [INITIATOR_nameDef]'s shaft.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>HandjobRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] deftly worked [INITIATOR_nameDef]'s shaft with [RECIPIENT_possessive] hand.</li>
<li>sent-> [INITIATOR_nameDef] was jerked off by [RECIPIENT_nameDef].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>FootjobRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] stroked [INITIATOR_nameDef] with [RECIPIENT_possessive] feet.</li>
<li>sent-> [RECIPIENT_nameDef] massaged [INITIATOR_nameDef] with [RECIPIENT_possessive] feet.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>FingeringRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [RECIPIENT_nameDef] teased [INITIATOR_nameDef] with [RECIPIENT_possessive] fingers.</li>
<li>sent-> [INITIATOR_nameDef] gasped as [RECIPIENT_nameDef] fondled [INITIATOR_objective].</li>
<li>sent-> [INITIATOR_nameDef] groaned as [RECIPIENT_nameDef] inserted [RECIPIENT_possessive] fingers inside [INITIATOR_objective].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>ScissoringRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] scissored with [RECIPIENT_nameDef].</li>
<li>sent-> [INITIATOR_nameDef] and [RECIPIENT_nameDef] ground themselves against each other.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>MutualMasturbationRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] and [RECIPIENT_nameDef] spent some time pleasuring each other.</li>
<li>sent-> [INITIATOR_nameDef] and [RECIPIENT_nameDef] explored each others bodies.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>MutualTailholdingRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] and [RECIPIENT_nameDef] intertwined tails.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>MutualHandholdingRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] and [RECIPIENT_nameDef] shyly held hands.</li>
</rulesStrings>
</rulePack>
</RulePackDef>
<RulePackDef>
<defName>FistingRP</defName>
<rulePack>
<rulesStrings>
<li>sent-> [INITIATOR_nameDef] gasped as [RECIPIENT_nameDef] worked [RECIPIENT_possessive]'s hand inside [INITIATOR_objective].</li>
</rulesStrings>
</rulePack>
</RulePackDef>
</Defs>

18
Defs/ThingDefs.xml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<ThingDef ParentName="MoteBase">
<defName>DiscardedApparel</defName>
<graphicData>
<texPath>Things/Pawn/Humanlike/Apparel/Pants/Pants</texPath>
<drawSize>0.75</drawSize>
</graphicData>
<altitudeLayer>ItemImportant</altitudeLayer>
<mote>
<solidTime>9999</solidTime>
<fadeOutTime>0</fadeOutTime>
</mote>
</ThingDef>
</Defs>

196
Defs/ThoughtDefs.xml Normal file
View File

@ -0,0 +1,196 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<ThoughtDef>
<defName>SeenMasturbating</defName>
<thoughtClass>Thought_Memory</thoughtClass>
<durationDays>1.0</durationDays>
<stackLimit>3</stackLimit>
<stackedEffectMultiplier>0.5</stackedEffectMultiplier>
<stages>
<li>
<label>was caught masturbatin'</label>
<description>I'm so embarrassed!</description>
<baseMoodEffect>-12</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SeenHavingSex</defName>
<thoughtClass>Thought_Memory</thoughtClass>
<durationDays>1.0</durationDays>
<stackLimit>3</stackLimit>
<stackedEffectMultiplier>0.5</stackedEffectMultiplier>
<stages>
<li>
<label>was caught lovin'</label>
<description>Well, that was awkward!</description>
<baseMoodEffect>-8</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SeenMasturbatingExhibitionist</defName>
<thoughtClass>Thought_Memory</thoughtClass>
<durationDays>1.0</durationDays>
<stackLimit>1</stackLimit>
<stages>
<li>
<label>exhibitionist caught masturbatin'</label>
<description>It's OK. You can look!</description>
<baseMoodEffect>4</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SeenHavingSexExhibitionist</defName>
<thoughtClass>Thought_Memory</thoughtClass>
<durationDays>1.0</durationDays>
<stackLimit>1</stackLimit>
<stages>
<li>
<label>exhibitionist caught lovin'</label>
<description>Hope they enjoyed the show!</description>
<baseMoodEffect>6</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawMasturbation</defName>
<thoughtClass>Thought_Memory</thoughtClass>
<durationDays>1.0</durationDays>
<stackLimit>1</stackLimit>
<stackedEffectMultiplier>0.5</stackedEffectMultiplier>
<stages>
<li>
<label>saw someone masturbatin'</label>
<description>Argh! I'm so sorry!</description>
<baseMoodEffect>-6</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawSex</defName>
<thoughtClass>Thought_Memory</thoughtClass>
<durationDays>1.0</durationDays>
<stackLimit>1</stackLimit>
<stackedEffectMultiplier>0.5</stackedEffectMultiplier>
<stages>
<li>
<label>saw someone lovin'</label>
<description>Jeez! Get a room!</description>
<baseMoodEffect>-4</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawMasturbationVoyeur</defName>
<thoughtClass>Thought_Memory</thoughtClass>
<durationDays>1.0</durationDays>
<stackLimit>1</stackLimit>
<stages>
<li>
<label>voyeur saw some masturbatin'</label>
<description>Damn, that was hot!</description>
<baseMoodEffect>4</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawSexVoyeur</defName>
<thoughtClass>Thought_Memory</thoughtClass>
<durationDays>1.0</durationDays>
<stackLimit>1</stackLimit>
<stages>
<li>
<label>voyeur saw some lovin'</label>
<description>Wow, they were really going at it!</description>
<baseMoodEffect>6</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>CaughtCheating</defName>
<thoughtClass>Thought_Memory</thoughtClass>
<durationDays>10.0</durationDays>
<stackLimit>300</stackLimit>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stages>
<li>
<label>caught cheating by {0}</label>
<description>What have I done? {1}, please forgive me...</description>
<baseMoodEffect>-10</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SeenCommittingTaboo</defName>
<thoughtClass>Thought_Memory</thoughtClass>
<durationDays>2.0</durationDays>
<stackLimit>1</stackLimit>
<stackedEffectMultiplier>0.5</stackedEffectMultiplier>
<stages>
<li>
<label>caught committing a taboo</label>
<description>I'm so ashamed.</description>
<baseMoodEffect>-15</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SeenCommittingMajorTaboo</defName>
<thoughtClass>Thought_Memory</thoughtClass>
<durationDays>3.0</durationDays>
<stackLimit>1</stackLimit>
<stackedEffectMultiplier>0.5</stackedEffectMultiplier>
<stages>
<li>
<label>caught committing a major taboo</label>
<description>How can I face the world after this?</description>
<baseMoodEffect>-20</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawTaboo</defName>
<thoughtClass>Thought_Memory</thoughtClass>
<durationDays>2.0</durationDays>
<stackLimit>1</stackLimit>
<stackedEffectMultiplier>0.5</stackedEffectMultiplier>
<stages>
<li>
<label>witnessed a taboo</label>
<description>What vile degeneracy!</description>
<baseMoodEffect>-10</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SawMajorTaboo</defName>
<thoughtClass>Thought_Memory</thoughtClass>
<durationDays>3.0</durationDays>
<stackLimit>1</stackLimit>
<stackedEffectMultiplier>0.5</stackedEffectMultiplier>
<stages>
<li>
<label>witnessed a major taboo</label>
<description>Disgusting vermin! You'll get yours!</description>
<baseMoodEffect>-15</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
</Defs>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<LanguageData>
<!-- Basic settings-->
<rimworld_animation_patch_basicsettings>Rimworld Animations Patch - Basic settings</rimworld_animation_patch_basicsettings>
<rimworld_animation_patch_apparelsettings>Rimworld Animations Patch - Clothing settings</rimworld_animation_patch_apparelsettings>
<rimworld_animation_patch_general>General Options</rimworld_animation_patch_general>
<need_privacy> People require privacy for lovin'</need_privacy>
<need_privacy_desc>If someone encounters another engaged in lovin' related activities, both individuals will feel embarrassed, depending on their traits and ideology.</need_privacy_desc>
<worry_about_infidelity> People will react if they encounter their partner cheating</worry_about_infidelity>
<worry_about_infidelity_desc>Their reaction depends on their traits and ideology. This is not considered committing taboo by default.</worry_about_infidelity_desc>
<worry_about_beastiality> People will react to beastiality</worry_about_beastiality>
<worry_about_beastiality_desc>Their reaction depends on their traits and ideology. This is considered committing a major taboo by default.</worry_about_beastiality_desc>
<worry_about_rape> People will react to rape</worry_about_rape>
<worry_about_rape_desc>Their reaction depends on their traits and ideology. This is considered committing a major taboo by default.</worry_about_rape_desc>
<ignore_slave_rape> People will ignore rape if the victim is a prisoner or slave</ignore_slave_rape>
<worry_about_necro> People will react to necrophilia</worry_about_necro>
<worry_about_necro_desc>Their reaction depends on their traits and ideology. This is considered committing a major taboo by default.</worry_about_necro_desc>
<worry_about_xeno> People will react to couplings between humanoids of different species</worry_about_xeno>
<worry_about_xeno_desc>Their reaction depends on their traits and ideology. This is not considered committing taboo by default.</worry_about_xeno_desc>
<major_taboo_can_start_fights> Witnessing a major taboo will result in an extreme reaction</major_taboo_can_start_fights>
<major_taboo_can_start_fights_desc>Individuals who witness something that seriously upsets them will cause them to flee, or even possibly attack the offender.</major_taboo_can_start_fights_desc>
<chance_for_other_to_join_in_sex> People who are lovin' have a chance to invite passersby to join them for some fun (default is 0.25)</chance_for_other_to_join_in_sex>
<chance_for_other_to_join_in_sex_desc>Note that setting this value to its maximum will not guarantee that this event will occur! It just maximises the chance of an invitation being made. All individuals involved must be attracted to / have a good opinion of each other, and the activity must not violate their traits or ideology. Set this value to zero to prevent these events from happening.</chance_for_other_to_join_in_sex_desc>
<hide_names_for_sex> Hide names when getting some lovin'</hide_names_for_sex>
<hide_names_for_sex_desc>May be useful if you find the names of your pawns cover up too much of the action.</hide_names_for_sex_desc>
<debug_mode> Turn on debugging console messages</debug_mode>
<debug_mode_desc>Only required for debugging purposes.</debug_mode_desc>
<rimworld_animation_patch_animation>Animation Options</rimworld_animation_patch_animation>
<autoscale_delta_pos> Auto-scale animation offsets based on body size</autoscale_delta_pos>
<autoscale_delta_pos_desc>Turning this setting on may help when using alien races which are much larger or smaller than regular humans.</autoscale_delta_pos_desc>
<show_hands> Add animated hands to animations</show_hands>
<show_hands_desc>Requires RimNudeWorld.</show_hands_desc>
<rimworld_animation_patch_clothing>Clothing options</rimworld_animation_patch_clothing>
<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>
<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>
</LanguageData>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LanguageData>
<VoyeurQuirk><![CDATA[
{pawn} gets a thrill out of spying upon others while they are engaged in intimate acts.
]]></VoyeurQuirk>
</LanguageData>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
<Operation Class="PatchOperationSequence">
<success>Always</success>
<operations>
<li Class="PatchOperationConditional">
<xpath>/Defs/ThingDef[thingClass="Apparel"]/comps</xpath>
<match Class="PatchOperationAdd">
<xpath>Defs/ThingDef[thingClass="Apparel"]/comps</xpath>
<value>
<li Class="Rimworld_Animations_Patch.CompProperties_ApparelVisibility">
<compClass>Rimworld_Animations_Patch.CompApparelVisibility</compClass>
</li>
</value>
</match>
<nomatch Class="PatchOperationAdd">
<xpath>/Defs/ThingDef[thingClass="Apparel"]</xpath>
<value>
<comps>
<li Class="Rimworld_Animations_Patch.CompProperties_ApparelVisibility">
<compClass>Rimworld_Animations_Patch.CompApparelVisibility</compClass>
</li>
</comps>
</value>
</nomatch>
</li>
<li Class="PatchOperationAdd">
<xpath>/Defs/ThingDef[thingClass="Apparel"]/apparel/bodyPartGroups[li="Torso"]</xpath>
<value>
<li>ChestBPG</li>
</value>
</li>
<li Class="PatchOperationAdd">
<xpath>/Defs/ThingDef[thingClass="Apparel"]/apparel/bodyPartGroups[li="Legs"]</xpath>
<value>
<li>GenitalsBPG</li>
</value>
</li>
</operations>
</Operation>
</Patch>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
<Operation Class="PatchOperationReplace">
<xpath>/Defs/BodyDef/corePart/parts/li[def = "Genitals"]/groups</xpath>
<value>
<groups>
<li>Legs</li>
<li>GenitalsBPG</li>
</groups>
</value>
</Operation>
<Operation Class="PatchOperationReplace">
<xpath>/Defs/BodyDef/corePart/parts/li[def = "Anus"]/groups</xpath>
<value>
<groups>
<li>Legs</li>
<li>AnusBPG</li>
</groups>
</value>
</Operation>
</Patch>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
<Operation Class="PatchOperationFindMod">
<mods>
<li>OTY_NUDE Unofficial Update</li>
</mods>
<match Class="PatchOperationSequence">
<success>Normal</success>
<operations>
<li Class="PatchOperationReplace">
<xpath>Defs/ThingDef/alienRace/generalSettings/alienPartGenerator/bodyAddons/li[bodyPart="Anus"]/inFrontOfBody</xpath>
<value>
<inFrontOfBody>false</inFrontOfBody>
</value>
</li>
</operations>
</match>
</Operation>
</Patch>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
<Operation Class="PatchOperationFindMod">
<mods>
<li>RimNudeWorld</li>
</mods>
<match Class="PatchOperationSequence">
<success>Normal</success>
<operations>
<li Class="PatchOperationAdd">
<xpath>Defs/ThingDef/alienRace/generalSettings/alienPartGenerator/bodyAddons/li/hiddenUnderApparelFor[li="Torso"]</xpath>
<value>
<li>ChestBPG</li>
</value>
</li>
<li Class="PatchOperationAdd">
<xpath>Defs/ThingDef/alienRace/generalSettings/alienPartGenerator/bodyAddons/li/hiddenUnderApparelFor[li="Legs"]</xpath>
<value>
<li>GenitalsBPG</li>
</value>
</li>
<li Class="PatchOperationConditional">
<xpath>Defs/ThingDef/alienRace/generalSettings/alienPartGenerator/bodyAddons/li[hediffGraphics/RJW_pregnancy]/hiddenUnderApparelFor</xpath>
<nomatch Class="PatchOperationAdd">
<xpath>Defs/ThingDef/alienRace/generalSettings/alienPartGenerator/bodyAddons/li[hediffGraphics/RJW_pregnancy]</xpath>
<value>
<hiddenUnderApparelFor>
<li>Torso</li>
</hiddenUnderApparelFor>
</value>
</nomatch>
</li>
<li Class="PatchOperationReplace">
<xpath>Defs/ThingDef/alienRace/generalSettings/alienPartGenerator/bodyAddons/li[hediffGraphics/RJW_pregnancy]/offsets/south/layerOffset</xpath>
<value>
<layerOffset>-0.291</layerOffset>
</value>
</li>
<li Class="PatchOperationReplace">
<xpath>Defs/ThingDef/alienRace/generalSettings/alienPartGenerator/bodyAddons/li[hediffGraphics/RJW_pregnancy]/offsets/north/layerOffset</xpath>
<value>
<layerOffset>-0.294</layerOffset>
</value>
</li>
<li Class="PatchOperationReplace">
<xpath>Defs/ThingDef/alienRace/generalSettings/alienPartGenerator/bodyAddons/li[hediffGraphics/RJW_pregnancy]/offsets/east/layerOffset</xpath>
<value>
<layerOffset>-0.291</layerOffset>
</value>
</li>
</operations>
</match>
</Operation>
</Patch>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
<Operation Class="PatchOperationFindMod">
<mods>
<li>UnderWhere</li>
</mods>
<match Class="PatchOperationSequence">
<success>Always</success>
<operations>
<li Class="PatchOperationReplace">
<xpath>Defs/ThingDef[defName="UnderWhere_Panties" or defName="UnderWhere_Boxers" or defName="UnderWhere_Loincloth"]/apparel/bodyPartGroups</xpath>
<value>
<bodyPartGroups>
<li>GenitalsBPG</li>
</bodyPartGroups>
</value>
</li>
<li Class="PatchOperationReplace">
<xpath>Defs/ThingDef[defName="UnderWhere_Bra" or defName="UnderWhere_Chestwrap"]/apparel/bodyPartGroups</xpath>
<value>
<bodyPartGroups>
<li>ChestBPG</li>
</bodyPartGroups>
</value>
</li>
</operations>
</match>
</Operation>
</Patch>

23
Patches/s16_Clothing.xml Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<Patch>
<Operation Class="PatchOperationFindMod">
<mods>
<li>S16's Extension</li>
</mods>
<match Class="PatchOperationSequence">
<success>Always</success>
<operations>
<li Class="PatchOperationReplace">
<xpath>Defs/ThingDef/thingCategories[li="S16_Underwear"]/../apparel/bodyPartGroups</xpath>
<value>
<bodyPartGroups>
<li>GenitalsBPG</li>
</bodyPartGroups>
</value>
</li>
</operations>
</match>
</Operation>
</Patch>

View File

@ -1 +0,0 @@

Binary file not shown.

View File

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

View File

@ -0,0 +1,25 @@

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using UnityEngine;
using HarmonyLib;
using Verse;
using Rimworld_Animations;
using rjw;
namespace Rimworld_Animations_Patch
{
public static class AnimationPatchUtility
{
public static int FindTrueAnimationLength(Pawn pawn, out int orgasmTick)
{
orgasmTick = int.MaxValue;
ActorAnimationData actorAnimationData = pawn.GetAnimationData();
CompBodyAnimator compBodyAnimator = pawn.TryGetComp<CompBodyAnimator>();
// No data
if (actorAnimationData == null || compBodyAnimator == null)
{
DebugMode.Message("There is no actor animation data for " + pawn.NameShortColored);
orgasmTick = 1500 + (int)(Rand.Value * 1000f);
return orgasmTick;
}
AnimationDef anim = actorAnimationData.animationDef;
int actorId = actorAnimationData.actorID;
bool isQuickie = compBodyAnimator.fastAnimForQuickie;
int ticks = 0;
foreach (AnimationStage animStage in anim.animationStages)
{
// Legacy: skip the first stage of quickies if there's no playTimeTicksQuick values declared
if (anim.animationStages.IndexOf(animStage) == 0 && isQuickie && anim.animationStages.Any(x => x.playTimeTicksQuick >= 0) == false)
{ continue; }
int curr_tick = 0;
foreach (PawnKeyframe keyframe in (animStage.animationClips[actorId] as PawnAnimationClip).keyframes)
{
curr_tick += keyframe.tickDuration;
if (keyframe.soundEffect != null && keyframe.soundEffect == "Cum" && orgasmTick > (ticks + curr_tick))
{ orgasmTick = ticks + curr_tick; }
if (isQuickie && animStage.playTimeTicksQuick > 0 && curr_tick >= animStage.playTimeTicksQuick)
{ break; }
}
ticks += isQuickie && animStage.playTimeTicksQuick > 0 && animStage.playTimeTicksQuick < animStage.playTimeTicks ? animStage.playTimeTicksQuick : animStage.playTimeTicks;
}
// Orgasm tick not found
if (orgasmTick > ticks)
{
// Safeguard for penial, vaginal and anal sex
if (anim.actors[actorId].isFucked || anim.actors[actorId].isFucking || anim.actors[actorId].requiredGenitals.Any(x => x.ToLower().ContainsAny("penis", "vagina", "anus")))
{ orgasmTick = Mathf.Clamp(ticks - 5, 0, int.MaxValue); }
// Actor does not orgasm
else
{ orgasmTick = (int)(ticks * (2f + Rand.Value)); }
}
return ticks;
}
// Extended version of PawnHeadRotInAnimation (prevents pawn hair from getting messed up when draw in portraits)
public static Rot4 PawnHeadRotInAnimation(Pawn pawn, Rot4 regularPos, PawnRenderFlags renderFlags)
{
if (!renderFlags.FlagSet(PawnRenderFlags.Portrait) && pawn?.TryGetComp<CompBodyAnimator>() != null && pawn.TryGetComp<CompBodyAnimator>().isAnimating)
{
return pawn.TryGetComp<CompBodyAnimator>().headFacing;
}
return regularPos;
}
public static BodyPartRecord GetBodyPartRecord(Pawn pawn, string bodyPart)
{
if (bodyPart.NullOrEmpty())
{ return null; }
return pawn.health.hediffSet.GetNotMissingParts(BodyPartHeight.Undefined, BodyPartDepth.Undefined, null, null).FirstOrDefault(x => x.untranslatedCustomLabel == bodyPart || x.def.defName == bodyPart);
}
public static Vector3 GetAnchorPosition(Pawn pawn, Thing thing = null)
{
Vector3 anchor;
if (thing == null)
{ return pawn.Position.ToVector3Shifted(); }
int numOfSleepingSlots = 0;
if (thing is Building_Bed)
{ numOfSleepingSlots = BedUtility.GetSleepingSlotsCount(thing.def.size); }
// Anchor to the pawn's sleeping slot when masturbating in own bed
if (thing is Building_Bed && (pawn.ownership.OwnedBed == thing || pawn.CurrentBed() == thing) && pawn.IsMasturbating())
{
anchor = RestUtility.GetBedSleepingSlotPosFor(pawn, thing as Building_Bed).ToVector3();
if (thing.Rotation.AsInt == 0)
{
anchor.x += 0.5f;
anchor.z += 1f;
}
else if (thing.Rotation.AsInt == 1)
{
anchor.x += 1f;
anchor.z += 0.5f;
}
else if (thing.Rotation.AsInt == 2)
{
anchor.x += 0.5f;
anchor.z += 0.5f;
}
else if (thing.Rotation.AsInt == 3)
{
anchor.x += 0f;
anchor.z += 0.5f;
}
}
// Anchor to the center of the bed (should work for beds of any size?)
else if (thing is Building_Bed && numOfSleepingSlots > 0)
{
anchor = thing.Position.ToVector3();
float halfSlots = numOfSleepingSlots / 2f;
if (thing.Rotation.AsInt == 0)
{
anchor.x += halfSlots;
anchor.z += 1f;
}
else if (thing.Rotation.AsInt == 1)
{
anchor.x += 1f;
anchor.z += (1.0f - halfSlots);
}
else if (thing.Rotation.AsInt == 2)
{
anchor.x += (1f - halfSlots);
anchor.z += 0f;
}
else if (thing.Rotation.AsInt == 3)
{
anchor.x += 0f;
anchor.z += halfSlots;
}
}
// Anchor to the centre of the thing
else
{
anchor = thing.Position.ToVector3Shifted();
}
return anchor;
}
}
}

View File

@ -0,0 +1,197 @@
using System.Collections.Generic;
using System.Linq;
using Verse;
using RimWorld;
using Rimworld_Animations;
using UnityEngine;
using AlienRace;
using rjw;
namespace Rimworld_Animations_Patch
{
public static class ApparelAnimationUtility
{
public static float apparelScale = 0.75f;
public static void TryToDrawApparelOnFloor(Pawn pawn)
{
if (pawn?.apparel?.WornApparel != null)
{
CompBodyAnimator compBodyAnimator = pawn.TryGetComp<CompBodyAnimator>();
if (ApparelSettings.clothesThrownOnGround == false || Find.CurrentMap != pawn.Map || compBodyAnimator == null || compBodyAnimator.isAnimating == false)
{ return; }
foreach (Apparel apparel in pawn.apparel.WornApparel)
{
CompApparelVisibility compApparelVisibility = apparel.TryGetComp<CompApparelVisibility>();
if (compApparelVisibility != null && compApparelVisibility.position != default && compApparelVisibility.isBeingWorn == false)
{
Graphic apparelGraphic = apparel.Graphic;
apparelGraphic.drawSize.x *= apparelScale;
apparelGraphic.drawSize.y *= apparelScale;
GenDraw.DrawMeshNowOrLater(mesh: apparelGraphic.MeshAt(rot: apparel.Rotation),
loc: compApparelVisibility.position,
quat: Quaternion.AngleAxis(angle: compApparelVisibility.rotation, axis: Vector3.up),
mat: apparelGraphic.MatAt(rot: apparel.Rotation),
false);
apparelGraphic.drawSize.x *= 1f / apparelScale;
apparelGraphic.drawSize.y *= 1f / apparelScale;
DebugMode.Message(compApparelVisibility.rotation.ToString());
//DebugMode.Message("Drawing " + apparel.def.defName + " on ground");
}
}
}
}
public static bool BodyAddonCoveredByApparel(Apparel apparel, AlienPartGenerator.BodyAddon bodyAddon)
{
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
if (comp != null && comp.isBeingWorn == false)
{ return false; }
RimNudeData rimNudeData = ApparelSettings.GetRimNudeData(apparel);
if (rimNudeData != null && bodyAddon?.bodyPart != null)
{
if (bodyAddon.bodyPart == "Genitals" && rimNudeData.coversGroin == false)
{ return false; }
if (bodyAddon.bodyPart == "Chest" && rimNudeData.coversChest == false)
{ return false; }
if (bodyAddon.bodyPart == "Torso" && rimNudeData.coversBelly == false)
{ return false; }
}
if (apparel.def.apparel.bodyPartGroups.Any(x => bodyAddon.hiddenUnderApparelFor.Contains(x)) ||
apparel.def.apparel.tags.Any(x => bodyAddon.hiddenUnderApparelTag.Contains(x)))
{ return true; }
return false;
}
public static bool BodyPartCoveredByApparel(Apparel apparel, BodyPartRecord bodyPart)
{
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
if (comp != null && comp.isBeingWorn == false)
{ return false; }
RimNudeData rimNudeData = ApparelSettings.GetRimNudeData(apparel);
if (rimNudeData != null)
{
if (bodyPart.def.defName == "Genitals" && rimNudeData.coversGroin == false)
{ return false; }
if (bodyPart.def.defName == "Chest" && rimNudeData.coversChest == false)
{ return false; }
if (bodyPart.def.defName == "Torso" && rimNudeData.coversBelly == false)
{ return false; }
}
if (apparel.def.apparel.CoversBodyPart(bodyPart))
{ return true; }
return false;
}
public static void DetermineApparelToKeepOn(Pawn pawn)
{
JobDriver_Sex jobdriver = pawn.jobs.curDriver as JobDriver_Sex;
if (pawn.RaceProps.Humanlike == false || pawn?.apparel?.WornApparel == null || jobdriver == null)
{ return; }
foreach (Apparel apparel in pawn.apparel?.WornApparel)
{
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
if (comp != null)
{ comp.isBeingWorn = true; }
}
ActorAnimationData animData = pawn.GetAnimationData();
if (animData == null)
{ return; }
AnimationDef anim = animData.animationDef;
int actorID = animData.actorID;
var clothingPreference = pawn.IsInBed(out Building bed) ? RJWPreferenceSettings.sex_wear : ApparelSettings.apparelWornForQuickies;
if (xxx.has_quirk(pawn, "Endytophile"))
{ clothingPreference = RJWPreferenceSettings.Clothing.Clothed; }
// Determine any obstructing apparel that must be removed
foreach (Apparel apparel in pawn.apparel.WornApparel)
{
CompApparelVisibility comp = apparel.TryGetComp<CompApparelVisibility>();
if (comp == null)
{ continue; }
if (apparel.def is bondage_gear_def)
{ continue; }
if (ApparelSettings.GetRimNudeData(apparel) != null && ApparelSettings.GetRimNudeData(apparel).sexWear)
{ continue; }
if (clothingPreference == RJWPreferenceSettings.Clothing.Nude)
{
comp.isBeingWorn = false;
continue;
}
bool isHat = apparel.def.apparel.bodyPartGroups.NullOrEmpty() == false && (apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.FullHead) || apparel.def.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.UpperHead));
if (clothingPreference == RJWPreferenceSettings.Clothing.Headgear && isHat == false)
{
comp.isBeingWorn = false;
continue;
}
if (ApparelCoversPawnRequiredBodyParts(pawn, apparel, anim, actorID))
{
comp.isBeingWorn = false;
continue;
}
}
}
public static bool ApparelCoversPawnRequiredBodyParts(Pawn pawn, Apparel apparel, AnimationDef anim, int actorID)
{
bool bodyPartCovered = false;
IEnumerable<BodyPartRecord> bodyParts = pawn.RaceProps.body.AllParts;
var requiredGenitals = anim.actors[actorID].requiredGenitals;
if (requiredGenitals.NullOrEmpty())
{ requiredGenitals = new List<string>(); }
if (anim.actors[actorID].isFucking || requiredGenitals.Contains("Penis"))
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.genitalsDef)); }
if (anim.actors[actorID].isFucked || requiredGenitals.Contains("Vagina"))
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.genitalsDef)); }
if (anim.actors[actorID].isFucked || requiredGenitals.Contains("Anus"))
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.anusDef)); }
if (requiredGenitals.Contains("Breasts"))
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def == xxx.breastsDef)); }
if (requiredGenitals.Contains("Mouth"))
{ bodyPartCovered = bodyPartCovered || BodyPartCoveredByApparel(apparel, bodyParts.FirstOrDefault(x => x.def.defName.ToLower().ContainsAny("mouth", "teeth", "jaw", "beak"))); }
return bodyPartCovered;
}
}
}

View File

@ -0,0 +1,199 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Verse;
using RimWorld;
namespace Rimworld_Animations_Patch
{
public static class ApparelSettingsUtility
{
public static List<ThingDef> GetApparelOfInterest()
{
List<ThingDef> thingDefs = new List<ThingDef>();
foreach (ThingDef thingDef in DefDatabase<ThingDef>.AllDefs)
{
if (thingDef.IsApparel && thingDef.apparel.layers.Count == 1 && thingDef.apparel.layers[0] == ApparelLayerDefOf.Belt)
{ continue; }
if (thingDef.IsApparel &&
(thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Torso) ||
thingDef.apparel.bodyPartGroups.Contains(BodyPartGroupDefOf.Legs) ||
thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.GenitalsBPG) ||
thingDef.apparel.bodyPartGroups.Contains(PatchBodyPartGroupDefOf.ChestBPG)))
{ thingDefs.Add(thingDef); }
}
return thingDefs;
}
// Resets all data
public static void ResetRimNudeData(List<RimNudeData> rimNudeData)
{
rimNudeData.Clear();
List<ThingDef> thingDefs = GetApparelOfInterest();
foreach (ThingDef thingDef in thingDefs)
{
for (int i = 0; i < 5; i++)
{ rimNudeData.Add(new RimNudeData(thingDef)); }
}
GetApparelDefaults(rimNudeData);
}
// Update apparel data
public static void UpdateRimNudeData(List<RimNudeData> rimNudeData, string thingDef, bool coversGroin, bool coversBelly, bool coversChest, bool sexWear)
{
for (int i = 0; i < rimNudeData.Count; i++)
{
RimNudeData apparelData = rimNudeData[i];
if (apparelData.thingDef == thingDef)
{
rimNudeData[i] = new RimNudeData(thingDef, coversGroin, coversBelly, coversChest, sexWear);
return;
}
}
}
public static void SetAllCoversGroin(List<RimNudeData> rimNudeData, bool value)
{
foreach (RimNudeData rimNudeApparel in rimNudeData)
{ rimNudeApparel.coversGroin = value; }
}
public static void SetAllCoversBelly(List<RimNudeData> rimNudeData, bool value)
{
foreach (RimNudeData rimNudeApparel in rimNudeData)
{ rimNudeApparel.coversBelly = value; }
}
public static void SetAllCoversChest(List<RimNudeData> rimNudeData, bool value)
{
foreach (RimNudeData rimNudeApparel in rimNudeData)
{ rimNudeApparel.coversChest = value; }
}
public static void SetAllSexWear(List<RimNudeData> rimNudeData, bool value)
{
foreach (RimNudeData rimNudeApparel in rimNudeData)
{ rimNudeApparel.sexWear = value; }
}
public static void GetApparelDefaults(List<RimNudeData> rimNudeData)
{
//Apparel_BasicShirt
UpdateRimNudeData(rimNudeData, "Apparel_BasicShirt", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
//Apparel_CollarShirt
UpdateRimNudeData(rimNudeData, "Apparel_CollarShirt", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
//Apparel_FlakVest
UpdateRimNudeData(rimNudeData, "Apparel_FlakVest", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
//Apparel_Duster
UpdateRimNudeData(rimNudeData, "Apparel_Duster", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
//Apparel_Jacket
UpdateRimNudeData(rimNudeData, "Apparel_Jacket", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
//Apparel_TribalA
UpdateRimNudeData(rimNudeData, "Apparel_TribalA", coversGroin: true, coversBelly: true, coversChest: true, sexWear: false);
//Apparel_BodyStrap
UpdateRimNudeData(rimNudeData, "Apparel_BodyStrap", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
//Apparel_PsyfocusRobe
UpdateRimNudeData(rimNudeData, "Apparel_PsyfocusRobe", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
//Apparel_Cape
UpdateRimNudeData(rimNudeData, "Apparel_Cape", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
//Apparel_RobeRoyal
UpdateRimNudeData(rimNudeData, "Apparel_RobeRoyal", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
//Apparel_Corset
UpdateRimNudeData(rimNudeData, "Apparel_Corset", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
//VAE_Apparel_Overalls
UpdateRimNudeData(rimNudeData, "VAE_Apparel_Overalls", coversGroin: true, coversBelly: true, coversChest: false, sexWear: false);
//VAE_Apparel_LabCoat
UpdateRimNudeData(rimNudeData, "VAE_Apparel_LabCoat", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
//VAE_Apparel_BuildersJacket
UpdateRimNudeData(rimNudeData, "VAE_Apparel_BuildersJacket", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
//VAE_Apparel_Apron
UpdateRimNudeData(rimNudeData, "VAE_Apparel_Apron", coversGroin: true, coversBelly: true, coversChest: false, sexWear: false);
//VAE_Apparel_Tunic
UpdateRimNudeData(rimNudeData, "VAE_Apparel_Tunic", coversGroin: false, coversBelly: true, coversChest: true, sexWear: false);
//VAE_Apparel_PeltCoat
UpdateRimNudeData(rimNudeData, "VAE_Apparel_PeltCoat", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
//VAE_Apparel_WoodenArmor
UpdateRimNudeData(rimNudeData, "VAE_Apparel_WoodenArmor", coversGroin: false, coversBelly: true, coversChest: false, sexWear: false);
//VAE_Apparel_AdvancedVest
UpdateRimNudeData(rimNudeData, "VAE_Apparel_AdvancedVest", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
//VAE_Apparel_BulletproofVest
UpdateRimNudeData(rimNudeData, "VAE_Apparel_BulletproofVest", coversGroin: false, coversBelly: true, coversChest: false, sexWear: false);
//VWE_Apparel_Exoframe
UpdateRimNudeData(rimNudeData, "VWE_Apparel_Exoframe", coversGroin: false, coversBelly: false, coversChest: false, sexWear: false);
//VFEM_Apparel_Tabard
UpdateRimNudeData(rimNudeData, "VFEM_Apparel_Tabard", coversGroin: true, coversBelly: true, coversChest: true, sexWear: false);
//VFEV_Apparel_JarlCape
UpdateRimNudeData(rimNudeData, "VFEV_Apparel_JarlCape", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
//VFEV_Apparel_RoyalFurCoat
UpdateRimNudeData(rimNudeData, "VFEV_Apparel_RoyalFurCoat", coversGroin: false, coversBelly: false, coversChest: true, sexWear: false);
//PrisonerChains
UpdateRimNudeData(rimNudeData, "PrisonerChains", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
//S16_ChainHarnessA
UpdateRimNudeData(rimNudeData, "S16_ChainHarnessA", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
//S16_NippleWristCuffs
UpdateRimNudeData(rimNudeData, "S16_NippleWristCuffs", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
//S16_ServantGirlDress
UpdateRimNudeData(rimNudeData, "S16_ServantGirlDress", coversGroin: true, coversBelly: true, coversChest: false, sexWear: true);
//S16_ZDress
UpdateRimNudeData(rimNudeData, "S16_ZDress", coversGroin: false, coversBelly: true, coversChest: true, sexWear: true);
//S16_MaidA
UpdateRimNudeData(rimNudeData, "S16_MaidA", coversGroin: false, coversBelly: true, coversChest: false, sexWear: true);
//S16_DiscoTop
UpdateRimNudeData(rimNudeData, "S16_DiscoTop", coversGroin: false, coversBelly: false, coversChest: true, sexWear: true);
//S16_TransparentSkirt
UpdateRimNudeData(rimNudeData, "S16_TransparentSkirt", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
//S16_Vibrator
UpdateRimNudeData(rimNudeData, "S16_Vibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
//S16_VibratorDouble
UpdateRimNudeData(rimNudeData, "S16_VibratorDouble", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
//S16_WiredVibrator
UpdateRimNudeData(rimNudeData, "S16_WiredVibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
//S16_DoubleWiredVibrator
UpdateRimNudeData(rimNudeData, "S16_DoubleWiredVibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
//S16_WiredAnalVibrator
UpdateRimNudeData(rimNudeData, "S16_WiredAnalVibrator", coversGroin: false, coversBelly: false, coversChest: false, sexWear: true);
}
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using Verse;
using RimWorld;
namespace Rimworld_Animations_Patch
{
public static class DebugMode
{
public static void Message(string text)
{
if (BasicSettings.debugMode)
{ Log.Message("[DEBUG] " + text); }
}
}
}

View File

@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using HarmonyLib;
using RimWorld;
using UnityEngine;
using Verse;
namespace Rimworld_Animations_Patch
{
public static class GraphicMaskingUtility
{
public static Texture2D GetReadableTexture2D(Texture2D source, int newWidth, int newHeight, Material mat = null) //rescales texture to newWidth and newHeight
{
source.filterMode = FilterMode.Trilinear;
RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight);
rt.filterMode = FilterMode.Trilinear;
RenderTexture.active = rt;
if (mat != null)
{ Graphics.Blit(source, rt, mat); }
else
{ Graphics.Blit(source, rt); }
Texture2D nTex = new Texture2D(newWidth, newHeight, TextureFormat.RGBA32, mipChain: true);
nTex.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0);
nTex.name = source.name;
nTex.filterMode = FilterMode.Trilinear;
nTex.anisoLevel = 2;
nTex.Apply(updateMipmaps: true);
GL.Clear(true, true, Color.clear);
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(rt);
return nTex;
}
public static Texture2D GetReadableTexture2D(Texture2D source, Material mat = null)
{
return GetReadableTexture2D(source, source.width, source.height, mat);
}
public static Texture2D ApplyMaskToTexture2D(Texture2D mainTex, Texture2D maskTex, bool writeOverMainTex)
{
if (mainTex == null || maskTex == null)
{
DebugMode.Message("mainTex or maskTex is missing!");
return mainTex;
}
Color[] mainArray = GetReadableTexture2D(mainTex).GetPixels();
Color[] maskArray = GetReadableTexture2D(maskTex, mainTex.width, mainTex.height).GetPixels();
for (int j = 0; j < mainArray.Length; j++)
{
if (maskArray[j] == Color.white)
{ /*null*/ }
else if (maskArray[j].a == 0)
{ mainArray[j].a = 0; }
else if (mainArray[j].a > 0 && maskArray[j].a > 0 && writeOverMainTex)
{ mainArray[j] = new Color(Mathf.Min(mainArray[j].r, maskArray[j].r), Mathf.Min(mainArray[j].g, maskArray[j].g), Mathf.Min(mainArray[j].b, maskArray[j].b), Mathf.Min(mainArray[j].a, maskArray[j].a)); }
}
Texture2D newTex = new Texture2D(mainTex.width, mainTex.height, TextureFormat.RGBA32, mipChain: true);
newTex.SetPixels(mainArray);
newTex.filterMode = FilterMode.Trilinear;
newTex.anisoLevel = 2;
newTex.Apply(updateMipmaps: true);
return newTex;
}
public static Graphic ApplyGraphicWithMasks(Graphic graphic, Graphic graphicWithMask, bool writeOverMainTex)
{
for (int i = 0; i < 4; i++)
{
Texture2D mainTex = (Texture2D)graphic.MatAt(new Rot4(i)).mainTexture;
Texture2D maskTex = graphicWithMask.MatAt(new Rot4(i)).GetMaskTexture();
graphic.MatAt(new Rot4(i)).mainTexture = ApplyMaskToTexture2D(mainTex, maskTex, writeOverMainTex);
}
return graphic;
}
public static Graphic ApplyGraphicWithMasks(Graphic graphic, string mask, bool writeOverMainTex)
{
for (int i = 0; i < 4; i++)
{
Texture2D mainTex = (Texture2D)graphic.MatAt(new Rot4(i)).mainTexture;
if (mainTex == null)
{ DebugMode.Message("Main Texture2D not found for " + graphic.path + ". Rotation: " + i.ToString()); continue; }
string suffix = string.Empty;
switch (i)
{
case 0: suffix = "_north"; break;
case 1: suffix = "_east"; break;
case 2: suffix = "_south"; break;
case 3: suffix = "_west"; break;
}
Texture2D maskTex = ContentFinder<Texture2D>.Get(mask + suffix, false);
if (maskTex == null)
{ DebugMode.Message("Mask Texture2D not found for " + mask + ". Rotation: " + i.ToString()); continue; }
graphic.MatAt(new Rot4(i)).mainTexture = ApplyMaskToTexture2D(mainTex, maskTex, writeOverMainTex);
}
return graphic;
}
public static void ResetGraphic(Graphic graphic)
{
for (int i = 0; i < 4; i++)
{
string suffix = string.Empty;
switch (i)
{
case 0: suffix = "_north"; break;
case 1: suffix = "_east"; break;
case 2: suffix = "_south"; break;
case 3: suffix = "_west"; break;
}
Texture2D texture2D = ContentFinder<Texture2D>.Get(graphic.path + suffix, false);
if (texture2D == null && i == 3)
{ texture2D = ContentFinder<Texture2D>.Get(graphic.path + "_east", false); }
if (texture2D == null)
{ texture2D = ContentFinder<Texture2D>.Get(graphic.path + "_north", false); }
if (texture2D != null)
{ graphic.MatAt(new Rot4(i)).mainTexture = texture2D; }
}
}
}
}

View File

@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Verse;
using RimWorld;
using HarmonyLib;
using Rimworld_Animations;
namespace Rimworld_Animations_Patch
{
public static class HandAnimationUtility
{
public static BodyPartDef handDef;
public static bool BodyPartIsBeingTouched(Pawn pawn, string bodypartFilePath, out List<HandAnimationData> handAnimationData)
{
handAnimationData = new List<HandAnimationData>();
ActorAnimationData actorAnimationData = pawn.GetAnimationData();
HandAnimationDef handAnimationDef = DefDatabase<HandAnimationDef>.AllDefs.FirstOrDefault(x => x.animationDefName == actorAnimationData.animationDef.defName);
if (handAnimationDef == null)
{ return false; }
foreach (HandAnimationData datum in handAnimationDef.handAnimationData)
{
if (datum.stageID != actorAnimationData.currentStage || datum.actorID != actorAnimationData.actorID)
{ continue; }
if (datum.bodySide.NullOrEmpty() == false && bodypartFilePath.ToLower().Contains(datum.bodySide) == false)
{ continue; }
if (datum.targetBodyPart.NullOrEmpty() == false && bodypartFilePath.ToLower().Contains(datum.targetBodyPart.ToLower()))
{ handAnimationData.Add(datum); }
else if (datum.targetBodyParts.Any(x => bodypartFilePath.ToLower().Contains(x.ToLower())))
{ handAnimationData.Add(datum); }
}
return handAnimationData.NullOrEmpty() == false;
}
public static Vector3 GetHandPosition(Pawn pawn, HandAnimationData handAnimationData, Vector3 basePosition, float baseAngle)
{
var methodInfo = AccessTools.Method(typeof(HandAnimationUtility), handAnimationData.motion, null, null);
if (methodInfo == null)
{
Debug.LogWarning("Hand anaimation motion '" + handAnimationData.motion + "' was not found");
return default;
}
Vector3 handPosition = (Vector3)methodInfo.Invoke(null, new object[] { pawn, handAnimationData, baseAngle });
return handPosition * pawn.RaceProps.baseBodySize + basePosition;
}
public static float GetGenitalSize(Pawn pawn, string genitalName)
{
switch(genitalName.ToLower())
{
case "penis": return pawn.health.hediffSet.hediffs.First(x => x.def.defName.ToLower().Contains("penis")).Severity;
case "breasts": return pawn.health.hediffSet.hediffs.First(x => x.def.defName.ToLower().Contains("breasts")).Severity;
case "vagina": return 0.1f;
case "anus": return 0.1f;
}
return 0.1f;
}
public static Vector3 Motion_StrokeGenitalsUpAndDownShort_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
{
Vector3 handPosition = new Vector3();
ActorAnimationData data = pawn.GetAnimationData();
float p = (Mathf.PingPong(data.stageTicks, handAnimationData.cycleTime) / handAnimationData.cycleTime);
float length = 0.035f;
handPosition.x = 0;
handPosition.z = length * p;
handPosition = handPosition.RotatedBy(baseAngle);
return handPosition;
}
public static Vector3 Motion_StrokeGenitalsUpAndDown_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
{
Vector3 handPosition = new Vector3();
ActorAnimationData data = pawn.GetAnimationData();
float p = (Mathf.PingPong(data.stageTicks, handAnimationData.cycleTime) / handAnimationData.cycleTime);
float size = GetGenitalSize(pawn, handAnimationData.targetBodyPart) * 0.2f;
float m = (data.actorFacing == Rot4.North ? -1f : 1f) * (handAnimationData.mirror ? -1f : 1f) * (pawn.TryGetComp<CompBodyAnimator>().Mirror ? -1f : 1f);
handPosition.x = 0.025f * m;
handPosition.z = size * p;
handPosition = handPosition.RotatedBy(baseAngle);
return handPosition;
}
public static Vector3 Motion_StrokeGenitalsUpAndDown_FacingEW(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
{
Vector3 handPosition = new Vector3();
ActorAnimationData data = pawn.GetAnimationData();
float p = Mathf.PingPong(data.stageTicks, handAnimationData.cycleTime) / handAnimationData.cycleTime;
float size = GetGenitalSize(pawn, handAnimationData.targetBodyPart) * 0.2f;
float m = (data.actorFacing == Rot4.West ? -1f : 1f) * (handAnimationData.mirror ? -1f : 1f);
handPosition.x = Mathf.Sin(m * (baseAngle + 45f) / 180f * Mathf.PI) * size * p;
handPosition.z = Mathf.Cos(m * (baseAngle + 45f) / 180f * Mathf.PI) * size * p;
return handPosition;
}
public static Vector3 Motion_RubGenitals_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
{
Vector3 handPosition = new Vector3();
ActorAnimationData data = pawn.GetAnimationData();
float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
float m = (data.actorFacing == Rot4.North ? 1f : -1f) * (handAnimationData.mirror ? -1f : 1f) * (pawn.TryGetComp<CompBodyAnimator>().Mirror ? -1f : 1f);
handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.05f - 0.025f) * m;
handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f + 0.03f;
handPosition = handPosition.RotatedBy(baseAngle);
return handPosition;
}
public static Vector3 Motion_RubGenitals_FacingEW(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
{
Vector3 handPosition = new Vector3();
ActorAnimationData data = pawn.GetAnimationData();
float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
float m = (data.actorFacing == Rot4.West ? 1f : -1f) * (handAnimationData.mirror ? -1f : 1f);
handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.005f - 0.05f) * m;
handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f;
//handPosition.y = -0.1f;
handPosition = handPosition.RotatedBy(baseAngle);
return handPosition;
}
public static Vector3 Motion_RubBreasts_FacingNS(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
{
Vector3 handPosition = new Vector3();
ActorAnimationData data = pawn.GetAnimationData();
float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
float m = (data.actorFacing == Rot4.North ? -1f : 1f) * (handAnimationData.mirror ? -1f : 1f) * (pawn.TryGetComp<CompBodyAnimator>().Mirror ? -1f : 1f);
float size = GetGenitalSize(pawn, "breasts");
handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.05f * size - size * 0.25f) * m;
handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f - size * 0.125f;
handPosition = handPosition.RotatedBy(baseAngle);
return handPosition;
}
public static Vector3 Motion_RubBreasts_FacingEW(Pawn pawn, HandAnimationData handAnimationData, float baseAngle)
{
Vector3 handPosition = new Vector3();
ActorAnimationData data = pawn.GetAnimationData();
float a = ((float)data.stageTicks % (float)handAnimationData.cycleTime) / (float)handAnimationData.cycleTime * 360f;
float m = (data.actorFacing == Rot4.West ? 1f : -1f) * (handAnimationData.mirror ? -1f : 1f);
float size = GetGenitalSize(pawn, "breasts");
handPosition.x = (Mathf.Sin(a / 180f * Mathf.PI) * 0.005f - size * 0.25f) * m;
handPosition.z = Mathf.Cos(a / 180f * Mathf.PI) * 0.015f - size * 0.125f;
handPosition = handPosition.RotatedBy(baseAngle);
return handPosition;
}
public static Graphic GetHandGraphic(Pawn touchingPawn, string touchedBodyAddonName, HandAnimationData handAnimationData)
{
string handGraphicPath = "Hands/HandClean";
Color skinColour = touchingPawn.story.SkinColor;
float handSize = 0.6667f * touchingPawn.RaceProps.baseBodySize;
return GraphicDatabase.Get<Graphic_Single>(handGraphicPath, ShaderDatabase.Cutout, new Vector2(handSize, handSize), skinColour);
}
public static bool TryToDrawHand(Pawn pawn, string bodyAddonName, Vector3 bodyAddonPosition, float bodyAddonAngle, Rot4 bodyAddonRotation, PawnRenderFlags renderFlags)
{
if (pawn.TryGetComp<CompBodyAnimator>() != null && pawn.TryGetComp<CompBodyAnimator>().isAnimating && BodyPartIsBeingTouched(pawn, bodyAddonName, out List<HandAnimationData> handAnimationData))
{
foreach (HandAnimationData datum in handAnimationData)
{
Pawn touchingPawn = datum.touchingActorID >= 0 && pawn.GetAllSexParticipants().Count > datum.touchingActorID ? pawn.GetAllSexParticipants()[datum.touchingActorID] : pawn;
Graphic handgraphic = GetHandGraphic(touchingPawn, bodyAddonName, datum);
Vector3 handPosition = GetHandPosition(pawn, datum, bodyAddonPosition, bodyAddonAngle);
GenDraw.DrawMeshNowOrLater(mesh: handgraphic.MeshAt(rot: bodyAddonRotation),
loc: handPosition + new Vector3(0f, 0.022f, 0f),
quat: Quaternion.identity,
mat: handgraphic.MatAt(rot: bodyAddonRotation), renderFlags.FlagSet(PawnRenderFlags.DrawNow));
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;
namespace Rimworld_Animations_Patch
{
public static class MathUtility
{
public static float Repeat(float value, float min, float max)
{
if (Mathf.Abs(max) < Mathf.Abs(min))
{
Log.Error("RepeatDual: min value must be greater than max value");
return -1;
}
float range = max - min;
float m = value % range;
if (m < 0)
{ m = range + m; }
return min + m;
}
public static IntVec3 FindRandomCellNearPawn(Pawn pawn, int maxRadius)
{
if (maxRadius > 0)
{
for (int radius = 1; radius < maxRadius; radius++)
{
List<IntVec3> cells = GenRadial.RadialCellsAround(pawn.Position, radius + 0.75f, false).Where(x => x.Standable(pawn.Map) && x.GetRoom(pawn.Map) == pawn.GetRoom())?.ToList();
if (cells.NullOrEmpty() == false && cells.Count > 0)
{ return cells.RandomElement(); }
}
}
return GenAdj.RandomAdjacentCellCardinal(pawn);
}
}
}

View File

@ -0,0 +1,14 @@
using Verse;
using RimWorld;
using AlienRace;
namespace Rimworld_Animations_Patch
{
[DefOf]
public static class PatchBodyPartGroupDefOf
{
public static BodyPartGroupDef GenitalsBPG;
public static BodyPartGroupDef AnusBPG;
public static BodyPartGroupDef ChestBPG;
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Rimworld_Animations_Patch
{
public static class SettingsUtility
{
public static float Align(float objectDim, float columnDim)
{
return (columnDim - objectDim) * 0.5f;
}
}
}

View File

@ -0,0 +1,241 @@
using System.Collections.Generic;
using System.Linq;
using Verse;
using Verse.AI;
using Verse.AI.Group;
using RimWorld;
using rjw;
using RJWSexperience.Ideology;
using UnityEngine;
namespace Rimworld_Animations_Patch
{
public static class SexInteractionUtility
{
public static bool PawnCaughtLovinByWitness(Pawn pawn, Pawn witness)
{
if (witness == null || pawn == witness || witness.AnimalOrWildMan() || witness.RaceProps.IsMechanoid || witness.Awake() == false || witness.CanSee(pawn) == false)
{ return false; }
if (pawn.IsHavingSex() == false && pawn.IsMasturbating() == false)
{ return false; }
List<Pawn> sexParticipants = pawn.GetAllSexParticipants();
bool witnessIsCourtingSexParticipant = witness.jobs.curDriver is JobDriver_SexBaseInitiator && sexParticipants.Contains((witness.jobs.curDriver as JobDriver_SexBaseInitiator).Partner);
if (sexParticipants.Contains(witness) || witnessIsCourtingSexParticipant)
{ return false; }
return true;
}
public static bool PawnIsCheatingOnPartner(Pawn pawn, Pawn partner)
{
if (BasicSettings.worryAboutInfidelity == false || pawn.IsMasturbating() || pawn.IsHavingSex() == false || pawn.GetAllSexParticipants().Contains(partner))
{ return false; }
if (pawn.GetAllSexParticipants().Any(x => pawn.GetSpouseCount(false) > 0 && pawn.GetSpouses(false).Contains(x)))
{ return false; }
return partner.IsLoverOfOther(pawn) && pawn.HasTrait("Polygamist") == false && partner.HasTrait("Polygamist") == false;
}
public static bool PawnCanInvitePasserbyForSex(Pawn passerby, List<Pawn> participants)
{
if (passerby == null || participants.NullOrEmpty() || participants.Contains(passerby) || passerby.AnimalOrWildMan() || passerby.RaceProps.IsMechanoid || passerby.Awake() == false || participants.All(x => x.CanSee(passerby) == false))
{ return false; }
if (participants.Any(x => x.IsForbidden(passerby) || x.HostileTo(passerby) || PawnIsCheatingOnPartner(x, passerby)) || CasualSex_Helper.CanHaveSex(passerby) == false || xxx.IsTargetPawnOkay(passerby) == false || participants.Count > 2)
{ return false; }
if (SexUtility.ReadyForHookup(passerby) &&
(passerby?.jobs?.curJob == null || (passerby.jobs.curJob.playerForced == false && CasualSex_Helper.quickieAllowedJobs.Contains(passerby.jobs.curJob.def))) &&
participants.Any(x => SexAppraiser.would_fuck(x, passerby) > 0.1f && SexAppraiser.would_fuck(passerby, x) > 0.1f) &&
participants.All(x => SexAppraiser.would_fuck(x, passerby, false, false, true) > 0.1f && SexAppraiser.would_fuck(passerby, x, false, false, true) > 0.1f))
{
return true;
}
return false;
}
public static TabooStatus CheckSexJobAgainstMorals(Pawn pawn, JobDriver_Sex jobDriver, out Precept precept)
{
bool sexIsNecro = jobDriver.Partner != null && jobDriver.Partner.Dead;
bool sexIsBeastial = jobDriver.Partner != null && jobDriver.Partner.RaceProps.Animal;
bool sexIsRape = sexIsBeastial == false && sexIsNecro == false &&
(jobDriver is JobDriver_Rape || jobDriver is JobDriver_RapeEnemy || jobDriver is JobDriver_SexBaseRecieverRaped) &&
jobDriver.Partner.IsPrisoner == false && jobDriver.Partner.IsSlave == false;
bool sexIsSlaveRape = sexIsBeastial == false && sexIsNecro == false &&
(jobDriver is JobDriver_Rape || jobDriver is JobDriver_RapeEnemy || jobDriver is JobDriver_SexBaseRecieverRaped) &&
(jobDriver.Partner.IsPrisoner || jobDriver.Partner.IsSlave);
bool sexIsXeno = jobDriver.Partner != null && jobDriver.Partner.def.defName != jobDriver.pawn.def.defName;
TabooStatus tabooStatus = TabooStatus.NotTaboo;
precept = null;
if (BasicSettings.worryAboutNecro && sexIsNecro && xxx.is_necrophiliac(pawn) == false)
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Necrophilia"), TabooStatus.MajorTaboo, out precept); }
else if (BasicSettings.worryAboutBeastiality && sexIsBeastial && xxx.is_zoophile(pawn) == false)
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Beastility"), TabooStatus.MajorTaboo, out precept); }
else if (BasicSettings.worryAboutRape && sexIsRape && xxx.is_rapist(pawn) == false)
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Rape"), TabooStatus.MajorTaboo, out precept); }
else if (BasicSettings.worryAboutRape && BasicSettings.ignoreSlaveRape == false && sexIsSlaveRape && xxx.is_rapist(pawn) == false)
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("Rape"), TabooStatus.MajorTaboo, out precept); }
else if (BasicSettings.worryAboutXeno && sexIsXeno && pawn.HasTrait("Xenophobia") && pawn.story.traits.DegreeOfTrait(DefDatabase<TraitDef>.GetNamedSilentFail("Xenophobia")) > 0)
{ tabooStatus = TabooStatus.MajorTaboo; }
else if (BasicSettings.worryAboutXeno && sexIsXeno)
{ tabooStatus = GetTabooStatusOfIssue(pawn, DefDatabase<IssueDef>.GetNamedSilentFail("HAR_AlienDating"), TabooStatus.NotTaboo, out precept); }
//DebugMode.Message("Sex job is: " + jobDriver + " Issue is: " + (precept?.def?.issue?.defName).ToStringSafe() + " Opinion is: " + (precept?.def?.defName).ToStringSafe() + " Judgement is: " + tabooStatus.ToString());
return tabooStatus;
}
public static TabooStatus GetTabooStatusOfIssue(Pawn pawn, IssueDef issueDef, TabooStatus defaultTabboStatus, out Precept precept)
{
if (pawn.IssueIsMajorTaboo(issueDef, out precept))
{ return TabooStatus.MajorTaboo; }
if (pawn.IssueIsMinorTaboo(issueDef, out precept))
{ return TabooStatus.MinorTaboo; }
return defaultTabboStatus;
}
public static bool ResolveThoughtsForWhenSexIsWitnessed(Pawn pawn, Pawn witness, out bool witnessJoiningSex)
{
witnessJoiningSex = false;
if (pawn.IsAnimal() || pawn.RaceProps.IsMechanoid || pawn.Dead)
{ return false; }
if (witness.IsAnimal() || witness.RaceProps.IsMechanoid || witness.Dead)
{ return false; }
JobDriver_Sex jobDriver = pawn.jobs.curDriver as JobDriver_Sex;
string pawnThoughtDefName = pawn.IsMasturbating() ? "SeenMasturbating" : "SeenHavingSex";
string witnessThoughtDefName = pawn.IsMasturbating() ? "SawMasturbation" : "SawSex";
bool pawnIsExhibitionist = pawn.HasTrait("Exhibitionist") || xxx.has_quirk(pawn, "Exhibitionist");
if (pawnIsExhibitionist)
{ pawnThoughtDefName += "Exhibitionist"; }
bool witnessIsVoyeur = witness.HasTrait("Voyeur") || xxx.has_quirk(witness, "Voyeur");
if (witnessIsVoyeur)
{ witnessThoughtDefName += "Voyeur"; }
bool sexIsRitual = pawn.GetLord() != null && pawn.GetLord().LordJob is LordJob_Ritual && witness?.Ideo == pawn?.Ideo;
bool pawnIsVictim = pawn.CurJob.def == xxx.gettin_raped || pawn.Dead;
bool pawnIsCheating = pawnIsVictim == false && PawnIsCheatingOnPartner(pawn, witness);
witnessJoiningSex = Random.value < BasicSettings.chanceForOtherToJoinInSex && PawnCanInvitePasserbyForSex(witness, pawn.GetAllSexParticipants());
// Determine if there are any issues with the witness' morals
TabooStatus tabooStatus = CheckSexJobAgainstMorals(witness, jobDriver, out Precept precept);
if (tabooStatus == TabooStatus.MajorTaboo)
{ witnessThoughtDefName = "SawMajorTaboo"; witnessJoiningSex = false; }
else if (tabooStatus == TabooStatus.MinorTaboo)
{ witnessThoughtDefName = "SawTaboo"; witnessJoiningSex = false; }
else if (pawnIsCheating)
{ witnessThoughtDefName = "CheatedOnMe"; witnessJoiningSex = false; }
else if (BasicSettings.needPrivacy == false)
{ witnessThoughtDefName = ""; }
// Apply thoughts to witness
ThoughtDef witnessThoughtDef = DefDatabase<ThoughtDef>.GetNamedSilentFail(witnessThoughtDefName);
if (witnessThoughtDef != null && pawnIsVictim == false && witnessJoiningSex == false && sexIsRitual == false)
{
witness.needs.mood.thoughts.memories.TryGainMemory(witnessThoughtDef, pawn, precept);
if (witnessThoughtDef.stages[0].baseMoodEffect < 0)
{ FleckMaker.ThrowMetaIcon(witness.Position, witness.Map, FleckDefOf.IncapIcon); }
// Fight or flight reaction
if (BasicSettings.majorTabooCanStartFights &&
(tabooStatus == TabooStatus.MajorTaboo || pawnIsCheating) &&
witness.Drafted == false &&
witness.jobs.curDriver is JobDriver_Flee == false &&
witness.jobs.curDriver is JobDriver_AttackMelee == false &&
witness.jobs.curDriver is JobDriver_Vomit == false)
{
// Fight
if (pawn.RaceProps.Humanlike && witness.RaceProps.Humanlike && witness.DislikesViolence() == false && (Random.value < 0.2f || witness.EnjoysViolence()) && witness.HostileTo(pawn) == false && InteractionUtility.TryGetRandomVerbForSocialFight(witness, out Verb verbToUse))
{
if (witness.LastAttackedTarget.Pawn != pawn || (pawn.mindState.lastAttackTargetTick < 0 && pawn.mindState.lastAttackTargetTick + Find.TickManager.TicksGame > 180))
{
pawn.mindState.lastAttackTargetTick = Find.TickManager.TicksGame;
string message = witness.LabelShort + " is going to punish " + pawn.LabelShort + " for " + GenderUtility.GetPossessive(pawn.gender) + " transgression.";
Messages.Message(message, pawn, MessageTypeDefOf.NegativeEvent);
}
Job job = JobMaker.MakeJob(JobDefOf.SocialFight, pawn);
job.maxNumMeleeAttacks = 1;
job.verbToUse = verbToUse;
witness.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
witness.jobs.StartJob(job);
}
// Vomit
else if (jobDriver.Partner != null && jobDriver.Partner.Dead)
{
Job jobVomit = JobMaker.MakeJob(JobDefOf.Vomit);
Job jobFlee = JobMaker.MakeJob(JobDefOf.FleeAndCower, CellFinderLoose.GetFleeDest(witness, new List<Thing>() { pawn }, 24f), pawn);
witness.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
witness.jobs.StartJob(jobVomit);
witness.jobs.jobQueue.EnqueueFirst(jobFlee);
}
// Flight
else
{
Job job = JobMaker.MakeJob(JobDefOf.FleeAndCower, CellFinderLoose.GetFleeDest(witness, new List<Thing>() { pawn }, 24f), pawn);
witness.jobs.EndCurrentJob(JobCondition.InterruptForced, false, false);
witness.jobs.StartJob(job);
}
}
}
// Check issue against pawn precepts
tabooStatus = CheckSexJobAgainstMorals(pawn, jobDriver, out precept);
if (tabooStatus == TabooStatus.MajorTaboo)
{ pawnThoughtDefName = "SeenCommittingMajorTaboo"; witnessJoiningSex = false; }
else if (tabooStatus == TabooStatus.MinorTaboo)
{ pawnThoughtDefName = "SeenCommittingTaboo"; witnessJoiningSex = false; }
else if (pawnIsCheating)
{ pawnThoughtDefName = "CaughtCheating"; witnessJoiningSex = false; }
else if (BasicSettings.needPrivacy == false)
{ pawnThoughtDefName = ""; }
// Apply thoughts to pawn
ThoughtDef pawnThoughtDef = DefDatabase<ThoughtDef>.GetNamedSilentFail(pawnThoughtDefName);
if (pawnThoughtDef != null && pawnIsVictim == false && witnessJoiningSex == false && sexIsRitual == false)
{
pawn.needs.mood.thoughts.memories.TryGainMemory(pawnThoughtDef, witness, precept);
if (pawnThoughtDef.stages[0].baseMoodEffect < 0)
{ FleckMaker.ThrowMetaIcon(pawn.Position, pawn.Map, FleckDefOf.IncapIcon); }
}
return witnessJoiningSex;
}
}
}

View File

@ -0,0 +1 @@
d5a3e9402b3033575abd239e1b750dc876117ae0

View File

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

Binary file not shown.

Binary file not shown.

BIN
Textures/Hands/Hand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB