diff --git a/About/About.xml b/About/About.xml index a89aa7b6..f0c68399 100644 --- a/About/About.xml +++ b/About/About.xml @@ -5,7 +5,7 @@
  • 1.0
  • - Version 0.9.11 + Version 0.10.1 This mod force prisoners to work. To enable this feature prisoners must have "Force to work" option checked ("Prisoner" tab). Prison labor needs management that consist: - Motivation - prisoners need to be motivated by presence of colonists. Wardens have new job - supervising prisoners. Low motivation can lead to revolts. @@ -16,3 +16,4 @@ This mod force prisoners to work. To enable this feature prisoners must have "Fo This is beta version. Some ascpects of mod still need to be balanced (like motivation). There can be still some bugs. + diff --git a/Assemblies/PrisonLabor.dll b/Assemblies/PrisonLabor.dll index b5380c85..ec9ef347 100644 Binary files a/Assemblies/PrisonLabor.dll and b/Assemblies/PrisonLabor.dll differ diff --git a/Defs/ConceptDef.xml b/Defs/ConceptDef.xml index ed6bcf75..91060a87 100644 --- a/Defs/ConceptDef.xml +++ b/Defs/ConceptDef.xml @@ -38,4 +38,11 @@ True You can make time restrictions for prisoners.\n\n"Work" time will force them to work even when they're hungry or tired.\n\n"Joy" time will let them rest from job and get motivation bonus.\n\n"Sleep" time will force them to stay in prison cell.\n\n"Anything" is default setting. + + PrisonLabor_Treatment + + 50 + True + In prison labor you need to take care for prisoners. Good treatment can prevent revolts, and can provoke recruit proposition (without recruting process!).\n\nTo maintain good treatment status you need to feed prisoners regularly and set some free time so they can regenerate their strength\n\nTreatment will go lower if you beat prisoners, starve them, or exploit in terms of labor.\nGood treatment will result in:\n - Random offers with request to join colony\n - Preventing revolts\n - Preventing suicide + diff --git a/Defs/Hediffs.xml b/Defs/Hediffs.xml index ca0032cb..791281fa 100644 --- a/Defs/Hediffs.xml +++ b/Defs/Hediffs.xml @@ -25,4 +25,4 @@ - + \ No newline at end of file diff --git a/Defs/Incidents.xml b/Defs/Incidents.xml index c2281ab4..30ee950f 100644 --- a/Defs/Incidents.xml +++ b/Defs/Incidents.xml @@ -5,14 +5,43 @@
  • Map_PlayerHome
  • - PrisonLabor.IncidentWorker_Revolt + PrisonLabor.Core.Incidents.IncidentWorker_Revolt Revolt Revolt has been started by {0}. The prisoners united under faction {1}, and began uprising with self-made weapons ThreatBig - 2.7 + 5.4 20 ThreatBig true 200 + + + PrisonLabor_ResocializationOffer + + Misc + +
  • Map_PlayerHome
  • +
    + PrisonLabor.Core.Incidents.IncidentWorker_ResocializationOffer + Resocialization offer + Offer by {0}. + PositiveEvent + 10 + IncreaseEasy +
    + + + PrisonLabor_Suicide + + Misc + +
  • Map_PlayerHome
  • +
    + PrisonLabor.Core.Incidents.IncidentWorker_Suicide + Prisoner suicide + {0} has commited suicide, because of bad treatment. + NegativeEvent + 3 +
    diff --git a/Defs/JobDef.xml b/Defs/JobDef.xml index b84ad042..3627197a 100644 --- a/Defs/JobDef.xml +++ b/Defs/JobDef.xml @@ -2,28 +2,31 @@ PrisonLabor_PrisonerSupervise - PrisonLabor.JobDriver_Supervise + PrisonLabor.Core.AI.JobDrivers.JobDriver_Supervise watching prisoner TargetA. true - PrisonLabor_DeliverFood_Tweak - PrisonLabor.JobDriver_FoodDeliver_Tweak - feeding TargetA to TargetB. + PrisonLabor_Arrest + JobDriver_TakeToBed + arresting TargetA. + true + true + false PrisonLabor_Mine_Tweak - PrisonLabor.JobDriver_Mine_Tweak + PrisonLabor.Tweaks.JobDriver_Mine_Tweak digging at TargetA. PrisonLabor_Harvest_Tweak - PrisonLabor.JobDriver_PlantHarvest_Tweak + PrisonLabor.Tweaks.JobDriver_PlantHarvest_Tweak harvesting TargetA. PrisonLabor_CutPlant_Tweak - PrisonLabor.JobDriver_PlantCut_Tweak + PrisonLabor.Tweaks.JobDriver_PlantCut_Tweak cutting TargetA. diff --git a/Defs/Needs.xml b/Defs/Needs.xml index 5e0db2db..e8ab7b5c 100644 --- a/Defs/Needs.xml +++ b/Defs/Needs.xml @@ -2,11 +2,21 @@ PrisonLabor_Motivation - PrisonLabor.Need_Motivation + PrisonLabor.Core.Needs.Need_Motivation Motivation represents how motivated to work is prisoner. Motivation can be improved by colonists standing nearby. 90 false false + + PrisonLabor_Treatment + PrisonLabor.Core.Needs.Need_Treatment + + Treatment happiness represents how prisoners are content of treatment in colony. + 89 + false + false + false + diff --git a/Defs/ThinkTreeDef.xml b/Defs/ThinkTreeDef.xml index 37e0fa6d..18571ade 100644 --- a/Defs/ThinkTreeDef.xml +++ b/Defs/ThinkTreeDef.xml @@ -4,14 +4,20 @@ PrisonLabor_WorkThinkTree Humanlike_PostDuty 80 - + false +
  • + +
  • + +
  • -
  • -
  • -
  • +
  • +
  • +
  • +
  • diff --git a/Defs/ThoughtsDef.xml b/Defs/ThoughtsDef.xml new file mode 100644 index 00000000..4fb5052d --- /dev/null +++ b/Defs/ThoughtsDef.xml @@ -0,0 +1,37 @@ + + + + + PrisonLabor_VeryGoodTreatment + PrisonLabor.Core.AI.ThoughtWorkers.ThoughtWorker_VeryGoodTreatment + +
  • + + In this prison I've been treated very well. + 15 +
  • +
    +
    + + PrisonLabor_LowMotivation + PrisonLabor.Core.AI.ThoughtWorkers.ThoughtWorker_LowMotivation + +
  • + + Nobody cares if I'm working or not. + 5 +
  • +
    +
    + + PrisonLabor_FreeTime + PrisonLabor.Core.AI.ThoughtWorkers.ThoughtWorker_FreeTime + +
  • + + Even tough I'm prisoner, I can still have some free time. + 5 +
  • +
    +
    +
    diff --git a/Defs/WorkGiverDef.xml b/Defs/WorkGiverDef.xml index 33d08d24..aa7f87aa 100644 --- a/Defs/WorkGiverDef.xml +++ b/Defs/WorkGiverDef.xml @@ -3,7 +3,7 @@ PrisonLabor_SupervisePrisonLabor - PrisonLabor.WorkGiver_Supervise + PrisonLabor.Core.AI.WorkGivers.WorkGiver_Supervise PrisonLabor_Jailor 5 watch prisoner diff --git a/Images/Discord.png b/Images/Discord.png new file mode 100644 index 00000000..314babf0 Binary files /dev/null and b/Images/Discord.png differ diff --git a/Images/Discord.xcf b/Images/Discord.xcf new file mode 100644 index 00000000..0f88c6d6 Binary files /dev/null and b/Images/Discord.xcf differ diff --git a/Images/FreezingIcon.xcf b/Images/FreezingIcon.xcf new file mode 100644 index 00000000..28177293 Binary files /dev/null and b/Images/FreezingIcon.xcf differ diff --git a/Images/InspireIcon.xcf b/Images/InspireIcon.xcf index bde0d8d1..42de3e7c 100644 Binary files a/Images/InspireIcon.xcf and b/Images/InspireIcon.xcf differ diff --git a/Images/LazyIcon.xcf b/Images/LazyIcon.xcf new file mode 100644 index 00000000..0b91a961 Binary files /dev/null and b/Images/LazyIcon.xcf differ diff --git a/Images/deleteLabor.png b/Images/deleteLabor.png deleted file mode 100644 index 2ddf9e3b..00000000 Binary files a/Images/deleteLabor.png and /dev/null differ diff --git a/Images/extendLabor.png b/Images/extendLabor.png deleted file mode 100644 index e3cd185e..00000000 Binary files a/Images/extendLabor.png and /dev/null differ diff --git a/Languages/ChineseSimplified/Keyed/Keys.xml b/Languages/ChineseSimplified/Keyed/Keys.xml index 27a7500b..d85aef5d 100644 --- a/Languages/ChineseSimplified/Keyed/Keys.xml +++ b/Languages/ChineseSimplified/Keyed/Keys.xml @@ -49,4 +49,6 @@ When enabled revolts will sometimes occur instead of vanilla incidents Labor area isn't required to make prisoners work.\n\nThis area forbids colonists from working.\n\nIt's for situations where you don't want colonists to work in certain area. Prisoners will work anywhere they can enter. Don't show again + +Upgrading [PrisonLabor] mod diff --git a/Languages/ChineseTraditional/Keyed/Keys.xml b/Languages/ChineseTraditional/Keyed/Keys.xml index 644a5571..b4fa319a 100644 --- a/Languages/ChineseTraditional/Keyed/Keys.xml +++ b/Languages/ChineseTraditional/Keyed/Keys.xml @@ -52,5 +52,5 @@ 所有人 殖民者 囚犯 - - +Upgrading [PrisonLabor] mod + \ No newline at end of file diff --git a/Languages/Dutch/Keyed/Keys.xml b/Languages/Dutch/Keyed/Keys.xml index 83938b3a..efa35396 100644 --- a/Languages/Dutch/Keyed/Keys.xml +++ b/Languages/Dutch/Keyed/Keys.xml @@ -49,4 +49,6 @@ When enabled revolts will sometimes occur instead of vanilla incidents Labor area isn't required to make prisoners work.\n\nThis area forbids colonists from working.\n\nIt's for situations where you don't want colonists to work in certain area. Prisoners will work anywhere they can enter. Don't show again + +Upgrading [PrisonLabor] mod diff --git a/Languages/English/Keyed/Keys.xml b/Languages/English/Keyed/Keys.xml index 5814442b..21849222 100644 --- a/Languages/English/Keyed/Keys.xml +++ b/Languages/English/Keyed/Keys.xml @@ -13,17 +13,13 @@ allow all allow all work types allowed work types: - browse + Browse Motivation mechanics (!) When checked prisoners need to be motivated.\n\nWARINING: Needs reloading save. Inspiration/Motivation Icons When enabled icons will be displayed above prisoners heads. Blue icon for inspiration, and green icon for gaining motivation by other factors. Prisoners can grow advanced plants When disabled prisoners can only grow plants that not require any skills. - Restart then re-save your game. - After this steps you can safely disable this mod. - Disable mod - When enabled, worlds that are saved are transferred to 'safe Mode', and can be played without mod. Version: Difficulty: Defaults @@ -42,15 +38,40 @@ Labor area is area where only prisoners can work. No colonist's work allowed here except warden type jobs. Clear Labor Area Expand Labor Area - Colonists only - Prisoners only - Colony only + Enable revolts When enabled revolts will sometimes occur instead of vanilla incidents Labor area isn't required to make prisoners work.\n\nThis area forbids colonists from working.\n\nIt's for situations where you don't want colonists to work in certain area. Prisoners will work anywhere they can enter. Don't show again + Show treatment happiness + When enabled treatment happiness will be shown in Needs tab. Everyone Colonists Prisoners + + For everyone + Limit to colonists + Limit to prisoners + + + Recruit + Ready to join colony + + Prisoners can escape + Prisoners can reach end of map and escape. They will run unless colonist will keep an eye on them. + Those prisoners can escape:\n\n{0}\nThey will run unless colonist will keep an eye on them. + + Select Save + Remove Prison Labor mod from save + + Backup + Proceed + + Upgrading [PrisonLabor] mod + You are going to remove Prison Labor mod from save. After this operation you will be able to play without the mod. + Please backup your file with button below. + + Tutorials + Show diff --git a/Languages/French/Keyed/Keys.xml b/Languages/French/Keyed/Keys.xml index bff7b1cc..c426fd2e 100644 --- a/Languages/French/Keyed/Keys.xml +++ b/Languages/French/Keyed/Keys.xml @@ -49,4 +49,6 @@ When enabled revolts will sometimes occur instead of vanilla incidents Labor area isn't required to make prisoners work.\n\nThis area forbids colonists from working.\n\nIt's for situations where you don't want colonists to work in certain area. Prisoners will work anywhere they can enter. Don't show again + +Upgrading [PrisonLabor] mod \ No newline at end of file diff --git a/Languages/Polish/Keyed/Keys.xml b/Languages/Polish/Keyed/Keys.xml index 464b5b87..85267cb9 100644 --- a/Languages/Polish/Keyed/Keys.xml +++ b/Languages/Polish/Keyed/Keys.xml @@ -1,4 +1,4 @@ - + Zmuś do pracy Praca i próba rekrutacji @@ -49,4 +49,6 @@ When enabled revolts will sometimes occur instead of vanilla incidents Labor area isn't required to make prisoners work.\n\nThis area forbids colonists from working.\n\nIt's for situations where you don't want colonists to work in certain area. Prisoners will work anywhere they can enter. Don't show again + + Aktualizacja modyfikacji [PrisonLabor] \ No newline at end of file diff --git a/Languages/Russian/Keyed/Keys.xml b/Languages/Russian/Keyed/Keys.xml index fb4926f4..d42025da 100644 --- a/Languages/Russian/Keyed/Keys.xml +++ b/Languages/Russian/Keyed/Keys.xml @@ -49,4 +49,5 @@ Если включить то бунт может случатся в качестве случайного события. Зона работ не обязательна для того чтоб заключенные работали. \n\n Зона работ запрещает поселенцам работу в указанных местах.\n\n Она создана для тех случаев, когда вы не хотите чтобы поселенцы работали в указанном месте. Заключенные же будут работать там, куда у них есть доступ. Больше не показывать +Upgrading [PrisonLabor] mod \ No newline at end of file diff --git a/Languages/Spanish/Keyed/Keys.xml b/Languages/Spanish/Keyed/Keys.xml index 41e340cb..abc5ec09 100644 --- a/Languages/Spanish/Keyed/Keys.xml +++ b/Languages/Spanish/Keyed/Keys.xml @@ -49,4 +49,6 @@ When enabled revolts will sometimes occur instead of vanilla incidents Labor area isn't required to make prisoners work.\n\nThis area forbids colonists from working.\n\nIt's for situations where you don't want colonists to work in certain area. Prisoners will work anywhere they can enter. Don't show again + +Upgrading [PrisonLabor] mod diff --git a/Languages/Swedish/Keyed/Keys.xml b/Languages/Swedish/Keyed/Keys.xml index 2710d648..1e5bfca0 100644 --- a/Languages/Swedish/Keyed/Keys.xml +++ b/Languages/Swedish/Keyed/Keys.xml @@ -49,4 +49,6 @@ When enabled revolts will sometimes occur instead of vanilla incidents Labor area isn't required to make prisoners work.\n\nThis area forbids colonists from working.\n\nIt's for situations where you don't want colonists to work in certain area. Prisoners will work anywhere they can enter. Don't show again + +Upgrading [PrisonLabor] mod \ No newline at end of file diff --git a/README.md b/README.md index b72947f8..30414ba6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

    - v0.9.11 + v0.10.0[DEV]

    diff --git a/Source/Behaviour_MotivationIcon.cs b/Source/Behaviour_MotivationIcon.cs deleted file mode 100644 index 820c533c..00000000 --- a/Source/Behaviour_MotivationIcon.cs +++ /dev/null @@ -1,106 +0,0 @@ -using RimWorld.Planet; -using System; -using UnityEngine; -using Verse; - -namespace PrisonLabor -{ - [StaticConstructorOnStartup] - internal class Behaviour_MotivationIcon : MonoBehaviour - { - // TODO delete later - private static bool displayedError = false; - - private static readonly Texture2D inspiredTexture; - private static readonly Texture2D motivatedTexture; - private static readonly Vector3 iconPos; - - private float worldScale; - - static Behaviour_MotivationIcon() - { - inspiredTexture = ContentFinder.Get("InspireIcon", false); - motivatedTexture = ContentFinder.Get("MotivateIcon", false); - iconPos = new Vector3(0f, 0f, 1.3f); - } - - private void DrawIcon(Texture2D texture, Vector3 pawnPos) - { - //TODO add iconSizeMult to prefs ? - var iconSizeMult = 1.0f; - //TODO add iconSize to prefs ? - var iconSize = 2.0f; - - if (texture == null) - { - Log.Message("texture cant be found"); - return; - } - - var scrPosVec = (pawnPos + iconPos).MapToUIPosition(); - var scrSize = worldScale * iconSizeMult * iconSize * 0.5f; - var scrPos = new Rect(scrPosVec.x - scrSize * 0.5f, scrPosVec.y - scrSize * 0.5f, scrSize, scrSize); - GUI.DrawTexture(scrPos, texture, ScaleMode.ScaleToFit, true); - } - - public virtual void OnGUI() - { - try - { - var iconsEnabled = PrisonLaborPrefs.EnableMotivationIcons && !PrisonLaborPrefs.DisableMod; - var inGame = Find.CurrentMap != null && Find.CurrentMap.mapPawns != null && !WorldRendererUtility.WorldRenderedNow; - - if (iconsEnabled && inGame) - foreach (var pawn in Find.CurrentMap.mapPawns.AllPawns) - { - if (pawn == null) continue; - if (pawn.RaceProps == null) continue; - - if (pawn.IsPrisonerOfColony) - { - var need = pawn.needs.TryGetNeed(); - if (need != null && need.Motivated) - if (need.Insipred) - DrawIcon(inspiredTexture, pawn.DrawPos); - else - DrawIcon(motivatedTexture, pawn.DrawPos); - } - } - } - catch (NullReferenceException e) - { - if (!displayedError) - { - Log.ErrorOnce("PrisonLaborError: null reference in OnGui() : " + e.Message + " trace: " + e.StackTrace, typeof(Behaviour_MotivationIcon).GetHashCode()); - displayedError = true; - } - } - } - - - public virtual void Update() - { - worldScale = Screen.height / (2 * Camera.current.orthographicSize); - } - - public static void Initialization() - { - var iconModule = new GameObject("PrisonLabor_Initializer"); - iconModule.AddComponent(); - DontDestroyOnLoad(iconModule); - } - } - - internal class IconModuleInitializer : MonoBehaviour - { - public void FixedUpdate() - { - var iconModule = GameObject.Find("PrisonLabor_IconModule"); - if (iconModule == null) - { - iconModule = new GameObject("PrisonLabor_IconModule"); - iconModule.AddComponent(); - } - } - } -} \ No newline at end of file diff --git a/Source/BugTracker.cs b/Source/BugTracker.cs deleted file mode 100644 index 104cc31d..00000000 --- a/Source/BugTracker.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace PrisonLabor -{ - class BugTracker - { - - } -} diff --git a/Source/CompatibilityPatches/OlderVersions.cs b/Source/CompatibilityPatches/OlderVersions.cs index 258e2560..a917ef46 100644 --- a/Source/CompatibilityPatches/OlderVersions.cs +++ b/Source/CompatibilityPatches/OlderVersions.cs @@ -1,4 +1,7 @@ -using RimWorld; +using PrisonLabor.Constants; +using PrisonLabor.Core.LaborWorkSettings; +using PrisonLabor.Core.Meta; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -13,8 +16,8 @@ internal static void Pre_v0_9_4() { if (WorkSettings.AllowedWorkTypes.Contains(WorkTypeDefOf.Warden)) WorkSettings.AllowedWorkTypes.Remove(WorkTypeDefOf.Warden); - if (WorkSettings.AllowedWorkTypes.Contains(PrisonLaborDefOf.PrisonLabor_Jailor)) - WorkSettings.AllowedWorkTypes.Remove(PrisonLaborDefOf.PrisonLabor_Jailor); + if (WorkSettings.AllowedWorkTypes.Contains(PL_DefOf.PrisonLabor_Jailor)) + WorkSettings.AllowedWorkTypes.Remove(PL_DefOf.PrisonLabor_Jailor); WorkSettings.Apply(); PrisonLaborPrefs.Save(); diff --git a/Source/CompatibilityPatches/SeedsPlease_WorkDriver_Patch.cs b/Source/CompatibilityPatches/SeedsPlease_WorkDriver_Patch.cs index 86c8f95e..5f56f931 100644 --- a/Source/CompatibilityPatches/SeedsPlease_WorkDriver_Patch.cs +++ b/Source/CompatibilityPatches/SeedsPlease_WorkDriver_Patch.cs @@ -82,7 +82,7 @@ public static IEnumerable DelegateTranspiler(ILGenerator gen, M { if (ci.opcode == OpCodes.Beq) { - yield return new CodeInstruction(OpCodes.Call, typeof(SeedsPlease_WorkDriver_Patch).GetMethod("CorrectCondition")); + yield return new CodeInstruction(OpCodes.Call, typeof(SeedsPlease_WorkDriver_Patch).GetMethod(nameof(CorrectCondition))); yield return new CodeInstruction(OpCodes.Brfalse, ci.operand); step++; } diff --git a/Source/CompatibilityPatches/SeedsPlease_WorkGiver.cs b/Source/CompatibilityPatches/SeedsPlease_WorkGiver.cs index 26b4ba7a..a8d276bc 100644 --- a/Source/CompatibilityPatches/SeedsPlease_WorkGiver.cs +++ b/Source/CompatibilityPatches/SeedsPlease_WorkGiver.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using PrisonLabor.Tweaks; using RimWorld; using Verse; using Verse.AI; diff --git a/Source/CompatibilityPatches/WorkTab.cs b/Source/CompatibilityPatches/WorkTab.cs index ff0c30d6..7b42da04 100644 --- a/Source/CompatibilityPatches/WorkTab.cs +++ b/Source/CompatibilityPatches/WorkTab.cs @@ -7,6 +7,8 @@ using PrisonLabor.Tweaks; using UnityEngine; using RimWorld.Planet; +using PrisonLabor.Core; +using PrisonLabor.Core.LaborWorkSettings; namespace PrisonLabor.CompatibilityPatches { diff --git a/Source/Constants/BGP.cs b/Source/Constants/BGP.cs new file mode 100644 index 00000000..08a78b36 --- /dev/null +++ b/Source/Constants/BGP.cs @@ -0,0 +1,51 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PrisonLabor.Constants +{ + /// + /// ||Balanced Gameplay Parameters|| + /// Defined constants for balananced gameplay, + /// stored in one place for optimized re-balancing + /// + public static class BGP + { + #region Insipiration + public const float InspireRate = 0.015f; + public const int WardenCapacity = (int)(InspireRate / Laziness_LazyRate); + public const float InpirationRange = 10.0f; + #endregion + + #region Laziness + public const float Laziness_LazyRate = 0.002f; + public const float Laziness_HungryRate = 0.006f; + public const float Laziness_TiredRate = 0.006f; + public const float Laziness_HealthRate = 0.006f; + public const float Laziness_JoyRate = 0.001f; + #endregion + + #region Escape + // Escape time = ax + b (x -- treatment level) + public const int Escape_MinLevel = 100; + public const int Escape_MaxLevel = 5000; + public const float Escape_LevelTreatmentMultiplier = 7000; + public const int Escape_LevelBase = -950; + #endregion + + #region Treatment + public const float ResocializationLevel = 0.1f; + + // 10% every 12 days + public const float LaborRate = 1f / (120f * GenDate.TicksPerDay / 150f); + // 1% every 12 days for every point of status + public const float StatusMultiplier = 1f / (1200f * GenDate.TicksPerDay / 150f); + // 10% every 6 days + public const float JoyRate = 1f / (60f * GenDate.TicksPerDay / 150f); + + public const float BeatenHit = -0.1f; + #endregion + } +} diff --git a/Source/PrisonLaborDefOf.cs b/Source/Constants/PL_DefOf.cs similarity index 64% rename from Source/PrisonLaborDefOf.cs rename to Source/Constants/PL_DefOf.cs index ffeb5866..f6a9fed4 100644 --- a/Source/PrisonLaborDefOf.cs +++ b/Source/Constants/PL_DefOf.cs @@ -1,18 +1,21 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; using System.Text; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Constants { [DefOf] - public static class PrisonLaborDefOf + public static class PL_DefOf { public static PrisonerInteractionModeDef PrisonLabor_workOption; public static PrisonerInteractionModeDef PrisonLabor_workAndRecruitOption; public static WorkTypeDef PrisonLabor_Jailor; + + public static NeedDef PrisonLabor_Motivation; + public static NeedDef PrisonLabor_Treatment; } } diff --git a/Source/JobDriver_Supervise.cs b/Source/Core/AI/JobDrivers/JobDriver_Supervise.cs similarity index 80% rename from Source/JobDriver_Supervise.cs rename to Source/Core/AI/JobDrivers/JobDriver_Supervise.cs index c46003bd..877117c1 100644 --- a/Source/JobDriver_Supervise.cs +++ b/Source/Core/AI/JobDrivers/JobDriver_Supervise.cs @@ -1,16 +1,19 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using PrisonLabor.Constants; +using PrisonLabor.Core.Needs; +using PrisonLabor.Core.Trackers; using RimWorld; using Verse; using Verse.AI; -namespace PrisonLabor +namespace PrisonLabor.Core.AI.JobDrivers { internal class JobDriver_Supervise : JobDriver { private static readonly float GapLengh = 3.0f; - protected Pawn Prisoner => (Pawn) job.targetA.Thing; + protected Pawn Prisoner => (Pawn)job.targetA.Thing; protected override IEnumerable MakeNewToils() { @@ -42,19 +45,19 @@ protected Toil MakeWatchToil(Pawn prisoner) int score = 0; int curScore = 0; bool found = false; - foreach(var cell in prisoner.GetRoom().Cells) + foreach (var cell in prisoner.GetRoom().Cells) { float distance = cell.DistanceTo(prisoner.InteractionCell); - if (distance < Need_Motivation.InpirationRange) + if (distance < BGP.InpirationRange) { if (distance < GapLengh) curScore = (int)distance; else - curScore = (int)(Need_Motivation.InpirationRange - distance); + curScore = (int)(BGP.InpirationRange - distance); foreach (var pawn in prisonersInRoom) - if (cell.DistanceTo(pawn.Position) < Need_Motivation.InpirationRange) - curScore += 100; + if (cell.DistanceTo(pawn.Position) < BGP.InpirationRange) + curScore += pawn.IsWatched() ? 50 : 100; if (curScore > score) { @@ -75,12 +78,12 @@ protected Toil MakeWatchToil(Pawn prisoner) private bool RangeCondition(Toil toil) { - return toil.actor.Position.DistanceTo(Prisoner.Position) > Need_Motivation.InpirationRange; + return toil.actor.Position.DistanceTo(Prisoner.Position) > BGP.InpirationRange; } private IEnumerable PrisonersInRoom(Room room) { - foreach(var pawn in room.Map.mapPawns.PrisonersOfColony) + foreach (var pawn in room.Map.mapPawns.PrisonersOfColony.Where(p => p.LaborEnabled() && p.needs?.TryGetNeed() != null)) { if (pawn.GetRoom() == room) yield return pawn; diff --git a/Source/JobGiver_BedTime.cs b/Source/Core/AI/JobGivers/JobGiver_BedTime.cs similarity index 90% rename from Source/JobGiver_BedTime.cs rename to Source/Core/AI/JobGivers/JobGiver_BedTime.cs index 39b1224f..30977965 100644 --- a/Source/JobGiver_BedTime.cs +++ b/Source/Core/AI/JobGivers/JobGiver_BedTime.cs @@ -1,9 +1,10 @@ -using RimWorld; +using PrisonLabor.Core.Needs; +using RimWorld; using Verse; using Verse.AI; using Verse.AI.Group; -namespace PrisonLabor +namespace PrisonLabor.Core.AI.JobGivers { internal class JobGiver_BedTime : ThinkNode_JobGiver { @@ -18,6 +19,8 @@ public override ThinkNode DeepCopy(bool resolve = true) public override float GetPriority(Pawn pawn) { + if (HealthAIUtility.ShouldHaveSurgeryDoneNow(pawn)) + return 15f; if (pawn.timetable != null && pawn.timetable.CurrentAssignment == TimeAssignmentDefOf.Sleep) return 10f; return 0f; @@ -31,7 +34,7 @@ protected override Job TryGiveJob(Pawn pawn) return null; var need = pawn.needs.TryGetNeed(); if (need != null) - need.Enabled = false; + need.IsPrisonerWorking = false; var lord = pawn.GetLord(); Building_Bed building_Bed; if (lord != null && lord.CurLordToil != null && !lord.CurLordToil.AllowRestingInBed) diff --git a/Source/Core/AI/JobGivers/JobGiver_Diet.cs b/Source/Core/AI/JobGivers/JobGiver_Diet.cs new file mode 100644 index 00000000..74411c86 --- /dev/null +++ b/Source/Core/AI/JobGivers/JobGiver_Diet.cs @@ -0,0 +1,49 @@ +using PrisonLabor.Core.Needs; +using RimWorld; +using Verse; +using Verse.AI; + +namespace PrisonLabor.Core.AI.JobGivers +{ + internal class JobGiver_Diet : JobGiver_GetFood + { + private HungerCategory minCategory = HungerCategory.Hungry; + private readonly HungerCategory stopWorkingCat = HungerCategory.UrgentlyHungry; + + public override ThinkNode DeepCopy(bool resolve = true) + { + var jobGiver_Diet = (JobGiver_Diet)base.DeepCopy(resolve); + jobGiver_Diet.minCategory = minCategory; + return jobGiver_Diet; + } + + public override float GetPriority(Pawn pawn) + { + var food = pawn.needs.food; + if (food == null) + return 0f; + if (pawn.needs.food.CurCategory < HungerCategory.Starving && FoodUtility.ShouldBeFedBySomeone(pawn)) + return 0f; + if (food.CurCategory < minCategory) + return 0f; + if (food.CurCategory <= stopWorkingCat) + return 11f; + if (food.CurLevelPercentage < pawn.RaceProps.FoodLevelPercentageWantEat) + return 7f; + return 0f; + } + + protected override Job TryGiveJob(Pawn pawn) + { + var food = pawn.needs.food; + if (food == null || food.CurCategory < minCategory) + return null; + + var need = pawn.needs.TryGetNeed(); + if (need != null) + need.IsPrisonerWorking = false; + + return base.TryGiveJob(pawn); + } + } +} \ No newline at end of file diff --git a/Source/JobGiver_Labor.cs b/Source/Core/AI/JobGivers/JobGiver_Labor.cs similarity index 95% rename from Source/JobGiver_Labor.cs rename to Source/Core/AI/JobGivers/JobGiver_Labor.cs index fa0038de..1a2f9c38 100644 --- a/Source/JobGiver_Labor.cs +++ b/Source/Core/AI/JobGivers/JobGiver_Labor.cs @@ -1,14 +1,20 @@ -using System; +using System; +using PrisonLabor.Core.LaborWorkSettings; +using PrisonLabor.Core.Meta; +using PrisonLabor.Core.Needs; +using PrisonLabor.Core.Other; using RimWorld; using Verse; using Verse.AI; -namespace PrisonLabor +namespace PrisonLabor.Core.AI.JobGivers { public class JobGiver_Labor : ThinkNode { public bool emergency; + public object Tutorials { get; private set; } + public override ThinkNode DeepCopy(bool resolve = true) { var jobGiver_Work = (JobGiver_Labor)base.DeepCopy(resolve); @@ -34,9 +40,9 @@ public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobPara //Check medical assistance, fed, and rest if not override if (!PrisonLaborUtility.WorkTime(pawn)) { - Tutorials.Timetable(); + Other.Tutorials.Timetable(); if (need != null) - need.Enabled = false; + need.IsPrisonerWorking = false; return ThinkResult.NoJob; } //Check motivation @@ -48,7 +54,7 @@ public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobPara //TODO check this //workList.RemoveAll(workGiver => workGiver.def.defName == "GrowerSow"); if (need != null) - need.Enabled = false; + need.IsPrisonerWorking = false; var num = -999; var targetInfo = TargetInfo.Invalid; @@ -66,7 +72,7 @@ public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobPara if (job2 != null) { if (need != null) - need.Enabled = true; + need.IsPrisonerWorking = true; return new ThinkResult(job2, this, workList[j].def.tagToGive); } var scanner = workGiver as WorkGiver_Scanner; @@ -162,7 +168,7 @@ public override ThinkResult TryIssueJobPackage(Pawn pawn, JobIssueParams jobPara if (job3 != null) { if (need != null) - need.Enabled = true; + need.IsPrisonerWorking = true; return new ThinkResult(job3, this, workList[j].def.tagToGive); } Log.ErrorOnce( diff --git a/Source/Core/AI/JobGivers/JobGiver_PickupWeapon.cs b/Source/Core/AI/JobGivers/JobGiver_PickupWeapon.cs new file mode 100644 index 00000000..6f5e4be1 --- /dev/null +++ b/Source/Core/AI/JobGivers/JobGiver_PickupWeapon.cs @@ -0,0 +1,127 @@ +using PrisonLabor.Core.Needs; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Verse; +using Verse.AI; + +namespace PrisonLabor.Core.AI.JobGivers +{ + public class JobGiver_PickupWeapon : ThinkNode_JobGiver + { + private static TreatmentCategory maxCategory = TreatmentCategory.Bad; + + private bool preferBuildingDestroyers; + + public override float GetPriority(Pawn pawn) => 12f; + + protected override Job TryGiveJob(Pawn pawn) + { + if (pawn.equipment == null) + { + return null; + } + if (AlreadySatisfiedWithCurrentWeapon(pawn)) + { + return null; + } + var treatmentNeed = pawn.needs?.TryGetNeed(); + if(treatmentNeed == null || treatmentNeed.CurCategory > maxCategory) + { + return null; + } + if (pawn.RaceProps.Humanlike && pawn.story.WorkTagIsDisabled(WorkTags.Violent)) + { + return null; + } + if (!pawn.health.capacities.CapableOf(PawnCapacityDefOf.Manipulation)) + { + return null; + } + if (pawn.GetRegion(RegionType.Set_Passable) == null) + { + return null; + } + Thing thing = GenClosest.ClosestThingReachable(pawn.Position, pawn.Map, ThingRequest.ForGroup(ThingRequestGroup.Weapon), PathEndMode.OnCell, TraverseParms.For(pawn, Danger.Deadly, TraverseMode.ByPawn, false), 8f, (Thing x) => pawn.CanReserve(x, 1, -1, null, false) && ShouldEquip(x, pawn), null, 0, 15, false, RegionType.Set_Passable, false); + if (thing != null) + { + return new Job(JobDefOf.Equip, thing); + } + return null; + } + + public override ThinkNode DeepCopy(bool resolve = true) + { + JobGiver_PickupWeapon jobGiver = (JobGiver_PickupWeapon)base.DeepCopy(resolve); + jobGiver.preferBuildingDestroyers = preferBuildingDestroyers; + return jobGiver; + } + + private bool ShouldEquip(Thing newWep, Pawn pawn) + { + return GetWeaponScore(newWep) > GetWeaponScore(pawn.equipment.Primary); + } + + private int GetWeaponScore(Thing wep) + { + if (wep == null) + { + return 0; + } + if (wep.def.IsMeleeWeapon && wep.GetStatValue(StatDefOf.MeleeWeapon_AverageDPS, true) < MinMeleeWeaponDPSThreshold) + { + return 0; + } + if (preferBuildingDestroyers && wep.TryGetComp().PrimaryVerb.verbProps.ai_IsBuildingDestroyer) + { + return 3; + } + if (wep.def.IsRangedWeapon) + { + return 2; + } + return 1; + } + + private bool AlreadySatisfiedWithCurrentWeapon(Pawn pawn) + { + ThingWithComps primary = pawn.equipment.Primary; + if (primary == null) + { + return false; + } + if (preferBuildingDestroyers) + { + if (!pawn.equipment.PrimaryEq.PrimaryVerb.verbProps.ai_IsBuildingDestroyer) + { + return false; + } + } + else if (!primary.def.IsRangedWeapon) + { + return false; + } + return true; + } + + private float MinMeleeWeaponDPSThreshold + { + get + { + List tools = ThingDefOf.Human.tools; + float num = 0f; + for (int i = 0; i < tools.Count; i++) + { + if (tools[i].linkedBodyPartsGroup == BodyPartGroupDefOf.LeftHand || tools[i].linkedBodyPartsGroup == BodyPartGroupDefOf.RightHand) + { + num = tools[i].power / tools[i].cooldownTime; + break; + } + } + return num + 2f; + } + } + } +} diff --git a/Source/ThinkNode_Labor.cs b/Source/Core/AI/ThinkNodes/ThinkNode_Labor.cs similarity index 69% rename from Source/ThinkNode_Labor.cs rename to Source/Core/AI/ThinkNodes/ThinkNode_Labor.cs index 336a1062..cada9731 100644 --- a/Source/ThinkNode_Labor.cs +++ b/Source/Core/AI/ThinkNodes/ThinkNode_Labor.cs @@ -1,8 +1,11 @@ -using RimWorld; +using PrisonLabor.Core.Needs; +using PrisonLabor.Core.Other; +using PrisonLabor.Core.Trackers; +using RimWorld; using Verse; using Verse.AI; -namespace PrisonLabor +namespace PrisonLabor.Core.AI.ThinkNodes { internal class ThinkNode_Labor : ThinkNode_Conditional { @@ -29,16 +32,17 @@ protected override bool Satisfied(Pawn pawn) // Prisoner will escape if get ready to run. // If he can run he will start ticking impatient, once complete he will get ready. - if (!pawn.guest.PrisonerIsSecure || - RCellFinder.TryFindBestExitSpot(pawn, out c, TraverseMode.ByPawn)) + var escapeTracker = EscapeTracker.Of(pawn, true); + if (pawn.guest.PrisonerIsSecure && RCellFinder.TryFindBestExitSpot(pawn, out c, TraverseMode.ByPawn)) { - need.CanEscape = true; - if (need.ReadyToRun) + if (escapeTracker.ReadyToEscape) return false; + else + escapeTracker.CanEscape = true; } else { - need.CanEscape = false; + escapeTracker.CanEscape = false; } @@ -47,7 +51,7 @@ protected override bool Satisfied(Pawn pawn) return true; } - need.Enabled = false; + need.IsPrisonerWorking = false; } return false; } diff --git a/Source/Core/AI/ThinkNodes/ThinkNode_SeekSafeTemperature.cs b/Source/Core/AI/ThinkNodes/ThinkNode_SeekSafeTemperature.cs new file mode 100644 index 00000000..97c76a3c --- /dev/null +++ b/Source/Core/AI/ThinkNodes/ThinkNode_SeekSafeTemperature.cs @@ -0,0 +1,21 @@ +using PrisonLabor.Core.Trackers; +using RimWorld; +using Verse; +using Verse.AI; + +namespace PrisonLabor.Core.AI.ThinkNodes +{ + internal class ThinkNode_SeekSafeTemperature : ThinkNode_Conditional + { + protected override bool Satisfied(Pawn pawn) + { + if (pawn.IsPrisoner) + { + if (pawn.IsWatched() && PrisonLaborUtility.WorkTime(pawn)) + return false; + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/Source/Core/AI/ThoughtWorkers/ThoughtWorker_FreeTime.cs b/Source/Core/AI/ThoughtWorkers/ThoughtWorker_FreeTime.cs new file mode 100644 index 00000000..a867d701 --- /dev/null +++ b/Source/Core/AI/ThoughtWorkers/ThoughtWorker_FreeTime.cs @@ -0,0 +1,19 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Verse; + +namespace PrisonLabor.Core.AI.ThoughtWorkers +{ + public class ThoughtWorker_FreeTime : ThoughtWorker + { + protected override ThoughtState CurrentStateInternal(Pawn p) + { + if (!p.IsPrisoner) + return false; + return p.timetable != null && p.timetable.CurrentAssignment == TimeAssignmentDefOf.Joy; + } + } +} diff --git a/Source/Core/AI/ThoughtWorkers/ThoughtWorker_LowMotivation.cs b/Source/Core/AI/ThoughtWorkers/ThoughtWorker_LowMotivation.cs new file mode 100644 index 00000000..5486083e --- /dev/null +++ b/Source/Core/AI/ThoughtWorkers/ThoughtWorker_LowMotivation.cs @@ -0,0 +1,21 @@ +using PrisonLabor.Core.Needs; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Verse; + +namespace PrisonLabor.Core.AI.ThoughtWorkers +{ + public class ThoughtWorker_LowMotivation : ThoughtWorker + { + protected override ThoughtState CurrentStateInternal(Pawn p) + { + if (!p.IsPrisoner) + return false; + var need = p.needs.TryGetNeed(); + return need != null && need.IsLazy; + } + } +} diff --git a/Source/Core/AI/ThoughtWorkers/ThoughtWorker_VeryGoodTreatment.cs b/Source/Core/AI/ThoughtWorkers/ThoughtWorker_VeryGoodTreatment.cs new file mode 100644 index 00000000..0bc43a26 --- /dev/null +++ b/Source/Core/AI/ThoughtWorkers/ThoughtWorker_VeryGoodTreatment.cs @@ -0,0 +1,21 @@ +using PrisonLabor.Core.Needs; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Verse; + +namespace PrisonLabor.Core.AI.ThoughtWorkers +{ + public class ThoughtWorker_VeryGoodTreatment : ThoughtWorker + { + protected override ThoughtState CurrentStateInternal(Pawn p) + { + if (!p.IsPrisoner) + return false; + var need = p.needs.TryGetNeed(); + return need != null && need.CurCategory == TreatmentCategory.VeryGood; + } + } +} diff --git a/Source/WorkGiver_Supervise.cs b/Source/Core/AI/WorkGivers/WorkGiver_Supervise.cs similarity index 70% rename from Source/WorkGiver_Supervise.cs rename to Source/Core/AI/WorkGivers/WorkGiver_Supervise.cs index 9c145838..a8520c1f 100644 --- a/Source/WorkGiver_Supervise.cs +++ b/Source/Core/AI/WorkGivers/WorkGiver_Supervise.cs @@ -1,16 +1,17 @@ -using RimWorld; +using PrisonLabor.Core.Needs; +using PrisonLabor.Core.Trackers; +using RimWorld; using Verse; using Verse.AI; -namespace PrisonLabor +namespace PrisonLabor.Core.AI.WorkGivers { internal class WorkGiver_Supervise : WorkGiver_Warden { public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false) { var prisoner = t as Pawn; - var need = prisoner.needs.TryGetNeed(); - + var need = prisoner?.needs.TryGetNeed(); if (need == null || prisoner == null) return null; if (!ShouldTakeCareOfPrisoner(pawn, prisoner)) @@ -19,11 +20,12 @@ public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false) return null; if (pawn.IsPrisoner) return null; - if (!PrisonLaborUtility.LaborEnabled(prisoner) && !need.CanEscape) + var escapeTracker = EscapeTracker.Of(prisoner, true); + if (!PrisonLaborUtility.LaborEnabled(prisoner) && !escapeTracker.CanEscape) return null; if (PrisonLaborUtility.RecruitInLaborEnabled(prisoner)) return new Job(JobDefOf.PrisonerAttemptRecruit, t); - if ((!PrisonLaborUtility.WorkTime(prisoner) || !need.NeedToBeInspired) && !need.CanEscape) + if ((!PrisonLaborUtility.WorkTime(prisoner) || !need.ShouldToBeMotivated) && !escapeTracker.CanEscape) return null; return new Job(DefDatabase.GetNamed("PrisonLabor_PrisonerSupervise"), prisoner); diff --git a/Source/Core/Alerts/Alert_EscapingPrisoners.cs b/Source/Core/Alerts/Alert_EscapingPrisoners.cs new file mode 100644 index 00000000..84394d61 --- /dev/null +++ b/Source/Core/Alerts/Alert_EscapingPrisoners.cs @@ -0,0 +1,50 @@ +using PrisonLabor.Core.Meta; +using PrisonLabor.Core.Other; +using PrisonLabor.Core.Trackers; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using Verse; + +namespace PrisonLabor.Core.Alerts +{ + public class Alert_EscapingPrisoners : Alert_Critical + { + public Alert_EscapingPrisoners() + { + defaultLabel = "PrisonLabor_Alert_EscapingPrisoners_Title".Translate(); + defaultExplanation = "PrisonLabor_Alert_EscapingPrisoners_DefaultExplanation".Translate(); + } + + private IEnumerable PotentialEscapingPrisoners + { + get + { + var maps = Find.Maps; + for (var i = 0; i < maps.Count; i++) + foreach (var pawn in maps[i].mapPawns.AllPawns.Where(p=>p.IsPrisoner && EscapeTracker.Of(p, true).CanEscape)) + yield return pawn; + } + } + + public override string GetExplanation() + { + Tutorials.Motivation(); + + var stringBuilder = new StringBuilder(); + foreach (var current in PotentialEscapingPrisoners) + stringBuilder.AppendLine(" " + current.Name.ToStringShort); + return string.Format("PrisonLabor_Alert_EscapingPrisoners_ExplanationFormat".Translate(), stringBuilder.ToString()); + } + + public override AlertReport GetReport() + { + if (PrisonLaborPrefs.EnableMotivationMechanics) + return AlertReport.CulpritIs(PotentialEscapingPrisoners.FirstOrDefault()); + return false; + } + } +} diff --git a/Source/Alert_LazyPrisoners.cs b/Source/Core/Alerts/Alert_LazyPrisoners.cs similarity index 88% rename from Source/Alert_LazyPrisoners.cs rename to Source/Core/Alerts/Alert_LazyPrisoners.cs index b7762fb1..67634481 100644 --- a/Source/Alert_LazyPrisoners.cs +++ b/Source/Core/Alerts/Alert_LazyPrisoners.cs @@ -1,10 +1,13 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text; +using PrisonLabor.Core.Meta; +using PrisonLabor.Core.Needs; +using PrisonLabor.Core.Other; using RimWorld; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core.Alerts { internal class Alert_LazyPrisoners : Alert { @@ -29,6 +32,8 @@ private IEnumerable LazyPrisoners public override string GetExplanation() { + Tutorials.Motivation(); + var stringBuilder = new StringBuilder(); foreach (var current in LazyPrisoners) stringBuilder.AppendLine(" " + current.Name.ToStringShort); diff --git a/Source/Alert_StarvingPrisoners.cs b/Source/Core/Alerts/Alert_StarvingPrisoners.cs similarity index 87% rename from Source/Alert_StarvingPrisoners.cs rename to Source/Core/Alerts/Alert_StarvingPrisoners.cs index 24b108b4..4c408527 100644 --- a/Source/Alert_StarvingPrisoners.cs +++ b/Source/Core/Alerts/Alert_StarvingPrisoners.cs @@ -1,10 +1,12 @@ -using System.Collections.Generic; +using PrisonLabor.Core.Meta; +using PrisonLabor.Core.Needs; +using RimWorld; +using System.Collections.Generic; using System.Linq; using System.Text; -using RimWorld; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core.Alerts { internal class Alert_StarvingPrisoners : Alert { @@ -40,9 +42,7 @@ public override string GetExplanation() public override AlertReport GetReport() { - if (!PrisonLaborPrefs.DisableMod) - return AlertReport.CulpritIs(StarvingPrisoners.FirstOrDefault()); - return false; + return AlertReport.CulpritIs(StarvingPrisoners.FirstOrDefault()); } } } \ No newline at end of file diff --git a/Source/Core/BaseClasses/SimpleTimer.cs b/Source/Core/BaseClasses/SimpleTimer.cs new file mode 100644 index 00000000..dadb8fb2 --- /dev/null +++ b/Source/Core/BaseClasses/SimpleTimer.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Verse; + +namespace PrisonLabor.Core.BaseClasses +{ + public class SimpleTimer : IExposable + { + private bool _IsActive; + public bool IsActive + { + get => _IsActive; + set => _IsActive = value; + } + + private int _Ticks; + public int Ticks + { + get => _Ticks; + set => _Ticks = value; + } + + public void Start() => IsActive = true; + + public void Stop() => IsActive = false; + + public void Reset() => Ticks = 0; + + public void Tick() + { + if (_IsActive) + { + Ticks++; + } + } + + public void ResetAndStop() + { + IsActive = false; + Ticks = 0; + } + + public void ExposeData() + { + Scribe_Values.Look(ref _IsActive, nameof(IsActive)); + Scribe_Values.Look(ref _Ticks, nameof(Ticks)); + } + } +} diff --git a/Source/BillUtility.cs b/Source/Core/BillAssignation/BillAssignationUtility.cs similarity index 87% rename from Source/BillUtility.cs rename to Source/Core/BillAssignation/BillAssignationUtility.cs index 06129088..36dcf153 100644 --- a/Source/BillUtility.cs +++ b/Source/Core/BillAssignation/BillAssignationUtility.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; +using System.Collections.Generic; using RimWorld; -namespace PrisonLabor +namespace PrisonLabor.Core.BillAssignation { - internal class BillUtility + internal class BillAssignationUtility { private static readonly Dictionary Map = new Dictionary(); diff --git a/Source/BillGroupData.cs b/Source/Core/BillAssignation/BillGroupData.cs similarity index 88% rename from Source/BillGroupData.cs rename to Source/Core/BillAssignation/BillGroupData.cs index 8b99daa2..0dce3ae3 100644 --- a/Source/BillGroupData.cs +++ b/Source/Core/BillAssignation/BillGroupData.cs @@ -1,6 +1,6 @@ -using Verse; +using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core.BillAssignation { public enum GroupMode { diff --git a/Source/Core/GUI_Components/PawnIcons.cs b/Source/Core/GUI_Components/PawnIcons.cs new file mode 100644 index 00000000..57a37a2f --- /dev/null +++ b/Source/Core/GUI_Components/PawnIcons.cs @@ -0,0 +1,93 @@ +using PrisonLabor.Core.Meta; +using PrisonLabor.Core.Needs; +using PrisonLabor.Core.Trackers; +using System; +using UnityEngine; +using Verse; + +namespace PrisonLabor.Core.GUI_Components +{ + [StaticConstructorOnStartup] + public class PawnIcons : MapComponent + { + private static readonly Texture2D watchedTexture; + private static readonly Texture2D lazyTexture; + private static readonly Texture2D freezingTexture; + private static readonly Vector3 iconPos; + + private static float worldScale; + + static PawnIcons() + { + watchedTexture = ContentFinder.Get("InspireIcon", false); + lazyTexture = ContentFinder.Get("LazyIcon", false); + freezingTexture = ContentFinder.Get("FreezingIcon", false); + iconPos = new Vector3(0.3f, 0f, 0.9f); + } + + public PawnIcons(Map map) : base(map) { } + + private static void DrawIcon(Texture2D texture, Vector3 pawnPos) + { + //TODO add iconSizeMult to prefs ? + var iconSizeMult = 1.0f; + //TODO add iconSize to prefs ? + var iconSize = 2.0f; + + if (texture == null) + { + Log.Message("texture cant be found"); + return; + } + + var scrPosVec = (pawnPos + iconPos).MapToUIPosition(); + var scrSize = worldScale * iconSizeMult * iconSize * 0.5f; + var scrPos = new Rect(scrPosVec.x - scrSize * 0.5f, scrPosVec.y - scrSize * 0.5f, scrSize, scrSize); + GUI.DrawTexture(scrPos, texture, ScaleMode.ScaleToFit, true); + } + + public override void MapComponentOnGUI() + { + try + { + if (!PrisonLaborPrefs.EnableMotivationIcons) + return; + + if (map.mapPawns == null) + return; + + foreach (var pawn in map.mapPawns.AllPawns) + { + if (pawn == null) continue; + if (pawn.RaceProps == null) continue; + + if (pawn.IsPrisonerOfColony && pawn.CarriedBy == null) + { + var need = pawn.needs.TryGetNeed(); + if (pawn.health.hediffSet.HasTemperatureInjury(TemperatureInjuryStage.Serious) && PrisonLaborUtility.WorkTime(pawn)) + { + DrawIcon(freezingTexture, pawn.DrawPos); + } + else if (pawn.IsWatched()) + { + DrawIcon(watchedTexture, pawn.DrawPos); + } + else if (need != null && need.IsLazy && PrisonLaborUtility.LaborEnabled(pawn) && PrisonLaborUtility.WorkTime(pawn)) + { + DrawIcon(lazyTexture, pawn.DrawPos); + } + } + } + } + catch (NullReferenceException e) + { + Log.ErrorOnce("PrisonLaborError: null reference in OnGui() : " + e.Message + " trace: " + e.StackTrace, typeof(PawnIcons).GetHashCode()); + } + } + + public override void MapComponentTick() + { + worldScale = Screen.height / (2 * Camera.current.orthographicSize); + } + } +} \ No newline at end of file diff --git a/Source/PawnTable_Prisoners.cs b/Source/Core/GUI_Components/PawnTable_Prisoners.cs similarity index 89% rename from Source/PawnTable_Prisoners.cs rename to Source/Core/GUI_Components/PawnTable_Prisoners.cs index 870f6756..311042da 100644 --- a/Source/PawnTable_Prisoners.cs +++ b/Source/Core/GUI_Components/PawnTable_Prisoners.cs @@ -1,11 +1,11 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; using System.Text; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core.GUI_Components { public class PawnTable_Prisoners : PawnTable { diff --git a/Source/Core/GUI_Components/RichListing.cs b/Source/Core/GUI_Components/RichListing.cs new file mode 100644 index 00000000..564f293d --- /dev/null +++ b/Source/Core/GUI_Components/RichListing.cs @@ -0,0 +1,232 @@ +using System.Collections.Generic; +using UnityEngine; +using Verse; + +namespace PrisonLabor.Core.GUI_Components +{ + public class RichListing + { + private Vector2 scrollPosition; + private Rect windowRect; + private Rect viewRect; + private IEnumerable entries; + + + public float Spacing { get; set; } + public float GapHeight { get; set; } + public string MarginText { get; set; } + public float MarginWidth { get; set; } + public GameFont TitleFont { get; set; } + public GameFont ItemFont { get; set; } + + public RichListing() // Defaults + { + TitleFont = GameFont.Medium; + ItemFont = GameFont.Small; + + MarginText = " - "; + MarginWidth = Text.fontStyles[1].CalcSize(new GUIContent(MarginText)).x; + + GapHeight = 12f; + Spacing = 2f; + } + + public void PreRender(Rect bounds, IEnumerable entries) + { + var calculatedRect = new Rect(0, 0, bounds.width - 16f, CalculateHeight(bounds.width - 16f, entries)); + viewRect = calculatedRect; + windowRect = bounds; + this.entries = entries; + } + + public void OnGui() + { + Start(windowRect, viewRect); + foreach (var entry in entries) + Append(entry); + End(); + } + + public void OnGui(ref Vector2 scroller) + { + Start(windowRect, viewRect, ref scroller); + foreach (var entry in entries) + Append(entry); + End(); + } + + public void Start(Rect windowRect, Rect viewRect) + { + this.viewRect = viewRect; + Widgets.BeginScrollView(windowRect, ref scrollPosition, viewRect, true); + } + + public void Start(Rect windowRect, Rect viewRect, ref Vector2 scroller) + { + this.viewRect = viewRect; + Widgets.BeginScrollView(windowRect, ref scroller, viewRect, true); + } + + public void Append(string item) + { + //Insert html formatting + item = item + .Replace("[b]", "") + .Replace("[/b]", ""); + + Text.Font = ItemFont; + + // Draw title + if (item.StartsWith("[title]")) + { + Text.Font = TitleFont; + Widgets.Label(viewRect, item.Substring(7)); + viewRect.y += Text.CalcHeight(item, viewRect.width) + Spacing; + + // Draw line gap + Color color = GUI.color; + GUI.color = GUI.color * new Color(1f, 1f, 1f, 0.4f); + Widgets.DrawLineHorizontal(viewRect.x, viewRect.y + +GapHeight * 0.5f, viewRect.width); + GUI.color = color; + viewRect.y += GapHeight; + } + // Draw Image with Text + else if (item.StartsWith("[img]")) + { + int imgLength = item.IndexOf("[/img]"); + var imageString = item.Substring(5, imgLength - 5); + var textToDraw = item.Substring(imgLength + 6); + + var content = new GUIContent(); + content.image = ContentFinder.Get(imageString, false); + content.text = textToDraw; + Widgets.Label(viewRect, content); + + viewRect.y += GuiStyle(Text.Font).CalcHeight(content, viewRect.width); + } + // Draw Gap + else if (item.StartsWith("[gap]")) + { + Color color = GUI.color; + GUI.color = GUI.color * new Color(1f, 1f, 1f, 0.4f); + Widgets.DrawLineHorizontal(viewRect.x, viewRect.y + +GapHeight * 0.5f, viewRect.width); + GUI.color = color; + viewRect.y += GapHeight; + } + // Draw Subtitle (without margin, old) + else if (item.StartsWith("[subtitle]")) + { + Widgets.Label(viewRect, item.Substring(10)); + viewRect.y += Text.CalcHeight(item.Substring(10), viewRect.width) + Spacing; + } + // Draw Video + else if (item.StartsWith("[video]")) + { + int imgLength = item.IndexOf("[/video]"); + var framesSrc = item.Substring(7, imgLength - 7); + var dimensions = item.Substring(imgLength + 8).Split('x'); + + Vector2 videoSize = dimensions.Length >= 2 ? new Vector2(int.Parse(dimensions[0]), int.Parse(dimensions[1])) : new Vector2(100, 100); + int framesPerSecond = dimensions.Length >= 3 ? int.Parse(dimensions[2]) : 10; + + new SimpleVideo(framesSrc, framesPerSecond).OnGui(new Rect((viewRect.x - videoSize.x) / 2, viewRect.y, videoSize.x, videoSize.y)); + + viewRect.y += videoSize.y; + } + // List point (with margin) + else if (item.StartsWith("[-]")) + { + viewRect.width -= MarginWidth; + Widgets.Label(viewRect, MarginText); + viewRect.x += MarginWidth; + Widgets.Label(viewRect, item.Substring(3)); + viewRect.x -= MarginWidth; + viewRect.y += Text.CalcHeight(item.Substring(3), viewRect.width) + Spacing; + viewRect.width += MarginWidth; + } + // Draw Text + else + { + Widgets.Label(viewRect, item); + viewRect.y += Text.CalcHeight(item, viewRect.width) + Spacing; + } + } + + public void End() + { + Widgets.EndScrollView(); + } + + private float CalculateHeight(float width, IEnumerable items) + { + float height = 0; + foreach (var item in items) + { + if (item.StartsWith("[title]")) + { + Text.Font = TitleFont; + height += Text.CalcHeight(item.Substring(7), width) + Spacing + GapHeight; + } + // Image with Text + else if (item.StartsWith("[img]")) + { + Text.Font = ItemFont; + int imgLength = item.IndexOf("[/img]"); + var imageString = item.Substring(5, imgLength - 5); + var textToDraw = item.Substring(imgLength + 6); + + var content = new GUIContent(); + content.image = ContentFinder.Get(imageString, false); + content.text = textToDraw; + + + height += GuiStyle(Text.Font).CalcHeight(content, width); + } + // Gap + else if (item.StartsWith("[gap]")) + { + height += GapHeight; + } + else if (item.StartsWith("[subtitle]")) + { + Text.Font = ItemFont; + height += Text.CalcHeight(item.Substring(10), width) + Spacing; + } + else if (item.StartsWith("[-]")) + { + Text.Font = ItemFont; + height += Text.CalcHeight(item.Substring(3), width - MarginWidth) + Spacing; + } + // Only Text + else + { + Text.Font = ItemFont; + height += Text.CalcHeight(item, width) + Spacing; + } + } + return height; + } + + private static GUIStyle GuiStyle(GameFont font) + { + GUIStyle gUIStyle; + switch (font) + { + case GameFont.Tiny: + gUIStyle = Text.fontStyles[0]; + break; + case GameFont.Small: + gUIStyle = Text.fontStyles[1]; + break; + case GameFont.Medium: + gUIStyle = Text.fontStyles[2]; + break; + default: + return null; + } + gUIStyle.alignment = Text.Anchor; + gUIStyle.wordWrap = Text.WordWrap; + return gUIStyle; + } + } +} diff --git a/Source/Core/GUI_Components/SImpleVideo.cs b/Source/Core/GUI_Components/SImpleVideo.cs new file mode 100644 index 00000000..ddca4668 --- /dev/null +++ b/Source/Core/GUI_Components/SImpleVideo.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using UnityEngine; +using Verse; + +namespace PrisonLabor.Core.GUI_Components +{ + public class SimpleVideo + { + Texture2D[] frames; + double framesPerSecond; + + public SimpleVideo(string texturesPath, double framesPerSecond) + { + var framesList = new List(); + int iterator = 0; + + Texture2D texture; + do + { + texture = ContentFinder.Get($"{texturesPath}/{iterator++}", false); + if (texture != null) + framesList.Add(texture); + } + while (texture != null); + + frames = framesList.ToArray(); + this.framesPerSecond = framesPerSecond; + } + + public void OnGui(Rect rect) + { + if (frames.Length > 0) + GUI.DrawTexture(rect, frames[(int)(Time.time * framesPerSecond) % frames.Length]); + else + Log.Warning("PrisonLabor Warning: no frames found in SimpleVideo"); + } + } +} diff --git a/Source/Core/GameSaves/SaveCleaner.cs b/Source/Core/GameSaves/SaveCleaner.cs new file mode 100644 index 00000000..92815495 --- /dev/null +++ b/Source/Core/GameSaves/SaveCleaner.cs @@ -0,0 +1,266 @@ +using PrisonLabor.Constants; +using PrisonLabor.Core.GUI_Components; +using PrisonLabor.Core.LaborArea; +using PrisonLabor.Core.Needs; +using RimWorld; +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using Verse; + +namespace PrisonLabor.Core.GameSaves +{ + public static class SaveCleaner + { + public static void BackupSavegame(string fileName) + { + string savegamePath = GenFilePaths.FilePathForSavedGame(fileName); + string backupPath = GetFilePathForBackup(savegamePath); + + File.Copy(savegamePath, backupPath, false); + Log.Message($"Save copied to \"{backupPath}\""); + } + + public static void RemoveFromSave(string fileName) + { + LongEventHandler.QueueLongEvent( + () => UpdateFile(fileName), + "Removing", + false, + (e) => OnError(e) + ); + } + + private static void UpdateFile(string fileName) + { + string filePath = GenFilePaths.FilePathForSavedGame(fileName); + + XmlElement xmlNode; + using (StreamReader streamReader = new StreamReader(filePath)) + { + using (XmlTextReader xmlTextReader = new XmlTextReader(streamReader)) + { + var XmlDocument = new XmlDocument(); + XmlDocument.Load(xmlTextReader); + xmlNode = XmlDocument.DocumentElement; + } + } + + UpdateData(xmlNode); + + using (FileStream saveStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + var xmlWriterSettings = new XmlWriterSettings(); + xmlWriterSettings.Indent = true; + xmlWriterSettings.IndentChars = "\t"; + using (XmlWriter writer = XmlWriter.Create(saveStream, xmlWriterSettings)) + { + writer.WriteStartDocument(); + writer.WriteNode(xmlNode.CreateNavigator(), false); + } + } + + Log.Message($"Save'{fileName}' converted successfuly"); + } + + private static void UpdateData(XmlElement xmlNode) + { + List removalBuffer = new List(); + XmlNode curNode = xmlNode; + + #region Meta + var metaNode = xmlNode["meta"]; + + // Meta.ModIds & Meta.ModNames + XmlNode modIdsNode = metaNode["modIds"], modNamesNode = metaNode["modNames"]; + for (int i = 0; i < modIdsNode.ChildNodes.Count; i++) + { + var modNode = modIdsNode.ChildNodes[i]; + + if (modNode.InnerText == "972057888" || modNode.InnerText == "PrisonLabor") + { + modIdsNode.RemoveChild(modIdsNode.ChildNodes[i]); + modNamesNode.RemoveChild(modNamesNode.ChildNodes[i]); + break; + } + } + + // Remove version of PL + var plVersionInfo = metaNode["PrisonLaborVersion"]; + if (plVersionInfo != null) + metaNode.RemoveChild(plVersionInfo); + #endregion + + #region Game + var gameNode = xmlNode["game"]; + + // Game.Tutor + string[] conceptDefs = { "PrisonLabor_Indroduction", "PrisonLabor_Motivation", "PrisonLabor_Growing", "PrisonLabor_Management", "PrisonLabor_Timetable" }; + + var tutorNode = gameNode["tutor"]; + //var activeLessonsNode = tutorNode["activeLesson"]; + var learningReadoutNode = tutorNode["learningReadout"]; + if (learningReadoutNode["activeConcepts"].HasChildNodes) + { + removalBuffer.Clear(); + foreach (XmlNode concept in learningReadoutNode["activeConcepts"].ChildNodes) + { + foreach (string conceptDef in conceptDefs) + { + if (concept.InnerText == conceptDef) + removalBuffer.Add(concept); + } + } + foreach (var concept in removalBuffer) + learningReadoutNode["activeConcepts"].RemoveChild(concept); + } + if (learningReadoutNode["selectedConcept"] != null) + { + removalBuffer.Clear(); + foreach (string conceptDef in conceptDefs) + { + if (learningReadoutNode["selectedConcept"].InnerText == conceptDef) + learningReadoutNode.RemoveChild(learningReadoutNode["selectedConcept"]); + } + } + //var tutorialStateNode = tutorNode["tutorialState"]; + + // Game.Maps + foreach (XmlNode mapNode in gameNode["maps"].ChildNodes) + { + // Game.Maps.AreaManager + var areaManagerNode = mapNode["areaManager"]; + + if (areaManagerNode["areas"] != null && areaManagerNode["areas"].HasChildNodes) + { + removalBuffer.Clear(); + foreach (XmlNode areaNode in areaManagerNode["areas"].ChildNodes) + { + if (areaNode.Attributes["Class"].Value == typeof(Area_Labor).FullName) + { + removalBuffer.Add(areaNode); + } + } + foreach (var node in removalBuffer) + areaManagerNode["areas"].RemoveChild(node); + } + + //Game.Maps.Components + var components = mapNode["components"]; + removalBuffer.Clear(); + foreach (XmlNode component in components) + { + if (component.Attributes["Class"].Value == typeof(PawnIcons).FullName) + removalBuffer.Add(component); + } + foreach (var item in removalBuffer) + components.RemoveChild(item); + } + + // TODO bills + + // Interaction Mode + string[] interactions = { PL_DefOf.PrisonLabor_workOption.defName, PL_DefOf.PrisonLabor_workAndRecruitOption.defName }; + + foreach (var guestTracker in gameNode.GetEveryNode("guest")) + { + var interactionMode = guestTracker["interactionMode"]; + if (interactionMode != null) + { + foreach (string interaction in interactions) + { + if (interactionMode.InnerText == interaction) + interactionMode.InnerText = PrisonerInteractionModeDefOf.NoInteraction.defName; + } + } + } + + + // Remove Heddifs + foreach (var needTracker in gameNode.GetEveryNode("needs")) + { + var needs = needTracker["needs"]; + if (needs != null) + { + + if (needs != null && needs.HasChildNodes) + { + removalBuffer.Clear(); + foreach (XmlNode need in needs.ChildNodes) + { + if (need.Attributes["Class"].Value == typeof(Need_Motivation).FullName) + removalBuffer.Add(need); + else if (need.Attributes["Class"].Value == typeof(Need_Treatment).FullName) + removalBuffer.Add(need); + } + foreach (var node in removalBuffer) + needs.RemoveChild(node); + } + } + } + + // Remove Heddifs + foreach (var hediffSet in gameNode.GetEveryNode("hediffSet")) + { + var hediffs = hediffSet["hediffs"]; + + if (hediffs != null && hediffs.HasChildNodes) + { + removalBuffer.Clear(); + foreach (XmlNode hediff in hediffs.ChildNodes) + { + if (hediff["def"].InnerText == "PrisonLabor_PrisonerChains") + { + removalBuffer.Add(hediff); + } + } + foreach (var node in removalBuffer) + hediffs.RemoveChild(node); + } + } + #endregion + } + + private static IEnumerable GetEveryNode(this XmlNode rootElement, string nodeName) + { + foreach (XmlNode node in rootElement.ChildNodes) + { + if (node.Name.Equals(nodeName)) + yield return node; + if (node.HasChildNodes) + { + foreach (XmlNode childNode in node.GetEveryNode(nodeName)) + yield return childNode; + } + } + } + + private static void OnError(Exception e) + { + Log.Error(e.ToString()); + } + + private static string GetFilePathForBackup(string filePath) + { + string originFilePathWithoutExtension = Path.GetDirectoryName(filePath) + @"\" + Path.GetFileNameWithoutExtension(filePath); + + string backupFileCoreString = originFilePathWithoutExtension + "_Backup"; + + string backupFilePathFinal = backupFileCoreString + ".rws"; + + if (!File.Exists(backupFilePathFinal)) + return backupFilePathFinal; + + for (int i = 1; i < int.MaxValue; i++) + { + backupFilePathFinal = backupFileCoreString + i.ToString() + ".rws"; + + if (!File.Exists(backupFilePathFinal)) + return backupFilePathFinal; + } + + throw new IndexOutOfRangeException(); + } + } +} diff --git a/Source/Core/GameSaves/SaveUpgrader.cs b/Source/Core/GameSaves/SaveUpgrader.cs new file mode 100644 index 00000000..55e506a1 --- /dev/null +++ b/Source/Core/GameSaves/SaveUpgrader.cs @@ -0,0 +1,47 @@ +using PrisonLabor.Core.GUI_Components; +using PrisonLabor.Core.Meta; +using PrisonLabor.Core.Needs; +using System; +using System.Xml; +using Verse; +using Version = PrisonLabor.Core.Meta.Version; + +namespace PrisonLabor.Core.GameSaves +{ + static class SaveUpgrader + { + public static void Upgrade() + { + if (Scribe.mode == LoadSaveMode.LoadingVars || Scribe.mode == LoadSaveMode.ResolvingCrossRefs || Scribe.mode == LoadSaveMode.PostLoadInit) + { + if (VersionUtility.VersionOfSaveFile == VersionUtility.versionNumber) + return; + + LongEventHandler.SetCurrentEventText("PrisonLabor_UpgradeSaveProcessMessage".Translate()); + + var xmlNode = Scribe.loader.curXmlParent; + + if (VersionUtility.VersionOfSaveFile < Version.v0_10_0) + UpTo_0_10(xmlNode); + } + } + + private static void UpTo_0_10(XmlNode xml) + { + // Replace job trackers + xml.InnerXml = xml.InnerXml.Replace("", ""); + + // Replace job defs + xml.InnerXml = xml.InnerXml.Replace("PrisonLabor_DeliverFood_Tweak", "DeliverFood"); + + // Replace need classes + xml.InnerXml = xml.InnerXml.Replace("PrisonLabor.Need_Motivation", typeof(Need_Motivation).FullName); + + // Replace need classes + xml.InnerXml = xml.InnerXml.Replace("PrisonLabor.Need_Treatment", typeof(Need_Treatment).FullName); + + // Replace need classes + xml.InnerXml = xml.InnerXml.Replace("PrisonLabor.MapComponent_Icons", typeof(PawnIcons).FullName); + } + } +} diff --git a/Source/HediffGiver_PrisonersChains.cs b/Source/Core/Hediffs/HediffGiver_PrisonersChains.cs similarity index 95% rename from Source/HediffGiver_PrisonersChains.cs rename to Source/Core/Hediffs/HediffGiver_PrisonersChains.cs index 02f06706..3c3ace79 100644 --- a/Source/HediffGiver_PrisonersChains.cs +++ b/Source/Core/Hediffs/HediffGiver_PrisonersChains.cs @@ -1,11 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using Verse; using RimWorld; -namespace PrisonLabor +namespace PrisonLabor.Core.Hediffs { class HediffGiver_PrisonersChains : HediffGiver { diff --git a/Source/HediffManager.cs b/Source/Core/Hediffs/HediffManager.cs similarity index 89% rename from Source/HediffManager.cs rename to Source/Core/Hediffs/HediffManager.cs index 6ccf0870..20198441 100644 --- a/Source/HediffManager.cs +++ b/Source/Core/Hediffs/HediffManager.cs @@ -1,11 +1,11 @@ -using RimWorld; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; using System.Text; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core.Hediffs { public static class HediffManager { diff --git a/Source/Core/Incidents/IncidentWorker_ResocializationOffer.cs b/Source/Core/Incidents/IncidentWorker_ResocializationOffer.cs new file mode 100644 index 00000000..8624ebfe --- /dev/null +++ b/Source/Core/Incidents/IncidentWorker_ResocializationOffer.cs @@ -0,0 +1,66 @@ +using System; +using UnityEngine; +using Verse; +using RimWorld; +using Verse.AI.Group; +using System.Collections.Generic; +using PrisonLabor.Constants; +using PrisonLabor.Core.Needs; +using PrisonLabor.Core.Other; + +namespace PrisonLabor.Core.Incidents +{ + public class IncidentWorker_ResocializationOffer : IncidentWorker + { + protected override bool CanFireNowSub(IncidentParms parms) + { + Map map = (Map)parms.target; + + foreach (var pawn in map.mapPawns.PrisonersOfColony) + { + if (pawn.IsColonist) + continue; + + var treatment = pawn.needs.TryGetNeed(); + if (treatment == null) + continue; + + if (treatment.CurLevel >= BGP.ResocializationLevel) + return true; + } + + return false; + } + + protected override bool TryExecuteWorker(IncidentParms parms) + { + Map map = (Map)parms.target; + Pawn prisoner = null; + var affectedPawns = new List(map.mapPawns.PrisonersOfColony); + foreach (var pawn in map.mapPawns.PrisonersOfColony) + { + if (pawn.IsColonist) + continue; + + var treatment = pawn.needs.TryGetNeed(); + if (treatment == null) + continue; + + if (treatment.CurLevel >= BGP.ResocializationLevel) + { + treatment.ResocializationReady = true; + parms.faction = pawn.Faction; + prisoner = pawn; + break; + } + } + if (prisoner == null) + return false; + + Tutorials.Treatment(); + + SendStandardLetter(prisoner, null, new string[] { prisoner.Name.ToStringShort, prisoner.Faction.Name }); + return true; + } + } +} \ No newline at end of file diff --git a/Source/Core/Incidents/IncidentWorker_Revolt.cs b/Source/Core/Incidents/IncidentWorker_Revolt.cs new file mode 100644 index 00000000..16e405d2 --- /dev/null +++ b/Source/Core/Incidents/IncidentWorker_Revolt.cs @@ -0,0 +1,179 @@ +using System; +using UnityEngine; +using Verse; +using RimWorld; +using Verse.AI.Group; +using System.Collections.Generic; +using PrisonLabor.Core.Needs; +using PrisonLabor.Core.Meta; +using PrisonLabor.Core.Other; + +namespace PrisonLabor.Core.Incidents +{ + public class IncidentWorker_Revolt : IncidentWorker + { + private const float MaxMotivationToStart = 0.4f; + private const float MaxTreatmentToStart = 3.5f; + + protected override bool CanFireNowSub(IncidentParms parms) + { + Map map = parms.target as Map; + + bool enemyFaction = false; + float accumulatedMotivation = 0.0f; + float accumulatedTreatment = 0.0f; + int prisonersCount = 0; + + // Calculate values + foreach (var pawn in map.mapPawns.PrisonersOfColony) + { + if (pawn.Faction.HostileTo(Faction.OfPlayer)) + enemyFaction = true; + + var need = pawn.needs.TryGetNeed(); + if (need == null) + continue; + accumulatedMotivation += need.CurLevel; + prisonersCount++; + } + + // If motivation is too high + if (accumulatedMotivation / prisonersCount >= MaxMotivationToStart) + return false; + // If treatment is too good + if (accumulatedTreatment / prisonersCount >= MaxTreatmentToStart) + return false; + + return enemyFaction && PrisonLaborPrefs.EnableRevolts; + } + + protected override bool TryExecuteWorker(IncidentParms parms) + { + try + { + Map map = (Map)parms.target; + Pawn t = null; + var affectedPawns = new List(map.mapPawns.PrisonersOfColony); + + // Calculate chance for blocking incident if prisoners are treated good + float treatment = 0f; + float chance = 0f; + foreach (Pawn pawn in affectedPawns) + if (pawn.needs.TryGetNeed() != null) + treatment += (float)pawn.needs.TryGetNeed().CurCategory; + treatment = treatment / affectedPawns.Count; + if (treatment < 0.5) + chance = 1f; + else if (treatment < 1.5) + chance = 0.95f; + else if (treatment < 2.5) + chance = 0.5f; + else if (treatment < 3.5) + chance = 0.1f; + + // When incident is forced, log instead of blocking + if (!parms.forced) + { + if (Prefs.DevMode) + { + string msg = $"Prison Labor: Revolt blocking chance is currently equal to {chance * 100}% (overall treatment = {treatment}). Rolling ..."; + Log.Message(msg); + } + if (UnityEngine.Random.value > chance) + return false; + } + + + foreach (Pawn pawn in affectedPawns) + { + if (pawn.Faction.HostileTo(Faction.OfPlayer)) + { + parms.faction = pawn.Faction; + t = pawn; + break; + } + } + float points = parms.points; + int prisonersLeft = affectedPawns.Count; + foreach (Pawn pawn in affectedPawns) + { + pawn.ClearMind(); + pawn.guest.SetGuestStatus(null, false); + pawn.SetFaction(parms.faction); + + ThingWithComps weapon = ThingMaker.MakeThing(DefDatabase.GetNamed("MeleeWeapon_Knife"), ThingDefOf.WoodLog) as ThingWithComps; + ThingWithComps ammo = null; + int pointsToRemove = 0; + + if (parms.points >= 1000) + weapon = ThingMaker.MakeThing(DefDatabase.GetNamed("MeleeWeapon_Knife"), ThingDefOf.Steel) as ThingWithComps; + + if (points >= 1000) + { + // If combat extended is enabled + if (DefDatabase.GetNamed("Weapon_GrenadeStickBomb", false) != null) + { + if (UnityEngine.Random.value > 0.5f) + { + weapon = ThingMaker.MakeThing(DefDatabase.GetNamed("Weapon_GrenadeStickBomb")) as ThingWithComps; + ammo = ThingMaker.MakeThing(DefDatabase.GetNamed("Weapon_GrenadeStickBomb")) as ThingWithComps; + ammo.stackCount = 6; + } + else + { + weapon = ThingMaker.MakeThing(DefDatabase.GetNamed("Weapon_GrenadeMolotov")) as ThingWithComps; + ammo = ThingMaker.MakeThing(DefDatabase.GetNamed("Weapon_GrenadeMolotov")) as ThingWithComps; + ammo.stackCount = 6; + } + } + else + { + weapon = ThingMaker.MakeThing(DefDatabase.GetNamed("Weapon_GrenadeMolotov")) as ThingWithComps; + } + + pointsToRemove = 500; + } + else if (points >= 500) + { + weapon = ThingMaker.MakeThing(DefDatabase.GetNamed("Bow_Short")) as ThingWithComps; + + if (DefDatabase.GetNamed("Ammo_Arrow_Stone", false) != null) + { + ammo = ThingMaker.MakeThing(DefDatabase.GetNamed("Ammo_Arrow_Stone")) as ThingWithComps; + ammo.stackCount = 30; + } + + pointsToRemove = 100; + } + else if (points >= 300) + { + weapon = ThingMaker.MakeThing(DefDatabase.GetNamed("MeleeWeapon_Club"), ThingDefOf.Granite) as ThingWithComps; + pointsToRemove = 100; + } + + if (pawn.equipment.Primary == null) + { + pawn.equipment.AddEquipment(weapon); + if (ammo != null) + pawn.inventory.innerContainer.TryAdd(ammo); + points -= pointsToRemove; + } + } + var lordJob = new LordJob_AssaultColony(parms.faction, true, true, false, true, true); + //TODO old code: + LordMaker.MakeNewLord(parms.faction, lordJob/*(new RaidStrategyWorker_ImmediateAttackSmart()).MakeLordJob(parms, map)*/, map, affectedPawns); + SendStandardLetter(t, null, new string[] { t.Name.ToStringShort, t.Faction.Name }); + Find.TickManager.slower.SignalForceNormalSpeedShort(); + + Tutorials.Treatment(); + + return true; + } + catch(Exception e) + { + Log.Error($"PrisonLabor: Erron on executing Revolt Incident: {e.ToString()}"); + return false; + } + } + } +} \ No newline at end of file diff --git a/Source/Core/Incidents/IncidentWorker_Suicide.cs b/Source/Core/Incidents/IncidentWorker_Suicide.cs new file mode 100644 index 00000000..e39b3771 --- /dev/null +++ b/Source/Core/Incidents/IncidentWorker_Suicide.cs @@ -0,0 +1,71 @@ +using System; +using UnityEngine; +using Verse; +using RimWorld; +using Verse.AI.Group; +using System.Collections.Generic; +using PrisonLabor.Core.Needs; +using PrisonLabor.Core.Other; + +namespace PrisonLabor.Core.Incidents +{ + public class IncidentWorker_Suicide : IncidentWorker + { + protected override bool CanFireNowSub(IncidentParms parms) + { + Map map = parms.target as Map; + + foreach (var pawn in map.mapPawns.PrisonersOfColony) + { + if (pawn.IsColonist) + continue; + + var need = pawn.needs.TryGetNeed(); + if (need == null) + continue; + + if (need.CurCategory >= TreatmentCategory.Bad) + return true; + } + + return false; + } + + protected override bool TryExecuteWorker(IncidentParms parms) + { + Map map = (Map)parms.target; + var affectedPawns = new List(map.mapPawns.PrisonersOfColony); + foreach (var pawn in map.mapPawns.PrisonersOfColony) + { + if (pawn.IsColonist) + continue; + + var need = pawn.needs.TryGetNeed(); + if (need == null) + continue; + + if (need.CurCategory >= TreatmentCategory.Bad) + { + // If treatment is only bad reduce chance by 50% + if (need.CurCategory == TreatmentCategory.Bad && !parms.forced) + { + if (UnityEngine.Random.value < 0.5f) + continue; + } + + SendStandardLetter(new TargetInfo(pawn.Position, pawn.Map), null, new string[] { pawn.Name.ToStringShort }); + parms.faction = pawn.Faction; + + DamageInfo dinfo = new DamageInfo(DamageDefOf.Cut, 29, 0, 0, pawn, pawn.RaceProps.body.AllParts.Find(p => p.def == BodyPartDefOf.Neck)); + while (!pawn.Dead) + pawn.TakeDamage(dinfo); + + Tutorials.Treatment(); + + return true; + } + } + return false; + } + } +} \ No newline at end of file diff --git a/Source/Area_Labor.cs b/Source/Core/LaborArea/Area_Labor.cs similarity index 89% rename from Source/Area_Labor.cs rename to Source/Core/LaborArea/Area_Labor.cs index 44599812..87744b49 100644 --- a/Source/Area_Labor.cs +++ b/Source/Core/LaborArea/Area_Labor.cs @@ -1,7 +1,7 @@ -using UnityEngine; +using UnityEngine; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core.LaborArea { internal class Area_Labor : Area { diff --git a/Source/Designator_AreaLabor.cs b/Source/Core/LaborArea/Designator_AreaLabor.cs similarity index 96% rename from Source/Designator_AreaLabor.cs rename to Source/Core/LaborArea/Designator_AreaLabor.cs index c30bcd99..608c46b8 100644 --- a/Source/Designator_AreaLabor.cs +++ b/Source/Core/LaborArea/Designator_AreaLabor.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using PrisonLabor.Core.Other; using RimWorld; using UnityEngine; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core.LaborArea { [StaticConstructorOnStartup] public abstract class Designator_AreaLabor : Designator_Area diff --git a/Source/Designator_AreaLaborClear.cs b/Source/Core/LaborArea/Designator_AreaLaborClear.cs similarity index 92% rename from Source/Designator_AreaLaborClear.cs rename to Source/Core/LaborArea/Designator_AreaLaborClear.cs index b068b003..fd1b1d45 100644 --- a/Source/Designator_AreaLaborClear.cs +++ b/Source/Core/LaborArea/Designator_AreaLaborClear.cs @@ -1,8 +1,8 @@ -using RimWorld; +using RimWorld; using UnityEngine; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core.LaborArea { internal class Designator_AreaLaborClear : Designator_AreaLabor { diff --git a/Source/Designator_AreaLaborExpand.cs b/Source/Core/LaborArea/Designator_AreaLaborExpand.cs similarity index 92% rename from Source/Designator_AreaLaborExpand.cs rename to Source/Core/LaborArea/Designator_AreaLaborExpand.cs index bdd0029a..cc434fb8 100644 --- a/Source/Designator_AreaLaborExpand.cs +++ b/Source/Core/LaborArea/Designator_AreaLaborExpand.cs @@ -1,8 +1,8 @@ -using RimWorld; +using RimWorld; using UnityEngine; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core.LaborArea { internal class Designator_AreaLaborExpand : Designator_AreaLabor { diff --git a/Source/WorkSettings.cs b/Source/Core/LaborWorkSettings/WorkSettings.cs similarity index 95% rename from Source/WorkSettings.cs rename to Source/Core/LaborWorkSettings/WorkSettings.cs index 6a3f65c6..711a8c36 100644 --- a/Source/WorkSettings.cs +++ b/Source/Core/LaborWorkSettings/WorkSettings.cs @@ -1,11 +1,13 @@ -using RimWorld; +using PrisonLabor.Constants; +using PrisonLabor.Core.Meta; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; using System.Text; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core.LaborWorkSettings { public static class WorkSettings { @@ -24,7 +26,7 @@ public static List AvailableWorkTypes _availableWorkTypes.Add(worktype); _availableWorkTypes.Remove(DefDatabase.GetNamed("Warden")); - _availableWorkTypes.Remove(PrisonLaborDefOf.PrisonLabor_Jailor); + _availableWorkTypes.Remove(PL_DefOf.PrisonLabor_Jailor); } return _availableWorkTypes; } diff --git a/Source/Prefs.cs b/Source/Core/Meta/Prefs.cs similarity index 91% rename from Source/Prefs.cs rename to Source/Core/Meta/Prefs.cs index 0e6a08b6..5eddf52f 100644 --- a/Source/Prefs.cs +++ b/Source/Core/Meta/Prefs.cs @@ -1,9 +1,12 @@ -using System; +using PrisonLabor.Core.LaborWorkSettings; +using PrisonLabor.Core.Needs; +using PrisonLabor.Core.Other; +using System; using System.IO; using System.Xml.Linq; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core.Meta { public static class PrisonLaborPrefs { @@ -105,12 +108,15 @@ public static bool EnableRevolts } } - public static bool DisableMod + public static bool ShowTreatmentHappiness { - get { return data.disable_mod; } + get + { + return data.show_treatment_happiness; + } set { - data.disable_mod = value; + data.show_treatment_happiness = value; Apply(); } } @@ -145,7 +151,7 @@ public static void Init() public static void Save() { - Tutorials.UpdateTutorialFlags(); + Other.Tutorials.UpdateTutorialFlags(); try { var xDocument = new XDocument(); @@ -165,6 +171,7 @@ public static void Apply() data.Apply(); WorkSettings.DataString = AllowedWorkTypes; Tutorials.Apply(); + Need_Treatment.ShowOnList = ShowTreatmentHappiness; } public static void RestoreToDefault() diff --git a/Source/PrefsData.cs b/Source/Core/Meta/PrefsData.cs similarity index 56% rename from Source/PrefsData.cs rename to Source/Core/Meta/PrefsData.cs index 9507fcf0..1dbe41f8 100644 --- a/Source/PrefsData.cs +++ b/Source/Core/Meta/PrefsData.cs @@ -1,6 +1,6 @@ -using System; +using System; -namespace PrisonLabor +namespace PrisonLabor.Core.Meta { public class PrisonLaborPrefsData { @@ -12,8 +12,7 @@ public class PrisonLaborPrefsData public bool enable_motivation_mechanics = true; public bool enable_motivation_icons = true; public bool enable_revolts = true; - - public bool disable_mod = false; + public bool show_treatment_happiness = false; public Version last_version = Version.v0_0; public bool show_news = true; @@ -27,48 +26,6 @@ public void Apply() } } - public enum Version - { - v0_0, - v0_1, - v0_2, - v0_3, - v0_4, - v0_5, - v0_6, - v0_7, - v0_7_dev1, - v0_7_dev2, - v0_7_dev3, - v0_7_dev4, - v0_7_dev5, - v0_8_0, - v0_8_1, - v0_8_2, - v0_8_3, - v0_8_4, - v0_8_5, - v0_8_6, - v0_8_7, - v0_8_8, - v0_8_9, - v0_8_9_1, - v0_8_9_2, - v0_8_9_4, - v0_8_9_5, - v0_9_0, - v0_9_1, - v0_9_2, - v0_9_3, - v0_9_4, - v0_9_5, - v0_9_6, - v0_9_8, - v0_9_9, - v0_9_10, - v0_9_11 - } - [Flags] public enum TutorialFlag { @@ -79,5 +36,6 @@ public enum TutorialFlag Managment = 0x8, Timetable = 0x10, LaborAreaWarning = 0x20, + Treatment = 0x40, } } \ No newline at end of file diff --git a/Source/Core/Meta/Version.cs b/Source/Core/Meta/Version.cs new file mode 100644 index 00000000..5856c083 --- /dev/null +++ b/Source/Core/Meta/Version.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PrisonLabor.Core.Meta +{ + public enum Version + { + v0_0, + v0_1, + v0_2, + v0_3, + v0_4, + v0_5, + v0_6, + v0_7, + v0_7_dev1, + v0_7_dev2, + v0_7_dev3, + v0_7_dev4, + v0_7_dev5, + v0_8_0, + v0_8_1, + v0_8_2, + v0_8_3, + v0_8_4, + v0_8_5, + v0_8_6, + v0_8_7, + v0_8_8, + v0_8_9, + v0_8_9_1, + v0_8_9_2, + v0_8_9_4, + v0_8_9_5, + v0_9_0, + v0_9_1, + v0_9_2, + v0_9_3, + v0_9_4, + v0_9_5, + v0_9_6, + v0_9_8, + v0_9_9, + v0_9_10, + v0_9_11, + v0_10_0, + v0_10_1, + } +} diff --git a/Source/Core/Meta/VersionUtility.cs b/Source/Core/Meta/VersionUtility.cs new file mode 100644 index 00000000..374fa1c3 --- /dev/null +++ b/Source/Core/Meta/VersionUtility.cs @@ -0,0 +1,55 @@ +using PrisonLabor.Core.Windows; + +namespace PrisonLabor.Core.Meta +{ + class VersionUtility + { + public const Version versionNumber = Version.v0_10_1; + public const string versionString = "0.10.1"; + + public static Version VersionOfSaveFile { get; set; } + + public static void CheckVersion() + { + // Update actual version + if (PrisonLaborPrefs.Version == Version.v0_0) + { + PrisonLaborPrefs.Version = versionNumber; + PrisonLaborPrefs.LastVersion = versionNumber; + } + else if (PrisonLaborPrefs.Version != versionNumber) + { + PrisonLaborPrefs.Version = versionNumber; + } + + // Client has new version + if (PrisonLaborPrefs.LastVersion < versionNumber) + { + // Show version news + NewsWindow.LastVersionString = GetVersionString(PrisonLaborPrefs.LastVersion); + NewsWindow.AutoShow = true; + + // Dev version fix, it can be removed in future + // There is no changelog for 0.10 so it will skip it, and display all changes + if (PrisonLaborPrefs.LastVersion == Version.v0_10_0) + { + NewsWindow.LastVersionString = GetVersionString(Version.v0_9_11); + } + + // Pre 0.9.4 + if (PrisonLaborPrefs.LastVersion < Version.v0_9_4) + { + CompatibilityPatches.OlderVersions.Pre_v0_9_4(); + } + } + + PrisonLaborPrefs.Version = versionNumber; + PrisonLaborPrefs.Save(); + } + + public static string GetVersionString(Version version) + { + return version.ToString().Replace("_", ".").Substring(1); + } + } +} diff --git a/Source/Core/Needs/Need_Motivation.cs b/Source/Core/Needs/Need_Motivation.cs new file mode 100644 index 00000000..4eb2da41 --- /dev/null +++ b/Source/Core/Needs/Need_Motivation.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using System.Text; +using PrisonLabor.Constants; +using PrisonLabor.Core.Trackers; +using PrisonLabor.HarmonyPatches; +using RimWorld; +using UnityEngine; +using Verse; + +namespace PrisonLabor.Core.Needs +{ + public class Need_Motivation : Need + { + private const float LazyLevel = 0.2f; + private const float NeedInspirationLevel = 0.5f; + + public float PercentageThreshNeedInsipration => NeedInspirationLevel; + + public float PercentageThreshLazy => LazyLevel; + //TODO change to lazy category? + public HungerCategory CurCategory => 0; + + public static NeedDef Def => PL_DefOf.PrisonLabor_Motivation; + + public bool Motivated => _GUIChangeArrow > 0; + + private int _GUIChangeArrow; + public override int GUIChangeArrow => _GUIChangeArrow; + + /// + /// Indicates whenever pawn should be motivated. + /// This property purpose is that pawn should be only motivated in semi-auto mode, + /// which means after getting to full, it should wait to drop a bit before recharging again. + /// + public bool ShouldToBeMotivated { get; private set; } + + /// + /// Indicates whenever pawn is currently working, and his motivation is decreasing by laziness rate. + /// + public bool IsPrisonerWorking { get; set; } + + /// + /// Indicates whenever pawn is lazy and stopped working by lack of motivation. + /// + public bool IsLazy { get; private set; } + + public Need_Motivation(Pawn pawn) : base(pawn) { } + + public override void SetInitialLevel() + { + CurLevelPercentage = 1.0f; + IsPrisonerWorking = false; + } + + public override void NeedInterval() + { + // Update value of need + CurLevel += GetChangePoints(); + + // Set NeedToBeMotivated + if (CurLevel == MaxLevel) + ShouldToBeMotivated = false; + else if (CurLevel <= NeedInspirationLevel) + ShouldToBeMotivated = true; + + // Set IsLazy + if (CurLevel <= LazyLevel && _GUIChangeArrow <= 0) + IsLazy = true; + else + IsLazy = false; + } + + private float GetChangePoints() + { + if (pawn.IsPrisoner && pawn.IsPrisonerOfColony) + { + if (pawn.GetRoomGroup() != null) + { + var value = InspirationTracker.GetInsiprationValue(pawn, true); + + if (PrisonLaborUtility.LaborEnabled(pawn)) + { + if (IsPrisonerWorking) + { + value -= BGP.Laziness_LazyRate; + if (HealthAIUtility.ShouldSeekMedicalRest(pawn)) + value -= BGP.Laziness_HealthRate; + value -= (int)pawn.needs.food.CurCategory * BGP.Laziness_HungryRate; + value -= (int)pawn.needs.rest.CurCategory * BGP.Laziness_TiredRate; + } + else if (pawn.timetable != null && pawn.timetable.CurrentAssignment == TimeAssignmentDefOf.Joy) + { + value += BGP.Laziness_JoyRate; + } + } + + _GUIChangeArrow = value.CompareTo(0.0f); + return value; + } + else + { + _GUIChangeArrow = 0; + return 0.0f; + } + } + else + { + _GUIChangeArrow = 1; + return +0.01f; + } + } + + public override string GetTipString() + { + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine(base.GetTipString()); + stringBuilder.AppendLine(); + stringBuilder.Append("PrisonLabor_WardenResponseThreshold".Translate()); + stringBuilder.AppendLine($": {PercentageThreshNeedInsipration.ToStringPercent()}"); + stringBuilder.Append("PrisonLabor_StoppingWorkThreshold".Translate()); + stringBuilder.AppendLine($": {PercentageThreshLazy.ToStringPercent()}"); + return stringBuilder.ToString(); + } + + public override void DrawOnGUI(Rect rect, int maxThresholdMarkers = 2147483647, float customMargin = -1f, bool drawArrows = true, bool doTooltip = true) + { + if (threshPercents == null) + threshPercents = new List(); + threshPercents.Clear(); + threshPercents.Add(PercentageThreshLazy); + threshPercents.Add(PercentageThreshNeedInsipration); + base.DrawOnGUI(rect, maxThresholdMarkers, customMargin, drawArrows, doTooltip); + } + + public override void ExposeData() + { + base.ExposeData(); + } + } +} \ No newline at end of file diff --git a/Source/Core/Needs/Need_Treatment.cs b/Source/Core/Needs/Need_Treatment.cs new file mode 100644 index 00000000..b036eb8d --- /dev/null +++ b/Source/Core/Needs/Need_Treatment.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using System.Text; +using PrisonLabor.Constants; +using RimWorld; +using UnityEngine; +using Verse; + +namespace PrisonLabor.Core.Needs +{ + /// + /// VeryGood: 4 + /// Good: 3 + /// Normal: 2 + /// Bad: 1 + /// VeryBad: 0 + /// + public enum TreatmentCategory : byte + { + VeryGood = 4, + Good = 3, + Normal = 2, + Bad = 1, + VeryBad = 0, + } + + public class Need_Treatment : Need + { + private bool _resocializationReady = false; + + public float PercentageThreshVeryGood => 0.85f; + public float PercentageThreshGood => 0.65f; + public float PercentageThreshNormal => 0.35f; + public float PercentageThreshBad => 0.15f; + + public override int GUIChangeArrow => 0; + + public static NeedDef Def => PL_DefOf.PrisonLabor_Treatment; + + public bool ResocializationReady + { + get => _resocializationReady; + set { _resocializationReady = value; } + } + + public TreatmentCategory CurCategory + { + get + { + if (CurLevelPercentage < PercentageThreshBad) + return TreatmentCategory.VeryBad; + else if (CurLevelPercentage < PercentageThreshNormal) + return TreatmentCategory.Bad; + else if (CurLevelPercentage < PercentageThreshGood) + return TreatmentCategory.Normal; + else if (CurLevelPercentage < PercentageThreshVeryGood) + return TreatmentCategory.Good; + else + return TreatmentCategory.VeryGood; + } + } + + public static bool ShowOnList + { + get + { + return PL_DefOf.PrisonLabor_Treatment.showOnNeedList; + } + set + { + PL_DefOf.PrisonLabor_Treatment.showOnNeedList = value; + } + } + + public Need_Treatment(Pawn pawn) : base(pawn) { } + + public override void SetInitialLevel() + { + CurLevelPercentage = 0.5f; + } + + public override void NeedInterval() + { + // Joy + if (pawn.timetable != null && pawn.timetable.CurrentAssignment == TimeAssignmentDefOf.Joy) + CurLevel += BGP.JoyRate; + + // Status + var hunger = pawn.needs.TryGetNeed(); + + int statusScore = 0; + if (hunger.CurCategory < HungerCategory.UrgentlyHungry) + statusScore += 5; + if (hunger.CurCategory < HungerCategory.Hungry) + statusScore += 5; + statusScore -= (int)pawn.health.hediffSet.PainTotal / 10; + if (pawn.health.HasHediffsNeedingTend()) + statusScore -= 7; + + CurLevel += statusScore * BGP.StatusMultiplier; + + + // Labor + var motivation = pawn.needs.TryGetNeed(); + if (motivation != null && motivation.IsPrisonerWorking) + CurLevel += BGP.LaborRate; + } + + public override string GetTipString() + { + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine(base.GetTipString()); + return stringBuilder.ToString(); + } + + public void NotifyPrisonerBeaten(DamageInfo dinfo, bool absorbed) + { + CurLevel += BGP.BeatenHit; + } + + public override void DrawOnGUI(Rect rect, int maxThresholdMarkers = 2147483647, float customMargin = -1f, + bool drawArrows = true, bool doTooltip = true) + { + if (threshPercents == null) + { + threshPercents = new List(); + threshPercents.Add(PercentageThreshBad); + threshPercents.Add(PercentageThreshNormal); + threshPercents.Add(PercentageThreshGood); + threshPercents.Add(PercentageThreshVeryGood); + } + base.DrawOnGUI(rect, maxThresholdMarkers, customMargin, drawArrows, doTooltip); + } + + public override void ExposeData() + { + base.ExposeData(); + Scribe_Values.Look(ref _resocializationReady, "PrisonLabor_resocialization_ready", false, false); + } + } +} \ No newline at end of file diff --git a/Source/Core/Other/ArrestUtility.cs b/Source/Core/Other/ArrestUtility.cs new file mode 100644 index 00000000..eabd184d --- /dev/null +++ b/Source/Core/Other/ArrestUtility.cs @@ -0,0 +1,88 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using Verse; +using Verse.AI; + +namespace PrisonLabor.Core.Other +{ + public static class ArrestUtility + { + public static void AddArrestOrder(Vector3 clickPos, Pawn pawn, List opts) + { + //TODO this is some copy-pasted example, refactor it so it should add arrest option + IntVec3 c = IntVec3.FromVector3(clickPos); + if (pawn.health.capacities.CapableOf(PawnCapacityDefOf.Manipulation)) + { + foreach (LocalTargetInfo current in GenUI.TargetsAt(clickPos, ForArrest(pawn), true)) + { + LocalTargetInfo dest = current; + bool flag = dest.HasThing && dest.Thing is Pawn && ((Pawn)dest.Thing).IsWildMan(); + if (!pawn.Drafted || flag) + { + if (!pawn.CanReach(dest, PathEndMode.OnCell, Danger.Deadly, false, TraverseMode.ByPawn)) + { + opts.Add(new FloatMenuOption("CannotArrest".Translate() + " (" + "NoPath".Translate() + ")", null, MenuOptionPriority.Default, null, null, 0f, null, null)); + } + else + { + Pawn pTarg = (Pawn)dest.Thing; + Action action = delegate + { + Building_Bed building_Bed = RestUtility.FindBedFor(pTarg, pawn, true, false, false); + if (building_Bed == null) + { + building_Bed = RestUtility.FindBedFor(pTarg, pawn, true, false, true); + } + if (building_Bed == null) + { + Messages.Message("CannotArrest".Translate() + ": " + "NoPrisonerBed".Translate(), pTarg, MessageTypeDefOf.RejectInput, false); + return; + } + Job job = new Job(JobDefOf.Arrest, pTarg, building_Bed); + job.count = 1; + pawn.jobs.TryTakeOrderedJob(job, JobTag.Misc); + if (pTarg.Faction != null && pTarg.Faction != Faction.OfPlayer && !pTarg.Faction.def.hidden) + { + TutorUtility.DoModalDialogIfNotKnown(ConceptDefOf.ArrestingCreatesEnemies); + } + }; + string label = "TryToArrest".Translate(dest.Thing.LabelCap, dest.Thing); + Action action2 = action; + MenuOptionPriority priority = MenuOptionPriority.High; + Thing thing = dest.Thing; + opts.Add(FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption(label, action2, priority, null, thing, 0f, null, null), pawn, pTarg, "ReservedBy")); + } + } + } + } + } + + public static TargetingParameters ForArrest(Pawn arrester) + { + return new TargetingParameters + { + canTargetPawns = true, + canTargetBuildings = false, + mapObjectTargetsMustBeAutoAttackable = false, + validator = delegate (TargetInfo targ) + { + if (!targ.HasThing) + { + return false; + } + Pawn pawn = targ.Thing as Pawn; + return pawn != null && pawn != arrester && CanBeArrestedBy(pawn, arrester) && !pawn.Downed; + } + }; + } + + public static bool CanBeArrestedBy(Pawn pawn, Pawn arrester) + { + return pawn.RaceProps.Humanlike && pawn.HostileTo(arrester.Faction) && pawn.CurJob.def == JobDefOf.Flee && (!pawn.IsPrisonerOfColony || !pawn.Position.IsInPrisonCell(pawn.Map)); + } + } +} diff --git a/Source/Core/Other/NewsProvider.cs b/Source/Core/Other/NewsProvider.cs new file mode 100644 index 00000000..babe8298 --- /dev/null +++ b/Source/Core/Other/NewsProvider.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml; +using Verse; + +namespace PrisonLabor.Core.Other +{ + public static class NewsProvider + { + public struct VersionNotes + { + public string version; + public string[] entries; + } + + public static VersionNotes[] allVersionNotes; + + static NewsProvider() + { + try + { + allVersionNotes = GetVersionNotesFromChangelog(Properties.Resources.changelog).ToArray(); + + int iterator = 0; + foreach (var notes in GetVersionNotesFromNewsFeed(Properties.Resources.NewsFeed)) + { + while (allVersionNotes[iterator].version != notes.version && iterator < allVersionNotes.Length) + iterator++; + + if (iterator < allVersionNotes.Length) + { + allVersionNotes[iterator] = notes; + } + else + { + Log.Warning($"PrisonLabor: couldn't find version {notes.version} to alter"); + iterator = 0; + } + } + } + catch (Exception e) + { + Log.Error("PrisonLabor: Failed to initialize news" + e.ToString()); + } + } + + public static IEnumerable NewsAfterVersion(string versionString) + { + bool stop = false; + for (int i = 0; i < allVersionNotes.Length && !stop; i++) + { + if (allVersionNotes[i].version == versionString) + stop = true; + else + yield return allVersionNotes[i]; + } + } + + private static IEnumerable GetVersionNotesFromChangelog(string text) + { + var lines = Regex.Split(text, "\r\n|\r|\n"); + + if (lines.Length < 2) + yield break; + + string currentPatch = lines[0]; + List currentPatchNotes = new List(); + + for (int i = 1; i < lines.Length; i++) + { + string line = lines[i]; + + var match = Regex.Match(line, "^\\*|-|\t"); + + if (match.Success) + { + currentPatchNotes.Add("[-]" + line.Replace(match.Value, "").Trim()); + } + else + { + if (currentPatchNotes.Count > 0) + { + currentPatchNotes.Insert(0, $"[title]Prison Labor v{currentPatch}"); + yield return new VersionNotes() { version = currentPatch, entries = currentPatchNotes.ToArray() }; + } + currentPatch = line; + currentPatchNotes = new List(); + } + } + + // Last iteration + if (currentPatchNotes.Count > 0) + { + currentPatchNotes.Insert(0, $"[title]Prison Labor v{currentPatch}"); + yield return new VersionNotes() { version = currentPatch, entries = currentPatchNotes.ToArray() }; + } + } + + private static IEnumerable GetVersionNotesFromNewsFeed(string xml) + { + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(xml); + foreach (XmlNode patch in xmlDocument.DocumentElement["patches"].ChildNodes) + { + if (patch.Name == "patch") + { + var entries = new List(); + entries.Add($"[title]{patch["title"].InnerXml}"); + foreach (XmlNode item in patch["items"].ChildNodes) + entries.Add(item.InnerXml); + yield return new VersionNotes() + { + version = patch.Attributes["version"].Value, + entries = entries.ToArray(), + }; + } + } + } + } +} diff --git a/Source/PrisonerFoodReservation.cs b/Source/Core/Other/PrisonerFoodReservation.cs similarity index 72% rename from Source/PrisonerFoodReservation.cs rename to Source/Core/Other/PrisonerFoodReservation.cs index 1eecf2b4..3af288c1 100644 --- a/Source/PrisonerFoodReservation.cs +++ b/Source/Core/Other/PrisonerFoodReservation.cs @@ -1,18 +1,18 @@ -using System.Collections.Generic; +using System.Collections.Generic; using RimWorld; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core.Other { internal class PrisonerFoodReservation { private static readonly Dictionary reservation = new Dictionary(); - public static bool isReserved(Thing t) + public static bool IsReserved(Thing t) { Pawn p; reservation.TryGetValue(t, out p); - if (p != null && p.GetRoom() == t.GetRoom() && p.needs.food.CurCategory != HungerCategory.Fed) + if (p != null && t!= null && p.GetRoom() == t.GetRoom() && p.needs != null && p.needs.food != null && p.needs.food.CurCategory != HungerCategory.Fed) return true; return false; } @@ -32,7 +32,7 @@ private static void clearEatenFood() { var removeList = new List(); foreach (var t in reservation.Keys) - if (t == null || t.GetRoom() == null || !isReserved(t)) + if (t == null || t.GetRoom() == null || !IsReserved(t)) removeList.Add(t); foreach (var t in removeList) reservation.Remove(t); diff --git a/Source/Core/Other/TreatmentUtility.cs b/Source/Core/Other/TreatmentUtility.cs new file mode 100644 index 00000000..18e491d8 --- /dev/null +++ b/Source/Core/Other/TreatmentUtility.cs @@ -0,0 +1,32 @@ +using PrisonLabor.Core.Needs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Verse; + +namespace PrisonLabor.Core.Other +{ + public static class TreatmentUtility + { + public static void OnApplyDamage(Pawn_HealthTracker instance, DamageInfo dinfo, bool absorbed) + { + if (dinfo.Instigator != null && !(dinfo.Instigator is Pawn)) + return; + + Pawn attacker = dinfo.Instigator as Pawn; + Pawn victim = (Pawn)(typeof(Pawn_HealthTracker).GetField("pawn", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(instance)); + + if (attacker == null || victim == null) + return; + + if (victim.IsPrisonerOfColony && attacker.IsColonist) + { + var need = victim.needs.TryGetNeed(); + if (need != null) + need.NotifyPrisonerBeaten(dinfo, absorbed); + } + } + } +} diff --git a/Source/Core/Other/TutorialProvider.cs b/Source/Core/Other/TutorialProvider.cs new file mode 100644 index 00000000..0dc4de74 --- /dev/null +++ b/Source/Core/Other/TutorialProvider.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using Verse; + +namespace PrisonLabor.Core.Other +{ + public static class TutorialProvider + { + public struct TutorialNode + { + public string[] entries; + } + + public static Dictionary TutorialNodes = new Dictionary(); + + static TutorialProvider() + { + try + { + Log.Message("PrisonLabor: tutorials initialized"); + + GetVersionNotesFromTutorialFeed(Properties.Resources.TutorialFeed); + } + catch (Exception e) + { + Log.Error("PrisonLabor: Failed to initialize tutorials" + e.ToString()); + } + } + + private static void GetVersionNotesFromTutorialFeed(string xml) + { + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(xml); + foreach (XmlNode tutorial in xmlDocument.DocumentElement["tutorials"].ChildNodes) + { + if (tutorial.Name == "tutorial") + { + var entries = new List(); + foreach (XmlNode item in tutorial["items"].ChildNodes) + entries.Add(item.InnerXml); + + TutorialNodes.Add( + key: tutorial.Attributes["key"].Value, + value: new TutorialNode() { entries = entries.ToArray(), } + ); + } + } + } + } +} diff --git a/Source/Tutorials.cs b/Source/Core/Other/Tutorials.cs similarity index 71% rename from Source/Tutorials.cs rename to Source/Core/Other/Tutorials.cs index ad35d50c..f64197df 100644 --- a/Source/Tutorials.cs +++ b/Source/Core/Other/Tutorials.cs @@ -1,25 +1,25 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using PrisonLabor.Core.Meta; +using PrisonLabor.Core.Windows; using RimWorld; using UnityEngine; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core.Other { - public class Tutorials + public static class Tutorials { - private static readonly ConceptDef introductionDef = - DefDatabase.GetNamed("PrisonLabor_Indroduction", true); + private static readonly ConceptDef introductionDef = DefDatabase.GetNamed("PrisonLabor_Indroduction", true); - private static readonly ConceptDef motivationDef = - DefDatabase.GetNamed("PrisonLabor_Motivation", true); + private static readonly ConceptDef motivationDef = DefDatabase.GetNamed("PrisonLabor_Motivation", true); private static readonly ConceptDef growingDef = DefDatabase.GetNamed("PrisonLabor_Growing", true); - private static readonly ConceptDef managementDef = - DefDatabase.GetNamed("PrisonLabor_Management", true); + private static readonly ConceptDef managementDef = DefDatabase.GetNamed("PrisonLabor_Management", true); - private static readonly ConceptDef timetableDef = - DefDatabase.GetNamed("PrisonLabor_Timetable", true); + private static readonly ConceptDef timetableDef = DefDatabase.GetNamed("PrisonLabor_Timetable", true); + + private static readonly ConceptDef treatmentDef = DefDatabase.GetNamed("PrisonLabor_Treatment", true); private static readonly List triggeredTutorials = new List(); private static float lastTutorTime; @@ -36,11 +36,18 @@ public static void Apply() PlayerKnowledgeDatabase.SetKnowledge(managementDef, 1.0f); if (PrisonLaborPrefs.HasTutorialFlag(TutorialFlag.Timetable)) PlayerKnowledgeDatabase.SetKnowledge(timetableDef, 1.0f); + if (PrisonLaborPrefs.HasTutorialFlag(TutorialFlag.Treatment)) + PlayerKnowledgeDatabase.SetKnowledge(treatmentDef, 1.0f); } public static void Introduction() { - TryActivateTutorial(introductionDef, OpportunityType.Important); + //TryActivateTutorial(introductionDef, OpportunityType.Important); + if (!PrisonLaborPrefs.HasTutorialFlag(TutorialFlag.Introduction)) + { + TutorialWindow.Show("Introduction"); + PrisonLaborPrefs.AddTutorialFlag(TutorialFlag.Introduction); + } } public static void Motivation() @@ -63,6 +70,11 @@ public static void Growing() TryActivateTutorial(growingDef, OpportunityType.Important); } + public static void Treatment() + { + TryActivateTutorial(treatmentDef, OpportunityType.Important); + } + public static void LaborAreaWarning() { if (!PrisonLaborPrefs.HasTutorialFlag(TutorialFlag.LaborAreaWarning)) @@ -105,6 +117,8 @@ public static void UpdateTutorialFlags() PrisonLaborPrefs.AddTutorialFlag(TutorialFlag.Timetable); if (!PlayerKnowledgeDatabase.IsComplete(growingDef)) PrisonLaborPrefs.AddTutorialFlag(TutorialFlag.Growing); + if (!PlayerKnowledgeDatabase.IsComplete(treatmentDef)) + PrisonLaborPrefs.AddTutorialFlag(TutorialFlag.Treatment); } public static void Reset() @@ -114,6 +128,7 @@ public static void Reset() PlayerKnowledgeDatabase.SetKnowledge(managementDef, 0.0f); PlayerKnowledgeDatabase.SetKnowledge(timetableDef, 0.0f); PlayerKnowledgeDatabase.SetKnowledge(growingDef, 0.0f); + PlayerKnowledgeDatabase.SetKnowledge(treatmentDef, 0.0f); } } } \ No newline at end of file diff --git a/Source/Core/PrisonLaborUtility.cs b/Source/Core/PrisonLaborUtility.cs new file mode 100644 index 00000000..c31e0d30 --- /dev/null +++ b/Source/Core/PrisonLaborUtility.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using PrisonLabor.Constants; +using PrisonLabor.Core.LaborArea; +using PrisonLabor.Core.LaborWorkSettings; +using PrisonLabor.Core.Meta; +using RimWorld; +using Verse; + +namespace PrisonLabor.Core +{ + internal static class PrisonLaborUtility + { + public static bool LaborEnabled(this Pawn pawn) + { + if (pawn.IsPrisoner) + if (pawn.guest.interactionMode == PL_DefOf.PrisonLabor_workOption || pawn.guest.interactionMode == PL_DefOf.PrisonLabor_workAndRecruitOption) + return true; + + return false; + } + + public static bool RecruitInLaborEnabled(Pawn pawn) + { + if (pawn.guest.interactionMode == PL_DefOf.PrisonLabor_workAndRecruitOption && pawn.guest.ScheduledForInteraction && pawn.health.capacities.CapableOf(PawnCapacityDefOf.Talking)) + return true; + + return false; + } + + public static bool WorkTime(Pawn pawn) + { + if (pawn.needs == null || pawn.needs.food == null || pawn.needs.rest == null) + return false; + if (pawn.timetable == null) + return true; + if (pawn.timetable.CurrentAssignment == TimeAssignmentDefOf.Work) + return true; + if (pawn.timetable.CurrentAssignment == TimeAssignmentDefOf.Anything) + { + if (HealthAIUtility.ShouldSeekMedicalRest(pawn) || + pawn.health.hediffSet.HasTemperatureInjury(TemperatureInjuryStage.Serious) || + pawn.needs.food.CurCategory > HungerCategory.Hungry || + pawn.needs.rest.CurCategory != RestCategory.Rested) + return false; + else + return true; + } + return false; + } + + public static bool IsDisabledByLabor(IntVec3 pos, Pawn pawn, WorkTypeDef workType) + { + if (pos != null && pawn.Map.areaManager.Get() != null && + !WorkSettings.WorkDisabled(workType)) + return pawn.Map.areaManager.Get()[pos]; + return false; + } + } +} \ No newline at end of file diff --git a/Source/PrisonLaborWidgets.cs b/Source/Core/PrisonLaborWidgets.cs similarity index 97% rename from Source/PrisonLaborWidgets.cs rename to Source/Core/PrisonLaborWidgets.cs index f992ca5c..d84949b6 100644 --- a/Source/PrisonLaborWidgets.cs +++ b/Source/Core/PrisonLaborWidgets.cs @@ -1,11 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core { public static class PrisonLaborWidgets { diff --git a/Source/Core/Settings/CleanSaveDialog.cs b/Source/Core/Settings/CleanSaveDialog.cs new file mode 100644 index 00000000..97fc3e6c --- /dev/null +++ b/Source/Core/Settings/CleanSaveDialog.cs @@ -0,0 +1,60 @@ +using PrisonLabor.Core.GameSaves; +using PrisonLabor.Tweaks; +using UnityEngine; +using Verse; + +namespace PrisonLabor.Core.Settings +{ + public class CleanSaveDialog : Window + { + private string fileName; + + private bool saveBackuped = false; + + public CleanSaveDialog(string fileName) + { + absorbInputAroundWindow = true; + doCloseX = true; + + windowRect = new Rect(0f, 0f, 464f, 232f); + + this.fileName = fileName; + } + + public override void DoWindowContents(Rect inRect) + { + var innerRect = new Rect(inRect.x, inRect.y, inRect.width, inRect.height).ContractedBy(10f); + GUI.BeginGroup(innerRect); + + var messageRect = new Rect(0f, 0f, innerRect.width, innerRect.height - 90f); + var cancelButtonRect = new Rect((innerRect.width - 100f) / 2, innerRect.height - 40f, 100f, 40f); + var backupButtonRect = new Rect(innerRect.width - 110f, innerRect.height - 90f, 100f, 40f); + var proceedButtonRect = new Rect(innerRect.width - 110f, innerRect.height - 40f, 100f, 40f); + + string dialogMessage = $"{"PrisonLabor_UpgradeSaveDialogMessage".Translate()}\n{"PrisonLabor_BackupSaveDialogMessage".Translate()}"; + var listing = new Listing_Standard(GameFont.Medium); + listing.Begin(messageRect); + listing.Label(dialogMessage); + listing.End(); + //GUI.Label(messageRect, dialogMessage); + + Text.Font = GameFont.Small; + if (Widgets.ButtonText(cancelButtonRect, "CancelButton".Translate())) + { + Close(); + } + if (!saveBackuped && Widgets.ButtonText(backupButtonRect, "PrisonLabor_ButtonBackup".Translate(), true, false, Color.green, !saveBackuped)) + { + saveBackuped = true; + SaveCleaner.BackupSavegame(fileName); + } + if (Widgets.ButtonText(proceedButtonRect, "PrisonLabor_ButtonProceed".Translate(), true, false, Color.red)) + { + SaveCleaner.RemoveFromSave(fileName); + Close(); + } + + GUI.EndGroup(); + } + } +} diff --git a/Source/Core/Settings/SelectSaveForCleaningDialog.cs b/Source/Core/Settings/SelectSaveForCleaningDialog.cs new file mode 100644 index 00000000..3a7d59de --- /dev/null +++ b/Source/Core/Settings/SelectSaveForCleaningDialog.cs @@ -0,0 +1,61 @@ +using PrisonLabor.Tweaks; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; +using Verse; + +namespace PrisonLabor.Core.Settings +{ + public class SelectSaveForCleaningDialog : Window + { + private float maxH; + private Vector2 position; + private List saves; + + public SelectSaveForCleaningDialog() + { + absorbInputAroundWindow = true; + doCloseX = true; + doCloseButton = true; + + saves = new List(); + foreach (var file in GenFilePaths.AllSavedGameFiles) + saves.Add(Path.GetFileNameWithoutExtension(file.Name)); + } + + public override void DoWindowContents(Rect inRect) + { + var listRect = new Rect(inRect.x, inRect.y + 10f, inRect.width, inRect.height - 50f); + var contentRect = new Rect(0f, 0f, inRect.width - 20f, 24f * saves.Count()); + Widgets.BeginScrollView(listRect, ref this.position, contentRect, true); + var listing_Standard = new Listing_Standard(); + listing_Standard.Begin(contentRect); + + foreach (var save in saves) + { + var lineHeight = Text.LineHeight; + + var rect = listing_Standard.GetRect(30f); + + //if (!tooltip.NullOrEmpty()) + //{ + // if (Mouse.IsOver(rect)) + // Widgets.DrawHighlight(rect); + // TooltipHandler.TipRegion(rect, tooltip); + //} + + if (Widgets.ButtonText(rect, save)) + { + Find.WindowStack.Add(new CleanSaveDialog(save)); + } + listing_Standard.Gap(listing_Standard.verticalSpacing); + } + + maxH = listing_Standard.CurHeight; + + listing_Standard.End(); + Widgets.EndScrollView(); + } + } +} diff --git a/Source/SelectWorkTypesDialog.cs b/Source/Core/Settings/SelectWorkTypesDialog.cs similarity index 95% rename from Source/SelectWorkTypesDialog.cs rename to Source/Core/Settings/SelectWorkTypesDialog.cs index fc6134b9..f06d1e85 100644 --- a/Source/SelectWorkTypesDialog.cs +++ b/Source/Core/Settings/SelectWorkTypesDialog.cs @@ -1,13 +1,14 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using PrisonLabor.Core.LaborWorkSettings; using RimWorld; using UnityEngine; using Verse; using Verse.Sound; -namespace PrisonLabor +namespace PrisonLabor.Core.Settings { - internal class SelectWorkTypesDialog : Window + public class SelectWorkTypesDialog : Window { private float maxH; private Vector2 position; diff --git a/Source/SettingsMenu.cs b/Source/Core/Settings/SettingsMenu.cs similarity index 68% rename from Source/SettingsMenu.cs rename to Source/Core/Settings/SettingsMenu.cs index 7905932c..638477cf 100644 --- a/Source/SettingsMenu.cs +++ b/Source/Core/Settings/SettingsMenu.cs @@ -1,9 +1,12 @@ -using RimWorld; +using PrisonLabor.Core.LaborWorkSettings; +using PrisonLabor.Core.Meta; +using PrisonLabor.Core.Windows; +using RimWorld; using System.Collections.Generic; using UnityEngine; using Verse; -namespace PrisonLabor +namespace PrisonLabor.Core.Settings { [StaticConstructorOnStartup] internal class SettingsMenu : Mod @@ -14,8 +17,8 @@ internal class SettingsMenu : Mod private static bool enableMotivationMechanics; private static bool enableMotivationIcons; private static bool enableRevolts; + private static bool showTreatmentHappiness; private static bool advancedGrowing; - private static bool disableMod; private static int defaultInteractionMode; private static List interactionModeList; @@ -32,7 +35,7 @@ public static void Init() enableMotivationMechanics = PrisonLaborPrefs.EnableMotivationMechanics; enableMotivationIcons = PrisonLaborPrefs.EnableMotivationIcons; enableRevolts = PrisonLaborPrefs.EnableRevolts; - disableMod = PrisonLaborPrefs.DisableMod; + showTreatmentHappiness = PrisonLaborPrefs.ShowTreatmentHappiness; interactionModeList = new List(DefDatabase.AllDefs); defaultInteractionMode = interactionModeList.IndexOf(DefDatabase.GetNamed(PrisonLaborPrefs.DefaultInteractionMode)); @@ -43,7 +46,7 @@ public static void Init() public override void DoSettingsWindowContents(Rect inRect) { var leftRect = new Rect(inRect.x, inRect.y, inRect.width * 0.65f, inRect.height); - var rightRect = new Rect(inRect.x + inRect.width * 0.65f + 30f, inRect.y, inRect.width * 0.35f - 30f, + var rightRect = new Rect((int)(inRect.x + inRect.width * 0.65f + 30f), inRect.y, inRect.width * 0.35f - 30f, inRect.height); var listing_options = new Listing_Standard(); @@ -60,57 +63,49 @@ public override void DoSettingsWindowContents(Rect inRect) listing_options.GapLine(); - if (!disableMod) + listing_options.Label("PrisonLabor_AllowedWorkTypes".Translate(), -1f); + listing_options.CheckboxLabeled(" " + "PrisonLabor_AllowAll".Translate(), ref allowAllWorktypes, "PrisonLabor_AllowAllWorkTypes".Translate()); + if (!allowAllWorktypes) + { + if (listing_options.ButtonTextLabeled(" " + "PrisonLabor_AllowedWorkTypesL".Translate(), "PrisonLabor_Browse".Translate())) + Find.WindowStack.Add(new SelectWorkTypesDialog()); + } + else { - listing_options.Label("PrisonLabor_AllowedWorkTypes".Translate(), -1f); - listing_options.CheckboxLabeled(" " + "PrisonLabor_AllowAll".Translate(), ref allowAllWorktypes, "PrisonLabor_AllowAllWorkTypes".Translate()); - if (!allowAllWorktypes) - { - if (listing_options.ButtonTextLabeled(" " + "PrisonLabor_AllowedWorkTypesL".Translate(), "PrisonLabor_Browse".Translate())) - Find.WindowStack.Add(new SelectWorkTypesDialog()); - } - else - { - listing_options.Gap(); - } + listing_options.Gap(); + } - listing_options.GapLine(); + listing_options.GapLine(); - listing_options.CheckboxLabeled("PrisonLabor_MotivationMechanics".Translate(), ref enableMotivationMechanics, - "PrisonLabor_MotivationWarning".Translate()); + listing_options.CheckboxLabeled("PrisonLabor_MotivationMechanics".Translate(), ref enableMotivationMechanics, + "PrisonLabor_MotivationWarning".Translate()); - listing_options.GapLine(); + listing_options.GapLine(); - listing_options.CheckboxLabeled("PrisonLabor_MotivationIcons".Translate(), ref enableMotivationIcons, - "PrisonLabor_MotivationIconsDesc".Translate()); + listing_options.CheckboxLabeled("PrisonLabor_MotivationIcons".Translate(), ref enableMotivationIcons, + "PrisonLabor_MotivationIconsDesc".Translate()); - listing_options.GapLine(); + listing_options.GapLine(); - listing_options.CheckboxLabeled("PrisonLabor_CanGrowAdvanced".Translate(), ref advancedGrowing, - "PrisonLabor_CanGrowAdvancedDesc".Translate()); + listing_options.CheckboxLabeled("PrisonLabor_CanGrowAdvanced".Translate(), ref advancedGrowing, + "PrisonLabor_CanGrowAdvancedDesc".Translate()); - listing_options.GapLine(); + listing_options.GapLine(); - listing_options.CheckboxLabeled("PrisonLabor_EnableRevolts".Translate(), ref enableRevolts, - "PrisonLabor_EnableRevoltsDesc".Translate()); - } - else - { - listing_options.Gap(); - listing_options.Gap(); - listing_options.Label("PrisonLabor_RestartInfo".Translate(), -1f); - listing_options.Label("PrisonLabor_RestartInfo2".Translate(), -1f); - listing_options.Gap(); - listing_options.Gap(); - listing_options.Gap(); - } + listing_options.CheckboxLabeled("PrisonLabor_EnableRevolts".Translate(), ref enableRevolts, + "PrisonLabor_EnableRevoltsDesc".Translate()); + + listing_options.GapLine(); + + listing_options.CheckboxLabeled("PrisonLabor_ShowTreatmentHappiness".Translate(), ref showTreatmentHappiness, + "PrisonLabor_ShowTreatmentHappinessDesc".Translate()); listing_options.Gap(); listing_options.Gap(); listing_options.Gap(); - listing_options.CheckboxLabeled("PrisonLabor_DisableMod".Translate(), ref disableMod, - "PrisonLabor_DisableModDesc".Translate()); + if (listing_options.ButtonTextLabeled("PrisonLabor_ButtonRemoveModFromSaveDesc".Translate(), "PrisonLabor_ButtonRemoveModFromSave".Translate())) + Find.WindowStack.Add(new SelectSaveForCleaningDialog()); listing_options.End(); @@ -138,8 +133,13 @@ public override void DoSettingsWindowContents(Rect inRect) if (listing_panel.ButtonText("PrisonLabor_ShowNews".Translate())) { - NewsDialog.showAll = true; - NewsDialog.ForceShow(); + NewsWindow.ShowAll = true; + NewsWindow.ForceShow(); + } + + if (listing_panel.ButtonText("PrisonLabor_ReplayTurorialsButton".Translate())) + { + ReplayTutorialsWindow.Show(); } listing_panel.End(); @@ -156,12 +156,11 @@ public override void WriteSettings() { PrisonLaborPrefs.ShowNews = showNews; PrisonLaborPrefs.AllowAllWorkTypes = allowAllWorktypes; - if (!disableMod) - PrisonLaborPrefs.EnableMotivationMechanics = enableMotivationMechanics; + PrisonLaborPrefs.EnableMotivationMechanics = enableMotivationMechanics; PrisonLaborPrefs.EnableMotivationIcons = enableMotivationIcons; PrisonLaborPrefs.EnableRevolts = enableRevolts; + PrisonLaborPrefs.ShowTreatmentHappiness = showTreatmentHappiness; PrisonLaborPrefs.AdvancedGrowing = advancedGrowing; - PrisonLaborPrefs.DisableMod = disableMod; PrisonLaborPrefs.DefaultInteractionMode = interactionModeList[defaultInteractionMode].defName; PrisonLaborPrefs.Save(); Log.Message("Prison Labor settings saved"); diff --git a/Source/Core/Trackers/EscapeTracker.cs b/Source/Core/Trackers/EscapeTracker.cs new file mode 100644 index 00000000..3fbb460e --- /dev/null +++ b/Source/Core/Trackers/EscapeTracker.cs @@ -0,0 +1,140 @@ +using PrisonLabor.Constants; +using PrisonLabor.Core.BaseClasses; +using PrisonLabor.Core.Needs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Verse; + +namespace PrisonLabor.Core.Trackers +{ + public class EscapeTracker : IExposable + { + #region Object Access + /** + * Object Access region: + * This region is for ensuring that for every pawn there will be only one escape tracker. + * It is constructed in this way to prevent heavy modification of Pawn class (on external library). + */ + private static Dictionary escapeTimers = new Dictionary(); + + /// + /// Access EscapeTracker of Pawn + /// + public static EscapeTracker Of(Pawn pawn, bool forced = false) + { + if(!pawn.IsPrisoner && !forced) + { + return null; + } + if (!escapeTimers.ContainsKey(pawn)) + { + escapeTimers.Add(pawn, new EscapeTracker(pawn)); + } + return escapeTimers[pawn]; + } + + public static void Save(Pawn pawn, EscapeTracker tracker) + { + if(pawn != null && tracker != null) + { + escapeTimers[pawn] = tracker; + } + } + #endregion + + private SimpleTimer timer = new SimpleTimer(); + + /// + /// Debug information + /// + public int ReadyToRunPercentage => timer.Ticks * 100 / EscapeLevel; + + /// + /// Attached pawn + /// + public Pawn Pawn { get; private set; } + + /// + /// Flag that indicates whenever pawn should escape when JobGiver is trying to assign him a job + /// + public bool ReadyToEscape { get; private set; } + + /// + /// This value is tracking whenever pawn is ready to run, + /// in order to count time of this state + /// + public bool CanEscape + { + get => timer.IsActive; + + set + { + if (value == true) + timer.Start(); + else + timer.Stop(); + } + } + + /// + /// This value represent how long pawn will wait until he decides to escape. + /// Measured in game ticks. + /// + public int EscapeLevel + { + get + { + var treatment = Pawn.needs?.TryGetNeed(); + if (treatment == null || treatment.CurCategory <= TreatmentCategory.Bad) + return BGP.Escape_MinLevel; + + int level = (int)(treatment.CurLevel * BGP.Escape_LevelTreatmentMultiplier) + BGP.Escape_LevelBase; + + if (level < BGP.Escape_MinLevel) + return BGP.Escape_MinLevel; + else if (level > BGP.Escape_MaxLevel) + return BGP.Escape_MaxLevel; + else + return level; + } + } + + /// + /// Creates new escape tracker attached to pawn + /// + /// + public EscapeTracker(Pawn pawn) + { + Pawn = pawn; + } + + public void Tick() + { + // Reset all + if (Pawn.IsWatched()) + { + // Check if state isn't reseted already + if (timer.Ticks != 0) + { + timer.ResetAndStop(); + ReadyToEscape = false; + } + } + // Check if timer should trigger escape + else if (timer.Ticks >= EscapeLevel) + { + ReadyToEscape = true; + } + + // Tick timer + timer.Tick(); + } + + public void ExposeData() + { + Scribe_Deep.Look(ref timer, "Timer"); + } + } +} diff --git a/Source/Core/Trackers/InspirationTracker.cs b/Source/Core/Trackers/InspirationTracker.cs new file mode 100644 index 00000000..263fee22 --- /dev/null +++ b/Source/Core/Trackers/InspirationTracker.cs @@ -0,0 +1,99 @@ +using PrisonLabor.Constants; +using System; +using System.Collections.Generic; +using Verse; + +namespace PrisonLabor.Core.Trackers +{ + internal static class InspirationTracker + { + public static Dictionary> inspirationValues = new Dictionary>(); + + /// + /// Check if pawn is watched(supervised) by a Jailor + /// + public static bool IsWatched(this Pawn pawn) + { + Dictionary iValues; + float value; + + var map = pawn?.Map; + lock (inspirationValues) + { + if (map != null && inspirationValues.TryGetValue(map, out iValues)) + { + if (iValues.TryGetValue(pawn, out value)) + { + return value != 0; + } + } + } + return false; + } + + public static float GetInsiprationValue(Pawn pawn, bool refresh = false) + { + var map = pawn.Map; + if (!inspirationValues.ContainsKey(map) || !inspirationValues[map].ContainsKey(pawn)) + { + Log.Message("PrisonLabor Warning: InspirationTracker.GetInsipirationValue() didn't find map or pawn. Maybe it was called before calculating values."); + return 0; + } + lock (inspirationValues) + { + return inspirationValues[map][pawn]; + } + } + + public static void Calculate(Map map) + { + lock (inspirationValues) + { + var wardens = new List(); + wardens.AddRange(map.mapPawns.FreeColonists); + var prisoners = new List(); + prisoners.AddRange(map.mapPawns.PrisonersOfColony); + + Dictionary mapCalculations; + if (inspirationValues.TryGetValue(map, out mapCalculations)) + mapCalculations.Clear(); + else + inspirationValues[map] = mapCalculations = new Dictionary(); + + foreach (var prisoner in prisoners) + { + mapCalculations[prisoner] = 0f; + } + + var inRange = new Dictionary(); + foreach (var warden in wardens) + { + inRange.Clear(); + foreach (var prisoner in prisoners) + { + float distance = warden.Position.DistanceTo(prisoner.Position); + if (distance < BGP.InpirationRange && prisoner.GetRoom() == warden.GetRoom()) + inRange.Add(prisoner, distance); + } + + var watchedPawns = new List(inRange.Keys); + float points; + if (inRange.Count > BGP.WardenCapacity) + { + watchedPawns.Sort(new Comparison((x, y) => inRange[x].CompareTo(inRange[y]))); + points = BGP.InspireRate / BGP.WardenCapacity; + } + else + { + points = BGP.InspireRate / inRange.Count; + } + + for (int i = 0; i < watchedPawns.Count && i < BGP.WardenCapacity; i++) + { + mapCalculations[watchedPawns[i]] += points; + } + } + } + } + } +} \ No newline at end of file diff --git a/Source/Core/Windows/NewsWindow.cs b/Source/Core/Windows/NewsWindow.cs new file mode 100644 index 00000000..e863e2a9 --- /dev/null +++ b/Source/Core/Windows/NewsWindow.cs @@ -0,0 +1,79 @@ +using PrisonLabor.Core.Meta; +using PrisonLabor.Core.Other; +using System.Collections.Generic; +using UnityEngine; +using Verse; + +namespace PrisonLabor.Core.Windows +{ + public class NewsWindow : Window + { + // Static Properties + public static bool AutoShow { get; set; } + + public static bool ShowAll { get; set; } + + public static string LastVersionString { get; set; } + + // Fields + private string[] entries; + private Vector2 position; + + public NewsWindow() + { + doCloseButton = true; + doCloseX = true; + Init(); + } + + public void Init() + { + var entriesList = new List(); + + if (ShowAll) + { + foreach (var patch in NewsProvider.allVersionNotes) + { + entriesList.AddRange(patch.entries); + } + } + else + { + foreach (var patch in NewsProvider.NewsAfterVersion(LastVersionString)) + { + entriesList.AddRange(patch.entries); + } + } + + entries = entriesList.ToArray(); + } + + public static void TryShow() + { + if (AutoShow && PrisonLaborPrefs.ShowNews) + { + Find.WindowStack.Add(new NewsWindow()); + PrisonLaborPrefs.LastVersion = PrisonLaborPrefs.Version; + PrisonLaborPrefs.Save(); + AutoShow = false; + } + } + + public static void ForceShow() + { + Find.WindowStack.Add(new NewsWindow()); + PrisonLaborPrefs.LastVersion = PrisonLaborPrefs.Version; + PrisonLaborPrefs.Save(); + AutoShow = false; + } + + public override void DoWindowContents(Rect inRect) + { + var displayRect = new Rect(inRect.x, inRect.y, inRect.width, inRect.height - 50f); + + var richListing = new GUI_Components.RichListing(); + richListing.PreRender(displayRect, entries); + richListing.OnGui(ref position); + } + } +} \ No newline at end of file diff --git a/Source/Core/Windows/ReplayTutorialsWindow.cs b/Source/Core/Windows/ReplayTutorialsWindow.cs new file mode 100644 index 00000000..f9b39fab --- /dev/null +++ b/Source/Core/Windows/ReplayTutorialsWindow.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using Verse; + +namespace PrisonLabor.Core.Windows +{ + public class ReplayTutorialsWindow : Window + { + public ReplayTutorialsWindow() + { + doCloseButton = true; + } + + public override void DoWindowContents(Rect inRect) + { + var listing_panel = new Listing_Standard(); + + listing_panel.Begin(inRect); + + if (listing_panel.ButtonTextLabeled("Introduction", "PrisonLabor_ShowTutorialButton".Translate())) + { + TutorialWindow.Show("Introduction"); + } + + if (listing_panel.ButtonTextLabeled("Labor Area", "PrisonLabor_ShowTutorialButton".Translate())) + { + TutorialWindow.Show("LaborArea"); + } + + if (listing_panel.ButtonTextLabeled("Treatment system", "PrisonLabor_ShowTutorialButton".Translate())) + { + TutorialWindow.Show("Treatment"); + } + + listing_panel.End(); + } + + public static void Show() + { + Find.WindowStack.Add(new ReplayTutorialsWindow()); + } + } +} diff --git a/Source/Core/Windows/TutorialWindow.cs b/Source/Core/Windows/TutorialWindow.cs new file mode 100644 index 00000000..42ec6d11 --- /dev/null +++ b/Source/Core/Windows/TutorialWindow.cs @@ -0,0 +1,36 @@ +using PrisonLabor.Core.Other; +using UnityEngine; +using Verse; + +namespace PrisonLabor.Core.Windows +{ + public class TutorialWindow : Window + { + // Fields + private string[] entries; + private Vector2 position; + + public override Vector2 InitialSize => new Vector2(800, 700); + + public TutorialWindow(string tutorialKey) + { + entries = TutorialProvider.TutorialNodes[tutorialKey].entries; + + doCloseButton = true; + } + + public override void DoWindowContents(Rect inRect) + { + var displayRect = new Rect(inRect.x, inRect.y, inRect.width, inRect.height - 50f); + + var richListing = new GUI_Components.RichListing(); + richListing.PreRender(displayRect, entries); + richListing.OnGui(ref position); + } + + public static void Show(string key) + { + Find.WindowStack.Add(new TutorialWindow(key)); + } + } +} diff --git a/Source/HarmonyPatches/DevTools.cs b/Source/HarmonyPatches/DevTools.cs new file mode 100644 index 00000000..a69276f5 --- /dev/null +++ b/Source/HarmonyPatches/DevTools.cs @@ -0,0 +1,108 @@ +using Harmony; +using PrisonLabor.Core.Needs; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Verse; + +namespace PrisonLabor.HarmonyPatches +{ + /// + /// This patch is adding Prison Labor dev tools + /// + [HarmonyPatch(typeof(Dialog_DebugActionsMenu), "DoListingItems_MapTools")] + public static class DevTools + { + public static bool LogEscapeUtilityEnabled { get; set; } + + static void Postfix(Dialog_DebugActionsMenu __instance) + { + var menu = __instance; + menu.DoLabel("Prison Labor Tools:"); + + // Increase motivation + menu.DebugToolMapForPawns("Tool: Motivation +10%", delegate (Pawn p) + { + if (p.needs.TryGetNeed() != null) + { + OffsetNeed(p, Need_Motivation.Def, 0.1f); + } + }); + // Decrease motivation + menu.DebugToolMapForPawns("Tool: Motivation -10%", delegate (Pawn p) + { + if (p.needs.TryGetNeed() != null) + { + OffsetNeed(p, Need_Motivation.Def, -0.1f); + } + }); + // Increase treatment + menu.DebugToolMapForPawns("Tool: Treatment +10%", delegate (Pawn p) + { + if (p.needs.TryGetNeed() != null) + { + OffsetNeed(p, Need_Treatment.Def, 0.1f); + } + }); + // Decrease treatment + menu.DebugToolMapForPawns("Tool: Treatment -10%", delegate (Pawn p) + { + if (p.needs.TryGetNeed() != null) + { + OffsetNeed(p, Need_Treatment.Def, -0.1f); + } + }); + // Turn into prisoner + menu.DebugToolMapForPawns("Tool: Turn into prisoner", delegate (Pawn p) + { + p.guest.SetGuestStatus(Faction.OfPlayer, true); + }); + // Set free + menu.DebugToolMapForPawns("Tool: Set free", delegate (Pawn p) + { + p.guest.SetGuestStatus(null, false); + }); + // Set free + menu.DebugAction("Toggle logging escape utility", ()=> + { + LogEscapeUtilityEnabled = !LogEscapeUtilityEnabled; + }); + } + + #region Utilities + static void DoLabel(this Dialog_DebugActionsMenu instance, string label) + { + var method = typeof(Dialog_DebugActionsMenu).GetMethod("DoLabel", BindingFlags.Instance | BindingFlags.NonPublic); + method.Invoke(instance, new object[] { label }); + } + + static void DebugAction(this Dialog_DebugActionsMenu instance, string label, Action action) + { + var method = typeof(Dialog_DebugActionsMenu).GetMethod("DebugAction", BindingFlags.Instance | BindingFlags.NonPublic); + method.Invoke(instance, new object[] { label, action }); + } + + static void DebugToolMapForPawns(this Dialog_DebugActionsMenu instance, string label, Action pawnAction) + { + var method = typeof(Dialog_DebugActionsMenu).GetMethod("DebugToolMapForPawns", BindingFlags.Instance | BindingFlags.NonPublic); + method.Invoke(instance, new object[] { label, pawnAction }); + } + + static void OffsetNeed(Pawn pawn, NeedDef nd, float offsetPct) + { + if (pawn != null) + { + Need need = pawn.needs.TryGetNeed(nd); + if (need != null) + { + need.CurLevel += offsetPct * need.MaxLevel; + pawn.Drawer.Notify_DebugAffected(); + } + } + } + #endregion + } +} diff --git a/Source/HarmonyPatches/HPatcher.cs b/Source/HarmonyPatches/HPatcher.cs index ced36df2..dbf07106 100644 --- a/Source/HarmonyPatches/HPatcher.cs +++ b/Source/HarmonyPatches/HPatcher.cs @@ -1,7 +1,6 @@ using System; using System.Reflection; using Harmony; -using PrisonLabor.Harmony; using Verse; using System.Collections.Generic; using System.IO; @@ -28,15 +27,15 @@ public static void Init() harmony.PatchAll(Assembly.GetExecutingAssembly()); // Print out not completed methods - foreach(var f in fragments.Keys) + foreach (var f in fragments.Keys) { - if(!fragments[f]) + if (!fragments[f]) Log.Error($"PrisonLaborWarning: Harmony patch failed to find \"{f}\" fragment."); } } catch (Exception e) { - Log.Error($"PrisonLaborException: failed to proceed harmony patches: {e.Message}"); + Log.Error($"PrisonLaborException: failed to proceed harmony patches: {e.InnerException.Message}"); } // SECTION - Patches with references in method @@ -49,7 +48,7 @@ public static void Init() typeof(IntVec3), typeof(ThingPlaceMode), typeof(Thing).MakeByRefType(), typeof(Action) }), - new HarmonyMethod(null), new HarmonyMethod(typeof(ForibiddenDropPatch).GetMethod("Postfix"))); + new HarmonyMethod(null), new HarmonyMethod(typeof(Patches_PermissionFix.ForibiddenDropPatch).GetMethod("Postfix"))); harmony.Patch( typeof(Pawn_CarryTracker).GetMethod("TryDropCarriedThing", new[] @@ -57,11 +56,11 @@ public static void Init() typeof(IntVec3), typeof(int), typeof(ThingPlaceMode), typeof(Thing).MakeByRefType(), typeof(Action) }), - new HarmonyMethod(null), new HarmonyMethod(typeof(ForibiddenDropPatch).GetMethod("Postfix2"))); + new HarmonyMethod(null), new HarmonyMethod(typeof(Patches_PermissionFix.ForibiddenDropPatch).GetMethod("Postfix2"))); } catch (Exception e) { - Log.Error($"PrisonLaborException: failed to proceed harmony patches (reference section): {e.Message}"); + Log.Error($"PrisonLaborException: failed to proceed harmony patches (reference section): {e.InnerException.Message}"); } } @@ -106,7 +105,7 @@ public static void CreateDebugFileOnDesktop(string fileName, IEnumerable /// /// - public static bool IsFragment(OpCode[] opCodes, String[] operands, CodeInstruction instr, ref int step, string fragmentName) + public static bool IsFragment(OpCode[] opCodes, String[] operands, CodeInstruction instr, ref int step, string fragmentName, bool perfectMatch = true) { if (opCodes.Length != operands.Length) { @@ -121,7 +120,8 @@ public static bool IsFragment(OpCode[] opCodes, String[] operands, CodeInstructi var finalStep = opCodes.Length; - if (instr.opcode == opCodes[step] && (instr.operand == null || instr.operand.ToString() == operands[step])) + + if (InstructionMatching(instr, opCodes[step], operands[step], perfectMatch)) step++; else step = 0; @@ -143,7 +143,7 @@ public static bool IsFragment(OpCode[] opCodes, String[] operands, CodeInstructi /// /// /// - public static object FindOperandAfter(OpCode[] opCodes, String[] operands, IEnumerable instr) + public static object FindOperandAfter(OpCode[] opCodes, String[] operands, IEnumerable instr, bool perfectMatch = true) { if (opCodes.Length != operands.Length) { @@ -156,7 +156,7 @@ public static object FindOperandAfter(OpCode[] opCodes, String[] operands, IEnum int step = 0; foreach (var ci in instr) { - if (ci.opcode == opCodes[step] && (ci.operand == null || ci.operand.ToString() == operands[step])) + if (InstructionMatching(ci, opCodes[step], operands[step], perfectMatch)) step++; else step = 0; @@ -168,5 +168,16 @@ public static object FindOperandAfter(OpCode[] opCodes, String[] operands, IEnum Log.Error("PrisonLaborException: FindOperandAfter() didn't find any lines. Trace:" + new StackTrace()); return null; } + + private static bool InstructionMatching(CodeInstruction instr, OpCode opCode, string operand, bool perfectMatch) + { + bool matchingOpCodes = instr.opcode == opCode; + bool noOperands = instr.operand == null || string.IsNullOrEmpty(operand); + bool matchingOperands; + if (perfectMatch) matchingOperands = instr.operand != null && instr.operand.ToString() == operand; + else matchingOperands = instr.operand != null && instr.operand.ToString().Contains(operand); + + return matchingOpCodes && (noOperands || matchingOperands); + } } } diff --git a/Source/HarmonyPatches/Patch_RemoveHediffIfDisabled.cs b/Source/HarmonyPatches/Patch_RemoveHediffIfDisabled.cs deleted file mode 100644 index 404aa036..00000000 --- a/Source/HarmonyPatches/Patch_RemoveHediffIfDisabled.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Harmony; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Verse; - -namespace PrisonLabor.HarmonyPatches -{ - [HarmonyPatch(typeof(Pawn))] - [HarmonyPatch("ExposeData")] - class Patch_RemoveHediffIfDisabled - { - private static void Prefix(Pawn __instance) - { - if (PrisonLaborPrefs.DisableMod) - { - if (__instance.health != null && __instance.health.hediffSet != null) - { - var hediff = __instance.health.hediffSet.GetFirstHediffOfDef(DefDatabase.GetNamed("PrisonLabor_PrisonerChains"), false); - if (hediff != null) - __instance.health.RemoveHediff(hediff); - } - } - } - } -} diff --git a/Source/HarmonyPatches/Patch_RemoveLaborAreaIfDisabled.cs b/Source/HarmonyPatches/Patch_RemoveLaborAreaIfDisabled.cs deleted file mode 100644 index bbb72cf0..00000000 --- a/Source/HarmonyPatches/Patch_RemoveLaborAreaIfDisabled.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Harmony; -using Verse; - -namespace PrisonLabor.HarmonyPatches -{ - [HarmonyPatch(typeof(AreaManager))] - [HarmonyPatch("ExposeData")] - internal class Patch_RemoveLaborAreaIfDisabled - { - private static void Prefix(AreaManager __instance) - { - if (PrisonLaborPrefs.DisableMod) - { - __instance.AllAreas.RemoveAll(area => area is Area_Labor); - } - } - } -} diff --git a/Source/HarmonyPatches/Patch_ShowNews.cs b/Source/HarmonyPatches/Patch_ShowNews.cs deleted file mode 100644 index 685a4ac5..00000000 --- a/Source/HarmonyPatches/Patch_ShowNews.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using Harmony; -using Verse; - -namespace PrisonLabor.HarmonyPatches -{ - [HarmonyPatch(typeof(Map))] - [HarmonyPatch("FinalizeInit")] - [HarmonyPatch(new Type[] { })] - internal class Patch_ShowNews - { - private static void Postfix() - { - NewsDialog.TryShow(); - } - } -} \ No newline at end of file diff --git a/Source/HarmonyPatches/Patches_AssignBed/Patch_AssignPrisonersToBed.cs b/Source/HarmonyPatches/Patches_AssignBed/Patch_AssignPrisonersToBed.cs new file mode 100644 index 00000000..5e4810af --- /dev/null +++ b/Source/HarmonyPatches/Patches_AssignBed/Patch_AssignPrisonersToBed.cs @@ -0,0 +1,160 @@ +using Harmony; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using UnityEngine; +using Verse; +using Verse.AI; + +namespace PrisonLabor.HarmonyPatches.Patches_AssignBed +{ + [HarmonyPatch(typeof(Building_Bed))] + [HarmonyPatch(nameof(Building_Bed.GetGizmos))] + static class Patch_AssignPrisonersToBed + { + static IEnumerable Transpiler(ILGenerator gen, MethodBase mBase, IEnumerable instructions) + { + foreach (var instr in instructions) + { + if (instr.opcode == OpCodes.Ret) + { + yield return new CodeInstruction(OpCodes.Ldarg_0); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_AssignPrisonersToBed).GetMethod(nameof(NewGizmos))); + } + yield return instr; + } + } + + public static IEnumerable NewGizmos(IEnumerable gizmos, Building_Bed bed) + { + foreach (var gizmo in gizmos) + yield return gizmo; + + if (bed.ForPrisoners) + { + yield return new Command_Action() + { + defaultLabel = "CommandBedSetOwnerLabel".Translate(), + defaultDesc = "CommandBedSetOwnerDesc".Translate(), + icon = ContentFinder.Get("ui/commands/AssignOwner", true), + action = new Action(() => Find.WindowStack.Add(new Dialog_AssignBuildingOwner(bed))), + }; + } + } + } + + + [HarmonyPatch(typeof(Building_Bed))] + [HarmonyPatch("get_" + nameof(Building_Bed.AssigningCandidates))] + static class Patch_MakePrisonersCandidates + { + static IEnumerable Transpiler(ILGenerator gen, MethodBase mBase, IEnumerable instructions) + { + foreach (var instr in instructions) + { + if (instr.opcode == OpCodes.Ret) + { + yield return new CodeInstruction(OpCodes.Ldarg_0); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_MakePrisonersCandidates).GetMethod(nameof(NewCandidates))); + } + yield return instr; + } + } + + public static IEnumerable NewCandidates(IEnumerable pawns, Building_Bed bed) + { + if (!bed.ForPrisoners) + { + foreach (var pawn in pawns) + yield return pawn; + } + else + { + foreach (var pawn in bed.Map.mapPawns.PrisonersOfColony) + yield return pawn; + } + } + } + + + [HarmonyPatch(typeof(WorkGiver_Warden_TakeToBed))] + [HarmonyPatch("TakeToPreferredBedJob")] + static class Patch_TakePrisonersToOwnedBed + { + /* === Orignal code Look-up=== + * + * if (RestUtility.FindBedFor(prisoner, prisoner, true, true, false) != null) + * { + * return null; + * } + * + * === CIL Instructions === + * + * ldarg.1 | | Label 2 + * ldarg.1 | | no labels + * ldc.i4.1 | | no labels + * ldc.i4.1 | | no labels + * ldc.i4.0 | | no labels + * call | RimWorld.Building_Bed FindBedFor(Verse.Pawn, Verse.Pawn, Boolean, Boolean, Boolean) | no labels + * brfalse | Label 3 | no labels + */ + + static IEnumerable Transpiler(ILGenerator gen, MethodBase mBase, IEnumerable instructions) + { + OpCode[] opCodes1 = +{ + OpCodes.Ldarg_1, + OpCodes.Ldarg_1, + OpCodes.Ldc_I4_1, + OpCodes.Ldc_I4_1, + OpCodes.Ldc_I4_0, + OpCodes.Call, + OpCodes.Brfalse, + }; + string[] operands1 = + { + "", + "", + "", + "", + "", + "RimWorld.Building_Bed FindBedFor(Verse.Pawn, Verse.Pawn, Boolean, Boolean, Boolean)", + "System.Reflection.Emit.Label", + }; + int step1 = 0; + + var label_OriginalBranch = gen.DefineLabel(); + + foreach (var instr in instructions) + { + if (HPatcher.IsFragment(opCodes1, operands1, instr, ref step1, nameof(Patch_TakePrisonersToOwnedBed), true)) + { + yield return new CodeInstruction(OpCodes.Ldarg_1); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_TakePrisonersToOwnedBed).GetMethod(nameof(HaveOwnedBed))); + yield return new CodeInstruction(OpCodes.Brfalse, label_OriginalBranch); + yield return new CodeInstruction(OpCodes.Pop); + yield return new CodeInstruction(OpCodes.Ldarg_1); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_TakePrisonersToOwnedBed).GetMethod(nameof(CanReachBed))); + yield return new CodeInstruction(OpCodes.Brfalse, instr.operand); + yield return new CodeInstruction(OpCodes.Ldnull); + yield return new CodeInstruction(OpCodes.Ret); + + instr.labels.Add(label_OriginalBranch); + } + yield return instr; + } + } + + public static bool HaveOwnedBed(Pawn pawn) + { + return pawn.ownership != null && pawn.ownership.OwnedBed != null; + } + + public static bool CanReachBed(Pawn pawn) + { + return pawn.CanReach(pawn.ownership.OwnedBed, PathEndMode.OnCell, Danger.Some); + } + } +} diff --git a/Source/HarmonyPatches/Patch_BillPrevention.cs b/Source/HarmonyPatches/Patches_BillAssignation/Patch_BillPrevention.cs similarity index 90% rename from Source/HarmonyPatches/Patch_BillPrevention.cs rename to Source/HarmonyPatches/Patches_BillAssignation/Patch_BillPrevention.cs index 5caf8781..1bb58de0 100644 --- a/Source/HarmonyPatches/Patch_BillPrevention.cs +++ b/Source/HarmonyPatches/Patches_BillAssignation/Patch_BillPrevention.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using Harmony; @@ -6,8 +6,9 @@ using Verse; using System; using System.IO; +using PrisonLabor.Core.BillAssignation; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_BillAssignation { [HarmonyPatch(typeof(WorkGiver_DoBill))] [HarmonyPatch("StartOrResumeBillJob")] @@ -63,7 +64,7 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa { yield return new CodeInstruction(OpCodes.Ldarg_1); yield return new CodeInstruction(OpCodes.Ldloc_1); - yield return new CodeInstruction(OpCodes.Call, typeof(Patch_BillPrevention).GetMethod("IsForCertainGroup")); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_BillPrevention).GetMethod(nameof(IsForCertainGroup))); yield return new CodeInstruction(OpCodes.Brfalse, label); } } @@ -71,7 +72,7 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa public static bool IsForCertainGroup(Pawn pawn, Bill bill) { - var group = BillUtility.IsFor(bill); + var group = BillAssignationUtility.IsFor(bill); if (group == GroupMode.ColonyOnly) return true; if (group == GroupMode.ColonistsOnly && !pawn.IsPrisoner) diff --git a/Source/HarmonyPatches/Patch_ExposeBillGroup.cs b/Source/HarmonyPatches/Patches_BillAssignation/Patch_ExposeBillGroup.cs similarity index 55% rename from Source/HarmonyPatches/Patch_ExposeBillGroup.cs rename to Source/HarmonyPatches/Patches_BillAssignation/Patch_ExposeBillGroup.cs index be05b353..84b65e43 100644 --- a/Source/HarmonyPatches/Patch_ExposeBillGroup.cs +++ b/Source/HarmonyPatches/Patches_BillAssignation/Patch_ExposeBillGroup.cs @@ -1,8 +1,10 @@ -using System; +using System; using Harmony; +using PrisonLabor.Core.BillAssignation; +using PrisonLabor.Core.Meta; using RimWorld; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_BillAssignation { [HarmonyPatch(typeof(Bill))] [HarmonyPatch("ExposeData")] @@ -11,10 +13,7 @@ internal class Patch_ExposeBillGroup { private static void Postfix(Bill __instance) { - if (PrisonLaborPrefs.DisableMod) - return; - - BillUtility.GetData(__instance).ExposeData(); + BillAssignationUtility.GetData(__instance).ExposeData(); } } } \ No newline at end of file diff --git a/Source/HarmonyPatches/Patch_RemoveBillFromUtility.cs b/Source/HarmonyPatches/Patches_BillAssignation/Patch_RemoveBillFromUtility.cs similarity index 60% rename from Source/HarmonyPatches/Patch_RemoveBillFromUtility.cs rename to Source/HarmonyPatches/Patches_BillAssignation/Patch_RemoveBillFromUtility.cs index b4e54c80..c9d91e0a 100644 --- a/Source/HarmonyPatches/Patch_RemoveBillFromUtility.cs +++ b/Source/HarmonyPatches/Patches_BillAssignation/Patch_RemoveBillFromUtility.cs @@ -1,7 +1,8 @@ -using Harmony; +using Harmony; +using PrisonLabor.Core.BillAssignation; using RimWorld; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_BillAssignation { [HarmonyPatch(typeof(BillStack))] [HarmonyPatch("Delete")] @@ -10,7 +11,7 @@ internal class Patch_RemoveBillFromUtility { public static void Postfix(Bill bill) { - BillUtility.Remove(bill); + BillAssignationUtility.Remove(bill); } } } \ No newline at end of file diff --git a/Source/HarmonyPatches/Patches_Construction/EnableConstructionFinishFrames.cs b/Source/HarmonyPatches/Patches_Construction/EnableConstructionFinishFrames.cs new file mode 100644 index 00000000..fa5a37d6 --- /dev/null +++ b/Source/HarmonyPatches/Patches_Construction/EnableConstructionFinishFrames.cs @@ -0,0 +1,65 @@ +using Harmony; +using RimWorld; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using Verse; + +namespace PrisonLabor.HarmonyPatches.Patches_Construction +{ + [HarmonyPatch(typeof(WorkGiver_ConstructFinishFrames))] + [HarmonyPatch(nameof(WorkGiver_ConstructFinishFrames.JobOnThing))] + static class EnableConstructionFinishFrames + { + /* === Orignal code Look-up=== + * + * if (t.Faction != pawn.Faction) + * { + * return false; + * } + * + * === CIL Instructions === + * + * ldarg.2 | | no labels + * callvirt | RimWorld.Faction get_Faction() | no labels + * ldarg.1 | | no labels + * callvirt | RimWorld.Faction get_Faction() | no labels + * beq | Label 1 | no labels + * ldc.i4.0 | | no labels + * ret | | no labels + * ldarg.2 | | Label 1 + */ + + static IEnumerable Transpiler(ILGenerator gen, MethodBase mBase, IEnumerable instructions) + { + //find label to jump + OpCode[] opCodes1 = + { + OpCodes.Ldarg_2, + OpCodes.Callvirt, + OpCodes.Ldarg_1, + OpCodes.Callvirt, + OpCodes.Beq, + }; + string[] operands1 = + { + "", + "RimWorld.Faction get_Faction()", + "", + "RimWorld.Faction get_Faction()", + "System.Reflection.Emit.Label", + }; + var label = HPatcher.FindOperandAfter(opCodes1, operands1, instructions, true); + + //Add If(pawn.IsPrisonerOfColony) {jump next condition} + yield return new CodeInstruction(OpCodes.Ldarg_1); + yield return new CodeInstruction(OpCodes.Callvirt, typeof(Pawn).GetProperty(nameof(Pawn.IsPrisoner)).GetGetMethod()); + yield return new CodeInstruction(OpCodes.Brtrue, label); + + foreach (var instr in instructions) + { + yield return instr; + } + } + } +} diff --git a/Source/HarmonyPatches/Patches_DeepDrill/EnableDeepDrillsToPrisoners.cs b/Source/HarmonyPatches/Patches_DeepDrill/EnableDeepDrillsToPrisoners.cs new file mode 100644 index 00000000..6584a152 --- /dev/null +++ b/Source/HarmonyPatches/Patches_DeepDrill/EnableDeepDrillsToPrisoners.cs @@ -0,0 +1,68 @@ +using Harmony; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using Verse; + +namespace PrisonLabor.HarmonyPatches.Patches_DeepDrill +{ + [HarmonyPatch(typeof(WorkGiver_DeepDrill))] + [HarmonyPatch(nameof(WorkGiver_DeepDrill.HasJobOnThing))] + static class EnableDeepDrillsToPrisoners + { + /* === Orignal code Look-up=== + * + * if (t.Faction != pawn.Faction) + * { + * return false; + * } + * + * === CIL Instructions === + * + * ldarg.2 | | no labels + * callvirt | RimWorld.Faction get_Faction() | no labels + * ldarg.1 | | no labels + * callvirt | RimWorld.Faction get_Faction() | no labels + * beq | Label 1 | no labels + * ldc.i4.0 | | no labels + * ret | | no labels + * ldarg.2 | | Label 1 + */ + + static IEnumerable Transpiler(ILGenerator gen, MethodBase mBase, IEnumerable instructions) + { + //find label to jump + OpCode[] opCodes1 = + { + OpCodes.Ldarg_2, + OpCodes.Callvirt, + OpCodes.Ldarg_1, + OpCodes.Callvirt, + OpCodes.Beq, + }; + string[] operands1 = + { + "", + "RimWorld.Faction get_Faction()", + "", + "RimWorld.Faction get_Faction()", + "System.Reflection.Emit.Label", + }; + var label = HPatcher.FindOperandAfter(opCodes1, operands1, instructions, true); + + //Add If(pawn.IsPrisonerOfColony) {jump next condition} + yield return new CodeInstruction(OpCodes.Ldarg_1); + yield return new CodeInstruction(OpCodes.Callvirt, typeof(Pawn).GetProperty(nameof(Pawn.IsPrisoner)).GetGetMethod()); + yield return new CodeInstruction(OpCodes.Brtrue, label); + + foreach (var instr in instructions) + { + yield return instr; + } + } + } +} diff --git a/Source/HarmonyPatches/Patches_Escaping/Patch_EscapeTracker.cs b/Source/HarmonyPatches/Patches_Escaping/Patch_EscapeTracker.cs new file mode 100644 index 00000000..4e500692 --- /dev/null +++ b/Source/HarmonyPatches/Patches_Escaping/Patch_EscapeTracker.cs @@ -0,0 +1,38 @@ +using Harmony; +using PrisonLabor.Core.Trackers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Verse; + +namespace PrisonLabor.HarmonyPatches.Patches_Escaping +{ + static class Patch_EscapeTracker + { + [HarmonyPatch(typeof(Pawn))] + [HarmonyPatch(nameof(Pawn.ExposeData))] + static class Patch_ExposeData + { + static void Postfix(Pawn __instance) + { + var escapeTracker = EscapeTracker.Of(__instance); + Scribe_Deep.Look(ref escapeTracker, "EscapeTracker", new object[] { __instance}); + EscapeTracker.Save(__instance, escapeTracker); + } + } + + [HarmonyPatch(typeof(Pawn))] + [HarmonyPatch(nameof(Pawn.Tick))] + static class Patch_Tick + { + static void Postfix(Pawn __instance) + { + if (!__instance.Dead) + { + EscapeTracker.Of(__instance)?.Tick(); + } + } + } + } +} diff --git a/Source/HarmonyPatches/Patch_EscapingPrisoner.cs b/Source/HarmonyPatches/Patches_Escaping/Patch_EscapingPrisoner.cs similarity index 81% rename from Source/HarmonyPatches/Patch_EscapingPrisoner.cs rename to Source/HarmonyPatches/Patches_Escaping/Patch_EscapingPrisoner.cs index c9a38303..bef1c698 100644 --- a/Source/HarmonyPatches/Patch_EscapingPrisoner.cs +++ b/Source/HarmonyPatches/Patches_Escaping/Patch_EscapingPrisoner.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using Harmony; @@ -6,8 +6,9 @@ using Verse; using System; using System.IO; +using PrisonLabor.Core.Trackers; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_Escaping { [HarmonyPatch(typeof(JobGiver_PrisonerEscape))] [HarmonyPatch("TryGiveJob")] @@ -19,7 +20,7 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa { Label restOfMethod = gen.DefineLabel(); yield return new CodeInstruction(OpCodes.Ldarg_1); - yield return new CodeInstruction(OpCodes.Call, typeof(Patch_EscapingPrisoner).GetMethod("IsReadyToEscape")); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_EscapingPrisoner).GetMethod(nameof(IsReadyToEscape))); yield return new CodeInstruction(OpCodes.Brtrue, restOfMethod); yield return new CodeInstruction(OpCodes.Ldnull); yield return new CodeInstruction(OpCodes.Ret); @@ -38,11 +39,11 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa public static bool IsReadyToEscape(Pawn pawn) { - Need_Motivation need = pawn.needs.TryGetNeed(); - if (need != null && !need.ReadyToRun) - return false; - else + var escapeTracker = EscapeTracker.Of(pawn, true); + if (escapeTracker.ReadyToEscape) return true; + else + return false; } } } diff --git a/Source/HarmonyPatches/Patches_Food/AddCustomFoodReservation.cs b/Source/HarmonyPatches/Patches_Food/AddCustomFoodReservation.cs new file mode 100644 index 00000000..6b6dc99a --- /dev/null +++ b/Source/HarmonyPatches/Patches_Food/AddCustomFoodReservation.cs @@ -0,0 +1,73 @@ +using Harmony; +using PrisonLabor.Core.Other; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Reflection.Emit; +using Verse; + +namespace PrisonLabor.HarmonyPatches.Patches_Food +{ + /// + /// Adds check if food is already reserved before trying to bring it + /// + [HarmonyPatch(typeof(FoodUtility))] + [HarmonyPatch(nameof(FoodUtility.BestFoodSourceOnMap))] + static class AddCustomFoodReservation + { + /* === Orignal code Look-up=== + * + * c__AnonStorey.foodValidator = delegate(Thing t) + * + * === CIL Instructions === + * + * ldloc.0 | | Label 6Label 8 + * ldloc.0 | | no labels + * ldftn | Boolean <>m__0(Verse.Thing) | no labels + * newobj | Void .ctor(Object, IntPtr) | no labels + * stfld | System.Predicate`1[Verse.Thing] foodValidator | no labels + * + */ + + static IEnumerable Transpiler(ILGenerator gen, IEnumerable instructions) + { + OpCode[] opCodes = + { + OpCodes.Ldftn, + OpCodes.Newobj, + OpCodes.Stfld, + }; + string[] operands = + { + "Boolean <>m__0(Verse.Thing)", + "Void .ctor(Object, IntPtr)", + "foodValidator", + }; + int step = 0; + + foreach (var instr in instructions) + { + if (HPatcher.IsFragment(opCodes, operands, instr, ref step, nameof(AddCustomFoodReservation), false)) + { + yield return new CodeInstruction(OpCodes.Ldarg_0); + yield return new CodeInstruction(OpCodes.Ldarg_1); + yield return new CodeInstruction(OpCodes.Ldarg_2); + yield return new CodeInstruction(OpCodes.Call, typeof(AddCustomFoodReservation).GetMethod(nameof(AddContitionToPredicate))); + } + yield return instr; + } + } + + public static Predicate AddContitionToPredicate(Predicate predicate, Pawn getter, Pawn eater, bool desperate) + { + return new Predicate(target => + { + if (PrisonerFoodReservation.IsReserved(target) && (eater != getter || !eater.IsPrisoner) && !desperate) + return false; + else + return predicate.Invoke(target); + } + ); + } + } +} diff --git a/Source/HarmonyPatches/Patches_Food/DeliverEvenOutsidePrisonCell.cs b/Source/HarmonyPatches/Patches_Food/DeliverEvenOutsidePrisonCell.cs new file mode 100644 index 00000000..10dc82dd --- /dev/null +++ b/Source/HarmonyPatches/Patches_Food/DeliverEvenOutsidePrisonCell.cs @@ -0,0 +1,62 @@ +using Harmony; +using RimWorld; +using System.Collections.Generic; +using System.Reflection.Emit; + +namespace PrisonLabor.HarmonyPatches.Patches_Food +{ + /// + /// This patch is ensuring prisoner will be brought food despite beign outside of prison cell. + /// It skips the condition "IsInPrisonCell" + /// + [HarmonyPatch(typeof(WorkGiver_Warden_DeliverFood))] + [HarmonyPatch(nameof(WorkGiver_Warden_DeliverFood.JobOnThing))] + static class DeliverEvenOutsidePrisonCell + { + /* === Orignal code Look-up=== + * + * if (!pawn2.Position.IsInPrisonCell(pawn2.Map)) + * { + * return null; + * } + * + * === CIL Instructions === + * + * ldloc.0 | | Label 2 + * callvirt | IntVec3 get_Position() | no labels + * ldloc.0 | | no labels + * callvirt | Verse.Map get_Map() | no labels + * call | Boolean IsInPrisonCell(IntVec3, Verse.Map) | no labels + * brtrue | Label 3 | no labels + * ldnull | | no labels + * ret | | no labels + * + * ldloc.0 | | Label 3 + */ + + static IEnumerable Transpiler(ILGenerator gen, IEnumerable instructions) + { + OpCode[] opCodes = + { + OpCodes.Call, + OpCodes.Brtrue, + }; + string[] operands = + { + "Boolean IsInPrisonCell(IntVec3, Verse.Map)", + "System.Reflection.Emit.Label", + }; + int step = 0; + + foreach (var instr in instructions) + { + if (HPatcher.IsFragment(opCodes, operands, instr, ref step, nameof(DeliverEvenOutsidePrisonCell), true)) + { + yield return new CodeInstruction(OpCodes.Pop); + instr.opcode = OpCodes.Br; + } + yield return instr; + } + } + } +} diff --git a/Source/HarmonyPatches/Patches_Food/FoodUtility_IsFoodSourceOnMapSociallyProper.cs b/Source/HarmonyPatches/Patches_Food/FoodUtility_IsFoodSourceOnMapSociallyProper.cs new file mode 100644 index 00000000..3fe17888 --- /dev/null +++ b/Source/HarmonyPatches/Patches_Food/FoodUtility_IsFoodSourceOnMapSociallyProper.cs @@ -0,0 +1,79 @@ +using Harmony; +using RimWorld; +using System.Collections.Generic; +using System.Reflection.Emit; + +namespace PrisonLabor.HarmonyPatches.Patches_Food +{ + /// + /// This patch is ignoring socially improper + /// + [HarmonyPatch(typeof(FoodUtility))] + [HarmonyPatch("IsFoodSourceOnMapSociallyProper")] + static class FoodUtility_IsFoodSourceOnMapSociallyProper + { + /* === Original code Look-up === + * + * if (!allowSociallyImproper) + * { + * bool animalsCare = !getter.RaceProps.Animal; + * if (!t.IsSociallyProper(getter) && !t.IsSociallyProper(eater, eater.IsPrisonerOfColony, animalsCare)) + * { + * return false; + * } + * } + * return true; + * + * === CIL Instructions === + * + * ldarg.3 | | no labels + * brtrue | Label 1 | no labels + * ldarg.1 | | no labels + * callvirt | Verse.RaceProperties get_RaceProps() | no labels + * callvirt | Boolean get_Animal() | no labels + * ldc.i4.0 | | no labels + * ceq | | no labels + * stloc.0 | | no labels + * ldarg.0 | | no labels + * ldarg.1 | | no labels + * call | Boolean IsSociallyProper(Verse.Thing, Verse.Pawn) | no labels + * brtrue | Label 2 | no labels + * ldarg.0 | | no labels + * ldarg.2 | | no labels + * ldarg.2 | | no labels + * callvirt | Boolean get_IsPrisonerOfColony() | no labels + * ldloc.0 | | no labels + * call | Boolean IsSociallyProper(Verse.Thing, Verse.Pawn, Boolean, Boolean) | no labels + * brtrue | Label 3 | no labels + * ldc.i4.0 | | no labels + * ret | | no labels + * ldc.i4.1 | | Label 1Label 2Label 3 + * ret | | no labels + */ + + static IEnumerable Transpiler(ILGenerator gen, IEnumerable instructions) + { + OpCode[] opCodes = + { + OpCodes.Ldarg_3, + OpCodes.Brtrue, + }; + string[] operands = + { + "", + "", + }; + int step = 0; + + foreach (var instr in instructions) + { + if (HPatcher.IsFragment(opCodes, operands, instr, ref step, nameof(FoodUtility_IsFoodSourceOnMapSociallyProper), false)) + { + yield return new CodeInstruction(OpCodes.Pop); + instr.opcode = OpCodes.Br; + } + yield return instr; + } + } + } +} diff --git a/Source/HarmonyPatches/Patches_Food/ReserveFoodForPrisonerAfterDropping.cs b/Source/HarmonyPatches/Patches_Food/ReserveFoodForPrisonerAfterDropping.cs new file mode 100644 index 00000000..49c78b8d --- /dev/null +++ b/Source/HarmonyPatches/Patches_Food/ReserveFoodForPrisonerAfterDropping.cs @@ -0,0 +1,32 @@ +using Harmony; +using PrisonLabor.Core.Other; +using RimWorld; +using System.Collections.Generic; +using System.Linq; +using Verse; +using Verse.AI; + +namespace PrisonLabor.HarmonyPatches.Patches_Food +{ + /// + /// Adds food reservation after dropping food for prisoner + /// + [HarmonyPatch(typeof(JobDriver_FoodDeliver))] + [HarmonyPatch("MakeNewToils")] + static class ReserveFoodForPrisonerAfterDropping + { + static void Postfix(ref IEnumerable __result, JobDriver_FoodDeliver __instance) + { + __result = new List(__result); + var lastToil = ((List)__result).Last(); + + lastToil.initAction = delegate + { + Thing thing; + __instance.pawn.carryTracker.TryDropCarriedThing(lastToil.actor.jobs.curJob.targetC.Cell, ThingPlaceMode.Direct, + out thing, null); + PrisonerFoodReservation.reserve(thing, (Pawn)lastToil.actor.jobs.curJob.targetB.Thing); + }; + } + } +} diff --git a/Source/HarmonyPatches/Patch_ReservedByPrisoner.cs b/Source/HarmonyPatches/Patches_Food/ReservedByPrisonerPatch.cs similarity index 70% rename from Source/HarmonyPatches/Patch_ReservedByPrisoner.cs rename to Source/HarmonyPatches/Patches_Food/ReservedByPrisonerPatch.cs index 9ee20eb8..4da67d3e 100644 --- a/Source/HarmonyPatches/Patch_ReservedByPrisoner.cs +++ b/Source/HarmonyPatches/Patches_Food/ReservedByPrisonerPatch.cs @@ -1,20 +1,23 @@ -using System.Collections.Generic; +using Harmony; +using PrisonLabor.Core.Other; +using RimWorld; +using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; -using Harmony; -using RimWorld; using Verse; using Verse.AI; -namespace PrisonLabor.Harmony +namespace PrisonLabor.HarmonyPatches.Patches_Food { + /// + /// Complete overhaul PawnCanAutomaticallyHaulFast check, to include FoodReservation + /// [HarmonyPatch(typeof(HaulAIUtility))] [HarmonyPatch("PawnCanAutomaticallyHaulFast")] - [HarmonyPatch(new[] {typeof(Pawn), typeof(Thing), typeof(bool)})] - internal class ReservedByPrisonerPatch + [HarmonyPatch(new[] { typeof(Pawn), typeof(Thing), typeof(bool) })] + static class ReservedByPrisonerPatch { - private static IEnumerable Transpiler(ILGenerator gen, MethodBase mBase, - IEnumerable instr) + static IEnumerable Transpiler(ILGenerator gen, MethodBase mBase, IEnumerable instr) { //Load arguments onto stack yield return new CodeInstruction(OpCodes.Ldarg_0); @@ -22,7 +25,7 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa yield return new CodeInstruction(OpCodes.Ldarg_2); //Call function yield return new CodeInstruction(OpCodes.Call, - typeof(ReservedByPrisonerPatch).GetMethod("CanHaulAndInPrisonCell")); + typeof(ReservedByPrisonerPatch).GetMethod(nameof(CanHaulAndInPrisonCell))); //Return yield return new CodeInstruction(OpCodes.Ret); } @@ -39,7 +42,7 @@ public static bool CanHaulAndInPrisonCell(Pawn p, Thing t, bool forced) return false; if (t.def.IsNutritionGivingIngestible && t.def.ingestible.HumanEdible && !t.IsSociallyProper(p, false, true)) - if (PrisonerFoodReservation.isReserved(t)) + if (PrisonerFoodReservation.IsReserved(t)) { JobFailReason.Is("ReservedForPrisoners".Translate()); return false; diff --git a/Source/HarmonyPatches/Patches_Food/StopIfPrisonerCanGetFoodByHimself.cs b/Source/HarmonyPatches/Patches_Food/StopIfPrisonerCanGetFoodByHimself.cs new file mode 100644 index 00000000..951bd65e --- /dev/null +++ b/Source/HarmonyPatches/Patches_Food/StopIfPrisonerCanGetFoodByHimself.cs @@ -0,0 +1,70 @@ +using Harmony; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using System.Text; +using Verse; + +namespace PrisonLabor.HarmonyPatches.Patches_Food +{ + /// + /// This patch is preventing wardens to deliver food when prisoner can get it by himself. + /// There is already some kind of this mechanic in Vanilla RimWorld, but it only affect room that prisoner is inside. + /// + [HarmonyPatch(typeof(WorkGiver_Warden_DeliverFood))] + [HarmonyPatch(nameof(WorkGiver_Warden_DeliverFood.JobOnThing))] + static class StopIfPrisonerCanGetFoodByHimself + { + /* === Orignal code Look-up=== + * + * if (WorkGiver_Warden_DeliverFood.FoodAvailableInRoomTo(pawn2)) + * { + * return null; + * } + * + * === CIL Instructions === + * + * ldloc.0 | | Label 7 + * call | Boolean FoodAvailableInRoomTo(Verse.Pawn) | no labels + * brfalse | Label 8 | no labels + * ldnull | | no labels + * ret | | no labels + * ldloc.2 | | Label 8 + */ + + static IEnumerable Transpiler(ILGenerator gen, IEnumerable instructions) + { + OpCode[] opCodes = + { + OpCodes.Call, + }; + string[] operands = + { + "Boolean FoodAvailableInRoomTo(Verse.Pawn)", + }; + int step = 0; + + foreach (var instr in instructions) + { + if (HPatcher.IsFragment(opCodes, operands, instr, ref step, nameof(StopIfPrisonerCanGetFoodByHimself) + 1, true)) + { + yield return new CodeInstruction(OpCodes.Call, typeof(StopIfPrisonerCanGetFoodByHimself).GetMethod(nameof(FoodAvailableForPrisoner))); + } + else + { + yield return instr; + } + } + } + + public static bool FoodAvailableForPrisoner(Pawn pawn) + { + Thing thing; + ThingDef thingDef; + + return FoodUtility.TryFindBestFoodSourceFor(pawn, pawn, false, out thing, out thingDef, true, true, false, false, true, pawn.IsWildMan()); + } + } +} diff --git a/Source/HarmonyPatches/Patch_BillCheckbox.cs b/Source/HarmonyPatches/Patches_GUI/GUI_Bill/Patch_BillCheckbox.cs similarity index 61% rename from Source/HarmonyPatches/Patch_BillCheckbox.cs rename to Source/HarmonyPatches/Patches_GUI/GUI_Bill/Patch_BillCheckbox.cs index 81e47f8a..c317c0e4 100644 --- a/Source/HarmonyPatches/Patch_BillCheckbox.cs +++ b/Source/HarmonyPatches/Patches_GUI/GUI_Bill/Patch_BillCheckbox.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using Harmony; @@ -8,11 +8,15 @@ using System; using System.IO; using Verse.Sound; +using PrisonLabor.Core.BillAssignation; #pragma warning disable CS0252 -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_GUI.GUI_Bill { + /// + /// This patch is adding checkbox to toggle between allowed workers + /// [HarmonyPatch(typeof(Dialog_BillConfig))] [HarmonyPatch("DoWindowContents")] [HarmonyPatch(new[] { typeof(Rect) })] @@ -71,13 +75,12 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa if (ci.labels.Contains((Label)label)) { var injectedInstruction = new CodeInstruction(OpCodes.Ldloc_S, listing); - foreach(var item in ci.labels) + foreach (var item in ci.labels) injectedInstruction.labels.Add(item); yield return injectedInstruction; yield return new CodeInstruction(OpCodes.Ldarg_0); yield return new CodeInstruction(OpCodes.Ldfld, billField); - yield return new CodeInstruction(OpCodes.Call, - typeof(Patch_BillCheckbox).GetMethod("GroupExclusionButton")); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_BillCheckbox).GetMethod(nameof(GroupExclusionButton))); ci.labels.Clear(); } @@ -87,51 +90,47 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa public static void GroupExclusionButton(Listing_Standard listing, Bill bill) { - if (BillUtility.IsFor(bill) == GroupMode.ColonistsOnly) + string label; + switch (BillAssignationUtility.IsFor(bill)) { - if (listing.ButtonText("PrisonLabor_ColonistsOnly".Translate())) - { - BillUtility.SetFor(bill, GroupMode.PrisonersOnly); - SoundDefOf.Click.PlayOneShotOnCamera(); - } - } - else if (BillUtility.IsFor(bill) == GroupMode.PrisonersOnly) - { - if (listing.ButtonText("PrisonLabor_PrisonersOnly".Translate())) - { - BillUtility.SetFor(bill, GroupMode.ColonyOnly); - SoundDefOf.Click.PlayOneShotOnCamera(); - } + case GroupMode.ColonistsOnly: + label = "PrisonLabor_ColonistsOnlyShort".Translate(); + break; + case GroupMode.PrisonersOnly: + label = "PrisonLabor_PrisonersOnlyShort".Translate(); + break; + case GroupMode.ColonyOnly: + label = "PrisonLabor_ColonyOnlyShort".Translate(); + break; + default: + label = "no label"; + break; } - else + + if (listing.ButtonText(label)) { - if (listing.ButtonText("PrisonLabor_ColonyOnly".Translate())) - { - BillUtility.SetFor(bill, GroupMode.ColonistsOnly); - SoundDefOf.Click.PlayOneShotOnCamera(); - } + MakeModeFloatMenu(bill); } - listing.Gap(12f); - } - public static Rect SetRect(Rect rect) - { - rect.height += 32; - rect.width -= 16; - return rect; - } - - public static Vector2 position; - public static void StartScrolling(Rect rect) - { - Rect viewRect = new Rect(0, 0, rect.width - 16, rect.height + 32); - Rect outRect = new Rect(0, 0, rect.width, rect.height); - Widgets.BeginScrollView(outRect, ref position, viewRect, true); + listing.Gap(12f); } - public static void StopScrolling() + private static void MakeModeFloatMenu(Bill bill) { - Widgets.EndScrollView(); + List list = new List(); + list.Add(new FloatMenuOption("PrisonLabor_ColonyOnly".Translate(), delegate + { + BillAssignationUtility.SetFor(bill, GroupMode.ColonyOnly); + })); + list.Add(new FloatMenuOption("PrisonLabor_ColonistsOnly".Translate(), delegate + { + BillAssignationUtility.SetFor(bill, GroupMode.ColonistsOnly); + })); + list.Add(new FloatMenuOption("PrisonLabor_PrisonersOnly".Translate(), delegate + { + BillAssignationUtility.SetFor(bill, GroupMode.PrisonersOnly); + })); + Find.WindowStack.Add(new FloatMenu(list)); } } } \ No newline at end of file diff --git a/Source/HarmonyPatches/Patch_AddScrollToPrisonerTab.cs b/Source/HarmonyPatches/Patches_GUI/GUI_PrisonerTab/Patch_AddScrollToPrisonerTab.cs similarity index 94% rename from Source/HarmonyPatches/Patch_AddScrollToPrisonerTab.cs rename to Source/HarmonyPatches/Patches_GUI/GUI_PrisonerTab/Patch_AddScrollToPrisonerTab.cs index 48f8636c..785db417 100644 --- a/Source/HarmonyPatches/Patch_AddScrollToPrisonerTab.cs +++ b/Source/HarmonyPatches/Patches_GUI/GUI_PrisonerTab/Patch_AddScrollToPrisonerTab.cs @@ -1,4 +1,4 @@ -using Harmony; +using Harmony; using RimWorld; using System; using System.Collections.Generic; @@ -8,7 +8,7 @@ using UnityEngine; using Verse; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_GUI.GUI_PrisonerTab { /// /// This patch is adding scroll bar to prisoner tab to ensure all interaction modes are visible @@ -80,7 +80,7 @@ private static IEnumerable Transpiler(ILGenerator gen, IEnumera // end scroll if (HPatcher.IsFragment(opCodes2, operands2, ci, ref step2, "AddScrollToPrisonerTab2")) { - var instruction = new CodeInstruction(OpCodes.Call, typeof(Patch_AddScrollToPrisonerTab).GetMethod("StopScrolling")); + var instruction = new CodeInstruction(OpCodes.Call, typeof(Patch_AddScrollToPrisonerTab).GetMethod(nameof(StopScrolling))); instruction.labels.AddRange(ci.labels); ci.labels.Clear(); yield return instruction; @@ -89,7 +89,7 @@ private static IEnumerable Transpiler(ILGenerator gen, IEnumera // resize if (HPatcher.IsFragment(opCodes3, operands3, ci, ref step3, "AddScrollToPrisonerTab3")) { - + } yield return ci; @@ -98,7 +98,7 @@ private static IEnumerable Transpiler(ILGenerator gen, IEnumera if (HPatcher.IsFragment(opCodes1, operands1, ci, ref step1, "AddScrollToPrisonerTab1")) { yield return new CodeInstruction(OpCodes.Ldloc_S, rect); - yield return new CodeInstruction(OpCodes.Call, typeof(Patch_AddScrollToPrisonerTab).GetMethod("StartScrolling")); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_AddScrollToPrisonerTab).GetMethod(nameof(StartScrolling))); yield return new CodeInstruction(OpCodes.Stloc_S, rect); } } diff --git a/Source/HarmonyPatches/Patch_ExtendVistorRect.cs b/Source/HarmonyPatches/Patches_GUI/GUI_PrisonerTab/Patch_ExtendVistorRect.cs similarity index 89% rename from Source/HarmonyPatches/Patch_ExtendVistorRect.cs rename to Source/HarmonyPatches/Patches_GUI/GUI_PrisonerTab/Patch_ExtendVistorRect.cs index ad229df2..1caaddd8 100644 --- a/Source/HarmonyPatches/Patch_ExtendVistorRect.cs +++ b/Source/HarmonyPatches/Patches_GUI/GUI_PrisonerTab/Patch_ExtendVistorRect.cs @@ -1,4 +1,4 @@ -using Harmony; +using Harmony; using RimWorld; using System; using System.Collections.Generic; @@ -8,7 +8,7 @@ using System.Text; using Verse; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_GUI.GUI_PrisonerTab { [HarmonyPatch(typeof(ITab_Pawn_Visitor))] [HarmonyPatch("FillTab")] diff --git a/Source/HarmonyPatches/Patches_GUI/GUI_PrisonerTab/Patch_PrisonerTab.cs b/Source/HarmonyPatches/Patches_GUI/GUI_PrisonerTab/Patch_PrisonerTab.cs new file mode 100644 index 00000000..32c0212d --- /dev/null +++ b/Source/HarmonyPatches/Patches_GUI/GUI_PrisonerTab/Patch_PrisonerTab.cs @@ -0,0 +1,92 @@ +using Harmony; +using PrisonLabor.Core.Needs; +using PrisonLabor.Core.Trackers; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using Verse; + +namespace PrisonLabor.HarmonyPatches.Patches_GUI.GUI_PrisonerTab +{ + /// + /// This patch is adding: + /// 1. string in dev mode indicating percentage of being unwatched before escaping + /// 2. Recruit option + /// + [HarmonyPatch(typeof(ITab_Pawn_Visitor), "FillTab")] + public static class Patch_PrisonerTab + { + static IEnumerable Transpiler(ILGenerator gen, IEnumerable instr) + { + // "if (Prefs.DevMode)" fragment + OpCode[] opCodes1 = + { + OpCodes.Call, + OpCodes.Brfalse, + }; + string[] operands1 = + { + "Boolean get_DevMode()", + "System.Reflection.Emit.Label", + }; + int step1 = 0; + + // "listing_Standard.End()" fragment + OpCode[] opCodes2 = + { + OpCodes.Ldloc_3, + OpCodes.Callvirt, + }; + string[] operands2 = + { + "", + "Void End()", + }; + int step2 = 0; + + foreach (var ci in instr) + { + if (HPatcher.IsFragment(opCodes2, operands2, ci, ref step2, "Patch_PrisonerTab listing_standard.End()")) + { + yield return new CodeInstruction(OpCodes.Ldloc_3); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_PrisonerTab).GetMethod(nameof(AddRecruitButton))); + } + + yield return ci; + + if (HPatcher.IsFragment(opCodes1, operands1, ci, ref step1, "Patch_PrisonerTab IfDevMode")) + { + yield return new CodeInstruction(OpCodes.Ldloc_3); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_PrisonerTab).GetMethod(nameof(AppendDevLines))); + } + + } + } + + public static void AppendDevLines(Listing_Standard listingStandard) + { + var pawn = Find.Selector.SingleSelectedThing as Pawn; + var escapeTracker = EscapeTracker.Of(pawn); + if (escapeTracker != null) + listingStandard.Label("Dev: Ready to escape: " + (escapeTracker.ReadyToEscape ? "ready" : escapeTracker.ReadyToRunPercentage + "%") + $" (Cap:{escapeTracker.EscapeLevel})", -1f); + } + + public static void AddRecruitButton(Listing_Standard listingStandard) + { + var pawn = Find.Selector.SingleSelectedThing as Pawn; + var need = pawn.needs.TryGetNeed(); + if (need != null && need.ResocializationReady) + { + if (listingStandard.ButtonTextLabeled("PrisonLabor_RecruitButtonDesc".Translate(), "PrisonLabor_RecruitButtonLabel".Translate())) + { + pawn.guest.SetGuestStatus(null); + pawn.SetFaction(Faction.OfPlayer); + } + } + } + } +} diff --git a/Source/HarmonyPatches/Patch_ChangeWorkTabPrisonerLabelColor.cs b/Source/HarmonyPatches/Patches_GUI/GUI_WorkTab/Patch_ChangeWorkTabPrisonerLabelColor.cs similarity index 88% rename from Source/HarmonyPatches/Patch_ChangeWorkTabPrisonerLabelColor.cs rename to Source/HarmonyPatches/Patches_GUI/GUI_WorkTab/Patch_ChangeWorkTabPrisonerLabelColor.cs index 2e50bc6b..04a92c12 100644 --- a/Source/HarmonyPatches/Patch_ChangeWorkTabPrisonerLabelColor.cs +++ b/Source/HarmonyPatches/Patches_GUI/GUI_WorkTab/Patch_ChangeWorkTabPrisonerLabelColor.cs @@ -1,9 +1,9 @@ -using Harmony; +using Harmony; using RimWorld; using UnityEngine; using Verse; -namespace PrisonLabor.Harmony +namespace PrisonLabor.HarmonyPatches.Patches_GUI.GUI_WorkTab { [HarmonyPatch(typeof(PawnColumnWorker_Label))] [HarmonyPatch("DoCell")] diff --git a/Source/HarmonyPatches/Patches_GUI/GUI_WorkTab/Patch_PawnTableSetDirtyFix.cs b/Source/HarmonyPatches/Patches_GUI/GUI_WorkTab/Patch_PawnTableSetDirtyFix.cs new file mode 100644 index 00000000..b207b298 --- /dev/null +++ b/Source/HarmonyPatches/Patches_GUI/GUI_WorkTab/Patch_PawnTableSetDirtyFix.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using Harmony; +using RimWorld; +using Verse; + +namespace PrisonLabor.HarmonyPatches.Patches_GUI.GUI_WorkTab +{ + /// + /// This partch is ensuring prisonersTable is set dirty, when parent component is set to dirty too. + /// + [HarmonyPatch(typeof(MainTabWindow_PawnTable))] + [HarmonyPatch("SetDirty")] + internal class Patch_PawnTableSetDirtyFix + { + private static void Prefix(MainTabWindow_PawnTable __instance) + { + var prisonersTable = __instance.GetType().GetField("prisonersTable", BindingFlags.NonPublic | BindingFlags.Instance); + if (prisonersTable != null) + { + var SetDirty = prisonersTable.FieldType.GetMethod("SetDirty"); + if (SetDirty != null) + SetDirty.Invoke(prisonersTable.GetValue(__instance), new object[] { }); + } + } + } +} \ No newline at end of file diff --git a/Source/HarmonyPatches/Patch_WorkDisable.cs b/Source/HarmonyPatches/Patches_GUI/GUI_WorkTab/Patch_WorkDisabled.cs similarity index 82% rename from Source/HarmonyPatches/Patch_WorkDisable.cs rename to Source/HarmonyPatches/Patches_GUI/GUI_WorkTab/Patch_WorkDisabled.cs index e91dcc9f..f9056a33 100644 --- a/Source/HarmonyPatches/Patch_WorkDisable.cs +++ b/Source/HarmonyPatches/Patches_GUI/GUI_WorkTab/Patch_WorkDisabled.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using Harmony; +using PrisonLabor.Core.LaborWorkSettings; using RimWorld; using Verse; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_GUI.GUI_WorkTab { [HarmonyPatch(typeof(WidgetsWork))] [HarmonyPatch("DrawWorkBoxFor")] @@ -20,7 +21,7 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa yield return new CodeInstruction(OpCodes.Ldarg_2); yield return new CodeInstruction(OpCodes.Ldarg_3); yield return new CodeInstruction(OpCodes.Call, - typeof(WorkSettings).GetMethod("WorkDisabled", new[] {typeof(Pawn), typeof(WorkTypeDef)})); + typeof(WorkSettings).GetMethod(nameof(WorkSettings.WorkDisabled), new[] {typeof(Pawn), typeof(WorkTypeDef)})); //If false continue yield return new CodeInstruction(OpCodes.Brfalse, jumpTo); //Return diff --git a/Source/HarmonyPatches/Patch_WorkDisable2.cs b/Source/HarmonyPatches/Patches_GUI/GUI_WorkTab/Patch_WorkDisabled2.cs similarity index 83% rename from Source/HarmonyPatches/Patch_WorkDisable2.cs rename to Source/HarmonyPatches/Patches_GUI/GUI_WorkTab/Patch_WorkDisabled2.cs index 25093990..894db34f 100644 --- a/Source/HarmonyPatches/Patch_WorkDisable2.cs +++ b/Source/HarmonyPatches/Patches_GUI/GUI_WorkTab/Patch_WorkDisabled2.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using Harmony; +using PrisonLabor.Core.LaborWorkSettings; using RimWorld; using Verse; -namespace PrisonLabor.Harmony +namespace PrisonLabor.HarmonyPatches.Patches_GUI.GUI_WorkTab { [HarmonyPatch(typeof(WidgetsWork))] [HarmonyPatch("TipForPawnWorker")] @@ -20,7 +21,7 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa yield return new CodeInstruction(OpCodes.Ldarg_0); yield return new CodeInstruction(OpCodes.Ldarg_1); yield return new CodeInstruction(OpCodes.Call, - typeof(WorkSettings).GetMethod("WorkDisabled", new[] {typeof(Pawn), typeof(WorkTypeDef)})); + typeof(WorkSettings).GetMethod(nameof(WorkSettings.WorkDisabled), new[] {typeof(Pawn), typeof(WorkTypeDef)})); //If false continue yield return new CodeInstruction(OpCodes.Brfalse, jumpTo); //Load string TODO translate diff --git a/Source/HarmonyPatches/Patch_DefaultInteractionMode.cs b/Source/HarmonyPatches/Patches_InteractionMode/Patch_DefaultInteractionMode.cs similarity index 84% rename from Source/HarmonyPatches/Patch_DefaultInteractionMode.cs rename to Source/HarmonyPatches/Patches_InteractionMode/Patch_DefaultInteractionMode.cs index 4a1cb96a..910c5b6b 100644 --- a/Source/HarmonyPatches/Patch_DefaultInteractionMode.cs +++ b/Source/HarmonyPatches/Patches_InteractionMode/Patch_DefaultInteractionMode.cs @@ -1,12 +1,13 @@ -using Harmony; +using Harmony; using System; using System.Collections.Generic; using System.Linq; using System.Text; using RimWorld; using Verse; +using PrisonLabor.Core.Meta; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_InteractionMode { [HarmonyPatch(typeof(Pawn_GuestTracker))] [HarmonyPatch("SetGuestStatus")] diff --git a/Source/HarmonyPatches/Patch_PrisonInteractionLabel.cs b/Source/HarmonyPatches/Patches_InteractionMode/Patch_PrisonInteractionLabel.cs similarity index 88% rename from Source/HarmonyPatches/Patch_PrisonInteractionLabel.cs rename to Source/HarmonyPatches/Patches_InteractionMode/Patch_PrisonInteractionLabel.cs index 62e3d7d7..dc16836f 100644 --- a/Source/HarmonyPatches/Patch_PrisonInteractionLabel.cs +++ b/Source/HarmonyPatches/Patches_InteractionMode/Patch_PrisonInteractionLabel.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using Harmony; using RimWorld; using Verse; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_InteractionMode { //[HarmonyPatch(typeof(PrisonerInteractionModeUtility))] //[HarmonyPatch("GetLabel")] @@ -18,10 +18,10 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa // create our WORK label var jumpTo = gen.DefineLabel(); yield return new CodeInstruction(OpCodes.Ldarg_0); - yield return new CodeInstruction(OpCodes.Call, typeof(Patch_PrisonInteractionLabel).GetMethod("getLabelWork")); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_PrisonInteractionLabel).GetMethod(nameof(getLabelWork))); yield return new CodeInstruction(OpCodes.Brfalse, jumpTo); yield return new CodeInstruction(OpCodes.Ldarg_0); - yield return new CodeInstruction(OpCodes.Call, typeof(Patch_PrisonInteractionLabel).GetMethod("getLabelWork")); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_PrisonInteractionLabel).GetMethod(nameof(getLabelWork))); yield return new CodeInstruction(OpCodes.Ret); var first = true; diff --git a/Source/HarmonyPatches/Patch_AddLaborArea.cs b/Source/HarmonyPatches/Patches_LaborArea/Patch_AddLaborArea.cs similarity index 74% rename from Source/HarmonyPatches/Patch_AddLaborArea.cs rename to Source/HarmonyPatches/Patches_LaborArea/Patch_AddLaborArea.cs index 4e923537..8b11b6a1 100644 --- a/Source/HarmonyPatches/Patch_AddLaborArea.cs +++ b/Source/HarmonyPatches/Patches_LaborArea/Patch_AddLaborArea.cs @@ -1,7 +1,8 @@ -using Harmony; +using Harmony; +using PrisonLabor.Core.LaborArea; using Verse; -namespace PrisonLabor.Harmony +namespace PrisonLabor.HarmonyPatches.Patches_LaborArea { [HarmonyPatch(typeof(AreaManager))] [HarmonyPatch("AddStartingAreas")] diff --git a/Source/HarmonyPatches/Patch_LaborForbid.cs b/Source/HarmonyPatches/Patches_LaborArea/Patch_LaborForbid.cs similarity index 83% rename from Source/HarmonyPatches/Patch_LaborForbid.cs rename to Source/HarmonyPatches/Patches_LaborArea/Patch_LaborForbid.cs index 31c753d5..4b8b1cca 100644 --- a/Source/HarmonyPatches/Patch_LaborForbid.cs +++ b/Source/HarmonyPatches/Patches_LaborArea/Patch_LaborForbid.cs @@ -1,13 +1,14 @@ -using System; +using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using Harmony; +using PrisonLabor.Core; using RimWorld; using Verse; using Verse.AI; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_LaborArea { [HarmonyPatch(typeof(JobGiver_Work))] [HarmonyPatch("TryIssueJobPackage")] @@ -18,9 +19,9 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa IEnumerable instr) { //var pawn = HPatcher.FindOperandAfter(new[] { OpCodes.Ldfld }, new[] { "Verse.Pawn pawn" }, instr); - var jobgiver = HPatcher.FindOperandAfter(new[] {OpCodes.Ldloc_S }, new[] { "RimWorld.JobGiver_Work+c__AnonStorey1 (11)" }, instr ); + var jobgiver = HPatcher.FindOperandAfter(new[] { OpCodes.Ldloc_S }, new[] { "RimWorld.JobGiver_Work+c__AnonStorey1 (11)" }, instr); var scanner = HPatcher.FindOperandAfter(new[] { OpCodes.Ldfld }, new[] { "RimWorld.WorkGiver_Scanner scanner" }, instr); - var cell = HPatcher.FindOperandAfter(new[] {OpCodes.Ldloc_S }, new[] { "Verse.IntVec3 (33)" }, instr ); + var cell = HPatcher.FindOperandAfter(new[] { OpCodes.Ldloc_S }, new[] { "Verse.IntVec3 (33)" }, instr); OpCode[] opcodes1 = { @@ -83,17 +84,17 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa yield return new CodeInstruction(OpCodes.Ldarg_1); yield return new CodeInstruction(OpCodes.Ldloc_S, jobgiver); yield return new CodeInstruction(OpCodes.Ldfld, scanner); - yield return new CodeInstruction(OpCodes.Call, typeof(Patch_LaborForbid).GetMethod("CreatePredicate")); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_LaborForbid).GetMethod(nameof(CreatePredicate))); } - if(HPatcher.IsFragment(opCodes2, operands2, ci, ref step2, "Patch_LaborForbid2")) + if (HPatcher.IsFragment(opCodes2, operands2, ci, ref step2, "Patch_LaborForbid2")) { yield return new CodeInstruction(OpCodes.Ldloc_S, cell); yield return new CodeInstruction(OpCodes.Ldarg_1); yield return new CodeInstruction(OpCodes.Ldloc_S, jobgiver); yield return new CodeInstruction(OpCodes.Ldfld, scanner); - yield return new CodeInstruction(OpCodes.Call, typeof(Patch_LaborForbid).GetMethod("GetWorkType")); - yield return new CodeInstruction(OpCodes.Call, typeof(LaborExclusionUtility).GetMethod("IsDisabledByLabor")); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_LaborForbid).GetMethod((nameof(GetWorkType)))); + yield return new CodeInstruction(OpCodes.Call, typeof(PrisonLaborUtility).GetMethod((nameof(PrisonLaborUtility.IsDisabledByLabor)))); yield return new CodeInstruction(OpCodes.Brtrue, ci.operand); } } @@ -103,7 +104,7 @@ public static Predicate CreatePredicate(Pawn pawn, WorkGiver_Scanner scan { return t => !t.IsForbidden(pawn) && scanner.HasJobOnThing(pawn, t, false) - && !LaborExclusionUtility.IsDisabledByLabor(t.Position, pawn, scanner.def.workType); + && !PrisonLaborUtility.IsDisabledByLabor(t.Position, pawn, scanner.def.workType); } public static WorkTypeDef GetWorkType(WorkGiver_Scanner scanner) @@ -112,4 +113,3 @@ public static WorkTypeDef GetWorkType(WorkGiver_Scanner scanner) } } } - \ No newline at end of file diff --git a/Source/HarmonyPatches/Patch_NeedOnlyByPrisoners.cs b/Source/HarmonyPatches/Patches_Needs/Patch_NeedOnlyByPrisoners.cs similarity index 74% rename from Source/HarmonyPatches/Patch_NeedOnlyByPrisoners.cs rename to Source/HarmonyPatches/Patches_Needs/Patch_NeedOnlyByPrisoners.cs index 9a1e4eb0..f495383d 100644 --- a/Source/HarmonyPatches/Patch_NeedOnlyByPrisoners.cs +++ b/Source/HarmonyPatches/Patches_Needs/Patch_NeedOnlyByPrisoners.cs @@ -1,19 +1,19 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using Harmony; +using PrisonLabor.Core.Meta; using RimWorld; using Verse; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_Needs { [HarmonyPatch(typeof(Pawn_NeedsTracker))] [HarmonyPatch("ShouldHaveNeed")] [HarmonyPatch(new[] { typeof(NeedDef) })] - internal class Patch_NeedOnlyByPrisoners + public class Patch_NeedOnlyByPrisoners { - private static IEnumerable Transpiler(ILGenerator gen, MethodBase mBase, - IEnumerable instr) + private static IEnumerable Transpiler(ILGenerator gen, MethodBase mBase, IEnumerable instr) { //Searches for pawn var pawn = HPatcher.FindOperandAfter(new[] { OpCodes.Ldfld }, new[] { "Verse.Pawn pawn" }, instr); @@ -25,8 +25,7 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa yield return new CodeInstruction(OpCodes.Ldarg_0); yield return new CodeInstruction(OpCodes.Ldfld, pawn); //Call function - yield return new CodeInstruction(OpCodes.Call, - typeof(Patch_NeedOnlyByPrisoners).GetMethod("ShouldHaveNeedPrisoner")); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_NeedOnlyByPrisoners).GetMethod(nameof(ShouldHaveNeedPrisoner))); //If true continue yield return new CodeInstruction(OpCodes.Brtrue, jumpTo); //Load false to stack @@ -46,11 +45,9 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa } } - public static bool ShouldHaveNeedPrisoner(NeedDef nd, Pawn pawn) { - if (nd.defName == "PrisonLabor_Motivation" && - !(pawn.IsPrisoner && PrisonLaborPrefs.EnableMotivationMechanics)) + if ((nd.defName == "PrisonLabor_Motivation" || nd.defName == "PrisonLabor_Treatment") && !(pawn.IsPrisoner && PrisonLaborPrefs.EnableMotivationMechanics)) return false; return true; } diff --git a/Source/HarmonyPatches/Patch_ForibiddenDrop.cs b/Source/HarmonyPatches/Patches_PermissionFix/Patch_ForibiddenDrop.cs similarity index 90% rename from Source/HarmonyPatches/Patch_ForibiddenDrop.cs rename to Source/HarmonyPatches/Patches_PermissionFix/Patch_ForibiddenDrop.cs index e76c8050..dddc951e 100644 --- a/Source/HarmonyPatches/Patch_ForibiddenDrop.cs +++ b/Source/HarmonyPatches/Patches_PermissionFix/Patch_ForibiddenDrop.cs @@ -1,8 +1,8 @@ -using System; +using System; using RimWorld; using Verse; -namespace PrisonLabor.Harmony +namespace PrisonLabor.HarmonyPatches.Patches_PermissionFix { internal class ForibiddenDropPatch { diff --git a/Source/HarmonyPatches/Patch_ItemIsForbidden.cs b/Source/HarmonyPatches/Patches_PermissionFix/Patch_ItemIsForbidden.cs similarity index 68% rename from Source/HarmonyPatches/Patch_ItemIsForbidden.cs rename to Source/HarmonyPatches/Patches_PermissionFix/Patch_ItemIsForbidden.cs index b14a9f81..8f024ff5 100644 --- a/Source/HarmonyPatches/Patch_ItemIsForbidden.cs +++ b/Source/HarmonyPatches/Patches_PermissionFix/Patch_ItemIsForbidden.cs @@ -1,11 +1,13 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using Harmony; +using PrisonLabor.Core.Other; +using PrisonLabor.Core.Trackers; using RimWorld; using Verse; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_PermissionFix { /// /// Add checking if food is reserved by prisoner @@ -20,11 +22,9 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa { var endOfPatch = gen.DefineLabel(); yield return new CodeInstruction(OpCodes.Ldarg_0); - yield return new CodeInstruction(OpCodes.Call, typeof(PrisonerFoodReservation).GetMethod("isReserved")); - yield return new CodeInstruction(OpCodes.Brfalse, endOfPatch); yield return new CodeInstruction(OpCodes.Ldarg_1); - yield return new CodeInstruction(OpCodes.Call, typeof(Pawn).GetMethod("get_IsPrisoner")); - yield return new CodeInstruction(OpCodes.Brtrue, endOfPatch); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_ItemIsForbidden).GetMethod(nameof(CustomForbidConditions))); + yield return new CodeInstruction(OpCodes.Brfalse, endOfPatch); yield return new CodeInstruction(OpCodes.Ldc_I4_1); yield return new CodeInstruction(OpCodes.Ret); @@ -39,5 +39,14 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa yield return ci; } } + + public static bool CustomForbidConditions(Thing thing, Pawn pawn) + { + if (PrisonerFoodReservation.IsReserved(thing) && !pawn.IsPrisoner) + return true; + if (pawn.IsWatched() && ForbidUtility.IsForbidden(thing, Faction.OfPlayer)) + return true; + return false; + } } } \ No newline at end of file diff --git a/Source/HarmonyPatches/Patch_RespectReservation.cs b/Source/HarmonyPatches/Patches_PermissionFix/Patch_RespectReservation.cs similarity index 91% rename from Source/HarmonyPatches/Patch_RespectReservation.cs rename to Source/HarmonyPatches/Patches_PermissionFix/Patch_RespectReservation.cs index f7778bae..95ae9568 100644 --- a/Source/HarmonyPatches/Patch_RespectReservation.cs +++ b/Source/HarmonyPatches/Patches_PermissionFix/Patch_RespectReservation.cs @@ -1,4 +1,4 @@ -using Harmony; +using Harmony; using RimWorld; using System; using System.Collections.Generic; @@ -8,7 +8,7 @@ using Verse; using Verse.AI; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_PermissionFix { [HarmonyPatch(typeof(ReservationManager))] [HarmonyPatch("RespectsReservationsOf")] @@ -19,7 +19,7 @@ private static IEnumerable Transpiler(ILGenerator gen, IEnumera Label label = gen.DefineLabel(); yield return new CodeInstruction(OpCodes.Ldarg_0); yield return new CodeInstruction(OpCodes.Ldarg_1); - yield return new CodeInstruction(OpCodes.Call, typeof(Patch_RespectReservation).GetMethod("RespectPrisoners")); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_RespectReservation).GetMethod(nameof(RespectPrisoners))); yield return new CodeInstruction(OpCodes.Brfalse, label); yield return new CodeInstruction(OpCodes.Ldc_I4_1); yield return new CodeInstruction(OpCodes.Ret); diff --git a/Source/HarmonyPatches/Patch_SocialPropernessFix.cs b/Source/HarmonyPatches/Patches_PermissionFix/Patch_SocialPropernessFix.cs similarity index 96% rename from Source/HarmonyPatches/Patch_SocialPropernessFix.cs rename to Source/HarmonyPatches/Patches_PermissionFix/Patch_SocialPropernessFix.cs index d7eb5ff5..69e13e32 100644 --- a/Source/HarmonyPatches/Patch_SocialPropernessFix.cs +++ b/Source/HarmonyPatches/Patches_PermissionFix/Patch_SocialPropernessFix.cs @@ -1,4 +1,4 @@ -using Harmony; +using Harmony; using RimWorld; using System; using System.Collections.Generic; @@ -7,7 +7,7 @@ using System.Text; using Verse; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_PermissionFix { /// /// This patch will fix conditions that allows prisoners only to use some activities inside room where he is. diff --git a/Source/HarmonyPatches/Patch_RenamePrisoners.cs b/Source/HarmonyPatches/Patches_RenamingPrisoners/Patch_RenamePrisoners.cs similarity index 97% rename from Source/HarmonyPatches/Patch_RenamePrisoners.cs rename to Source/HarmonyPatches/Patches_RenamingPrisoners/Patch_RenamePrisoners.cs index f9eed890..0f1d0d54 100644 --- a/Source/HarmonyPatches/Patch_RenamePrisoners.cs +++ b/Source/HarmonyPatches/Patches_RenamingPrisoners/Patch_RenamePrisoners.cs @@ -1,4 +1,4 @@ -using Harmony; +using Harmony; using RimWorld; using System; using System.Collections.Generic; @@ -9,7 +9,7 @@ using UnityEngine; using Verse; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_RenamingPrisoners { /// /// This patch is enabling to temporary rename prisoners for a duration of improsiment @@ -56,7 +56,7 @@ static IEnumerable Transpiler(ILGenerator gen, MethodBase mBase foreach (var instr in instructions) { - if (HPatcher.IsFragment(opCodes, operands, instr, ref step, nameof(Patch_RenamePrisoners) + nameof(EnableRenamingPrisoners))) + if (HPatcher.IsFragment(opCodes, operands, instr, ref step, nameof(Patch_RenamePrisoners) + nameof(EnableRenamingPrisoners), true)) { yield return new CodeInstruction(OpCodes.Call, typeof(EnableRenamingPrisoners).GetMethod(nameof(IsColonistOrPrisonerOfColony))); } diff --git a/Source/HarmonyPatches/Patch_RestrainsPatch.cs b/Source/HarmonyPatches/Patches_Restraints/Patch_RestrainsPatch.cs similarity index 93% rename from Source/HarmonyPatches/Patch_RestrainsPatch.cs rename to Source/HarmonyPatches/Patches_Restraints/Patch_RestrainsPatch.cs index 24370bf0..3e4c7fe8 100644 --- a/Source/HarmonyPatches/Patch_RestrainsPatch.cs +++ b/Source/HarmonyPatches/Patches_Restraints/Patch_RestrainsPatch.cs @@ -1,4 +1,4 @@ -using Harmony; +using Harmony; using System; using System.Collections.Generic; using System.Linq; @@ -6,7 +6,7 @@ using System.Text; using Verse; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_Restraints { [HarmonyPatch(typeof(Pawn))] [HarmonyPatch("TicksPerMove")] diff --git a/Source/HarmonyPatches/Patch_JailorTypeSaveCompatibility.cs b/Source/HarmonyPatches/Patches_SaveCompatibility/Patch_JailorTypeSaveCompatibility.cs similarity index 87% rename from Source/HarmonyPatches/Patch_JailorTypeSaveCompatibility.cs rename to Source/HarmonyPatches/Patches_SaveCompatibility/Patch_JailorTypeSaveCompatibility.cs index e443a7f7..6588f99e 100644 --- a/Source/HarmonyPatches/Patch_JailorTypeSaveCompatibility.cs +++ b/Source/HarmonyPatches/Patches_SaveCompatibility/Patch_JailorTypeSaveCompatibility.cs @@ -1,4 +1,5 @@ -using Harmony; +using Harmony; +using PrisonLabor.Constants; using RimWorld; using System; using System.Collections.Generic; @@ -7,7 +8,7 @@ using System.Text; using Verse; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_SaveCompatibility { [HarmonyPatch(typeof(Pawn_WorkSettings))] [HarmonyPatch("ExposeData")] @@ -21,7 +22,7 @@ private static IEnumerable Transpiler(ILGenerator gen, IEnumera yield return new CodeInstruction(OpCodes.Ldarg_0); yield return new CodeInstruction(OpCodes.Dup); yield return new CodeInstruction(OpCodes.Ldfld, priorities); - yield return new CodeInstruction(OpCodes.Call, typeof(Patch_JailorTypeSaveCompatibility).GetMethod("AddJailor")); + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_JailorTypeSaveCompatibility).GetMethod(nameof(AddJailor))); //yield return new CodeInstruction(OpCodes.Pop); //yield return new CodeInstruction(OpCodes.Ldarg_0); yield return new CodeInstruction(OpCodes.Stfld, priorities); @@ -46,8 +47,8 @@ public static DefMap AddJailor(DefMap priori { var newPriorities = new DefMap(); - int jailorIndex = PrisonLaborDefOf.PrisonLabor_Jailor.index; - newPriorities[PrisonLaborDefOf.PrisonLabor_Jailor] = 0; + int jailorIndex = PL_DefOf.PrisonLabor_Jailor.index; + newPriorities[PL_DefOf.PrisonLabor_Jailor] = 0; foreach (var def in DefDatabase.AllDefs.Where(d => d.index < priorities.Count)) { if (def.index < jailorIndex) diff --git a/Source/HarmonyPatches/Patches_TreatmentTinkering/Patch_ReduceChanceForMentalBreak.cs b/Source/HarmonyPatches/Patches_TreatmentTinkering/Patch_ReduceChanceForMentalBreak.cs new file mode 100644 index 00000000..3ea59cf3 --- /dev/null +++ b/Source/HarmonyPatches/Patches_TreatmentTinkering/Patch_ReduceChanceForMentalBreak.cs @@ -0,0 +1,58 @@ +using Harmony; +using PrisonLabor.Core.Needs; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Verse; +using Verse.AI; + +namespace PrisonLabor.HarmonyPatches.Patches_TreatmentTinkering +{ + /// + /// This patch is reducing chance of mental break for prisoners with low treatment + /// + [HarmonyPatch(typeof(MentalStateHandler), "TryStartMentalState")] + static class Patch_ReduceChanceForMentalBreak + { + static bool Prefix(MentalStateHandler __instance, bool __result, MentalStateDef stateDef, string reason, bool forceWake, bool causedByMood, Pawn otherPawn) + { + if (!causedByMood) + return true; + + Pawn pawn = (Pawn)(typeof(MentalStateHandler).GetField("pawn", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance)); + + if (pawn.IsPrisonerOfColony) + { + var need = pawn.needs.TryGetNeed(); + TreatmentCategory treatmentCat = need.CurCategory; + bool suspended = false; + float chance = 0f; + + switch (treatmentCat) + { + case TreatmentCategory.Normal: + chance = 0.1f; + break; + case TreatmentCategory.Bad: + chance = 0.5f; + break; + case TreatmentCategory.VeryBad: + chance = 1f; + break; + } + + suspended = UnityEngine.Random.value < chance; + + if (suspended) + { + __result = false; + return false; + } + } + return true; + } + } +} diff --git a/Source/HarmonyPatches/Patches_Version/Patch_AddModVersionToFile.cs b/Source/HarmonyPatches/Patches_Version/Patch_AddModVersionToFile.cs new file mode 100644 index 00000000..e0200df8 --- /dev/null +++ b/Source/HarmonyPatches/Patches_Version/Patch_AddModVersionToFile.cs @@ -0,0 +1,117 @@ +using Harmony; +using PrisonLabor.Core.Meta; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using Verse; +using Version = PrisonLabor.Core.Meta.Version; + +namespace PrisonLabor.HarmonyPatches.Patches_Version +{ + static class Patch_AddModVersionToFile + { + private const string ParameterName = "PrisonLaborVersion"; + + [HarmonyPatch(typeof(ScribeMetaHeaderUtility))] + [HarmonyPatch(nameof(ScribeMetaHeaderUtility.WriteMetaHeader))] + static class Patch_WriteVersion + { + /* === Orignal code Look-up=== + * + * catch + * { + * (...) + * <--- + * } + * finally + * { + * (...) + * } + * + * === CIL Instructions === + * + * leave | label 4 | no labels + * + */ + + static IEnumerable Transpiler(ILGenerator gen, MethodBase mBase, IEnumerable instructions) + { + OpCode[] opCodes1 = + { + OpCodes.Leave, + }; + string[] operands1 = + { + "System.Reflection.Emit.Label", + }; + int step1 = 0; + + foreach (var instr in instructions) + { + if (HPatcher.IsFragment(opCodes1, operands1, instr, ref step1, nameof(Patch_WriteVersion), true)) + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_WriteVersion).GetMethod(nameof(AddMetaData))); + yield return instr; + } + } + + public static void AddMetaData() + { + var currentVersionString = VersionUtility.versionNumber; + Scribe_Values.Look(ref currentVersionString, ParameterName, default(Version), true); + } + } + + + [HarmonyPatch(typeof(ScribeMetaHeaderUtility))] + [HarmonyPatch(nameof(ScribeMetaHeaderUtility.LoadGameDataHeader))] + static class Patch_LoadVersion + { + /* === Orignal code Look-up=== + * + * catch + * { + * (...) + * <--- + * } + * finally + * { + * (...) + * } + * + * === CIL Instructions === + * + * leave | label x | no labels + * + */ + + static IEnumerable Transpiler(ILGenerator gen, MethodBase mBase, IEnumerable instructions) + { + OpCode[] opCodes1 = + { + OpCodes.Leave, + }; + string[] operands1 = + { + "System.Reflection.Emit.Label", + }; + int step1 = 0; + + foreach (var instr in instructions) + { + if (HPatcher.IsFragment(opCodes1, operands1, instr, ref step1, nameof(Patch_LoadVersion), true)) + yield return new CodeInstruction(OpCodes.Call, typeof(Patch_LoadVersion).GetMethod(nameof(ReadMetaData))); + yield return instr; + } + } + + public static void ReadMetaData() + { + var currentVersionString = VersionUtility.versionNumber; + // TODO change version to 0.9.6 later, on next major update from RimWorld 1.0 + Scribe_Values.Look(ref currentVersionString, ParameterName, Version.v0_9_6, true); + VersionUtility.VersionOfSaveFile = currentVersionString; + } + } + + } +} diff --git a/Source/HarmonyPatches/Patch_DisableAreaRestrictionsForPrisoners.cs b/Source/HarmonyPatches/Patches_WorkSettings/Patch_DisableAreaRestrictionsForPrisoners.cs similarity index 92% rename from Source/HarmonyPatches/Patch_DisableAreaRestrictionsForPrisoners.cs rename to Source/HarmonyPatches/Patches_WorkSettings/Patch_DisableAreaRestrictionsForPrisoners.cs index 47422715..6d4cc489 100644 --- a/Source/HarmonyPatches/Patch_DisableAreaRestrictionsForPrisoners.cs +++ b/Source/HarmonyPatches/Patches_WorkSettings/Patch_DisableAreaRestrictionsForPrisoners.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using Harmony; @@ -6,7 +6,7 @@ using UnityEngine; using Verse; -namespace PrisonLabor.Harmony +namespace PrisonLabor.HarmonyPatches.Patches_WorkSettings { /// /// This patch will remove prisoners in "Restrict" tab. @@ -23,7 +23,7 @@ private static IEnumerable Transpiler(ILGenerator gen, MethodBa var jumpTo = gen.DefineLabel(); yield return new CodeInstruction(OpCodes.Ldarg_2); yield return new CodeInstruction(OpCodes.Call, - typeof(DisableAreaRestrictionsForPrisoners).GetMethod("isPrisoner")); + typeof(DisableAreaRestrictionsForPrisoners).GetMethod(nameof(isPrisoner))); yield return new CodeInstruction(OpCodes.Brfalse, jumpTo); yield return new CodeInstruction(OpCodes.Ret); diff --git a/Source/HarmonyPatches/Patches_WorkSettings/Patch_ResetWorktableWhenRecruited.cs b/Source/HarmonyPatches/Patches_WorkSettings/Patch_ResetWorktableWhenRecruited.cs new file mode 100644 index 00000000..e0ab96fe --- /dev/null +++ b/Source/HarmonyPatches/Patches_WorkSettings/Patch_ResetWorktableWhenRecruited.cs @@ -0,0 +1,26 @@ +using Harmony; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Verse; + +//Faction newFaction, Pawn recruiter = null + +namespace PrisonLabor.HarmonyPatches.Patches_WorkSettings +{ + [HarmonyPatch(typeof(Pawn))] + [HarmonyPatch("SetFaction")] + [HarmonyPatch(new[] { typeof(Faction), typeof(Pawn) })] + class Patch_ResetWorktableWhenRecruited + { + private static void Prefix(Pawn __instance, Faction newFaction, Pawn recruiter) + { + if(__instance.IsPrisonerOfColony && newFaction == Faction.OfPlayer) + { + __instance.workSettings = null; + } + } + } +} diff --git a/Source/HarmonyPatches/Patch_TimetableFix.cs b/Source/HarmonyPatches/Patches_WorkSettings/Patch_TimetableFix.cs similarity index 95% rename from Source/HarmonyPatches/Patch_TimetableFix.cs rename to Source/HarmonyPatches/Patches_WorkSettings/Patch_TimetableFix.cs index f68e3b15..a8dafa2d 100644 --- a/Source/HarmonyPatches/Patch_TimetableFix.cs +++ b/Source/HarmonyPatches/Patches_WorkSettings/Patch_TimetableFix.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -7,7 +7,7 @@ using System.Reflection.Emit; using Verse; -namespace PrisonLabor.HarmonyPatches +namespace PrisonLabor.HarmonyPatches.Patches_WorkSettings { [HarmonyPatch(typeof(Pawn_TimetableTracker))] [HarmonyPatch("get_CurrentAssignment")] diff --git a/Source/HarmonyPatches/Triggers.cs b/Source/HarmonyPatches/Triggers.cs new file mode 100644 index 00000000..3956dd29 --- /dev/null +++ b/Source/HarmonyPatches/Triggers.cs @@ -0,0 +1,73 @@ +using Harmony; +using PrisonLabor.Core.GameSaves; +using PrisonLabor.Core.Other; +using PrisonLabor.Core.Trackers; +using PrisonLabor.Core.Windows; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Reflection.Emit; +using UnityEngine; +using Verse; + +namespace PrisonLabor.HarmonyPatches +{ + /// + /// This is group of patch dedicated to attach execution of other methods similar to event handling. + /// The difference between patches inside this class, and others is that + /// this patches are executing single method of Prison Labor classes. + /// + static class Triggers + { + [HarmonyPatch(typeof(FloatMenuMakerMap))] + [HarmonyPatch("AddHumanlikeOrders")] + [HarmonyPatch(new[] { typeof(Vector3), typeof(Pawn), typeof(List) })] + static class AddHumanlikeOrders + { + public static void Prefix(Vector3 clickPos, Pawn pawn, List opts) { ArrestUtility.AddArrestOrder(clickPos, pawn, opts); } + } + + [HarmonyPatch(typeof(Pawn_HealthTracker), "PreApplyDamage")] + static class Trigger_PreApplyDamage + { + static IEnumerable Transpiler(IEnumerable instr) + { + foreach (var ci in instr) + { + if (ci.opcode == OpCodes.Ret) + { + yield return new CodeInstruction(OpCodes.Ldarg_0); + yield return new CodeInstruction(OpCodes.Ldarg_1); + yield return new CodeInstruction(OpCodes.Ldarg_2); + yield return new CodeInstruction(OpCodes.Ldind_I1); + yield return new CodeInstruction(OpCodes.Call, typeof(TreatmentUtility).GetMethod(nameof(TreatmentUtility.OnApplyDamage))); + } + + yield return ci; + } + } + } + + [HarmonyPatch(typeof(Game))] + [HarmonyPatch(nameof(Game.LoadGame))] + static class UpgradeSave + { + static bool Prefix() { SaveUpgrader.Upgrade(); return true; } + } + + [HarmonyPatch(typeof(Map))] + [HarmonyPatch(nameof(Map.MapPostTick))] + static class InsiprationTracker + { + static bool Prefix(Map __instance) { InspirationTracker.Calculate(__instance); return true; } + } + + [HarmonyPatch(typeof(Map))] + [HarmonyPatch("FinalizeInit")] + [HarmonyPatch(new Type[] { })] + static class Patch_ShowNews + { + static void Postfix() { NewsWindow.TryShow();} + } + } +} diff --git a/Source/IncidentWorker_Revolt.cs b/Source/IncidentWorker_Revolt.cs deleted file mode 100644 index d01db652..00000000 --- a/Source/IncidentWorker_Revolt.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using UnityEngine; -using Verse; -using RimWorld; -using Verse.AI.Group; -using System.Collections.Generic; - -namespace PrisonLabor -{ - public class IncidentWorker_Revolt : IncidentWorker - { - private const float HivePoints = 400f; - private const float MinMotivationToStart = 0.4f; - - protected override bool CanFireNowSub(IncidentParms parms) - { - Map map = parms.target as Map; - - bool enemyFaction = false; - float accumulatedMotivation = 0.0f; - int prisonersCount = 0; - - foreach (var pawn in map.mapPawns.PrisonersOfColony) - { - if (pawn.Faction.HostileTo(Faction.OfPlayer)) - enemyFaction = true; - - var need = pawn.needs.TryGetNeed(); - if (need == null) - continue; - accumulatedMotivation += need.CurLevel; - prisonersCount++; - } - - if (accumulatedMotivation / prisonersCount > MinMotivationToStart) - return false; - - return enemyFaction && PrisonLaborPrefs.EnableRevolts; - } - - protected override bool TryExecuteWorker(IncidentParms parms) - { - Map map = (Map)parms.target; - Pawn t = null; - var affectedPawns = new List(map.mapPawns.PrisonersOfColony); - foreach (Pawn pawn in affectedPawns) - { - if (pawn.Faction.HostileTo(Faction.OfPlayer)) - { - parms.faction = pawn.Faction; - t = pawn; - break; - } - } - float points = parms.points; - int prisonersLeft = affectedPawns.Count; - foreach (Pawn pawn in affectedPawns) - { - pawn.ClearMind(); - pawn.guest.SetGuestStatus(null, false); - pawn.SetFaction(parms.faction); - - ThingWithComps weapon = ThingMaker.MakeThing(DefDatabase.GetNamed("MeleeWeapon_Shiv"), ThingDefOf.WoodLog) as ThingWithComps; - ThingWithComps ammo = null; - int pointsToRemove = 0; - - if (parms.points >= 1000) - weapon = ThingMaker.MakeThing(DefDatabase.GetNamed("MeleeWeapon_Shiv"), ThingDefOf.Steel) as ThingWithComps; - - if (points >= 1000) - { - // If combat extended is enabled - if (DefDatabase.GetNamed("Weapon_GrenadeStickBomb", false) != null) - { - if (UnityEngine.Random.value > 0.5f) - { - weapon = ThingMaker.MakeThing(DefDatabase.GetNamed("Weapon_GrenadeStickBomb")) as ThingWithComps; - ammo = ThingMaker.MakeThing(DefDatabase.GetNamed("Weapon_GrenadeStickBomb")) as ThingWithComps; - ammo.stackCount = 6; - } - else - { - weapon = ThingMaker.MakeThing(DefDatabase.GetNamed("Weapon_GrenadeMolotov")) as ThingWithComps; - ammo = ThingMaker.MakeThing(DefDatabase.GetNamed("Weapon_GrenadeMolotov")) as ThingWithComps; - ammo.stackCount = 6; - } - } - else - { - weapon = ThingMaker.MakeThing(DefDatabase.GetNamed("Weapon_GrenadeMolotov")) as ThingWithComps; - } - - pointsToRemove = 500; - } - else if (points >= 500) - { - weapon = ThingMaker.MakeThing(DefDatabase.GetNamed("Bow_Short")) as ThingWithComps; - - if (DefDatabase.GetNamed("Ammo_Arrow_Stone", false) != null) - { - ammo = ThingMaker.MakeThing(DefDatabase.GetNamed("Ammo_Arrow_Stone")) as ThingWithComps; - ammo.stackCount = 30; - } - - pointsToRemove = 100; - } - else if (points >= 300) - { - weapon = ThingMaker.MakeThing(DefDatabase.GetNamed("MeleeWeapon_Club"), ThingDefOf.Granite) as ThingWithComps; - pointsToRemove = 100; - } - - if (pawn.equipment.Primary == null) - { - pawn.equipment.AddEquipment(weapon); - if (ammo != null) - pawn.inventory.innerContainer.TryAdd(ammo); - points -= pointsToRemove; - } - } - var lordJob = new LordJob_AssaultColony(); - //TODO old code: - LordMaker.MakeNewLord(parms.faction, lordJob/*(new RaidStrategyWorker_ImmediateAttackSmart()).MakeLordJob(parms, map)*/, map, affectedPawns); - base.SendStandardLetter(t, null, new string[] { t.Name.ToStringShort, t.Faction.Name }); - Find.TickManager.slower.SignalForceNormalSpeedShort(); - return true; - } - } -} \ No newline at end of file diff --git a/Source/Initialization.cs b/Source/Initialization.cs index f687a9a9..da58a7da 100644 --- a/Source/Initialization.cs +++ b/Source/Initialization.cs @@ -1,6 +1,13 @@ -using PrisonLabor.HarmonyPatches; +using PrisonLabor.Core.Hediffs; +using PrisonLabor.Core.LaborArea; +using PrisonLabor.Core.Meta; +using PrisonLabor.Core.Settings; +using PrisonLabor.HarmonyPatches; using PrisonLabor.Tweaks; +using RimWorld; using System; +using System.Collections.Generic; +using System.Linq; using Verse; namespace PrisonLabor @@ -18,13 +25,12 @@ static Initialization() SettingsMenu.Init(); VersionUtility.CheckVersion(); Designator_AreaLabor.Initialization(); - Behaviour_MotivationIcon.Initialization(); CompatibilityPatches.Initialization.Run(); HediffManager.Init(); Log.Message($"Enabled Prison Labor v{VersionUtility.versionString}"); } - catch(Exception e) + catch (Exception e) { Log.Error($"Prison Labor v{VersionUtility.versionString} caught error during start up:\n{e.Message}"); } diff --git a/Source/InspirationUtility.cs b/Source/InspirationUtility.cs deleted file mode 100644 index a0092878..00000000 --- a/Source/InspirationUtility.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Collections.Generic; -using Verse; - -namespace PrisonLabor -{ - internal class InspirationUtility - { - public static Dictionary> inspirationValues = new Dictionary>(); - - public static float GetInsiprationValue(Pawn pawn) - { - var map = pawn.Map; - if (!inspirationValues.ContainsKey(map) || !inspirationValues[map].ContainsKey(pawn)) - Calculate(map); - var value = inspirationValues[map][pawn]; - inspirationValues[map].Remove(pawn); - return value; - } - - private static void Calculate(Map map) - { - var wardens = new List(); - wardens.AddRange(map.mapPawns.FreeColonists); - var prisoners = new List(); - prisoners.AddRange(map.mapPawns.PrisonersOfColony); - - inspirationValues[map] = new Dictionary(); - foreach (var prisoner in prisoners) - inspirationValues[map][prisoner] = 0f; - - var prisonersInRange = new List(); - foreach (var warden in wardens) - { - prisonersInRange.Clear(); - foreach (var prisoner in prisoners) - if (warden.Position.DistanceTo(prisoner.Position) < Need_Motivation.InpirationRange && - prisoner.GetRoom() == warden.GetRoom()) - prisonersInRange.Add(prisoner); - - var delta = Need_Motivation.InspireRate / prisonersInRange.Count; - - foreach (var prisoner in prisonersInRange) - inspirationValues[map][prisoner] += delta; - } - } - } -} \ No newline at end of file diff --git a/Source/JobGiver_Diet.cs b/Source/JobGiver_Diet.cs deleted file mode 100644 index d49b50b3..00000000 --- a/Source/JobGiver_Diet.cs +++ /dev/null @@ -1,90 +0,0 @@ -using RimWorld; -using Verse; -using Verse.AI; - -namespace PrisonLabor -{ - internal class JobGiver_Diet : ThinkNode_JobGiver - { - private HungerCategory minCategory = HungerCategory.Hungry; - private readonly HungerCategory stopWorkingCat = HungerCategory.UrgentlyHungry; - - public override ThinkNode DeepCopy(bool resolve = true) - { - var jobGiver_Diet = (JobGiver_Diet) base.DeepCopy(resolve); - jobGiver_Diet.minCategory = minCategory; - return jobGiver_Diet; - } - - public override float GetPriority(Pawn pawn) - { - var food = pawn.needs.food; - if (food == null) - return 0f; - if (pawn.needs.food.CurCategory < HungerCategory.Starving && FoodUtility_Tweak.ShouldBeFedBySomeone(pawn)) - return 0f; - if (food.CurCategory < minCategory) - return 0f; - if (food.CurCategory <= stopWorkingCat) - return 11f; - if (food.CurLevelPercentage < pawn.RaceProps.FoodLevelPercentageWantEat) - return 7f; - return 0f; - } - - protected override Job TryGiveJob(Pawn pawn) - { - var food = pawn.needs.food; - if (food == null || food.CurCategory < minCategory) - return null; - var need = pawn.needs.TryGetNeed(); - if (need != null) - need.Enabled = false; - bool flag; - if (pawn.RaceProps.Animal) - { - flag = true; - } - else - { - var firstHediffOfDef = pawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.Malnutrition, false); - flag = firstHediffOfDef != null && firstHediffOfDef.Severity > 0.4f; - } - var desperate = pawn.needs.food.CurCategory == HungerCategory.Starving; - var allowCorpse = flag; - Thing thing; - ThingDef def; - if (!FoodUtility_Tweak.TryFindBestFoodSourceFor(pawn, pawn, desperate, out thing, out def, true, true, true, - allowCorpse, false)) - return null; - var pawn2 = thing as Pawn; - if (pawn2 != null) - return new Job(JobDefOf.PredatorHunt, pawn2) - { - killIncappedTarget = true - }; - var building_NutrientPasteDispenser = thing as Building_NutrientPasteDispenser; - if (building_NutrientPasteDispenser != null && - !building_NutrientPasteDispenser.HasEnoughFeedstockInHoppers()) - { - var building = building_NutrientPasteDispenser.AdjacentReachableHopper(pawn); - if (building != null) - { - var hopperSgp = building as ISlotGroupParent; - var job = WorkGiver_CookFillHopper.HopperFillFoodJob(pawn, hopperSgp); - if (job != null) - return job; - } - thing = FoodUtility_Tweak.BestFoodSourceOnMap(pawn, pawn, desperate, FoodPreferability.MealLavish, - false, !pawn.IsTeetotaler(), false, false, false, false, false); - if (thing == null) - return null; - def = thing.def; - } - return new Job(JobDefOf.Ingest, thing) - { - count = FoodUtility_Tweak.WillIngestStackCountOf(pawn, def) - }; - } - } -} \ No newline at end of file diff --git a/Source/LaborExclusionUtility.cs b/Source/LaborExclusionUtility.cs deleted file mode 100644 index c469a730..00000000 --- a/Source/LaborExclusionUtility.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Verse; - -namespace PrisonLabor -{ - internal class LaborExclusionUtility - { - public static bool IsDisabledByLabor(IntVec3 pos, Pawn pawn, WorkTypeDef workType) - { - if (pos != null && pawn.Map.areaManager.Get() != null && - !WorkSettings.WorkDisabled(workType)) - return pawn.Map.areaManager.Get()[pos]; - return false; - } - } -} \ No newline at end of file diff --git a/Source/Libraries/0Harmony.dll b/Source/Libraries/0Harmony.dll new file mode 100644 index 00000000..9654e2ac Binary files /dev/null and b/Source/Libraries/0Harmony.dll differ diff --git a/Source/Need_Motivation.cs b/Source/Need_Motivation.cs deleted file mode 100644 index 62ff6256..00000000 --- a/Source/Need_Motivation.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System.Collections.Generic; -using System.Text; -using RimWorld; -using UnityEngine; -using Verse; - -namespace PrisonLabor -{ - public class Need_Motivation : Need - { - public const float InspireRate = 0.015f; - public const int WardenCapacity = (int)(InspireRate / LazyRate); - public const float InpirationRange = 10.0f; - - private const float LazyLevel = 0.2f; - private const float NeedInspirationLevel = 0.5f; - private const float LazyRate = 0.002f; - private const float HungryRate = 0.006f; - private const float TiredRate = 0.006f; - private const float HealthRate = 0.006f; - private const float JoyRate = 0.006f; - private const int ReadyToRunLevel = 100; - - private static NeedDef def; - - private int delta; - private int impatient; - - public Need_Motivation(Pawn pawn) : base(pawn) - { - delta = 0; - impatient = 0; - ReadyToRun = false; - Insipred = false; - } - - public bool Enabled { get; set; } - - public bool NeedToBeInspired { get; private set; } - - public bool IsLazy { get; private set; } - - public bool ReadyToRun { get; private set; } - - public bool Insipred { get; private set; } - - public bool CanEscape { get; set; } - - public float PercentageThreshNeedInsipration => NeedInspirationLevel; - - public float PercentageThreshLazy => LazyLevel; - - //TODO change to lazy category? - public HungerCategory CurCategory => 0; - - public override int GUIChangeArrow => delta; - - public bool Motivated - { - get - { - if (delta == 1) - return true; - return false; - } - } - - public static NeedDef Def - { - get - { - if (def == null) - def = DefDatabase.GetNamed("PrisonLabor_Motivation"); - return def; - } - } - - private float LazinessRate - { - get - { - if (pawn.IsPrisoner && pawn.IsPrisonerOfColony) - { - if (pawn.GetRoomGroup() != null) - { - var value = InspirationUtility.GetInsiprationValue(pawn); - - if (PrisonLaborUtility.LaborEnabled(pawn)) - { - if (Enabled) - { - value -= LazyRate; - if (HealthAIUtility.ShouldSeekMedicalRest(pawn)) - value -= HealthRate; - value -= (int)pawn.needs.food.CurCategory * HungryRate; - value -= (int)pawn.needs.rest.CurCategory * TiredRate; - if (value >= 0) - Insipred = true; - else - Insipred = false; - } - else if (pawn.timetable != null && - pawn.timetable.CurrentAssignment == TimeAssignmentDefOf.Joy) - { - if (value != 0) - Insipred = true; - else - Insipred = false; - value += JoyRate; - } - else - { - if (value != 0) - Insipred = true; - else - Insipred = false; - } - delta = value.CompareTo(0.0f); - return value; - } - else - { - if (value != 0) - Insipred = true; - else - Insipred = false; - - delta = value.CompareTo(0.0f); - return value; - } - } - else - { - delta = 0; - return 0.0f; - } - } - delta = 1; - return +0.01f; - } - } - - public override void ExposeData() - { - base.ExposeData(); - //Scribe_Values.Look(ref this.lastNonStarvingTick, "lastNonStarvingTick", -99999, false); - } - - public override void NeedInterval() - { - CurLevel += LazinessRate; - - if (CurLevel == MaxLevel) - NeedToBeInspired = false; - if (CurLevel <= NeedInspirationLevel && !NeedToBeInspired) - NeedToBeInspired = true; - if (CurLevel <= LazyLevel && !IsLazy && delta <= 0) - { - IsLazy = true; - Tutorials.Motivation(); - } - else if (IsLazy && delta > 0) - { - IsLazy = false; - } - - ImpatientTick(); - } - - public override void SetInitialLevel() - { - CurLevelPercentage = 1.0f; - Enabled = false; - } - - public override string GetTipString() - { - var stringBuilder = new StringBuilder(); - stringBuilder.AppendLine(base.GetTipString()); - stringBuilder.AppendLine(); - stringBuilder.AppendLine("PrisonLabor_WardenResponseThreshold".Translate() + ": " + - PercentageThreshNeedInsipration.ToStringPercent()); - stringBuilder.AppendLine( - "PrisonLabor_StoppingWorkThreshold".Translate() + ": " + PercentageThreshLazy.ToStringPercent()); - return stringBuilder.ToString(); - } - - public override void DrawOnGUI(Rect rect, int maxThresholdMarkers = 2147483647, float customMargin = -1f, - bool drawArrows = true, bool doTooltip = true) - { - if (threshPercents == null) - threshPercents = new List(); - threshPercents.Clear(); - threshPercents.Add(PercentageThreshLazy); - threshPercents.Add(PercentageThreshNeedInsipration); - base.DrawOnGUI(rect, maxThresholdMarkers, customMargin, drawArrows, doTooltip); - } - - private void ImpatientTick() - { - if (Insipred || !CanEscape) - { - if (impatient != 0) - { - impatient = 0; - ReadyToRun = false; - } - } - else if (!ReadyToRun) - { - impatient++; - if (impatient >= ReadyToRunLevel) - { - ReadyToRun = true; - } - } - } - } -} \ No newline at end of file diff --git a/Source/NewsDialog.cs b/Source/NewsDialog.cs deleted file mode 100644 index 31dd528e..00000000 --- a/Source/NewsDialog.cs +++ /dev/null @@ -1,526 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using Verse; - -namespace PrisonLabor -{ - internal class NewsDialog : Window - { - // Constans - private const float Spacing = 2f; - private const float GapHeight = 12f; - private const string MarginText = " - "; - private readonly float MarginWidth = Text.fontStyles[1].CalcSize(new GUIContent(MarginText)).x; - private const GameFont TitleFont = GameFont.Medium; - private const GameFont ItemFont = GameFont.Small; - - // Static Properties - public static bool autoShow; - - public static bool showAll = false; - - public static bool news_0_5 = false; - public static bool news_0_6 = false; - public static bool news_0_7 = false; - public static bool news_0_8_0 = false; - public static bool news_0_8_1 = false; - public static bool news_0_8_3 = false; - public static bool news_0_8_6 = false; - public static bool news_0_9_0 = false; - public static bool news_0_9_1 = false; - public static bool news_0_9_2 = false; - public static bool news_0_9_9 = false; - - // Fields - private string[] titles; - private string[][] items; - - private Vector2 position; - - public NewsDialog() - { - doCloseButton = true; - doCloseX = true; - Init(); - } - - public void Init() - { - List titlesList = new List(); - List itemsList = new List(); - - // How to insert news: - // [subtitle] for subtitle - // [img] ... [/img] for image (inside name of file) - // [gap] for gap - - // 0.9.11 - if (showAll) - { - titlesList.Add("Prison Labor Beta v0.9.11"); - string[] itemsArray = - { - "fixed compatibility with Fluffy's WorkTab (final)", - }; - itemsList.Add(itemsArray); - } - - // 0.9.10 - if (showAll) - { - titlesList.Add("Prison Labor Beta v0.9.10"); - string[] itemsArray = - { - "hotfixed compatibility with Fluffy's WorkTab (still have some visual flaws)", - }; - itemsList.Add(itemsArray); - } - - // 0.9.9 - if (news_0_9_9 || showAll) - { - titlesList.Add("Prison Labor Beta v0.9.9"); - string[] itemsArray = - { - "added sub-tabs in \"Work\" Tab and \"Assign\" Tab for \"Colonists\" and \"Prisoners\"", - "added renaming Prisoners for imprisonment time (pawns will restore old names after releasing)", - }; - itemsList.Add(itemsArray); - } - - // 0.9.8 - if (showAll) - { - titlesList.Add("Prison Labor Beta v0.9.8"); - string[] itemsArray = - { - "fixed SeedsPlease compatibility", - }; - itemsList.Add(itemsArray); - } - - // 0.9.7 - if (showAll) - { - titlesList.Add("Prison Labor Beta v0.9.7"); - string[] itemsArray = - { - "added warning message before placing labor area for the first time", - }; - itemsList.Add(itemsArray); - } - - // 0.9.6 - if (showAll) - { - titlesList.Add("Prison Labor Beta v0.9.6"); - string[] itemsArray = - { - "updated to RimWorld 1.0", - }; - itemsList.Add(itemsArray); - } - - // 0.9.5 - if (showAll) - { - titlesList.Add("Prison Labor Beta v0.9.5"); - string[] itemsArray = - { - "updated to RimWorld Beta 19", - }; - itemsList.Add(itemsArray); - } - - // 0.9.4 - if (showAll) - { - titlesList.Add("Prison Labor Beta v0.9.4"); - string[] itemsArray = - { - "disabled Warden and Jailor types of work for prisoner labor, it should fix bug, where jailors do not warden inside labor area", - }; - itemsList.Add(itemsArray); - } - - // 0.9.3 - if (showAll) - { - titlesList.Add("Prison Labor Beta v0.9.3"); - string[] itemsArray = - { - "fixed compatibility with No Water no Life", - "fixed compatibility with Dubs Bad Hygiene Mod", - "fixed error with loading old saves", - }; - itemsList.Add(itemsArray); - } - - // 0.9.2 - if (news_0_9_2 || showAll) - { - titlesList.Add("Prison Labor Beta v0.9.2"); - string[] itemsArray = - { - "fixed seeds please compatibility issue", - "added option to disable revolts", - }; - itemsList.Add(itemsArray); - } - // 0.9.1 - if (news_0_9_1 || showAll) - { - titlesList.Add("Prison Labor Beta v0.9.1"); - string[] itemsArray = - { - "changed max. skill required for non-advanced growing by prisoners to 6 instead of 0", - "added new work type Jailor", - "fixed drawing icons on world map", - "fixed disabling mod from existing saves", - "fixed incorrectly showing \"advanced growing by prisoners\" option", - }; - itemsList.Add(itemsArray); - } - // 0.9.0 - if (news_0_9_0 || showAll) - { - titlesList.Add("Prison Labor Beta v0.9.0"); - string[] itemsArray = - { - "updated to RimWorld beta v18", - "added option to disable icons above prisoners heads in mod menu", - "fixed error \"null reference in onGui()\" when loading save", - }; - itemsList.Add(itemsArray); - } - // 0.8.8 (silent) - if (showAll) - { - titlesList.Add("Prison Labor Beta v0.8.8"); - string[] itemsArray = - { - "changed slow from prisoners chains to act as factor instead offset", - "fixed compatibility issues with Seeds Please(again)", - }; - itemsList.Add(itemsArray); - } - // 0.8.7 (silent) - if (showAll) - { - titlesList.Add("Prison Labor Beta v0.8.7"); - string[] itemsArray = - { - "fixed bug with dropping motivation while in bed", - "prisoners will now get different weapons when revolt triggers (molotovs, bows, or clubs)", - "replaced orginal jobs with \"tweak\" jobs (instead of overriding them, this fix is for users who use \"WorkTab\" by Fluffy)", - "removed warning message from logs", - "prisoners will now have 50% of normal speed in chains (instead of 35%)", - "prisoners will now break chains after some period of time instead of immadiately(matter in incidents, breakouts etc.)", - "wardens will now try to motivate most prisoners at once, but with priority to motivate lowest motivation first", - "fixed bug with animals do not respect reservations (and vice versa)", - }; - itemsList.Add(itemsArray); - } - // 0.8.6 - if (news_0_8_6 || showAll) - { - titlesList.Add("Prison Labor Beta v0.8.6"); - string[] itemsArray = - { - "[img]NewsElement_Locks[/img]Locks mod:\nIf you want to allow prisoners to pass by closed doors, please check out my other mod called Locks", - "[gap]", - "fixed bug that Sowing job do not comply to Labor Area", - "fixed bug with JoyGiver debris (sorry about that)", - "reduced number of null reference errors with OnGui() method (fixed in v 0.8.5)", - "single warden will be able to maintain 7 prisoners, instead of 5 (because of laziness rate reduction) (changed in v 0.8.5)", - "decreased laziness rate to 0.002, instead of 0.003 (prisoners will get lazy 1.5x slower) (changed in v 0.8.5)", - "decreased manipulation to 70% (instead of 80%) (changed in v 0.8.5)", - "fixed null reference exception at loading game (fixed in v 0.8.4)", - }; - itemsList.Add(itemsArray); - } - // 0.8.3 - if (news_0_8_3 || showAll) - { - titlesList.Add("Prison Labor Beta v0.8.3"); - string[] itemsArray = - { - "fixed bugs with disabling mod(now you can safely disable mod again)", - "fixed bug with prioritizing work", - "fixed bug with rendering icons on world map", - }; - itemsList.Add(itemsArray); - } - // 0.8.1 - if (news_0_8_1 || showAll) - { - titlesList.Add("Prison Labor Beta v0.8.1"); - string[] itemsArray = - { - "[subtitle] Sorry for any inconvenience caused by 0.8.0 update. Some part of mod are very vulnerable to any mods installed", - "[subtitle] If you encouter any bugs please report it on github. I'm fixing most important ones every day. This is (recently) beta version and it has to consist some bugs. Thank you for understaning.", - "[subtitle] Also you can always download old version via github, but I think this was last big update", - "re-enabled button in Bills detail panel", - "added slider to Bills (temporary fix)", - "fixed Bill \"Prisoner only\" button (I think, let me know if you still experience errors)", - "fixed prisoners aren't working when Motivation is disabled (via Settings)", - "fixed null-reference error on some revolts incidents", - }; - itemsList.Add(itemsArray); - } - // 0.8.0 - if (news_0_8_0 || showAll) - { - titlesList.Add("Prison Labor Beta v0.8.0"); - string[] itemsArray = - { - "[subtitle]Now in Beta! I would no longer add any more features. Instead I will focus on improving existing ones.", - "[gap]", - "[subtitle]Main changes:", - "[img]NewsElement_Revolt[/img]Revolts:\nPrisoners will now form organized group under self-elected leader if motivation of prisoners is low. They will try to inflict damage to your colony or they will attemp running to elected enemy faction", - "[img]NewsElement_InspirationReworked[/img]Insiration reworked:\nQuicker, better and more intuitive.\nYou can now send your prisoners to work outside your walls, but be carefull: they will try to escape if left alone. Prisoners will start thinking about escape after being left for some time.", - "[img]LaborAreaExpand[/img]Labor area:\nYou can now select area for labor only. Your colonists will no longer go mine with peasants.\nTo access this tool look into \"Architect->Zones\" panel.", - "[img]NewsElement_PrisonersOnly[/img]Prisoners Only button\nGo to Bill details to mark bills for prisoners only!", - "[img]NewsElement_WorkAndRecruit[/img]Work and recruit:\nThis feature has been mostly requested by community. I hope it will be well received.", - "[gap]", - "[subtitle]Other changes:", - "added default prisoner interaction mode option to settings menu", - "added icons above prisoners indicating whenever he's being motivated/inspired", - "reduced manipulation capability of prisoners (now they have 80% of normal manipulation, down from 100%)", - "added tutorials triggers (now all tutorials will be shown)", - "added watched tutorials to properties (tutorials will no longer be shown after reenabling mod)", - "fixed forbidden bug with harvesting plants (again)", - "fixed Toil reservation bug (not respecting prisoners' job)", - "fixed compatibility with Dubs Hygiene Mod", - "fixed SeedsPlease compatibility", - "excluded supervising from labor", - "rewritten news dialog - now with images and stuff", - "perfomance and code improvements", - "translation improvements", - "[gap]", - "[subtitle]Also I want to annouce that I will start new mod called Prison Expansion that would be PrisonExtensions remake.The aim of this mod would be improving Prison Labor experience, especially cell doors and fences.", - }; - itemsList.Add(itemsArray); - } - // 0.7 - if (news_0_7 || showAll) - { - titlesList.Add("Prison Labor Alpha v0.7"); - string[] itemsArray = - { - "Added settings! You can now change almost any aspect of this mod, including:\n * work types\n * motivation mechanics\n * prevention of planting advanced plants.", - "Added \"uninstaller\" (\"disable\" option in settings), which will allow to disable this mod from existing saves.", - "\"No more beeping!\". Changed way of informing player what's going on with prisoners. It should be less annoying and more insightful.", - "Fixed bugs, including bug that prevents prisoners from cleaning and bug that causes warden to stuck in loop of delivering food to prisoner.", - "\"No more watching while prisoner is sleeping.\"Wardens will no longer watch over not working prisoners.", - "Prisoners will now stay in bed while waiting for operation", - "Prisoners will now stop work when starving for default (\"Anything\" time), instead of hungry. They will still get minor debuff.", - }; - itemsList.Add(itemsArray); - } - // 0.6 - if (news_0_6 || showAll) - { - titlesList.Add("Prison Labor Alpha v0.6"); - string[] itemsArray = - { - "Time restrictions - now you can manage your prisoners time for sleep, work and joy. You can now even force them to work when they're hungry!", - "Getting food by prisoners - Now prisoners will look for food in much better way, and now (when they desperate enough) they will eat corpses!", - "\"Laziness\" changed to \"Motivation\" and inverted.\n\n ATTENTION: After PrisonLabor reaches beta all saves with PrisonLabor v0.5a or lower will be corrupted and unplayable. This version (0.6) is safe and converts all older saves.", - }; - itemsList.Add(itemsArray); - } - // 0.5 - if (news_0_5 || showAll) - { - titlesList.Add("Prison Labor Alpha v0.5"); - string[] itemsArray = - { - "Prisoners can now grow, but only plants that not require any skills.", - "You can now manage prisoners work types. Just check \"Work\" tab!", - "Laziness now appear on \"Needs\" tab. Above 50% wardens will watch prisoners. Above 80% prisoners won't work unless supervised.", - "Wardens will now bring food to prisoners that went too far from his bed.", - "Prisoners won't gain laziness when not working anymore.", - "Fixed many bugs", - }; - itemsList.Add(itemsArray); - } - - // If count of items in both lists aren't equal that means someone (me) fucked up - if (titlesList.Count != itemsList.Count) - throw new System.Exception("Prison Labor exception: news lists aren't equal"); - - // Transfer items: dynamic list => static array, for optimalization - titles = new string[titlesList.Count]; - for (int i = 0; i < titlesList.Count; i++) - { - titles[i] = titlesList[i]; - } - items = new string[itemsList.Count][]; - for (int i = 0; i < itemsList.Count; i++) - { - items[i] = itemsList[i]; - } - } - - public static void TryShow() - { - if (autoShow && PrisonLaborPrefs.ShowNews) - { - Find.WindowStack.Add(new NewsDialog()); - PrisonLaborPrefs.LastVersion = PrisonLaborPrefs.Version; - PrisonLaborPrefs.Save(); - autoShow = false; - } - } - - public static void ForceShow() - { - Find.WindowStack.Add(new NewsDialog()); - PrisonLaborPrefs.LastVersion = PrisonLaborPrefs.Version; - PrisonLaborPrefs.Save(); - autoShow = false; - } - - public override void DoWindowContents(Rect inRect) - { - var displayRect = new Rect(inRect.x, inRect.y, inRect.width, inRect.height - 50f); - var viewRect = new Rect(0, 0, inRect.width - 16f, CalculateHeight(inRect.width - 16f)); - - Widgets.BeginScrollView(displayRect, ref position, viewRect, true); - - for (int i = 0; i < titles.Length; i++) - { - // Draw title - Text.Font = TitleFont; - Widgets.Label(viewRect, titles[i]); - viewRect.y += Text.CalcHeight(titles[i], viewRect.width) + Spacing; - - // Draw line gap - Color color = GUI.color; - GUI.color = GUI.color * new Color(1f, 1f, 1f, 0.4f); - Widgets.DrawLineHorizontal(viewRect.x, viewRect.y + +GapHeight * 0.5f, viewRect.width); - GUI.color = color; - viewRect.y += GapHeight; - - // Draw items - Text.Font = ItemFont; - for (int j = 0; j < items[i].Length; j++) - { - // Draw Image with Text - if (items[i][j].StartsWith("[img]")) - { - int imgLength = items[i][j].IndexOf("[/img]"); - var imageString = items[i][j].Substring(5, imgLength - 5); - var textToDraw = items[i][j].Substring(imgLength + 6); - - var content = new GUIContent(); - content.image = ContentFinder.Get(imageString, false); - content.text = textToDraw; - Widgets.Label(viewRect, content); - - viewRect.y += GuiStyle(Text.Font).CalcHeight(content, viewRect.width); - } - // Draw Gap - else if (items[i][j].StartsWith("[gap]")) - { - color = GUI.color; - GUI.color = GUI.color * new Color(1f, 1f, 1f, 0.4f); - Widgets.DrawLineHorizontal(viewRect.x, viewRect.y + +GapHeight * 0.5f, viewRect.width); - GUI.color = color; - viewRect.y += GapHeight; - } - // Draw Subtitle (without margin) - else if (items[i][j].StartsWith("[subtitle]")) - { - Widgets.Label(viewRect, items[i][j].Substring(10)); - viewRect.y += Text.CalcHeight(items[i][j], viewRect.width) + Spacing; - } - // Draw Text - else - { - viewRect.width -= MarginWidth; - Widgets.Label(viewRect, MarginText); - viewRect.x += MarginWidth; - Widgets.Label(viewRect, items[i][j]); - viewRect.x -= MarginWidth; - viewRect.y += Text.CalcHeight(items[i][j], viewRect.width) + Spacing; - viewRect.width += MarginWidth; - } - } - - // Make gap - viewRect.y += GapHeight; - } - - Widgets.EndScrollView(); - } - - private float CalculateHeight(float width) - { - float height = 0; - Text.Font = TitleFont; - foreach (var item in titles) - { - height += Text.CalcHeight(item, width) + Spacing + GapHeight; - } - Text.Font = ItemFont; - foreach (var array in items) - foreach (var item in array) - { - // Image with Text - if (item.StartsWith("[img]")) - { - int imgLength = item.IndexOf("[/img]"); - var imageString = item.Substring(5, imgLength - 5); - var textToDraw = item.Substring(imgLength + 6); - - var content = new GUIContent(); - content.image = ContentFinder.Get(imageString, false); - content.text = textToDraw; - - - height += GuiStyle(Text.Font).CalcHeight(content, width); - } - // Gap - else if (item.StartsWith("[gap]")) - { - height += GapHeight; - } - else if (item.StartsWith("[subtitle]")) - { - height += Text.CalcHeight(item, width) + Spacing; - } - // Only Text - else - { - height += Text.CalcHeight(item, width - MarginWidth) + Spacing; - } - } - return height; - } - - private static GUIStyle GuiStyle(GameFont font) - { - GUIStyle gUIStyle; - switch (font) - { - case GameFont.Tiny: - gUIStyle = Text.fontStyles[0]; - break; - case GameFont.Small: - gUIStyle = Text.fontStyles[1]; - break; - case GameFont.Medium: - gUIStyle = Text.fontStyles[2]; - break; - default: - return null; - } - gUIStyle.alignment = Text.Anchor; - gUIStyle.wordWrap = Text.WordWrap; - return gUIStyle; - } - - } -} \ No newline at end of file diff --git a/Source/Organizer/NewsFeed.xml b/Source/Organizer/NewsFeed.xml new file mode 100644 index 00000000..e0dcd108 --- /dev/null +++ b/Source/Organizer/NewsFeed.xml @@ -0,0 +1,254 @@ + + + + + + + + Prison Labor v0.10.1 + + This patch contains a lot of changes, so there can be bugs. Please [b]report bugs[/b] so I can repair them ASAP + [gap] + + [b]Treatment system[/b] + [-]added new hidden need "Treatment" that indicates level of prison treatment towards prisoner + [-]prisoners will now give offer to join colony if treatment is good enough (random) + [-]prisoners now will pick up weapons if treated bad + [-]added +5 bonus to mood while prisoner have free time + [-]added +5 bonus to mood if prisoner is not supervised and got lazy + [-]added +15 bonus to mood if treatment is above 75% + [-]added prisoner suicides + [-]added blocking revolts (100%, 95%, 50%, 10%) if overall prisoner treatment is good enough + [-]added treatment drop when prisoner is being beaten + [-]increased base chance for Revolts + [-]added blocking mental breaks for prisoners with low treatment levels + [-]"Treatment happiness" will decrease if health conditions are bad, when prisoners are hungry, or they're working. + + [b]Prisoners can do construction jobs now[/b] + + [b]Enchantments[/b] + [-]new system for removing mod from save (new button in mod menu) + [-]prisoners will now seek safe temperature when not supervised + [-]prisoners will respect forbidden items, if "inspired" + [-]prisoners will now work in cold only if "work" time is set + [-]added alert when prisoners can escape + [-]reworked news popup window + [-]wardens no longer deliver food if prisoners can get it from another room + [-]cosmetic changes to bill checkbox + [-]prisoners will now stay at bed if waiting for surgery + + [b]Bugs[/b] + [-]fixed food reservation throwing errors + [-]fixed cutting some content of bill config in some languages + [-]now work settings reset after prisoner is recruited, so it should fix some issues + [-]finally fixed "OnGui()" error, big thanks to @notfood (https://github.com/notfood) + [-]fixed blurred effect on settings window + [-]fixed Revolts + + [gap] + + If you want to support the work I do, please consider small donation. Link to Ko-fi will be placed at steam page. + + + + Prison Labor Beta v0.9.11 + + [-]fixed compatibility with Fluffy's WorkTab (final) + + + + Prison Labor Beta v0.9.10 + + [-]hotfixed compatibility with Fluffy's WorkTab (still have some visual flaws) + + + + Prison Labor Beta v0.9.9 + + [-]added sub-tabs in \"Work\" Tab and \"Assign\" Tab for \"Colonists\" and \"Prisoners\" + [-]added renaming Prisoners for imprisonment time (pawns will restore old names after releasing) + + + + Prison Labor Beta v0.9.8 + + [-]fixed SeedsPlease compatibility + + + + Prison Labor Beta v0.9.7 + + [-]added warning message before placing labor area for the first time + + + + Prison Labor Beta v0.9.6 + + [-]updated to RimWorld 1.0 + + + + Prison Labor Beta v0.9.5 + + [-]updated to RimWorld Beta 19 + + + + Prison Labor Beta v0.9.4 + + [-]disabled Warden and Jailor types of work for prisoner labor, it should fix bug, where jailors do not warden inside labor area + + + + Prison Labor Beta v0.9.3 + + [-]fixed compatibility with No Water no Life + [-]fixed compatibility with Dubs Bad Hygiene Mod + [-]fixed error with loading old saves + + + + Prison Labor Beta v0.9.2 + + [-]fixed seeds please compatibility issue + [-]added option to disable revolts + + + + Prison Labor Beta v0.9.1 + + [-]changed max. skill required for non-advanced growing by prisoners to 6 instead of 0 + [-]added new work type Jailor + [-]fixed drawing icons on world map + [-]fixed disabling mod from existing saves + [-]fixed incorrectly showing \"advanced growing by prisoners\" option + + + + Prison Labor Beta v0.9.0 + + [-]updated to RimWorld beta v18 + [-]added option to disable icons above prisoners heads in mod menu + [-]fixed error \"null reference in onGui()\" when loading save + + + + Prison Labor Beta v0.8.8 + + [-]changed slow from prisoners chains to act as factor instead offset + [-]fixed compatibility issues with Seeds Please(again) + + + + Prison Labor Beta v0.8.7 + + [-]fixed bug with dropping motivation while in bed + [-]prisoners will now get different weapons when revolt triggers (molotovs, bows, or clubs) + [-]replaced orginal jobs with \"tweak\" jobs (instead of overriding them, this fix is for users who use \"WorkTab\" by Fluffy) + [-]removed warning message from logs + [-]prisoners will now have 50% of normal speed in chains (instead of 35%) + [-]prisoners will now break chains after some period of time instead of immadiately(matter in incidents, breakouts etc.) + [-]wardens will now try to motivate most prisoners at once, but with priority to motivate lowest motivation first + [-]fixed bug with animals do not respect reservations (and vice versa) + + + + Prison Labor Beta v0.8.6 + + [img]NewsElement_Locks[/img][b]Locks mod:[/b]\nIf you want to allow prisoners to pass by closed doors, please check out my other mod called [b]Locks[/b] + [gap] + [-]fixed bug that Sowing job do not comply to Labor Area + [-]fixed bug with JoyGiver debris (sorry about that) + [-]reduced number of null reference errors with OnGui() method (fixed in v 0.8.5) + [-]single warden will be able to maintain 7 prisoners, instead of 5 (because of laziness rate reduction) (changed in v 0.8.5) + [-]decreased laziness rate to 0.002, instead of 0.003 (prisoners will get lazy 1.5x slower) (changed in v 0.8.5) + [-]decreased manipulation to 70% (instead of 80%) (changed in v 0.8.5) + [-]fixed null reference exception at loading game (fixed in v 0.8.4) + + + + Prison Labor Beta v0.8.3 + + [-]fixed bugs with disabling mod(now you can safely disable mod again) + [-]fixed bug with prioritizing work + [-]fixed bug with rendering icons on world map + + + + Prison Labor Beta v0.8.1 + + [-]fixed bug with rendering icons on world map + [subtitle] If you encouter any bugs [b]please report it on github[/b]. I'm fixing most important ones every day. This is (recently) beta version and it has to consist some bugs. Thank you for understaning. + [subtitle] Also you can always [b]download old version[/b] via github, but I think this was last big update + [-]re-enabled button in Bills detail panel + [-]added slider to Bills (temporary fix) + [-]fixed Bill \"Prisoner only\" button (I think, let me know if you still experience errors) + [-]fixed prisoners aren't working when Motivation is disabled (via Settings) + [-]fixed null-reference error on some revolts incidents + + + + Prison Labor Beta v0.8.0 + + [subtitle][b]Now in Beta![/b] I would no longer add any more features. Instead I will focus on improving existing ones. + [gap] + [subtitle][b]Main changes:[/b] + [img]NewsElement_Revolt[/img][b]Revolts:[/b]\nPrisoners will now form organized group under self-elected leader if motivation of prisoners is low. They will try to inflict damage to your colony or they will attemp running to elected enemy faction + [img]NewsElement_InspirationReworked[/img][b]Insiration reworked:[/b]\nQuicker, better and more intuitive.\nYou can now send your prisoners to work outside your walls, but be carefull: they will try to escape if left alone. Prisoners will start thinking about escape after being left for some time. + [img]LaborAreaExpand[/img][b]Labor area:[/b]\nYou can now select area for labor only. Your colonists will no longer go mine with peasants.\nTo access this tool look into \"Architect->Zones\" panel. + [img]NewsElement_PrisonersOnly[/img][b]Prisoners Only button[/b]\nGo to Bill details to mark bills for prisoners only! + [img]NewsElement_WorkAndRecruit[/img][b]Work and recruit:[/b]\nThis feature has been mostly requested by community. I hope it will be well received. + [gap] + [subtitle][b]Other changes:[/b] + [-]added default prisoner interaction mode option to settings menu + [-]added icons above prisoners indicating whenever he's being motivated/inspired + [-]reduced manipulation capability of prisoners (now they have 80% of normal manipulation, down from 100%) + [-]added tutorials triggers (now all tutorials will be shown) + [-]added watched tutorials to properties (tutorials will no longer be shown after reenabling mod) + [-]fixed forbidden bug with harvesting plants (again) + [-]fixed Toil reservation bug (not respecting prisoners' job) + [-]fixed compatibility with Dubs Hygiene Mod + [-]fixed SeedsPlease compatibility + [-]excluded supervising from labor + [-]rewritten news dialog - now with images and stuff + [-]perfomance and code improvements + [-]translation improvements + [gap] + [subtitle]Also I want to annouce that I will start new mod called [b]Prison Expansion[/b] that would be PrisonExtensions remake.The aim of this mod would be improving Prison Labor experience, especially cell doors and fences. + + + + Prison Labor Alpha v0.7 + + [-]Added settings! You can now change almost any aspect of this mod, including:\n * work types\n * motivation mechanics\n * prevention of planting advanced plants. + [-]Added \"uninstaller\" (\"disable\" option in settings), which will allow to disable this mod from existing saves. + [-]\"No more beeping!\". Changed way of informing player what's going on with prisoners. It should be less annoying and more insightful. + [-]Fixed bugs, including bug that prevents prisoners from cleaning and bug that causes warden to stuck in loop of delivering food to prisoner. + [-]\"No more watching while prisoner is sleeping.\"Wardens will no longer watch over not working prisoners. + [-]Prisoners will now stay in bed while waiting for operation + [-]Prisoners will now stop work when starving for default (\"Anything\" time), instead of hungry. They will still get minor debuff. + + + + Prison Labor Alpha v0.6 + + [-]Time restrictions - now you can manage your prisoners time for sleep, work and joy. You can now even force them to work when they're hungry! + [-]Getting food by prisoners - Now prisoners will look for food in much better way, and now (when they desperate enough) they will eat corpses! + [-]\"Laziness\" changed to \"Motivation\" and inverted.\n\n ATTENTION: After PrisonLabor reaches beta all saves with PrisonLabor v0.5a or lower will be corrupted and unplayable. This version (0.6) is safe and converts all older saves. + + + + Prison Labor Alpha v0.5 + + [-]Prisoners can now grow, but only plants that not require any skills. + [-]You can now manage prisoners work types. Just check \"Work\" tab! + [-]Laziness now appear on \"Needs\" tab. Above 50% wardens will watch prisoners. Above 80% prisoners won't work unless supervised. + [-]Wardens will now bring food to prisoners that went too far from his bed. + [-]Prisoners won't gain laziness when not working anymore. + [-]Fixed many bugs + + + + + + diff --git a/Source/Organizer/TutorialFeed.xml b/Source/Organizer/TutorialFeed.xml new file mode 100644 index 00000000..cd96edf7 --- /dev/null +++ b/Source/Organizer/TutorialFeed.xml @@ -0,0 +1,44 @@ + + + + + + + + + [title]Prison Labor Mod - Introduction + Are you new to Prison Labor Mod? Don't worry, in this tutorial you will learn how to use this mod. + The main goal of this mod is to make prisoners work, but managing prisoners is challenge on it's own, so let's go with tutorial ... + [title]Enabling labor for prisoners + There is two main things you need to do before prisoners starts working: + [img]Tutorials/ForceToWork[/img]1. Firstly you need to enable labor in Prisoner tab + [gap] + [img]Tutorials/WorkTab[/img]2. Then you need to adjust work priorities in work tab. There is a special sub-tab in top of Work tab + There is limited number of available jobs for prisoners. Additionaly you can change that in setting menu, but not all jobs are well implemented and you can experience weird behaviour by doing so. + [title]Motivation + Prisoners are not colonists and they do not want to work on their own. You need to motivate them and watch them constantly. + [img]Tutorials/InspirationPreview[/img]To watch prisoners colonists must be within 10-meters radius around prisoner (and vice-versa). Prisoners that are nearby colonists are working and gaining motivation. Additionaly prisoners do not escape if colonists is nearby, but they can take a chance when nobody is watching them. It is worth noting that watching prisoners need colonist and prisoner to be in same room. + Watching prisoners can be done manually (drafting colonists) or by enabling new work type Jailor. + [title]Desiging prison encampment + There is few thing that you should keep in mind while building prison: + [-]Walls: Prisoners want to escape, so make sure they cannot open doors to open spaces. + [-]Accessing resources: If you want prisoners to refine some materials like cooking, make sure they can access those materials like meat and potatoes. + [-]Accessing food: Prisoners should have access to food, like fridge or food stockpiles. In Prison Labor mod, prisoners can take care of themself and go for a food un if you let them + [-]Labor Areas: Those areas are optional, but in some cases you want to keep colonists away from working in prison. That where Labor area comes, it prevents colonists from working inside this area + [title]Icons + [img]Tutorials/Icons[/img]There is three icons in mod. Inspiration icon (blue one) - that indicates whenever prisoner is motivated by standing colonist, Lazy icon (red one) - that indicates whenever prisoner is to lazy to work, Freezing Icon - that indicates prisoner is working despite freezing temperature + [title]Locks mod + [img]NewsElement_Locks[/img]I highly recommend using Locks mod. It allows you to make doors passable by prisoners without making them force to open. + If you have any questions please visit FAQ first + + + + + [title]Labor areas + Labor area isn't required to make prisoners work. + This area forbids colonists from working. + It's for situations where you don't want colonists to work in certain area. Prisoners will work anywhere they can enter. + + + + \ No newline at end of file diff --git a/Source/PrisonLabor.csproj b/Source/PrisonLabor.csproj index f2c84f8a..d6b343cd 100644 --- a/Source/PrisonLabor.csproj +++ b/Source/PrisonLabor.csproj @@ -51,12 +51,15 @@ - - - - - - + + + + + + + + + @@ -64,65 +67,97 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + - - - - + + + + - - + + @@ -130,13 +165,16 @@ + + - - - - + + + + + @@ -151,6 +189,7 @@ Defs\Hediffs.xml + Designer Defs\Incidents.xml @@ -167,6 +206,9 @@ Defs\ThinkTreeDef.xml + + Defs\ThoughtsDef.xml + Defs\WorkGiverDef.xml @@ -180,6 +222,12 @@ Organizer\TaskList.txt + + Designer + + + Designer + @@ -188,6 +236,18 @@ Organizer\README.md + + Organizer\To-Do.md + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\..\changelog.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;windows-1250 + + + ..\Organizer\NewsFeed.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + ..\organizer\tutorialfeed.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + \ No newline at end of file diff --git a/Source/Tweaks/ClassInjector.cs b/Source/Tweaks/ClassInjector.cs index d49421d8..c7d39919 100644 --- a/Source/Tweaks/ClassInjector.cs +++ b/Source/Tweaks/ClassInjector.cs @@ -1,4 +1,5 @@ -using RimWorld; +using PrisonLabor.Constants; +using RimWorld; using System; using System.Collections.Generic; using System.Linq; @@ -31,10 +32,6 @@ private static void UITweaks() private static void JobTweaks() { - // Deliver food to prisoners (include other rooms etc.) - var deliverFoodWorkGiver = DefDatabase.GetNamed("DeliverFoodToPrisoner"); - deliverFoodWorkGiver.giverClass = typeof(WorkGiver_Warden_DeliverFood_Tweak); - // Mine var minerJob = JobDefOf.Mine; minerJob.driverClass = typeof(JobDriver_Mine_Tweak); @@ -61,15 +58,15 @@ private static void JobTweaks() private static void SplitWardenType() { - DefDatabase.GetNamed("DoExecution").workType = PrisonLaborDefOf.PrisonLabor_Jailor; - DefDatabase.GetNamed("ReleasePrisoner").workType = PrisonLaborDefOf.PrisonLabor_Jailor; - DefDatabase.GetNamed("TakePrisonerToBed").workType = PrisonLaborDefOf.PrisonLabor_Jailor; + DefDatabase.GetNamed("DoExecution").workType = PL_DefOf.PrisonLabor_Jailor; + DefDatabase.GetNamed("ReleasePrisoner").workType = PL_DefOf.PrisonLabor_Jailor; + DefDatabase.GetNamed("TakePrisonerToBed").workType = PL_DefOf.PrisonLabor_Jailor; //DefDatabase.GetNamed("FeedPrisoner").workType = PrisonLaborDefOf.PrisonLabor_Jailor; //DefDatabase.GetNamed("DeliverFoodToPrisoner").workType = PrisonLaborDefOf.PrisonLabor_Jailor; WorkTypeDefOf.Warden.workGiversByPriority.Clear(); WorkTypeDefOf.Warden.ResolveReferences(); - PrisonLaborDefOf.PrisonLabor_Jailor.workGiversByPriority.Clear(); - PrisonLaborDefOf.PrisonLabor_Jailor.ResolveReferences(); + PL_DefOf.PrisonLabor_Jailor.workGiversByPriority.Clear(); + PL_DefOf.PrisonLabor_Jailor.ResolveReferences(); } } } diff --git a/Source/Tweaks/FoodUtility_Tweak.cs b/Source/Tweaks/FoodUtility_Tweak.cs deleted file mode 100644 index 05c04ff6..00000000 --- a/Source/Tweaks/FoodUtility_Tweak.cs +++ /dev/null @@ -1,583 +0,0 @@ -using System; -using System.Collections.Generic; -using RimWorld; -using UnityEngine; -using Verse; -using Verse.AI; - -namespace PrisonLabor -{ - public static class FoodUtility_Tweak - { - private static readonly HashSet filtered = new HashSet(); - - private static readonly SimpleCurve FoodOptimalityEffectFromMoodCurve = new SimpleCurve - { - { - new CurvePoint(-100f, -600f), - true - }, - { - new CurvePoint(-10f, -100f), - true - }, - { - new CurvePoint(-5f, -70f), - true - }, - { - new CurvePoint(-1f, -50f), - true - }, - { - new CurvePoint(0f, 0f), - true - }, - { - new CurvePoint(100f, 800f), - true - } - }; - - private static readonly List ingestThoughts = new List(); - - public static bool TryFindBestFoodSourceFor(Pawn getter, Pawn eater, bool desperate, out Thing foodSource, - out ThingDef foodDef, bool canRefillDispenser = true, bool canUseInventory = true, - bool allowForbidden = false, bool allowCorpse = true, bool allowSociallyImproper = false) - { - var flag = getter.RaceProps.ToolUser && getter.health.capacities.CapableOf(PawnCapacityDefOf.Manipulation); - var allowDrug = !eater.IsTeetotaler(); - Thing thing = null; - if (canUseInventory) - { - if (flag) - thing = BestFoodInInventory(getter, null, FoodPreferability.MealAwful, FoodPreferability.MealLavish, - 0f, false); - if (thing != null) - { - if (getter.Faction != Faction.OfPlayer) - { - foodSource = thing; - foodDef = GetFinalIngestibleDef(foodSource); - return true; - } - var compRottable = thing.TryGetComp(); - if (compRottable != null && compRottable.Stage == RotStage.Fresh && - compRottable.TicksUntilRotAtCurrentTemp < 30000) - { - foodSource = thing; - foodDef = GetFinalIngestibleDef(foodSource); - return true; - } - } - } - var allowPlant = getter == eater; - var thing2 = BestFoodSourceOnMap(getter, eater, desperate, FoodPreferability.MealLavish, allowPlant, - allowDrug, allowCorpse, true, canRefillDispenser, allowForbidden, allowSociallyImproper); - if (thing == null && thing2 == null) - { - if (canUseInventory && flag) - { - thing = BestFoodInInventory(getter, null, FoodPreferability.DesperateOnly, - FoodPreferability.MealLavish, 0f, allowDrug); - if (thing != null) - { - foodSource = thing; - foodDef = GetFinalIngestibleDef(foodSource); - return true; - } - } - if (thing2 == null && getter == eater && getter.RaceProps.predator) - { - var pawn = BestPawnToHuntForPredator(getter); - if (pawn != null) - { - foodSource = pawn; - foodDef = GetFinalIngestibleDef(foodSource); - return true; - } - } - foodSource = null; - foodDef = null; - return false; - } - if (thing == null && thing2 != null) - { - foodSource = thing2; - foodDef = GetFinalIngestibleDef(foodSource); - return true; - } - if (thing2 == null && thing != null) - { - foodSource = thing; - foodDef = GetFinalIngestibleDef(foodSource); - return true; - } - var num = FoodSourceOptimality(eater, thing2, (getter.Position - thing2.Position).LengthManhattan, false); - var num2 = FoodSourceOptimality(eater, thing, 0f, false); - num2 -= 32f; - if (num > num2) - { - foodSource = thing2; - foodDef = GetFinalIngestibleDef(foodSource); - return true; - } - foodSource = thing; - foodDef = GetFinalIngestibleDef(foodSource); - return true; - } - - public static ThingDef GetFinalIngestibleDef(Thing foodSource) - { - var building_NutrientPasteDispenser = foodSource as Building_NutrientPasteDispenser; - if (building_NutrientPasteDispenser != null) - return building_NutrientPasteDispenser.DispensableDef; - var pawn = foodSource as Pawn; - if (pawn != null) - return pawn.RaceProps.corpseDef; - return foodSource.def; - } - - public static Thing BestFoodInInventory(Pawn holder, Pawn eater = null, - FoodPreferability minFoodPref = FoodPreferability.NeverForNutrition, - FoodPreferability maxFoodPref = FoodPreferability.MealLavish, float minStackNutrition = 0f, - bool allowDrug = false) - { - if (holder.inventory == null) - return null; - if (eater == null) - eater = holder; - var innerContainer = holder.inventory.innerContainer; - for (var i = 0; i < innerContainer.Count; i++) - { - var thing = innerContainer[i]; - if (thing.def.IsNutritionGivingIngestible && thing.IngestibleNow && eater.RaceProps.CanEverEat(thing) && - thing.def.ingestible.preferability >= minFoodPref && - thing.def.ingestible.preferability <= maxFoodPref && (allowDrug || !thing.def.IsDrug)) - { - var num = thing.def.ingestible.CachedNutrition * thing.stackCount; - if (num >= minStackNutrition) - return thing; - } - } - return null; - } - - public static Thing BestFoodSourceOnMap(Pawn getter, Pawn eater, bool desperate, - FoodPreferability maxPref = FoodPreferability.MealLavish, bool allowPlant = true, bool allowDrug = true, - bool allowCorpse = true, bool allowDispenserFull = true, bool allowDispenserEmpty = true, - bool allowForbidden = false, bool allowSociallyImproper = false) - { - var getterCanManipulate = getter.RaceProps.ToolUser && - getter.health.capacities.CapableOf(PawnCapacityDefOf.Manipulation); - if (!getterCanManipulate && getter != eater) - { - Log.Error(string.Concat(getter, " tried to find food to bring to ", eater, " but ", getter, - " is incapable of Manipulation.")); - return null; - } - FoodPreferability minPref; - if (!eater.RaceProps.Humanlike || eater == getter && eater.IsPrisoner) - minPref = FoodPreferability.NeverForNutrition; - else if (desperate) - minPref = FoodPreferability.DesperateOnly; - else - minPref = eater.needs.food.CurCategory <= HungerCategory.UrgentlyHungry - ? FoodPreferability.RawBad - : FoodPreferability.MealAwful; - Predicate foodValidator = delegate(Thing t) - { - if (PrisonerFoodReservation.isReserved(t) && (eater != getter || !eater.IsPrisoner) && !desperate) - return false; - if (!allowForbidden && t.IsForbidden(getter)) - return false; - var building_NutrientPasteDispenser = t as Building_NutrientPasteDispenser; - if (building_NutrientPasteDispenser != null) - { - if (!allowDispenserFull || ThingDefOf.MealNutrientPaste.ingestible.preferability < minPref || - ThingDefOf.MealNutrientPaste.ingestible.preferability > maxPref || !getterCanManipulate || - t.Faction != getter.Faction && t.Faction != getter.HostFaction || - !building_NutrientPasteDispenser.powerComp.PowerOn || - !allowDispenserEmpty && !building_NutrientPasteDispenser.HasEnoughFeedstockInHoppers() || - !IsFoodSourceOnMapSociallyProper(t, getter, eater, allowSociallyImproper) || - !t.InteractionCell.Standable(t.Map) || !getter.Map.reachability.CanReachNonLocal( - getter.Position, new TargetInfo(t.InteractionCell, t.Map, false), PathEndMode.OnCell, - TraverseParms.For(getter, Danger.Some, TraverseMode.ByPawn, false))) - return false; - } - else - { - if (t.def.ingestible.preferability < minPref) - return false; - if (t.def.ingestible.preferability > maxPref) - return false; - if (!t.IngestibleNow || !t.def.IsNutritionGivingIngestible || !allowCorpse && t is Corpse || - !allowDrug && t.def.IsDrug || !desperate && t.IsNotFresh() || t.IsDessicated() || - !eater.WillEat(t) || - !IsFoodSourceOnMapSociallyProper(t, getter, eater, allowSociallyImproper) || - !getter.AnimalAwareOf(t) || !getter.CanReserve(t, 1, -1, null, false)) - return false; - } - return true; - }; - ThingRequest thingRequest; - if ((eater.RaceProps.foodType & (FoodTypeFlags.Plant | FoodTypeFlags.Tree)) != FoodTypeFlags.None && - allowPlant) - thingRequest = ThingRequest.ForGroup(ThingRequestGroup.FoodSource); - else - thingRequest = ThingRequest.ForGroup(ThingRequestGroup.FoodSourceNotPlantOrTree); - Thing thing; - if (getter.RaceProps.Humanlike) - { - var validator = foodValidator; - thing = SpawnedFoodSearchInnerScan(eater, getter.Position, - getter.Map.listerThings.ThingsMatching(thingRequest), PathEndMode.ClosestTouch, - TraverseParms.For(getter, Danger.Deadly, TraverseMode.ByPawn, false), 9999f, validator); - } - else - { - var searchRegionsMax = 30; - if (getter.Faction == Faction.OfPlayer) - searchRegionsMax = 100; - filtered.Clear(); - foreach (var current in GenRadial.RadialDistinctThingsAround(getter.Position, getter.Map, 2f, true)) - { - var pawn = current as Pawn; - if (pawn != null && pawn != getter && pawn.RaceProps.Animal && pawn.CurJob != null && - pawn.CurJob.def == JobDefOf.Ingest && pawn.CurJob.GetTarget(TargetIndex.A).HasThing) - filtered.Add(pawn.CurJob.GetTarget(TargetIndex.A).Thing); - } - var flag = !allowForbidden && ForbidUtility.CaresAboutForbidden(getter, true) && - getter.playerSettings != null && - getter.playerSettings.EffectiveAreaRestrictionInPawnCurrentMap != null; - Predicate predicate = t => - foodValidator(t) && !filtered.Contains(t) && - t.def.ingestible.preferability > FoodPreferability.DesperateOnly && !t.IsNotFresh(); - var validator = predicate; - var ignoreEntirelyForbiddenRegions = flag; - thing = GenClosest.ClosestThingReachable(getter.Position, getter.Map, thingRequest, - PathEndMode.ClosestTouch, TraverseParms.For(getter, Danger.Deadly, TraverseMode.ByPawn, false), - 9999f, validator, null, 0, searchRegionsMax, false, RegionType.Set_Passable, - ignoreEntirelyForbiddenRegions); - filtered.Clear(); - if (thing == null) - { - desperate = true; - validator = foodValidator; - ignoreEntirelyForbiddenRegions = flag; - thing = GenClosest.ClosestThingReachable(getter.Position, getter.Map, thingRequest, - PathEndMode.ClosestTouch, TraverseParms.For(getter, Danger.Deadly, TraverseMode.ByPawn, false), - 9999f, validator, null, 0, searchRegionsMax, false, RegionType.Set_Passable, - ignoreEntirelyForbiddenRegions); - } - } - return thing; - } - - private static bool IsFoodSourceOnMapSociallyProper(Thing t, Pawn getter, Pawn eater, - bool allowSociallyImproper) - { - if (!allowSociallyImproper) - { - var animalsCare = !getter.RaceProps.Animal; - if (!t.IsSociallyProper(getter) && !t.IsSociallyProper(eater, false, animalsCare)) - return false; - } - return true; - } - - public static float FoodSourceOptimality(Pawn eater, Thing t, float dist, bool takingToInventory = false) - { - var num = 300f; - num -= dist; - var thingDef = !(t is Building_NutrientPasteDispenser) ? t.def : ThingDefOf.MealNutrientPaste; - var preferability = thingDef.ingestible.preferability; - if (preferability != FoodPreferability.NeverForNutrition) - { - if (preferability == FoodPreferability.DesperateOnly) - num -= 150f; - var compRottable = t.TryGetComp(); - if (compRottable != null) - { - if (compRottable.Stage == RotStage.Dessicated) - return -9999999f; - if (!takingToInventory && compRottable.Stage == RotStage.Fresh && - compRottable.TicksUntilRotAtCurrentTemp < 30000) - num += 12f; - } - if (eater.needs != null && eater.needs.mood != null) - { - var list = ThoughtsFromIngesting(eater, t); - for (var i = 0; i < list.Count; i++) - num += FoodOptimalityEffectFromMoodCurve.Evaluate(list[i].stages[0].baseMoodEffect); - } - if (thingDef.ingestible != null) - if (eater.RaceProps.Humanlike) - { - num += thingDef.ingestible.optimalityOffsetHumanlikes; - } - else if (eater.RaceProps.Animal) - { - num += thingDef.ingestible.optimalityOffsetFeedingAnimals; - } - return num; - } - return -9999999f; - } - - private static Thing SpawnedFoodSearchInnerScan(Pawn eater, IntVec3 root, List searchSet, - PathEndMode peMode, TraverseParms traverseParams, float maxDistance = 9999f, - Predicate validator = null) - { - if (searchSet == null) - return null; - var pawn = traverseParams.pawn ?? eater; - var num = 0; - var num2 = 0; - Thing result = null; - var num3 = -3.40282347E+38f; - for (var i = 0; i < searchSet.Count; i++) - { - var thing = searchSet[i]; - num2++; - float num4 = (root - thing.Position).LengthManhattan; - if (num4 <= maxDistance) - { - var num5 = FoodSourceOptimality(eater, thing, num4, false); - if (num5 >= num3) - if (pawn.Map.reachability.CanReach(root, thing, peMode, traverseParams)) - if (thing.Spawned) - if (validator == null || validator(thing)) - { - result = thing; - num3 = num5; - num++; - } - } - } - return result; - } - - public static void DebugFoodSearchFromMouse_Update() - { - var root = UI.MouseCell(); - var pawn = Find.Selector.SingleSelectedThing as Pawn; - if (pawn == null) - return; - if (pawn.Map != Find.CurrentMap) - return; - var thing = SpawnedFoodSearchInnerScan(pawn, root, - Find.CurrentMap.listerThings.ThingsInGroup(ThingRequestGroup.FoodSourceNotPlantOrTree), - PathEndMode.ClosestTouch, TraverseParms.For(TraverseMode.PassDoors, Danger.Deadly, false), 9999f, null); - if (thing != null) - GenDraw.DrawLineBetween(root.ToVector3Shifted(), thing.Position.ToVector3Shifted()); - } - - public static void DebugFoodSearchFromMouse_OnGUI() - { - var a = UI.MouseCell(); - var pawn = Find.Selector.SingleSelectedThing as Pawn; - if (pawn == null) - return; - if (pawn.Map != Find.CurrentMap) - return; - Text.Anchor = TextAnchor.MiddleCenter; - Text.Font = GameFont.Tiny; - foreach (var current in Find.CurrentMap.listerThings.ThingsInGroup(ThingRequestGroup - .FoodSourceNotPlantOrTree)) - { - var num = FoodSourceOptimality(pawn, current, (a - current.Position).LengthHorizontal, false); - var vector = current.DrawPos.MapToUIPosition(); - var rect = new Rect(vector.x - 100f, vector.y - 100f, 200f, 200f); - var text = num.ToString("F0"); - var list = ThoughtsFromIngesting(pawn, current); - for (var i = 0; i < list.Count; i++) - { - var text2 = text; - text = string.Concat(text2, "\n", list[i].defName, "(", - FoodOptimalityEffectFromMoodCurve.Evaluate(list[i].stages[0].baseMoodEffect).ToString("F0"), - ")"); - } - Widgets.Label(rect, text); - } - Text.Anchor = TextAnchor.UpperLeft; - } - - private static Pawn BestPawnToHuntForPredator(Pawn predator) - { - if (predator.meleeVerbs.TryGetMeleeVerb(null) == null) - return null; - var flag = false; - var summaryHealthPercent = predator.health.summaryHealth.SummaryHealthPercent; - if (summaryHealthPercent < 0.25f) - flag = true; - var allPawnsSpawned = predator.Map.mapPawns.AllPawnsSpawned; - Pawn pawn = null; - var num = 0f; - var tutorialMode = TutorSystem.TutorialMode; - for (var i = 0; i < allPawnsSpawned.Count; i++) - { - var pawn2 = allPawnsSpawned[i]; - if (predator.GetRoom(RegionType.Set_Passable) == pawn2.GetRoom(RegionType.Set_Passable)) - if (predator != pawn2) - if (!flag || pawn2.Downed) - if (IsAcceptablePreyFor(predator, pawn2)) - if (predator.CanReach(pawn2, PathEndMode.ClosestTouch, Danger.Deadly, false, - TraverseMode.ByPawn)) - if (!pawn2.IsForbidden(predator)) - if (!tutorialMode || pawn2.Faction != Faction.OfPlayer) - { - var preyScoreFor = GetPreyScoreFor(predator, pawn2); - if (preyScoreFor > num || pawn == null) - { - num = preyScoreFor; - pawn = pawn2; - } - } - } - return pawn; - } - - public static bool IsAcceptablePreyFor(Pawn predator, Pawn prey) - { - if (!prey.RaceProps.canBePredatorPrey) - return false; - if (!prey.RaceProps.IsFlesh) - return false; - if (prey.BodySize > predator.RaceProps.maxPreyBodySize) - return false; - if (!prey.Downed) - { - if (prey.kindDef.combatPower > 2f * predator.kindDef.combatPower) - return false; - var num = prey.kindDef.combatPower * prey.health.summaryHealth.SummaryHealthPercent * - prey.ageTracker.CurLifeStage.bodySizeFactor; - var num2 = predator.kindDef.combatPower * predator.health.summaryHealth.SummaryHealthPercent * - predator.ageTracker.CurLifeStage.bodySizeFactor; - if (num > 0.85f * num2) - return false; - } - return (predator.Faction == null || prey.Faction == null || predator.HostileTo(prey)) && - (predator.Faction != Faction.OfPlayer || prey.Faction != Faction.OfPlayer) && - (!predator.RaceProps.herdAnimal || predator.def != prey.def); - } - - public static float GetPreyScoreFor(Pawn predator, Pawn prey) - { - var num = prey.kindDef.combatPower / predator.kindDef.combatPower; - var num2 = prey.health.summaryHealth.SummaryHealthPercent; - var bodySizeFactor = prey.ageTracker.CurLifeStage.bodySizeFactor; - var lengthHorizontal = (predator.Position - prey.Position).LengthHorizontal; - if (prey.Downed) - num2 = Mathf.Min(num2, 0.2f); - var num3 = -lengthHorizontal - 56f * num2 * num2 * num * bodySizeFactor; - if (prey.RaceProps.Humanlike) - num3 -= 35f; - return num3; - } - - public static void DebugDrawPredatorFoodSource() - { - var pawn = Find.Selector.SingleSelectedThing as Pawn; - if (pawn == null) - return; - Thing thing; - ThingDef thingDef; - if (TryFindBestFoodSourceFor(pawn, pawn, true, out thing, out thingDef, false, false, false, true, false)) - { - GenDraw.DrawLineBetween(pawn.Position.ToVector3Shifted(), thing.Position.ToVector3Shifted()); - if (!(thing is Pawn)) - { - var pawn2 = BestPawnToHuntForPredator(pawn); - if (pawn2 != null) - GenDraw.DrawLineBetween(pawn.Position.ToVector3Shifted(), pawn2.Position.ToVector3Shifted()); - } - } - } - - public static List ThoughtsFromIngesting(Pawn ingester, Thing t) - { - ingestThoughts.Clear(); - if (ingester.needs == null || ingester.needs.mood == null) - return ingestThoughts; - var thingDef = t.def; - if (thingDef == ThingDefOf.NutrientPasteDispenser) - thingDef = ThingDefOf.MealNutrientPaste; - if (!ingester.story.traits.HasTrait(TraitDefOf.Ascetic) && thingDef.ingestible.tasteThought != null) - ingestThoughts.Add(thingDef.ingestible.tasteThought); - var compIngredients = t.TryGetComp(); - if (IsHumanlikeMeat(thingDef) && ingester.RaceProps.Humanlike) - ingestThoughts.Add(!ingester.story.traits.HasTrait(TraitDefOf.Cannibal) - ? ThoughtDefOf.AteHumanlikeMeatDirect - : ThoughtDefOf.AteHumanlikeMeatDirectCannibal); - else if (compIngredients != null) - for (var i = 0; i < compIngredients.ingredients.Count; i++) - { - var thingDef2 = compIngredients.ingredients[i]; - if (thingDef2.ingestible != null) - if (ingester.RaceProps.Humanlike && IsHumanlikeMeat(thingDef2)) - ingestThoughts.Add(!ingester.story.traits.HasTrait(TraitDefOf.Cannibal) - ? ThoughtDefOf.AteHumanlikeMeatAsIngredient - : ThoughtDefOf.AteHumanlikeMeatAsIngredientCannibal); - else if (thingDef2.ingestible.specialThoughtAsIngredient != null) - ingestThoughts.Add(thingDef2.ingestible.specialThoughtAsIngredient); - } - else if (thingDef.ingestible.specialThoughtDirect != null) - ingestThoughts.Add(thingDef.ingestible.specialThoughtDirect); - if (t.IsNotFresh()) - ingestThoughts.Add(ThoughtDefOf.AteRottenFood); - return ingestThoughts; - } - - public static bool IsHumanlikeMeat(ThingDef def) - { - return def.ingestible.sourceDef != null && def.ingestible.sourceDef.race != null && - def.ingestible.sourceDef.race.Humanlike; - } - - public static bool IsHumanlikeMeatOrHumanlikeCorpse(Thing thing) - { - if (IsHumanlikeMeat(thing.def)) - return true; - var corpse = thing as Corpse; - return corpse != null && corpse.InnerPawn.RaceProps.Humanlike; - } - - public static int WillIngestStackCountOf(Pawn ingester, ThingDef def) - { - var num = Mathf.Min(def.ingestible.maxNumToIngestAtOnce, - StackCountForNutrition(def, ingester.needs.food.NutritionWanted)); - if (num < 1) - num = 1; - return num; - } - - public static float GetBodyPartNutrition(Pawn pawn, BodyPartRecord part) - { - if (!pawn.RaceProps.IsFlesh) - return 0f; - return 5.2f * pawn.BodySize * pawn.health.hediffSet.GetCoverageOfNotMissingNaturalParts(part); - } - - public static int StackCountForNutrition(ThingDef def, float nutrition) - { - if (nutrition <= 0.0001f) - return 0; - return Mathf.Max(Mathf.RoundToInt(nutrition / def.ingestible.CachedNutrition), 1); - } - - public static bool ShouldBeFedBySomeone(Pawn pawn) - { - return FeedPatientUtility.ShouldBeFed(pawn) || WardenFeedUtility.ShouldBeFed(pawn); - } - - public static void AddFoodPoisoningHediff(Pawn pawn, Thing ingestible) - { - pawn.health.AddHediff(HediffMaker.MakeHediff(HediffDefOf.FoodPoisoning, pawn, null), null, null); - if (PawnUtility.ShouldSendNotificationAbout(pawn)) - Messages.Message( - "MessageFoodPoisoning".Translate(pawn.LabelShort, ingestible.LabelCapNoCount).CapitalizeFirst(), - pawn, MessageTypeDefOf.NegativeEvent); - } - } -} \ No newline at end of file diff --git a/Source/Tweaks/JobDriver_FoodDeliver_Tweak.cs b/Source/Tweaks/JobDriver_FoodDeliver_Tweak.cs deleted file mode 100644 index d65e16a3..00000000 --- a/Source/Tweaks/JobDriver_FoodDeliver_Tweak.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics; -using RimWorld; -using Verse; -using Verse.AI; - -namespace PrisonLabor -{ - public class JobDriver_FoodDeliver_Tweak : JobDriver - { - private const TargetIndex FoodSourceInd = TargetIndex.A; - - private const TargetIndex DelivereeInd = TargetIndex.B; - - private bool eatingFromInventory; - - private bool usingNutrientPasteDispenser; - - private Pawn Deliveree => (Pawn) job.targetB.Thing; - - public override void ExposeData() - { - base.ExposeData(); - Scribe_Values.Look(ref usingNutrientPasteDispenser, "usingNutrientPasteDispenser", false, false); - Scribe_Values.Look(ref eatingFromInventory, "eatingFromInventory", false, false); - } - - public override string GetReport() - { - if (job.GetTarget(TargetIndex.A).Thing is Building_NutrientPasteDispenser) - return job.def.reportString.Replace("TargetA", ThingDefOf.MealNutrientPaste.label) - .Replace("TargetB", ((Pawn) (Thing) job.targetB).LabelShort); - return base.GetReport(); - } - - public override void Notify_Starting() - { - base.Notify_Starting(); - usingNutrientPasteDispenser = TargetThingA is Building_NutrientPasteDispenser; - eatingFromInventory = pawn.inventory != null && pawn.inventory.Contains(TargetThingA); - } - - public override bool TryMakePreToilReservations(bool errorOnFailed) - { - return true; - } - - [DebuggerHidden] - protected override IEnumerable MakeNewToils() - { - this.FailOn(() => PrisonerFoodReservation.isReserved(TargetA.Thing)); - yield return Toils_Reserve.Reserve(TargetIndex.B, 1, -1, null); - if (eatingFromInventory) - { - yield return Toils_Misc.TakeItemFromInventoryToCarrier(pawn, TargetIndex.A); - } - else if (usingNutrientPasteDispenser) - { - yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.InteractionCell) - .FailOnForbidden(TargetIndex.A); - yield return Toils_Ingest.TakeMealFromDispenser(TargetIndex.A, pawn); - } - else - { - yield return Toils_Reserve.Reserve(TargetIndex.A, 1, -1, null); - yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.ClosestTouch) - .FailOnForbidden(TargetIndex.A); - yield return Toils_Ingest.PickupIngestible(TargetIndex.A, Deliveree); - } - var toil = new Toil(); - toil.initAction = delegate - { - var actor = toil.actor; - var curJob = actor.jobs.curJob; - actor.pather.StartPath(curJob.targetC, PathEndMode.OnCell); - }; - toil.defaultCompleteMode = ToilCompleteMode.PatherArrival; - toil.FailOnDestroyedNullOrForbidden(TargetIndex.B); - toil.AddFailCondition(delegate - { - var pawn = (Pawn) toil.actor.jobs.curJob.targetB.Thing; - return !pawn.IsPrisonerOfColony || !pawn.guest.CanBeBroughtFood; - }); - yield return toil; - yield return new Toil - { - initAction = delegate - { - Thing thing; - pawn.carryTracker.TryDropCarriedThing(toil.actor.jobs.curJob.targetC.Cell, ThingPlaceMode.Direct, - out thing, null); - PrisonerFoodReservation.reserve(thing, (Pawn) toil.actor.jobs.curJob.targetB.Thing); - }, - defaultCompleteMode = ToilCompleteMode.Instant - }; - } - } -} \ No newline at end of file diff --git a/Source/Tweaks/JobDriver_Mine_Tweak.cs b/Source/Tweaks/JobDriver_Mine_Tweak.cs index 634721f4..e82eca08 100644 --- a/Source/Tweaks/JobDriver_Mine_Tweak.cs +++ b/Source/Tweaks/JobDriver_Mine_Tweak.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Collections.Generic; using RimWorld; using Verse; using Verse.AI; -namespace PrisonLabor +namespace PrisonLabor.Tweaks { public class JobDriver_Mine_Tweak : JobDriver { diff --git a/Source/Tweaks/JobDriver_PlantCut_Tweak.cs b/Source/Tweaks/JobDriver_PlantCut_Tweak.cs index 47dc4ec9..8ade914f 100644 --- a/Source/Tweaks/JobDriver_PlantCut_Tweak.cs +++ b/Source/Tweaks/JobDriver_PlantCut_Tweak.cs @@ -1,8 +1,8 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Verse; using Verse.AI; -namespace PrisonLabor +namespace PrisonLabor.Tweaks { public class JobDriver_PlantCut_Tweak : JobDriver_PlantWork_Tweak { diff --git a/Source/Tweaks/JobDriver_PlantHarvest_Tweak.cs b/Source/Tweaks/JobDriver_PlantHarvest_Tweak.cs index e8069b71..7382d20c 100644 --- a/Source/Tweaks/JobDriver_PlantHarvest_Tweak.cs +++ b/Source/Tweaks/JobDriver_PlantHarvest_Tweak.cs @@ -1,8 +1,8 @@ -using System.Collections.Generic; +using System.Collections.Generic; using RimWorld; using Verse.AI; -namespace PrisonLabor +namespace PrisonLabor.Tweaks { public class JobDriver_PlantHarvest_Tweak : JobDriver_PlantWork_Tweak { diff --git a/Source/Tweaks/JobDriver_PlantWork_Tweak.cs b/Source/Tweaks/JobDriver_PlantWork_Tweak.cs index d7761d4c..34d41d85 100644 --- a/Source/Tweaks/JobDriver_PlantWork_Tweak.cs +++ b/Source/Tweaks/JobDriver_PlantWork_Tweak.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using RimWorld; using Verse; using Verse.AI; using Verse.Sound; -namespace PrisonLabor +namespace PrisonLabor.Tweaks { public abstract class JobDriver_PlantWork_Tweak : JobDriver { diff --git a/Source/Tweaks/MainTabWindow_Assign_Tweak.cs b/Source/Tweaks/MainTabWindow_Assign_Tweak.cs new file mode 100644 index 00000000..46cfed0a --- /dev/null +++ b/Source/Tweaks/MainTabWindow_Assign_Tweak.cs @@ -0,0 +1,109 @@ +using RimWorld; +using RimWorld.Planet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using Verse; + +namespace PrisonLabor.Tweaks +{ + public class MainTabWindow_Assign_Tweak : MainTabWindow_PawnTable + { + #region Original/Vanilla content of class + protected override PawnTableDef PawnTableDef + { + get + { + return PawnTableDefOf.Assign; + } + } + #endregion + + private PawnTable prisonersTable; + + public const int ColonistsTabIndex = 0; + public const int PrisonersTabIndex = 1; + private int currentTabIndex = 0; + + protected virtual IEnumerable Prisoners + { + get + { + foreach (var pawn in Find.CurrentMap.mapPawns.PrisonersOfColony) + { + if (PrisonLaborUtility.LaborEnabled(pawn)) + { + WorkSettings.InitWorkSettings(pawn); + yield return pawn; + } + } + } + } + + public override void DoWindowContents(Rect rect) + { + string[] tabs; + if (Prisoners.Count() > 0) + tabs = new string[] { "PrisonLabor_ColonistsOnlyShort".Translate(), "PrisonLabor_PrisonersOnlyShort".Translate() }; + else + tabs = new string[] { "PrisonLabor_ColonistsOnlyShort".Translate() }; + + PrisonLaborWidgets.BeginTabbedView(rect, tabs, ref currentTabIndex); + if (currentTabIndex == ColonistsTabIndex) + { + base.DoWindowContents(rect); + } + else if (currentTabIndex == PrisonersTabIndex) + { + SetInitialSizeAndPosition(); + prisonersTable.PawnTableOnGUI(new Vector2(rect.x, rect.y + this.ExtraTopSpace)); + } + PrisonLaborWidgets.EndTabbedView(); + } + + private PawnTable CreatePrisonerTable() + { + return new PawnTable( + PawnTableDef, + new Func>(() => Prisoners), + UI.screenWidth - (int)(this.Margin * 2f), + (int)((float)(UI.screenHeight - 35) - this.ExtraBottomSpace - this.ExtraTopSpace - this.Margin * 2f) + ); + } + + public override void Notify_ResolutionChanged() + { + prisonersTable = CreatePrisonerTable(); + base.Notify_ResolutionChanged(); + } + + public override void PostOpen() + { + if (this.prisonersTable == null) + { + this.prisonersTable = this.CreatePrisonerTable(); + } + base.PostOpen(); + Find.World.renderer.wantedMode = WorldRenderMode.None; + } + + public override Vector2 RequestedTabSize + { + get + { + if (prisonersTable != null) + { + var pTableSize = new Vector2(this.prisonersTable.Size.x + this.Margin * 2f, this.prisonersTable.Size.y + this.ExtraBottomSpace + this.ExtraTopSpace + this.Margin * 2f); + var cTableSize = base.RequestedTabSize; + var maxTableSize = new Vector2(Math.Max(pTableSize.x, cTableSize.x), Math.Max(pTableSize.y, cTableSize.y) + PrisonLaborWidgets.TabHeight); + return maxTableSize; + } + else + return base.RequestedTabSize; + } + } + + } +} diff --git a/Source/Tweaks/MainTabWindow_Dual.cs b/Source/Tweaks/MainTabWindow_Dual.cs index 3e1ffe28..1483bc92 100644 --- a/Source/Tweaks/MainTabWindow_Dual.cs +++ b/Source/Tweaks/MainTabWindow_Dual.cs @@ -1,4 +1,6 @@ -using RimWorld; +using PrisonLabor.Core; +using PrisonLabor.Core.LaborWorkSettings; +using RimWorld; using RimWorld.Planet; using System; using System.Collections.Generic; diff --git a/Source/Tweaks/WorkGiver_CleanFilth_Tweak.cs b/Source/Tweaks/WorkGiver_CleanFilth_Tweak.cs index def1d67f..0198d94a 100644 --- a/Source/Tweaks/WorkGiver_CleanFilth_Tweak.cs +++ b/Source/Tweaks/WorkGiver_CleanFilth_Tweak.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; +using System.Collections.Generic; using RimWorld; using Verse; using Verse.AI; -namespace PrisonLabor +namespace PrisonLabor.Tweaks { internal class WorkGiver_CleanFilth_Tweak : WorkGiver_Scanner { diff --git a/Source/Tweaks/WorkGiver_GrowerSow_Tweak.cs b/Source/Tweaks/WorkGiver_GrowerSow_Tweak.cs index 8f3f910e..4cc5d677 100644 --- a/Source/Tweaks/WorkGiver_GrowerSow_Tweak.cs +++ b/Source/Tweaks/WorkGiver_GrowerSow_Tweak.cs @@ -1,8 +1,10 @@ -using RimWorld; +using PrisonLabor.Core.Meta; +using PrisonLabor.Core.Other; +using RimWorld; using Verse; using Verse.AI; -namespace PrisonLabor +namespace PrisonLabor.Tweaks { public class WorkGiver_GrowerSow_Tweak : WorkGiver_Grower { diff --git a/Source/Tweaks/WorkGiver_Warden_DeliverFood_Tweak.cs b/Source/Tweaks/WorkGiver_Warden_DeliverFood_Tweak.cs deleted file mode 100644 index bf73c608..00000000 --- a/Source/Tweaks/WorkGiver_Warden_DeliverFood_Tweak.cs +++ /dev/null @@ -1,86 +0,0 @@ -using RimWorld; -using Verse; -using Verse.AI; - -namespace PrisonLabor -{ - internal class WorkGiver_Warden_DeliverFood_Tweak : WorkGiver_Warden - { - public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false) - { - if (!ShouldTakeCareOfPrisoner(pawn, t)) - return null; - var pawn2 = (Pawn) t; - if (!pawn2.guest.CanBeBroughtFood) - return null; - //if (pawn2.Position.IsInPrisonCell(pawn2.Map) || RCellFinder.TryFindBestExitSpot((Pawn)t, out c, TraverseMode.ByPawn)) - if (pawn2.needs.food.CurLevelPercentage >= pawn2.needs.food.PercentageThreshHungry + 0.02f) - return null; - if (WardenFeedUtility.ShouldBeFed(pawn2)) - return null; - Thing thing; - ThingDef def; - //Tweak: changes way of finding food - if (!FoodUtility_Tweak.TryFindBestFoodSourceFor(pawn, pawn2, - pawn2.needs.food.CurCategory == HungerCategory.Starving, out thing, out def, false, true, false, false, - false)) - return null; - if (thing.GetRoom(RegionType.Set_Passable) == pawn2.GetRoom(RegionType.Set_Passable)) - return null; - if (FoodAvailableInRoomTo(pawn2)) - return null; - return new Job(DefDatabase.GetNamed("PrisonLabor_DeliverFood_Tweak"), thing, pawn2) - { - count = FoodUtility_Tweak.WillIngestStackCountOf(pawn2, def), - targetC = RCellFinder.SpotToChewStandingNear(pawn2, thing) - }; - } - - private static bool FoodAvailableInRoomTo(Pawn prisoner) - { - if (prisoner.carryTracker.CarriedThing != null && - NutritionAvailableForFrom(prisoner, prisoner.carryTracker.CarriedThing) > 0f) - return true; - var num = 0f; - var num2 = 0f; - var room = prisoner.GetRoom(RegionType.Set_Passable); - if (room == null) - return false; - for (var i = 0; i < room.RegionCount; i++) - { - var region = room.Regions[i]; - var list = region.ListerThings.ThingsInGroup(ThingRequestGroup.FoodSourceNotPlantOrTree); - for (var j = 0; j < list.Count; j++) - { - var thing = list[j]; - if (!thing.def.IsIngestible || thing.def.ingestible.preferability > FoodPreferability.DesperateOnly) - num2 += NutritionAvailableForFrom(prisoner, thing); - } - var list2 = region.ListerThings.ThingsInGroup(ThingRequestGroup.Pawn); - for (var k = 0; k < list2.Count; k++) - { - var pawn = list2[k] as Pawn; - if (pawn.IsPrisonerOfColony && - pawn.needs.food.CurLevelPercentage < pawn.needs.food.PercentageThreshHungry + 0.02f && - (pawn.carryTracker.CarriedThing == null || - !pawn.WillEat(pawn.carryTracker.CarriedThing))) - num += pawn.needs.food.NutritionWanted; - } - } - return num2 + 0.5f >= num; - } - - private static float NutritionAvailableForFrom(Pawn p, Thing foodSource) - { - if (foodSource.def.IsNutritionGivingIngestible && p.WillEat(foodSource)) - return foodSource.def.ingestible.CachedNutrition * foodSource.stackCount; - if (p.RaceProps.ToolUser && p.health.capacities.CapableOf(PawnCapacityDefOf.Manipulation)) - { - var building_NutrientPasteDispenser = foodSource as Building_NutrientPasteDispenser; - if (building_NutrientPasteDispenser != null && building_NutrientPasteDispenser.CanDispenseNow) - return 99999f; - } - return 0f; - } - } -} \ No newline at end of file diff --git a/Source/VersionUtility.cs b/Source/VersionUtility.cs deleted file mode 100644 index cbd7ba53..00000000 --- a/Source/VersionUtility.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Verse; - -namespace PrisonLabor -{ - class VersionUtility - { - public const Version versionNumber = Version.v0_9_11; - public const string versionString = "0.9.11"; - - public static void CheckVersion() - { - // Update actual version - if (PrisonLaborPrefs.Version == Version.v0_0) - { - PrisonLaborPrefs.Version = versionNumber; - PrisonLaborPrefs.LastVersion = versionNumber; - } - else if (PrisonLaborPrefs.Version != versionNumber) - { - PrisonLaborPrefs.Version = versionNumber; - } - - // Check for news - if (PrisonLaborPrefs.LastVersion < Version.v0_5) - { - NewsDialog.news_0_5 = true; - NewsDialog.autoShow = true; - } - if (PrisonLaborPrefs.LastVersion < Version.v0_6) - { - NewsDialog.news_0_6 = true; - NewsDialog.autoShow = true; - } - if (PrisonLaborPrefs.LastVersion < Version.v0_7) - { - NewsDialog.news_0_7 = true; - NewsDialog.autoShow = true; - } - if (PrisonLaborPrefs.LastVersion < Version.v0_8_0) - { - NewsDialog.news_0_8_0 = true; - NewsDialog.autoShow = true; - } - if (PrisonLaborPrefs.LastVersion < Version.v0_8_1) - { - NewsDialog.news_0_8_1 = true; - NewsDialog.autoShow = true; - } - if (PrisonLaborPrefs.LastVersion < Version.v0_8_3) - { - NewsDialog.news_0_8_3 = true; - NewsDialog.autoShow = true; - } - if (PrisonLaborPrefs.LastVersion < Version.v0_8_6) - { - NewsDialog.news_0_8_6 = true; - NewsDialog.autoShow = true; - } - if (PrisonLaborPrefs.LastVersion < Version.v0_9_0) - { - NewsDialog.news_0_9_0 = true; - NewsDialog.autoShow = true; - } - if (PrisonLaborPrefs.LastVersion < Version.v0_9_1) - { - NewsDialog.news_0_9_1 = true; - NewsDialog.autoShow = true; - } - if (PrisonLaborPrefs.LastVersion < Version.v0_9_2) - { - NewsDialog.news_0_9_2 = true; - NewsDialog.autoShow = true; - } - if (PrisonLaborPrefs.LastVersion < Version.v0_9_4) - { - CompatibilityPatches.OlderVersions.Pre_v0_9_4(); - } - if (PrisonLaborPrefs.LastVersion < Version.v0_9_9) - { - NewsDialog.news_0_9_9 = true; - NewsDialog.autoShow = true; - } - - PrisonLaborPrefs.Version = versionNumber; - PrisonLaborPrefs.Save(); - } - } -} diff --git a/TaskList.txt b/TaskList.txt index 759f118c..76c99398 100644 --- a/TaskList.txt +++ b/TaskList.txt @@ -6,8 +6,25 @@ Agenda: - Scheduled for implementation * Considered ========================== - - add buff to mood for free time (Thoughts) - - add buff to mood if motivation is low - - add buff to mood if got beat by warden - - rewrite jobs def (transfer to class injection if possible) - - fix bug when prisoner is stuck on snow clearing + +- Prevent Wardens from delivering food if prisoner can get some by himself + + +-add tutorials: +Advanced (videos) +* Reporting bugs? +* labor area +* revolts +* accept prisoner +* hypothermia +Minor (Hints) +* Putting on clothes? +* Advanced planting (growing) +* timetable (old) +* managment (old) +* Available jobs for prisoners +* reaasign beds +* treatment +* accesing food +* bills +* icons (settings) \ No newline at end of file diff --git a/Test/PrisonLabor-Tests.csproj b/Test/PrisonLabor-Tests.csproj index 2cc87a30..afd6abab 100644 --- a/Test/PrisonLabor-Tests.csproj +++ b/Test/PrisonLabor-Tests.csproj @@ -32,6 +32,10 @@ 4 + + False + C:\Users\avius\Desktop\RimworldMods Shortcuts\Libraries\0Harmony.dll + ..\Source\Libraries\Assembly-CSharp.dll @@ -51,6 +55,8 @@ + + diff --git a/Test/Program.cs b/Test/Program.cs index 8ab07b8c..62602ae1 100644 --- a/Test/Program.cs +++ b/Test/Program.cs @@ -1,8 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using RimWorld; +using System; +using System.IO; +using Verse; +using Verse.AI; namespace PrisonLabor_Tests { @@ -10,6 +10,16 @@ class Program { static void Main(string[] args) { + + } + + static void OldToils() + { + var pawn1 = PawnGenerator.GeneratePawn(PawnKindDefOf.Colonist); + var pawn2 = PawnGenerator.GeneratePawn(PawnKindDefOf.Colonist); + var job = new Job(JobDefOf.PrisonerAttemptRecruit, new LocalTargetInfo(pawn1)); + var jobdriver = job.MakeDriver(pawn2); + ToilLister.PrintToils(jobdriver); } } } diff --git a/Test/SortingVersion.cs b/Test/SortingVersion.cs new file mode 100644 index 00000000..9580e895 --- /dev/null +++ b/Test/SortingVersion.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PrisonLabor_Tests +{ + public static class SortingVersion + { + + public static void Test() + { + var list = new List(); + list.Add("1.12"); + list.Add("3.22"); + list.Add("3.22.13"); + list.Add("1.0.0"); + list.Add("0.9999"); + list.Add("9999"); + list.Add("123"); + list.Add("2"); + list.Add("3"); + list.Add("3.12.32.32.13"); + + list.Sort(new Comparison((x, y) => CompareVersion(x, y))); + for (int i = 0; i < list.Count; i++) + { + System.Console.WriteLine(list[i]); + } + } + + private static int CompareVersion(string x, string y) + { + var xFragments = x.Split('.'); + var yFragments = y.Split('.'); + + for (int i = 0; i < xFragments.Length || i < yFragments.Length; i++) + { + if (i == xFragments.Length) + return -1; + if (i == yFragments.Length) + return 1; + + try + { + int xValue = int.Parse(xFragments[i]); + int yValue = int.Parse(yFragments[i]); + if (xValue != yValue) + return xValue - yValue; + } + catch (FormatException e) { } + } + return 0; + } + } +} diff --git a/Test/ToilLister.cs b/Test/ToilLister.cs new file mode 100644 index 00000000..defccf98 --- /dev/null +++ b/Test/ToilLister.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Verse.AI; +using Harmony; +using Harmony.ILCopying; +using System.Reflection.Emit; +using System.Reflection; + +namespace PrisonLabor_Tests +{ + static class ToilLister + { + public static IEnumerable PickToils(JobDriver toilMaker) + { + var method = toilMaker.GetType().GetMethod("MakeNewToils", BindingFlags.NonPublic | BindingFlags.Instance); + return (IEnumerable)method.Invoke(toilMaker, new object[] { }); + } + + public static void PrintToils(JobDriver toilMaker) + { + var toils = PickToils(toilMaker); + if (toils != null) + foreach (var toil in toils) + { + Console.WriteLine($"{toil.GetType()}"); + foreach (var ci in MethodBodyReader.GetInstructions(toil.initAction.Method)) + { + var operand = ci.operand is Label ? ("Label " + ci.operand.GetHashCode()).ToString() : ci.operand; + string labels = ""; + if (ci.labels.Count > 0) + foreach (var label in ci.labels) + labels += $"Label {label.GetHashCode()}"; + else + labels += "no labels"; + Console.WriteLine($" {ci.opcode} | {operand} | {labels}"); + } + } + else + Console.WriteLine("Null"); + } + + } +} diff --git a/Textures/FreezingIcon.png b/Textures/FreezingIcon.png new file mode 100644 index 00000000..4c9b5f7a Binary files /dev/null and b/Textures/FreezingIcon.png differ diff --git a/Textures/InspireIcon.png b/Textures/InspireIcon.png index 88303e83..74100de3 100644 Binary files a/Textures/InspireIcon.png and b/Textures/InspireIcon.png differ diff --git a/Textures/LazyIcon.png b/Textures/LazyIcon.png new file mode 100644 index 00000000..e1913565 Binary files /dev/null and b/Textures/LazyIcon.png differ diff --git a/Textures/MotivateIcon.png b/Textures/MotivateIcon.png deleted file mode 100644 index 81a39d6b..00000000 Binary files a/Textures/MotivateIcon.png and /dev/null differ diff --git a/Textures/Tutorials/ForceToWork.jpg b/Textures/Tutorials/ForceToWork.jpg new file mode 100644 index 00000000..b9facfed Binary files /dev/null and b/Textures/Tutorials/ForceToWork.jpg differ diff --git a/Textures/Tutorials/Hypothermia.jpg b/Textures/Tutorials/Hypothermia.jpg new file mode 100644 index 00000000..6becf580 Binary files /dev/null and b/Textures/Tutorials/Hypothermia.jpg differ diff --git a/Textures/Tutorials/Icons.png b/Textures/Tutorials/Icons.png new file mode 100644 index 00000000..546fc64a Binary files /dev/null and b/Textures/Tutorials/Icons.png differ diff --git a/Textures/Tutorials/InspirationPreview.png b/Textures/Tutorials/InspirationPreview.png new file mode 100644 index 00000000..a9cee787 Binary files /dev/null and b/Textures/Tutorials/InspirationPreview.png differ diff --git a/Textures/Tutorials/Resocialization.jpg b/Textures/Tutorials/Resocialization.jpg new file mode 100644 index 00000000..87aad900 Binary files /dev/null and b/Textures/Tutorials/Resocialization.jpg differ diff --git a/Textures/Tutorials/WorkTab.png b/Textures/Tutorials/WorkTab.png new file mode 100644 index 00000000..6c38b54f Binary files /dev/null and b/Textures/Tutorials/WorkTab.png differ diff --git a/To-Do.md b/To-Do.md index 0e53eeff..a150328f 100644 --- a/To-Do.md +++ b/To-Do.md @@ -1,7 +1,13 @@ # TO-DO In this section I will describe some major changes that I plan to implement. This list is dynamic and will change eventually. Minor issues, bugs, and others can be found [here](../../issues). - +## Pawn attributes & Motivation & Treatment happiness +- There will be more adjustments to how quickly pawn is beign motivated, how fast he is going to be lazy etc. +- Motivation will be increased with high social/combat power of warden +- Prisoners will be able to do forbidden jobs, but: +- Prisoners will get more lazy if doing jobs that they do not like. +- Prisoners will get more lazy if they are working in bad conditions. +- Prisoners will get less treat. happiness when their faction is at very bad stance with player's faction. ## Treatment - I will add new need "Treatment" or "Treatment happiness"(by default hidden, changeable via settings if possible). It will indicate "how good" prisoners are treated. This will make boost to mood, and will reduce chance of revolts.[/li] - Free time, or joy time will now give very little motivation boost, but instead it will give treatment happiness. diff --git a/Translation sheets/Dutch.xlsx b/Translation sheets/Dutch.xlsx index 6eb53544..c5d54778 100644 Binary files a/Translation sheets/Dutch.xlsx and b/Translation sheets/Dutch.xlsx differ diff --git a/Translation sheets/Spanish.xlsx b/Translation sheets/Spanish.xlsx new file mode 100644 index 00000000..399f0752 Binary files /dev/null and b/Translation sheets/Spanish.xlsx differ diff --git a/changelog.txt b/changelog.txt index 0f815e55..bd9ad4a2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,12 @@ Changelog: -0.10.0 +============== BETA VERSION ============== +0.10.1 +- reworked news pop-up window +- fixed construction job +- new system for removing mod from save (new button in mod menu) +- wardens no longer deliver food if prisoners can get it from another room +- added alert when prisoners can escape +- prisoners now will pick up weapons if treated bad - fixed food reservation throwing errors - added prisoner sucicides - added +5 bonus to mood while prisoner have free time @@ -19,7 +26,9 @@ Changelog: - "Treatment happiness" will decrease if health conditions are bad, when prisoners are hungry, or they're working. - added new hidden need "Treatment" that indicates level of prison treatment towards prisoner (WIP) - now work settings reset after prisoner is recruited, so it should fix some issues +- finally fixed "OnGui()" error, big thanks to @notfood (https://github.com/notfood) - fixed blurred effect on settings window +- fixed Revolts 0.9.11 - fixed compatibility with Fluffy's WorkTab (final) 0.9.10 @@ -54,7 +63,7 @@ Changelog: - added option to disable icons above prisoners heads in mod menu - corrected casing in default prisoner interaction mode option in mod menu - fixed error "null reference in onGui()" when loading save -*********** BETA 18 COMPATIBILITY PATCHES ******************* +============== BETA 18 COMPATIBILITY PATCHES ============== 0.8.9.5 - cleared some code debris, hope it fix the "unknown bug" 0.8.9.4 @@ -71,7 +80,7 @@ Changelog: 0.8.9 - updated libraries to RimWorld b18 - minor fixes related to uptading to RimWorld b18 -************************************************************* +=========================================================== 0.8.8 - changed slow from prisoners chains to act as factor instead offset - fixed compatibility issues with Seeds Please (again) @@ -142,31 +151,25 @@ Changelog: - fixed that new subscribers see all "old player" messages - fixed reserving food for prisoners - fixed "work settings not initialized" error - 0.5 - added growing to available jobs - added prisoner work priorities to "Work" tab - food is no longer reserved in prison cell unless is brought by warden - food is now delivered by Wardens even if prisoner get out of his prison cell - disabled passive "Laziness" when prisoner have no work to do - 0.4 - added "Laziness" bar in "Needs" tab - fixed plant cut / harvest result being forbidden - added German translation - 0.3b - fixed "Forbidden" bug - 0.3a - wardens no longer watch over hungry or tired prisoners - 0.3 - added work of Warden type that supervise prisoners - prisoners will get lazy - added version checker - added stat laziness - added "Work" prisoner interaction mode - 0.2a -- added tutorial in "LearningHelper" +- added tutorial in "LearningHelper" \ No newline at end of file diff --git a/credits.md b/credits.md index 08822a7d..db8a1308 100644 --- a/credits.md +++ b/credits.md @@ -2,4 +2,11 @@ Reflection powered by **[Harmony](https://github.com/pardeike/Harmony/wiki)** [![Harmony](https://camo.githubusercontent.com/074bf079275fa90809f51b74e9dd0deccc70328f/68747470733a2f2f7332342e706f7374696d672e6f72672f3538626c31727a33392f6c6f676f2e706e67)](https://github.com/pardeike/Harmony/wiki) -Also I want to thank community for support and feedback! \ No newline at end of file +Icons downloaded from: +* freepik.com +* flaticon.com + +Arrow in motivation icon made by [Dave Gandy](https://www.flaticon.com/authors/dave-gandy) + +Also I want to thank community for great support and feedback! +This mod wouldn't be so great without you. \ No newline at end of file