diff --git a/1.3/Source/Mod/Whoring.csproj b/1.3/Source/Mod/Whoring.csproj
index 3030a5d..80f1f7d 100644
--- a/1.3/Source/Mod/Whoring.csproj
+++ b/1.3/Source/Mod/Whoring.csproj
@@ -40,7 +40,7 @@
False
- ..\..\..\..\..\..\..\workshop\content\294100\818773962\v1.2\Assemblies\HugsLib.dll
+ ..\..\..\..\..\..\..\workshop\content\294100\818773962\v1.3\Assemblies\HugsLib.dll
False
diff --git a/1.4/Assemblies/RimJobWorldWhoring.dll b/1.4/Assemblies/RimJobWorldWhoring.dll
new file mode 100644
index 0000000..af89fa2
Binary files /dev/null and b/1.4/Assemblies/RimJobWorldWhoring.dll differ
diff --git a/1.4/Defs/JobDefs/Jobs_Whoring.xml b/1.4/Defs/JobDefs/Jobs_Whoring.xml
new file mode 100644
index 0000000..d3e6bed
--- /dev/null
+++ b/1.4/Defs/JobDefs/Jobs_Whoring.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ WhoreIsServingVisitors
+ rjwwhoring.JobDriver_WhoreIsServingVisitors
+ serving visitors
+ false
+
+
+
+ WhoreInvitingVisitors
+ rjwwhoring.JobDriver_WhoreInvitingVisitors
+ attempting hookup
+ false
+
+
\ No newline at end of file
diff --git a/1.4/Defs/RecordDefs/Records.xml b/1.4/Defs/RecordDefs/Records.xml
new file mode 100644
index 0000000..f94897f
--- /dev/null
+++ b/1.4/Defs/RecordDefs/Records.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+ CountOfWhore
+ times whored
+ The number of times I whored myself.
+ Int
+
+
+
+ EarnedMoneyByWhore
+ silvers earned as a whore
+ The amount of silvers I have earned as a whore.
+ Int
+
+
diff --git a/1.4/Defs/Rooms/RoomRoles.xml b/1.4/Defs/Rooms/RoomRoles.xml
new file mode 100644
index 0000000..855f0bd
--- /dev/null
+++ b/1.4/Defs/Rooms/RoomRoles.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ Brothel
+ brothel
+ rjwwhoring.RoomRoleWorker_Brothel
+
+ Beauty
+ Cleanliness
+ Wealth
+ Space
+ Impressiveness
+
+
+
+
\ No newline at end of file
diff --git a/1.4/Defs/ThinkTreeDefs/ThinkTrees_Prisoner.xml b/1.4/Defs/ThinkTreeDefs/ThinkTrees_Prisoner.xml
new file mode 100644
index 0000000..8af3edf
--- /dev/null
+++ b/1.4/Defs/ThinkTreeDefs/ThinkTrees_Prisoner.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ PrisonerWhoreSexualEmergencyTree
+ Humanlike_PostDuty
+ 100
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/1.4/Defs/ThinkTreeDefs/ThinkTrees_Whore.xml b/1.4/Defs/ThinkTreeDefs/ThinkTrees_Whore.xml
new file mode 100644
index 0000000..def7e6b
--- /dev/null
+++ b/1.4/Defs/ThinkTreeDefs/ThinkTrees_Whore.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+ WhoreSexualEmergencyTree
+ Humanlike_PreMain
+ 15
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ WhoreJobTree
+ Humanlike_PostMain
+ 15
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/1.4/Defs/ThoughtDefs/Thoughts_Whore.xml b/1.4/Defs/ThoughtDefs/Thoughts_Whore.xml
new file mode 100644
index 0000000..0bbd196
--- /dev/null
+++ b/1.4/Defs/ThoughtDefs/Thoughts_Whore.xml
@@ -0,0 +1,125 @@
+
+
+
+
+ Whorish_Thoughts
+ rjwwhoring.ThoughtWorker_Whore
+ 2.0
+ 10
+ 0.4
+
+
+
+ 30
+ 50
+
+ 30
+
+
+ Resentful whore
+ We just need money! I'm not a ...
+ -5
+
+
+ Whore
+ Well, at least it pays well...
+ -1
+
+
+ WHORE
+ This job isn't so bad afterall!
+ 2
+
+
+
+
+
+ Whorish_Thoughts_Captive
+ rjwwhoring.ThoughtWorker_Whore
+ 4.0
+ 10
+ 0.4
+
+
+
+ 30
+ 40
+ 80
+
+ 10
+
+
+
+ Forced whore
+ They forced me to serve as a sex toy!
+ -20
+
+
+ Unwilling whore
+ I'm not just a rental ride!
+ -10
+
+
+ Accustomed whore
+ Just no beatings, please.
+ -1
+
+
+ Complete whore
+ I could get more clients if not these restraints!
+ 2
+
+
+
+
+
+
+ RJWFailedSolicitation
+ Thought_MemorySocial
+ 0.4
+ 100
+ 1
+ 0.5
+
+
+ tried to solicit me
+ -1
+
+
+
+
+
+
+ RJWTurnedDownWhore
+ Thought_MemorySocial
+ 0.2
+ 1
+ 1
+ 0.5
+
+
+ bothered me
+ -1
+
+
+
+
+
+ SleptInBrothel
+ 1
+ 1
+ 1
+
+
+ slept in brothel
+ Eww, the sheets were all sticky.
+ -10
+
+
+ slept in brothel!
+ I just love this place, the smell, the sounds...
+ 2
+
+
+
+
diff --git a/1.4/Defs/TipSetDefs/Tips.xml b/1.4/Defs/TipSetDefs/Tips.xml
new file mode 100644
index 0000000..4f36f27
--- /dev/null
+++ b/1.4/Defs/TipSetDefs/Tips.xml
@@ -0,0 +1,13 @@
+
+
+
+ RjwWhoringTips
+
+
+ Whoring price is visible in the "show sexuality" menu on the bio tab (the icon looks like a heart).
+
+
+ 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.
+
+
+
\ No newline at end of file
diff --git a/1.4/Defs/WhoreBackstories.xml b/1.4/Defs/WhoreBackstories.xml
new file mode 100644
index 0000000..329d45c
--- /dev/null
+++ b/1.4/Defs/WhoreBackstories.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+ WhoreBackstories
+
+
+
+
+
+ Sex slave
+ Courtesean
+ Housemate
+ Model
+ idol
+
+ Nymph
+
+ Pleasure
+ Courtesan
+
+ Dancer
+ Holo-star
+
+ Prostitute
+ Breeder
+
+
+
\ No newline at end of file
diff --git a/1.4/Defs/WhoringTab/PawnColumnDefs/PawnColumns.xml b/1.4/Defs/WhoringTab/PawnColumnDefs/PawnColumns.xml
new file mode 100644
index 0000000..63985a5
--- /dev/null
+++ b/1.4/Defs/WhoringTab/PawnColumnDefs/PawnColumns.xml
@@ -0,0 +1,52 @@
+
+
+
+ RJW_IsWhore
+ Whores
+ rjwwhoring.MainTab.PawnColumnWorker_IsWhore
+ true
+ 80
+
+
+ RJW_WhoreExperience
+ Whoring experience
+ Experience
+ rjwwhoring.MainTab.PawnColumnWorker_WhoreExperience
+ 100
+
+
+ RJW_PriceRangeOfWhore
+ Price range for whore
+ Price
+ rjwwhoring.MainTab.PawnColumnWorker_PriceRangeOfWhore
+ 100
+
+
+ RJW_EarnedMoneyByWhore
+ Money earned(total)
+ Earned
+ rjwwhoring.MainTab.PawnColumnWorker_EarnedMoneyByWhore
+ 100
+
+
+ RJW_CountOfWhore
+ Clients served
+ Clients
+ rjwwhoring.MainTab.PawnColumnWorker_CountOfWhore
+ 100
+
+
+ RJW_AverageMoneyByWhore
+ Money earned(average)
+ Average
+ rjwwhoring.MainTab.PawnColumnWorker_AverageMoneyByWhore
+ 100
+
+
+ RJW_WhoreMood
+ Mood of pawn
+ Mood
+ rjwwhoring.MainTab.PawnColumnWorker_Mood
+ 100
+
+
diff --git a/1.4/Defs/WhoringTab/PawnTableDefs.xml b/1.4/Defs/WhoringTab/PawnTableDefs.xml
new file mode 100644
index 0000000..f321b97
--- /dev/null
+++ b/1.4/Defs/WhoringTab/PawnTableDefs.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ RJW_Brothel
+ rjwwhoring.MainTab.PawnTable_Whores
+
+ Label
+ GapTiny
+ RJW_Gender
+ GapTiny
+ LifeStage
+ GapTiny
+ RJW_IsPrisoner
+ RJW_IsSlave
+ GapTiny
+ RJW_IsWhore
+ RJW_WhoreMood
+ RJW_CountOfWhore
+ RJW_WhoreExperience
+ RJW_EarnedMoneyByWhore
+ RJW_AverageMoneyByWhore
+ RJW_PriceRangeOfWhore
+ RemainingSpace
+
+
+
+ Brothel
+
+
+
+
+
diff --git a/1.4/Languages/English/Keyed/Whoring.xml b/1.4/Languages/English/Keyed/Whoring.xml
new file mode 100644
index 0000000..401855b
--- /dev/null
+++ b/1.4/Languages/English/Keyed/Whoring.xml
@@ -0,0 +1,43 @@
+
+
+ Enable whoring tab
+ Shows/hide whoring tab
+ Show whore price factor on beds
+ Show whore price factor as a label on beds that are enabled for whoring.
+ Show bed widgets
+ Show bed widgets to mark beds for public/private for whoring.
+
+ Money Printing
+ Clients will spawn silver instead of using their own/caravan
+ Client RNG disable
+ Instead of doing rng roll, Clients always accept solicitation.
+
+ Debug log whoring info
+ Enables some very spamming debug logs to help find bugs in Whoring solicitation operations.
+
+
+ {0} accepted the deal {1} offered.
+ {0} wants to be serviced by {1}.
+ {0} rejected the deal {1} offered.
+ {0} rejected the offer because {1} does not look healthy
+
+
+ Assign to whorin'
+
+ Won't agree to be a whore
+
+
+ Allow everyone to whore
+ Allow all whores to use this bed to entertain customers.
+ Allow owner to whore
+ Whether owner(s) are allowed to use this bed for whoring.
+ Whoring price factor (based on comfort, room impressiveness and number of beds in room: {0}
+
+ Whoring price range:
+ Prisoner
+ Slave
+
\ No newline at end of file
diff --git a/1.4/Patches/FacialAnimation_compatibility.xml b/1.4/Patches/FacialAnimation_compatibility.xml
new file mode 100644
index 0000000..8cae3d9
--- /dev/null
+++ b/1.4/Patches/FacialAnimation_compatibility.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ [NL] Facial Animation - WIP
+
+
+ Always
+
+
+ /Defs/FacialAnimation.FaceAnimationDef[defName="Lovin" or defName="Lovin2"]/targetJobs
+ Always
+
+ WhoreIsServingVisitors
+
+
+
+ /Defs/FacialAnimation.FaceAnimationDef[defName="StandAndBeSociallyActive"]/targetJobs
+ Always
+
+ WhoreInvitingVisitors
+
+
+
+
+
+
\ No newline at end of file
diff --git a/1.4/Source/Mod/Data/BedData.cs b/1.4/Source/Mod/Data/BedData.cs
new file mode 100644
index 0000000..5d6a1ff
--- /dev/null
+++ b/1.4/Source/Mod/Data/BedData.cs
@@ -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; } }
+ }
+}
diff --git a/1.4/Source/Mod/Data/DataStore.cs b/1.4/Source/Mod/Data/DataStore.cs
new file mode 100644
index 0000000..1f3566b
--- /dev/null
+++ b/1.4/Source/Mod/Data/DataStore.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using Verse;
+using RimWorld;
+using RimWorld.Planet;
+
+namespace rjwwhoring
+{
+ ///
+ /// Rimworld object for storing the world/save info
+ ///
+ public class DataStore : WorldComponent
+ {
+ public Dictionary bedData = new Dictionary();
+
+ 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();
+ }
+ }
+
+ 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/1.4/Source/Mod/Data/StringListDef.cs b/1.4/Source/Mod/Data/StringListDef.cs
new file mode 100644
index 0000000..541b00c
--- /dev/null
+++ b/1.4/Source/Mod/Data/StringListDef.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+using Verse;
+
+namespace rjwwhoring
+{
+ ///
+ /// Whore backstories from xml GET!
+ /// Just a simplest form of passing data from xml to the code
+ ///
+ public class StringListDef : Def
+ {
+ public List strings = new List();
+ }
+}
diff --git a/1.4/Source/Mod/DefOf/RecordDefDefOf.cs b/1.4/Source/Mod/DefOf/RecordDefDefOf.cs
new file mode 100644
index 0000000..0a13f1f
--- /dev/null
+++ b/1.4/Source/Mod/DefOf/RecordDefDefOf.cs
@@ -0,0 +1,12 @@
+using RimWorld;
+using Verse;
+
+namespace rjwwhoring
+{
+ [DefOf]
+ public static class RecordDefOf
+ {
+ public static RecordDef EarnedMoneyByWhore;
+ public static RecordDef CountOfWhore;
+ }
+}
diff --git a/1.4/Source/Mod/DefOf/ThoughtDefOf.cs b/1.4/Source/Mod/DefOf/ThoughtDefOf.cs
new file mode 100644
index 0000000..b7a2e14
--- /dev/null
+++ b/1.4/Source/Mod/DefOf/ThoughtDefOf.cs
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/1.4/Source/Mod/JobDrivers/JobDriver_WhoreInvitingVisitors.cs b/1.4/Source/Mod/JobDrivers/JobDriver_WhoreInvitingVisitors.cs
new file mode 100644
index 0000000..3e6aa9a
--- /dev/null
+++ b/1.4/Source/Mod/JobDrivers/JobDriver_WhoreInvitingVisitors.cs
@@ -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 allowedJobs = new List { 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 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 extraSentencePacks = new List();
+ 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.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.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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/1.4/Source/Mod/JobDrivers/JobDriver_WhoreIsServingVisitors.cs b/1.4/Source/Mod/JobDrivers/JobDriver_WhoreIsServingVisitors.cs
new file mode 100644
index 0000000..ecd45d2
--- /dev/null
+++ b/1.4/Source/Mod/JobDrivers/JobDriver_WhoreIsServingVisitors.cs
@@ -0,0 +1,183 @@
+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 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() + ":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);
+ int basePrice = WhoringHelper.PriceOfWhore(pawn);
+ float bedMult = WhoreBed_Utility.CalculatePriceFactor(Bed);
+ //yield return Toils_Reserve.Reserve(BedInd, Bed.SleepingSlotsCount, 0);
+
+ if (WhoringBase.DebugWhoring) ModLog.Message("" + this.GetType().ToString() + ":generate job 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() + ":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() + ":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() + ":SexToil start");
+
+ // 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 netPrice = (int) (basePrice * bedMult);
+ if (netPrice == 0)
+ netPrice += 1;
+
+ int bedTip = netPrice - basePrice;
+ int defect = WhoringHelper.PayPriceToWhore(Partner, netPrice, pawn);
+
+ if (WhoringBase.DebugWhoring)
+ {
+ ModLog.Message($"{GetType()}:afterSex toil - {Partner} tried to pay {basePrice}(whore price) + {bedTip}(room modifier) 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(pawn.Map);
+
+ Job clean = JobMaker.MakeJob(JobDefOf.Clean);
+ clean.AddQueuedTarget(TargetIndex.A, cum);
+
+ pawn.jobs.jobQueue.EnqueueFirst(clean);
+ }
+ },
+ defaultCompleteMode = ToilCompleteMode.Instant
+ };
+ yield return afterSex;
+ }
+ }
+}
diff --git a/1.4/Source/Mod/JobGivers/JobGiver_WhoreInvitingVisitors.cs b/1.4/Source/Mod/JobGivers/JobGiver_WhoreInvitingVisitors.cs
new file mode 100644
index 0000000..c438e29
--- /dev/null
+++ b/1.4/Source/Mod/JobGivers/JobGiver_WhoreInvitingVisitors.cs
@@ -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 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 valid_targets = new List();
+
+ 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 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 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 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/1.4/Source/Mod/Location/Brothel_Room.cs b/1.4/Source/Mod/Location/Brothel_Room.cs
new file mode 100644
index 0000000..27921c3
--- /dev/null
+++ b/1.4/Source/Mod/Location/Brothel_Room.cs
@@ -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
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/1.4/Source/Mod/ThinkTreeNodes/ThinkNode_ChancePerHour_Whore.cs b/1.4/Source/Mod/ThinkTreeNodes/ThinkNode_ChancePerHour_Whore.cs
new file mode 100644
index 0000000..0239322
--- /dev/null
+++ b/1.4/Source/Mod/ThinkTreeNodes/ThinkNode_ChancePerHour_Whore.cs
@@ -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();
+ 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;
+ }
+ }
+ */
+}
\ No newline at end of file
diff --git a/1.4/Source/Mod/ThinkTreeNodes/ThinkNode_ConditionalWhore.cs b/1.4/Source/Mod/ThinkTreeNodes/ThinkNode_ConditionalWhore.cs
new file mode 100644
index 0000000..fcfa9d5
--- /dev/null
+++ b/1.4/Source/Mod/ThinkTreeNodes/ThinkNode_ConditionalWhore.cs
@@ -0,0 +1,25 @@
+using Verse;
+using Verse.AI;
+using RimWorld;
+using rjw;
+
+namespace rjwwhoring
+{
+ ///
+ /// Whore/prisoner look for customers
+ ///
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/1.4/Source/Mod/Thoughts/ThoughtWorker_Whore.cs b/1.4/Source/Mod/Thoughts/ThoughtWorker_Whore.cs
new file mode 100644
index 0000000..cbe88f9
--- /dev/null
+++ b/1.4/Source/Mod/Thoughts/ThoughtWorker_Whore.cs
@@ -0,0 +1,43 @@
+using RimWorld;
+using Verse;
+using System.Collections.Generic;
+
+namespace rjwwhoring
+{
+ ///
+ /// Extends the standard thought to add a counter for the whore stages
+ ///
+ public class ThoughtDef_Whore : ThoughtDef
+ {
+ public List stageCounts = new List();
+ public int storyOffset = 0;
+ }
+
+ public class ThoughtWorker_Whore : Thought_Memory
+ {
+ public static readonly HashSet backstories = new HashSet(DefDatabase.GetNamed("WhoreBackstories").strings);
+
+ protected List 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;
+ }
+ }
+ }
+}
diff --git a/1.4/Source/Mod/Whoring.csproj b/1.4/Source/Mod/Whoring.csproj
new file mode 100644
index 0000000..b213640
--- /dev/null
+++ b/1.4/Source/Mod/Whoring.csproj
@@ -0,0 +1,112 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {3FC2D442-19B8-4CF9-9D35-CD13B6AC7B28}
+ Library
+ rjwwhoring
+ RimJobWorldWhoring
+ v4.7.2
+ 512
+
+
+ AnyCPU
+ true
+ full
+ false
+ ..\..\Assemblies\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ ..\..\Assemblies\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Lib.Harmony.2.2.2\lib\net472\0Harmony.dll
+ False
+
+
+ ..\..\..\..\..\RimWorldWin64_Data\Managed\Assembly-CSharp.dll
+ False
+
+
+ ..\..\..\..\..\..\..\workshop\content\294100\818773962\v1.4\Assemblies\HugsLib.dll
+ False
+
+
+ ..\..\..\..\rjw\1.4\Assemblies\RJW.dll
+ False
+
+
+
+
+ ..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll
+ False
+
+
+ ..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.IMGUIModule.dll
+ False
+
+
+ ..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.InputLegacyModule.dll
+ False
+
+
+ ..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.InputModule.dll
+ False
+
+
+ ..\..\..\..\..\RimWorldWin64_Data\Managed\UnityEngine.TextRenderingModule.dll
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/1.4/Source/Mod/WhoringBase.cs b/1.4/Source/Mod/WhoringBase.cs
new file mode 100644
index 0000000..0edb576
--- /dev/null
+++ b/1.4/Source/Mod/WhoringBase.cs
@@ -0,0 +1,71 @@
+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();
+ //ToggleTabIfNeeded();
+ }
+
+ private void ToggleTabIfNeeded()
+ {
+ //DefDatabase.GetNamed("RJW_Brothel").buttonVisible = whoringtab_enabled;
+ }
+
+ //public static SettingHandle whoringtab_enabled;
+ public static SettingHandle show_whore_price_factor_on_bed;
+ public static SettingHandle show_whore_widgets_on_bed;
+ public static SettingHandle DebugWhoring;
+ public static SettingHandle MoneyPrinting;
+ public static SettingHandle 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);
+ show_whore_widgets_on_bed = Settings.GetHandle("show_whore_widgets_on_bed",
+ "show_whore_widgets_on_bed".Translate(),
+ "show_whore_widgets_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);
+ }
+ }
+}
diff --git a/1.4/Source/Mod/WhoringTab/PawnColumnCheckbox_Whore.cs b/1.4/Source/Mod/WhoringTab/PawnColumnCheckbox_Whore.cs
new file mode 100644
index 0000000..72bb337
--- /dev/null
+++ b/1.4/Source/Mod/WhoringTab/PawnColumnCheckbox_Whore.cs
@@ -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 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();
+ }
+ }
+}
+
diff --git a/1.4/Source/Mod/WhoringTab/PawnColumnWorker_AverageMoneyByWhore.cs b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_AverageMoneyByWhore.cs
new file mode 100644
index 0000000..8a38a33
--- /dev/null
+++ b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_AverageMoneyByWhore.cs
@@ -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);
+ }
+ }
+}
diff --git a/1.4/Source/Mod/WhoringTab/PawnColumnWorker_CountOfWhore.cs b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_CountOfWhore.cs
new file mode 100644
index 0000000..0e4cf05
--- /dev/null
+++ b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_CountOfWhore.cs
@@ -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);
+ }
+ }
+}
diff --git a/1.4/Source/Mod/WhoringTab/PawnColumnWorker_EarnedMoneyByWhore.cs b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_EarnedMoneyByWhore.cs
new file mode 100644
index 0000000..3665822
--- /dev/null
+++ b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_EarnedMoneyByWhore.cs
@@ -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);
+ }
+ }
+}
diff --git a/1.4/Source/Mod/WhoringTab/PawnColumnWorker_IsWhore.cs b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_IsWhore.cs
new file mode 100644
index 0000000..cf02240
--- /dev/null
+++ b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_IsWhore.cs
@@ -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.Get("UI/Tab/Service_on");
+ private static readonly Texture2D serviceOff = ContentFinder.Get("UI/Tab/Service_off");
+
+ protected override Texture2D GetIconFor(Pawn pawn)
+ {
+ return pawn.IsDesignatedService() ? serviceOn : null;
+ }*/
+ }
+}
diff --git a/1.4/Source/Mod/WhoringTab/PawnColumnWorker_Mood.cs b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_Mood.cs
new file mode 100644
index 0000000..f4a6f6b
--- /dev/null
+++ b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_Mood.cs
@@ -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;
+ }
+ }
+}
diff --git a/1.4/Source/Mod/WhoringTab/PawnColumnWorker_PriceRangeOfWhore.cs b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_PriceRangeOfWhore.cs
new file mode 100644
index 0000000..220713b
--- /dev/null
+++ b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_PriceRangeOfWhore.cs
@@ -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;
+ }
+ }
+}
diff --git a/1.4/Source/Mod/WhoringTab/PawnColumnWorker_TextCenter.cs b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_TextCenter.cs
new file mode 100644
index 0000000..f72193f
--- /dev/null
+++ b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_TextCenter.cs
@@ -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);
+ }
+ }
+ }
+ }
+}
diff --git a/1.4/Source/Mod/WhoringTab/PawnColumnWorker_WhoreExperience.cs b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_WhoreExperience.cs
new file mode 100644
index 0000000..c0d85ba
--- /dev/null
+++ b/1.4/Source/Mod/WhoringTab/PawnColumnWorker_WhoreExperience.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using Verse;
+
+namespace rjwwhoring.MainTab
+{
+ [StaticConstructorOnStartup]
+ public class PawnColumnWorker_WhoreExperience : PawnColumnWorker_TextCenter
+ {
+ public static readonly HashSet backstories = new HashSet(DefDatabase.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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/1.4/Source/Mod/WhoringTab/PawnTable_Whores.cs b/1.4/Source/Mod/WhoringTab/PawnTable_Whores.cs
new file mode 100644
index 0000000..3a25bb4
--- /dev/null
+++ b/1.4/Source/Mod/WhoringTab/PawnTable_Whores.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using RimWorld;
+using rjw;
+using Verse;
+
+namespace rjwwhoring.MainTab
+{
+ public class PawnTable_Whores : PawnTable_PlayerPawns
+ {
+ public PawnTable_Whores(PawnTableDef def, Func> pawnsGetter, int uiWidth, int uiHeight) : base(def, pawnsGetter, uiWidth, uiHeight) { }
+
+ //default sorting
+ protected override IEnumerable LabelSortFunction(IEnumerable input)
+ {
+ //return input.OrderBy(p => p.Name);
+ foreach (Pawn p in input)
+ p.UpdatePermissions();
+ return input.OrderByDescending(p => (p.IsPrisonerOfColony || p.IsSlaveOfColony) != false).ThenBy(p => xxx.get_pawnname(p));
+ //return input.OrderByDescending(p => (p.IsPrisonerOfColony || p.IsSlaveOfColony) != false).ThenBy(p => (p.Name.ToStringShort.Colorize(Color.yellow)));
+ //return input.OrderBy(p => xxx.get_pawnname(p));
+ }
+
+ protected override IEnumerable PrimarySortFunction(IEnumerable input)
+ {
+ foreach (Pawn p in input)
+ p.UpdatePermissions();
+ return input;
+ //return base.PrimarySortFunction(input);
+ }
+ }
+}
\ No newline at end of file
diff --git a/1.4/Source/Mod/WhoringTab/WhoreCheckbox.cs b/1.4/Source/Mod/WhoringTab/WhoreCheckbox.cs
new file mode 100644
index 0000000..1f21bdb
--- /dev/null
+++ b/1.4/Source/Mod/WhoringTab/WhoreCheckbox.cs
@@ -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.Get("UI/Commands/Service_on");
+ public static readonly Texture2D WhoreCheckboxOffTex = ContentFinder.Get("UI/Commands/Service_off");
+ public static readonly Texture2D WhoreCheckboxDisabledTex = ContentFinder.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);
+ }
+
+ }
+}
diff --git a/1.4/Source/Mod/Whoring_Bed_Utilities.cs b/1.4/Source/Mod/Whoring_Bed_Utilities.cs
new file mode 100644
index 0000000..a93a898
--- /dev/null
+++ b/1.4/Source/Mod/Whoring_Bed_Utilities.cs
@@ -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.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 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 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 beds = FindReachableAndAvailableWhoreBeds(whore, customer);
+ return GetCheapestBedFactor(beds);
+ }
+ public static float GetCheapestBedFactor(List beds)
+ {
+ if (beds != null && beds.Any())
+ {
+ return CalculatePriceFactor(beds.MinBy(bed => CalculatePriceFactor(bed)));
+ }
+ else
+ {
+ return 0f;
+ }
+ }
+ // unused
+ /*public static float GetMostExpensiveBedFactor(List 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 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 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 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 FindReachableAndAvailableWhoreBeds(Pawn whore, Pawn customer)
+ {
+ List wb = new List();
+
+ 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 GetWhoreBeds(this Map map, Area area = null)
+ {
+ if (map == null) return new Building_Bed[0];
+ if (area == null) return map.listerBuildings.AllBuildingsColonistOfClass();
+ return map.listerBuildings.AllBuildingsColonistOfClass().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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/1.4/Source/Mod/Whoring_Helper.cs b/1.4/Source/Mod/Whoring_Helper.cs
new file mode 100644
index 0000000..d313a98
--- /dev/null
+++ b/1.4/Source/Mod/Whoring_Helper.cs
@@ -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
+{
+ ///
+ /// Helper for whoring related stuff
+ ///
+ public class WhoringHelper
+ {
+ public const float baseMinPrice = 10f;
+ public const float baseMaxPrice = 20f;
+
+ public static readonly HashSet backstories = new HashSet(DefDatabase.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 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 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 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;
+ }
+
+ ///
+ /// Check if the pawn is willing to hook up. Checked for both target and the whore(when hooking colonists).
+ ///
+ //[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;
+ }
+
+ ///
+ /// Updates records for whoring.
+ ///
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/1.4/Source/Mod/harmony_AftersexPatch.cs b/1.4/Source/Mod/harmony_AftersexPatch.cs
new file mode 100644
index 0000000..e7accc6
--- /dev/null
+++ b/1.4/Source/Mod/harmony_AftersexPatch.cs
@@ -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
+ {
+ ///
+ ///add aftersex thoughts for whoring
+ ///
+ 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);
+ }
+ }
+ }
+}
diff --git a/1.4/Source/Mod/harmony_Building_BedPatches.cs b/1.4/Source/Mod/harmony_Building_BedPatches.cs
new file mode 100644
index 0000000..852b633
--- /dev/null
+++ b/1.4/Source/Mod/harmony_Building_BedPatches.cs
@@ -0,0 +1,292 @@
+using HarmonyLib;
+using Verse;
+using System;
+using RimWorld;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using rjw;
+
+///
+/// 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)
+///
+
+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 __result)
+ {
+ if (!WhoringBase.show_whore_widgets_on_bed)
+ {
+ return;
+ }
+ __result = Process(__instance, __result);
+ }
+
+ private static IEnumerable Process(Building_Bed __instance, IEnumerable __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.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.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())
+ || __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())
+ {
+ // 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 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.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);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/1.4/Source/Mod/harmony_RJWTab_patch.cs b/1.4/Source/Mod/harmony_RJWTab_patch.cs
new file mode 100644
index 0000000..5f32cd0
--- /dev/null
+++ b/1.4/Source/Mod/harmony_RJWTab_patch.cs
@@ -0,0 +1,53 @@
+using Verse;
+using HarmonyLib;
+using rjw;
+using System;
+using RimWorld;
+using System.Collections.Generic;
+using UnityEngine;
+using rjw.MainTab.DefModExtensions;
+using System.Linq;
+using System.Reflection;
+
+namespace rjwwhoring
+{
+ [HarmonyPatch(typeof(rjw.MainTab.MainTabWindow))]
+ [HarmonyPatch(nameof(rjw.MainTab.MainTabWindow.MakeOptions))]
+ static class RJWTab_Brothel
+ {
+ [HarmonyPostfix]
+ private static void MakeOptionsPatch(rjw.MainTab.MainTabWindow __instance, ref List __result)
+ {
+ try
+ {
+ RJWTab_Brothel_Patch.MakeOptionsPatch(__instance, ref __result);
+ }
+ catch (Exception e)
+ {
+ Log.Error(e.ToString());
+ }
+ }
+ }
+
+ static class RJWTab_Brothel_Patch
+ {
+ public static List MakeOptionsPatch(rjw.MainTab.MainTabWindow __instance, ref List __result)
+ {
+ PawnTableDef RJW_Brothel = DefDatabase.GetNamed("RJW_Brothel");
+ ModLog.Message("0");
+ __result.Add(new FloatMenuOption(RJW_Brothel.GetModExtension().label, () =>
+ {
+ ModLog.Message("1");
+ __instance.pawnTableDef = RJW_Brothel;
+ ModLog.Message("2");
+ __instance.pawns = Find.CurrentMap.mapPawns.AllPawns.Where(p => xxx.is_human(p) && (p.IsColonist || p.IsPrisonerOfColony));
+ ModLog.Message("3");
+ __instance.Notify_ResolutionChanged();
+ ModLog.Message("4");
+ rjw.MainTab.MainTabWindow.Reloadtab();
+ ModLog.Message("5");
+ }, MenuOptionPriority.Default));
+ return __result;
+ }
+ }
+}
diff --git a/1.4/Source/Mod/packages.config b/1.4/Source/Mod/packages.config
new file mode 100644
index 0000000..c5bef78
--- /dev/null
+++ b/1.4/Source/Mod/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/1.4/Source/Properties/AssemblyInfo.cs b/1.4/Source/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..7b4716d
--- /dev/null
+++ b/1.4/Source/Properties/AssemblyInfo.cs
@@ -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")]
diff --git a/1.4/Source/mod.sln b/1.4/Source/mod.sln
new file mode 100644
index 0000000..981a536
--- /dev/null
+++ b/1.4/Source/mod.sln
@@ -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
diff --git a/1.4/Textures/UI/Commands/AsWhore.png b/1.4/Textures/UI/Commands/AsWhore.png
new file mode 100644
index 0000000..b5f14e1
Binary files /dev/null and b/1.4/Textures/UI/Commands/AsWhore.png differ
diff --git a/1.4/Textures/UI/Commands/AsWhoreMany.png b/1.4/Textures/UI/Commands/AsWhoreMany.png
new file mode 100644
index 0000000..f7c1989
Binary files /dev/null and b/1.4/Textures/UI/Commands/AsWhoreMany.png differ
diff --git a/1.4/Textures/UI/Commands/Service_Refuse.png b/1.4/Textures/UI/Commands/Service_Refuse.png
new file mode 100644
index 0000000..7b57c3b
Binary files /dev/null and b/1.4/Textures/UI/Commands/Service_Refuse.png differ
diff --git a/1.4/Textures/UI/Commands/Service_off.png b/1.4/Textures/UI/Commands/Service_off.png
new file mode 100644
index 0000000..3b46e97
Binary files /dev/null and b/1.4/Textures/UI/Commands/Service_off.png differ
diff --git a/1.4/Textures/UI/Commands/Service_on.png b/1.4/Textures/UI/Commands/Service_on.png
new file mode 100644
index 0000000..b427f88
Binary files /dev/null and b/1.4/Textures/UI/Commands/Service_on.png differ
diff --git a/1.4/Textures/UI/Tab/ComfortPrisoner_off.png b/1.4/Textures/UI/Tab/ComfortPrisoner_off.png
new file mode 100644
index 0000000..7cb8f64
Binary files /dev/null and b/1.4/Textures/UI/Tab/ComfortPrisoner_off.png differ
diff --git a/1.4/Textures/UI/Tab/ComfortPrisoner_off_nobg.png b/1.4/Textures/UI/Tab/ComfortPrisoner_off_nobg.png
new file mode 100644
index 0000000..ce033a1
Binary files /dev/null and b/1.4/Textures/UI/Tab/ComfortPrisoner_off_nobg.png differ
diff --git a/1.4/Textures/UI/Tab/ComfortPrisoner_on.png b/1.4/Textures/UI/Tab/ComfortPrisoner_on.png
new file mode 100644
index 0000000..c2a76ee
Binary files /dev/null and b/1.4/Textures/UI/Tab/ComfortPrisoner_on.png differ
diff --git a/1.4/Textures/UI/Tab/Service_off.png b/1.4/Textures/UI/Tab/Service_off.png
new file mode 100644
index 0000000..510041b
Binary files /dev/null and b/1.4/Textures/UI/Tab/Service_off.png differ
diff --git a/1.4/Textures/UI/Tab/Service_on.png b/1.4/Textures/UI/Tab/Service_on.png
new file mode 100644
index 0000000..cdcc073
Binary files /dev/null and b/1.4/Textures/UI/Tab/Service_on.png differ
diff --git a/About/About.xml b/About/About.xml
index 4bea346..f068906 100644
--- a/About/About.xml
+++ b/About/About.xml
@@ -6,6 +6,7 @@
https://gitgud.io/Ed86/rjw-whoring
1.3
+ 1.4
rjw.whoring