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