This commit is contained in:
Ed86 2022-07-16 22:50:21 +03:00
parent c76cb005aa
commit b738a16dbc
54 changed files with 3813 additions and 89 deletions

63
.gitattributes vendored Normal file
View file

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

340
.gitignore vendored Normal file
View file

@ -0,0 +1,340 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- Backup*.rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb

Binary file not shown.

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<JobDef>
<defName>WhoreIsServingVisitors</defName>
<driverClass>rjwwhoring.JobDriver_WhoreIsServingVisitors</driverClass>
<reportString>serving visitors</reportString>
<casualInterruptible>false</casualInterruptible>
</JobDef>
<JobDef>
<defName>WhoreInvitingVisitors</defName>
<driverClass>rjwwhoring.JobDriver_WhoreInvitingVisitors</driverClass>
<reportString>attempting hookup</reportString>
<casualInterruptible>false</casualInterruptible>
</JobDef>
</Defs>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<!--Counts whoring stuff -->
<RecordDef>
<defName>CountOfWhore</defName>
<label>times whored</label>
<description>The number of times I whored myself.</description>
<type>Int</type>
</RecordDef>
<RecordDef>
<defName>EarnedMoneyByWhore</defName>
<label>silvers earned as a whore</label>
<description>The amount of silvers I have earned as a whore.</description>
<type>Int</type>
</RecordDef>
</Defs>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<RoomRoleDef>
<defName>Brothel</defName>
<label>brothel</label>
<workerClass>rjwwhoring.RoomRoleWorker_Brothel</workerClass>
<relatedStats>
<li>Beauty</li>
<li>Cleanliness</li>
<li>Wealth</li>
<li>Space</li>
<li>Impressiveness</li>
</relatedStats>
</RoomRoleDef>
</Defs>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<ThinkTreeDef>
<defName>PrisonerWhoreSexualEmergencyTree</defName>
<insertTag>Humanlike_PostDuty</insertTag>
<insertPriority>100</insertPriority>
<thinkRoot Class="ThinkNode_Priority">
<subNodes>
<li Class="rjw.ThinkNode_ConditionalSexChecks">
<subNodes>
<li Class="rjwwhoring.ThinkNode_ConditionalWhore">
<subNodes>
<li Class="rjwwhoring.JobGiver_WhoreInvitingVisitors" />
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</thinkRoot>
</ThinkTreeDef>
</Defs>

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<!-- Whores will find visitors before looking for work to do -->
<ThinkTreeDef>
<defName>WhoreSexualEmergencyTree</defName>
<insertTag>Humanlike_PreMain</insertTag>
<insertPriority>15</insertPriority>
<thinkRoot Class="ThinkNode_Priority">
<subNodes>
<li Class="rjw.ThinkNode_ConditionalSexChecks">
<subNodes>
<li Class="rjwwhoring.ThinkNode_ConditionalWhore">
<subNodes>
<li Class="rjwwhoring.JobGiver_WhoreInvitingVisitors" />
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</thinkRoot>
</ThinkTreeDef>
<!-- Whores will always look for sex if they have nothing else to do -->
<ThinkTreeDef>
<defName>WhoreJobTree</defName>
<insertTag>Humanlike_PostMain</insertTag>
<insertPriority>15</insertPriority>
<thinkRoot Class="ThinkNode_Priority">
<subNodes>
<li Class="rjw.ThinkNode_ConditionalSexChecks">
<subNodes>
<li Class="rjwwhoring.ThinkNode_ConditionalWhore">
<subNodes>
<li Class="rjwwhoring.JobGiver_WhoreInvitingVisitors" />
</subNodes>
</li>
</subNodes>
</li>
</subNodes>
</thinkRoot>
</ThinkTreeDef>
</Defs>

View file

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<rjwwhoring.ThoughtDef_Whore>
<defName>Whorish_Thoughts</defName>
<thoughtClass>rjwwhoring.ThoughtWorker_Whore</thoughtClass>
<durationDays>2.0</durationDays>
<stackLimit>10</stackLimit>
<stackedEffectMultiplier>0.4</stackedEffectMultiplier>
<!--Numbers of whoring times needed to pass to next stage, one less than the stages count-->
<!--I couldn't find a way to move them to thoughtStageDefs, fo a better code structure-->
<stageCounts>
<li>30</li>
<li>50</li>
</stageCounts>
<storyOffset>30</storyOffset><!--Special stories count as this much experience to grow accustomed-->
<stages>
<li>
<label>Resentful whore</label>
<description>We just need money! I'm not a ...</description>
<baseMoodEffect>-5</baseMoodEffect>
</li>
<li>
<label>Whore</label>
<description>Well, at least it pays well...</description>
<baseMoodEffect>-1</baseMoodEffect>
</li>
<li>
<label>WHORE</label>
<description>This job isn't so bad afterall!</description>
<baseMoodEffect>2</baseMoodEffect>
</li>
</stages>
</rjwwhoring.ThoughtDef_Whore>
<rjwwhoring.ThoughtDef_Whore>
<defName>Whorish_Thoughts_Captive</defName>
<thoughtClass>rjwwhoring.ThoughtWorker_Whore</thoughtClass>
<durationDays>4.0</durationDays>
<stackLimit>10</stackLimit>
<stackedEffectMultiplier>0.4</stackedEffectMultiplier>
<!--Numbers of whoring times needed to pass to next stage, one less than the stages count-->
<!--I couldn't find a way to move them to thoughtStageDefs, for a better code structure-->
<stageCounts>
<li>30</li>
<li>40</li>
<li>80</li>
</stageCounts>
<storyOffset>10</storyOffset>
<!--Special stories count as this much experience to grow accustomed-->
<stages>
<li>
<label>Forced whore</label>
<description>They forced me to serve as a sex toy!</description>
<baseMoodEffect>-20</baseMoodEffect>
</li>
<li>
<label>Unwilling whore</label>
<description>I'm not just a rental ride!</description>
<baseMoodEffect>-10</baseMoodEffect>
</li>
<li>
<label>Accustomed whore</label>
<description>Just no beatings, please.</description>
<baseMoodEffect>-1</baseMoodEffect>
</li>
<li>
<label>Complete whore</label>
<description>I could get more clients if not these restraints!</description>
<baseMoodEffect>2</baseMoodEffect>
</li>
</stages>
</rjwwhoring.ThoughtDef_Whore>
<!--Used to keep track of customers, so a whore doesn't repeatedly solicit the same guest-->
<ThoughtDef>
<defName>RJWFailedSolicitation</defName>
<thoughtClass>Thought_MemorySocial</thoughtClass>
<durationDays>0.4</durationDays>
<stackLimit>100</stackLimit>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stackedEffectMultiplier>0.5</stackedEffectMultiplier>
<stages>
<li>
<label>tried to solicit me</label>
<baseOpinionOffset>-1</baseOpinionOffset>
</li>
</stages>
</ThoughtDef>
<!--Same as above, but for colonists-->
<ThoughtDef>
<defName>RJWTurnedDownWhore</defName>
<thoughtClass>Thought_MemorySocial</thoughtClass>
<durationDays>0.2</durationDays>
<stackLimit>1</stackLimit>
<stackLimitForSameOtherPawn>1</stackLimitForSameOtherPawn>
<stackedEffectMultiplier>0.5</stackedEffectMultiplier>
<stages>
<li>
<label>bothered me</label>
<baseOpinionOffset>-1</baseOpinionOffset>
</li>
</stages>
</ThoughtDef>
<ThoughtDef>
<defName>SleptInBrothel</defName>
<durationDays>1</durationDays>
<stackedEffectMultiplier>1</stackedEffectMultiplier>
<stackLimit>1</stackLimit>
<stages>
<li>
<label>slept in brothel</label>
<description>Eww, the sheets were all sticky.</description>
<baseMoodEffect>-10</baseMoodEffect>
</li>
<li>
<label>slept in brothel!</label>
<description>I just love this place, the smell, the sounds...</description>
<baseMoodEffect>2</baseMoodEffect>
</li>
</stages>
</ThoughtDef>
</Defs>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<TipSetDef>
<defName>RjwWhoringTips</defName>
<tips>
<!-- UI -->
<li>Whoring price is visible in the "show sexuality" menu on the bio tab (the icon looks like a heart).</li>
<!-- Mechanics -->
<li>Condoms can be automatically used if placed in a stockpile next to a bed - it might be a good idea to have some around if you intend on whoring your colonists out.</li>
</tips>
</TipSetDef>
</Defs>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<!--Provide bonus exp/less penalties for whores-->
<rjwwhoring.StringListDef>
<defName>WhoreBackstories</defName>
<strings>
<!--Story Short names-->
<!--Only adult stories are checked-->
<!--Due to some genius(that is not compliment) encrypting backstories in the game executable, there is no reliable way to determine them-->
<!--Will have to do string match along the names, this can have unexpected results-->
<li>Sex slave</li>
<li>Courtesean</li>
<li>Housemate</li><!--filthy MILF whores-->
<li>Model</li><!--in fashion business they are either whores or gays... or both-->
<li>idol</li><!--same as above-->
<!--rjw own-->
<li>Nymph</li>
<!--ChJees Androids-->
<li>Pleasure</li>
<li>Courtesan</li>
<!--Orassan-->
<li>Dancer</li>
<li>Holo-star</li>
<!--Nihal-->
<li>Prostitute</li>
<li>Breeder</li>
</strings>
</rjwwhoring.StringListDef>
</Defs>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<MainButtonDef>
<defName>RJW_Brothel</defName>
<label>Brothel</label>
<description>List whores in the area.</description>
<tabWindowClass>rjwwhoring.MainTab.MainTabWindow_Brothel</tabWindowClass>
<order>53</order>
<minimized>True</minimized>
<iconPath>UI/Commands/Service_off</iconPath>
</MainButtonDef>
</Defs>

View file

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<Defs>
<PawnColumnDef>
<defName>RJW_IsWhore</defName>
<headerTip>Whores</headerTip>
<headerIcon>UI/Tab/Service_off</headerIcon>
<workerClass>rjwwhoring.MainTab.PawnColumnWorker_IsWhore</workerClass>
<sortable>true</sortable>
<width>80</width>
</PawnColumnDef>
<PawnColumnDef>
<defName>RJW_IsPrisoner</defName>
<headerTip>Prisoners</headerTip>
<headerIcon>UI/Tab/ComfortPrisoner_off</headerIcon>
<workerClass>rjwwhoring.MainTab.PawnColumnWorker_IsPrisoner</workerClass>
<sortable>true</sortable>
<width>80</width>
</PawnColumnDef>
<PawnColumnDef>
<defName>RJW_IsSlave</defName>
<headerTip>Slaves</headerTip>
<headerIcon>UI/Tab/ComfortPrisoner_off</headerIcon>
<workerClass>rjwwhoring.MainTab.PawnColumnWorker_IsSlave</workerClass>
<sortable>true</sortable>
<width>80</width>
</PawnColumnDef>
<PawnColumnDef>
<defName>RJW_WhoreExperience</defName>
<headerTip>Whoring experience</headerTip>
<label>Experience</label>
<workerClass>rjwwhoring.MainTab.PawnColumnWorker_WhoreExperience</workerClass>
<sortable>true</sortable>
<width>100</width>
</PawnColumnDef>
<PawnColumnDef>
<defName>RJW_PriceRangeOfWhore</defName>
<headerTip>Price range for whore</headerTip>
<label>Price</label>
<workerClass>rjwwhoring.MainTab.PawnColumnWorker_PriceRangeOfWhore</workerClass>
<sortable>true</sortable>
<width>100</width>
</PawnColumnDef>
<PawnColumnDef>
<defName>RJW_EarnedMoneyByWhore</defName>
<headerTip>Money earned(total)</headerTip>
<label>Earned</label>
<workerClass>rjwwhoring.MainTab.PawnColumnWorker_EarnedMoneyByWhore</workerClass>
<sortable>true</sortable>
<width>100</width>
</PawnColumnDef>
<PawnColumnDef>
<defName>RJW_CountOfWhore</defName>
<headerTip>Clients served</headerTip>
<label>Clients</label>
<workerClass>rjwwhoring.MainTab.PawnColumnWorker_CountOfWhore</workerClass>
<sortable>true</sortable>
<width>100</width>
</PawnColumnDef>
<PawnColumnDef>
<defName>RJW_AverageMoneyByWhore</defName>
<headerTip>Money earned(average)</headerTip>
<label>Average</label>
<workerClass>rjwwhoring.MainTab.PawnColumnWorker_AverageMoneyByWhore</workerClass>
<sortable>true</sortable>
<width>100</width>
</PawnColumnDef>
<PawnColumnDef>
<defName>RJW_WhoreMood</defName>
<headerTip>Mood of pawn</headerTip>
<label>Mood</label>
<workerClass>rjwwhoring.MainTab.PawnColumnWorker_Mood</workerClass>
<sortable>true</sortable>
<width>100</width>
</PawnColumnDef>
</Defs>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<Defs>
<PawnTableDef>
<defName>RJW_Brothel</defName>
<workerClass>rjwwhoring.MainTab.PawnTable_Whores</workerClass>
<columns>
<li>Label</li>
<li>RJW_IsWhore</li>
<li>RJW_IsPrisoner</li>
<li>RJW_IsSlave</li>
<li>RJW_WhoreMood</li>
<li>GapTiny</li>
<li>RJW_CountOfWhore</li>
<li>RJW_WhoreExperience</li>
<li>RJW_EarnedMoneyByWhore</li>
<li>RJW_AverageMoneyByWhore</li>
<li>GapTiny</li>
<li>RJW_PriceRangeOfWhore</li>
<li>RemainingSpace</li>
</columns>
</PawnTableDef>
</Defs>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LanguageData>
<whoringtab_enabled>Enable whoring tab</whoringtab_enabled>
<whoringtab_enabled_desc>Shows/hide whoring tab</whoringtab_enabled_desc>
<show_whore_price_factor_on_bed>Show whore price factor on beds</show_whore_price_factor_on_bed>
<show_whore_price_factor_on_bed_desc>Show whore price factor as a label on beds that are enabled for whoring.</show_whore_price_factor_on_bed_desc>
<MoneyPrinting>Money Printing</MoneyPrinting>
<MoneyPrinting_desc>Clients will spawn silver instead of using their own/caravan</MoneyPrinting_desc>
<ClientAlwaysAccept>Client RNG disable</ClientAlwaysAccept>
<ClientAlwaysAccept_desc>Instead of doing rng roll, Clients always accept solicitation.</ClientAlwaysAccept_desc>
<DebugWhoring>Debug log whoring info</DebugWhoring>
<DebugWhoring_desc>Enables some very spamming debug logs to help find bugs in Whoring solicitation operations.</DebugWhoring_desc>
<!--Whore Activities-->
<RJW_VisitorAcceptWhore>{0} accepted the deal {1} offered.</RJW_VisitorAcceptWhore>
<RJW_VisitorSolicitWhore>{0} wants to be serviced by {1}.</RJW_VisitorSolicitWhore>
<RJW_VisitorRejectWhore>{0} rejected the deal {1} offered.</RJW_VisitorRejectWhore>
<RJW_VisitorSickWhore>{0} rejected the offer because {1} does not look healthy</RJW_VisitorSickWhore>
<!--Whore designators-->
<ForService>Assign to whorin'</ForService> <!-- This field is not visible -->
<ForServiceDesc><![CDATA[Mark for whoring
- Will try to hook up with visitors.
- Entertains other colonists if not busy.
- Tries to refrain from masturbation while on the job, may cause frustration.
]]></ForServiceDesc>
<ForServiceRefuseDesc>Won't agree to be a whore</ForServiceRefuseDesc>
<!--Bed designators-->
<CommandBedSetAsWhoreBedLabel>Allow everyone to whore</CommandBedSetAsWhoreBedLabel>
<CommandBedSetAsWhoreBedDesc>Allow all whores to use this bed to entertain customers.</CommandBedSetAsWhoreBedDesc>
<CommandBedAllowWhoringLabel>Allow owner to whore</CommandBedAllowWhoringLabel>
<CommandBedAllowWhoringDesc>Whether owner(s) are allowed to use this bed for whoring.</CommandBedAllowWhoringDesc>
<WhorePriceCalcDesc>Whoring price factor (based on comfort, room impressiveness and number of beds in room: {0}</WhorePriceCalcDesc>
<WhorePrice>Whoring price range: </WhorePrice>
</LanguageData>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<Patch>
<Operation Class="PatchOperationFindMod">
<mods>
<li>[NL] Facial Animation - WIP</li>
</mods>
<match Class="PatchOperationSequence">
<success>Always</success>
<operations>
<li Class="PatchOperationAdd">
<xpath>/Defs/FacialAnimation.FaceAnimationDef[defName="Lovin" or defName="Lovin2"]/targetJobs</xpath>
<success>Always</success>
<value>
<li>WhoreIsServingVisitors</li>
</value>
</li>
<li Class="PatchOperationAdd">
<xpath>/Defs/FacialAnimation.FaceAnimationDef[defName="StandAndBeSociallyActive"]/targetJobs</xpath>
<success>Always</success>
<value>
<li>WhoreInvitingVisitors</li>
</value>
</li>
</operations>
</match>
</Operation>
</Patch>

View file

@ -0,0 +1,39 @@
using System;
using Verse;
using System.Linq;
using RimWorld;
namespace rjwwhoring
{
public class BedData : IExposable
{
public Building_Bed bed = null;
public bool allowedForWhoringOwner = true;
public bool allowedForWhoringAll = false;
public int reservedUntilGameTick = 0;
public int reservedForPawnID = 0;
public int lastScoreUpdateTick = -70; // GenTicks.TicksGame
public float bedScore = -1f;
public int scoreUpdateTickDelay = 60;
public float roomScore = -1f;
public BedData() { }
public BedData(Building_Bed bed)
{
this.bed = bed;
}
public void ExposeData()
{
Scribe_References.Look(ref bed, "Bed");
Scribe_Values.Look(ref allowedForWhoringOwner, "allowedForWhoringOwner", true, true);
Scribe_Values.Look(ref allowedForWhoringAll, "allowedForWhoringAll", false, true);
Scribe_Values.Look(ref reservedUntilGameTick, "lastUsed", 0, true);
Scribe_Values.Look(ref reservedForPawnID, "lastUsedBy", 0, true);
}
public bool IsValid { get { return bed != null; } }
}
}

View file

@ -0,0 +1,50 @@
using System.Collections.Generic;
using Verse;
using RimWorld;
using RimWorld.Planet;
namespace rjwwhoring
{
/// <summary>
/// Rimworld object for storing the world/save info
/// </summary>
public class DataStore : WorldComponent
{
public Dictionary<int, BedData> bedData = new Dictionary<int, BedData>();
public DataStore(World world) : base(world)
{
}
public override void ExposeData()
{
if (Scribe.mode == LoadSaveMode.Saving)
{
bedData.RemoveAll(item => item.Value == null || !item.Value.IsValid);
}
base.ExposeData();
Scribe_Collections.Look(ref bedData, "BedData", LookMode.Value, LookMode.Deep);
if (Scribe.mode == LoadSaveMode.LoadingVars)
{
if (bedData == null) bedData = new Dictionary<int, BedData>();
}
}
public BedData GetBedData(Building_Bed bed)
{
BedData res;
var filled = bedData.TryGetValue(bed.thingIDNumber, out res);
if ((res == null) || (!res.IsValid))
{
if (filled)
{
bedData.Remove(bed.thingIDNumber);
}
res = new BedData(bed);
bedData.Add(bed.thingIDNumber, res);
}
return res;
}
}
}

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
using Verse;
namespace rjwwhoring
{
/// <summary>
/// Whore backstories from xml GET!
/// Just a simplest form of passing data from xml to the code
/// </summary>
public class StringListDef : Def
{
public List<string> strings = new List<string>();
}
}

View file

@ -0,0 +1,12 @@
using RimWorld;
using Verse;
namespace rjwwhoring
{
[DefOf]
public static class RecordDefOf
{
public static RecordDef EarnedMoneyByWhore;
public static RecordDef CountOfWhore;
}
}

View file

@ -0,0 +1,19 @@
using RimWorld;
using Verse;
namespace rjwwhoring
{
[DefOf]
public static class ThoughtDefOf
{
public static ThoughtDef_Whore Whorish_Thoughts;
public static ThoughtDef_Whore Whorish_Thoughts_Captive;
public static ThoughtDef SleptInBrothel;
public static ThoughtDef RJWFailedSolicitation;
public static ThoughtDef RJWTurnedDownWhore;
}
}

View file

@ -0,0 +1,172 @@
using System.Collections.Generic;
using RimWorld;
using rjw;
using Verse;
using Verse.AI;
namespace rjwwhoring
{
public class JobDriver_WhoreInvitingVisitors : JobDriver
{
// List of jobs that can be interrupted by whores.
public static readonly List<JobDef> allowedJobs = new List<JobDef> { null, JobDefOf.Wait_Wander, JobDefOf.GotoWander, JobDefOf.Clean, JobDefOf.ClearSnow,
JobDefOf.CutPlant, JobDefOf.HaulToCell, JobDefOf.Deconstruct, JobDefOf.Harvest, JobDefOf.LayDown, JobDefOf.Research, JobDefOf.SmoothFloor, JobDefOf.SmoothWall,
JobDefOf.SocialRelax, JobDefOf.StandAndBeSociallyActive, JobDefOf.RemoveApparel, JobDefOf.Strip, JobDefOf.Tame, JobDefOf.Wait, JobDefOf.Wear, JobDefOf.FixBrokenDownBuilding,
JobDefOf.FillFermentingBarrel, JobDefOf.DoBill, JobDefOf.Sow, JobDefOf.Shear, JobDefOf.BuildRoof, JobDefOf.DeliverFood, JobDefOf.HaulToContainer, JobDefOf.Hunt, JobDefOf.Mine,
JobDefOf.OperateDeepDrill, JobDefOf.OperateScanner, JobDefOf.RearmTurret, JobDefOf.Refuel, JobDefOf.RefuelAtomic, JobDefOf.RemoveFloor, JobDefOf.RemoveRoof, JobDefOf.Repair,
JobDefOf.TakeBeerOutOfFermentingBarrel, JobDefOf.Train, JobDefOf.Uninstall, xxx.Masturbate};
public bool successfulPass = true;
private Pawn Whore => GetActor();
private Pawn TargetPawn => TargetThingA as Pawn;
private Building_Bed TargetBed => TargetThingB as Building_Bed;
private readonly TargetIndex TargetPawnIndex = TargetIndex.A;
private readonly TargetIndex TargetBedIndex = TargetIndex.B;
private bool DoesTargetPawnAcceptAdvance()
{
if (WhoringBase.DebugWhoring) ModLog.Message($"JobDriver_InvitingVisitors::DoesTargetPawnAcceptAdvance() - {xxx.get_pawnname(TargetPawn)}");
//if (RJWSettings.WildMode) return true;
if (PawnUtility.EnemiesAreNearby(TargetPawn))
{
if (WhoringBase.DebugWhoring) ModLog.Message($" fail - enemy near");
return false;
}
if (!allowedJobs.Contains(TargetPawn.jobs.curJob.def))
{
if (WhoringBase.DebugWhoring) ModLog.Message($" fail - not allowed job");
return false;
}
if (WhoringBase.DebugWhoring)
{
ModLog.Message("Will try hookup " + WhoringHelper.WillPawnTryHookup(TargetPawn));
ModLog.Message("Is whore appealing " + WhoringHelper.IsHookupAppealing(TargetPawn, Whore));
ModLog.Message("Can afford whore " + WhoringHelper.CanAfford(TargetPawn, Whore));
ModLog.Message("Need sex " + (xxx.need_some_sex(TargetPawn) >= 1));
}
if (WhoringHelper.WillPawnTryHookup(TargetPawn) && WhoringHelper.IsHookupAppealing(TargetPawn, Whore) && WhoringHelper.CanAfford(TargetPawn, Whore) && xxx.need_some_sex(TargetPawn) >= 1f)
{
if (!Whore.IsPrisoner)
Whore.skills.Learn(SkillDefOf.Social, 1.2f);
return true;
}
return false;
}
public override bool TryMakePreToilReservations(bool errorOnFailed) => true;
protected override IEnumerable<Toil> MakeNewToils()
{
this.FailOnDespawnedOrNull(TargetPawnIndex);
this.FailOnDespawnedNullOrForbidden(TargetBedIndex);
this.FailOn(() => Whore is null || !WhoreBed_Utility.CanUseForWhoring(Whore, TargetBed));//|| !Whore.CanReserve(TargetPawn)
this.FailOn(() => pawn.Drafted);
if (!Whore.IsPrisoner)
{
yield return Toils_Goto.GotoThing(TargetPawnIndex, PathEndMode.Touch);
Toil TryItOn = new Toil();
TryItOn.AddFailCondition(() => !xxx.IsTargetPawnOkay(TargetPawn));
TryItOn.defaultCompleteMode = ToilCompleteMode.Delay;
TryItOn.initAction = delegate
{
//ModLog.Message("JobDriver_InvitingVisitors::MakeNewToils - TryItOn - initAction is called");
Whore.jobs.curDriver.ticksLeftThisToil = 50;
FleckMaker.ThrowMetaIcon(Whore.Position, Whore.Map, FleckDefOf.Heart);
};
yield return TryItOn;
}
Toil AwaitResponse = new Toil();
AwaitResponse.defaultCompleteMode = ToilCompleteMode.Delay;
AwaitResponse.defaultDuration = 10;
AwaitResponse.initAction = delegate
{
List<RulePackDef> extraSentencePacks = new List<RulePackDef>();
successfulPass = DoesTargetPawnAcceptAdvance();
//ModLog.Message("JobDriver_InvitingVisitors::MakeNewToils - AwaitResponse - initAction is called");
if (successfulPass)
{
FleckMaker.ThrowMetaIcon(TargetPawn.Position, TargetPawn.Map, FleckDefOf.Heart);
TargetPawn.jobs.EndCurrentJob(JobCondition.Incompletable);
if (xxx.RomanceDiversifiedIsActive)
{
extraSentencePacks.Add(RulePackDef.Named("HookupSucceeded"));
}
if (Whore.health.HasHediffsNeedingTend())
{
successfulPass = false;
string key = "RJW_VisitorSickWhore";
string text = key.Translate(TargetPawn.LabelIndefinite(), Whore.LabelIndefinite()).CapitalizeFirst();
Messages.Message(text, Whore, MessageTypeDefOf.TaskCompletion);
}
else
{
string key = "RJW_VisitorAcceptWhore";
if (Whore.IsPrisoner)
{
key = "RJW_VisitorSolicitWhore";
}
string text = key.Translate(TargetPawn.LabelIndefinite(), Whore.LabelIndefinite()).CapitalizeFirst();
Messages.Message(text, TargetPawn, MessageTypeDefOf.TaskCompletion);
}
}
if (!successfulPass)
{
// remove bed reservation
TargetBed.UnreserveForWhoring();
FleckMaker.ThrowMetaIcon(TargetPawn.Position, TargetPawn.Map, FleckDefOf.IncapIcon);
TargetPawn.needs.mood.thoughts.memories.TryGainMemory(
TargetPawn.Faction == Whore.Faction
? ThoughtDef.Named("RJWTurnedDownWhore")
: ThoughtDef.Named("RJWFailedSolicitation"), Whore);
if (xxx.RomanceDiversifiedIsActive)
{
Whore.needs.mood.thoughts.memories.TryGainMemory(ThoughtDef.Named("RebuffedMyHookupAttempt"), TargetPawn);
TargetPawn.needs.mood.thoughts.memories.TryGainMemory(ThoughtDef.Named("FailedHookupAttemptOnMe"), Whore);
extraSentencePacks.Add(RulePackDef.Named("HookupFailed"));
}
//Disabled rejection notifications
//Messages.Message("RJW_VisitorRejectWhore".Translate(new object[] { xxx.get_pawnname(TargetPawn), xxx.get_pawnname(Whore) }), TargetPawn, MessageTypeDefOf.SilentInput);
}
if (xxx.RomanceDiversifiedIsActive)
{
Find.PlayLog.Add(new PlayLogEntry_Interaction(DefDatabase<InteractionDef>.GetNamed("TriedHookupWith"), pawn, TargetPawn, extraSentencePacks));
}
//Log.Message("[RJW] AwaitResponse initAction - successfulPass: " + successfulPass.ToString());
};
yield return AwaitResponse;
Toil BothGoToBed = new Toil();
BothGoToBed.AddFailCondition(() => !successfulPass || !WhoreBed_Utility.CanUseForWhoring(Whore, TargetBed));
BothGoToBed.defaultCompleteMode = ToilCompleteMode.Instant;
BothGoToBed.initAction = delegate
{
//ModLog.Message("JobDriver_InvitingVisitors::MakeNewToils - BothGoToBed - initAction is called0");
if (!successfulPass) return;
if (!WhoreBed_Utility.CanUseForWhoring(Whore, TargetBed) && Whore.CanReserve(TargetPawn, 1, 0))
{
//ModLog.Message("JobDriver_InvitingVisitors::MakeNewToils - BothGoToBed - cannot use the whore bed");
return;
}
//ModLog.Message("JobDriver_InvitingVisitors::MakeNewToils - BothGoToBed - initAction is called1");
TargetBed.ReserveForWhoring(Whore, 1800);//is 1800 ticks long enough to go to the bed (until next reservation is made?)
Whore.jobs.jobQueue.EnqueueFirst(JobMaker.MakeJob(xxx.whore_is_serving_visitors, TargetPawn, TargetBed));
//TargetPawn.jobs.jobQueue.EnqueueFirst(JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("WhoreIsServingVisitors"), Whore, TargetBed, (TargetBed.MaxAssignedPawnsCount>1)?TargetBed.GetSleepingSlotPos(1): TargetBed.)), null);
Whore.jobs.curDriver.EndJobWith(JobCondition.InterruptOptional);
//TargetPawn.jobs.curDriver.EndJobWith(JobCondition.InterruptOptional);
};
yield return BothGoToBed;
}
}
}

View file

@ -0,0 +1,181 @@
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
//using Multiplayer.API;
using rjw;
namespace rjwwhoring
{
public class JobDriver_WhoreIsServingVisitors : JobDriver_SexBaseInitiator
{
public IntVec3 SleepSpot => Bed.SleepPosOfAssignedPawn(pawn);
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
return pawn.Reserve(Target, job, 1, 0, null, errorOnFailed);
}
//[SyncMethod]
protected override IEnumerable<Toil> MakeNewToils()
{
if (WhoringBase.DebugWhoring) ModLog.Message("" + this.GetType().ToString() + ":MakeNewToils() - making toils");
setup_ticks();
var PartnerJob = xxx.gettin_loved;
this.FailOnDespawnedOrNull(iTarget);
this.FailOnDespawnedNullOrForbidden(iBed);
if (WhoringBase.DebugWhoring) ModLog.Message("" + this.GetType().ToString() + ":MakeNewToils() fail conditions check " + !WhoreBed_Utility.CanUseForWhoring(pawn, Bed) + " " + !pawn.CanReserve(Partner));
this.FailOn(() => !WhoreBed_Utility.CanUseForWhoring(pawn, Bed) || !pawn.CanReserve(Partner));
this.FailOn(() => pawn.Drafted);
this.FailOn(() => Partner.IsFighting());
yield return Toils_Reserve.Reserve(iTarget, 1, 0);
//yield return Toils_Reserve.Reserve(BedInd, Bed.SleepingSlotsCount, 0);
if (WhoringBase.DebugWhoring) ModLog.Message("" + this.GetType().ToString() + ":MakeNewToils() - generate toils");
Toil gotoBed = new Toil();
gotoBed.defaultCompleteMode = ToilCompleteMode.PatherArrival;
gotoBed.FailOnBedNoLongerUsable(iBed, Bed);
gotoBed.AddFailCondition(() => Partner.Downed);
gotoBed.FailOn(() => !Partner.CanReach(Bed, PathEndMode.Touch, Danger.Deadly));
gotoBed.initAction = delegate
{
if (WhoringBase.DebugWhoring) ModLog.Message("" + this.GetType().ToString() + ":MakeNewToils() - gotoWhoreBed");
pawn.pather.StartPath(SleepSpot, PathEndMode.OnCell);
Partner.jobs.StopAll();
Job job = JobMaker.MakeJob(JobDefOf.GotoMindControlled, SleepSpot);
Partner.jobs.StartJob(job, JobCondition.InterruptForced);
};
yield return gotoBed;
ticks_left = (int)(2000.0f * Rand.Range(0.30f, 1.30f));
Toil waitInBed = new Toil();
waitInBed.initAction = delegate
{
ticksLeftThisToil = 5000;
};
waitInBed.tickAction = delegate
{
pawn.GainComfortFromCellIfPossible();
if (IsInOrByBed(Bed, Partner) && pawn.PositionHeld == Partner.PositionHeld)
{
ReadyForNextToil();
}
};
waitInBed.defaultCompleteMode = ToilCompleteMode.Delay;
yield return waitInBed;
Toil StartPartnerJob = new Toil();
StartPartnerJob.defaultCompleteMode = ToilCompleteMode.Instant;
StartPartnerJob.socialMode = RandomSocialMode.Off;
StartPartnerJob.initAction = delegate
{
if (WhoringBase.DebugWhoring) ModLog.Message("" + this.GetType().ToString() + ":MakeNewToils() - StartPartnerJob");
var gettin_loved = JobMaker.MakeJob(PartnerJob, pawn, Bed);
Partner.jobs.StartJob(gettin_loved, JobCondition.InterruptForced);
};
yield return StartPartnerJob;
Toil SexToil = new Toil();
SexToil.defaultCompleteMode = ToilCompleteMode.Never;
SexToil.socialMode = RandomSocialMode.Off;
SexToil.handlingFacing = true;
SexToil.FailOn(() => Partner.Dead);
SexToil.FailOn(() => Partner.CurJob.def != PartnerJob);
SexToil.initAction = delegate
{
if (WhoringBase.DebugWhoring) ModLog.Message("" + this.GetType().ToString() + ":MakeNewToils() - loveToil");
// refresh bed reservation
Bed.ReserveForWhoring(pawn, ticks_left+100);
Start();
// TODO: replace this quick n dirty way
CondomUtility.GetCondomFromRoom(pawn);
// Try to use whore's condom first, then client's
Sexprops.usedCondom = CondomUtility.TryUseCondom(pawn) || CondomUtility.TryUseCondom(Partner);
if (!RJWSettings.HippieMode && xxx.HasNonPolyPartner(Partner, true))
{
Pawn lover = LovePartnerRelationUtility.ExistingLovePartner(Partner);
// We have to do a few other checks because the pawn might have multiple lovers and ExistingLovePartner() might return the wrong one
if (lover != null && pawn != lover && !lover.Dead && (lover.Map == Partner.Map || Rand.Value < 0.25) && GenSight.LineOfSight(lover.Position, Partner.Position, lover.Map))
{
lover.needs.mood.thoughts.memories.TryGainMemory(RimWorld.ThoughtDefOf.CheatedOnMe, Partner);
}
}
};
SexToil.AddPreTickAction(delegate
{
if (pawn.IsHashIntervalTick(ticks_between_hearts))
if (xxx.is_nympho(pawn))
FleckMaker.ThrowMetaIcon(pawn.Position, pawn.Map, FleckDefOf.Heart);
else
FleckMaker.ThrowMetaIcon(pawn.Position, pawn.Map, xxx.mote_noheart);
SexTick(pawn, Partner);
SexUtility.reduce_rest(Partner, 1);
SexUtility.reduce_rest(pawn, 2);
if (ticks_left % 100 == 0)
Bed.ReserveForWhoring(pawn, ticks_left + 100); // without this, reservation sometimes expires before sex is finished
if (ticks_left <= 0)
ReadyForNextToil();
});
SexToil.AddFinishAction(delegate
{
End();
});
yield return SexToil;
Toil afterSex = new Toil
{
initAction = delegate
{
// Adding interactions, social logs, etc
SexUtility.ProcessSex(Sexprops);
Bed.UnreserveForWhoring();
if (!(Partner.IsColonist && (pawn.IsPrisonerOfColony || pawn.IsColonist)))
{
int basePrice = WhoringHelper.PriceOfWhore(pawn);
float bedMult = WhoreBed_Utility.CalculatePriceFactor(Bed);
int netPrice = (int) (basePrice * bedMult);
int bedTip = netPrice - basePrice;
int defect = WhoringHelper.PayPriceToWhore(Partner, netPrice, pawn);
if (WhoringBase.DebugWhoring)
{
ModLog.Message($"{GetType()}:MakeNewToild() - {Partner} tried to pay {basePrice} + {bedTip} silver to {pawn}");
if (defect <= 0)
ModLog.Message(" Paid full price");
else if (defect <= bedTip)
ModLog.Message(" Could not pay full tip");
else
ModLog.Message(" Failed to pay base price");
}
WhoringHelper.UpdateRecords(pawn, netPrice - defect);
}
if (SexUtility.ConsiderCleaning(pawn))
{
LocalTargetInfo cum = pawn.PositionHeld.GetFirstThing<Filth>(pawn.Map);
Job clean = JobMaker.MakeJob(JobDefOf.Clean);
clean.AddQueuedTarget(TargetIndex.A, cum);
pawn.jobs.jobQueue.EnqueueFirst(clean);
}
},
defaultCompleteMode = ToilCompleteMode.Instant
};
yield return afterSex;
}
}
}

View file

@ -0,0 +1,260 @@
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using rjw;
using Verse;
using Verse.AI;
//using Multiplayer.API;
namespace rjwwhoring
{
public class JobGiver_WhoreInvitingVisitors : ThinkNode_JobGiver
{
// Checks if pawn has a memory.
// Maybe not the best place for function, might be useful elsewhere too.
public static bool MemoryChecker(Pawn pawn, ThoughtDef thought)
{
Thought_Memory val = pawn.needs.mood.thoughts.memories.Memories.Find((Thought_Memory x) => (object)x.def == thought);
return val == null ? false : true;
}
//[SyncMethod]
private static bool Roll_to_skip(Pawn client, Pawn whore)
{
//Rand.PopState();
//Rand.PushState(RJW_Multiplayer.PredictableSeed());
float fuckability = SexAppraiser.would_fuck(client, whore); // 0.0 to 1.
// More likely to skip other whores, because they're supposed to be working.
if (client.IsDesignatedService())
fuckability *= 0.6f;
if (WhoringBase.ClientAlwaysAccept)
{
return fuckability >= 0.1f && xxx.need_some_sex(client) >= 1f;
}
else
return fuckability >= 0.1f && xxx.need_some_sex(client) >= 1f && Rand.Chance(0.5f);
}
/*
public static Pawn Find_pawn_to_fuck(Pawn whore, Map map)
{
Pawn best_fuckee = null;
float best_distance = 1.0e6f;
foreach (Pawn q in map.mapPawns.FreeColonists)
if ((q != whore) &&
xxx.need_some_sex(q)>0 &&
whore.CanReserve(q, 1, 0) &&
q.CanReserve(whore, 1, 0) &&
Roll_to_skip(whore, q) &&
(!q.Position.IsForbidden(whore)) &&
xxx.is_healthy(q))
{
var dis = whore.Position.DistanceToSquared(q.Position);
if (dis < best_distance)
{
best_fuckee = q;
best_distance = dis;
}
}
return best_fuckee;
}
*/
private sealed class FindAttractivePawnHelper
{
internal Pawn whore;
internal bool TraitCheckFail(Pawn client)
{
if (!xxx.is_human(client))
return true;
if (!xxx.has_traits(client))
return true;
if (!(xxx.can_fuck(client) || xxx.can_be_fucked(client)) || !xxx.IsTargetPawnOkay(client))
return true;
//Log.Message("client:" + client + " whore:" + whore);
if (CompRJW.CheckPreference(client, whore) == false)
return true;
return false; // Everything ok.
}
//Use this check when client is not in the same faction as the whore
internal bool FactionCheckPass(Pawn client)
{
return ((client.Map == whore.Map) && (client.Faction != null && client.Faction != whore.Faction) && !client.IsPrisoner && !xxx.is_slave(client) && !client.HostileTo(whore));
}
//Use this check when client is in the same faction as the whore
//[SyncMethod]
internal bool RelationCheckPass(Pawn client)
{
//Rand.PopState();
//Rand.PushState(RJW_Multiplayer.PredictableSeed());
if (xxx.isSingleOrPartnerNotHere(client) || xxx.is_lecher(client) || Rand.Value < 0.9f)
{
if (client != LovePartnerRelationUtility.ExistingLovePartner(whore))
{ //Exception for prisoners to account for PrisonerWhoreSexualEmergencyTree, which allows prisoners to try to hook up with anyone who's around (mostly other prisoners or warden)
return (client != whore) & (client.Map == whore.Map) && (client.Faction == whore.Faction || whore.IsPrisoner) && (client.IsColonist || whore.IsPrisoner) && WhoringHelper.IsHookupAppealing(whore, client);
}
}
return false;
}
}
//[SyncMethod]
public static Pawn FindAttractivePawn(Pawn whore, out int price)
{
price = 0;
if (whore == null || xxx.is_asexual(whore))
{
if (WhoringBase.DebugWhoring) ModLog.Message($" {xxx.get_pawnname(whore)} is asexual, abort");
return null;
}
//Rand.PopState();
//Rand.PushState(RJW_Multiplayer.PredictableSeed());
FindAttractivePawnHelper client = new FindAttractivePawnHelper
{
whore = whore
};
price = WhoringHelper.PriceOfWhore(whore);
int priceOfWhore = price;
IntVec3 pos = whore.Position;
IEnumerable<Pawn> potentialClients = whore.Map.mapPawns.AllPawnsSpawned;
potentialClients = potentialClients.Where(x
=> x != whore
&& !x.IsForbidden(whore)
&& !x.HostileTo(whore)
&& !x.IsPrisoner
&& !xxx.is_slave(x)
&& !x.IsColonist
&& x.Position.DistanceTo(pos) < 100
&& xxx.is_healthy_enough(x));
potentialClients = potentialClients.Except(potentialClients.Where(client.TraitCheckFail));
if (!whore.IsPrisoner)
potentialClients = potentialClients.Except(potentialClients.Where(x => !whore.CanReserveAndReach(x, PathEndMode.ClosestTouch, Danger.Some, 1)));
else
potentialClients = potentialClients.Except(potentialClients.Where(x => !x.CanReserveAndReach(whore, PathEndMode.ClosestTouch, Danger.Some, 1)));
if (!potentialClients.Any()) return null;
if (WhoringBase.DebugWhoring) ModLog.Message($" FindAttractivePawn number of all potential clients {potentialClients.Count()}");
List<Pawn> valid_targets = new List<Pawn>();
if (!whore.IsPrisoner)
foreach (Pawn target in potentialClients)
{
if(Pather_Utility.cells_to_target_casual(whore, target.Position))
if (Pather_Utility.can_path_to_target(whore, target.Position))
valid_targets.Add(target);
}
else
foreach (Pawn target in potentialClients)
{
if (Pather_Utility.cells_to_target_casual(target, whore.Position))
if (Pather_Utility.can_path_to_target(target, whore.Position))
valid_targets.Add(target);
}
if (WhoringBase.DebugWhoring) ModLog.Message($" number of reachable clients {valid_targets.Count()}");
//IEnumerable<Pawn> guestsSpawned = valid_targets.Where(x => x.Faction != whore.Faction
// && WhoringHelper.CanAfford(x, whore, priceOfWhore));
//if (RJWSettings.DebugWhoring) ModLog.Message($" number of clients can afford {guestsSpawned.Count()}");
//guestsSpawned = valid_targets.Where(x => x.Faction != whore.Faction
// && x != LovePartnerRelationUtility.ExistingLovePartner(whore));
//if (RJWSettings.DebugWhoring) ModLog.Message($" number of relations OK {guestsSpawned.Count()}");
//guestsSpawned = valid_targets.Where(x => x.Faction != whore.Faction
// && !MemoryChecker(x, ThoughtDef.Named("RJWFailedSolicitation")));
//if (RJWSettings.DebugWhoring) ModLog.Message($" number of clients can memory OK {guestsSpawned.Count()}");
IEnumerable<Pawn> guestsSpawned = valid_targets.Where(x => x.Faction != whore.Faction
&& !MemoryChecker(x, ThoughtDef.Named("RJWFailedSolicitation"))
&& WhoringHelper.CanAfford(x, whore, priceOfWhore)
&& x != LovePartnerRelationUtility.ExistingLovePartner(whore));
if (guestsSpawned.Any())
{
if (WhoringBase.DebugWhoring) ModLog.Message($" number of all acceptable Guests {guestsSpawned.Count()}");
return guestsSpawned.RandomElement();
}
return null;
//use casual sex for colonist hooking
if (WhoringBase.DebugWhoring) ModLog.Message($" found no guests, trying colonists");
if (!WhoringHelper.WillPawnTryHookup(whore))// will hookup colonists?
{
return null;
}
IEnumerable<Pawn> freeColonists = valid_targets.Where(x => x.Faction == whore.Faction
&& Roll_to_skip(x, whore));
if (WhoringBase.DebugWhoring) ModLog.Message($" number of free colonists {freeColonists.Count()}");
freeColonists = freeColonists.Where(x => client.RelationCheckPass(x) && !MemoryChecker(x, ThoughtDef.Named("RJWTurnedDownWhore")));
if (freeColonists.Any())
{
if (WhoringBase.DebugWhoring) ModLog.Message($" number of all acceptable Colonists {freeColonists.Count()}");
return freeColonists.RandomElement();
}
return null;
}
protected override Job TryGiveJob(Pawn pawn)
{
// Most things are now checked in the ThinkNode_ConditionalWhore.
if (pawn.Drafted) return null;
//if (MP.IsInMultiplayer) return null; //fix error someday, maybe
if (pawn.jobs.curDriver is JobDriver_Sex || pawn.jobs.curDriver is JobDriver_WhoreInvitingVisitors) return null; // already having sex
if (!SexUtility.ReadyForLovin(pawn))
{
//Whores need rest too, but this'll reduxe the wait a bit every it triggers.
pawn.mindState.canLovinTick -= 40;
return null;
}
if (WhoringBase.DebugWhoring) ModLog.Message($"JobGiver_WhoreInvitingVisitors.TryGiveJob:({xxx.get_pawnname(pawn)})");
int price;
Pawn client = FindAttractivePawn(pawn, out price);
if (client == null)
{
if (WhoringBase.DebugWhoring) ModLog.Message($" no clients found");
return null;
}
if (WhoringBase.DebugWhoring) ModLog.Message($" {xxx.get_pawnname(client)} is client");
Building_Bed whorebed = WhoreBed_Utility.FindBed(pawn, client);
if (whorebed == null)
{
if (WhoringBase.DebugWhoring) ModLog.Message($" {xxx.get_pawnname(pawn)} + {xxx.get_pawnname(client)} - no usable bed found");
return null;
}
whorebed.ReserveForWhoring(pawn, 600); // reserve for a short while until whore can actually ask customer
return JobMaker.MakeJob(xxx.whore_inviting_visitors, client, whorebed);
}
}
}

View file

@ -0,0 +1,28 @@
using Verse;
using RimWorld;
namespace rjwwhoring
{
public class RoomRoleWorker_Brothel : RoomRoleWorker
{
public override float GetScore(Room room)
{
int num = 0;
var allContainedThings = room.ContainedAndAdjacentThings;
foreach (var thing in allContainedThings)
{
var building_Bed = thing as Building_Bed;
if (building_Bed?.def.building.bed_humanlike == true)
{
if (building_Bed.ForPrisoners) return 0;
if (building_Bed.IsAllowedForWhoringAll()) num++;
}
}
if (num < 1) return 0;
return num * 110001; // higher than guest beds or "regular" beds when counting barracks
}
}
}

View file

@ -0,0 +1,59 @@
namespace rjwwhoring
{
//This class is not used now.
/*
public class ThinkNode_ChancePerHour_Whore : ThinkNode_ChancePerHour
{
protected override float MtbHours(Pawn pawn)
{
// Use the fappin mtb hours as the base number b/c it already accounts for things like age
var base_mtb = xxx.config.whore_mtbh_mul * ThinkNode_ChancePerHour_Fappin.get_fappin_mtb_hours(pawn);
if (base_mtb < 0.0f)
return -1.0f;
float desire_factor;
{
var need_sex = pawn.needs.TryGetNeed<Need_Sex>();
if (need_sex != null)
{
if (need_sex.CurLevel <= need_sex.thresh_frustrated())
desire_factor = 0.15f;
else if (need_sex.CurLevel <= need_sex.thresh_horny())
desire_factor = 0.60f;
else
desire_factor = 1.00f;
}
else
desire_factor = 1.00f;
}
float personality_factor;
{
personality_factor = 1.0f;
if (pawn.story != null)
{
foreach (var trait in pawn.story.traits.allTraits)
{
if (trait.def == xxx.nymphomaniac) personality_factor *= 0.25f;
else if (trait.def == TraitDefOf.Greedy) personality_factor *= 0.50f;
else if (xxx.RomanceDiversifiedIsActive&&(trait.def==xxx.philanderer || trait.def == xxx.polyamorous)) personality_factor *= 0.75f;
else if (xxx.RomanceDiversifiedIsActive && (trait.def == xxx.faithful)&& LovePartnerRelationUtility.HasAnyLovePartner(pawn)) personality_factor *= 30f;
}
}
}
float fun_factor;
{
if ((pawn.needs.joy != null) && (xxx.is_nympho(pawn)))
fun_factor = Mathf.Clamp01(0.50f + pawn.needs.joy.CurLevel);
else
fun_factor = 1.00f;
}
var gender_factor = (pawn.gender == Gender.Male) ? 1.0f : 3.0f;
return base_mtb * desire_factor * personality_factor * fun_factor * gender_factor;
}
}
*/
}

View file

@ -0,0 +1,25 @@
using Verse;
using Verse.AI;
using RimWorld;
using rjw;
namespace rjwwhoring
{
/// <summary>
/// Whore/prisoner look for customers
/// </summary>
public class ThinkNode_ConditionalWhore : ThinkNode_Conditional
{
protected override bool Satisfied(Pawn p)
{
// No animal whorin' for now.
if (xxx.is_animal(p))
return false;
if (!InteractionUtility.CanInitiateInteraction(p))
return false;
return xxx.is_whore(p);
}
}
}

View file

@ -0,0 +1,43 @@
using RimWorld;
using Verse;
using System.Collections.Generic;
namespace rjwwhoring
{
/// <summary>
/// Extends the standard thought to add a counter for the whore stages
/// </summary>
public class ThoughtDef_Whore : ThoughtDef
{
public List<int> stageCounts = new List<int>();
public int storyOffset = 0;
}
public class ThoughtWorker_Whore : Thought_Memory
{
public static readonly HashSet<string> backstories = new HashSet<string>(DefDatabase<StringListDef>.GetNamed("WhoreBackstories").strings);
protected List<int> Stages => ((ThoughtDef_Whore) def).stageCounts;
protected int StoryOffset => ((ThoughtDef_Whore) def).storyOffset;
public override int CurStageIndex
{
get
{
int timesWhored = pawn.records.GetAsInt(RecordDefOf.CountOfWhore);
if (backstories.Contains(pawn.story?.adulthood?.titleShort))
{
timesWhored += StoryOffset;
}
if (timesWhored > Stages[Stages.Count - 1])
{
return Stages.Count - 1;
}
return Stages.FindLastIndex(v => timesWhored > v) + 1;
}
}
}
}

View file

@ -0,0 +1,114 @@
<?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>{3FC2D442-19B8-4CF9-9D35-CD13B6AC7B28}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>rjwwhoring</RootNamespace>
<AssemblyName>RimJobWorldWhoring</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\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>..\..\Assemblies\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="0Harmony, Version=2.2.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Lib.Harmony.2.2.1\lib\net472\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\..\..\..\..\RimWorldWin64_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="HugsLib">
<HintPath>..\..\..\..\..\..\..\workshop\content\294100\818773962\v1.2\Assemblies\HugsLib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="RJW">
<HintPath>..\..\..\..\rjw\1.3\Assemblies\RJW.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Runtime.InteropServices.RuntimeInformation" />
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.IMGUIModule">
<HintPath>E:\Games\SteamLibrary\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.IMGUIModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.InputLegacyModule">
<HintPath>E:\Games\SteamLibrary\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.InputModule">
<HintPath>E:\Games\SteamLibrary\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.InputModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule">
<HintPath>E:\Games\SteamLibrary\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Properties\AssemblyInfo.cs" />
<Compile Include="Data\BedData.cs" />
<Compile Include="Data\DataStore.cs" />
<Compile Include="Data\StringListDef.cs" />
<Compile Include="DefOf\RecordDefDefOf.cs" />
<Compile Include="harmony_Building_BedPatches.cs" />
<Compile Include="harmony_AftersexPatch.cs" />
<Compile Include="JobDrivers\JobDriver_WhoreInvitingVisitors.cs" />
<Compile Include="JobDrivers\JobDriver_WhoreIsServingVisitors.cs" />
<Compile Include="JobGivers\JobGiver_WhoreInvitingVisitors.cs" />
<Compile Include="Location\Brothel_Room.cs" />
<Compile Include="WhoringTab\MainTabWindow_Brothel.cs" />
<Compile Include="WhoringTab\PawnColumnCheckbox_Whore.cs" />
<Compile Include="WhoringTab\PawnColumnWorker_AverageMoneyByWhore.cs" />
<Compile Include="WhoringTab\PawnColumnWorker_CountOfWhore.cs" />
<Compile Include="WhoringTab\PawnColumnWorker_EarnedMoneyByWhore.cs" />
<Compile Include="WhoringTab\PawnColumnWorker_IsPrisoner.cs" />
<Compile Include="WhoringTab\PawnColumnWorker_IsSlave.cs" />
<Compile Include="WhoringTab\PawnColumnWorker_IsWhore.cs" />
<Compile Include="WhoringTab\PawnColumnWorker_Mood.cs" />
<Compile Include="WhoringTab\PawnColumnWorker_PriceRangeOfWhore.cs" />
<Compile Include="WhoringTab\PawnColumnWorker_TextCenter.cs" />
<Compile Include="WhoringTab\PawnColumnWorker_WhoreExperience.cs" />
<Compile Include="WhoringTab\PawnTable_Whores.cs" />
<Compile Include="WhoringTab\WhoreCheckbox.cs" />
<Compile Include="ThinkTreeNodes\ThinkNode_ChancePerHour_Whore.cs" />
<Compile Include="ThinkTreeNodes\ThinkNode_ConditionalWhore.cs" />
<Compile Include="Thoughts\ThoughtWorker_Whore.cs" />
<Compile Include="DefOf\ThoughtDefOf.cs" />
<Compile Include="WhoringBase.cs" />
<Compile Include="Whoring_Bed_Utilities.cs" />
<Compile Include="Whoring_Helper.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,66 @@
using System;
using HugsLib;
using HugsLib.Settings;
using RimWorld;
using Verse;
namespace rjwwhoring
{
public class WhoringBase : ModBase
{
public override string ModIdentifier
{
get
{
return "RJW_Whoring";
}
}
public static DataStore DataStore;//reference to savegame data, hopefully
public override void SettingsChanged()
{
ToggleTabIfNeeded();
}
public override void WorldLoaded()
{
DataStore = Find.World.GetComponent<DataStore>();
ToggleTabIfNeeded();
}
private void ToggleTabIfNeeded()
{
DefDatabase<MainButtonDef>.GetNamed("RJW_Brothel").buttonVisible = whoringtab_enabled;
}
public static SettingHandle<bool> whoringtab_enabled;
public static SettingHandle<bool> show_whore_price_factor_on_bed;
public static SettingHandle<bool> DebugWhoring;
public static SettingHandle<bool> MoneyPrinting;
public static SettingHandle<bool> ClientAlwaysAccept;
public override void DefsLoaded()
{
whoringtab_enabled = Settings.GetHandle("whoringtab_enabled",
"whoringtab_enabled".Translate(),
"whoringtab_enabled_desc".Translate(),
true);
show_whore_price_factor_on_bed = Settings.GetHandle("show_whore_price_factor_on_bed",
"show_whore_price_factor_on_bed".Translate(),
"show_whore_price_factor_on_bed_desc".Translate(),
false);
MoneyPrinting = Settings.GetHandle("MoneyPrinting",
"MoneyPrinting".Translate(),
"MoneyPrinting_desc".Translate(),
false);
ClientAlwaysAccept = Settings.GetHandle("ClientAlwaysAccept",
"ClientAlwaysAccept".Translate(),
"ClientAlwaysAccept_desc".Translate(),
false);
DebugWhoring = Settings.GetHandle("DebugWhoring",
"DebugWhoring".Translate(),
"DebugWhoring_desc".Translate(),
false);
}
}
}

View file

@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Linq;
using System;
using Verse;
using RimWorld;
using RimWorld.Planet;
using rjw;
namespace rjwwhoring.MainTab
{
public class MainTabWindow_Brothel : MainTabWindow_PawnTable
{
private static PawnTableDef pawnTableDef;
protected override PawnTableDef PawnTableDef => pawnTableDef ?? (pawnTableDef = DefDatabase<PawnTableDef>.GetNamed("RJW_Brothel"));
protected override IEnumerable<Pawn> Pawns => Find.CurrentMap.mapPawns.AllPawns.Where(p => xxx.is_human(p) && (p.IsColonist || p.IsPrisonerOfColony));
public override void PostOpen()
{
base.PostOpen();
Find.World.renderer.wantedMode = WorldRenderMode.None;
}
}
}

View file

@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Verse;
using UnityEngine;
using RimWorld;
using Verse.Sound;
using rjw;
namespace rjwwhoring.MainTab
{
public abstract class PawnColumnCheckbox_Whore : PawnColumnWorker
{
public const int HorizontalPadding = 2;
public override void DoCell(Rect rect, Pawn pawn, PawnTable table)
{
if (!this.HasCheckbox(pawn))
{
return;
}
int num = (int)((rect.width - 24f) / 2f);
int num2 = Mathf.Max(3, 0);
Vector2 vector = new Vector2(rect.x + (float)num, rect.y + (float)num2);
Rect rect2 = new Rect(vector.x, vector.y, 24f, 24f);
if (Find.TickManager.TicksGame % 60 == 0)
{
pawn.UpdatePermissions();
//Log.Message("GetDisabled UpdateCanDesignateService for " + xxx.get_pawnname(pawn));
//Log.Message("UpdateCanDesignateService " + pawn.UpdateCanDesignateService());
//Log.Message("CanDesignateService " + pawn.CanDesignateService());
//Log.Message("GetDisabled " + GetDisabled(pawn));
}
bool disabled = this.GetDisabled(pawn);
bool value;
if (disabled)
{
value = false;
}
else
{
value = this.GetValue(pawn);
}
bool flag = value;
Vector2 topLeft = vector;
WhoreCheckbox.Checkbox(topLeft, ref value, 24f, disabled, WhoreCheckbox.WhoreCheckboxOnTex, WhoreCheckbox.WhoreCheckboxOffTex, WhoreCheckbox.WhoreCheckboxDisabledTex);
if (Mouse.IsOver(rect2))
{
string tip = this.GetTip(pawn);
if (!tip.NullOrEmpty())
{
TooltipHandler.TipRegion(rect2, tip);
}
}
if (value != flag)
{
this.SetValue(pawn, value);
}
}
public override int GetMinWidth(PawnTable table)
{
return Mathf.Max(base.GetMinWidth(table), 28);
}
public override int GetMaxWidth(PawnTable table)
{
return Mathf.Min(base.GetMaxWidth(table), this.GetMinWidth(table));
}
public override int GetMinCellHeight(Pawn pawn)
{
return Mathf.Max(base.GetMinCellHeight(pawn), 24);
}
public override int Compare(Pawn a, Pawn b)
{
return this.GetValueToCompare(a).CompareTo(this.GetValueToCompare(b));
}
private int GetValueToCompare(Pawn pawn)
{
if (!this.HasCheckbox(pawn))
{
return 0;
}
if (!this.GetValue(pawn))
{
return 1;
}
return 2;
}
protected virtual string GetTip(Pawn pawn)
{
return null;
}
protected virtual bool HasCheckbox(Pawn pawn)
{
return true;
}
protected abstract bool GetValue(Pawn pawn);
protected abstract void SetValue(Pawn pawn, bool value);
protected abstract bool GetDisabled(Pawn pawn);
protected override void HeaderClicked(Rect headerRect, PawnTable table)
{
base.HeaderClicked(headerRect, table);
if (Event.current.shift)
{
List<Pawn> pawnsListForReading = table.PawnsListForReading;
for (int i = 0; i < pawnsListForReading.Count; i++)
{
if (this.HasCheckbox(pawnsListForReading[i]))
{
if (Event.current.button == 0)
{
if (!this.GetValue(pawnsListForReading[i]))
{
this.SetValue(pawnsListForReading[i], true);
}
}
else if (Event.current.button == 1 && this.GetValue(pawnsListForReading[i]))
{
this.SetValue(pawnsListForReading[i], false);
}
}
}
if (Event.current.button == 0)
{
SoundDefOf.Checkbox_TurnedOn.PlayOneShotOnCamera(null);
}
else if (Event.current.button == 1)
{
SoundDefOf.Checkbox_TurnedOff.PlayOneShotOnCamera(null);
}
}
}
protected override string GetHeaderTip(PawnTable table)
{
return base.GetHeaderTip(table) + "\n" + "CheckboxShiftClickTip".Translate();
}
}
}

View file

@ -0,0 +1,29 @@
using Verse;
namespace rjwwhoring.MainTab
{
[StaticConstructorOnStartup]
public class PawnColumnWorker_AverageMoneyByWhore : PawnColumnWorker_TextCenter
{
protected override string GetTextFor(Pawn pawn)
{
return ((int)GetValueToCompare(pawn)).ToString();
}
public override int Compare(Pawn a, Pawn b)
{
return GetValueToCompare(a).CompareTo(GetValueToCompare(b));
}
private float GetValueToCompare(Pawn pawn)
{
float total = pawn.records.GetValue(RecordDefOf.EarnedMoneyByWhore);
float count = pawn.records.GetValue(RecordDefOf.CountOfWhore);
if ((int)count == 0)
{
return 0;
}
return (total / count);
}
}
}

View file

@ -0,0 +1,23 @@
using Verse;
namespace rjwwhoring.MainTab
{
[StaticConstructorOnStartup]
public class PawnColumnWorker_CountOfWhore : PawnColumnWorker_TextCenter
{
protected override string GetTextFor(Pawn pawn)
{
return GetValueToCompare(pawn).ToString();
}
public override int Compare(Pawn a, Pawn b)
{
return GetValueToCompare(a).CompareTo(GetValueToCompare(b));
}
private int GetValueToCompare(Pawn pawn)
{
return pawn.records.GetAsInt(RecordDefOf.CountOfWhore);
}
}
}

View file

@ -0,0 +1,23 @@
using Verse;
namespace rjwwhoring.MainTab
{
[StaticConstructorOnStartup]
public class PawnColumnWorker_EarnedMoneyByWhore : PawnColumnWorker_TextCenter
{
protected override string GetTextFor(Pawn pawn)
{
return GetValueToCompare(pawn).ToString();
}
public override int Compare(Pawn a, Pawn b)
{
return GetValueToCompare(a).CompareTo(GetValueToCompare(b));
}
private int GetValueToCompare(Pawn pawn)
{
return pawn.records.GetAsInt(RecordDefOf.EarnedMoneyByWhore);
}
}
}

View file

@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RimWorld;
using RimWorld.Planet;
using rjw;
using UnityEngine;
using Verse;
namespace rjwwhoring.MainTab
{
[StaticConstructorOnStartup]
public class PawnColumnWorker_IsPrisoner : PawnColumnWorker_Icon
{
private static readonly Texture2D comfortOn = ContentFinder<Texture2D>.Get("UI/Tab/ComfortPrisoner_on");
private readonly Texture2D comfortOff = ContentFinder<Texture2D>.Get("UI/Tab/ComfortPrisoner_off");
protected override Texture2D GetIconFor(Pawn pawn)
{
return pawn.IsPrisonerOfColony ? comfortOn : null;
}
}
}

View file

@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RimWorld;
using RimWorld.Planet;
using rjw;
using UnityEngine;
using Verse;
namespace rjwwhoring.MainTab
{
[StaticConstructorOnStartup]
public class PawnColumnWorker_IsSlave : PawnColumnWorker_Icon
{
private static readonly Texture2D comfortOn = ContentFinder<Texture2D>.Get("UI/Tab/ComfortPrisoner_on");
private readonly Texture2D comfortOff = ContentFinder<Texture2D>.Get("UI/Tab/ComfortPrisoner_off");
protected override Texture2D GetIconFor(Pawn pawn)
{
return xxx.is_slave(pawn) ? comfortOn : null;
}
}
}

View file

@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RimWorld;
using RimWorld.Planet;
using rjw;
using UnityEngine;
using Verse;
namespace rjwwhoring.MainTab
{
[StaticConstructorOnStartup]
public class PawnColumnWorker_IsWhore : PawnColumnCheckbox_Whore
{
protected override bool GetDisabled(Pawn pawn)
{
return !pawn.CanDesignateService();
}
protected override bool GetValue(Pawn pawn)
{
return pawn.IsDesignatedService() && xxx.is_human(pawn);
}
protected override void SetValue(Pawn pawn, bool value)
{
if (value == this.GetValue(pawn)) return;
pawn.ToggleService();
}
/*
private static readonly Texture2D serviceOn = ContentFinder<Texture2D>.Get("UI/Tab/Service_on");
private static readonly Texture2D serviceOff = ContentFinder<Texture2D>.Get("UI/Tab/Service_off");
protected override Texture2D GetIconFor(Pawn pawn)
{
return pawn.IsDesignatedService() ? serviceOn : null;
}*/
}
}

View file

@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RimWorld;
using RimWorld.Planet;
using UnityEngine;
using Verse;
namespace rjwwhoring.MainTab
{
[StaticConstructorOnStartup]
public class PawnColumnWorker_Mood : PawnColumnWorker_TextCenter
{
protected override string GetTextFor(Pawn pawn)
{
return GetValueToCompare(pawn).ToStringPercent();
}
public override int Compare(Pawn a, Pawn b)
{
return GetValueToCompare(a).CompareTo(GetValueToCompare(b));
}
private float GetValueToCompare(Pawn pawn)
{
return pawn.needs.mood.CurLevelPercentage;
}
}
}

View file

@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RimWorld;
using RimWorld.Planet;
using UnityEngine;
using Verse;
namespace rjwwhoring.MainTab
{
[StaticConstructorOnStartup]
public class PawnColumnWorker_PriceRangeOfWhore : PawnColumnWorker_TextCenter
{
protected internal int min;
protected internal int max;
protected override string GetTextFor(Pawn pawn)
{
min = WhoringHelper.WhoreMinPrice(pawn);
max = WhoringHelper.WhoreMaxPrice(pawn);
return string.Format("{0} - {1}", min, max);
}
public override int Compare(Pawn a, Pawn b)
{
return GetValueToCompare(a).CompareTo(GetValueToCompare(b));
}
protected override string GetTip(Pawn pawn)
{
string minPriceTip = string.Format(
" Base: {0}\n Traits: {1}",
WhoringHelper.baseMinPrice,
(WhoringHelper.WhoreTraitAdjustmentMin(pawn) -1f).ToStringPercent()
);
string maxPriceTip = string.Format(
" Base: {0}\n Traits: {1}",
WhoringHelper.baseMaxPrice,
(WhoringHelper.WhoreTraitAdjustmentMax(pawn) -1f).ToStringPercent()
);
string bothTip = string.Format(
" Gender: {0}\n Age: {1}\n Injuries: {2}",
(WhoringHelper.WhoreGenderAdjustment(pawn) - 1f).ToStringPercent(),
(WhoringHelper.WhoreAgeAdjustment(pawn) - 1f).ToStringPercent(),
(WhoringHelper.WhoreInjuryAdjustment(pawn) - 1f).ToStringPercent()
);
return string.Format("Min:\n{0}\nMax:\n{1}\nBoth:\n{2}", minPriceTip, maxPriceTip, bothTip);
}
private int GetValueToCompare(Pawn pawn)
{
return min;
}
}
}

View file

@ -0,0 +1,33 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RimWorld;
using RimWorld.Planet;
using UnityEngine;
using Verse;
namespace rjwwhoring.MainTab
{
public abstract class PawnColumnWorker_TextCenter : PawnColumnWorker_Text
{
public override void DoCell(Rect rect, Pawn pawn, PawnTable table)
{
Rect rect2 = new Rect(rect.x, rect.y, rect.width, Mathf.Min(rect.height, 30f));
string textFor = GetTextFor(pawn);
if (textFor != null)
{
Text.Font = GameFont.Small;
Text.Anchor = TextAnchor.MiddleCenter;
Text.WordWrap = false;
Widgets.Label(rect2, textFor);
Text.WordWrap = true;
Text.Anchor = TextAnchor.UpperLeft;
string tip = GetTip(pawn);
if (!tip.NullOrEmpty())
{
TooltipHandler.TipRegion(rect2, tip);
}
}
}
}
}

View file

@ -0,0 +1,19 @@
using System.Collections.Generic;
using Verse;
namespace rjwwhoring.MainTab
{
[StaticConstructorOnStartup]
public class PawnColumnWorker_WhoreExperience : PawnColumnWorker_TextCenter
{
public static readonly HashSet<string> backstories = new HashSet<string>(DefDatabase<StringListDef>.GetNamed("WhoreBackstories").strings);
protected override string GetTextFor(Pawn pawn)
{
int b = backstories.Contains(pawn.story?.adulthood?.titleShort) ? 30 : 0;
int score = pawn.records.GetAsInt(RecordDefOf.CountOfWhore);
return (score + b).ToString();
}
}
}

View file

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using rjw;
using Verse;
namespace rjwwhoring.MainTab
{
public class PawnTable_Whores : PawnTable
{
public PawnTable_Whores(PawnTableDef def, Func<IEnumerable<Pawn>> pawnsGetter, int uiWidth, int uiHeight) : base(def, pawnsGetter, uiWidth, uiHeight) { }
protected override IEnumerable<Pawn> LabelSortFunction(IEnumerable<Pawn> input)
{
//return input.OrderBy(p => p.Name?.Numerical != false).ThenBy(p => (p.Name as NameSingle)?.Number ?? 0).ThenBy(p => p.def.label);
return input.OrderBy(p => xxx.get_pawnname(p));
}
protected override IEnumerable<Pawn> PrimarySortFunction(IEnumerable<Pawn> input)
{
///return input.OrderByDescending(p => p.Faction?.Name);
//return input.OrderBy(p => xxx.get_pawnname(p));
foreach (Pawn p in input)
p.UpdatePermissions();
return input.OrderByDescending(p => p.IsColonist);
}
}
}

View file

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Verse;
using UnityEngine;
using RimWorld;
using Verse.Sound;
namespace rjwwhoring.MainTab
{
[StaticConstructorOnStartup]
public static class WhoreCheckbox
{
public static readonly Texture2D WhoreCheckboxOnTex = ContentFinder<Texture2D>.Get("UI/Commands/Service_on");
public static readonly Texture2D WhoreCheckboxOffTex = ContentFinder<Texture2D>.Get("UI/Commands/Service_off");
public static readonly Texture2D WhoreCheckboxDisabledTex = ContentFinder<Texture2D>.Get("UI/Commands/Service_Refuse");
private static bool checkboxPainting;
private static bool checkboxPaintingState;
public static void Checkbox(Vector2 topLeft, ref bool checkOn, float size = 24f, bool disabled = false, Texture2D texChecked = null, Texture2D texUnchecked = null, Texture2D texDisabled = null)
{
WhoreCheckbox.Checkbox(topLeft.x, topLeft.y, ref checkOn, size, disabled, texChecked, texUnchecked);
}
public static void Checkbox(float x, float y, ref bool checkOn, float size = 24f, bool disabled = false, Texture2D texChecked = null, Texture2D texUnchecked = null, Texture2D texDisabled = null)
{
Rect rect = new Rect(x, y, size, size);
WhoreCheckbox.CheckboxDraw(x, y, checkOn, disabled, size, texChecked, texUnchecked,texDisabled);
if (!disabled)
{
MouseoverSounds.DoRegion(rect);
bool flag = false;
Widgets.DraggableResult draggableResult = Widgets.ButtonInvisibleDraggable(rect, false);
if (draggableResult == Widgets.DraggableResult.Pressed)
{
checkOn = !checkOn;
flag = true;
}
else if (draggableResult == Widgets.DraggableResult.Dragged)
{
checkOn = !checkOn;
flag = true;
WhoreCheckbox.checkboxPainting = true;
WhoreCheckbox.checkboxPaintingState = checkOn;
}
if (Mouse.IsOver(rect) && WhoreCheckbox.checkboxPainting && Input.GetMouseButton(0) && checkOn != WhoreCheckbox.checkboxPaintingState)
{
checkOn = WhoreCheckbox.checkboxPaintingState;
flag = true;
}
if (flag)
{
if (checkOn)
{
SoundDefOf.Checkbox_TurnedOn.PlayOneShotOnCamera(null);
}
else
{
SoundDefOf.Checkbox_TurnedOff.PlayOneShotOnCamera(null);
}
}
}
}
private static void CheckboxDraw(float x, float y, bool active, bool disabled, float size = 24f, Texture2D texChecked = null, Texture2D texUnchecked = null, Texture2D texDisabled = null)
{
Texture2D image;
if (disabled)
{
image = ((!(texDisabled != null)) ? WhoreCheckbox.WhoreCheckboxDisabledTex : texDisabled);
}
else if (active)
{
image = ((!(texChecked != null)) ? WhoreCheckbox.WhoreCheckboxOnTex : texChecked);
}
else
{
image = ((!(texUnchecked != null)) ? WhoreCheckbox.WhoreCheckboxOffTex : texUnchecked);
}
Rect position = new Rect(x, y, size, size);
GUI.DrawTexture(position, image);
}
}
}

View file

@ -0,0 +1,371 @@
using Verse;
using Verse.AI;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using System;
using UnityEngine;
using rjw;
namespace rjwwhoring
{
public static class WhoreBed_Utility
{
public static readonly RoomRoleDef roleDefBrothel = DefDatabase<RoomRoleDef>.GetNamed("Brothel");
// find the best bed for a customer; whore is needed as parameter to only select beds that are reachable
public static Building_Bed FindBed(Pawn whore, Pawn customer)
{
List<Building_Bed> b = FindReachableAndAvailableWhoreBeds(whore, customer);
return GetBestBedForCustomer(customer, b);
}
public static bool CanUseForWhoring(Pawn pawn, Building_Bed bed)
{
bool flag = bed.IsAvailableForWhoring(pawn) && pawn.CanReserveAndReach(bed, PathEndMode.InteractionCell, Danger.Unspecified) && !bed.IsForbidden(pawn);
return flag;
}
public static Building_Bed GetBestBedForCustomer(Pawn customer, List<Building_Bed> beds)
{
if (beds != null && beds.Any())
{
return beds.MaxBy(bed => CalculateBedScore(customer, bed));
}
else
{
return null;
}
}
public static float GetCheapestBedFactor(Pawn whore, Pawn customer)
{
List<Building_Bed> beds = FindReachableAndAvailableWhoreBeds(whore, customer);
return GetCheapestBedFactor(beds);
}
public static float GetCheapestBedFactor(List<Building_Bed> beds)
{
if (beds != null && beds.Any())
{
return CalculatePriceFactor(beds.MinBy(bed => CalculatePriceFactor(bed)));
}
else
{
return 0f;
}
}
// unused
/*public static float GetMostExpensiveBedFactor(List<Building_Bed> beds)
{
if (beds != null && beds.Any())
{
return CalculatePriceFactor(beds.MaxBy(bed => CalculatePriceFactor(bed)));
}
else
{
return 0f;
}
}//*/
public static float CalculateRoomFactor(Room room, int num_humanlike_beds)
{
if (room == null || room.Role == RoomRoleDefOf.None || room.OutdoorsForWork)
return 0.1f;
float room_multiplier = 1f;
if (room.Role == roleDefBrothel)
{
room_multiplier *= (float)Math.Pow(0.8, num_humanlike_beds - 1);
}
else // if(room.Role == RoomRoleDefOf.Barracks)
{
room_multiplier /= 2 * (num_humanlike_beds - 1) + 1;
}
int scoreStageIndex = RoomStatDefOf.Impressiveness.GetScoreStageIndex(room.GetStat(RoomStatDefOf.Impressiveness));
//Room impressiveness factor
//0 < scoreStageIndex < 10 (Last time checked)
//3 is mediocore
room_multiplier *= (float)(scoreStageIndex <= 3 ? .4f + scoreStageIndex * .2f : 1f + .3f * (scoreStageIndex - 3));
return Mathf.Max(room_multiplier, 0);
}
public static float CalculateBedFactorsForRoom(Room room, Building_Bed except_this_bed = null)
{
float room_factor = 0.1f;
if (room == null)
return room_factor;
// get eligible beds
IEnumerable<Building_Bed> humanlike_beds = room.ContainedBeds.Where(b => b.def.building.bed_humanlike);
int num_humanlike_beds = humanlike_beds.Count();
if (num_humanlike_beds <= 0)
{
return room_factor;
}
IEnumerable<Building_Bed> whoring_beds = humanlike_beds.Where(b => b.IsAllowedForWhoringOwner());
if (whoring_beds.Any())
{
// if beds exist, calculate room score
room_factor = CalculateRoomFactor(room, num_humanlike_beds);
// and update all beds
foreach (Building_Bed b in whoring_beds)
{
// except the bed given as parameter (will be calculated in that bed's function)
if (except_this_bed == null || b.thingIDNumber != except_this_bed.thingIDNumber)
CalculatePriceFactor(b, room_factor);
}
}
return room_factor;
}
public static void ResetTicksUntilUpdate(Room room)
{
IEnumerable<Building_Bed> whoring_beds = room.ContainedBeds.Where(b => b.IsAllowedForWhoringOwner());
foreach (Building_Bed bed in whoring_beds)
{
// set all to 0
// if one is needed, it updates all the other beds
// if none is needed, it doesn't matter
// only setting one bed to update has the risk that a different bed's value is required that doesn't trigger an update
WhoringBase.DataStore.GetBedData(bed).scoreUpdateTickDelay = 0;
}
}
public static float CalculatePriceFactor(Building_Bed bed, float room_multiplier = -1f)
{
BedData saved_bed_data = WhoringBase.DataStore.GetBedData(bed);
// cache result (no need for saving): if no result available, calculate; otherwise save tick at which it has been calculated.
// additional parameter "room_multiplier" to skip room analysis
if ((room_multiplier == -1 || room_multiplier >= 0 && room_multiplier == saved_bed_data.roomScore)
&& saved_bed_data.bedScore >= 0f
&& saved_bed_data.lastScoreUpdateTick >
GenTicks.TicksGame - saved_bed_data.scoreUpdateTickDelay)
{
if (room_multiplier >= 0 && saved_bed_data.scoreUpdateTickDelay < 720)
{
// if saved value is used due to unchanged room multiplier, increase recalc delay
saved_bed_data.scoreUpdateTickDelay += 60 + Rand.Int % 10;
}
//Log.Message("[RJW] lastScoreUpdateTick: " + BukkakeBase.DataStore.GetBedData(bed).lastScoreUpdateTick.ToString() + " / TicksGame: "+ GenTicks.TicksGame.ToString());
return saved_bed_data.bedScore;
}
if (room_multiplier < 0)
{
Room room = bed.Map != null && bed.Map.regionAndRoomUpdater.Enabled ? bed.GetRoom() : null;
room_multiplier = CalculateBedFactorsForRoom(room, bed);
}
// uncomfortable beds reduce price, comfortable beds make customers pay a tip
float comfort = bed.GetStatValue(StatDefOf.Comfort);
float price_factor = room_multiplier * comfort;
// delay recalculation if result is the same as before
// Rand.Int % 10 flattens the spike over time if many beds are toggled at once
if (price_factor == saved_bed_data.bedScore)
{
if (saved_bed_data.scoreUpdateTickDelay < 720)
{
// slowly increase recalculation delay to two seconds on speed 3
saved_bed_data.scoreUpdateTickDelay += 60 + Rand.Int % 10;
}
}
else
{
// reset recalculation delay
saved_bed_data.scoreUpdateTickDelay = 60 + Rand.Int % 10;
}
// update bed data
saved_bed_data.lastScoreUpdateTick = GenTicks.TicksGame;
saved_bed_data.bedScore = price_factor;
saved_bed_data.roomScore = room_multiplier;
// this is quite spammy
//if (RJWSettings.DebugWhoring)
// Log.Message("[RJW]CalculatePriceFactor for bed " + bed.thingIDNumber.ToString() + ": "
// + "room_multiplier (num beds, impressiveness) ("+room_multiplier.ToString() +") * "
// + "comfort (" + comfort.ToString() + ") = " + price_factor.ToString());
return price_factor;
}
// customers would want the best bed
// TODO: price as factor, rebalance
public static float CalculateBedScore(Pawn customer, Building_Bed bed)
{
float basePriceFactor = CalculatePriceFactor(bed);
// ascetic pawns want the least impressive room
if (customer.story.traits.HasTrait(TraitDefOf.Ascetic))
{
float comfort = bed.GetStatValue(StatDefOf.Comfort);
basePriceFactor = comfort * comfort / basePriceFactor; // inverse room effects - may be cheap, but should still be comfortable. ascetic isn't masochistic!
if (WhoringBase.DebugWhoring)
Log.Message("[RJW]CalculateBedScore - Customer is ascetic");
}
basePriceFactor *= 200; // make a larger number for better distance scaling (and random effect)
// horny pawns are in a hurry and want a closer bed
int distance = 0;
if (xxx.is_hornyorfrustrated(customer))
{
distance = (int)bed.Position.DistanceTo(customer.Position);
//if (RJWSettings.DebugWhoring)
// Log.Message("[RJW]CalculateBedScore - Pawn is horny - distance = "+distance.ToString());
}
int random_factor = Rand.Int % 100;
float score = basePriceFactor - distance + random_factor;
if (WhoringBase.DebugWhoring)
Log.Message("[RJW]CalculateBedScore for bed " + bed.thingIDNumber.ToString() + ": "
+ "score from price (" + basePriceFactor.ToString() + ") "
+ "- distance (" + distance.ToString() + ") "
+ "+ randomness (" + random_factor.ToString() + ") "
+ "= " + score.ToString());
return score;
}
public static List<Building_Bed> FindReachableAndAvailableWhoreBeds(Pawn whore, Pawn customer)
{
List<Building_Bed> wb = new List<Building_Bed>();
wb = whore.MapHeld.GetWhoreBeds().Where(bed =>
!bed.IsForbidden(whore) &&
!bed.IsForbidden(customer) &&
!bed.IsBurning() &&
bed.WhoringIsAllowedForPawn(whore) &&
bed.IsAvailableForWhoring(whore) &&
whore.CanReserveAndReach(bed, PathEndMode.OnCell, Danger.Unspecified) &&
customer.CanReach(bed, PathEndMode.OnCell, Danger.Some)
// TODO: price/affordable?
).ToList();
if (WhoringBase.DebugWhoring)
Log.Message("[RJW]FindReachableAndAvailableWhoreBeds - found " + wb.Count().ToString() + " beds");
return wb;
}
public static IEnumerable<Building_Bed> GetWhoreBeds(this Map map, Area area = null)
{
if (map == null) return new Building_Bed[0];
if (area == null) return map.listerBuildings.AllBuildingsColonistOfClass<Building_Bed>();
return map.listerBuildings.AllBuildingsColonistOfClass<Building_Bed>().Where(b => area[b.Position]);
}
public static bool WhoringIsAllowedForPawn(this Building_Bed bed, Pawn pawn)
{
if (bed.IsAllowedForWhoringAll())
return true;
if (bed == pawn.ownership.OwnedBed && bed.IsAllowedForWhoringOwner())
return true;
return false;
}
public static void SetAllowedForWhoringOwner(this Building_Bed bed, bool isAllowed)
{
if (!isAllowed)
{
// if whoring is disallowed for owner, also disallow for all
bed.SetAllowedForWhoringAll(false);
}
WhoringBase.DataStore.GetBedData(bed).allowedForWhoringOwner = isAllowed;
}
public static void ToggleAllowedForWhoringOwner(this Building_Bed bed)
{
bed.SetAllowedForWhoringOwner(!WhoringBase.DataStore.GetBedData(bed).allowedForWhoringOwner);
}
public static bool IsAllowedForWhoringOwner(this Building_Bed bed)
{
if (!bed.def.building.bed_humanlike || bed.Faction != Faction.OfPlayerSilentFail || bed.Medical || bed.def.defName.Contains("Guest"))
{
return false;
}
if (bed.ForPrisoners)
{
// no toggle on prisoner beds, they may always use their own bed (if they are supposed to whore, anyway)
return true;
}
return WhoringBase.DataStore.GetBedData(bed).allowedForWhoringOwner;
}
public static void SetAllowedForWhoringAll(this Building_Bed bed, bool isAllowed)
{
if (isAllowed)
{
// if whoring is allowed for all, also visibly allow for owner
bed.SetAllowedForWhoringOwner(true);
// if bed is designated for whoring, disable prisoner/medical use
bed.ForPrisoners = false;
bed.Medical = false;
}
WhoringBase.DataStore.GetBedData(bed).allowedForWhoringAll = isAllowed;
bed.GetRoom()?.Notify_BedTypeChanged();
bed.Notify_ColorChanged();
}
public static void ToggleAllowedForWhoringAll(this Building_Bed bed)
{
bed.SetAllowedForWhoringAll(!WhoringBase.DataStore.GetBedData(bed).allowedForWhoringAll);
}
public static bool IsAllowedForWhoringAll(this Building_Bed bed)
{
if (WhoringBase.DataStore.GetBedData(bed).allowedForWhoringAll)
{
if (!bed.def.building.bed_humanlike || bed.Faction != Faction.OfPlayerSilentFail || bed.Medical || bed.ForPrisoners || bed.def.defName.Contains("Guest") || bed.GetRoom()?.IsPrisonCell == true)
{
return false;
}
return true;
}
return false;
}
public static void ReserveForWhoring(this Building_Bed bed, Pawn p, int num_ticks)
{
WhoringBase.DataStore.GetBedData(bed).reservedUntilGameTick = GenTicks.TicksGame + num_ticks;
WhoringBase.DataStore.GetBedData(bed).reservedForPawnID = p.thingIDNumber;
}
public static void UnreserveForWhoring(this Building_Bed bed)
{
WhoringBase.DataStore.GetBedData(bed).reservedUntilGameTick = 0;
WhoringBase.DataStore.GetBedData(bed).reservedForPawnID = 0;
}
public static bool IsAvailableForWhoring(this Building_Bed bed, Pawn p)
{
// check for active reservation
if (WhoringBase.DataStore.GetBedData(bed).reservedUntilGameTick > GenTicks.TicksGame)
{
if (WhoringBase.DataStore.GetBedData(bed).reservedForPawnID != p.thingIDNumber)
{
// a different pawn has reserved this bed
return false;
}
}
if (bed.OwnersForReading.Any())
{
for (int i = 0; i < bed.OwnersForReading.Count; i++)
{
if (bed.OwnersForReading[i].InBed() && bed.OwnersForReading[i].CurrentBed() == bed)
{
// someone sleeping in this bed
return false;
}
}
}
return true;
}
}
}

View file

@ -0,0 +1,425 @@
// #define TESTMODE // Uncomment to enable logging.
using Verse;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using System;
using UnityEngine;
using Verse.AI.Group;
//using Multiplayer.API;
using rjw;
namespace rjwwhoring
{
/// <summary>
/// Helper for whoring related stuff
/// </summary>
public class WhoringHelper
{
public const float baseMinPrice = 10f;
public const float baseMaxPrice = 20f;
public static readonly HashSet<string> backstories = new HashSet<string>(DefDatabase<StringListDef>.GetNamed("WhoreBackstories").strings);
public static int WhoreMinPrice(Pawn whore)
{
float min_price = baseMinPrice;
min_price *= WhoreAgeAdjustment(whore);
min_price *= WhoreGenderAdjustment(whore);
min_price *= WhoreInjuryAdjustment(whore);
min_price *= WhoreAbilityAdjustmentMin(whore);
//min_price *= WhoreRoomAdjustment(whore);
if (xxx.has_traits(whore))
min_price *= WhoreTraitAdjustmentMin(whore);
return (int)min_price;
}
public static int WhoreMaxPrice(Pawn whore)
{
float max_price = baseMaxPrice;
max_price *= WhoreAgeAdjustment(whore);
max_price *= WhoreGenderAdjustment(whore);
max_price *= WhoreInjuryAdjustment(whore);
max_price *= WhoreAbilityAdjustmentMax(whore);
//max_price *= WhoreRoomAdjustment(whore);
if (xxx.has_traits(whore))
max_price *= WhoreTraitAdjustmentMax(whore);
return (int)max_price;
}
public static float WhoreGenderAdjustment(Pawn whore)
{
if (GenderHelper.GetSex(whore) == GenderHelper.Sex.futa)
return 1.2f;
return 1f;
}
public static float WhoreAgeAdjustment(Pawn whore)
{
return AgeConfigDef.Instance.whoringPriceByAge.Evaluate(SexUtility.ScaleToHumanAge(whore));
}
public static float WhoreInjuryAdjustment(Pawn whore)
{
float modifier = 1.0f;
int injuries = whore.health.hediffSet.hediffs.Count(x => x.Visible && x.def.everCurableByItem && x.IsPermanent());
if (injuries == 0) return modifier;
while (injuries > 0)
{
modifier *= 0.85f;
injuries--;
}
return modifier;
}
public static float WhoreAbilityAdjustmentMin(Pawn whore)
{
int b = backstories.Contains(whore.story?.adulthood?.titleShort) ? 30 : 0;
int score = whore.records.GetAsInt(RecordDefOf.EarnedMoneyByWhore);
float multiplier = (score + b) / 100;
multiplier = Math.Min(multiplier, 2);
multiplier = (multiplier - 0.5f) * 0.5f + 1;
return multiplier;
}
public static float WhoreAbilityAdjustmentMax(Pawn whore)
{
int b = backstories.Contains(whore.story?.adulthood?.titleShort) ? 30 : 0;
int score = whore.records.GetAsInt(RecordDefOf.CountOfWhore);
float multiplier = (score + b) / 100;
multiplier = Math.Min(multiplier, 2);
multiplier = (multiplier - 0.5f) * 0.5f + 1;
return multiplier;
}
public static float WhoreTraitAdjustmentMin(Pawn whore)
{
float multiplier = WhoreTraitAdjustment(whore);
if (xxx.is_masochist(whore)) // Won't haggle, may settle for low price
multiplier *= 0.7f;
if (xxx.is_nympho(whore)) // Same as above
multiplier *= 0.4f;
if (whore.story.traits.HasTrait(TraitDefOf.Wimp)) // Same as above
multiplier *= 0.4f;
return multiplier;
}
public static float WhoreTraitAdjustmentMax(Pawn whore)
{
float multiplier = WhoreTraitAdjustment(whore);
if (xxx.IndividualityIsActive && whore.story.traits.HasTrait(xxx.SYR_Haggler))
multiplier *= 1.5f;
if (whore.story.traits.HasTrait(TraitDefOf.Greedy))
multiplier *= 2f;
return multiplier;
}
public static float WhoreTraitAdjustment(Pawn whore)
{
float multiplier = 1f;
if (xxx.has_traits(whore))
{
if (whore.story.traits.DegreeOfTrait(TraitDefOf.Industriousness) == 2) // Industrious
multiplier *= 1.2f;
if (whore.story.traits.DegreeOfTrait(TraitDefOf.Industriousness) == 1) // Hard Worker
multiplier *= 1.1f;
if (whore.story.traits.HasTrait(TraitDefOf.CreepyBreathing))
multiplier *= 0.75f;
if (whore.GetStatValue(StatDefOf.PawnBeauty) >= 2)
multiplier *= 3.5f;
else if (whore.GetStatValue(StatDefOf.PawnBeauty) >= 1)
multiplier *= 2f;
else if (whore.GetStatValue(StatDefOf.PawnBeauty) < 0)
if (whore.GetStatValue(StatDefOf.PawnBeauty) >= -1)
multiplier *= 0.8f;
else
multiplier *= 0.6f;
}
return multiplier;
}
/*public static float WhoreRoomAdjustment(Pawn whore)
{
float room_multiplier = 1f;
Room ownedRoom = whore.ownership.OwnedRoom;
if (ownedRoom == null) return 0f;
//Room sharing is not liked by patrons
room_multiplier = room_multiplier / (2 * (ownedRoom.Owners.Count() - 1) + 1);
int scoreStageIndex = RoomStatDefOf.Impressiveness.GetScoreStageIndex(ownedRoom.GetStat(RoomStatDefOf.Impressiveness));
//Room impressiveness factor
//0 < scoreStageIndex < 10 (Last time checked)
//3 is mediocore
if (scoreStageIndex == 0)
{
room_multiplier *= 0.3f;
}
else if (scoreStageIndex > 3)
{
room_multiplier *= 1 + ((float)scoreStageIndex - 3) / 3.5f;
} //top room triples the price
return room_multiplier;
}//*/
//[SyncMethod]
public static int PriceOfWhore(Pawn whore)
{
float NeedSexFactor = xxx.is_hornyorfrustrated(whore) ? 1 - xxx.need_some_sex(whore) / 8 : 1f;
//Rand.PopState();
//Rand.PushState(RJW_Multiplayer.PredictableSeed());
float price = Rand.Range(WhoreMinPrice(whore), WhoreMaxPrice(whore));
price *= NeedSexFactor;
//--ModLog.Message(" xxx::PriceOfWhore - price is " + price);
return (int)Math.Round(price);
}
public static bool CanAfford(Pawn targetPawn, Pawn whore, int priceOfWhore = -1)
{
//if (targetPawn.Faction == whore.Faction) return true;
if (WhoringBase.MoneyPrinting) return true;
//if (RJWSettings.DebugWhoring) ModLog.Message($"CanAfford for client {xxx.get_pawnname(targetPawn)}");
int price = priceOfWhore < 0 ? PriceOfWhore(whore) : priceOfWhore;
if (price == 0)
return true;
// can customer afford the cheapest bed? - skip check, should rarely make a difference
//float bed_factor = WhoreBed_Utility.GetCheapestBedFactor(whore, targetPawn);
//price = (int)(price * bed_factor);
//if (RJWSettings.DebugWhoring) ModLog.Message($" whore price {price}");
Lord lord = targetPawn.GetLord();
Faction faction = targetPawn.Faction;
int clientAmountOfSilvers = targetPawn.inventory.innerContainer.TotalStackCountOfDef(ThingDefOf.Silver);
int totalAmountOfSilvers = clientAmountOfSilvers;
if (faction != null)
{
List<Pawn> caravanMembers = targetPawn.Map.mapPawns.PawnsInFaction(targetPawn.Faction).Where(x => x.GetLord() == lord && x.inventory?.innerContainer?.TotalStackCountOfDef(ThingDefOf.Silver) > 0).ToList();
if (!caravanMembers.Any())
{
//if (RJWSettings.DebugWhoring) ModLog.Message($" client not in caravan");
//if (RJWSettings.DebugWhoring) ModLog.Message("CanAfford::(" + xxx.get_pawnname(targetPawn) + "," + xxx.get_pawnname(whore) + ") - totalAmountOfSilvers is " + totalAmountOfSilvers);
return totalAmountOfSilvers >= price;
}
totalAmountOfSilvers += caravanMembers.Sum(member => member.inventory.innerContainer.TotalStackCountOfDef(ThingDefOf.Silver));
}
//if (RJWSettings.DebugWhoring) ModLog.Message($" client silver = {clientAmountOfSilvers} caravan silver = {totalAmountOfSilvers - clientAmountOfSilvers}");
//if (RJWSettings.DebugWhoring) ModLog.Message("CanAfford:: can afford the whore: " + (totalAmountOfSilvers >= price));
return totalAmountOfSilvers >= price;
}
//priceOfWhore is assumed >=0, and targetPawn is assumed to be able to pay the price(either by caravan, or by himself)
//This means that targetPawn has total stackcount of silvers >= priceOfWhore.
public static int PayPriceToWhore(Pawn targetPawn, int priceOfWhore, Pawn whore)
{
if (WhoringBase.DebugWhoring) ModLog.Message($"PayPriceToWhore for client {xxx.get_pawnname(targetPawn)}");
if (WhoringBase.MoneyPrinting)
{
DebugThingPlaceHelper.DebugSpawn(ThingDefOf.Silver, whore.PositionHeld, priceOfWhore, false, null);
if (WhoringBase.DebugWhoring) ModLog.Message($" MoneyPrinting " + priceOfWhore + " silver to pay price");
return 0;
}
int AmountLeft = priceOfWhore;
if ((targetPawn.Faction == whore.Faction && targetPawn.GuestStatus != GuestStatus.Guest) || priceOfWhore == 0)
{
if (WhoringBase.DebugWhoring) ModLog.Message($" No need to pay price");
return AmountLeft;
}
Lord lord = targetPawn.GetLord();
//Caravan guestCaravan = Find.WorldObjects.Caravans.Where(x => x.Spawned && x.ContainsPawn(targetPawn) && x.Faction == targetPawn.Faction && !x.IsPlayerControlled).FirstOrDefault();
List<Pawn> caravan = targetPawn.Map.mapPawns.PawnsInFaction(targetPawn.Faction).Where(x => x.GetLord() == lord && x.inventory?.innerContainer != null && x.inventory.innerContainer.TotalStackCountOfDef(ThingDefOf.Silver) > 0).ToList();
IEnumerable<Thing> TraderSilvers;
if (!caravan.Any())
{
if (WhoringBase.DebugWhoring) ModLog.Message($" (not a caravan member), try to pay with own silver");
TraderSilvers = targetPawn.inventory.innerContainer.Where(x => x.def == ThingDefOf.Silver);
foreach (Thing silver in TraderSilvers)
{
if (AmountLeft <= 0)
break;
int dropAmount = silver.stackCount >= AmountLeft ? AmountLeft : silver.stackCount;
if (targetPawn.inventory.innerContainer.TryDrop(silver, whore.Position, whore.Map, ThingPlaceMode.Near, dropAmount, out Thing resultingSilvers))
{
if (resultingSilvers is null)
{
if (WhoringBase.DebugWhoring) ModLog.Message($" have no silver");
continue;
}
AmountLeft -= resultingSilvers.stackCount;
if (AmountLeft <= 0)
{
if (WhoringBase.DebugWhoring) ModLog.Message($" {xxx.get_pawnname(targetPawn)} paid {resultingSilvers.stackCount} silver");
break;
}
}
else
{
if (WhoringBase.DebugWhoring) ModLog.Message($" TryDrop failed");
break;
}
}
if (WhoringBase.DebugWhoring) ModLog.Message($" price: {priceOfWhore}, paid: {priceOfWhore - AmountLeft}");
}
else
{
if (WhoringBase.DebugWhoring) ModLog.Message($" (caravan member), try to pay {AmountLeft} silver with caravan silver");
foreach (Pawn caravanMember in caravan)
{
TraderSilvers = caravanMember.inventory.innerContainer.Where(x => x.def == ThingDefOf.Silver);
if (WhoringBase.DebugWhoring) ModLog.Message($" try to pay with {xxx.get_pawnname(caravanMember)} silver");
foreach (Thing silver in TraderSilvers)
{
if (AmountLeft <= 0)
break;
int dropAmount = silver.stackCount >= AmountLeft ? AmountLeft : silver.stackCount;
if (caravanMember.inventory.innerContainer.TryDrop(silver, whore.Position, whore.Map, ThingPlaceMode.Near, dropAmount, out Thing resultingSilvers))
{
if (resultingSilvers is null)
{
if (WhoringBase.DebugWhoring) ModLog.Message($" have no silver");
continue;
}
AmountLeft -= resultingSilvers.stackCount;
if (AmountLeft <= 0)
{
if (WhoringBase.DebugWhoring) ModLog.Message($" {xxx.get_pawnname(caravanMember)} paid {resultingSilvers.stackCount} silver");
break;
}
}
}
}
}
if (WhoringBase.DebugWhoring) ModLog.Message($" price: {priceOfWhore}, paid: {priceOfWhore - AmountLeft}");
return AmountLeft;
}
//[SyncMethod]
public static bool IsHookupAppealing(Pawn target, Pawn whore)
{
if (PawnUtility.WillSoonHaveBasicNeed(target))
{
//Log.Message("IsHookupAppealing - fail: " + xxx.get_pawnname(target) + " has need to do");
return false;
}
if (WhoringBase.ClientAlwaysAccept)
{
return true;
}
float num = target.relations.SecondaryRomanceChanceFactor(whore) / 1.5f;
if (xxx.is_frustrated(target))
{
num *= 3.0f;
}
else if (xxx.is_hornyorfrustrated(target))
{
num *= 2.0f;
}
if (xxx.is_zoophile(target) && !xxx.is_animal(whore))
{
num *= 0.5f;
}
if (xxx.AlienFrameworkIsActive)
{
if (xxx.is_xenophile(target))
{
if (target.def.defName == whore.def.defName)
num *= 0.5f; // Same species, xenophile less interested.
else
num *= 1.5f; // Different species, xenophile more interested.
}
else if (xxx.is_xenophobe(target))
{
if (target.def.defName != whore.def.defName)
num *= 0.25f; // Different species, xenophobe less interested.
}
}
num *= 0.8f + (float)whore.skills.GetSkill(SkillDefOf.Social).Level / 40; // 0.8 to 1.3
num *= Mathf.InverseLerp(-100f, 0f, target.relations.OpinionOf(whore)); // 1 to 0 reduce score by negative opinion/relations to whore
//Log.Message("IsHookupAppealing - score: " + num);
//Rand.PopState();
//Rand.PushState(RJW_Multiplayer.PredictableSeed());
return Rand.Range(0.05f, 1f) < num;
}
/// <summary>
/// Check if the pawn is willing to hook up. Checked for both target and the whore(when hooking colonists).
/// </summary>
//[SyncMethod]
public static bool WillPawnTryHookup(Pawn target)
{
if (WhoringBase.ClientAlwaysAccept)
{
return true;
}
if (target.story.traits.HasTrait(TraitDefOf.Asexual))
{
return false;
}
Pawn lover = LovePartnerRelationUtility.ExistingMostLikedLovePartner(target, false);
if (lover == null)
{
return true;
}
float num = target.relations.OpinionOf(lover);
float num2 = Mathf.InverseLerp(30f, -80f, num);
if (xxx.is_prude(target))
{
num2 = 0f;
}
else if (xxx.is_lecher(target))
{
//Lechers are always up for it.
num2 = Mathf.InverseLerp(100f, 50f, num);
}
else if (target.Map == lover.Map)
{
//Less likely to cheat if the lover is on the same map.
num2 = Mathf.InverseLerp(70f, 15f, num);
}
//else default values
if (xxx.is_frustrated(target))
{
num2 *= 1.8f;
}
else if (xxx.is_hornyorfrustrated(target))
{
num2 *= 1.4f;
}
num2 /= 1.5f;
//Rand.PopState();
//Rand.PushState(RJW_Multiplayer.PredictableSeed());
return Rand.Range(0f, 1f) < num2;
}
/// <summary>
/// Updates records for whoring.
/// </summary>
public static void UpdateRecords(Pawn pawn, int price)
{
pawn.records.AddTo(RecordDefOf.EarnedMoneyByWhore, price);
pawn.records.Increment(RecordDefOf.CountOfWhore);
//this is added by normal outcome
//pawn.records.Increment(CountOfSex);
}
}
}

View file

@ -0,0 +1,64 @@
using Verse;
using HarmonyLib;
using rjw;
using System;
using RimWorld;
namespace rjwwhoring
{
[HarmonyPatch(typeof(AfterSexUtility), "think_about_sex", new Type[] {typeof(Pawn), typeof(Pawn), typeof(bool), typeof(SexProps), typeof(bool)})]
[StaticConstructorOnStartup]
static class Aftersex_WhoringhoughtApply
{
[HarmonyPostfix]
private static void ThinkAboutWhoringPatch(Pawn pawn, Pawn partner, bool isReceiving, SexProps props, bool whoring = false)
{
try
{
AfterSexUtilityPatch.ThinkAboutWhoring(pawn, partner, isReceiving, props, whoring);
}
catch (Exception e)
{
Log.Error(e.ToString());
}
}
}
static class AfterSexUtilityPatch
{
///<summary>
///add aftersex thoughts for whoring
///</summary>
public static void ThinkAboutWhoring(Pawn pawn, Pawn partner, bool isReceiving, SexProps props, bool whoring = false)
{
//no whoring in vanilla sex
if (props.isCoreLovin)
return;
//masturbation?
if (pawn == null || partner == null)
return;
//necro
if (pawn.Dead || partner.Dead)
return;
//bestiality
if (!(xxx.is_human(pawn) && xxx.is_human(partner)))
return;
//whoring, initiator is whore
if (whoring && !props.isReceiver)
{
ThoughtDef memory;
if (pawn.IsPrisoner || xxx.is_slave(pawn))
memory = ThoughtDefOf.Whorish_Thoughts_Captive;
else
memory = ThoughtDefOf.Whorish_Thoughts;
pawn.needs.mood.thoughts.memories.TryGainMemory(memory);
}
}
}
}

View file

@ -0,0 +1,289 @@
using HarmonyLib;
using Verse;
using System;
using RimWorld;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using rjw;
/// <summary>
/// patches Building_Bed to add stuff for WhoreBeds
///
/// Also contains smaller patches for RoomRoleWorker_Barracks (don't count whore beds) (disabled) and Toils_LayDown.ApplyBedThoughts (slept in brothel thought)
/// </summary>
namespace rjwwhoring
{
public static class harmony_Building_BedPatches
{
private static readonly Color sheetColorForWhores = new Color(181 / 255f, 55 / 255f, 109 / 255f);
// Set color for whore beds
[HarmonyPatch(typeof(Building_Bed))]
[HarmonyPatch(nameof(Building_Bed.DrawColorTwo), MethodType.Getter)]
public static class Building_Bed_DrawColor_Patch
{
[HarmonyPostfix]
public static void Postfix(Building_Bed __instance, ref Color __result)
{
if (__instance.IsAllowedForWhoringAll())
{
__result = sheetColorForWhores;
}
}
}
// add whoring toggles to beds
[HarmonyPatch(typeof(Building_Bed), nameof(Building_Bed.GetGizmos))]
public static class Building_Bed_GetGizmos_Patch
{
[HarmonyPostfix]
public static void Postfix(Building_Bed __instance, ref IEnumerable<Gizmo> __result)
{
__result = Process(__instance, __result);
}
private static IEnumerable<Gizmo> Process(Building_Bed __instance, IEnumerable<Gizmo> __result)
{
var isPrisonCell = __instance.GetRoom()?.IsPrisonCell == true;
if (!__instance.ForPrisoners && !__instance.Medical && __instance.def.building.bed_humanlike && __instance.Faction == Faction.OfPlayerSilentFail && !__instance.def.defName.Contains("Guest") && !isPrisonCell)
{
yield return
new Command_Toggle
{
defaultLabel = "CommandBedAllowWhoringLabel".Translate(),
defaultDesc = "CommandBedAllowWhoringDesc".Translate(),
icon = ContentFinder<Texture2D>.Get("UI/Commands/AsWhore"),
isActive = __instance.IsAllowedForWhoringOwner,
toggleAction = __instance.ToggleAllowedForWhoringOwner,
hotKey = KeyBindingDefOf.Misc5, // Guest Beds uses Misc4
disabled = !__instance.def.HasAssignableCompFrom(typeof(CompAssignableToPawn_Bed)),
disabledReason = "This bed type is not assignable to pawns."
};
yield return
new Command_Toggle
{
defaultLabel = "CommandBedSetAsWhoreBedLabel".Translate(),
defaultDesc = "CommandBedSetAsWhoreBedDesc".Translate(),
icon = ContentFinder<Texture2D>.Get("UI/Commands/AsWhoreMany"),
isActive = __instance.IsAllowedForWhoringAll,
toggleAction = __instance.ToggleAllowedForWhoringAll,
hotKey = KeyBindingDefOf.Misc6, // Guest Beds uses Misc4
disabled = !__instance.def.HasAssignableCompFrom(typeof(CompAssignableToPawn_Bed)),
disabledReason = "This bed type is not assignable to pawns."
};
}
foreach (var gizmo in __result)
{
if (__instance.IsAllowedForWhoringAll())
{
if (gizmo is Command_Toggle && ((Command_Toggle)gizmo).defaultLabel == "CommandBedSetAsGuestLabel".Translate())
{
// hide set as guest bed
continue;
};
// old: instead of hiding, just disable
/*switch (gizmo)
{
case Command_Toggle toggle:
{
// Disable prisoner and medical, and guest buttons
if (//toggle.defaultLabel == "CommandBedSetForPrisonersLabel".Translate() ||
//toggle.defaultLabel == "CommandBedSetAsMedicalLabel".Translate() ||
toggle.defaultLabel == "CommandBedSetAsGuestLabel".Translate()) gizmo.Disable();
break;
}
}//*/
}
yield return gizmo;
}
}
}
// add description of whore price factor to inspect string (bottom left corner if item selected)
[HarmonyPatch(typeof(Building_Bed), nameof(Building_Bed.GetInspectString))]
public static class Building_Bed_GetInspectString_Patch
{
[HarmonyPostfix]
public static void Postfix(Building_Bed __instance, ref string __result)
{
if (__instance.def.building.bed_humanlike && __instance.Faction == Faction.OfPlayerSilentFail && (__instance.IsAllowedForWhoringAll() || __instance.IsAllowedForWhoringOwner()))
{
__result = __result + "\n" + "WhorePriceCalcDesc".Translate(WhoreBed_Utility.CalculatePriceFactor(__instance).ToString("F2"));
if (WhoringBase.DebugWhoring)
{
__result = __result + "\nbed.thingIDNumber: " + __instance.thingIDNumber.ToString();
__result = __result + "\nscoreUpdateTickDelay: " + WhoringBase.DataStore.GetBedData(__instance).scoreUpdateTickDelay.ToString();
if (WhoringBase.DataStore.GetBedData(__instance).reservedUntilGameTick > GenTicks.TicksGame)
{
__result = __result + "\nreserved by pawn id: " + WhoringBase.DataStore.GetBedData(__instance).reservedForPawnID.ToString();
}
}
}
}
}
// add whore price factor as overlay
[HarmonyPatch(typeof(Building_Bed), nameof(Building_Bed.DrawGUIOverlay))]
public static class Building_Bed_DrawGUIOverlay_Patch
{
[HarmonyPrefix]
public static bool Prefix(Building_Bed __instance)
{
if (WhoringBase.show_whore_price_factor_on_bed && __instance.def.building.bed_humanlike && __instance.Faction == Faction.OfPlayerSilentFail) {
// if whore bed, print price factor on it
if (Find.CameraDriver.CurrentZoom == CameraZoomRange.Closest
&& ((__instance.IsAllowedForWhoringOwner() && __instance.OwnersForReading.Any<Pawn>())
|| __instance.IsAllowedForWhoringAll()))
{
Color defaultThingLabelColor = GenMapUI.DefaultThingLabelColor;
// make string
float whore_price_factor = WhoreBed_Utility.CalculatePriceFactor(__instance);
string wpf;
if (Math.Abs(whore_price_factor) >= 100)
{
wpf = ((int)whore_price_factor).ToString("D");
}
else if (Math.Abs(whore_price_factor) >= 10)
{
wpf = whore_price_factor.ToString("F1");
}
else
{
wpf = whore_price_factor.ToString("F2");
}
// get dimensions of text and make it appear above names
Vector2 textsize = Text.CalcSize(wpf);
Vector2 baseLabelPos = GenMapUI.LabelDrawPosFor(__instance, -0.4f); // -0.4f is copied from vanilla code
baseLabelPos.y -= textsize.y * 0.75f;
GenMapUI.DrawThingLabel(baseLabelPos, wpf, defaultThingLabelColor);
if (__instance.IsAllowedForWhoringAll() && !__instance.OwnersForReading.Any<Pawn>())
{
// hide "Unowned" if whore bed with no owner
return false;
}
}
}
// after drawing whore price factor, draw the actual names
// could have been done as a postfix, but I started with a prefix, hoping I could get by with only one draw call
return true;
}
}
// barracks don't count whore beds, so room type switches to brothel sooner
// disabled - barracks have their own slept in ~ debuff; doesn't really matter; put some effort in your brothels!
/*[HarmonyPatch(typeof(RoomRoleWorker_Barracks), nameof(RoomRoleWorker_Barracks.GetScore))]
public static class RoomRoleWorker_Barracks_GetScore_Patch
{
public static bool Prefix(Room room, ref float __result)
{
int num = 0;
int num2 = 0;
List<Thing> containedAndAdjacentThings = room.ContainedAndAdjacentThings;
for (int i = 0; i < containedAndAdjacentThings.Count; i++)
{
Building_Bed building_Bed = containedAndAdjacentThings[i] as Building_Bed;
if (building_Bed != null && building_Bed.def.building.bed_humanlike)
{
if (building_Bed.ForPrisoners)
{
__result = 0f;
return false;
}
num++;
if (!building_Bed.Medical && !building_Bed.IsAllowedForWhoringAll())
{
num2++;
}
}
}
if (num <= 1)
{
__result = 0f;
return false;
}
__result = (float)num2 * 100100f;
return false;
}
}*/
// if pawns sleep in a brothel or a whoring bed, they get a thought
[HarmonyPatch(typeof(Toils_LayDown), "ApplyBedThoughts")]
public class Toils_LayDown_ApplyBedThoughts_Patch
{
[HarmonyPostfix]
public static void Postfix(Pawn actor)
{
if (actor?.needs?.mood == null) return;
Building_Bed building_Bed = actor.CurrentBed();
actor?.needs?.mood?.thoughts?.memories?.RemoveMemoriesOfDef(ThoughtDefOf.SleptInBrothel);
if (building_Bed == null) return;
if (building_Bed?.GetRoom()?.Role == WhoreBed_Utility.roleDefBrothel || building_Bed.IsAllowedForWhoringAll())
{
var memoryHandler = actor.needs.mood.thoughts.memories;
int thoughtStage = 0;
foreach (var thoughtDef in DefDatabase<ThoughtDef_Whore>.AllDefsListForReading)
{
var memory = memoryHandler.GetFirstMemoryOfDef(thoughtDef);
if (memory?.CurStageIndex >= thoughtDef.stages.Count - 1)
{
thoughtStage = 1;
break;
}
}
memoryHandler.TryGainMemory(ThoughtMaker.MakeThought(ThoughtDefOf.SleptInBrothel, thoughtStage));
}
}
}
// if room stats are updated, update beds within
// "necessary" if beds are toggled during pause
[HarmonyPatch(typeof(Room), "UpdateRoomStatsAndRole")]
public class Room_UpdateRoomStatsAndRole_Patch
{
[HarmonyPostfix]
public static void Postfix(Room __instance)
{
// note: with room stat display enabled, this get's called quite often for whatever room the mouse hovers over
// even large outdoor areas
// where iterating over all things to find all beds (even if there are none) is expensive
// for now, skip doing anything if even the game decides it's not worth it
// (game sets role to None if region count >36 or something)
// - the beds will update eventually
if (/*Find.PlaySettings.showRoomStats && */__instance.Role == RoomRoleDefOf.None)
return;
if (Find.TickManager.Paused)
{
// if paused, update immediately
WhoreBed_Utility.CalculateBedFactorsForRoom(__instance);
}
else
{
// else, just make beds update as soon as needed
WhoreBed_Utility.ResetTicksUntilUpdate(__instance);
}
}
}
}
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Lib.Harmony" version="2.2.1" targetFramework="net472" />
</packages>

View file

@ -0,0 +1,32 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("RimJobWorld Whoring")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RimJobWorld Whoring")]
[assembly: AssemblyCopyright("Copyright © 2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c2825019-7f0b-456d-85a3-479c1a2a8805")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.0.0.0")]

25
1.3/Source/mod.sln Normal file
View file

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30907.101
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Whoring", "Mod\Whoring.csproj", "{3FC2D442-19B8-4CF9-9D35-CD13B6AC7B28}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3FC2D442-19B8-4CF9-9D35-CD13B6AC7B28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3FC2D442-19B8-4CF9-9D35-CD13B6AC7B28}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3FC2D442-19B8-4CF9-9D35-CD13B6AC7B28}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3FC2D442-19B8-4CF9-9D35-CD13B6AC7B28}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5A0C2732-36A9-4ACA-807E-019E02C37E10}
EndGlobalSection
EndGlobal

42
About/About.xml Normal file
View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<ModMetaData>
<name>RimJobWorld - Whoring</name>
<author>Ed86</author>
<url>https://gitgud.io/Ed86/rjw-whoring</url>
<supportedVersions>
<li>1.3</li>
</supportedVersions>
<packageId>rjw.whoring</packageId>
<modDependencies>
<li>
<packageId>rim.job.world</packageId>
<displayName>RJW</displayName>
<downloadUrl>https://gitgud.io/Ed86/rjw</downloadUrl>
</li>
<li>
<packageId>brrainz.harmony</packageId>
<displayName>Harmony</displayName>
<downloadUrl>https://github.com/pardeike/HarmonyRimWorld/releases/latest</downloadUrl>
<steamWorkshopUrl>steam://url/CommunityFilePage/2009463077</steamWorkshopUrl>
</li>
<li>
<packageId>UnlimitedHugs.HugsLib</packageId>
<displayName>HugsLib</displayName>
<downloadUrl>https://github.com/UnlimitedHugs/RimworldHugsLib/releases/latest</downloadUrl>
<steamWorkshopUrl>steam://url/CommunityFilePage/818773962</steamWorkshopUrl>
</li>
</modDependencies>
<loadAfter>
<li>brrainz.harmony</li>
<li>UnlimitedHugs.HugsLib</li>
<li>erdelf.HumanoidAlienRaces</li>
<li>rim.job.world</li>
</loadAfter>
<description>
<![CDATA[
Additional features to RimJobWorld:
whoring
]]>
</description>
</ModMetaData>

13
About/Manifest.xml Normal file
View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Manifest>
<identifier>RimJobWorld Whoring</identifier>
<version>1.0.0</version>
<dependencies>
<li>RimJobWorld</li>
</dependencies>
<loadAfter>
<li>RimJobWorld</li>
</loadAfter>
<manifestUri>https://gitgud.io/Ed86/rjw-whoring/raw/master/About/Manifest.xml</manifestUri>
<downloadUri>https://gitgud.io/Ed86/rjw-whoring</downloadUri>
</Manifest>

102
README.md
View file

@ -1,92 +1,16 @@
# RJW - Whoring
Requirements:
Harmony
Hugslib
Rimjobworld 5.0+(https://gitgud.io/Ed86/rjw)
Features:
Adds whoring, widgets to mark beds for whoring, beds price overlay.
Soliciting only works non colonists, guests, caravans.
Prisoners get passively solicited inside cells since they cant leave those.
Support Ed:
https://subscribestar.adult/Ed86
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin https://gitgud.io/Ed86/rjw-whoring.git
git branch -M master
git push -uf origin master
```
## Integrate with your tools
- [ ] [Set up project integrations](https://gitgud.io/Ed86/rjw-whoring/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
Discord:
https://discord.gg/CXwHhv8