code:
using RimWorld;
using Verse;

namespace rjw
{
	public class ThoughtWorker_NeedSex : ThoughtWorker
	{
		protected override ThoughtState CurrentStateInternal(Pawn p)
		{
			var sex_need = p.needs.TryGetNeed<Need_Sex>();

			if (sex_need != null)
				if (xxx.can_do_loving(p))
			{
				var lev = sex_need.CurLevel;
				if (lev <= sex_need.thresh_frustrated())
					return ThoughtState.ActiveAtStage(0);
				else if (lev <= sex_need.thresh_horny())
					return ThoughtState.ActiveAtStage(1);
				else if (lev >= sex_need.thresh_satisfied())
					return ThoughtState.ActiveAtStage(2);
			}

			return ThoughtState.Inactive;
		}
	}
}


code:
using RimWorld;
using Verse;

namespace rjw
{
	//This thought system of RW is retarded AF. It needs separate thought handler for each hediff.
	public abstract class ThoughtWorker_SexChange : ThoughtWorker
	{
		public virtual HediffDef hediff_served { get; }
		protected override ThoughtState CurrentStateInternal(Pawn pawn)
		{
			//Log.Message(" "+this.GetType() + " is called for " + pawn +" and hediff" + hediff_served);
			Hediff denial = pawn.health.hediffSet.GetFirstHediffOfDef(hediff_served);
			//Log.Message("Hediff of the class is null " + (hediff_served == null));
			if (denial != null && denial.CurStageIndex!=0)
			{
				//Log.Message("Current denial level is  " + denial.CurStageIndex );
				return ThoughtState.ActiveAtStage(denial.CurStageIndex-1);
			}
			return ThoughtState.Inactive;
		}
	}
	public class ThoughtWorker_MtT : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.m2t; } }
	}
	public class ThoughtWorker_MtF:ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.m2f; }  }
	}
	public class ThoughtWorker_MtH : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.m2h; } }
	}
	public class ThoughtWorker_FtT : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.f2t; } }
	}
	public class ThoughtWorker_FtM : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.f2m; } }
	}
	public class ThoughtWorker_FtH : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.f2h; } }
	}
	public class ThoughtWorker_HtT : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.h2t; } }
	}
	public class ThoughtWorker_HtM : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.h2m; } }
	}
	public class ThoughtWorker_HtF : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.h2f; } }
	}
	public class ThoughtWorker_TtH : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.t2h; } }
	}
	public class ThoughtWorker_TtM : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.t2m; } }
	}
	public class ThoughtWorker_TtF : ThoughtWorker_SexChange
	{
		public override HediffDef hediff_served { get { return GenderHelper.t2f; } }
	}
}

repo_name: Keagro/rjw

license: mit


code:
using Verse;
using Verse.AI.Group;

namespace rjw
{
	public class Trigger_SexSatisfy : TriggerFilter
	{
		private const int CheckInterval = 120;
		private const int TickTimeout = 900;
		private int currentTick = 0;
		public float targetValue = 0.3f;

		public Trigger_SexSatisfy(float t)
		{
			this.targetValue = t;
			currentTick = 0;
		}

		public override bool AllowActivation(Lord lord, TriggerSignal signal)
		{
			currentTick++;
			if (signal.type == TriggerSignalType.Tick && Find.TickManager.TicksGame % CheckInterval == 0)
			{
				float? avgValue = null;
				foreach (var pawn in lord.ownedPawns)
				{
					/*foreach(Pawn p in lord.Map.mapPawns.PawnsInFaction(Faction.OfPlayer))
					{
					}*/
					Need_Sex n = pawn.needs.TryGetNeed<Need_Sex>();
					//if (n != null && pawn.gender == Gender.Male && !pawn.Downed)
					if(xxx.can_rape(pawn) && (xxx.is_healthy_enough(pawn) && xxx.IsTargetPawnOkay(pawn) || !xxx.is_human(pawn)) && Find.TickManager.TicksGame > pawn.mindState.canLovinTick)
					{
						avgValue = (avgValue == null) ? n.CurLevel : (avgValue + n.CurLevel) / 2f;
					}
				}
				//--Log.Message("[ABF]Trigger_SexSatisfy::ActivateOn Checked value :" + avgValue + "/" + targetValue);
				return avgValue == null || avgValue >= targetValue;
			}
			return currentTick >= TickTimeout;
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape a comfort prisoner
	/// </summary>
	public class WorkGiver_BestialityF : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			if (!RJWSettings.rape_enabled) return false;

			Pawn target = t as Pawn;
			if (!RJWSettings.WildMode)
			{
				if (xxx.is_kind(pawn))
				{
					JobFailReason.Is("refuses to rape");
					return false;
				}
				//satisfied pawns
				//horny non rapists
				if ((xxx.need_some_sex(pawn) <= 1f)
					|| (xxx.need_some_sex(pawn) <= 2f && !(xxx.is_rapist(pawn) || xxx.is_psychopath(pawn) || xxx.is_nympho(pawn))))
					{
						JobFailReason.Is("not horny enough");
						return false;
					}
				if (!target.IsDesignatedComfort())
				{
					//JobFailReason.Is("not designated as CP", null);
					return false;
				}
				if (!xxx.is_healthy_enough(target)
					|| !xxx.is_not_dying(target) && (xxx.is_bloodlust(pawn) || xxx.is_psychopath(pawn) || xxx.is_rapist(pawn) || xxx.has_quirk(pawn, "Somnophile")))
				{
					//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called0 - target isn't healthy enough or is in a forbidden position.");
					JobFailReason.Is("target not healthy enough");
					return false;
				}
				if (pawn.relations.OpinionOf(target) > 50 && !xxx.is_rapist(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_masochist(target))
				{
					JobFailReason.Is("refuses to rape a friend");
					return false;
				}
				if (!xxx.can_rape(pawn))
				{
					//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called1 - pawn don't need sex or is not healthy, or cannot rape");
					JobFailReason.Is("cannot rape target (vulnerability too low, or age mismatch)");
					return false;
				}
				if (!xxx.isSingleOrPartnerNotHere(pawn) && !xxx.is_lecher(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_nympho(pawn))
					if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target))
					{
						//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called2 - pawn is not single or has partner around");
						JobFailReason.Is("cannot rape while in stable relationship");
						return false;
					}
			}

			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			//--Log.Message("[RJW]WorkGiver_RapeCP::JobOnThing(" + xxx.get_pawnname(pawn) + "," + t.ToStringSafe() + ") is called.");
			return new Job(xxx.comfort_prisoner_rapin, t);
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to breed animal(passive)
	/// </summary>
	public class WorkGiver_BestialityForFemale : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//ModLog.Message("" + this.GetType().ToString() + ":: base checks: pass");
			if (!RJWSettings.bestiality_enabled) return false;

			Pawn target = t as Pawn;
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.can_be_fucked(pawn))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("pawn cant be fucked");
				return false;
			}

			if (!(pawn.IsDesignatedHero() || RJWSettings.override_control))
				if (!RJWSettings.WildMode)
				{
					if (!xxx.is_zoophile(pawn) && !xxx.is_frustrated(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not willing to have sex with animals");
						return false;
					}
					if (!xxx.is_hornyorfrustrated(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
						return false;
					}
					if (!xxx.is_healthy_enough(target))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("target not healthy enough");
						return false;
					}

					//ModLog.Message("WorkGiver_BestialityForFemale::" + SexAppraiser.would_fuck_animal(pawn, target));
					if (SexAppraiser.would_fuck_animal(pawn, target) < 0.1f)
					{
						return false;
					}
					//add some more fancy conditions from JobGiver_Bestiality?
				}

			//ModLog.Message("" + this.GetType().ToString() + ":: extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (!xxx.is_animal(target))
			{
				return false;
			}
			Building_Bed bed = pawn.ownership.OwnedBed;
			if (bed == null)
			{
				if (RJWSettings.DevMode) JobFailReason.Is("pawn has no bed");
				return false;
			}
			if (!target.CanReach(bed, PathEndMode.OnCell, Danger.Some) || target.Downed)
			{
				if (RJWSettings.DevMode) JobFailReason.Is("target cant reach bed");
				return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			Building_Bed bed = pawn.ownership.OwnedBed;
			return JobMaker.MakeJob(xxx.bestialityForFemale, t, bed);
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to breed animal(active)
	/// </summary>
	public class WorkGiver_BestialityForMale : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//ModLog.Message("" + this.GetType().ToString() + " base checks: pass");
			if (!RJWSettings.bestiality_enabled) return false;

			Pawn target = t as Pawn;
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.can_be_fucked(target))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("target cant be fucked");
				return false;
			}

			if (!(pawn.IsDesignatedHero() || RJWSettings.override_control))
				if (!RJWSettings.WildMode)
				{
					if (!xxx.is_zoophile(pawn) && !xxx.is_frustrated(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not willing to have sex with animals");
						return false;
					}
					if (!xxx.is_hornyorfrustrated(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
						return false;
					}
					if (!xxx.is_healthy_enough(target)
						|| !xxx.is_not_dying(target) && (xxx.is_bloodlust(pawn) || xxx.is_psychopath(pawn) || xxx.has_quirk(pawn, "Somnophile")))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("target not healthy enough");
						return false;
					}

					//ModLog.Message("WorkGiver_BestialityForMale::" + SexAppraiser.would_fuck_animal(pawn, target));
					if (SexAppraiser.would_fuck_animal(pawn, target) < 0.1f)
					{
						return false;
					}
					//add some more fancy conditions from JobGiver_Bestiality?
				}

			//ModLog.Message("" + this.GetType().ToString() + ":: extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (!xxx.is_animal(target))
			{
				return false;
			}
			if (!xxx.can_fuck(pawn))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("cant fuck");
				return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.bestiality, t);
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape a comfort prisoner
	/// </summary>
	public class WorkGiver_BestialityM : WorkGiver_RJW_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			if (!RJWSettings.rape_enabled) return false;

			Pawn target = t as Pawn;
			if (!RJWSettings.WildMode)
			{
				if (xxx.is_kind(pawn))
				{
					JobFailReason.Is("refuses to rape");
					return false;
				}
				//satisfied pawns
				//horny non rapists
				if ((xxx.need_some_sex(pawn) <= 1f)
					|| (xxx.need_some_sex(pawn) <= 2f && !(xxx.is_rapist(pawn) || xxx.is_psychopath(pawn) || xxx.is_nympho(pawn))))
					{
						JobFailReason.Is("not horny enough");
						return false;
					}
				if (!target.IsDesignatedComfort())
				{
					//JobFailReason.Is("not designated as CP", null);
					return false;
				}
				if (!xxx.is_healthy_enough(target)
					|| !xxx.is_not_dying(target) && (xxx.is_bloodlust(pawn) || xxx.is_psychopath(pawn) || xxx.is_rapist(pawn) || xxx.has_quirk(pawn, "Somnophile")))
				{
					//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called0 - target isn't healthy enough or is in a forbidden position.");
					JobFailReason.Is("target not healthy enough");
					return false;
				}
				if (pawn.relations.OpinionOf(target) > 50 && !xxx.is_rapist(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_masochist(target))
				{
					JobFailReason.Is("refuses to rape a friend");
					return false;
				}
				if (!xxx.can_rape(pawn))
				{
					//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called1 - pawn don't need sex or is not healthy, or cannot rape");
					JobFailReason.Is("cannot rape target (vulnerability too low, or age mismatch)");
					return false;
				}
				if (!xxx.isSingleOrPartnerNotHere(pawn) && !xxx.is_lecher(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_nympho(pawn))
					if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target))
					{
						//--Log.Message("[RJW]WorkGiver_RapeCP::HasJobOnThing called2 - pawn is not single or has partner around");
						JobFailReason.Is("cannot rape while in stable relationship");
						return false;
					}
			}

			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			//Log.Message("[RJW]" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			//--Log.Message("[RJW]WorkGiver_RapeCP::JobOnThing(" + xxx.get_pawnname(pawn) + "," + t.ToStringSafe() + ") is called.");
			return new Job(xxx.comfort_prisoner_rapin, t);
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to cleanup/collect sex fluids
	/// </summary>
	//TODO: add sex fluid collection/cleaning
	public class WorkGiver_CleanSexStuff : WorkGiver_Sexchecks
	{
		public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.filth);

		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			return false;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return null;
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;
using System.Linq;

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to fap in bed
	/// </summary>
	public class WorkGiver_Masturbate_Bed : WorkGiver_Sexchecks
	{
		public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.BuildingArtificial);

		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			if (pawn.Position == t.Position)
			{
				//use quickfap
				return false;
			}
			//ModLog.Message("" + this.GetType().ToString() + " base checks: pass");
			Building_Bed target = t as Building_Bed;
			if (!(target is Building_Bed))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("not a bed");
				return false;
			}
			if (!pawn.CanReserve(target))
			{
				//if (RJWSettings.DevMode) JobFailReason.Is("not a bed");
				return false;
			}

			if (!(pawn.IsDesignatedHero() || RJWSettings.override_control))
				if (!RJWSettings.WildMode)
				{
					if (!target.OwnersForReading.Contains(pawn) && !xxx.is_psychopath(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not my bed");
						return false;
					}
					if (!xxx.is_nympho(pawn))
						if (!xxx.is_hornyorfrustrated(pawn))
						{
							if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
							return false;
						}
					if (target.CurOccupants.Count() != 0)
					{
						if (target.CurOccupants.Count() == 1 && !target.CurOccupants.Contains(pawn))
						{
							if (RJWSettings.DevMode) JobFailReason.Is("bed not empty");
							return false;
						}
						if (target.CurOccupants.Count() > 1)
						{
							if (RJWSettings.DevMode) JobFailReason.Is("bed not empty");
							return false;
						}
					}

					//TODO: more exhibitionsts checks?
					bool canbeseen = false;
					foreach (Pawn bystander in pawn.Map.mapPawns.AllPawnsSpawned.Where(x => xxx.is_human(x) && x != pawn))
					{
						// dont see through walls, dont see whole map, only 15 cells around
						if (bystander.CanSee(target) && bystander.Position.DistanceTo(target.Position) < 15)
						{
							//if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, bystander))
							canbeseen = true;
						}
					}
					if (!xxx.has_quirk(pawn, "Exhibitionist") && canbeseen)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("can be seen");
						return false;
					}
					if (xxx.has_quirk(pawn, "Exhibitionist") && !canbeseen)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("can not be seen");
						return false;
					}
				}

			//ModLog.Message("" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.Masturbate, pawn, t, t.Position);
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;
using System.Linq;

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to fap in chair
	/// </summary>
	public class WorkGiver_Masturbate_Chair : WorkGiver_Sexchecks
	{
		public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.BuildingArtificial);

		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			if (pawn.Position == t.Position)
			{
				//use quickfap
				return false;
			}
			//ModLog.Message("" + this.GetType().ToString() + " base checks: pass");
			Building target = t as Building;
			if (!(target is Building))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("not a building");
				return false;
			}
			if (!(target.def.building.isSittable))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("not a sittable building");
				return false;
			}
			if (!pawn.CanReserve(target))
			{
				//if (RJWSettings.DevMode) JobFailReason.Is("not a bed");
				return false;
			}

			if (!(pawn.IsDesignatedHero() || RJWSettings.override_control))
				if (!RJWSettings.WildMode)
				{
					if (!xxx.is_nympho(pawn))
						if (!xxx.is_hornyorfrustrated(pawn))
						{
							if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
							return false;
						}

					//TODO: more exhibitionsts checks?
					bool canbeseen = false;
					foreach (Pawn bystander in pawn.Map.mapPawns.AllPawnsSpawned.Where(x => xxx.is_human(x) && x != pawn))
					{
						// dont see through walls, dont see whole map, only 15 cells around
						if (bystander.CanSee(target) && bystander.Position.DistanceTo(target.Position) < 15)
						{
							//if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, bystander))
							canbeseen = true;
						}
					}
					if (!xxx.has_quirk(pawn, "Exhibitionist") && canbeseen)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("can be seen");
						return false;
					}
					if (xxx.has_quirk(pawn, "Exhibitionist") && !canbeseen)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("can not be seen");
						return false;
					}
				}

			//ModLog.Message("" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.Masturbate, pawn, t, t.Position);
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;
using System.Linq;

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to fap
	/// </summary>
	public class WorkGiver_Masturbate : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//ModLog.Message("" + this.GetType().ToString() + " base checks: pass");
			Pawn target = t as Pawn;
			if (target != pawn)
			{
				return false;
			}

			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			if (!(pawn.IsDesignatedHero() || RJWSettings.override_control))
				if (!RJWSettings.WildMode)
				{
					if (!xxx.is_nympho(pawn))
						if (!xxx.is_hornyorfrustrated(pawn))
						{
							if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
							return false;
						}

					//TODO: more exhibitionsts checks?
					bool canbeseen = false;
					foreach (Pawn bystander in pawn.Map.mapPawns.AllPawnsSpawned.Where(x => xxx.is_human(x) && x != pawn))
					{
						// dont see through walls, dont see whole map, only 15 cells around
						if (pawn.CanSee(bystander) && pawn.Position.DistanceTo(bystander.Position) < 15)
						{
							//if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, bystander))
							canbeseen = true;
						}
					}
					if (!xxx.has_quirk(pawn, "Exhibitionist") && canbeseen)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("can be seen");
						return false;
					}
					if (xxx.has_quirk(pawn, "Exhibitionist") && !canbeseen)
					{
						if (RJWSettings.DevMode) JobFailReason.Is("can not be seen");
						return false;
					}
				}
			//experimental change textures of bed to whore bed
			//ModLog.Message(" bed " + t.GetType().ToString() + " path " + t.Graphic.data.texPath);
			//t.Graphic.data.texPath = "Things/Building/Furniture/Bed/DoubleBedWhore";
			//t.Graphic.path = "Things/Building/Furniture/Bed/DoubleBedWhore";
			//t.DefaultGraphic.data.texPath = "Things/Building/Furniture/Bed/DoubleBedWhore";
			//t.DefaultGraphic.path = "Things/Building/Furniture/Bed/DoubleBedWhore";
			//ModLog.Message(" bed " + t.GetType().ToString() + " texPath " + t.Graphic.data.texPath);
			//ModLog.Message(" bed " + t.GetType().ToString() + " drawSize " + t.Graphic.data.drawSize);
			//t.Draw();
			//t.ExposeData();
			//Scribe_Values.Look(ref t.Graphic.data.texPath, t.Graphic.data.texPath, "Things/Building/Furniture/Bed/DoubleBedWhore", true);

			//ModLog.Message("" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.Masturbate, pawn, null, t.Position);
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape
	/// </summary>
	public class WorkGiver_Quickie : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{

			Pawn target = t as Pawn;
			if (target == pawn)
			{
				//JobFailReason.Is("no self rape", null);
				return false;
			}
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.is_human(target))
			{
				return false;
			}

			if(target.GetPosture() == PawnPosture.LayingInBed)
			{
				return false;
			}
			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			if (!(pawn.IsDesignatedHero() || RJWSettings.override_control))
				return false;
			//ModLog.Message("" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (pawn.HostileTo(target))
			{
				return false;
			}
			return true;
		}
		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.quick_sex, t as Pawn);
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape
	/// </summary>
	public class WorkGiver_Rape : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			if (RJWSettings.DebugRape) ModLog.Message("" + this.GetType().ToString() + " base checks: pass");
			if (!RJWSettings.rape_enabled) return false;

			Pawn target = t as Pawn;
			if (target == pawn)
			{
				//JobFailReason.Is("no self rape", null);
				return false;
			}
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.is_human(target))
			{
				return false;
			}
			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			if (!(pawn.IsDesignatedHero() || RJWSettings.override_control))
				if (!RJWSettings.WildMode)
				{
					if (xxx.is_kind(pawn) || xxx.is_masochist(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("refuses to rape");
						return false;
					}
					if (pawn.relations.OpinionOf(target) > 50 && !xxx.is_rapist(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_masochist(target))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("refuses to rape a friend");
						return false;
					}
					if (!xxx.can_get_raped(target))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("cannot rape target");
						return false;
					}
					//fail for:
					//satisfied pawns
					//horny non rapists
					if ((xxx.need_some_sex(pawn) <= 1f)
						|| (xxx.need_some_sex(pawn) <= 2f && !(xxx.is_rapist(pawn) || xxx.is_psychopath(pawn) || xxx.is_nympho(pawn))))
						{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
							return false;
						}
					if (!xxx.can_rape(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("cannot rape");
						return false;
					}
					if (!xxx.is_healthy_enough(target)
						|| !xxx.is_not_dying(target) && (xxx.is_bloodlust(pawn) || xxx.is_psychopath(pawn) || xxx.is_rapist(pawn) || xxx.has_quirk(pawn, "Somnophile")))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("target not healthy enough");
						return false;
					}
					if (!xxx.is_lecher(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_nympho(pawn))
						if (!xxx.isSingleOrPartnerNotHere(pawn))
							if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target))
							{
								if (RJWSettings.DevMode) JobFailReason.Is("cannot rape while partner around");
								return false;
							}
					//ModLog.Message("WorkGiver_RapeCP::" + SexAppraiser.would_fuck(pawn, target));
					if (SexAppraiser.would_fuck(pawn, target) < 0.1f)
					{
						return false;
					}
				}

			//ModLog.Message("" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (pawn.HostileTo(target) || target.IsDesignatedComfort())
			{
				return false;
			}
			return true;
		}
		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.RapeRandom, t);
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape a comfort prisoner
	/// </summary>
	public class WorkGiver_RapeCP : WorkGiver_Rape
	{
		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (!target.IsDesignatedComfort())
			{
				if (RJWSettings.DevMode) JobFailReason.Is("not designated for comfort", null);
					return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.RapeCP, t);
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape enemy
	/// </summary>
	public class WorkGiver_RapeEnemy : WorkGiver_Rape
	{
		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (!pawn.HostileTo(target))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("not hostile", null);
					return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.RapeEnemy, t);
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to have sex with
	/// </summary>
	public class WorkGiver_Sex : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//ModLog.Message("" + this.GetType().ToString() + " base checks: pass");

			Pawn target = t as Pawn;
			if (target == pawn)
			{
				//JobFailReason.Is("no self sex", null);
				return false;
			}
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.is_human(target))
			{
				return false;
			}
			if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
				return false;

			if (!(pawn.IsDesignatedHero() || RJWSettings.override_control))
				if (!RJWSettings.WildMode)
				{
					//check initiator
					//fail for:
					//satisfied non nymph pawns
					if (xxx.need_some_sex(pawn) <= 1f && !xxx.is_nympho(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
						return false;
					}
					if (!xxx.IsTargetPawnOkay(target))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("target not healthy enough");
						return false;
					}
					if (!xxx.is_lecher(pawn) && !xxx.is_psychopath(pawn) && !xxx.is_nympho(pawn))
						if (!xxx.isSingleOrPartnerNotHere(pawn))
							if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target))
							{
								if (RJWSettings.DevMode) JobFailReason.Is("cannot have sex while partner around");
								return false;
							}

					float relations = 0;
					float attraction = 0;
					if (!xxx.is_animal(target))
					{
						relations = pawn.relations.OpinionOf(target);
						if (relations < RJWHookupSettings.MinimumRelationshipToHookup)
						{
							if (!(relations > 0 && xxx.is_nympho(pawn)))
							{
								if (RJWSettings.DevMode) JobFailReason.Is($"i don't like them:({relations})");
								return false;
							}
						}

						attraction = pawn.relations.SecondaryRomanceChanceFactor(target);
						if (attraction < RJWHookupSettings.MinimumAttractivenessToHookup)
						{
							if (!(attraction > 0 && xxx.is_nympho(pawn)))
							{
								if (RJWSettings.DevMode) JobFailReason.Is($"i don't find them attractive:({attraction})");
								return false;
							}
						}
					}
					//ModLog.Message("WorkGiver_Sex::" + SexAppraiser.would_fuck(pawn, target));
					if (SexAppraiser.would_fuck(pawn, target) < 0.1f)
					{
						return false;
					}

					if (!xxx.is_animal(target))
					{
						//check partner
						if (xxx.need_some_sex(target) <= 1f && !xxx.is_nympho(target))
						{
							if (RJWSettings.DevMode) JobFailReason.Is("partner not horny enough");
							return false;
						}
						if (!xxx.is_lecher(target) && !xxx.is_psychopath(target) && !xxx.is_nympho(target))
							if (!xxx.isSingleOrPartnerNotHere(target))
								if (!LovePartnerRelationUtility.LovePartnerRelationExists(pawn, target))
								{
									if (RJWSettings.DevMode) JobFailReason.Is("partner cannot have sex while their partner around");
									return false;
								}

						relations = target.relations.OpinionOf(pawn);
						if (relations <= RJWHookupSettings.MinimumRelationshipToHookup)
						{
							if (!(relations > 0 && xxx.is_nympho(target)))
							{
								if (RJWSettings.DevMode) JobFailReason.Is($"don't like me:({relations})");
								return false;
							}
						}

						attraction = target.relations.SecondaryRomanceChanceFactor(pawn);
						if (attraction <= RJWHookupSettings.MinimumAttractivenessToHookup)
						{
							if (!(attraction > 0 && xxx.is_nympho(target)))
							{
								if (RJWSettings.DevMode) JobFailReason.Is($"doesn't find me attractive:({attraction})");
								return false;
							}
						}
					}

					//ModLog.Message("WorkGiver_Sex::" + SexAppraiser.would_fuck(target, pawn));
					if (SexAppraiser.would_fuck(target, pawn) < 0.1f)
					{
						return false;
					}
				}

			//ModLog.Message("" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (pawn.HostileTo(target) || target.IsDesignatedComfort())
			{
				return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			//TODO:: fix bed stealing during join other pawn
			//Building_Bed bed = pawn.ownership.OwnedBed;
			//if (bed == null)
			//	bed = (t as Pawn).ownership.OwnedBed;

			Building_Bed bed = (t as Pawn).CurrentBed();

			if (bed == null)
				return null;

			//if (pawn.CurrentBed() != (t as Pawn).CurrentBed())
			//	return null;

			return JobMaker.MakeJob(xxx.casual_sex, t as Pawn, bed);
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;
using Multiplayer.API;

namespace rjw
{
	/// <summary>
	/// Allow pawn to have sex
	/// dunno if this should be used to allow manual sex start or limit it behind sort of "hero" designator for RP purposes, so player can only control 1 pawn directly?
	/// </summary>
	public class WorkGiver_Sexchecks : WorkGiver_Scanner
	{
		public override int MaxRegionsToScanBeforeGlobalSearch => 4;
		public override PathEndMode PathEndMode => PathEndMode.OnCell;
		public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.Pawn);

		public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			if (!forced)
			//if (!(forced || RJWSettings.WildMode))
			{
				//ModLog.Message("WorkGiver_RJW_Sexchecks::not player interaction, exit:" + forced);
				return false;
			}
			var isHero = RJWSettings.RPG_hero_control && pawn.IsDesignatedHero();
			if (!(RJWSettings.override_control || isHero))
			{
				//ModLog.Message("WorkGiver_RJW_Sexchecks::direct_control disabled or not hero, exit");
				return false;
			}
			//!
			if (!isHero)
			{
				if (!RJWSettings.override_control || MP.IsInMultiplayer)
				{
					//ModLog.Message("WorkGiver_RJW_Sexchecks::direct_control disabled or is in MP , exit");
					return false;
				}
			}
			else if (!pawn.IsHeroOwner())
			{
				//ModLog.Message("WorkGiver_RJW_Sexchecks::not hero owner, exit");
				return false;
			}
			Pawn target = t as Pawn;
			if (t is Corpse)
			{
				Corpse corpse = t as Corpse;
				target = corpse.InnerPawn;
				//ModLog.Message("WorkGiver_RJW_Sexchecks::Pawn(" + xxx.get_pawnname(pawn) + "), Target corpse(" + xxx.get_pawnname(target) + ")");
			}
			else
			{
				//ModLog.Message("WorkGiver_RJW_Sexchecks::Pawn(" + xxx.get_pawnname(pawn) + "), Target pawn(" + xxx.get_pawnname(target) + ")");
			}

			//Log.Message("1");
			if (t == null || t.Map == null)
			{
				return false;
			}
			//Log.Message("2");
			if (!(xxx.can_fuck(pawn) || xxx.can_be_fucked(pawn)))
			{
				//ModLog.Message("WorkGiver_RJW_Sexchecks::JobOnThing(" + xxx.get_pawnname(pawn) + ") is cannot fuck or be fucked.");
				return false;
			}
			//Log.Message("3");
			if (t is Pawn)
				if (!(xxx.can_fuck(target) || xxx.can_be_fucked(target)))
				{
					//ModLog.Message("WorkGiver_RJW_Sexchecks::JobOnThing(" + xxx.get_pawnname(target) + ") is cannot fuck or be fucked.");
					return false;
				}
			//Log.Message("4");

			if (!RJWSettings.bestiality_enabled && xxx.is_animal(target))
			{
				//if (RJWSettings.DevMode) JobFailReason.Is("bestiality disabled");
				return false;
			}
			//investigate AoA, someday
			//move this?
			//if (xxx.is_animal(pawn) && xxx.is_animal(target) && !RJWSettings.animal_on_animal_enabled)
			//{
			//	return false;
			//}
			if (!xxx.is_human(pawn) && !(xxx.RoMIsActive && pawn.health.hediffSet.HasHediff(xxx.TM_ShapeshiftHD)))
			{
				return false;
			}

			//Log.Message("5");
			if (!pawn.CanReach(t, PathEndMode, Danger.Some))
			{
				if (RJWSettings.DevMode) JobFailReason.Is(
					pawn.CanReach(t, PathEndMode, Danger.Deadly)
						? "unable to reach target safely" : "target unreachable");
				return false;
			}
			//Log.Message("6");
			if (t.IsForbidden(pawn))
			{
				if (RJWSettings.DevMode) JobFailReason.Is("target is outside of allowed area");
				return false;
			}
			//Log.Message("7");
			if (!pawn.IsDesignatedHero())
			{
				if (!RJWSettings.WildMode)
				{
					if (pawn.IsDesignatedComfort() || pawn.IsDesignatedBreeding())
					{
						if (RJWSettings.DevMode) JobFailReason.Is("designated pawns cannot initiate sex");
						return false;
					}
					if (!xxx.is_healthy_enough(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("not healthy enough for sex");
						return false;
					}
					if (xxx.is_asexual(pawn))
					{
						if (RJWSettings.DevMode) JobFailReason.Is("refuses to have sex");
						return false;
					}
				}
			}
			else
			{
				if (!pawn.IsHeroOwner())
				{
					//ModLog.Message("WorkGiver_Sexchecks::player interaction for not owned hero, exit");
					return false;
				}
			}

			if (!MoreChecks(pawn, t, forced))
				return false;

			return true;
		}

		public virtual bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			return false;
		}

		public virtual bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return null;
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Try to solicit pawn to have sex with
	/// </summary>
	public class WorkGiver_Solicit : WorkGiver_Sexchecks
	{
		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//ModLog.Message("" + this.GetType().ToString() + " base checks: pass");

			Pawn target = t as Pawn;
			if (target == pawn)
			{
				//JobFailReason.Is("no self solicit", null);
				return false;
			}
			if (!WorkGiverChecks(pawn, t, forced))
				return false;

			if (!xxx.is_human(target))
			{
				return false;
			}
			//if (!pawn.CanReserve(target, xxx.max_rapists_per_prisoner, 0))
			//	return false;

			//ModLog.Message("WorkGiver_Sex::" + SexAppraiser.would_fuck(target, pawn));
			//if (SexAppraiser.would_fuck(target, pawn) < 0.1f)
			//{
			//	return false;
			//}

			//ModLog.Message("" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override bool WorkGiverChecks(Pawn pawn, Thing t, bool forced = false)
		{
			Pawn target = t as Pawn;
			if (pawn.HostileTo(target) || target.IsDesignatedComfort())
			{
				return false;
			}
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			//TODO:: fix bed stealing during join other pawn
			//Building_Bed bed = pawn.ownership.OwnedBed;
			//if (bed == null)
			//	bed = (t as Pawn).ownership.OwnedBed;

			Building_Bed bed = (pawn as Pawn).ownership.OwnedBed;

			if (bed == null)
				return null;

			//if (pawn.CurrentBed() != (t as Pawn).CurrentBed())
			//	return null;

			return JobMaker.MakeJob(xxx.whore_inviting_visitors, t as Pawn, bed);
		}
	}
}


code:
using RimWorld;
using Verse;
using Verse.AI;

namespace rjw
{
	/// <summary>
	/// Assigns a pawn to rape a corpse
	/// </summary>
	public class WorkGiver_ViolateCorpse : WorkGiver_Sexchecks
	{
		public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForGroup(ThingRequestGroup.Corpse);

		public override bool MoreChecks(Pawn pawn, Thing t, bool forced = false)
		{
			//ModLog.Message("" + this.GetType().ToString() + " base checks: pass");
			if (!RJWSettings.necrophilia_enabled) return false;

			//Pawn target = (t as Corpse).InnerPawn;
			if (!pawn.CanReserve(t, xxx.max_rapists_per_prisoner, 0))
				return false;

			if (!(pawn.IsDesignatedHero() || RJWSettings.override_control))
				if (!RJWSettings.WildMode)
				{
					if (xxx.is_necrophiliac(pawn) && !xxx.is_hornyorfrustrated(pawn))
						{
						if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
							return false;
						}
					if (!xxx.is_necrophiliac(pawn))
						if ((t as Corpse).CurRotDrawMode != RotDrawMode.Fresh)
						{
							if (RJWSettings.DevMode) JobFailReason.Is("refuse to rape rotten");
							return false;
						}
						else if (!xxx.is_frustrated(pawn))
							{
							if (RJWSettings.DevMode) JobFailReason.Is("not horny enough");
								return false;
							}
					//ModLog.Message("WorkGiver_ViolateCorpse::" + SexAppraiser.would_fuck(pawn, t as Corpse));
					if (SexAppraiser.would_fuck(pawn, t as Corpse) > 0.1f)
					{
						return false;
					}
				}
			//ModLog.Message("" + this.GetType().ToString() + " extended checks: can start sex");
			return true;
		}

		public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
		{
			return JobMaker.MakeJob(xxx.RapeCorpse, t as Corpse);
		}
	}
}


code:
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Lib.Harmony" version="2.2.2" targetFramework="net472" />
  <package id="UnlimitedHugs.Rimworld.HugsLib" version="9.0.1" targetFramework="net472" />
</packages>


code:
<?xml version="1.0" encoding="utf-8"?>

<ModMetaData>
	<name>SafeJobWorld</name>
	<author>Ed86</author>
	<supportedVersions>

1.1

1.2

1.3

1.4

	</supportedVersions>
	<packageId>safe.job.world</packageId>
	<modDependencies>

			<packageId>brrainz.harmony</packageId>
			<displayName>Harmony</displayName>
			<steamWorkshopUrl>steam://url/CommunityFilePage/2009463077</steamWorkshopUrl>
			<downloadUrl>https://github.com/pardeike/HarmonyRimWorld/releases/latest</downloadUrl>

			<packageId>UnlimitedHugs.HugsLib</packageId>
			<displayName>HugsLib</displayName>
			<downloadUrl>https://github.com/UnlimitedHugs/RimworldHugsLib/releases/latest</downloadUrl>
			<steamWorkshopUrl>steam://url/CommunityFilePage/818773962</steamWorkshopUrl>

	</modDependencies>
	<loadAfter>

UnlimitedHugs.HugsLib

brrainz.harmony

	</loadAfter>
	<incompatibleWith>

gregorycurrie.AnimalGenetics

EdB.PrepareCarefully

EdB.PrepareCarefully.UnofficialPatch

EdB.PrepareCarefully.alonske.fork

Dalrae.GaramRaceAddon

IGNI.LostForest

Sierra.RT.MedievalTalents

com.yayo.raceQuestPawn

Xenofell.HermaphroditeGene

	</incompatibleWith>
	<description>
M for Mature Handholding

Load mod at bottom of mod list:
	Harmony
	Core
	HugsLib
	++Mods
	SafeJobWorld

Support dev:
https://subscribestar.adult/Ed86
	</description>
</ModMetaData>


code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Manifest>
	<identifier>SafeJobWorld</identifier>
	<version>5.3.7</version>
	<dependencies>

HugsLib

	</dependencies>
	<incompatibleWith />
	<loadAfter>

HugsLib

	</loadAfter>
	<suggests>
	</suggests>
</Manifest>


code:
@ECHO OFF
SET ThisScriptsDirectory=%~dp0
SET PSSctiptName=Deploy.ps1
SET PowerShellScriptPath=%ThisScriptsDirectory%%PSSctiptName%
PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& '%PowerShellScriptPath%'";


code:
# Configurable variables
$repo           = '..'
$packing        = 'rjw_packing'
$outputFormat   = '..\..\RJW-{0}.7z'
$internalPath   = 'RJW'
$pathsToRemove = '.git', '.gitattributes', '.gitignore', '1.1\Source', '1.2\Source', '1.3\Source', 'Deploy'

[Console]::ResetColor()

# Progress Bar Variables
$Activity             = "Deploying"
$Id                   = 1

# Complex Progress Bar
$Step                 = 0
$TotalSteps           = 6
$StatusText           = '"Step $($Step.ToString().PadLeft($TotalSteps.ToString().Length)) of $TotalSteps | $Task"' # Single quotes need to be on the outside
$StatusBlock          = [ScriptBlock]::Create($StatusText) # This script block allows the string above to use the current values of embedded values each time it's run

# Read environvemt
$Task = "Collecting info..."
$Step++
##Write-Progress -Id $Id -Activity $Activity -Status (& $StatusBlock) -CurrentOperation " " -PercentComplete ($Step / $TotalSteps * 100)

$startupPath = Get-Location
$7z = (GET-ItemProperty 'HKLM:\SOFTWARE\7-Zip').Path + '7z.exe'
$packingRjw = $packing + "\" + $internalPath

Push-Location -Path $repo

try {
	[string]$version = git describe --tags
} catch {
	[string]$version = ""
}

if ($version -eq "") {
	$manifest = "./About/Manifest.xml"
	[string]$version = (Select-Xml -Path $manifest -XPath '/Manifest/version' | Select-Object -ExpandProperty Node).innerText
}

$output = $outputFormat -f $version

Pop-Location

# Cleanup
$Task = "Cleanup..."
$Step++
Write-Progress -Id $Id -Activity $Activity -Status (& $StatusBlock) -CurrentOperation " " -PercentComplete ($Step / $TotalSteps * 100)

if (Test-Path $packing) { Remove-Item -Recurse -Force $packing }
if (Test-Path $output) { Remove-Item $output }

# Prepating data
$Task = "Copying..."
$Step++
Write-Progress -Id $Id -Activity $Activity -Status (& $StatusBlock) -CurrentOperation " " -PercentComplete ($Step / $TotalSteps * 100)

$items = Get-ChildItem -Force -Path $repo
$items | Foreach-Object -Begin {
	$i = 0
} -Process {
    $i++

	if (-Not ($pathsToRemove -contains $_.Name)) {
		Copy-Item -Recurse $_.FullName -Destination ($packingRjw + "\" + $_.Name)
	}

	$p = $i * 100 / $items.Count
	Write-Progress -Id ($Id+1) -ParentId $Id -Activity 'Copying' -Status ('{0:0}% complete' -f $p) -PercentComplete $p
}
Write-Progress -Id ($Id+1) -ParentId $Id -Activity "Copying" -Status "Ready" -Completed

# removing files from subfolders
$Task = "Excluding..."
$Step++
Write-Progress -Id $Id -Activity $Activity -Status (& $StatusBlock) -CurrentOperation " " -PercentComplete ($Step / $TotalSteps * 100)

foreach ($path in $pathsToRemove) {
	$p = $packingRjw + '\' + $path
	if (Test-Path $p) { Remove-Item -Recurse -Force $p }
}

# archiving
$Task = "Archiving..."
$Step++
Write-Progress -Id $Id -Activity $Activity -Status (& $StatusBlock) -CurrentOperation " " -PercentComplete ($Step / $TotalSteps * 100)

Push-Location -Path  $packing
& $7z a -r -bsp1 ($startupPath.Path+'\'+$output) + $internalPath | Foreach-Object -Begin {
	Write-Progress -Id ($Id+1) -ParentId $Id -Activity 'Archiving' -Status "Starting..." -PercentComplete 0
} -Process {
	$line = $_.Trim()
	if ($line -ne "") {
		[int]$p = 0
		[bool]$result = [int]::TryParse($line.Split('%')[0], [ref]$p)
		if ($result) {
			Write-Progress -Id ($Id+1) -ParentId $Id -Activity 'Archiving' -Status $line -PercentComplete $p
		}
	}
}

Write-Progress -Id ($Id+1)  -Activity "Archiving" -Status "Ready" -Completed
Pop-Location

# cleanup
$Task = "Cleanup..."
$Step++
Write-Progress -Id $Id -Activity $Activity -Status (& $StatusBlock) -CurrentOperation " " -PercentComplete ($Step / $TotalSteps * 100)

if (Test-Path $packing) { Remove-Item -Recurse -Force $packing }

Write-Progress -Id $Id -Activity $Activity -Status (& $StatusBlock) -Completed

# If running in the console, wait for input before closing.
if ($Host.Name -eq "ConsoleHost")
{
	Write-Host "Done"
    Write-Host "Press any key to continue..."
    $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyUp") > $null
}


code:
And why would you read me?

You can support current Dev(Me) here:
https://subscribestar.adult/Ed86

Mod git:
https://gitgud.io/Ed86/rjw

Discord:
https://discord.gg/CXwHhv8

LoversLab:
https://www.loverslab.com/files/file/7257-rimjobworld/

Requirements:
Hugslib ([Steam](https://steamcommunity.com/sharedfiles/filedetails/?id=818773962)) ([GitHub](https://github.com/UnlimitedHugs/RimworldHugsLib))

Installation:
(if updating - Delete all files from previous RJW versions)
Unpack the the content from the .7z into "...steam/steamapps/common/rimworld/mods/RJW", you will see RimJobWorld.Main.csproj
Start Game
Activate mod & the other required mods
Restart game

Load Order:
-Rimworld Core (that's the game)
--Hugslib
---your mods
--RimJobWorld
---addons for RimJobWorld

Contributing:
use TABS not SPACES for xml/cs, please?
do comments in code for other people

P.S.
im not a pro C# programmer or git user, so i might do weird stuff ;)