From ba305c0e1043b9e59acf139b4646346e1fe3870e Mon Sep 17 00:00:00 2001 From: emipa606 Date: Sat, 29 May 2021 19:23:10 +0200 Subject: [PATCH] Restructured the settings menu to make it less confusing --- 1.2/Assemblies/RoadsOfTheRim.dll | Bin 71680 -> 70656 bytes About/About.xml | 3 +- About/Changelog.txt | 3 + About/Manifest.xml | 2 +- About/ModSync.xml | 2 +- README.md | 2 + Source/RoadsOfTheRim.csproj.oldversioncscproj | 69 ++++ Source/RoadsOfTheRim/Bridges.cs | 109 ------ ...CaravanArrivalAction_StartWorkingOnRoad.cs | 9 +- Source/RoadsOfTheRim/CaravanState.cs | 12 + ...roperties_RoadsOfTheRimConstructionSite.cs | 12 + Source/RoadsOfTheRim/ConstructionMenu.cs | 85 ++-- .../DefModExtension_RotR_RoadDef.cs | 74 ++-- .../RoadsOfTheRim/GenStep_CleanBuiltRoads.cs | 81 ++-- Source/RoadsOfTheRim/HarmonyPatches.cs | 362 +----------------- ...ldDefsCleanup.cs => OldDefsCleanupComp.cs} | 10 +- .../Patch_Alert_CaravanIdle_GetExplanation.cs | 30 ++ .../Patch_Alert_CaravanIdle_GetReport.cs | 31 ++ ...Patch_CaravanUIUtility_AddPawnsSections.cs | 34 ++ ...tility_CreateCaravanTransferableWidgets.cs | 29 ++ .../RoadsOfTheRim/Patch_Caravan_GetGizmos.cs | 44 +++ .../Patch_Caravan_GetInspectString.cs | 73 ++++ ...esignator_RemoveBridge_CanDesignateCell.cs | 22 ++ ...tch_FactionDialogMaker_FactionDialogFor.cs | 22 ++ .../Patch_GenConstruct_CanBuildOnTerrain.cs | 27 ++ .../Patch_GenConstruct_CanPlaceBlueprintAt.cs | 23 ++ .../Patch_RoadDefGenStep_Place_Place.cs | 41 ++ .../Patch_ThingFilter_SetFromPreset.cs | 22 ++ Source/RoadsOfTheRim/Patch_Tile_Roads.cs | 16 + ...rid_GetRoadMovementDifficultyMultiplier.cs | 75 ++++ ...PathGrid_CalculatedMovementDifficultyAt.cs | 75 ++++ .../Patch_WorldTargeter_StopTargeting.cs | 22 ++ Source/RoadsOfTheRim/PawnBuildingUtility.cs | 92 +++++ .../RoadsOfTheRim/Properties/AssemblyInfo.cs | 25 -- Source/RoadsOfTheRim/RoadConstructionLeg.cs | 142 ++++--- Source/RoadsOfTheRim/RoadConstructionSite.cs | 231 ++++++----- .../{ModMain.cs => RoadsOfTheRim.cs} | 259 +++++++------ Source/RoadsOfTheRim/RoadsOfTheRim.csproj | 69 +--- Source/RoadsOfTheRim/RoadsOfTheRimSettings.cs | 36 ++ .../RotR_StaticConstructorOnStartup.cs | 17 + Source/RoadsOfTheRim/RotR_cost.cs | 23 ++ Source/RoadsOfTheRim/SettlementInfo.cs | 16 + .../RoadsOfTheRim/TerrainAffordanceDefOf.cs | 12 + Source/RoadsOfTheRim/TerrainDefOf.cs | 25 ++ Source/RoadsOfTheRim/WITab_Caravan_Build.cs | 41 +- ...dComponent_FactionRoadConstructionHelp.cs} | 58 +-- .../WorldComponent_RoadBuildingState.cs | 5 +- .../RoadsOfTheRim/WorldLayer_RoadsOnWater.cs | 40 +- .../RoadsOfTheRim/WorldObjectComp_Caravan.cs | 218 ++++------- .../WorldObjectComp_ConstructionSite.cs | 299 +++++++++------ 50 files changed, 1768 insertions(+), 1261 deletions(-) create mode 100644 Source/RoadsOfTheRim.csproj.oldversioncscproj delete mode 100644 Source/RoadsOfTheRim/Bridges.cs create mode 100644 Source/RoadsOfTheRim/CaravanState.cs create mode 100644 Source/RoadsOfTheRim/CompProperties_RoadsOfTheRimConstructionSite.cs rename Source/RoadsOfTheRim/{OldDefsCleanup.cs => OldDefsCleanupComp.cs} (82%) create mode 100644 Source/RoadsOfTheRim/Patch_Alert_CaravanIdle_GetExplanation.cs create mode 100644 Source/RoadsOfTheRim/Patch_Alert_CaravanIdle_GetReport.cs create mode 100644 Source/RoadsOfTheRim/Patch_CaravanUIUtility_AddPawnsSections.cs create mode 100644 Source/RoadsOfTheRim/Patch_CaravanUIUtility_CreateCaravanTransferableWidgets.cs create mode 100644 Source/RoadsOfTheRim/Patch_Caravan_GetGizmos.cs create mode 100644 Source/RoadsOfTheRim/Patch_Caravan_GetInspectString.cs create mode 100644 Source/RoadsOfTheRim/Patch_Designator_RemoveBridge_CanDesignateCell.cs create mode 100644 Source/RoadsOfTheRim/Patch_FactionDialogMaker_FactionDialogFor.cs create mode 100644 Source/RoadsOfTheRim/Patch_GenConstruct_CanBuildOnTerrain.cs create mode 100644 Source/RoadsOfTheRim/Patch_GenConstruct_CanPlaceBlueprintAt.cs create mode 100644 Source/RoadsOfTheRim/Patch_RoadDefGenStep_Place_Place.cs create mode 100644 Source/RoadsOfTheRim/Patch_ThingFilter_SetFromPreset.cs create mode 100644 Source/RoadsOfTheRim/Patch_Tile_Roads.cs create mode 100644 Source/RoadsOfTheRim/Patch_WorldGrid_GetRoadMovementDifficultyMultiplier.cs create mode 100644 Source/RoadsOfTheRim/Patch_WorldPathGrid_CalculatedMovementDifficultyAt.cs create mode 100644 Source/RoadsOfTheRim/Patch_WorldTargeter_StopTargeting.cs create mode 100644 Source/RoadsOfTheRim/PawnBuildingUtility.cs delete mode 100644 Source/RoadsOfTheRim/Properties/AssemblyInfo.cs rename Source/RoadsOfTheRim/{ModMain.cs => RoadsOfTheRim.cs} (74%) create mode 100644 Source/RoadsOfTheRim/RoadsOfTheRimSettings.cs create mode 100644 Source/RoadsOfTheRim/RotR_StaticConstructorOnStartup.cs create mode 100644 Source/RoadsOfTheRim/RotR_cost.cs create mode 100644 Source/RoadsOfTheRim/SettlementInfo.cs create mode 100644 Source/RoadsOfTheRim/TerrainAffordanceDefOf.cs create mode 100644 Source/RoadsOfTheRim/TerrainDefOf.cs rename Source/RoadsOfTheRim/{WorldComponent_FactionHelpConstruction.cs => WorldComponent_FactionRoadConstructionHelp.cs} (75%) diff --git a/1.2/Assemblies/RoadsOfTheRim.dll b/1.2/Assemblies/RoadsOfTheRim.dll index c04de44f0d81d931d897cef4253eff1303686e4f..920f55b5f228d5f5c5d7392b7a66546a14735b47 100644 GIT binary patch literal 70656 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 J&i_vk_=oZx{&Vi3g> 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

wPB$6gdC^V?tK4w9Tp8_x^xFAmyUVplgVQ(MFW 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 iPss<36L9c1&%b6EDCOKZAWxq8YX - diff --git a/About/Changelog.txt b/About/Changelog.txt index 80461fd..af5d545 100644 --- a/About/Changelog.txt +++ b/About/Changelog.txt @@ -1,5 +1,8 @@ # Changelog for RoadsOfTheRim +1.0.6.0 - Restructured the settings menu to make it less confusing + + 1.0.5.0 - Russian translation added, thanks Festival! diff --git a/About/Manifest.xml b/About/Manifest.xml index 4ab4749..8885c0a 100644 --- a/About/Manifest.xml +++ b/About/Manifest.xml @@ -1,7 +1,7 @@ RoadsOfTheRim - 1.0.5.0 + 1.0.6.0 diff --git a/About/ModSync.xml b/About/ModSync.xml index b02d59f..30bee44 100644 --- a/About/ModSync.xml +++ b/About/ModSync.xml @@ -2,7 +2,7 @@ 1a0c92d0-3054-4626-a0d1-2ed267af412e Roads of the Rim (Continued) - 1.0.5.0 + 1.0.6.0 False emipa606 diff --git a/README.md b/README.md index 8c097eb..9050181 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Update of Loconekos mod https://steamcommunity.com/sharedfiles/filedetails/?id=1613783924 - Can be added to save-games +- Russian translation added, thanks Festival! ![Image](https://i.imgur.com/7Gzt3Rg.png) @@ -203,3 +204,4 @@ It will save a lot of your time (and a little bit of mine ;-) ) + diff --git a/Source/RoadsOfTheRim.csproj.oldversioncscproj b/Source/RoadsOfTheRim.csproj.oldversioncscproj new file mode 100644 index 0000000..53da408 --- /dev/null +++ b/Source/RoadsOfTheRim.csproj.oldversioncscproj @@ -0,0 +1,69 @@ + + + Debug + AnyCPU + {32C59479-1ECF-4609-BD0D-BB12658BF92B} + Library + false + RoadsOfTheRim + v4.7.2 + 512 + + + + true + full + false + ..\..\1.2\Assemblies\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + true + ..\..\1.2\Assemblies\ + TRACE + prompt + 4 + false + + + RoadsOfTheRim + + + + 1.2.2753 + runtime + compile; build; native; contentfiles; analyzers; buildtransitive + + + 2.0.2 + runtime + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Bridges.cs b/Source/RoadsOfTheRim/Bridges.cs deleted file mode 100644 index 19c3151..0000000 --- a/Source/RoadsOfTheRim/Bridges.cs +++ /dev/null @@ -1,109 +0,0 @@ -using RimWorld; -using Verse; -using HarmonyLib; - -/* - * This file contains all C# related to placing & removing Concrete bridges - */ -namespace RoadsOfTheRim -{ - [DefOf] - public static class TerrainDefOf - { - public static TerrainDef StoneRecent; - public static TerrainDef AsphaltRecent ; - public static TerrainDef GlitterRoad; - public static TerrainDef ConcreteBridge ; - public static TerrainDef MarshyTerrain; - public static TerrainDef Mud; - } - - [DefOf] - public static class TerrainAffordanceDefOf - { - public static TerrainAffordanceDef Bridgeable; - public static TerrainAffordanceDef BridgeableAny; - } - - [HarmonyPatch(typeof(Designator_RemoveBridge), "CanDesignateCell")] - public static class Patch_Designator_RemoveBridge_CanDesignateCell - { - [HarmonyPostfix] - public static void Postfix(ref AcceptanceReport __result, Designator_RemoveBridge __instance, IntVec3 c) - { - if (!c.InBounds(__instance.Map) || c.GetTerrain(__instance.Map) != TerrainDefOf.ConcreteBridge) - { - return; - } - __result = true; - RoadsOfTheRim.DebugLog(c.GetTerrain(__instance.Map).label); - } - } - - [HarmonyPatch(typeof(GenConstruct), "CanPlaceBlueprintAt")] - public static class Patch_GenConstruct_CanPlaceBlueprintAt - { - [HarmonyPostfix] - public static void Postfix(ref AcceptanceReport __result, BuildableDef entDef, IntVec3 center, Rot4 rot, Map map, bool godMode = false, Thing thingToIgnore = null, Thing thing = null, ThingDef stuffDef = null) - { - if (entDef != TerrainDefOf.ConcreteBridge || !map.terrainGrid.TerrainAt(center).affordances.Contains(TerrainAffordanceDefOf.Bridgeable)) // ConcreteBridge on normal water (bridgeable) - { - return; - } - __result = AcceptanceReport.WasAccepted; - } - } - /* - * Both Concrete bridges, Stone Roads, and Asphalt roads must check the terrain they're placed on and : - * - Change it (Marsh & marshy soil to be removed when a "good" road was placed - * - Be placed despite affordance (Concrete bridges on top of normal bridgeable water) - */ - - [HarmonyPatch(typeof(RoadDefGenStep_Place), "Place")] - public static class Patch_RoadDefGenStep_Place_Place - { - public static bool IsGoodTerrain(TerrainDef terrain) - { - return (terrain == TerrainDefOf.Mud) || (terrain == TerrainDefOf.MarshyTerrain); - } - - [HarmonyPostfix] - public static void Postfix(ref RoadDefGenStep_Place __instance, Map map, IntVec3 position, TerrainDef rockDef, IntVec3 origin, GenStep_Roads.DistanceElement[,] distance) - { - if (__instance.place == TerrainDefOf.ConcreteBridge && position.GetTerrain(map).IsWater) - { - map.terrainGrid.SetTerrain(position, TerrainDefOf.ConcreteBridge) ; - } - if (__instance.place == TerrainDefOf.GlitterRoad && (IsGoodTerrain(position.GetTerrain(map)) || position.GetTerrain(map).IsWater)) - { - map.terrainGrid.SetTerrain(position, TerrainDefOf.GlitterRoad); - } - if (__instance.place == TerrainDefOf.AsphaltRecent && IsGoodTerrain(position.GetTerrain(map))) - { - map.terrainGrid.SetTerrain(position, TerrainDefOf.AsphaltRecent); - } - if (__instance.place == TerrainDefOf.StoneRecent && IsGoodTerrain(position.GetTerrain(map))) - { - map.terrainGrid.SetTerrain(position, TerrainDefOf.StoneRecent); - } - } - } - - [HarmonyPatch(typeof(GenConstruct), "CanBuildOnTerrain")] - public static class Patch_GenConstruct_CanBuildOnTerrain - { - [HarmonyPostfix] - public static void Postfix (ref bool __result , BuildableDef entDef, IntVec3 c, Map map) - { - if (entDef != TerrainDefOf.ConcreteBridge && entDef != TerrainDefOf.AsphaltRecent && entDef != TerrainDefOf.GlitterRoad) - { - return; - } - if (!map.terrainGrid.TerrainAt(c).IsWater) - { - return; - } - __result = true; - } - } -} diff --git a/Source/RoadsOfTheRim/CaravanArrivalAction_StartWorkingOnRoad.cs b/Source/RoadsOfTheRim/CaravanArrivalAction_StartWorkingOnRoad.cs index e748c15..bf6b220 100644 --- a/Source/RoadsOfTheRim/CaravanArrivalAction_StartWorkingOnRoad.cs +++ b/Source/RoadsOfTheRim/CaravanArrivalAction_StartWorkingOnRoad.cs @@ -6,18 +6,15 @@ namespace RoadsOfTheRim { public class CaravanArrivalAction_StartWorkingOnRoad : CaravanArrivalAction { - public CaravanArrivalAction_StartWorkingOnRoad() - { - } public override string Label => "Start working, you lazy bastards"; public override string ReportString => "Work for your rich masters"; - public override void Arrived (Caravan caravan) + public override void Arrived(Caravan caravan) { try { - WorldObjectComp_Caravan CaravanComp = caravan.GetComponent(); + var CaravanComp = caravan.GetComponent(); CaravanComp.StartWorking(); } catch (Exception e) @@ -26,4 +23,4 @@ public override void Arrived (Caravan caravan) } } } -} +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/CaravanState.cs b/Source/RoadsOfTheRim/CaravanState.cs new file mode 100644 index 0000000..a61bc9d --- /dev/null +++ b/Source/RoadsOfTheRim/CaravanState.cs @@ -0,0 +1,12 @@ +namespace RoadsOfTheRim +{ + public enum CaravanState : byte + { + Moving, + NightResting, + AllOwnersHaveMentalBreak, + AllOwnersDowned, + ImmobilizedByMass, + ReadyToWork + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/CompProperties_RoadsOfTheRimConstructionSite.cs b/Source/RoadsOfTheRim/CompProperties_RoadsOfTheRimConstructionSite.cs new file mode 100644 index 0000000..3da7518 --- /dev/null +++ b/Source/RoadsOfTheRim/CompProperties_RoadsOfTheRimConstructionSite.cs @@ -0,0 +1,12 @@ +using RimWorld; + +namespace RoadsOfTheRim +{ + public class CompProperties_RoadsOfTheRimConstructionSite : WorldObjectCompProperties + { + public CompProperties_RoadsOfTheRimConstructionSite() + { + compClass = typeof(WorldObjectComp_ConstructionSite); + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/ConstructionMenu.cs b/Source/RoadsOfTheRim/ConstructionMenu.cs index 72b55e3..484cbf4 100644 --- a/Source/RoadsOfTheRim/ConstructionMenu.cs +++ b/Source/RoadsOfTheRim/ConstructionMenu.cs @@ -1,10 +1,10 @@ -using RimWorld; -using RimWorld.Planet; +using System; using System.Collections.Generic; +using RimWorld; +using RimWorld.Planet; using UnityEngine; using Verse; using Verse.Sound; -using System; namespace RoadsOfTheRim { @@ -31,10 +31,9 @@ Each line starts with the icon of the resource (work uses the construction site public class ConstructionMenu : Window { - private readonly RoadConstructionSite site; - private readonly Caravan caravan; private readonly List buildableRoads; - public override Vector2 InitialSize => new Vector2(676+128, 544+128); + private readonly Caravan caravan; + private readonly RoadConstructionSite site; // TO DO : Use the below to dynamically draw the window based on number of buildable roads (which could include technolcogy limits) // public bool resizeable = true ; @@ -42,32 +41,36 @@ public class ConstructionMenu : Window // private Rect resizeLaterRect ; - public ConstructionMenu(RoadConstructionSite site , Caravan caravan) + public ConstructionMenu(RoadConstructionSite site, Caravan caravan) { - this.site = site ; - this.caravan = caravan ; - buildableRoads = new List() ; + this.site = site; + this.caravan = caravan; + buildableRoads = new List(); // TO DO : COunt number of buildable roads, set the resize later rect based on that - } + public override Vector2 InitialSize => new Vector2(676 + 128, 544 + 128); + public int CountBuildableRoads() { - foreach (RoadDef thisDef in DefDatabase.AllDefs) + foreach (var thisDef in DefDatabase.AllDefs) { // Only add RoadDefs that are buildable, based on DefModExtension_RotR_RoadDef.built - if (!thisDef.HasModExtension() || !thisDef.GetModExtension().built) + if (!thisDef.HasModExtension() || + !thisDef.GetModExtension().built) { continue; } + buildableRoads.Add(thisDef); } - return buildableRoads!=null ? buildableRoads.Count : 0; + + return buildableRoads?.Count ?? 0; } public override void DoWindowContents(Rect inRect) { - if (Event.current.isKey && site!=null) + if (Event.current.isKey && site != null) { RoadsOfTheRim.DeleteConstructionSite(site.Tile); Close(); @@ -109,7 +112,8 @@ public override void DoWindowContents(Rect inRect) theDef = ThingDefOf.ComponentSpacer; break; } - if (i==0) + + if (i == 0) { Widgets.ButtonImage(ResourceRect, ContentFinder.Get("UI/Commands/AddConstructionSite")); } @@ -121,42 +125,38 @@ public override void DoWindowContents(Rect inRect) // Sections : one per type of buildable road var nbOfSections = 0; - var groupSize = new Vector2(144 , 512+128); - foreach (RoadDef aDef in buildableRoads) + var groupSize = new Vector2(144, 512 + 128); + foreach (var aDef in buildableRoads) { - DefModExtension_RotR_RoadDef roadDefExtension = aDef.GetModExtension(); + var roadDefExtension = aDef.GetModExtension(); // Check if a tech is necessary to build this road, don't display the road if it isn't researched yet - ResearchProjectDef NeededTech = roadDefExtension.techNeededToBuild; - var TechResearched = false; - if (NeededTech != null) - { - TechResearched = NeededTech.IsFinished; - } - else - { - TechResearched = true; - } + var NeededTech = roadDefExtension.techNeededToBuild; + var TechResearched = NeededTech == null || NeededTech.IsFinished; if (!TechResearched) { continue; } + GUI.BeginGroup(new Rect(new Vector2(64 + (144 * nbOfSections), 32f), groupSize)); // Buildable Road icon - Texture2D theButton = ContentFinder.Get("UI/Commands/Build_" + aDef.defName, true); + var theButton = ContentFinder.Get("UI/Commands/Build_" + aDef.defName); var ButtonRect = new Rect(8, 8, 128, 128); if (Widgets.ButtonImage(ButtonRect, theButton)) { if (Event.current.button == 0) { - SoundStarter.PlayOneShotOnCamera(SoundDefOf.Tick_High, null); - site.roadDef = aDef; - Close(); - RoadsOfTheRim.RoadBuildingState.CurrentlyTargeting = site; - RoadsOfTheRim.RoadBuildingState.Caravan = caravan; - RoadConstructionLeg.Target(site); + SoundDefOf.Tick_High.PlayOneShotOnCamera(); + if (site != null) + { + site.roadDef = aDef; + Close(); + RoadsOfTheRim.RoadBuildingState.CurrentlyTargeting = site; + RoadsOfTheRim.RoadBuildingState.Caravan = caravan; + RoadConstructionLeg.Target(site); + } } } @@ -173,16 +173,22 @@ public override void DoWindowContents(Rect inRect) { var ResourceAmountRect = new Rect(0, 176f + (i++ * 40f), 144f, 32f); Widgets.Label(ResourceAmountRect, - (roadDefExtension.GetCost(resourceName) > 0) ? (roadDefExtension.GetCost(resourceName) * ((float)RoadsOfTheRim.settings.BaseEffort / 10)).ToString() : "-" + roadDefExtension.GetCost(resourceName) > 0 + ? (roadDefExtension.GetCost(resourceName) * + ((float) RoadsOfTheRim.settings.BaseEffort / 10)).ToString() + : "-" ); } + GUI.EndGroup(); nbOfSections++; } + Text.Anchor = TextAnchor.UpperLeft; } - public override void PostClose() // If the menu was somehow closed without picking a road, delete the construction site + public override void + PostClose() // If the menu was somehow closed without picking a road, delete the construction site { try { @@ -190,6 +196,7 @@ public override void PostClose() // If the menu was somehow closed without picki { return; } + RoadsOfTheRim.DeleteConstructionSite(site.Tile); } catch (Exception e) @@ -198,4 +205,4 @@ public override void PostClose() // If the menu was somehow closed without picki } } } -} +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/DefModExtension_RotR_RoadDef.cs b/Source/RoadsOfTheRim/DefModExtension_RotR_RoadDef.cs index 6f534cb..b52eb5b 100644 --- a/Source/RoadsOfTheRim/DefModExtension_RotR_RoadDef.cs +++ b/Source/RoadsOfTheRim/DefModExtension_RotR_RoadDef.cs @@ -1,46 +1,38 @@ -using Verse; -using RimWorld; -using RimWorld.Planet; using System.Collections.Generic; using System.Text; -using System.Xml; +using RimWorld; +using RimWorld.Planet; +using Verse; namespace RoadsOfTheRim { - public class RotR_cost + public class DefModExtension_RotR_RoadDef : DefModExtension { - public string name; - public int count; + public static string[] allResources = + {"WoodLog", "Stone", "Steel", "Chemfuel", "Plasteel", "Uranium", "ComponentIndustrial", "ComponentSpacer"}; - public void LoadDataFromXmlCustom(XmlNode xmlRoot) + public static string[] allResourcesAndWork = { - if (xmlRoot.ChildNodes.Count != 1) - { - Log.Error("Misconfigured RotR_cost: " + xmlRoot.OuterXml, false); - return; - } - name = xmlRoot.Name; - count = (int)ParseHelper.FromString(xmlRoot.FirstChild.Value, typeof(int)); - } - } + "Work", "WoodLog", "Stone", "Steel", "Chemfuel", "Plasteel", "Uranium", "ComponentIndustrial", + "ComponentSpacer" + }; - public class DefModExtension_RotR_RoadDef : DefModExtension - { - public bool built = false; // Whether or not this road is built or generated - // Base roads (DirtPath, DirtRoad, StoneRoad, AncientAsphaltRoad, AncientAsphaltHighway) will have this set to false, - // Built roads (DirtPath+, DirtRoad+, StoneRoad+, AsphaltRoad+, GlitterRoad) will have this set to true - // Built roads will prevent rocks from being generated on top of them on maps + public static string[] allResourcesWithoutModifiers = {"Uranium", "ComponentIndustrial", "ComponentSpacer"}; + // Base roads (DirtPath, DirtRoad, StoneRoad, AncientAsphaltRoad, AncientAsphaltHighway) will have this set to false, + // Built roads (DirtPath+, DirtRoad+, StoneRoad+, AsphaltRoad+, GlitterRoad) will have this set to true + // Built roads will prevent rocks from being generated on top of them on maps public float biomeModifier = 0f; - - public float hillinessModifier = 0f; - - public float winterModifier = 0f; + public bool built = false; // Whether or not this road is built or generated public bool canBuildOnImpassable = false; public bool canBuildOnWater = false; + public List costs = new List(); + + public float hillinessModifier = 0f; + public float minConstruction = 0f; public float percentageOfminConstruction = 0f; @@ -49,29 +41,24 @@ public class DefModExtension_RotR_RoadDef : DefModExtension public ResearchProjectDef techNeededToBuild = null; - public static string[] allResources = new string[] { "WoodLog", "Stone", "Steel", "Chemfuel", "Plasteel", "Uranium", "ComponentIndustrial", "ComponentSpacer" }; - - public static string[] allResourcesAndWork = new string[] { "Work", "WoodLog", "Stone", "Steel", "Chemfuel", "Plasteel", "Uranium", "ComponentIndustrial", "ComponentSpacer" }; - - public static string[] allResourcesWithoutModifiers = new string[] { "Uranium", "ComponentIndustrial", "ComponentSpacer" }; - - public List costs = new List(); + public float winterModifier = 0f; public string GetCosts() { var s = new StringBuilder(); s.Append("The road is " + (built ? "" : "not") + " built. Costs : "); - foreach (RotR_cost c in costs) + foreach (var c in costs) { s.Append(c.count + " " + c.name + ", "); } + return s.ToString(); } public int GetCost(string name) { - RotR_cost aCost = costs.Find(c => c.name == name); - return (aCost == null) ? 0 : aCost.count; + var aCost = costs.Find(c => c.name == name); + return aCost == null ? 0 : aCost.count; } public static bool GetInSituModifier(string resourceName, int ISR2G) @@ -97,32 +84,33 @@ public static bool GetInSituModifier(string resourceName, int ISR2G) case "Uranium": result = ISR2G > 1; break; - default: - break; } + return result; } public static bool BiomeAllowed(int tile, RoadDef roadDef, out BiomeDef biomeHere) { - DefModExtension_RotR_RoadDef RoadDefMod = roadDef.GetModExtension(); + var RoadDefMod = roadDef.GetModExtension(); biomeHere = Find.WorldGrid.tiles[tile].biome; if (RoadDefMod.canBuildOnWater && (biomeHere.defName == "Ocean" || biomeHere.defName == "Lake")) { return true; } + return biomeHere.allowRoads; } public static bool ImpassableAllowed(int tile, RoadDef roadDef) { - DefModExtension_RotR_RoadDef RoadDefMod = roadDef.GetModExtension(); - Hilliness hillinnessHere = Find.WorldGrid.tiles[tile].hilliness; + var RoadDefMod = roadDef.GetModExtension(); + var hillinnessHere = Find.WorldGrid.tiles[tile].hilliness; if (RoadDefMod.canBuildOnImpassable && hillinnessHere == Hilliness.Impassable) { return true; } + return hillinnessHere != Hilliness.Impassable; } } -} +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/GenStep_CleanBuiltRoads.cs b/Source/RoadsOfTheRim/GenStep_CleanBuiltRoads.cs index 21973ef..eeffaca 100644 --- a/Source/RoadsOfTheRim/GenStep_CleanBuiltRoads.cs +++ b/Source/RoadsOfTheRim/GenStep_CleanBuiltRoads.cs @@ -15,15 +15,16 @@ public class GenStep_CleanBuiltRoads : GenStep public override void Generate(Map map, GenStepParams parms) { //RoadsOfTheRim.DebugLog("Cleaning up roads if I can"); - TerrainGrid terrainGrid = map.terrainGrid; - foreach (IntVec3 current in map.AllCells) + var terrainGrid = map.terrainGrid; + foreach (var current in map.AllCells) { - List thingList = current.GetThingList(map); - TerrainDef terrainDefHere = terrainGrid.TerrainAt(current); + var thingList = current.GetThingList(map); + var terrainDefHere = terrainGrid.TerrainAt(current); if (!IsBuiltRoad(terrainDefHere)) { continue; } + map.roofGrid.SetRoof(current, null); // remove any roof if (map.fogGrid.IsFogged(current)) { @@ -45,35 +46,41 @@ public override void Generate(Map map, GenStepParams parms) { map.terrainGrid.SetTerrain(current, TerrainDefOf.GlitterRoad); } + if (terrainDefHere == TerrainDefOf.AsphaltRecent) { map.terrainGrid.SetTerrain(current, TerrainDefOf.ConcreteBridge); } + if (terrainDefHere == TerrainDefOf.StoneRecent) { map.terrainGrid.SetTerrain(current, TerrainDefOf.ConcreteBridge); } } - if (map.terrainGrid.UnderTerrainAt(current) == TerrainDefOf.MarshyTerrain) + if (map.terrainGrid.UnderTerrainAt(current) != TerrainDefOf.MarshyTerrain) { - if (terrainDefHere == TerrainDefOf.GlitterRoad) - { - map.terrainGrid.SetTerrain(current, TerrainDefOf.GlitterRoad); - } - if (terrainDefHere == TerrainDefOf.AsphaltRecent) - { - map.terrainGrid.SetTerrain(current, TerrainDefOf.AsphaltRecent); - } - if (terrainDefHere == TerrainDefOf.StoneRecent) - { - map.terrainGrid.SetTerrain(current, TerrainDefOf.StoneRecent); - } + continue; + } + + if (terrainDefHere == TerrainDefOf.GlitterRoad) + { + map.terrainGrid.SetTerrain(current, TerrainDefOf.GlitterRoad); + } + + if (terrainDefHere == TerrainDefOf.AsphaltRecent) + { + map.terrainGrid.SetTerrain(current, TerrainDefOf.AsphaltRecent); + } + + if (terrainDefHere == TerrainDefOf.StoneRecent) + { + map.terrainGrid.SetTerrain(current, TerrainDefOf.StoneRecent); } } } - public static bool IsBuiltRoad(TerrainDef def) + private static bool IsBuiltRoad(TerrainDef def) { return RoadsOfTheRim.builtRoadTerrains.Contains(def); } @@ -81,12 +88,12 @@ public static bool IsBuiltRoad(TerrainDef def) /* Moves all things in a cell to the closest cell that is empty and not a built road */ - public static void MoveThings(Map map, IntVec3 cell) + private static void MoveThings(Map map, IntVec3 cell) { - List thingList = cell.GetThingList(map); - TerrainGrid terrainGrid = map.terrainGrid; + var thingList = cell.GetThingList(map); + var terrainGrid = map.terrainGrid; //thingList.RemoveAll(item => item !=null); - foreach (Thing thingToMove in thingList) // Go through all things on that cell + foreach (var thingToMove in thingList) // Go through all things on that cell { //RoadsOfTheRim.DebugLog("Trying to move " + thingToMove.Label); var cellChecked = new List @@ -96,39 +103,44 @@ public static void MoveThings(Map map, IntVec3 cell) var goodCellFound = false; while (!goodCellFound) // Keep doing this as long as I haven't found a good cell (empty, and not a road) { - List newCells = cellChecked; + var newCells = cellChecked; ExpandNeighbouringCells(ref newCells, map); - foreach (IntVec3 c in newCells) + foreach (var c in newCells) { - TerrainDef terrainDefHere = terrainGrid.TerrainAt(c); - List thingList2 = c.GetThingList(map); - if (!IsBuiltRoad(terrainDefHere) && thingList2.Count == 0) + var terrainDefHere = terrainGrid.TerrainAt(c); + var thingList2 = c.GetThingList(map); + if (IsBuiltRoad(terrainDefHere) || thingList2.Count != 0) { - //RoadsOfTheRim.DebugLog("Moved "+thingToMove.Label); - thingToMove.SetPositionDirect(c); - goodCellFound = true; - break; + continue; } + + //RoadsOfTheRim.DebugLog("Moved "+thingToMove.Label); + thingToMove.SetPositionDirect(c); + goodCellFound = true; + break; } + if (newCells.Count <= cellChecked.Count) // break out of the loop if we couldn't find any new cells { break; } + cellChecked = newCells; } } } - public static void ExpandNeighbouringCells(ref List cells, Map map) + private static void ExpandNeighbouringCells(ref List cells, Map map) { var expandedCells = new List(); - foreach (IntVec3 c in cells) + foreach (var c in cells) { if (!expandedCells.Contains(c) && !cells.Contains(c)) // Add the current cell { expandedCells.Add(c); } - foreach (IntVec3 c2 in GenAdjFast.AdjacentCells8Way(c)) // Add all the current cell's neighbours + + foreach (var c2 in GenAdjFast.AdjacentCells8Way(c)) // Add all the current cell's neighbours { if (!expandedCells.Contains(c2) && !cells.Contains(c2) && c.InBounds(map)) { @@ -136,6 +148,7 @@ public static void ExpandNeighbouringCells(ref List cells, Map map) } } } + cells = expandedCells; } } diff --git a/Source/RoadsOfTheRim/HarmonyPatches.cs b/Source/RoadsOfTheRim/HarmonyPatches.cs index fe544ee..3619e4a 100644 --- a/Source/RoadsOfTheRim/HarmonyPatches.cs +++ b/Source/RoadsOfTheRim/HarmonyPatches.cs @@ -1,19 +1,16 @@ -using HarmonyLib; +using System.Linq; +using System.Reflection; +using HarmonyLib; using RimWorld; using Verse; -using RimWorld.Planet; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System; namespace RoadsOfTheRim { [StaticConstructorOnStartup] public class HarmonyPatches { - public static RoadsOfTheRimSettings settings = LoadedModManager.GetMod().GetSettings(); + public static RoadsOfTheRimSettings settings = + LoadedModManager.GetMod().GetSettings(); static HarmonyPatches() { @@ -28,16 +25,20 @@ static HarmonyPatches() */ // Initialise the list of terrains that are specific to built roads. Doing it here is hacky, but this is a quick way to use defs after they were loaded - foreach (RoadDef thisDef in DefDatabase.AllDefs) + foreach (var thisDef in DefDatabase.AllDefs) { //RoadsOfTheRim.DebugLog("initialising roadDef " + thisDef); - if (!thisDef.HasModExtension() || !thisDef.GetModExtension().built) // Only add RoadDefs that are buildable, based on DefModExtension_RotR_RoadDef.built + if (!thisDef.HasModExtension() || + !thisDef.GetModExtension().built + ) // Only add RoadDefs that are buildable, based on DefModExtension_RotR_RoadDef.built { continue; } - foreach (RoadDefGenStep_Place aStep in thisDef.roadGenSteps.OfType()) // Only get RoadDefGenStep_Place + + foreach (var aStep in thisDef.roadGenSteps.OfType() + ) // Only get RoadDefGenStep_Place { - var t = (TerrainDef)aStep.place; // Cast the buildableDef into a TerrainDef + var t = (TerrainDef) aStep.place; // Cast the buildableDef into a TerrainDef if (!RoadsOfTheRim.builtRoadTerrains.Contains(t)) { RoadsOfTheRim.builtRoadTerrains.Add(t); @@ -47,350 +48,15 @@ static HarmonyPatches() } } - [HarmonyPatch(typeof(Caravan), "GetGizmos")] - public static class Patch_Caravan_GetGizmos - { - [HarmonyPostfix] - public static void Postfix(ref IEnumerable __result, Caravan __instance) - { - var isThereAConstructionSiteHere = Find.WorldObjects.AnyWorldObjectOfDefAt(DefDatabase.GetNamed("RoadConstructionSite", true), __instance.Tile); - var isTheCaravanWorkingOnASite = true; - try - { - isTheCaravanWorkingOnASite = __instance.GetComponent().currentlyWorkingOnSite; - } - catch (Exception e) - { - RoadsOfTheRim.DebugLog(null, e); - } - __result = __result.Concat(new Gizmo[] { RoadsOfTheRim.AddConstructionSite(__instance) }) - .Concat(new Gizmo[] { RoadsOfTheRim.RemoveConstructionSite(__instance.Tile) }); - if (isThereAConstructionSiteHere & !isTheCaravanWorkingOnASite && RoadsOfTheRim.RoadBuildingState.CurrentlyTargeting == null) - { - __result = __result.Concat(new Gizmo[] { RoadsOfTheRim.WorkOnSite(__instance) }); - } - if (isTheCaravanWorkingOnASite) - { - __result = __result.Concat(new Gizmo[] { RoadsOfTheRim.StopWorkingOnSite(__instance) }); - } - } - } - // TO DO : Ideally, this should be a transpiler. But should I bother ? The code below does the job - [HarmonyPatch(typeof(Caravan), "GetInspectString")] - public static class Patch_Caravan_GetInspectString - { - [HarmonyPostfix] - public static void Postfix(ref string __result, Caravan __instance) - { - try - { - WorldObjectComp_Caravan CaravanComp = __instance.GetComponent(); - if (CaravanComp == null || !CaravanComp.currentlyWorkingOnSite) - { - return; - } - var stringBuilder = new StringBuilder(); - stringBuilder.Append(__result); - // remove "waiting" - var waitingIndex = stringBuilder.ToString().IndexOf("CaravanWaiting".Translate()); - if (waitingIndex >= 0) - { - stringBuilder.Remove(waitingIndex, "CaravanWaiting".Translate().Length); - } - // remove "resting (using x bedrolls)" - var usedBedCount = __instance.beds.GetUsedBedCount(); - var bedrollIndex = 0; - var stringToFind = ""; - if (usedBedCount == 1) - { - // remove singular version - stringToFind = " (" + (string)"UsingBedroll".Translate() + ")"; - bedrollIndex = stringBuilder.ToString().IndexOf(stringToFind); - } - else - { - // remove plural version - stringToFind = " (" + (string)"UsingBedrolls".Translate(usedBedCount) + ")"; - bedrollIndex = stringBuilder.ToString().IndexOf(stringToFind); - } - if (bedrollIndex >= 0) - { - stringBuilder.Remove(bedrollIndex, stringToFind.Length); - var restingIndex = stringBuilder.ToString().IndexOf("CaravanResting".Translate()); - if (restingIndex >= 0) - { - stringBuilder.Remove(restingIndex, "CaravanResting".Translate().Length); - } - } - // Appending "working on road" - stringBuilder.Replace("\n", ""); - stringBuilder.Replace("\r", ""); - stringBuilder.AppendLine(); - stringBuilder.Append("RoadsOfTheRim_CaravanInspectStringWorkingOn".Translate(CaravanComp.GetSite().FullName(), string.Format("{0:0.00}", CaravanComp.AmountOfWork()))); - __result = stringBuilder.ToString(); - } - catch - { - // lazy way out : the caravan can, on occasions (mainly debug teleport, though...), not have a site linked to the comp - } - } - - } - - [HarmonyPatch(typeof(Alert_CaravanIdle), "GetExplanation")] - public static class Patch_Alert_CaravanIdle_GetExplanation - { - [HarmonyPostfix] - public static void Postfix(ref TaggedString __result) - { - var stringBuilder = new StringBuilder(); - foreach (Caravan caravan in Find.WorldObjects.Caravans) - { - WorldObjectComp_Caravan caravanComp = caravan.GetComponent(); - if (!caravan.Spawned || !caravan.IsPlayerControlled || caravan.pather.MovingNow || caravan.CantMove || caravanComp.currentlyWorkingOnSite) - { - continue; - } - stringBuilder.AppendLine(" - " + caravan.Label); - } - __result = "CaravanIdleDesc".Translate(stringBuilder.ToString()); - } - } - - [HarmonyPatch(typeof(Alert_CaravanIdle), "GetReport")] - public static class Patch_Alert_CaravanIdle_GetReport - { - [HarmonyPostfix] - public static void Postfix(ref AlertReport __result) - { - var newList = new List(); - foreach (Caravan caravan in Find.WorldObjects.Caravans) - { - WorldObjectComp_Caravan caravanComp = caravan.GetComponent(); - if (!caravan.Spawned || !caravan.IsPlayerControlled || caravan.pather.MovingNow || caravan.CantMove || caravanComp.currentlyWorkingOnSite) - { - continue; - } - newList.Add(caravan); - } - __result = AlertReport.CulpritsAre(newList); - } - } - - [HarmonyPatch(typeof(FactionDialogMaker), "FactionDialogFor")] - public static class Patch_FactionDialogMaker_FactionDialogFor - { - [HarmonyPostfix] - public static void Postfix(ref DiaNode __result, Pawn negotiator, Faction faction) - { - // Allies can help build roads - if (faction.PlayerRelationKind != FactionRelationKind.Ally) - { - return; - } - __result.options.Insert(0, RoadsOfTheRim.HelpRoadConstruction(faction, negotiator)); - } - } /* * Patching roads so they cancel all or part of the Tile.biome.movementDifficulty and Hilliness * The actual rates are stored in static method RoadsOfTheRim.calculateRoadModifier */ - [HarmonyPatch(typeof(WorldGrid), "GetRoadMovementDifficultyMultiplier")] - public static class Patch_WorldGrid_GetRoadMovementDifficultyMultiplier - { - private static readonly MethodInfo HillinessMovementDifficultyOffset = AccessTools.Method(typeof(WorldPathGrid), "HillinessMovementDifficultyOffset", new Type[] { typeof(Hilliness) }); - [HarmonyPostfix] - public static void Postifx(ref float __result, WorldGrid __instance, ref int fromTile, ref int toTile, ref StringBuilder explanation) - { - List roads = __instance.tiles[fromTile].Roads; - if (roads == null) - { - return; - } - if (toTile == -1) - { - toTile = __instance.FindMostReasonableAdjacentTileForDisplayedPathCost(fromTile); - } - - for (var i = 0; i < roads.Count; i++) - { - if (roads[i].neighbor != toTile) - { - continue; - } - Tile ToTileAsTile = Find.WorldGrid[toTile]; - var HillinessOffset = (float)HillinessMovementDifficultyOffset.Invoke(null, new object[] { ToTileAsTile.hilliness }); - if (HillinessOffset > 12f) { HillinessOffset = 12f; } - - // If the tile has an impassable biome, set the biomemovement difficulty to 12, as per the patch for CalculatedMovementDifficultyAt - var biomeMovementDifficulty = ToTileAsTile.biome.impassable ? 12f : ToTileAsTile.biome.movementDifficulty; - - // Calculate biome, Hillines & winter modifiers, update explanation & multiply result by biome modifier - var RoadModifier = RoadsOfTheRim.CalculateRoadModifier( - roads[i].road, - biomeMovementDifficulty, - HillinessOffset, - WorldPathGrid.GetCurrentWinterMovementDifficultyOffset(toTile), - out var BiomeModifier, - out var HillModifier, - out var WinterModifier - ); - var resultBefore = __result; - __result *= RoadModifier; - if (explanation != null) - { - explanation.AppendLine(); - explanation.Append(string.Format( - "The road cancels {0:P0} of the biome ({3:##.###}), {1:P0} of the hills ({4:##.###}) & {2:P0} of winter movement costs. Total modifier={5} applied to {6}", - BiomeModifier, HillModifier, WinterModifier, - biomeMovementDifficulty, HillinessOffset, RoadModifier, resultBefore - )); - } - return; - } - } - } - - [HarmonyPatch(typeof(WorldPathGrid), "CalculatedMovementDifficultyAt")] - static class Patch_WorldPathGrid_CalculatedMovementDifficultyAt - { - [HarmonyPostfix] - public static void PostFix(ref float __result, int tile, bool perceivedStatic, int? ticksAbs, StringBuilder explanation) - { - if (__result <= 999f || !Find.WorldGrid.InBounds(tile)) - { - return; - } - try - { - Tile tile2 = Find.WorldGrid[tile]; - if(tile2.Roads == null) - { - return; - } - RoadDef BestRoad = null; - foreach (var roadLink in tile2.Roads) - { - var currentRoad = roadLink.road; - if(currentRoad == null) - { - continue; - } - if (BestRoad == null) - { - BestRoad = currentRoad; - continue; - } - if (BestRoad.movementCostMultiplier < currentRoad.movementCostMultiplier) - { - BestRoad = roadLink.road; - } - } - if (BestRoad == null) - { - return; - } - DefModExtension_RotR_RoadDef roadDefExtension = BestRoad.GetModExtension(); - if (roadDefExtension == null) - { - return; - } - if ((tile2.biome.impassable && roadDefExtension.biomeModifier > 0) || tile2.hilliness == Hilliness.Impassable) - { - __result = 12f; - RoadsOfTheRim.DebugLog(string.Format("[RotR] - Impassable Tile {0} of biome {1} movement difficulty patched to 12", tile , tile2.biome.label)); - } - } - catch (Exception e) - { - RoadsOfTheRim.DebugLog($"[RotR] - CalculatedMovementDifficultyAt Patch - Catastrophic failure for tile {Find.WorldGrid[tile]}", e); - return; - } - } - } - - [HarmonyPatch(typeof(WorldTargeter), "StopTargeting")] - public static class Patch_WorldTargeter_StopTargeting - { - [HarmonyPrefix] - public static void Prefix() - { - if (RoadsOfTheRim.RoadBuildingState.CurrentlyTargeting == null) - { - return; - } - //RoadsOfTheRim.DebugLog("StopTargeting"); - RoadsOfTheRim.FinaliseConstructionSite(RoadsOfTheRim.RoadBuildingState.CurrentlyTargeting); - RoadsOfTheRim.RoadBuildingState.CurrentlyTargeting = null; - } - } - - [HarmonyPatch(typeof(CaravanUIUtility), "AddPawnsSections")] - /* - * Adds a Road equipment section to pawns & animals - */ - public static class Patch_CaravanUIUtility_AddPawnsSections - { - [HarmonyPostfix] - public static void Postfix(ref TransferableOneWayWidget widget, List transferables) - { - RoadsOfTheRim.DebugLog("DEBUG AddPawnsSection: "); - var source = new List(); - foreach (TransferableOneWay tow in transferables) - { - if (!tow.ThingDef.IsWithinCategory(ThingCategoryDef.Named("RoadEquipment"))) - { - continue; - } - source.Add(tow); - RoadsOfTheRim.DebugLog("Found an ISR2G"); - } - widget.AddSection("RoadsOfTheRim_RoadEquipment".Translate(), source); - } - } - - - [HarmonyPatch(typeof(CaravanUIUtility), "CreateCaravanTransferableWidgets")] - //Remove Road equipment from Item tab when forming caravans - public static class Patch_CaravanUIUtility_CreateCaravanTransferableWidgets - { - [HarmonyPostfix] - public static void Postfix(List transferables, ref TransferableOneWayWidget pawnsTransfer, ref TransferableOneWayWidget itemsTransfer, string thingCountTip, IgnorePawnsInventoryMode ignorePawnInventoryMass, Func availableMassGetter, bool ignoreSpawnedCorpsesGearAndInventoryMass, int tile, bool playerPawnsReadOnly) - { - var modifiedTransferables = transferables.Where((TransferableOneWay x) => x.ThingDef.category != ThingCategory.Pawn).ToList(); - modifiedTransferables = modifiedTransferables.Where(x => !x.ThingDef.IsWithinCategory(ThingCategoryDef.Named("RoadEquipment"))).ToList(); - itemsTransfer = new TransferableOneWayWidget(modifiedTransferables, null, null, thingCountTip, drawMass: true, ignorePawnInventoryMass, includePawnsMassInMassUsage: false, availableMassGetter, 0f, ignoreSpawnedCorpsesGearAndInventoryMass, tile, drawMarketValue: true, drawEquippedWeapon: false, drawNutritionEatenPerDay: false, drawItemNutrition: true, drawForagedFoodPerDay: false, drawDaysUntilRot: true); - } - } - - [HarmonyPatch(typeof(ThingFilter), "SetFromPreset")] - //Remove Road equipment from Item tab when forming caravans - public static class Patch_ThingFilter_SetFromPreset - { - [HarmonyPostfix] - public static void Postfix(ref ThingFilter __instance, StorageSettingsPreset preset) - { - if (preset != StorageSettingsPreset.DefaultStockpile) - { - return; - } - __instance.SetAllow(ThingCategoryDef.Named("RoadEquipment"), allow: true); - } - } // All Tiles can now have roads - [HarmonyPatch(typeof(Tile), "Roads", MethodType.Getter)] - public static class Patch_Tile_Roads - { - [HarmonyPostfix] - public static void Postfix(Tile __instance, ref List __result) - { - __result = __instance.potentialRoads; - } - } // When WorldLayer_Paths.AddPathEndPoint calls WaterCovered, it should return 1, not 0.5 /* @@ -423,4 +89,4 @@ static IEnumerable Transpiler(IEnumerable inst } } */ -} +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/OldDefsCleanup.cs b/Source/RoadsOfTheRim/OldDefsCleanupComp.cs similarity index 82% rename from Source/RoadsOfTheRim/OldDefsCleanup.cs rename to Source/RoadsOfTheRim/OldDefsCleanupComp.cs index 582b394..ca65660 100644 --- a/Source/RoadsOfTheRim/OldDefsCleanup.cs +++ b/Source/RoadsOfTheRim/OldDefsCleanupComp.cs @@ -19,15 +19,17 @@ public override void CompTick() level = 2; break; } + if (level <= 0) { return; } + var newThingDefName = level == 1 ? "RotR_ISR2GNew" : "RotR_AISR2GNew"; - Thing newThing = ThingMaker.MakeThing(ThingDef.Named(newThingDefName)); - IntVec3 position = oldISR2G.Position; - Map map = oldISR2G.MapHeld; - RoadsOfTheRim.DebugLog("Replacing a ISR2G level " + level + " at position " + position.ToString()); + var newThing = ThingMaker.MakeThing(ThingDef.Named(newThingDefName)); + var position = oldISR2G.Position; + var map = oldISR2G.MapHeld; + RoadsOfTheRim.DebugLog("Replacing a ISR2G level " + level + " at position " + position); oldISR2G.Destroy(); GenPlace.TryPlaceThing(newThing, position, map, ThingPlaceMode.Near); } diff --git a/Source/RoadsOfTheRim/Patch_Alert_CaravanIdle_GetExplanation.cs b/Source/RoadsOfTheRim/Patch_Alert_CaravanIdle_GetExplanation.cs new file mode 100644 index 0000000..a0be695 --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_Alert_CaravanIdle_GetExplanation.cs @@ -0,0 +1,30 @@ +using System.Text; +using HarmonyLib; +using RimWorld; +using Verse; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(Alert_CaravanIdle), "GetExplanation")] + public static class Patch_Alert_CaravanIdle_GetExplanation + { + [HarmonyPostfix] + public static void Postfix(ref TaggedString __result) + { + var stringBuilder = new StringBuilder(); + foreach (var caravan in Find.WorldObjects.Caravans) + { + var caravanComp = caravan.GetComponent(); + if (!caravan.Spawned || !caravan.IsPlayerControlled || caravan.pather.MovingNow || caravan.CantMove || + caravanComp.currentlyWorkingOnSite) + { + continue; + } + + stringBuilder.AppendLine(" - " + caravan.Label); + } + + __result = "CaravanIdleDesc".Translate(stringBuilder.ToString()); + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_Alert_CaravanIdle_GetReport.cs b/Source/RoadsOfTheRim/Patch_Alert_CaravanIdle_GetReport.cs new file mode 100644 index 0000000..9b54902 --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_Alert_CaravanIdle_GetReport.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using HarmonyLib; +using RimWorld; +using RimWorld.Planet; +using Verse; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(Alert_CaravanIdle), "GetReport")] + public static class Patch_Alert_CaravanIdle_GetReport + { + [HarmonyPostfix] + public static void Postfix(ref AlertReport __result) + { + var newList = new List(); + foreach (var caravan in Find.WorldObjects.Caravans) + { + var caravanComp = caravan.GetComponent(); + if (!caravan.Spawned || !caravan.IsPlayerControlled || caravan.pather.MovingNow || caravan.CantMove || + caravanComp.currentlyWorkingOnSite) + { + continue; + } + + newList.Add(caravan); + } + + __result = AlertReport.CulpritsAre(newList); + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_CaravanUIUtility_AddPawnsSections.cs b/Source/RoadsOfTheRim/Patch_CaravanUIUtility_AddPawnsSections.cs new file mode 100644 index 0000000..7d42ae6 --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_CaravanUIUtility_AddPawnsSections.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using HarmonyLib; +using RimWorld; +using RimWorld.Planet; +using Verse; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(CaravanUIUtility), "AddPawnsSections")] + /* + * Adds a Road equipment section to pawns & animals + */ + public static class Patch_CaravanUIUtility_AddPawnsSections + { + [HarmonyPostfix] + public static void Postfix(ref TransferableOneWayWidget widget, List transferables) + { + RoadsOfTheRim.DebugLog("DEBUG AddPawnsSection: "); + var source = new List(); + foreach (var tow in transferables) + { + if (!tow.ThingDef.IsWithinCategory(ThingCategoryDef.Named("RoadEquipment"))) + { + continue; + } + + source.Add(tow); + RoadsOfTheRim.DebugLog("Found an ISR2G"); + } + + widget.AddSection("RoadsOfTheRim_RoadEquipment".Translate(), source); + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_CaravanUIUtility_CreateCaravanTransferableWidgets.cs b/Source/RoadsOfTheRim/Patch_CaravanUIUtility_CreateCaravanTransferableWidgets.cs new file mode 100644 index 0000000..b15cbd5 --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_CaravanUIUtility_CreateCaravanTransferableWidgets.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using HarmonyLib; +using RimWorld; +using RimWorld.Planet; +using Verse; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(CaravanUIUtility), "CreateCaravanTransferableWidgets")] + //Remove Road equipment from Item tab when forming caravans + public static class Patch_CaravanUIUtility_CreateCaravanTransferableWidgets + { + [HarmonyPostfix] + public static void Postfix(List transferables, ref TransferableOneWayWidget pawnsTransfer, + ref TransferableOneWayWidget itemsTransfer, string thingCountTip, + IgnorePawnsInventoryMode ignorePawnInventoryMass, Func availableMassGetter, + bool ignoreSpawnedCorpsesGearAndInventoryMass, int tile, bool playerPawnsReadOnly) + { + var modifiedTransferables = transferables.Where(x => x.ThingDef.category != ThingCategory.Pawn).ToList(); + modifiedTransferables = modifiedTransferables + .Where(x => !x.ThingDef.IsWithinCategory(ThingCategoryDef.Named("RoadEquipment"))).ToList(); + itemsTransfer = new TransferableOneWayWidget(modifiedTransferables, null, null, thingCountTip, true, + ignorePawnInventoryMass, false, availableMassGetter, 0f, ignoreSpawnedCorpsesGearAndInventoryMass, tile, + true, false, false, true, false, true); + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_Caravan_GetGizmos.cs b/Source/RoadsOfTheRim/Patch_Caravan_GetGizmos.cs new file mode 100644 index 0000000..3f700d5 --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_Caravan_GetGizmos.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using HarmonyLib; +using RimWorld; +using RimWorld.Planet; +using Verse; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(Caravan), "GetGizmos")] + public static class Patch_Caravan_GetGizmos + { + [HarmonyPostfix] + public static void Postfix(ref IEnumerable __result, Caravan __instance) + { + var isThereAConstructionSiteHere = + Find.WorldObjects.AnyWorldObjectOfDefAt(DefDatabase.GetNamed("RoadConstructionSite"), + __instance.Tile); + var isTheCaravanWorkingOnASite = true; + try + { + isTheCaravanWorkingOnASite = __instance.GetComponent().currentlyWorkingOnSite; + } + catch (Exception e) + { + RoadsOfTheRim.DebugLog(null, e); + } + + __result = __result.Concat(new Gizmo[] {RoadsOfTheRim.AddConstructionSite(__instance)}) + .Concat(new Gizmo[] {RoadsOfTheRim.RemoveConstructionSite(__instance.Tile)}); + if (isThereAConstructionSiteHere & !isTheCaravanWorkingOnASite && + RoadsOfTheRim.RoadBuildingState.CurrentlyTargeting == null) + { + __result = __result.Concat(new Gizmo[] {RoadsOfTheRim.WorkOnSite(__instance)}); + } + + if (isTheCaravanWorkingOnASite) + { + __result = __result.Concat(new Gizmo[] {RoadsOfTheRim.StopWorkingOnSite(__instance)}); + } + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_Caravan_GetInspectString.cs b/Source/RoadsOfTheRim/Patch_Caravan_GetInspectString.cs new file mode 100644 index 0000000..6bdf400 --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_Caravan_GetInspectString.cs @@ -0,0 +1,73 @@ +using System; +using System.Text; +using HarmonyLib; +using RimWorld.Planet; +using Verse; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(Caravan), "GetInspectString")] + public static class Patch_Caravan_GetInspectString + { + [HarmonyPostfix] + public static void Postfix(ref string __result, Caravan __instance) + { + try + { + var CaravanComp = __instance.GetComponent(); + if (CaravanComp == null || !CaravanComp.currentlyWorkingOnSite) + { + return; + } + + var stringBuilder = new StringBuilder(); + stringBuilder.Append(__result); + // remove "waiting" + var waitingIndex = stringBuilder.ToString().IndexOf("CaravanWaiting".Translate()); + if (waitingIndex >= 0) + { + stringBuilder.Remove(waitingIndex, "CaravanWaiting".Translate().Length); + } + + // remove "resting (using x bedrolls)" + var usedBedCount = __instance.beds.GetUsedBedCount(); + int bedrollIndex; + string stringToFind; + if (usedBedCount == 1) + { + // remove singular version + stringToFind = " (" + (string) "UsingBedroll".Translate() + ")"; + bedrollIndex = stringBuilder.ToString().IndexOf(stringToFind, StringComparison.Ordinal); + } + else + { + // remove plural version + stringToFind = " (" + (string) "UsingBedrolls".Translate(usedBedCount) + ")"; + bedrollIndex = stringBuilder.ToString().IndexOf(stringToFind, StringComparison.Ordinal); + } + + if (bedrollIndex >= 0) + { + stringBuilder.Remove(bedrollIndex, stringToFind.Length); + var restingIndex = stringBuilder.ToString().IndexOf("CaravanResting".Translate()); + if (restingIndex >= 0) + { + stringBuilder.Remove(restingIndex, "CaravanResting".Translate().Length); + } + } + + // Appending "working on road" + stringBuilder.Replace("\n", ""); + stringBuilder.Replace("\r", ""); + stringBuilder.AppendLine(); + stringBuilder.Append("RoadsOfTheRim_CaravanInspectStringWorkingOn".Translate( + CaravanComp.GetSite().FullName(), $"{CaravanComp.AmountOfWork():0.00}")); + __result = stringBuilder.ToString(); + } + catch + { + // lazy way out : the caravan can, on occasions (mainly debug teleport, though...), not have a site linked to the comp + } + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_Designator_RemoveBridge_CanDesignateCell.cs b/Source/RoadsOfTheRim/Patch_Designator_RemoveBridge_CanDesignateCell.cs new file mode 100644 index 0000000..e63ea05 --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_Designator_RemoveBridge_CanDesignateCell.cs @@ -0,0 +1,22 @@ +using HarmonyLib; +using RimWorld; +using Verse; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(Designator_RemoveBridge), "CanDesignateCell")] + public static class Patch_Designator_RemoveBridge_CanDesignateCell + { + [HarmonyPostfix] + public static void Postfix(ref AcceptanceReport __result, Designator_RemoveBridge __instance, IntVec3 c) + { + if (!c.InBounds(__instance.Map) || c.GetTerrain(__instance.Map) != TerrainDefOf.ConcreteBridge) + { + return; + } + + __result = true; + RoadsOfTheRim.DebugLog(c.GetTerrain(__instance.Map).label); + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_FactionDialogMaker_FactionDialogFor.cs b/Source/RoadsOfTheRim/Patch_FactionDialogMaker_FactionDialogFor.cs new file mode 100644 index 0000000..b37331b --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_FactionDialogMaker_FactionDialogFor.cs @@ -0,0 +1,22 @@ +using HarmonyLib; +using RimWorld; +using Verse; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(FactionDialogMaker), "FactionDialogFor")] + public static class Patch_FactionDialogMaker_FactionDialogFor + { + [HarmonyPostfix] + public static void Postfix(ref DiaNode __result, Pawn negotiator, Faction faction) + { + // Allies can help build roads + if (faction.PlayerRelationKind != FactionRelationKind.Ally) + { + return; + } + + __result.options.Insert(0, RoadsOfTheRim.HelpRoadConstruction(faction, negotiator)); + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_GenConstruct_CanBuildOnTerrain.cs b/Source/RoadsOfTheRim/Patch_GenConstruct_CanBuildOnTerrain.cs new file mode 100644 index 0000000..4e8153e --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_GenConstruct_CanBuildOnTerrain.cs @@ -0,0 +1,27 @@ +using HarmonyLib; +using RimWorld; +using Verse; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(GenConstruct), "CanBuildOnTerrain")] + public static class Patch_GenConstruct_CanBuildOnTerrain + { + [HarmonyPostfix] + public static void Postfix(ref bool __result, BuildableDef entDef, IntVec3 c, Map map) + { + if (entDef != TerrainDefOf.ConcreteBridge && entDef != TerrainDefOf.AsphaltRecent && + entDef != TerrainDefOf.GlitterRoad) + { + return; + } + + if (!map.terrainGrid.TerrainAt(c).IsWater) + { + return; + } + + __result = true; + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_GenConstruct_CanPlaceBlueprintAt.cs b/Source/RoadsOfTheRim/Patch_GenConstruct_CanPlaceBlueprintAt.cs new file mode 100644 index 0000000..0295f02 --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_GenConstruct_CanPlaceBlueprintAt.cs @@ -0,0 +1,23 @@ +using HarmonyLib; +using RimWorld; +using Verse; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(GenConstruct), "CanPlaceBlueprintAt")] + public static class Patch_GenConstruct_CanPlaceBlueprintAt + { + [HarmonyPostfix] + public static void Postfix(ref AcceptanceReport __result, BuildableDef entDef, IntVec3 center, Rot4 rot, + Map map, bool godMode = false, Thing thingToIgnore = null, Thing thing = null, ThingDef stuffDef = null) + { + if (entDef != TerrainDefOf.ConcreteBridge || !map.terrainGrid.TerrainAt(center).affordances + .Contains(TerrainAffordanceDefOf.Bridgeable)) // ConcreteBridge on normal water (bridgeable) + { + return; + } + + __result = AcceptanceReport.WasAccepted; + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_RoadDefGenStep_Place_Place.cs b/Source/RoadsOfTheRim/Patch_RoadDefGenStep_Place_Place.cs new file mode 100644 index 0000000..0e3d8f2 --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_RoadDefGenStep_Place_Place.cs @@ -0,0 +1,41 @@ +using HarmonyLib; +using RimWorld; +using Verse; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(RoadDefGenStep_Place), "Place")] + public static class Patch_RoadDefGenStep_Place_Place + { + public static bool IsGoodTerrain(TerrainDef terrain) + { + return terrain == TerrainDefOf.Mud || terrain == TerrainDefOf.MarshyTerrain; + } + + [HarmonyPostfix] + public static void Postfix(ref RoadDefGenStep_Place __instance, Map map, IntVec3 position, TerrainDef rockDef, + IntVec3 origin, GenStep_Roads.DistanceElement[,] distance) + { + if (__instance.place == TerrainDefOf.ConcreteBridge && position.GetTerrain(map).IsWater) + { + map.terrainGrid.SetTerrain(position, TerrainDefOf.ConcreteBridge); + } + + if (__instance.place == TerrainDefOf.GlitterRoad && + (IsGoodTerrain(position.GetTerrain(map)) || position.GetTerrain(map).IsWater)) + { + map.terrainGrid.SetTerrain(position, TerrainDefOf.GlitterRoad); + } + + if (__instance.place == TerrainDefOf.AsphaltRecent && IsGoodTerrain(position.GetTerrain(map))) + { + map.terrainGrid.SetTerrain(position, TerrainDefOf.AsphaltRecent); + } + + if (__instance.place == TerrainDefOf.StoneRecent && IsGoodTerrain(position.GetTerrain(map))) + { + map.terrainGrid.SetTerrain(position, TerrainDefOf.StoneRecent); + } + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_ThingFilter_SetFromPreset.cs b/Source/RoadsOfTheRim/Patch_ThingFilter_SetFromPreset.cs new file mode 100644 index 0000000..ed38b4e --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_ThingFilter_SetFromPreset.cs @@ -0,0 +1,22 @@ +using HarmonyLib; +using RimWorld; +using Verse; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(ThingFilter), "SetFromPreset")] + //Remove Road equipment from Item tab when forming caravans + public static class Patch_ThingFilter_SetFromPreset + { + [HarmonyPostfix] + public static void Postfix(ref ThingFilter __instance, StorageSettingsPreset preset) + { + if (preset != StorageSettingsPreset.DefaultStockpile) + { + return; + } + + __instance.SetAllow(ThingCategoryDef.Named("RoadEquipment"), true); + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_Tile_Roads.cs b/Source/RoadsOfTheRim/Patch_Tile_Roads.cs new file mode 100644 index 0000000..027a7f4 --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_Tile_Roads.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using HarmonyLib; +using RimWorld.Planet; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(Tile), "Roads", MethodType.Getter)] + public static class Patch_Tile_Roads + { + [HarmonyPostfix] + public static void Postfix(Tile __instance, ref List __result) + { + __result = __instance.potentialRoads; + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_WorldGrid_GetRoadMovementDifficultyMultiplier.cs b/Source/RoadsOfTheRim/Patch_WorldGrid_GetRoadMovementDifficultyMultiplier.cs new file mode 100644 index 0000000..50f0970 --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_WorldGrid_GetRoadMovementDifficultyMultiplier.cs @@ -0,0 +1,75 @@ +using System.Reflection; +using System.Text; +using HarmonyLib; +using RimWorld.Planet; +using Verse; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(WorldGrid), "GetRoadMovementDifficultyMultiplier")] + public static class Patch_WorldGrid_GetRoadMovementDifficultyMultiplier + { + private static readonly MethodInfo HillinessMovementDifficultyOffset = AccessTools.Method(typeof(WorldPathGrid), + "HillinessMovementDifficultyOffset", new[] {typeof(Hilliness)}); + + [HarmonyPostfix] + public static void Postifx(ref float __result, WorldGrid __instance, ref int fromTile, ref int toTile, + ref StringBuilder explanation) + { + var roads = __instance.tiles[fromTile].Roads; + if (roads == null) + { + return; + } + + if (toTile == -1) + { + toTile = __instance.FindMostReasonableAdjacentTileForDisplayedPathCost(fromTile); + } + + for (var i = 0; i < roads.Count; i++) + { + if (roads[i].neighbor != toTile) + { + continue; + } + + var ToTileAsTile = Find.WorldGrid[toTile]; + var HillinessOffset = + (float) HillinessMovementDifficultyOffset.Invoke(null, new object[] {ToTileAsTile.hilliness}); + if (HillinessOffset > 12f) + { + HillinessOffset = 12f; + } + + // If the tile has an impassable biome, set the biomemovement difficulty to 12, as per the patch for CalculatedMovementDifficultyAt + var biomeMovementDifficulty = + ToTileAsTile.biome.impassable ? 12f : ToTileAsTile.biome.movementDifficulty; + + // Calculate biome, Hillines & winter modifiers, update explanation & multiply result by biome modifier + var RoadModifier = RoadsOfTheRim.CalculateRoadModifier( + roads[i].road, + biomeMovementDifficulty, + HillinessOffset, + WorldPathGrid.GetCurrentWinterMovementDifficultyOffset(toTile), + out var BiomeModifier, + out var HillModifier, + out var WinterModifier + ); + var resultBefore = __result; + __result *= RoadModifier; + if (explanation != null) + { + explanation.AppendLine(); + explanation.Append(string.Format( + "The road cancels {0:P0} of the biome ({3:##.###}), {1:P0} of the hills ({4:##.###}) & {2:P0} of winter movement costs. Total modifier={5} applied to {6}", + BiomeModifier, HillModifier, WinterModifier, + biomeMovementDifficulty, HillinessOffset, RoadModifier, resultBefore + )); + } + + return; + } + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_WorldPathGrid_CalculatedMovementDifficultyAt.cs b/Source/RoadsOfTheRim/Patch_WorldPathGrid_CalculatedMovementDifficultyAt.cs new file mode 100644 index 0000000..3559c43 --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_WorldPathGrid_CalculatedMovementDifficultyAt.cs @@ -0,0 +1,75 @@ +using System; +using System.Text; +using HarmonyLib; +using RimWorld; +using RimWorld.Planet; +using Verse; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(WorldPathGrid), "CalculatedMovementDifficultyAt")] + internal static class Patch_WorldPathGrid_CalculatedMovementDifficultyAt + { + [HarmonyPostfix] + public static void PostFix(ref float __result, int tile, bool perceivedStatic, int? ticksAbs, + StringBuilder explanation) + { + if (__result <= 999f || !Find.WorldGrid.InBounds(tile)) + { + return; + } + + try + { + var tile2 = Find.WorldGrid[tile]; + if (tile2.Roads == null) + { + return; + } + + RoadDef BestRoad = null; + foreach (var roadLink in tile2.Roads) + { + var currentRoad = roadLink.road; + if (currentRoad == null) + { + continue; + } + + if (BestRoad == null) + { + BestRoad = currentRoad; + continue; + } + + if (BestRoad.movementCostMultiplier < currentRoad.movementCostMultiplier) + { + BestRoad = roadLink.road; + } + } + + var roadDefExtension = BestRoad?.GetModExtension(); + if (roadDefExtension == null) + { + return; + } + + if ((!tile2.biome.impassable || !(roadDefExtension.biomeModifier > 0)) && + tile2.hilliness != Hilliness.Impassable) + { + return; + } + + __result = 12f; + RoadsOfTheRim.DebugLog( + $"[RotR] - Impassable Tile {tile} of biome {tile2.biome.label} movement difficulty patched to 12"); + } + catch (Exception e) + { + RoadsOfTheRim.DebugLog( + $"[RotR] - CalculatedMovementDifficultyAt Patch - Catastrophic failure for tile {Find.WorldGrid[tile]}", + e); + } + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Patch_WorldTargeter_StopTargeting.cs b/Source/RoadsOfTheRim/Patch_WorldTargeter_StopTargeting.cs new file mode 100644 index 0000000..f9fda36 --- /dev/null +++ b/Source/RoadsOfTheRim/Patch_WorldTargeter_StopTargeting.cs @@ -0,0 +1,22 @@ +using HarmonyLib; +using RimWorld; + +namespace RoadsOfTheRim +{ + [HarmonyPatch(typeof(WorldTargeter), "StopTargeting")] + public static class Patch_WorldTargeter_StopTargeting + { + [HarmonyPrefix] + public static void Prefix() + { + if (RoadsOfTheRim.RoadBuildingState.CurrentlyTargeting == null) + { + return; + } + + //RoadsOfTheRim.DebugLog("StopTargeting"); + RoadsOfTheRim.FinaliseConstructionSite(RoadsOfTheRim.RoadBuildingState.CurrentlyTargeting); + RoadsOfTheRim.RoadBuildingState.CurrentlyTargeting = null; + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/PawnBuildingUtility.cs b/Source/RoadsOfTheRim/PawnBuildingUtility.cs new file mode 100644 index 0000000..c6f6f93 --- /dev/null +++ b/Source/RoadsOfTheRim/PawnBuildingUtility.cs @@ -0,0 +1,92 @@ +using RimWorld; +using Verse; + +namespace RoadsOfTheRim +{ + public static class PawnBuildingUtility + { + public static bool HealthyColonist(Pawn p) + { + return p.IsFreeColonist && p.health.State == PawnHealthState.Mobile; + } + + public static bool HealthyPackAnimal(Pawn p) + { + return p.RaceProps.packAnimal && p.health.State == PawnHealthState.Mobile; + } + + public static float ConstructionValue(Pawn p) + { + return p.GetStatValue(StatDefOf.ConstructionSpeed) * p.GetStatValue(StatDefOf.ConstructSuccessChance); + } + + public static int ConstructionLevel(Pawn p) + { + return p.skills.GetSkill(SkillDefOf.Construction).levelInt; + } + + public static string ShowConstructionValue(Pawn p) + { + if (HealthyColonist(p)) + { + return $"{ConstructionValue(p):0.##}"; + } + + if (HealthyPackAnimal(p)) + { + return $"+{ConstructionValue(p):0.##}"; + } + + return "-"; + } + + public static string ShowSkill(Pawn p) + { + if (HealthyColonist(p)) + { + return $"{ConstructionLevel(p):0}"; + } + + return "-"; + } + + public static string ShowBestRoad(Pawn p) + { + RoadDef BestRoadDef = null; + if (!HealthyColonist(p)) + { + return "-"; + } + + foreach (var thisDef in DefDatabase.AllDefs) + { + if (!thisDef.HasModExtension() || + !thisDef.GetModExtension().built + ) // Only add RoadDefs that are buildable, based on DefModExtension_RotR_RoadDef.built + { + continue; + } + + var RoadDefMod = thisDef.GetModExtension(); + if (ConstructionLevel(p) < RoadDefMod.minConstruction) + { + continue; + } + + if (BestRoadDef != null && thisDef.movementCostMultiplier >= BestRoadDef.movementCostMultiplier) + { + continue; + } + + BestRoadDef = thisDef; + } + + if (BestRoadDef == null) + { + return "-"; + } + + return BestRoadDef.label; + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/Properties/AssemblyInfo.cs b/Source/RoadsOfTheRim/Properties/AssemblyInfo.cs deleted file mode 100644 index e1da6c4..0000000 --- a/Source/RoadsOfTheRim/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Reflection; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. - -[assembly: AssemblyTitle("RoadsOfTheRim")] -[assembly: AssemblyDescription("A Rimworld mod that adds the ability to build roads on the World Map")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("LocoNeko under MIT License")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. - -[assembly: AssemblyVersion("2.1.*")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("")] diff --git a/Source/RoadsOfTheRim/RoadConstructionLeg.cs b/Source/RoadsOfTheRim/RoadConstructionLeg.cs index d249091..7476cd2 100644 --- a/Source/RoadsOfTheRim/RoadConstructionLeg.cs +++ b/Source/RoadsOfTheRim/RoadConstructionLeg.cs @@ -1,42 +1,33 @@ -using System ; -using System.Text; -using System.Collections.Generic ; +using System; +using System.Collections.Generic; using System.Linq; -using RimWorld ; +using System.Text; +using RimWorld; using RimWorld.Planet; -using Verse ; using UnityEngine; +using Verse; namespace RoadsOfTheRim { public class RoadConstructionLeg : WorldObject { - private RoadConstructionSite site ; - - private RoadConstructionLeg previous ; - private RoadConstructionLeg next; - public override void ExposeData() - { - base.ExposeData(); - Scribe_References.Look(ref site, "site"); - Scribe_References.Look(ref previous, "previous"); - Scribe_References.Look(ref next, "next"); - } + private RoadConstructionLeg previous; + private RoadConstructionSite site; public override Material Material { get { - if (next==null) + if (next == null) { // This alternate Material : goal flag return RotR_StaticConstructorOnStartup.ConstructionLegLast_Material; } - return base.Material ; - } + return base.Material; + } } public RoadConstructionLeg Previous @@ -51,6 +42,14 @@ public RoadConstructionLeg Next set => next = value; } + public override void ExposeData() + { + base.ExposeData(); + Scribe_References.Look(ref site, "site"); + Scribe_References.Look(ref previous, "previous"); + Scribe_References.Look(ref next, "next"); + } + public RoadConstructionSite GetSite() { return site; @@ -64,30 +63,38 @@ public override string GetInspectString() { stringBuilder.AppendLine(); } + if (Next is null) { stringBuilder.Append("Goal"); } else { - stringBuilder.Append("RoadsOfTheRim_siteInspectString".Translate(GetSite().roadDef.label, string.Format("{0:0.0}", GetSite().roadDef.movementCostMultiplier))); + stringBuilder.Append("RoadsOfTheRim_siteInspectString".Translate(GetSite().roadDef.label, + $"{GetSite().roadDef.movementCostMultiplier:0.0}")); var totalCostModifier = 0f; - stringBuilder.Append(WorldObjectComp_ConstructionSite.CostModifersDescription(Tile , Next.Tile , ref totalCostModifier)); + stringBuilder.Append( + WorldObjectComp_ConstructionSite.CostModifersDescription(Tile, Next.Tile, ref totalCostModifier)); // Show costs - WorldObjectComp_ConstructionSite SiteComp = GetSite().GetComponent(); + var SiteComp = GetSite().GetComponent(); foreach (var resourceName in DefModExtension_RotR_RoadDef.allResourcesAndWork) { if (SiteComp.GetCost(resourceName) <= 0) { continue; } + // The cost modifier doesn't affect some advanced resources, as defined in static DefModExtension_RotR_RoadDef.allResourcesWithoutModifiers // TO DO : COuld this be combined with WorldObjectComp_ConstructionSite.setCosts() ? shares a lot in common except rebates. Can we really calcualte rebate on a leg ? - var costModifierForThisResource = DefModExtension_RotR_RoadDef.allResourcesWithoutModifiers.Contains(resourceName) ? 1 : totalCostModifier; + var costModifierForThisResource = + DefModExtension_RotR_RoadDef.allResourcesWithoutModifiers.Contains(resourceName) + ? 1 + : totalCostModifier; stringBuilder.AppendLine(); - stringBuilder.Append((SiteComp.GetCost(resourceName) * costModifierForThisResource) + " " + resourceName); + stringBuilder.Append((SiteComp.GetCost(resourceName) * costModifierForThisResource) + " " + + resourceName); } } @@ -101,29 +108,35 @@ public override string GetInspectString() // Yes -> We are done creating the site // No -> delete this leg and all legs after it // No -> create a new Leg - public static bool ActionOnTile(RoadConstructionSite site , int tile) + private static bool ActionOnTile(RoadConstructionSite site, int tile) { - if (site.def != DefDatabase.GetNamed("RoadConstructionSite", true)) + if (site.def != DefDatabase.GetNamed("RoadConstructionSite")) { Log.Error("[RotR] - The RoadConstructionSite given is somehow wrong"); - return true ; + return true; } + try { - foreach (WorldObject o in Find.WorldObjects.ObjectsAt(tile)) + foreach (var o in Find.WorldObjects.ObjectsAt(tile)) { // Action on the construction site = we're done - if ( (o.def == DefDatabase.GetNamed("RoadConstructionSite", true)) && (RoadConstructionSite)o == site) + if (o.def == DefDatabase.GetNamed("RoadConstructionSite") && + (RoadConstructionSite) o == site) { - return true; + return true; } + // Action on a leg that's part of this chain = we should delete all legs after that & keep targetting - if ((o.def == DefDatabase.GetNamed("RoadConstructionLeg", true)) && ((RoadConstructionLeg)o).site == site) + if (o.def != DefDatabase.GetNamed("RoadConstructionLeg") || + ((RoadConstructionLeg) o).site != site) { - Remove((RoadConstructionLeg)o); - Target(site); - return false; + continue; } + + Remove((RoadConstructionLeg) o); + Target(site); + return false; } // Check whether we clicked on a neighbour @@ -137,36 +150,44 @@ public static bool ActionOnTile(RoadConstructionSite site , int tile) } // There can be no ConstructionLeg on a biome that doesn't allow roads - if (!DefModExtension_RotR_RoadDef.BiomeAllowed(tile , site.roadDef , out BiomeDef biomeHere)) + if (!DefModExtension_RotR_RoadDef.BiomeAllowed(tile, site.roadDef, out var biomeHere)) { - Messages.Message("RoadsOfTheRim_BiomePreventsConstruction".Translate(site.roadDef.label , biomeHere.label) , MessageTypeDefOf.RejectInput); + Messages.Message( + "RoadsOfTheRim_BiomePreventsConstruction".Translate(site.roadDef.label, biomeHere.label), + MessageTypeDefOf.RejectInput); Target(site); - return false ; + return false; } - else if (!DefModExtension_RotR_RoadDef.ImpassableAllowed(tile , site.roadDef)) + + if (!DefModExtension_RotR_RoadDef.ImpassableAllowed(tile, site.roadDef)) { - Messages.Message("RoadsOfTheRim_BiomePreventsConstruction".Translate(site.roadDef.label, " impassable mountains"), MessageTypeDefOf.RejectInput); + Messages.Message( + "RoadsOfTheRim_BiomePreventsConstruction".Translate(site.roadDef.label, + " impassable mountains"), MessageTypeDefOf.RejectInput); Target(site); return false; } - var newLeg = (RoadConstructionLeg)WorldObjectMaker.MakeWorldObject(DefDatabase.GetNamed("RoadConstructionLeg", true)); + var newLeg = + (RoadConstructionLeg) WorldObjectMaker.MakeWorldObject( + DefDatabase.GetNamed("RoadConstructionLeg")); newLeg.Tile = tile; newLeg.site = site; // This is not the first Leg - if (site.LastLeg.def == DefDatabase.GetNamed("RoadConstructionLeg", true)) + if (site.LastLeg.def == DefDatabase.GetNamed("RoadConstructionLeg")) { var l = site.LastLeg as RoadConstructionLeg; - l.SetNext(newLeg); + l?.SetNext(newLeg); newLeg.previous = l; } else { newLeg.previous = null; } + newLeg.SetNext(null); Find.WorldObjects.Add(newLeg); - site.LastLeg = newLeg ; + site.LastLeg = newLeg; Target(site); return false; } @@ -180,9 +201,9 @@ public static bool ActionOnTile(RoadConstructionSite site , int tile) public override void Draw() { base.Draw(); - WorldGrid worldGrid = Find.WorldGrid; - Vector3 fromPos = worldGrid.GetTileCenter(Tile); - Vector3 toPos = (previous != null) ? worldGrid.GetTileCenter(previous.Tile) : worldGrid.GetTileCenter(site.Tile); + var worldGrid = Find.WorldGrid; + var fromPos = worldGrid.GetTileCenter(Tile); + var toPos = previous != null ? worldGrid.GetTileCenter(previous.Tile) : worldGrid.GetTileCenter(site.Tile); var d = 0.05f; fromPos += fromPos.normalized * d; toPos += toPos.normalized * d; @@ -191,44 +212,39 @@ public override void Draw() // Site---Leg---Leg---Leg---Leg---Goal } - public void SetNext(RoadConstructionLeg nextLeg) + private void SetNext(RoadConstructionLeg nextLeg) { try { - next = nextLeg ; + next = nextLeg; } catch (Exception e) { - Log.Error("[RotR] Exception : "+e); + Log.Error("[RotR] Exception : " + e); } } public static void Target(RoadConstructionSite site) { // Log.Warning("[RotR] - Target(site)"); - Find.WorldTargeter.BeginTargeting(delegate (GlobalTargetInfo target) - { - return ActionOnTile(site, target.Tile); - }, - true, RotR_StaticConstructorOnStartup.ConstructionLeg_MouseAttachment , false, null , - delegate (GlobalTargetInfo target) - { - return "RoadsOfTheRim_BuildToHere".Translate(); - }); + Find.WorldTargeter.BeginTargeting( + delegate(GlobalTargetInfo target) { return ActionOnTile(site, target.Tile); }, + true, RotR_StaticConstructorOnStartup.ConstructionLeg_MouseAttachment, false, null, + delegate { return "RoadsOfTheRim_BuildToHere".Translate(); }); } /* * Remove all legs up to and including the one passed in argument - */ + */ public static void Remove(RoadConstructionLeg leg) { - RoadConstructionSite site = leg.site; - var CurrentLeg = (RoadConstructionLeg)site.LastLeg; + var site = leg.site; + var CurrentLeg = (RoadConstructionLeg) site.LastLeg; while (CurrentLeg != leg.previous) { - if (CurrentLeg.previous!=null) + if (CurrentLeg.previous != null) { - RoadConstructionLeg PreviousLeg = CurrentLeg.previous; + var PreviousLeg = CurrentLeg.previous; PreviousLeg.SetNext(null); site.LastLeg = PreviousLeg; Find.WorldObjects.Remove(CurrentLeg); diff --git a/Source/RoadsOfTheRim/RoadConstructionSite.cs b/Source/RoadsOfTheRim/RoadConstructionSite.cs index c715100..78950cb 100644 --- a/Source/RoadsOfTheRim/RoadConstructionSite.cs +++ b/Source/RoadsOfTheRim/RoadConstructionSite.cs @@ -1,6 +1,7 @@ -using System.Text; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Text; using RimWorld; using RimWorld.Planet; using UnityEngine; @@ -8,28 +9,13 @@ namespace RoadsOfTheRim { - public class SettlementInfo // Convenience class to store Settlements and their distance to the Site - { - public Settlement settlement; - - public int distance; - - public SettlementInfo(Settlement s, int d) - { - settlement = s; - distance = d; - } - } - public class RoadConstructionSite : WorldObject { - public RoadDef roadDef; // TO DO as part of the phasing out of road buildable + private static readonly int maxTicksToNeighbour = 2 * GenDate.TicksPerDay; // 2 days - public static int maxTicksToNeighbour = 2 * GenDate.TicksPerDay; // 2 days + private static readonly int maxNeighbourDistance = 100; // search 100 tiles away - public static int maxNeighbourDistance = 100; // search 100 tiles away - - public static int MaxSettlementsInDescription = 5; + private static readonly int MaxSettlementsInDescription = 5; private static readonly Color ColorTransparent = new Color(0.0f, 0.0f, 0.0f, 0f); @@ -37,13 +23,7 @@ public class RoadConstructionSite : WorldObject private static readonly Color ColorUnfilled = new Color(0.3f, 0.3f, 0.3f, 1f); - private Material ProgressBarMaterial; - - public List listOfSettlements; - - public string NeighbouringSettlementsDescription; - - public WorldObject LastLeg; + public float helpAmount; // How much will the faction help /* Factions help @@ -57,62 +37,80 @@ Factions help public int helpFromTick; // From when will the faction help - public float helpAmount; // How much will the faction help - public float helpWorkPerTick; // How much will the faction help per tick + public WorldObject LastLeg; + + private List listOfSettlements; + + private string NeighbouringSettlementsDescription; + + private Material ProgressBarMaterial; + public RoadDef roadDef; // TO DO as part of the phasing out of road buildable + public static void DeleteSite(RoadConstructionSite site) { - IEnumerable constructionLegs = Find.WorldObjects.AllWorldObjects.Cast().Where( - leg => leg.def == DefDatabase.GetNamed("RoadConstructionLeg", true) && - ((RoadConstructionLeg)leg).GetSite() == site + IEnumerable constructionLegs = Find.WorldObjects.AllWorldObjects.Where( + leg => leg.def == DefDatabase.GetNamed("RoadConstructionLeg") && + ((RoadConstructionLeg) leg).GetSite() == site ).ToArray(); - foreach (RoadConstructionLeg l in constructionLegs) + foreach (var o in constructionLegs) { + var l = (RoadConstructionLeg) o; Find.WorldObjects.Remove(l); } + Find.WorldObjects.Remove(site); } public override IEnumerable GetGizmos() { - foreach (Gizmo g in base.GetGizmos()) + foreach (var g in base.GetGizmos()) { yield return g; g.disabledReason = null; } + // Ability to remove the construction site without needing to go there with a Caravan. yield return RoadsOfTheRim.RemoveConstructionSite(Tile); - yield break; } - public void InitListOfSettlements() + private void InitListOfSettlements() { if (listOfSettlements != null) { return; } + listOfSettlements = NeighbouringSettlements(); } - public void PopulateDescription() + private void PopulateDescription() { InitListOfSettlements(); var s = new List(); - if ((listOfSettlements != null) && (listOfSettlements.Count > 0)) + if (listOfSettlements != null && listOfSettlements.Count > 0) { - foreach (SettlementInfo si in listOfSettlements.Take(MaxSettlementsInDescription)) + foreach (var si in listOfSettlements.Take(MaxSettlementsInDescription)) { - s.Add("RoadsOfTheRim_siteDescription".Translate(si.settlement.Name, string.Format("{0:0.00}", si.distance / (float)GenDate.TicksPerDay))); + s.Add("RoadsOfTheRim_siteDescription".Translate(si.settlement.Name, + $"{si.distance / (float) GenDate.TicksPerDay:0.00}")); } } + NeighbouringSettlementsDescription = string.Join(", ", s.ToArray()); RoadsOfTheRim.DebugLog(NeighbouringSettlementsDescription); - if (listOfSettlements.Count <= MaxSettlementsInDescription) + if (listOfSettlements != null && listOfSettlements.Count <= MaxSettlementsInDescription) { return; } - NeighbouringSettlementsDescription += "RoadsOfTheRim_siteDescriptionExtra".Translate(listOfSettlements.Count - MaxSettlementsInDescription); + + if (listOfSettlements != null) + { + NeighbouringSettlementsDescription += + "RoadsOfTheRim_siteDescriptionExtra".Translate( + listOfSettlements.Count - MaxSettlementsInDescription); + } } public string FullName() @@ -122,42 +120,47 @@ public string FullName() { PopulateDescription(); } + var result = new StringBuilder(); result.Append("RoadsOfTheRim_siteFullName".Translate(roadDef.label)); if (NeighbouringSettlementsDescription.Length > 0) { result.Append("RoadsOfTheRim_siteFullNameNeighbours".Translate(NeighbouringSettlementsDescription)); } + return result.ToString(); } - public List NeighbouringSettlements() + private List NeighbouringSettlements() { if (Tile == -1) { return null; } + var result = new List(); - var tileSearched = new List(); SearchForSettlements(Tile, ref result); return result.OrderBy(si => si.distance).ToList(); } - public void SearchForSettlements(int startTile, ref List settlementsSearched) + private void SearchForSettlements(int startTile, ref List settlementsSearched) { - var timer = System.Diagnostics.Stopwatch.StartNew(); - WorldGrid worldGrid = Find.WorldGrid; - foreach (Settlement s in Find.WorldObjects.Settlements) + var timer = Stopwatch.StartNew(); + var worldGrid = Find.WorldGrid; + foreach (var s in Find.WorldObjects.Settlements) { - if (worldGrid.ApproxDistanceInTiles(startTile, s.Tile) <= maxNeighbourDistance) + if (!(worldGrid.ApproxDistanceInTiles(startTile, s.Tile) <= maxNeighbourDistance)) { - var distance = CaravanArrivalTimeEstimator.EstimatedTicksToArrive(startTile, s.Tile, null); - if (distance <= maxTicksToNeighbour) - { - settlementsSearched.Add(new SettlementInfo(s, distance)); - } + continue; + } + + var distance = CaravanArrivalTimeEstimator.EstimatedTicksToArrive(startTile, s.Tile, null); + if (distance <= maxTicksToNeighbour) + { + settlementsSearched.Add(new SettlementInfo(s, distance)); } } + timer.Stop(); RoadsOfTheRim.DebugLog("Time spent searching for settlements : " + timer.ElapsedMilliseconds + "ms"); } @@ -167,21 +170,29 @@ public SettlementInfo ClosestSettlementOfFaction(Faction faction) InitListOfSettlements(); var travelTicks = maxTicksToNeighbour; SettlementInfo closestSettlement = null; - if (listOfSettlements != null) + if (listOfSettlements == null) + { + return null; + } + + foreach (var si in listOfSettlements) { - foreach (SettlementInfo si in listOfSettlements) + if (si.settlement.Faction != faction) { - if (si.settlement.Faction == faction) - { - var travelTicksFromHere = CaravanArrivalTimeEstimator.EstimatedTicksToArrive(si.settlement.Tile, Tile, null); - if (travelTicksFromHere < travelTicks) - { - closestSettlement = si; - travelTicks = travelTicksFromHere; - } - } + continue; } + + var travelTicksFromHere = + CaravanArrivalTimeEstimator.EstimatedTicksToArrive(si.settlement.Tile, Tile, null); + if (travelTicksFromHere >= travelTicks) + { + continue; + } + + closestSettlement = si; + travelTicks = travelTicksFromHere; } + return closestSettlement; } @@ -200,34 +211,41 @@ public RoadConstructionLeg GetNextLeg() { return null; } - var CurrentLeg = (RoadConstructionLeg)LastLeg; + + var CurrentLeg = (RoadConstructionLeg) LastLeg; while (CurrentLeg.Previous != null) { CurrentLeg = CurrentLeg.Previous; } + return CurrentLeg; } public void MoveWorkersToNextLeg(int fromTile) { - RoadConstructionLeg nextLeg = GetNextLeg(); + var nextLeg = GetNextLeg(); if (nextLeg == null) { return; } + var CaravansWorkingHere = new List(); Find.WorldObjects.GetPlayerControlledCaravansAt(fromTile, CaravansWorkingHere); - foreach (Caravan c in CaravansWorkingHere) // Move to the nextLeg all caravans that are currently set to work on this site + foreach (var c in CaravansWorkingHere + ) // Move to the nextLeg all caravans that are currently set to work on this site { if (c.GetComponent().GetSite() == this) { c.pather.StartPath(Tile, new CaravanArrivalAction_StartWorkingOnRoad()); } } - if (helpFromFaction == null) // Delay when the help starts on the next leg by as many ticks as it would take a caravan to travel from the site to the next leg + + if (helpFromFaction == null + ) // Delay when the help starts on the next leg by as many ticks as it would take a caravan to travel from the site to the next leg { return; } + var delay = CaravanArrivalTimeEstimator.EstimatedTicksToArrive(fromTile, Tile, null); if (helpFromTick > Find.TickManager.TicksGame) { @@ -238,33 +256,42 @@ public void MoveWorkersToNextLeg(int fromTile) helpFromTick = Find.TickManager.TicksGame + delay; } } + public void TryToSkipBetterRoads(Caravan caravan = null) { - RoadConstructionLeg nextLeg = GetNextLeg(); + var nextLeg = GetNextLeg(); if (nextLeg == null) // nextLeg == null should never happen { return; } - RoadDef bestExistingRoad = RoadsOfTheRim.BestExistingRoad(Tile, nextLeg.Tile); + + var bestExistingRoad = RoadsOfTheRim.BestExistingRoad(Tile, nextLeg.Tile); // We've found an existing road that is better than the one we intend to build : skip this leg and move to the next if (RoadsOfTheRim.IsRoadBetter(roadDef, bestExistingRoad)) { return; } - Messages.Message("RoadsOfTheRim_BetterRoadFound".Translate(caravan.Name, bestExistingRoad.label, roadDef.label), MessageTypeDefOf.NeutralEvent); - var currentTile = Tile; - Tile = nextLeg.Tile; // The construction site moves to the next leg - RoadConstructionLeg nextNextLeg = nextLeg.Next; - if (nextNextLeg != null) - { - nextNextLeg.Previous = null; // The nextNext leg is now the next - GetComponent().SetCosts(); - MoveWorkersToNextLeg(currentTile); - } - else // Finish construction + + if (caravan != null) { - GetComponent().EndConstruction(caravan); + Messages.Message( + "RoadsOfTheRim_BetterRoadFound".Translate(caravan.Name, bestExistingRoad.label, roadDef.label), + MessageTypeDefOf.NeutralEvent); + var currentTile = Tile; + Tile = nextLeg.Tile; // The construction site moves to the next leg + var nextNextLeg = nextLeg.Next; + if (nextNextLeg != null) + { + nextNextLeg.Previous = null; // The nextNext leg is now the next + GetComponent().SetCosts(); + MoveWorkersToNextLeg(currentTile); + } + else // Finish construction + { + GetComponent().EndConstruction(caravan); + } } + Find.World.worldObjects.Remove(nextLeg); } @@ -287,7 +314,9 @@ public override string GetInspectString() { stringBuilder.AppendLine(); } - stringBuilder.Append("RoadsOfTheRim_siteInspectString".Translate(roadDef.label, string.Format("{0:0.0}", roadDef.movementCostMultiplier))); + + stringBuilder.Append("RoadsOfTheRim_siteInspectString".Translate(roadDef.label, + $"{roadDef.movementCostMultiplier:0.0}")); stringBuilder.AppendLine(); stringBuilder.Append(GetComponent().ProgressDescription()); return stringBuilder.ToString(); @@ -317,14 +346,7 @@ public void UpdateProgressBarMaterial() { if (x >= 80) { - if (y < (int)(100 * percentageDone)) - { - texture.SetPixel(x, y, ColorFilled); - } - else - { - texture.SetPixel(x, y, ColorUnfilled); - } + texture.SetPixel(x, y, y < (int) (100 * percentageDone) ? ColorFilled : ColorUnfilled); } else { @@ -332,6 +354,7 @@ public void UpdateProgressBarMaterial() } } } + texture.Apply(); } @@ -340,19 +363,23 @@ Check WorldObject Draw method to find why the construction site icon is rotated */ public override void Draw() { - if (RoadsOfTheRim.RoadBuildingState.CurrentlyTargeting == this && roadDef != null) // Do not draw the site if it's not yet finalised or if we don't know the type of road to build yet + if (RoadsOfTheRim.RoadBuildingState.CurrentlyTargeting == this && roadDef != null + ) // Do not draw the site if it's not yet finalised or if we don't know the type of road to build yet { return; } + base.Draw(); - WorldGrid worldGrid = Find.WorldGrid; - Vector3 fromPos = worldGrid.GetTileCenter(Tile); + var worldGrid = Find.WorldGrid; + var fromPos = worldGrid.GetTileCenter(Tile); _ = GetComponent().PercentageDone(); if (!ProgressBarMaterial) { UpdateProgressBarMaterial(); } - WorldRendererUtility.DrawQuadTangentialToPlanet(fromPos, Find.WorldGrid.averageTileSize * .8f, 0.15f, ProgressBarMaterial); + + WorldRendererUtility.DrawQuadTangentialToPlanet(fromPos, Find.WorldGrid.averageTileSize * .8f, 0.15f, + ProgressBarMaterial); } public void InitiateFactionHelp(Faction faction, int tick, float amount, float amountPerTick) @@ -363,11 +390,11 @@ public void InitiateFactionHelp(Faction faction, int tick, float amount, float a helpWorkPerTick = amountPerTick; Find.LetterStack.ReceiveLetter( "RoadsOfTheRim_FactionStartsHelping".Translate(), - "RoadsOfTheRim_FactionStartsHelpingText".Translate(helpFromFaction.Name, FullName(), string.Format("{0:0.00}", (tick - Find.TickManager.TicksGame) / (float)GenDate.TicksPerDay)), + "RoadsOfTheRim_FactionStartsHelpingText".Translate(helpFromFaction.Name, FullName(), + $"{(tick - Find.TickManager.TicksGame) / (float) GenDate.TicksPerDay:0.00}"), LetterDefOf.PositiveEvent, new GlobalTargetInfo(this) ); - } public float FactionHelp() @@ -377,6 +404,7 @@ public float FactionHelp() { return amountOfHelp; } + if (helpFromFaction.PlayerRelationKind == FactionRelationKind.Ally) { // amountOfHelp is capped at the total amount of help provided (which is site.helpAmount) @@ -387,6 +415,7 @@ public float FactionHelp() //Log.Message(String.Format("[RotR] - faction {0} helps with {1:0.00} work", helpFromFaction.Name, amountOfHelp)); EndFactionHelp(); } + helpAmount -= amountOfHelp; } // Cancel help if the faction is not an ally any more @@ -400,10 +429,11 @@ public float FactionHelp() ); EndFactionHelp(); } + return amountOfHelp; } - public void EndFactionHelp() + private void EndFactionHelp() { RoadsOfTheRim.FactionsHelp.HelpFinished(helpFromFaction); helpFromFaction = null; @@ -422,5 +452,4 @@ public void EndFactionHelp() - Applies the effects of work done by a caravan - Creates the road once work is done */ - -} +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/ModMain.cs b/Source/RoadsOfTheRim/RoadsOfTheRim.cs similarity index 74% rename from Source/RoadsOfTheRim/ModMain.cs rename to Source/RoadsOfTheRim/RoadsOfTheRim.cs index 29a2b3a..6e710d5 100644 --- a/Source/RoadsOfTheRim/ModMain.cs +++ b/Source/RoadsOfTheRim/RoadsOfTheRim.cs @@ -1,55 +1,19 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using RimWorld; -using Verse; using RimWorld.Planet; -using Verse.Sound; using UnityEngine; -using System; +using Verse; +using Verse.Sound; namespace RoadsOfTheRim { - [StaticConstructorOnStartup] - static class RotR_StaticConstructorOnStartup - { - public static readonly Texture2D ConstructionLeg_MouseAttachment = ContentFinder.Get("UI/Overlays/ConstructionLeg", true); - - public static Material ConstructionLegLast_Material = MaterialPool.MatFrom("World/WorldObjects/ConstructionLegLast", ShaderDatabase.WorldOverlayTransparentLit, WorldMaterials.DynamicObjectRenderQueue); - } - - public class RoadsOfTheRimSettings : ModSettings - { - // Constants - public const int MinBaseEffort = 1; - public const int DefaultBaseEffort = 10; - public const int MaxBaseEffort = 10; - public const float ElevationCostDouble = 2000f; - public const float HillinessCostDouble = 4f; - public const float SwampinessCostDouble = 0.5f; - public int BaseEffort = DefaultBaseEffort; - public bool OverrideCosts = true; - public float CostIncreaseElevationThreshold = 1000f; - public int CostUpgradeRebate = 30; - public bool useISR2G = true; - - public override void ExposeData() - { - base.ExposeData(); - // Costs are always 100% when using ISR2G - if (useISR2G) { BaseEffort = MaxBaseEffort; } - Scribe_Values.Look(ref BaseEffort, "BaseEffort", DefaultBaseEffort, true); - Scribe_Values.Look(ref OverrideCosts, "OverrideCosts", true, true); - Scribe_Values.Look(ref CostIncreaseElevationThreshold, "CostIncreaseElevationThreshold", 1000f, true); - Scribe_Values.Look(ref CostUpgradeRebate, "CostUpgradeRebate", 30, true); - Scribe_Values.Look(ref useISR2G, "useISR2G", true, true); - } - } - public class RoadsOfTheRim : Mod { public static RoadsOfTheRimSettings settings; - public static List builtRoadTerrains = new List(); + public static readonly List builtRoadTerrains = new List(); public RoadsOfTheRim(ModContentPack content) : base(content) { @@ -65,10 +29,12 @@ public static WorldComponent_FactionRoadConstructionHelp FactionsHelp { get { - if (Find.World.GetComponent(typeof(WorldComponent_FactionRoadConstructionHelp)) is WorldComponent_FactionRoadConstructionHelp f) + if (Find.World.GetComponent(typeof(WorldComponent_FactionRoadConstructionHelp)) is + WorldComponent_FactionRoadConstructionHelp f) { return f; } + Log.Warning("[RotR] - ERROR, couldn't find WorldComponent_FactionRoadConstructionHelp"); return null; } @@ -78,10 +44,12 @@ public static WorldComponent_RoadBuildingState RoadBuildingState { get { - if (Find.World.GetComponent(typeof(WorldComponent_RoadBuildingState)) is WorldComponent_RoadBuildingState f) + if (Find.World.GetComponent(typeof(WorldComponent_RoadBuildingState)) is + WorldComponent_RoadBuildingState f) { return f; } + Log.Message("[RotR] - ERROR, couldn't find WorldComponent_RoadBuildingState"); return null; } @@ -105,7 +73,8 @@ public static void DebugLog(string message = null, Exception e = null) #endif } - public static float CalculateRoadModifier(RoadDef roadDef, float BiomeMovementDifficulty, float HillinessOffset, float WinterOffset, out float BiomeModifier, out float HillModifier, out float WinterModifier) + public static float CalculateRoadModifier(RoadDef roadDef, float BiomeMovementDifficulty, float HillinessOffset, + float WinterOffset, out float BiomeModifier, out float HillModifier, out float WinterModifier) { BiomeModifier = 0f; HillModifier = 0f; @@ -116,9 +85,11 @@ public static float CalculateRoadModifier(RoadDef roadDef, float BiomeMovementDi HillModifier = roadDef.GetModExtension().hillinessModifier; WinterModifier = roadDef.GetModExtension().winterModifier; } + var BiomeCoef = (1 + ((BiomeMovementDifficulty - 1) * (1 - BiomeModifier))) / BiomeMovementDifficulty; //RoadsOfTheRim.DebugLog("calculateRoadModifier: BiomeCoef=" +BiomeCoef+ ", BiomeMovementDifficulty="+ BiomeMovementDifficulty+ ", HillModifier"+ HillModifier+ ", HillinessOffset="+ HillinessOffset+ ", WinterModifier="+ WinterModifier+ ", WinterOffset="+ WinterOffset); - return ((BiomeCoef * BiomeMovementDifficulty) + ((1 - HillModifier) * HillinessOffset) + ((1 - WinterModifier) * WinterOffset)) / (BiomeMovementDifficulty + HillinessOffset + WinterOffset); + return ((BiomeCoef * BiomeMovementDifficulty) + ((1 - HillModifier) * HillinessOffset) + + ((1 - WinterModifier) * WinterOffset)) / (BiomeMovementDifficulty + HillinessOffset + WinterOffset); } @@ -133,8 +104,8 @@ public static float CalculateRoadModifier(RoadDef roadDef, float BiomeMovementDi */ public static bool DoSomeWork(Caravan caravan, RoadConstructionSite site, out bool noMoreResources) { - WorldObjectComp_Caravan caravanComp = caravan.GetComponent(); - WorldObjectComp_ConstructionSite siteComp = site.GetComponent(); + var caravanComp = caravan.GetComponent(); + var siteComp = site.GetComponent(); _ = site.roadDef.GetModExtension(); noMoreResources = false; var useISR2G = caravanComp.UseISR2G(); @@ -162,7 +133,8 @@ public static bool DoSomeWork(Caravan caravan, RoadConstructionSite site, out bo // Work was 0 (not enough skill) if (Math.Abs(amountOfWork) < double.Epsilon) { - Messages.Message("RoadsOfTheRim_CaravanNoWork".Translate(caravan.Name, site.roadDef.label), MessageTypeDefOf.RejectInput); + Messages.Message("RoadsOfTheRim_CaravanNoWork".Translate(caravan.Name, site.roadDef.label), + MessageTypeDefOf.RejectInput); caravanComp.StopWorking(); return false; } @@ -173,7 +145,7 @@ public static bool DoSomeWork(Caravan caravan, RoadConstructionSite site, out bo available[resourceName] = 0; } - foreach (Thing aThing in CaravanInventoryUtility.AllInventoryItems(caravan)) + foreach (var aThing in CaravanInventoryUtility.AllInventoryItems(caravan)) { foreach (var resourceName in DefModExtension_RotR_RoadDef.allResources) { @@ -191,34 +163,42 @@ public static bool DoSomeWork(Caravan caravan, RoadConstructionSite site, out bo // Materials that would be needed to do that much work foreach (var resourceName in DefModExtension_RotR_RoadDef.allResources) { - needed[resourceName] = (int)Math.Round(siteComp.GetLeft(resourceName) - (percentOfWorkLeftToDoAfter * siteComp.GetCost(resourceName))); + needed[resourceName] = (int) Math.Round(siteComp.GetLeft(resourceName) - + (percentOfWorkLeftToDoAfter * siteComp.GetCost(resourceName))); // Check if there's enough material to go through this batch. Materials with a cost of 0 are always OK // Don't check when ISR2G is in use for this resource, don't check for work - if (!DefModExtension_RotR_RoadDef.GetInSituModifier(resourceName, useISR2G) && resourceName != "Work") + if (DefModExtension_RotR_RoadDef.GetInSituModifier(resourceName, useISR2G) || resourceName == "Work") { - ratio[resourceName] = needed[resourceName] == 0 ? 1f : Math.Min(available[resourceName] / (float)needed[resourceName], 1f); - if (ratio[resourceName] < ratio_final) - { - ratio_final = ratio[resourceName]; - } + continue; + } + + ratio[resourceName] = needed[resourceName] == 0 + ? 1f + : Math.Min(available[resourceName] / (float) needed[resourceName], 1f); + if (ratio[resourceName] < ratio_final) + { + ratio_final = ratio[resourceName]; } } // The caravan didn't have enough resources for a full batch of work. Use as much as we can then stop working if (ratio_final < 1f) { - Messages.Message("RoadsOfTheRim_CaravanNoResource".Translate(caravan.Name, site.roadDef.label), MessageTypeDefOf.RejectInput); + Messages.Message("RoadsOfTheRim_CaravanNoResource".Translate(caravan.Name, site.roadDef.label), + MessageTypeDefOf.RejectInput); foreach (var resourceName in DefModExtension_RotR_RoadDef.allResources) { - needed[resourceName] = (int)(needed[resourceName] * ratio_final); + needed[resourceName] = (int) (needed[resourceName] * ratio_final); } + caravanComp.StopWorking(); } //RoadsOfTheRim.DebugLog("[RotR] ISR2G DEBUG ratio final = " + ratio_final); // Consume resources from the caravan - _ = site.roadDef.defName == "DirtPathBuilt"; // Always consider resources have been consumed when the road is a dirt path - foreach (Thing aThing in CaravanInventoryUtility.AllInventoryItems(caravan)) + _ = site.roadDef.defName == + "DirtPathBuilt"; // Always consider resources have been consumed when the road is a dirt path + foreach (var aThing in CaravanInventoryUtility.AllInventoryItems(caravan)) { foreach (var resourceName in DefModExtension_RotR_RoadDef.allResources) { @@ -229,7 +209,10 @@ public static bool DoSomeWork(Caravan caravan, RoadConstructionSite site, out bo continue; //RoadsOfTheRim.DebugLog("[RotR] ISR2G consumption DEBUG =" + resourceName + " Qty consumed = " + amountUsed); } - var amountUsed = (aThing.stackCount > needed[resourceName]) ? needed[resourceName] : aThing.stackCount; + + var amountUsed = aThing.stackCount > needed[resourceName] + ? needed[resourceName] + : aThing.stackCount; aThing.stackCount -= amountUsed; // Reduce how much of this resource is needed needed[resourceName] -= amountUsed; @@ -241,11 +224,13 @@ public static bool DoSomeWork(Caravan caravan, RoadConstructionSite site, out bo { continue; } + //RoadsOfTheRim.DebugLog("[RotR] ISR2G consumption DEBUG =" + resourceName + " Qty freely awarded = " + needed[resourceName]); siteComp.ReduceLeft(resourceName, needed[resourceName]); needed[resourceName] = 0; } } + if (aThing.stackCount == 0) { aThing.Destroy(); @@ -258,6 +243,7 @@ public static bool DoSomeWork(Caravan caravan, RoadConstructionSite site, out bo { amountOfWork = amountOfWork * 0.25f * useISR2G; } + // Update amountOfWork based on the actual ratio worked & finally reducing the work & resources left amountOfWork = ratio_final * amountOfWork; return siteComp.UpdateProgress(amountOfWork, caravan); @@ -278,32 +264,44 @@ public override void DoSettingsWindowContents(Rect rect) var CurrentOverOverrideCosts = settings.OverrideCosts; var listing_Standard = new Listing_Standard(); listing_Standard.Begin(rect); - listing_Standard.Label("RoadsOfTheRimSettingsBaseEffort".Translate() + ": " + string.Format("{0:0%}", (float)settings.BaseEffort / 10)); - listing_Standard.Gap(); - settings.BaseEffort = (int)listing_Standard.Slider(settings.BaseEffort, RoadsOfTheRimSettings.MinBaseEffort, RoadsOfTheRimSettings.MaxBaseEffort); + listing_Standard.CheckboxLabeled("RoadsOfTheRimSettingsOverrideCosts".Translate() + ": ", + ref settings.OverrideCosts); listing_Standard.Gap(); - listing_Standard.CheckboxLabeled("RoadsOfTheRimSettingsOverrideCosts".Translate() + ": ", ref settings.OverrideCosts); + listing_Standard.Label("RoadsOfTheRimSettingsElevationThreshold".Translate() + ": " + + settings.CostIncreaseElevationThreshold); listing_Standard.Gap(); - listing_Standard.Label("RoadsOfTheRimSettingsElevationThreshold".Translate() + ": " + settings.CostIncreaseElevationThreshold); + settings.CostIncreaseElevationThreshold = + listing_Standard.Slider(settings.CostIncreaseElevationThreshold, 0f, 5000f); listing_Standard.Gap(); - settings.CostIncreaseElevationThreshold = listing_Standard.Slider(settings.CostIncreaseElevationThreshold, 0f, 5000f); + listing_Standard.Label("RoadsOfTheRimSettingsUpgradeRebate".Translate() + ": " + + settings.CostUpgradeRebate + "%"); listing_Standard.Gap(); - listing_Standard.Label("RoadsOfTheRimSettingsUpgradeRebate".Translate() + ": " + settings.CostUpgradeRebate + "%"); - listing_Standard.Gap(); - settings.CostUpgradeRebate = (int)listing_Standard.Slider(settings.CostUpgradeRebate, 0, 100); + settings.CostUpgradeRebate = (int) listing_Standard.Slider(settings.CostUpgradeRebate, 0, 100); listing_Standard.Gap(); listing_Standard.CheckboxLabeled("RoadsOfTheRimSettingsUseISR2G".Translate() + ": ", ref settings.useISR2G); - listing_Standard.End(); // Always make sure to set costs to 100% when using ISR2G if (settings.useISR2G) { settings.BaseEffort = RoadsOfTheRimSettings.MaxBaseEffort; } + else + { + listing_Standard.Gap(); + listing_Standard.Label("RoadsOfTheRimSettingsBaseEffort".Translate() + ": " + + $"{(float) settings.BaseEffort / 10:0%}"); + listing_Standard.Gap(); + settings.BaseEffort = (int) listing_Standard.Slider(settings.BaseEffort, + RoadsOfTheRimSettings.MinBaseEffort, RoadsOfTheRimSettings.MaxBaseEffort); + } + + listing_Standard.End(); + settings.Write(); if (CurrentOverOverrideCosts == settings.OverrideCosts) { return; } + try { Find.WorldPathGrid.RecalculateAllPerceivedPathCosts(); @@ -324,10 +322,12 @@ public static Command AddConstructionSite(Caravan caravan) { defaultLabel = "RoadsOfTheRimAddConstructionSite".Translate(), defaultDesc = "RoadsOfTheRimAddConstructionSiteDescription".Translate(), - icon = ContentFinder.Get("UI/Commands/AddConstructionSite", true), - action = delegate () + icon = ContentFinder.Get("UI/Commands/AddConstructionSite"), + action = delegate { - var constructionSite = (RoadConstructionSite)WorldObjectMaker.MakeWorldObject(DefDatabase.GetNamed("RoadConstructionSite", true)); + var constructionSite = + (RoadConstructionSite) WorldObjectMaker.MakeWorldObject( + DefDatabase.GetNamed("RoadConstructionSite")); constructionSite.Tile = caravan.Tile; Find.WorldObjects.Add(constructionSite); @@ -336,7 +336,8 @@ public static Command AddConstructionSite(Caravan caravan) if (menu.CountBuildableRoads() == 0) { Find.WorldObjects.Remove(constructionSite); - Messages.Message("RoadsOfTheRim_NoBetterRoadCouldBeBuilt".Translate(), MessageTypeDefOf.RejectInput); + Messages.Message("RoadsOfTheRim_NoBetterRoadCouldBeBuilt".Translate(), + MessageTypeDefOf.RejectInput); } else { @@ -348,7 +349,8 @@ public static Command AddConstructionSite(Caravan caravan) }; // Disable if there's already a construction site here - if (Find.WorldObjects.AnyWorldObjectOfDefAt(DefDatabase.GetNamed("RoadConstructionSite", true), caravan.Tile)) + if (Find.WorldObjects.AnyWorldObjectOfDefAt(DefDatabase.GetNamed("RoadConstructionSite"), + caravan.Tile)) { command_Action.Disable("RoadsOfTheRimBuildConstructionSiteAlreadyHere".Translate()); } @@ -394,10 +396,10 @@ public static Command WorkOnSite(Caravan caravan) { defaultLabel = "RoadsOfTheRimWorkOnSite".Translate(), defaultDesc = "RoadsOfTheRimWorkOnSiteDescription".Translate(), - icon = ContentFinder.Get("UI/Commands/AddConstructionSite", true), - action = delegate () + icon = ContentFinder.Get("UI/Commands/AddConstructionSite"), + action = delegate { - SoundStarter.PlayOneShotOnCamera(SoundDefOf.Click, null); + SoundDefOf.Click.PlayOneShotOnCamera(); caravan.GetComponent().StartWorking(); } }; @@ -406,6 +408,7 @@ public static Command WorkOnSite(Caravan caravan) { command_Action.Disable("RoadsOfTheRimBuildWorkOnSiteCantWork".Translate(caravan.GetDescription())); } + return command_Action; } @@ -418,10 +421,10 @@ public static Command StopWorkingOnSite(Caravan caravan) { defaultLabel = "RoadsOfTheRimStopWorkingOnSite".Translate(), defaultDesc = "RoadsOfTheRimStopWorkingOnSiteDescription".Translate(), - icon = ContentFinder.Get("UI/Commands/RemoveConstructionSite", true), - action = delegate () + icon = ContentFinder.Get("UI/Commands/RemoveConstructionSite"), + action = delegate { - SoundStarter.PlayOneShotOnCamera(SoundDefOf.CancelMode, null); + SoundDefOf.CancelMode.PlayOneShotOnCamera(); caravan.GetComponent().StopWorking(); } }; @@ -438,10 +441,10 @@ public static Command RemoveConstructionSite(int tile) { defaultLabel = "RoadsOfTheRimRemoveConstructionSite".Translate(), defaultDesc = "RoadsOfTheRimRemoveConstructionSiteDescription".Translate(), - icon = ContentFinder.Get("UI/Commands/RemoveConstructionSite", true), - action = delegate () + icon = ContentFinder.Get("UI/Commands/RemoveConstructionSite"), + action = delegate { - SoundStarter.PlayOneShotOnCamera(SoundDefOf.CancelMode, null); + SoundDefOf.CancelMode.PlayOneShotOnCamera(); DeleteConstructionSite(tile); } }; @@ -449,35 +452,41 @@ public static Command RemoveConstructionSite(int tile) var ConstructionSiteAlreadyHere = false; try { - ConstructionSiteAlreadyHere = Find.WorldObjects.AnyWorldObjectOfDefAt(DefDatabase.GetNamed("RoadConstructionSite", true), tile); + ConstructionSiteAlreadyHere = + Find.WorldObjects.AnyWorldObjectOfDefAt( + DefDatabase.GetNamed("RoadConstructionSite"), tile); } catch { - + // ignored } + if (!ConstructionSiteAlreadyHere) { command_Action.Disable("RoadsOfTheRimBuildConstructionSiteNotAlreadyHere".Translate()); } + return command_Action; } /*Delete Construction Site */ public static void DeleteConstructionSite(int tile) { - var ConstructionSite = (RoadConstructionSite)Find.WorldObjects.WorldObjectOfDefAt(DefDatabase.GetNamed("RoadConstructionSite", true), tile); + var ConstructionSite = + (RoadConstructionSite) Find.WorldObjects.WorldObjectOfDefAt( + DefDatabase.GetNamed("RoadConstructionSite"), tile); if (ConstructionSite == null) { return; } + // Confirm construction site deletion if resources were already consumed var s = ConstructionSite.ResourcesAlreadyConsumed(); if (!s.NullOrEmpty()) { - Find.WindowStack.Add(Dialog_MessageBox.CreateConfirmation("RoadsOfTheRim_ConfirmDestroyResourcesAlreadyConsumed".Translate(s), delegate - { - DeleteConstructionSiteConfirmed(ConstructionSite); - }, false, null)); + Find.WindowStack.Add(Dialog_MessageBox.CreateConfirmation( + "RoadsOfTheRim_ConfirmDestroyResourcesAlreadyConsumed".Translate(s), + delegate { DeleteConstructionSiteConfirmed(ConstructionSite); })); } else { @@ -486,12 +495,13 @@ public static void DeleteConstructionSite(int tile) } /*Delete Cosntruction Site once it's been confirmed, or no confirmation was necessary */ - public static void DeleteConstructionSiteConfirmed(RoadConstructionSite ConstructionSite) + private static void DeleteConstructionSiteConfirmed(RoadConstructionSite ConstructionSite) { if (ConstructionSite.helpFromFaction != null) { FactionsHelp.HelpFinished(ConstructionSite.helpFromFaction); } + RoadConstructionSite.DeleteSite(ConstructionSite); } @@ -500,7 +510,8 @@ public static DiaOption HelpRoadConstruction(Faction faction, Pawn negotiator) var dialog = new DiaOption("RoadsOfTheRim_commsAskHelp".Translate()); // Find all construction sites on the world map - IEnumerable constructionSites = Find.WorldObjects.AllWorldObjects.Cast().Where(site => site.def == DefDatabase.GetNamed("RoadConstructionSite", true)).ToArray(); + IEnumerable constructionSites = Find.WorldObjects.AllWorldObjects + .Where(site => site.def == DefDatabase.GetNamed("RoadConstructionSite")).ToArray(); // If none : option should be disabled if (!constructionSites.Any()) { @@ -508,14 +519,12 @@ public static DiaOption HelpRoadConstruction(Faction faction, Pawn negotiator) } var diaNode = new DiaNode("RoadsOfTheRim_commsSitesList".Translate()); - foreach (RoadConstructionSite site in constructionSites) + foreach (var o in constructionSites) { + var site = (RoadConstructionSite) o; var diaOption = new DiaOption(site.FullName()) { - action = delegate - { - FactionsHelp.StartHelping(faction, site, negotiator); - } + action = delegate { FactionsHelp.StartHelping(faction, site, negotiator); } }; // Disable sites that do not have a settlement of this faction close enough (as defined by ConstructionSite.maxTicksToNeighbour) if (site.ClosestSettlementOfFaction(faction) == null) @@ -523,19 +532,25 @@ public static DiaOption HelpRoadConstruction(Faction faction, Pawn negotiator) diaOption = new DiaOption("Invalid site"); diaOption.Disable("RoadsOfTheRim_commsNotClose".Translate(faction.Name)); } + if (site.helpFromFaction != null) { diaOption = new DiaOption("Invalid site"); diaOption.Disable("RoadsOfTheRim_commsAnotherFactionIsHelping".Translate(site.helpFromFaction)); } - if (!FactionsHelp.IsDeveloppedEnough(faction, site.roadDef.GetModExtension())) + + if (!FactionsHelp.IsDeveloppedEnough(faction, + site.roadDef.GetModExtension())) { diaOption = new DiaOption("Invalid site"); - diaOption.Disable("RoadsOfTheRim_commsNotDevelopedEnough".Translate(faction.Name, site.roadDef.label)); + diaOption.Disable( + "RoadsOfTheRim_commsNotDevelopedEnough".Translate(faction.Name, site.roadDef.label)); } + diaNode.options.Add(diaOption); diaOption.resolveTree = true; } + // If the faction is already helping, it must be disabled if (FactionsHelp.GetCurrentlyHelping(faction)) { @@ -547,7 +562,8 @@ public static DiaOption HelpRoadConstruction(Faction faction, Pawn negotiator) if (FactionsHelp.InCooldown(faction)) { dialog = new DiaOption("Can't help build roads"); - dialog.Disable("RoadsOfTheRim_commsHasHelpedRecently".Translate(string.Format("{0:0.0}", FactionsHelp.DaysBeforeFactionCanHelp(faction)))); + dialog.Disable("RoadsOfTheRim_commsHasHelpedRecently".Translate( + $"{FactionsHelp.DaysBeforeFactionCanHelp(faction):0.0}")); } // Cancel option (needed when all sites are disabled for one of the above reason) @@ -571,32 +587,32 @@ public static bool IsRoadBetter(RoadDef roadA, RoadDef roadB) { return false; } + if (roadB == null) { return true; } + return roadA.movementCostMultiplier < roadB.movementCostMultiplier; } /* Tells me whether or not a ThingDef is what I want */ - public static bool IsThis(ThingDef def, string name) + private static bool IsThis(ThingDef def, string name) { if (name == "Stone" && def.IsWithinCategory(ThingCategoryDefOf.StoneBlocks)) { return true; } - else + + try { - try - { - return def.Equals(DefDatabase.GetNamed(name, false)); - } - catch - { - return false; - } + return def.Equals(DefDatabase.GetNamed(name, false)); + } + catch + { + return false; } } @@ -609,25 +625,26 @@ public static RoadDef BestExistingRoad(int fromTile_int, int toTile_int) RoadDef bestExistingRoad = null; try { - WorldGrid worldGrid = Find.WorldGrid; - Tile fromTile = worldGrid[fromTile_int]; - Tile toTile = worldGrid[toTile_int]; + var worldGrid = Find.WorldGrid; + var fromTile = worldGrid[fromTile_int]; + var toTile = worldGrid[toTile_int]; if (fromTile.potentialRoads != null) { - foreach (Tile.RoadLink aLink in fromTile.potentialRoads) + foreach (var aLink in fromTile.potentialRoads) { - if (aLink.neighbor == toTile_int & IsRoadBetter(aLink.road, bestExistingRoad)) + if ((aLink.neighbor == toTile_int) & IsRoadBetter(aLink.road, bestExistingRoad)) { bestExistingRoad = aLink.road; } } } + if (toTile.potentialRoads != null) { - foreach (Tile.RoadLink aLink in toTile.potentialRoads) + foreach (var aLink in toTile.potentialRoads) { - if (aLink.neighbor == fromTile_int & IsRoadBetter(aLink.road, bestExistingRoad)) + if ((aLink.neighbor == fromTile_int) & IsRoadBetter(aLink.road, bestExistingRoad)) { bestExistingRoad = aLink.road; } @@ -638,8 +655,8 @@ public static RoadDef BestExistingRoad(int fromTile_int, int toTile_int) { DebugLog(null, e); } + return bestExistingRoad; } - } -} +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/RoadsOfTheRim.csproj b/Source/RoadsOfTheRim/RoadsOfTheRim.csproj index 53da408..c199b6d 100644 --- a/Source/RoadsOfTheRim/RoadsOfTheRim.csproj +++ b/Source/RoadsOfTheRim/RoadsOfTheRim.csproj @@ -1,69 +1,22 @@ - + - Debug - AnyCPU - {32C59479-1ECF-4609-BD0D-BB12658BF92B} - Library - false - RoadsOfTheRim - v4.7.2 - 512 - - - - true - full - false - ..\..\1.2\Assemblies\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - ..\..\1.2\Assemblies\ - TRACE - prompt - 4 - false - - - RoadsOfTheRim + ..\..\1.2\Assemblies + net472 + false + false + false + None + latest - 1.2.2753 + * runtime compile; build; native; contentfiles; analyzers; buildtransitive - 2.0.2 + * runtime - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + diff --git a/Source/RoadsOfTheRim/RoadsOfTheRimSettings.cs b/Source/RoadsOfTheRim/RoadsOfTheRimSettings.cs new file mode 100644 index 0000000..504bb82 --- /dev/null +++ b/Source/RoadsOfTheRim/RoadsOfTheRimSettings.cs @@ -0,0 +1,36 @@ +using Verse; + +namespace RoadsOfTheRim +{ + public class RoadsOfTheRimSettings : ModSettings + { + // Constants + public const int MinBaseEffort = 1; + public const int DefaultBaseEffort = 10; + public const int MaxBaseEffort = 10; + public const float ElevationCostDouble = 2000f; + public const float HillinessCostDouble = 4f; + public const float SwampinessCostDouble = 0.5f; + public int BaseEffort = DefaultBaseEffort; + public float CostIncreaseElevationThreshold = 1000f; + public int CostUpgradeRebate = 30; + public bool OverrideCosts = true; + public bool useISR2G = true; + + public override void ExposeData() + { + base.ExposeData(); + // Costs are always 100% when using ISR2G + if (useISR2G) + { + BaseEffort = MaxBaseEffort; + } + + Scribe_Values.Look(ref BaseEffort, "BaseEffort", DefaultBaseEffort, true); + Scribe_Values.Look(ref OverrideCosts, "OverrideCosts", true, true); + Scribe_Values.Look(ref CostIncreaseElevationThreshold, "CostIncreaseElevationThreshold", 1000f, true); + Scribe_Values.Look(ref CostUpgradeRebate, "CostUpgradeRebate", 30, true); + Scribe_Values.Look(ref useISR2G, "useISR2G", true, true); + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/RotR_StaticConstructorOnStartup.cs b/Source/RoadsOfTheRim/RotR_StaticConstructorOnStartup.cs new file mode 100644 index 0000000..90c4b67 --- /dev/null +++ b/Source/RoadsOfTheRim/RotR_StaticConstructorOnStartup.cs @@ -0,0 +1,17 @@ +using RimWorld.Planet; +using UnityEngine; +using Verse; + +namespace RoadsOfTheRim +{ + [StaticConstructorOnStartup] + internal static class RotR_StaticConstructorOnStartup + { + public static readonly Texture2D ConstructionLeg_MouseAttachment = + ContentFinder.Get("UI/Overlays/ConstructionLeg"); + + public static Material ConstructionLegLast_Material = MaterialPool.MatFrom( + "World/WorldObjects/ConstructionLegLast", ShaderDatabase.WorldOverlayTransparentLit, + WorldMaterials.DynamicObjectRenderQueue); + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/RotR_cost.cs b/Source/RoadsOfTheRim/RotR_cost.cs new file mode 100644 index 0000000..c658f15 --- /dev/null +++ b/Source/RoadsOfTheRim/RotR_cost.cs @@ -0,0 +1,23 @@ +using System.Xml; +using Verse; + +namespace RoadsOfTheRim +{ + public class RotR_cost + { + public int count; + public string name; + + public void LoadDataFromXmlCustom(XmlNode xmlRoot) + { + if (xmlRoot.ChildNodes.Count != 1) + { + Log.Error("Misconfigured RotR_cost: " + xmlRoot.OuterXml); + return; + } + + name = xmlRoot.Name; + count = (int) ParseHelper.FromString(xmlRoot.FirstChild.Value, typeof(int)); + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/SettlementInfo.cs b/Source/RoadsOfTheRim/SettlementInfo.cs new file mode 100644 index 0000000..3a5bde2 --- /dev/null +++ b/Source/RoadsOfTheRim/SettlementInfo.cs @@ -0,0 +1,16 @@ +using RimWorld.Planet; + +namespace RoadsOfTheRim +{ + public class SettlementInfo // Convenience class to store Settlements and their distance to the Site + { + public int distance; + public Settlement settlement; + + public SettlementInfo(Settlement s, int d) + { + settlement = s; + distance = d; + } + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/TerrainAffordanceDefOf.cs b/Source/RoadsOfTheRim/TerrainAffordanceDefOf.cs new file mode 100644 index 0000000..1e411b4 --- /dev/null +++ b/Source/RoadsOfTheRim/TerrainAffordanceDefOf.cs @@ -0,0 +1,12 @@ +using RimWorld; +using Verse; + +namespace RoadsOfTheRim +{ + [DefOf] + public static class TerrainAffordanceDefOf + { + public static TerrainAffordanceDef Bridgeable; + public static TerrainAffordanceDef BridgeableAny; + } +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/TerrainDefOf.cs b/Source/RoadsOfTheRim/TerrainDefOf.cs new file mode 100644 index 0000000..c2cc469 --- /dev/null +++ b/Source/RoadsOfTheRim/TerrainDefOf.cs @@ -0,0 +1,25 @@ +using RimWorld; +using Verse; + +/* + * This file contains all C# related to placing & removing Concrete bridges + */ +namespace RoadsOfTheRim +{ + [DefOf] + public static class TerrainDefOf + { + public static TerrainDef StoneRecent; + public static TerrainDef AsphaltRecent; + public static TerrainDef GlitterRoad; + public static TerrainDef ConcreteBridge; + public static TerrainDef MarshyTerrain; + public static TerrainDef Mud; + } + + /* + * Both Concrete bridges, Stone Roads, and Asphalt roads must check the terrain they're placed on and : + * - Change it (Marsh & marshy soil to be removed when a "good" road was placed + * - Be placed despite affordance (Concrete bridges on top of normal bridgeable water) + */ +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/WITab_Caravan_Build.cs b/Source/RoadsOfTheRim/WITab_Caravan_Build.cs index 5b8423e..87b5a34 100644 --- a/Source/RoadsOfTheRim/WITab_Caravan_Build.cs +++ b/Source/RoadsOfTheRim/WITab_Caravan_Build.cs @@ -1,35 +1,36 @@ using System.Collections.Generic; using RimWorld.Planet; -using Verse; using UnityEngine; +using Verse; namespace RoadsOfTheRim { public class WITab_Caravan_Build : WITab { - private float scrollViewHeight; - private Vector2 scrollPosition; + private float scrollViewHeight; - private List Pawns => SelCaravan.PawnsListForReading; public WITab_Caravan_Build() { labelKey = "RoadsOfTheRim_WITab_Caravan_Build"; } + private List Pawns => SelCaravan.PawnsListForReading; + protected override void FillTab() { Text.Font = GameFont.Small; - Rect rect = new Rect(0f, 0f, size.x, size.y).ContractedBy(10f); + var rect = new Rect(0f, 0f, size.x, size.y).ContractedBy(10f); var rect2 = new Rect(0f, 0f, rect.width - 16f, scrollViewHeight); var num = 0f; - Widgets.BeginScrollView(rect, ref scrollPosition, rect2, true); - DoColumnHeaders(ref num); + Widgets.BeginScrollView(rect, ref scrollPosition, rect2); + DoColumnHeaders(); DoRows(ref num, rect2, rect); if (Event.current.type == EventType.Layout) { scrollViewHeight = num + 30f; } + Widgets.EndScrollView(); } @@ -39,7 +40,7 @@ protected override void UpdateSize() size = GetRawSize(); } - private void DoColumnHeaders(ref float curY) + private void DoColumnHeaders() { Text.Anchor = TextAnchor.UpperCenter; GUI.color = Widgets.SeparatorLabelColor; @@ -52,35 +53,38 @@ private void DoColumnHeaders(ref float curY) private void DoRows(ref float curY, Rect scrollViewRect, Rect scrollOutRect) { - List pawns = Pawns; + var pawns = Pawns; var flag = false; - for (var i = 0; i < pawns.Count; i++) + foreach (var pawn in pawns) { - Pawn pawn = pawns[i]; if (!pawn.IsColonist) { continue; } + if (!flag) { Widgets.ListSeparator(ref curY, scrollViewRect.width, "CaravanColonists".Translate()); flag = true; } + DoRow(ref curY, scrollViewRect, scrollOutRect, pawn); } + var flag2 = false; - for (var j = 0; j < pawns.Count; j++) + foreach (var pawn2 in pawns) { - Pawn pawn2 = pawns[j]; if (pawn2.IsColonist) { continue; } + if (!flag2) { Widgets.ListSeparator(ref curY, scrollViewRect.width, "CaravanPrisonersAndAnimals".Translate()); flag2 = true; } + DoRow(ref curY, scrollViewRect, scrollOutRect, pawn2); } } @@ -93,6 +97,7 @@ private void DoRow(ref float curY, Rect viewRect, Rect scrollOutRect, Pawn p) { DoRow(new Rect(0f, curY, viewRect.width, 50f), p); } + curY += 50f; } @@ -100,7 +105,7 @@ private void DoRow(ref float curY, Rect viewRect, Rect scrollOutRect, Pawn p) private void DoRow(Rect rect, Pawn p) { GUI.BeginGroup(rect); - Rect rect2 = rect.AtZero(); + var rect2 = rect.AtZero(); CaravanThingsTabUtility.DoAbandonButton(rect2, p, SelCaravan); rect2.width -= 24f; Widgets.InfoCardButton(rect2.width - 24f, (rect.height - 24f) / 2f, p); @@ -109,8 +114,9 @@ private void DoRow(Rect rect, Pawn p) { Widgets.DrawHighlight(rect2); } + var rect3 = new Rect(4f, (rect.height - 27f) / 2f, 27f, 27f); - Widgets.ThingIcon(rect3, p, 1f); + Widgets.ThingIcon(rect3, p); var bgRect = new Rect(rect3.xMax + 4f, 16f, 100f, 18f); GenMapUI.DrawPawnLabel(p, bgRect, 1f, 100f, null, GameFont.Small, false, false); var num = bgRect.xMax; @@ -121,6 +127,7 @@ private void DoRow(Rect rect, Pawn p) { Widgets.DrawHighlight(rect5); } + Text.Anchor = TextAnchor.MiddleCenter; string s; switch (i) @@ -138,6 +145,7 @@ private void DoRow(Rect rect, Pawn p) s = "-"; break; } + Widgets.Label(rect5, s); GUI.color = Color.white; Text.Anchor = TextAnchor.UpperLeft; @@ -151,6 +159,7 @@ private void DoRow(Rect rect, Pawn p) Widgets.DrawLineHorizontal(0f, rect.height / 2f, rect.width); GUI.color = Color.white; } + GUI.EndGroup(); } @@ -164,4 +173,4 @@ private Vector2 GetRawSize() return result; } } -} +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/WorldComponent_FactionHelpConstruction.cs b/Source/RoadsOfTheRim/WorldComponent_FactionRoadConstructionHelp.cs similarity index 75% rename from Source/RoadsOfTheRim/WorldComponent_FactionHelpConstruction.cs rename to Source/RoadsOfTheRim/WorldComponent_FactionRoadConstructionHelp.cs index 38252b2..8a4667d 100644 --- a/Source/RoadsOfTheRim/WorldComponent_FactionHelpConstruction.cs +++ b/Source/RoadsOfTheRim/WorldComponent_FactionRoadConstructionHelp.cs @@ -8,62 +8,70 @@ namespace RoadsOfTheRim { public class WorldComponent_FactionRoadConstructionHelp : WorldComponent { - public const int helpCooldownTicks = 5 * GenDate.TicksPerDay; // A faction can only help on a construction site 5 days after it's finished helping on another one + private const int + helpCooldownTicks = + 5 * GenDate + .TicksPerDay; // A faction can only help on a construction site 5 days after it's finished helping on another one - public const float helpRequestFailChance = 0.1f; + private const float helpRequestFailChance = 0.1f; - public const float helpBaseAmount = 600f; + private const float helpBaseAmount = 600f; - public const float helpPerTickMedian = 25f; + private const float helpPerTickMedian = 25f; - public const float helpPerTickVariance = 10f; + private const float helpPerTickVariance = 10f; - public const float helpPerTickMin = 5f; + private const float helpPerTickMin = 5f; + private List boolList_currentlyHelping = new List(); private Dictionary canHelpAgainAtTick = new Dictionary(); private Dictionary currentlyHelping = new Dictionary(); - public WorldComponent_FactionRoadConstructionHelp(World world) : base(world) - { - } - // those lists are used for ExposeData() to load & save correctly private List factionList_canHelpAgainAtTick = new List(); private List factionList_currentlyHelping = new List(); private List intList_canHelpAgainAtTick = new List(); - private List boolList_currentlyHelping = new List(); + + public WorldComponent_FactionRoadConstructionHelp(World world) : base(world) + { + } public override void ExposeData() { base.ExposeData(); - Scribe_Collections.Look(ref canHelpAgainAtTick, "RotR_canHelpAgainAtTick", LookMode.Reference, LookMode.Value, ref factionList_canHelpAgainAtTick, ref intList_canHelpAgainAtTick); - Scribe_Collections.Look(ref currentlyHelping, "RotR_currentlyHelping", LookMode.Reference, LookMode.Value, ref factionList_currentlyHelping, ref boolList_currentlyHelping); + Scribe_Collections.Look(ref canHelpAgainAtTick, "RotR_canHelpAgainAtTick", LookMode.Reference, + LookMode.Value, ref factionList_canHelpAgainAtTick, ref intList_canHelpAgainAtTick); + Scribe_Collections.Look(ref currentlyHelping, "RotR_currentlyHelping", LookMode.Reference, LookMode.Value, + ref factionList_currentlyHelping, ref boolList_currentlyHelping); if (Scribe.mode != LoadSaveMode.PostLoadInit) { return; } + if (canHelpAgainAtTick == null) { canHelpAgainAtTick = new Dictionary(); } + if (currentlyHelping == null) { currentlyHelping = new Dictionary(); } } - public void SetHelpAgainTick(Faction faction, int tick) + private void SetHelpAgainTick(Faction faction, int tick) { canHelpAgainAtTick[faction] = tick; } - public int? GetHelpAgainTick(Faction faction) + private int? GetHelpAgainTick(Faction faction) { if (canHelpAgainAtTick != null && canHelpAgainAtTick.TryGetValue(faction, out var result)) { return result; } + return null; } @@ -73,10 +81,11 @@ public bool GetCurrentlyHelping(Faction faction) { return result; } + return false; } - public void SetCurrentlyHelping(Faction faction, bool value = true) + private void SetCurrentlyHelping(Faction faction, bool value = true) { currentlyHelping[faction] = value; } @@ -84,14 +93,14 @@ public void SetCurrentlyHelping(Faction faction, bool value = true) public void StartHelping(Faction faction, RoadConstructionSite site, Pawn negotiator) { // Test success or failure of the negotiator, plus amount of help obtained (based on negotiation value & roll) - var negotiationValue = negotiator.GetStatValue(StatDefOf.NegotiationAbility, true); + var negotiationValue = negotiator.GetStatValue(StatDefOf.NegotiationAbility); _ = helpRequestFailChance / negotiationValue; var roll = Rand.Value; var amountOfHelp = helpBaseAmount * (1 + (negotiationValue * roll * 5)); //Log.Message(String.Format("[RotR] - Negotiation for road construction help : negotiation value = {0:0.00} , fail chance = {1:P} , roll = {2:0.00} , help = {3:0.00}", negotiationValue , failChance, roll , amountOfHelp)); // Calculate how long the faction needs to start helping - SettlementInfo closestSettlement = site.ClosestSettlementOfFaction(faction); + var closestSettlement = site.ClosestSettlementOfFaction(faction); var tick = Find.TickManager.TicksGame + closestSettlement.distance; // Determine amount of help per tick @@ -103,7 +112,8 @@ public void StartHelping(Faction faction, RoadConstructionSite site, Pawn negoti public void HelpFinished(Faction faction) { - faction.TryAffectGoodwillWith(Faction.OfPlayer, -10, true, true, "Help with road construction cost 10 goodwill"); + faction.TryAffectGoodwillWith(Faction.OfPlayer, -10, true, true, + "Help with road construction cost 10 goodwill"); SetCurrentlyHelping(faction, false); SetHelpAgainTick(faction, Find.TickManager.TicksGame + helpCooldownTicks); } @@ -111,10 +121,11 @@ public void HelpFinished(Faction faction) public bool InCooldown(Faction faction) { var helpAgainTick = GetHelpAgainTick(faction); - if ((helpAgainTick == null) || (Find.TickManager.TicksGame >= GetHelpAgainTick(faction))) + if (helpAgainTick == null || Find.TickManager.TicksGame >= GetHelpAgainTick(faction)) { return false; } + return true; } @@ -125,10 +136,9 @@ public bool IsDeveloppedEnough(Faction faction, DefModExtension_RotR_RoadDef Roa public float DaysBeforeFactionCanHelp(Faction faction) { - int? tick; try { - tick = GetHelpAgainTick(faction); + var tick = GetHelpAgainTick(faction); if (tick == null) { return 0; @@ -138,8 +148,8 @@ public float DaysBeforeFactionCanHelp(Faction faction) { return 0; } - return (float)(GetHelpAgainTick(faction) - Find.TickManager.TicksGame) / GenDate.TicksPerDay; - } + return (float) (GetHelpAgainTick(faction) - Find.TickManager.TicksGame) / GenDate.TicksPerDay; + } } } \ No newline at end of file diff --git a/Source/RoadsOfTheRim/WorldComponent_RoadBuildingState.cs b/Source/RoadsOfTheRim/WorldComponent_RoadBuildingState.cs index fa0b63b..d01767e 100644 --- a/Source/RoadsOfTheRim/WorldComponent_RoadBuildingState.cs +++ b/Source/RoadsOfTheRim/WorldComponent_RoadBuildingState.cs @@ -6,12 +6,11 @@ public class WorldComponent_RoadBuildingState : WorldComponent { public WorldComponent_RoadBuildingState(World world) : base(world) { - CurrentlyTargeting = null ; + CurrentlyTargeting = null; } public RoadConstructionSite CurrentlyTargeting { get; set; } public Caravan Caravan { get; set; } - } -} +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/WorldLayer_RoadsOnWater.cs b/Source/RoadsOfTheRim/WorldLayer_RoadsOnWater.cs index 0ecf582..3368790 100644 --- a/Source/RoadsOfTheRim/WorldLayer_RoadsOnWater.cs +++ b/Source/RoadsOfTheRim/WorldLayer_RoadsOnWater.cs @@ -1,16 +1,16 @@ // RimWorld.Planet.WorldLayer_Roads -using RimWorld; -using RimWorld.Planet; + using System.Collections; using System.Collections.Generic; using System.Linq; +using RimWorld; +using RimWorld.Planet; using UnityEngine; using Verse; using Verse.Noise; namespace RoadsOfTheRim { - public class WorldLayer_RoadsOnWater : WorldLayer_Paths { private readonly ModuleBase roadDisplacementX = new Perlin(1.0, 2.0, 0.5, 3, 78951234, QualityMode.Medium); @@ -26,9 +26,10 @@ public override IEnumerable Regenerate() { yield return item; } - LayerSubMesh subMesh = GetSubMesh(WorldMaterials.Roads); - WorldGrid grid = Find.WorldGrid; - var roadLayerDefs = DefDatabase.AllDefs.OrderBy((RoadWorldLayerDef rwld) => rwld.order).ToList(); + + var subMesh = GetSubMesh(WorldMaterials.Roads); + var grid = Find.WorldGrid; + var roadLayerDefs = DefDatabase.AllDefs.OrderBy(rwld => rwld.order).ToList(); var i = 0; while (i < grid.TilesCount) { @@ -36,11 +37,13 @@ public override IEnumerable Regenerate() { yield return null; } + if (subMesh.verts.Count > 60000) { subMesh = GetSubMesh(WorldMaterials.Roads); } - Tile tile = grid[i]; + + var tile = grid[i]; if (tile.WaterCovered) { var list = new List(); @@ -50,24 +53,28 @@ public override IEnumerable Regenerate() var allowSmoothTransition = true; for (var j = 0; j < tile.potentialRoads.Count - 1; j++) { - if (tile.potentialRoads[j].road.worldTransitionGroup == tile.potentialRoads[j + 1].road.worldTransitionGroup) + if (tile.potentialRoads[j].road.worldTransitionGroup == + tile.potentialRoads[j + 1].road.worldTransitionGroup) { continue; } + allowSmoothTransition = false; } - for (var k = 0; k < roadLayerDefs.Count; k++) + + foreach (var roadWorldLayerDef in roadLayerDefs) { var flag = false; list.Clear(); for (var l = 0; l < tile.potentialRoads.Count; l++) { - RoadDef road = tile.potentialRoads[l].road; - var layerWidth = road.GetLayerWidth(roadLayerDefs[k]); + var road = tile.potentialRoads[l].road; + var layerWidth = road.GetLayerWidth(roadWorldLayerDef); if (layerWidth > 0f) { flag = true; } + list.Add(new OutputDirection { neighbor = tile.potentialRoads[l].neighbor, @@ -76,29 +83,34 @@ public override IEnumerable Regenerate() distortionIntensity = road.distortionIntensity }); } + if (flag) { - GeneratePaths(subMesh, i, list, roadLayerDefs[k].color, allowSmoothTransition); + GeneratePaths(subMesh, i, list, roadWorldLayerDef.color, allowSmoothTransition); } } } } + var num = i + 1; i = num; } + FinalizeMesh(MeshParts.All); } public override Vector3 FinalizePoint(Vector3 inp, float distortionFrequency, float distortionIntensity) { - Vector3 coordinate = inp * distortionFrequency; + var coordinate = inp * distortionFrequency; var magnitude = inp.magnitude; - var a = new Vector3(roadDisplacementX.GetValue(coordinate), roadDisplacementY.GetValue(coordinate), roadDisplacementZ.GetValue(coordinate)); + var a = new Vector3(roadDisplacementX.GetValue(coordinate), roadDisplacementY.GetValue(coordinate), + roadDisplacementZ.GetValue(coordinate)); if (a.magnitude > 0.0001) { var d = ((1f / (1f + Mathf.Exp((0f - a.magnitude) / 1f * 2f)) * 2f) - 1f) * 1f; a = a.normalized * d; } + inp = (inp + (a * distortionIntensity)).normalized * magnitude; return inp + (inp.normalized * 0.012f); } diff --git a/Source/RoadsOfTheRim/WorldObjectComp_Caravan.cs b/Source/RoadsOfTheRim/WorldObjectComp_Caravan.cs index a690490..f1b01de 100644 --- a/Source/RoadsOfTheRim/WorldObjectComp_Caravan.cs +++ b/Source/RoadsOfTheRim/WorldObjectComp_Caravan.cs @@ -1,114 +1,28 @@ using System; -using System.Collections.Generic; using RimWorld; using RimWorld.Planet; using Verse; namespace RoadsOfTheRim { - public enum CaravanState : byte - { - Moving, - NightResting, - AllOwnersHaveMentalBreak, - AllOwnersDowned, - ImmobilizedByMass, - ReadyToWork - } - - public static class PawnBuildingUtility - { - public static bool HealthyColonist(Pawn p) - { - return p.IsFreeColonist && p.health.State == PawnHealthState.Mobile; - } - - public static bool HealthyPackAnimal(Pawn p) - { - return p.RaceProps.packAnimal && p.health.State == PawnHealthState.Mobile; - } - - public static float ConstructionValue(Pawn p) - { - return p.GetStatValue(StatDefOf.ConstructionSpeed) * p.GetStatValue(StatDefOf.ConstructSuccessChance); - } - - public static int ConstructionLevel(Pawn p) - { - return p.skills.GetSkill(SkillDefOf.Construction).levelInt; - } - - public static string ShowConstructionValue(Pawn p) - { - if (HealthyColonist(p)) - { - return string.Format("{0:0.##}", ConstructionValue(p)); - } - if (HealthyPackAnimal(p)) - { - return string.Format("+{0:0.##}", ConstructionValue(p)); - } - return "-"; - } - - public static string ShowSkill(Pawn p) - { - if (HealthyColonist(p)) - { - return string.Format("{0:0}", ConstructionLevel(p)); - } - return "-"; - } - - public static string ShowBestRoad(Pawn p) - { - RoadDef BestRoadDef = null; - if (!HealthyColonist(p)) - { - return "-"; - } - foreach (RoadDef thisDef in DefDatabase.AllDefs) - { - if (!thisDef.HasModExtension() || !thisDef.GetModExtension().built) // Only add RoadDefs that are buildable, based on DefModExtension_RotR_RoadDef.built - { - continue; - } - DefModExtension_RotR_RoadDef RoadDefMod = thisDef.GetModExtension(); - if (ConstructionLevel(p) < RoadDefMod.minConstruction) - { - continue; - } - if (BestRoadDef != null && thisDef.movementCostMultiplier >= BestRoadDef.movementCostMultiplier) - { - continue; - } - BestRoadDef = thisDef; - } - if (BestRoadDef == null) - { - return "-"; - } - return BestRoadDef.label; - } - } - public class WorldObjectComp_Caravan : WorldObjectComp { - public bool currentlyWorkingOnSite = false; - - // workOnWakeUp must be more than just working when waking up, it must tell the caravan to work as long as the site is not finished - public bool workOnWakeUp = false; + public bool currentlyWorkingOnSite; private RoadConstructionSite site; - public Caravan GetCaravan() + // workOnWakeUp must be more than just working when waking up, it must tell the caravan to work as long as the site is not finished + private bool workOnWakeUp; + + private Caravan GetCaravan() { - return (Caravan)parent; + return (Caravan) parent; } - public bool IsThereAConstructionSiteHere() + private bool IsThereAConstructionSiteHere() { - return Find.WorldObjects.AnyWorldObjectOfDefAt(DefDatabase.GetNamed("RoadConstructionSite", true), GetCaravan().Tile); + return Find.WorldObjects.AnyWorldObjectOfDefAt(DefDatabase.GetNamed("RoadConstructionSite"), + GetCaravan().Tile); } public RoadConstructionSite GetSite() @@ -116,44 +30,47 @@ public RoadConstructionSite GetSite() return site; } - public bool SetSiteFromTile() + private void SetSiteFromTile() { try { - site = (RoadConstructionSite)Find.WorldObjects.WorldObjectOfDefAt(DefDatabase.GetNamed("RoadConstructionSite", true), GetCaravan().Tile); - return true; + site = (RoadConstructionSite) Find.WorldObjects.WorldObjectOfDefAt( + DefDatabase.GetNamed("RoadConstructionSite"), GetCaravan().Tile); } catch (Exception e) { RoadsOfTheRim.DebugLog("", e); - return false; } } - public void UnsetSite() + private void UnsetSite() { site = null; } public CaravanState CaravanCurrentState() { - Caravan caravan = GetCaravan(); + var caravan = GetCaravan(); if (caravan.pather.MovingNow) { return CaravanState.Moving; } + if (caravan.AllOwnersDowned) { return CaravanState.AllOwnersDowned; } + if (caravan.AllOwnersHaveMentalBreak) { return CaravanState.AllOwnersHaveMentalBreak; } + if (caravan.NightResting) { return CaravanState.NightResting; } + return CaravanState.ReadyToWork; } @@ -164,18 +81,21 @@ public override void CompTick() { return; } - Caravan caravan = GetCaravan(); + + var caravan = GetCaravan(); // Wake up the caravan if it's ready to work if (workOnWakeUp && CaravanCurrentState() == CaravanState.ReadyToWork) { workOnWakeUp = false; currentlyWorkingOnSite = true; - Messages.Message("RotR_CaravanWakesUp".Translate(caravan.Label, site.roadDef.label), MessageTypeDefOf.NeutralEvent); + Messages.Message("RotR_CaravanWakesUp".Translate(caravan.Label, site.roadDef.label), + MessageTypeDefOf.NeutralEvent); } // Do some work & stop working if finished // Caravan is working AND there's a site here AND caravan can work AND the site is indeed the same the caravan was working on - if (currentlyWorkingOnSite & IsThereAConstructionSiteHere() & (CaravanCurrentState() == CaravanState.ReadyToWork) && (GetCaravan().Tile == GetSite().Tile)) + if (currentlyWorkingOnSite & IsThereAConstructionSiteHere() & + (CaravanCurrentState() == CaravanState.ReadyToWork) && GetCaravan().Tile == GetSite().Tile) { base.CompTick(); site.TryToSkipBetterRoads(caravan); // No need to work if there's a better road here @@ -187,7 +107,7 @@ public override void CompTick() } // Site tile and Caravan tile mismatch - if (GetSite() != null && (GetCaravan().Tile != GetSite().Tile)) + if (GetSite() != null && GetCaravan().Tile != GetSite().Tile) { StopWorking(); UnsetSite(); @@ -204,22 +124,27 @@ public override void CompTick() { stoppedReason = "RotR_EveryoneDown".Translate(); } + if (CaravanCurrentState() == CaravanState.AllOwnersHaveMentalBreak) { stoppedReason = "RotR_EveryoneCrazy".Translate(); } + // I decided to remove this (Issue #38) so code should never reach here if (CaravanCurrentState() == CaravanState.ImmobilizedByMass) { stoppedReason = "RotR_TooHeavy".Translate(); } + if (CaravanCurrentState() == CaravanState.NightResting) { stoppedReason = "RotR_RestingAtNight".Translate(); } + if (stoppedReason != "") { - Messages.Message("RotR_CaravanStopped".Translate(caravan.Label, site.roadDef.label) + stoppedReason, MessageTypeDefOf.RejectInput); + Messages.Message("RotR_CaravanStopped".Translate(caravan.Label, site.roadDef.label) + stoppedReason, + MessageTypeDefOf.RejectInput); } // This should not happen ? @@ -240,7 +165,7 @@ public void StartWorking() { if (CaravanCurrentState() == CaravanState.ReadyToWork) { - Caravan caravan = GetCaravan(); + var caravan = GetCaravan(); caravan.pather.StopDead(); SetSiteFromTile(); currentlyWorkingOnSite = true; @@ -265,18 +190,22 @@ public void StopWorking() */ public float AmountOfWork(bool verbose = false) { - List pawns = GetCaravan().PawnsListForReading; + var pawns = GetCaravan().PawnsListForReading; DefModExtension_RotR_RoadDef roadDefModExtension = null; try { roadDefModExtension = site.roadDef.GetModExtension(); } - catch { /* Either there's no site, no roaddef, or no modextension. In any case, not much to do here */} + catch + { + /* Either there's no site, no roaddef, or no modextension. In any case, not much to do here */ + } + //site.roadDef.GetModExtension().minConstruction ; var totalConstruction = 0f; var totalConstructionAboveMinLevel = 0f; var animalConstruction = 0f; - foreach (Pawn pawn in pawns) + foreach (var pawn in pawns) { /* if (pawn.IsFreeColonist && pawn.health.State == PawnHealthState.Mobile) @@ -299,12 +228,15 @@ public float AmountOfWork(bool verbose = false) { totalConstruction += PawnConstructionValue; - if (roadDefModExtension != null && PawnBuildingUtility.ConstructionLevel(pawn) >= roadDefModExtension.minConstruction) + if (roadDefModExtension != null && PawnBuildingUtility.ConstructionLevel(pawn) >= + roadDefModExtension.minConstruction) { totalConstructionAboveMinLevel += PawnConstructionValue; } + continue; } + if (PawnBuildingUtility.HealthyPackAnimal(pawn)) { animalConstruction += PawnConstructionValue; @@ -317,11 +249,15 @@ public float AmountOfWork(bool verbose = false) if (ratioOfConstructionAboveMinLevel < roadDefModExtension.percentageOfminConstruction) { // Check minimum construction level requirements if needed - var ratioActuallyWorked = ratioOfConstructionAboveMinLevel / roadDefModExtension.percentageOfminConstruction; + var ratioActuallyWorked = ratioOfConstructionAboveMinLevel / + roadDefModExtension.percentageOfminConstruction; totalConstruction *= ratioActuallyWorked; if (verbose) { - Messages.Message("RoadsOfTheRim_InsufficientConstructionMinLevel".Translate(totalConstruction, roadDefModExtension.percentageOfminConstruction.ToString("P0"), roadDefModExtension.minConstruction), MessageTypeDefOf.NegativeEvent); + Messages.Message( + "RoadsOfTheRim_InsufficientConstructionMinLevel".Translate(totalConstruction, + roadDefModExtension.percentageOfminConstruction.ToString("P0"), + roadDefModExtension.minConstruction), MessageTypeDefOf.NegativeEvent); } } } @@ -331,6 +267,7 @@ public float AmountOfWork(bool verbose = false) { animalConstruction = totalConstruction; } + totalConstruction += animalConstruction; return totalConstruction; } @@ -338,40 +275,47 @@ public float AmountOfWork(bool verbose = false) public void TeachPawns(float ratio) // The pawns learn a little construction { ratio = Math.Max(Math.Min(1, ratio), 0); - List pawns = GetCaravan().PawnsListForReading; + var pawns = GetCaravan().PawnsListForReading; //RoadsOfTheRim.DebugLog("Teaching Construction to pawns"); - foreach (Pawn pawn in pawns) + foreach (var pawn in pawns) { if (!pawn.IsFreeColonist || pawn.health.State != PawnHealthState.Mobile || pawn.RaceProps.packAnimal) { continue; //RoadsOfTheRim.DebugLog(pawn.Name+" learned " + ratio + " Xp = "+pawn.skills.GetSkill(SkillDefOf.Construction).XpTotalEarned); } - pawn.skills.Learn(SkillDefOf.Construction, 3f * ratio, false); + + pawn.skills.Learn(SkillDefOf.Construction, 3f * ratio); } } public int UseISR2G() { var result = 0; - RoadsOfTheRimSettings settings = LoadedModManager.GetMod().GetSettings(); + var settings = LoadedModManager.GetMod().GetSettings(); // Setting the caravan to use ISR2G or AISR2G if present and settings allow it // TO DO : I can do better than hardcode - if (settings.useISR2G) + if (!settings.useISR2G) { - foreach (Thing aThing in CaravanInventoryUtility.AllInventoryItems(GetCaravan())) + return result; + } + + foreach (var aThing in CaravanInventoryUtility.AllInventoryItems(GetCaravan())) + { + if (result < 1 && aThing.GetInnerIfMinified().def.defName == "RotR_ISR2GNew") { - if (result < 1 && aThing.GetInnerIfMinified().def.defName == "RotR_ISR2GNew") - { - result = 1; - } - if (result < 2 && aThing.GetInnerIfMinified().def.defName == "RotR_AISR2GNew") - { - result = 2; - return result; - } + result = 1; + } + + if (aThing.GetInnerIfMinified().def.defName != "RotR_AISR2GNew") + { + continue; } + + result = 2; + return result; } + return result; } @@ -384,12 +328,12 @@ public override void PostExposeData() } // I had to take into account the old defs of ISR2G that used to be buildings, and replace them with new ISR2G defs that are craftable items - public void OldDefsCleanup() + private void OldDefsCleanup() { var newISRG2 = 0; var newAISRG2 = 0; - Caravan caravan = GetCaravan(); - foreach (Thing aThing in CaravanInventoryUtility.AllInventoryItems(caravan)) + var caravan = GetCaravan(); + foreach (var aThing in CaravanInventoryUtility.AllInventoryItems(caravan)) { switch (aThing.GetInnerIfMinified().def.defName) { @@ -403,18 +347,20 @@ public void OldDefsCleanup() break; } } + for (var i = newISRG2; i > 0; i--) { - Thing newThing = ThingMaker.MakeThing(ThingDef.Named("RotR_ISR2GNew")); + var newThing = ThingMaker.MakeThing(ThingDef.Named("RotR_ISR2GNew")); CaravanInventoryUtility.GiveThing(caravan, newThing); - RoadsOfTheRim.DebugLog("Replacing an ISR2G in caravan " + caravan.ToString()); + RoadsOfTheRim.DebugLog("Replacing an ISR2G in caravan " + caravan); } + for (var j = newAISRG2; j > 0; j--) { - Thing newThing = ThingMaker.MakeThing(ThingDef.Named("RotR_AISR2GNew")); + var newThing = ThingMaker.MakeThing(ThingDef.Named("RotR_AISR2GNew")); CaravanInventoryUtility.GiveThing(caravan, newThing); - RoadsOfTheRim.DebugLog("Replacing an AISR2G in caravan " + caravan.ToString()); + RoadsOfTheRim.DebugLog("Replacing an AISR2G in caravan " + caravan); } } } -} +} \ No newline at end of file diff --git a/Source/RoadsOfTheRim/WorldObjectComp_ConstructionSite.cs b/Source/RoadsOfTheRim/WorldObjectComp_ConstructionSite.cs index 5c3ed15..08afcfc 100644 --- a/Source/RoadsOfTheRim/WorldObjectComp_ConstructionSite.cs +++ b/Source/RoadsOfTheRim/WorldObjectComp_ConstructionSite.cs @@ -1,44 +1,36 @@ using System; using System.Collections.Generic; -using System.Text; using System.Linq; +using System.Text; using RimWorld; using RimWorld.Planet; using Verse; namespace RoadsOfTheRim { - - public class CompProperties_RoadsOfTheRimConstructionSite : WorldObjectCompProperties - { - public CompProperties_RoadsOfTheRimConstructionSite() - { - compClass = typeof(WorldObjectComp_ConstructionSite); - } - } - public class WorldObjectComp_ConstructionSite : WorldObjectComp { - - public CompProperties_RoadsOfTheRimConstructionSite Properties => (CompProperties_RoadsOfTheRimConstructionSite)props; - // TO DO : Make those 2 private - public Dictionary costs = new Dictionary(); - - public Dictionary left = new Dictionary(); + private Dictionary costs = new Dictionary(); // Used for ExposeData() private List costs_Keys = new List(); private List costs_Values = new List(); + + private Dictionary left = new Dictionary(); private List left_Keys = new List(); private List left_Values = new List(); + public CompProperties_RoadsOfTheRimConstructionSite Properties => + (CompProperties_RoadsOfTheRimConstructionSite) props; + public int GetCost(string name) { if (!costs.TryGetValue(name, out var value)) { return 0; // TO DO : Throwing an excepion would be bettah } + return value; } @@ -48,6 +40,7 @@ public float GetLeft(string name) { return 0; // TO DO : Throwing an excepion would be bettah } + return value; } @@ -57,36 +50,45 @@ public void ReduceLeft(string name, float amount) { return; } - left[name] -= (amount > value) ? value : amount; + + left[name] -= amount > value ? value : amount; } - public float GetPercentageDone(string name) + private float GetPercentageDone(string name) { if (!costs.TryGetValue(name, out var costTotal) & !left.TryGetValue(name, out var leftTotal)) { return 0; } - return (float)(costTotal - leftTotal) / costTotal; + + return (costTotal - leftTotal) / costTotal; } /* Returns the cost modifiers for building a road from one tile to another, based on Elevation, Hilliness, Swampiness & river crossing */ - public static void GetCostsModifiers(int fromTile_int, int toTile_int, ref float elevationModifier, ref float hillinessModifier, ref float swampinessModifier, ref float bridgeModifier) + private static void GetCostsModifiers(int fromTile_int, int toTile_int, ref float elevationModifier, + ref float hillinessModifier, ref float swampinessModifier, ref float bridgeModifier) { try { - RoadsOfTheRimSettings settings = LoadedModManager.GetMod().GetSettings(); - Tile fromTile = Find.WorldGrid[fromTile_int]; - Tile toTile = Find.WorldGrid[toTile_int]; + var settings = LoadedModManager.GetMod().GetSettings(); + var fromTile = Find.WorldGrid[fromTile_int]; + var toTile = Find.WorldGrid[toTile_int]; // Cost increase from elevation : if elevation is above {CostIncreaseElevationThreshold} (default 1000m), cost is doubled every {ElevationCostDouble} (default 2000m) - elevationModifier = (fromTile.elevation <= settings.CostIncreaseElevationThreshold) ? 0 : (fromTile.elevation - settings.CostIncreaseElevationThreshold) / RoadsOfTheRimSettings.ElevationCostDouble; - elevationModifier += (toTile.elevation <= settings.CostIncreaseElevationThreshold) ? 0 : (toTile.elevation - settings.CostIncreaseElevationThreshold) / RoadsOfTheRimSettings.ElevationCostDouble; + elevationModifier = fromTile.elevation <= settings.CostIncreaseElevationThreshold + ? 0 + : (fromTile.elevation - settings.CostIncreaseElevationThreshold) / + RoadsOfTheRimSettings.ElevationCostDouble; + elevationModifier += toTile.elevation <= settings.CostIncreaseElevationThreshold + ? 0 + : (toTile.elevation - settings.CostIncreaseElevationThreshold) / + RoadsOfTheRimSettings.ElevationCostDouble; // Hilliness and swampiness are the average between that of the from & to tiles // Hilliness is 0 on flat terrain, never negative. It's between 0 (flat) and 5(Impassable) - var hilliness = Math.Max((((float)fromTile.hilliness + (float)toTile.hilliness) / 2) - 1, 0); + var hilliness = Math.Max((((float) fromTile.hilliness + (float) toTile.hilliness) / 2) - 1, 0); var swampiness = (fromTile.swampiness + toTile.swampiness) / 2; // Hilliness and swampiness double the costs when they equal {HillinessCostDouble} (default 4) and {SwampinessCostDouble} (default 0.5) @@ -114,20 +116,24 @@ public static void GetCostsModifiers(int fromTile_int, int toTile_int, ref float * grant CostUpgradeRebate% (default 30%) of the best existing road build costs as a rebate on the costs of the road to be built * i.e. the exisitng road cost 300 stones, the new road cost 600 stones, the rebate is 300*30% = 90 stones */ - public static void GetUpgradeModifiers(int fromTile_int, int toTile_int, RoadDef roadToBuild, out Dictionary rebate) + private static void GetUpgradeModifiers(int fromTile_int, int toTile_int, RoadDef roadToBuild, + out Dictionary rebate) { rebate = new Dictionary(); - RoadDef bestExistingRoad = RoadsOfTheRim.BestExistingRoad(fromTile_int, toTile_int); + var bestExistingRoad = RoadsOfTheRim.BestExistingRoad(fromTile_int, toTile_int); if (bestExistingRoad == null) { return; } - DefModExtension_RotR_RoadDef bestExistingRoadDefModExtension = bestExistingRoad.GetModExtension(); - DefModExtension_RotR_RoadDef roadToBuildRoadDefModExtension = roadToBuild.GetModExtension(); - if (bestExistingRoadDefModExtension == null || roadToBuildRoadDefModExtension == null || !RoadsOfTheRim.IsRoadBetter(roadToBuild, bestExistingRoad)) + + var bestExistingRoadDefModExtension = bestExistingRoad.GetModExtension(); + var roadToBuildRoadDefModExtension = roadToBuild.GetModExtension(); + if (bestExistingRoadDefModExtension == null || roadToBuildRoadDefModExtension == null || + !RoadsOfTheRim.IsRoadBetter(roadToBuild, bestExistingRoad)) { return; } + foreach (var resourceName in DefModExtension_RotR_RoadDef.allResourcesAndWork) { var existingCost = bestExistingRoadDefModExtension.GetCost(resourceName); @@ -136,13 +142,15 @@ public static void GetUpgradeModifiers(int fromTile_int, int toTile_int, RoadDef { continue; } - if ((int)(existingCost * (float)RoadsOfTheRim.settings.CostUpgradeRebate / 100) > toBuildCost) + + if ((int) (existingCost * (float) RoadsOfTheRim.settings.CostUpgradeRebate / 100) > toBuildCost) { rebate[resourceName] = toBuildCost; } else { - rebate[resourceName] = (int)(existingCost * (float)RoadsOfTheRim.settings.CostUpgradeRebate / 100); + rebate[resourceName] = + (int) (existingCost * (float) RoadsOfTheRim.settings.CostUpgradeRebate / 100); } } } @@ -151,21 +159,23 @@ public static void GetUpgradeModifiers(int fromTile_int, int toTile_int, RoadDef public static string CostModifersDescription(int fromTile_int, int toTile_int, ref float totalCostModifier) { var result = new StringBuilder(); - RoadsOfTheRimSettings settings = LoadedModManager.GetMod().GetSettings(); + var settings = LoadedModManager.GetMod().GetSettings(); // Show total cost modifiers var elevationModifier = 0f; var hillinessModifier = 0f; var swampinessModifier = 0f; var bridgeModifier = 0f; - GetCostsModifiers(fromTile_int, toTile_int, ref elevationModifier, ref hillinessModifier, ref swampinessModifier, ref bridgeModifier); + GetCostsModifiers(fromTile_int, toTile_int, ref elevationModifier, ref hillinessModifier, + ref swampinessModifier, ref bridgeModifier); result.Append("RoadsOfTheRim_ConstructionSiteDescription_CostModifiers".Translate( - string.Format("{0:P0}", elevationModifier + hillinessModifier + swampinessModifier + bridgeModifier), - string.Format("{0:P0}", elevationModifier), - string.Format("{0:P0}", hillinessModifier), - string.Format("{0:P0}", swampinessModifier), - string.Format("{0:P0}", bridgeModifier) + $"{elevationModifier + hillinessModifier + swampinessModifier + bridgeModifier:P0}", + $"{elevationModifier:P0}", + $"{hillinessModifier:P0}", + $"{swampinessModifier:P0}", + $"{bridgeModifier:P0}" )); - totalCostModifier = (1 + elevationModifier + hillinessModifier + swampinessModifier + bridgeModifier) * ((float)settings.BaseEffort / 10); + totalCostModifier = (1 + elevationModifier + hillinessModifier + swampinessModifier + bridgeModifier) * + ((float) settings.BaseEffort / 10); return result.ToString(); } @@ -179,23 +189,28 @@ public override void CompTick() { try { - if (((RoadConstructionSite)parent).helpFromFaction == null || CaravanNightRestUtility.RestingNowAt(((RoadConstructionSite)parent).Tile) || Find.TickManager.TicksGame % 100 != 50) + if (((RoadConstructionSite) parent).helpFromFaction == null || + CaravanNightRestUtility.RestingNowAt(((RoadConstructionSite) parent).Tile) || + Find.TickManager.TicksGame % 100 != 50) { return; } - ((RoadConstructionSite)parent).TryToSkipBetterRoads(); // No need to work if there's a better road here - var amountOfWork = ((RoadConstructionSite)parent).FactionHelp(); - var percentOfWorkLeftToDoAfter = ((float)GetLeft("Work") - amountOfWork) / GetCost("Work"); + ((RoadConstructionSite) parent).TryToSkipBetterRoads(); // No need to work if there's a better road here + var amountOfWork = ((RoadConstructionSite) parent).FactionHelp(); + + var percentOfWorkLeftToDoAfter = (GetLeft("Work") - amountOfWork) / GetCost("Work"); foreach (var resourceName in DefModExtension_RotR_RoadDef.allResources) { - ReduceLeft(resourceName, (int)Math.Round((float)GetLeft(resourceName) - (percentOfWorkLeftToDoAfter * GetCost(resourceName)))); + ReduceLeft(resourceName, + (int) Math.Round(GetLeft(resourceName) - (percentOfWorkLeftToDoAfter * GetCost(resourceName)))); } + UpdateProgress(amountOfWork); } catch (Exception e) { - RoadsOfTheRim.DebugLog("Construction Site CompTick. parentsite = " + ((RoadConstructionSite)parent), e); + RoadsOfTheRim.DebugLog("Construction Site CompTick. parentsite = " + (RoadConstructionSite) parent, e); } } @@ -210,13 +225,16 @@ public string ResourcesAlreadyConsumed() { continue; } - resourceList.Add(string.Format("{0} {1}", GetCost(resourceName) - GetLeft(resourceName), resourceName)); + + resourceList.Add($"{GetCost(resourceName) - GetLeft(resourceName)} {resourceName}"); } } catch { - RoadsOfTheRim.DebugLog("resourcesAlreadyConsumed failed. This will happen after upgrading to the 20190207 version"); + RoadsOfTheRim.DebugLog( + "resourcesAlreadyConsumed failed. This will happen after upgrading to the 20190207 version"); } + return string.Join(", ", resourceList.ToArray()); } @@ -224,22 +242,31 @@ public void SetCosts() { try { - RoadsOfTheRimSettings settings = LoadedModManager.GetMod().GetSettings(); + var settings = LoadedModManager.GetMod().GetSettings(); var parentSite = parent as RoadConstructionSite; var elevationModifier = 0f; var hillinessModifier = 0f; var swampinessModifier = 0f; var bridgeModifier = 0f; - GetCostsModifiers(parentSite.Tile, parentSite.GetNextLeg().Tile, ref elevationModifier, ref hillinessModifier, ref swampinessModifier, ref bridgeModifier); + if (parentSite == null) + { + return; + } + + GetCostsModifiers(parentSite.Tile, parentSite.GetNextLeg().Tile, ref elevationModifier, + ref hillinessModifier, ref swampinessModifier, ref bridgeModifier); // Total cost modifier - var totalCostModifier = (1 + elevationModifier + hillinessModifier + swampinessModifier + bridgeModifier) * ((float)settings.BaseEffort / 10); + var totalCostModifier = + (1 + elevationModifier + hillinessModifier + swampinessModifier + bridgeModifier) * + ((float) settings.BaseEffort / 10); - DefModExtension_RotR_RoadDef roadDefExtension = parentSite.roadDef.GetModExtension(); + var roadDefExtension = parentSite.roadDef.GetModExtension(); // Check existing roads for potential rebates when upgrading - GetUpgradeModifiers(parentSite.Tile, parentSite.GetNextLeg().Tile, parentSite.roadDef, out Dictionary rebate); + GetUpgradeModifiers(parentSite.Tile, parentSite.GetNextLeg().Tile, parentSite.roadDef, + out var rebate); var s = new List(); foreach (var resourceName in DefModExtension_RotR_RoadDef.allResourcesAndWork) @@ -248,21 +275,32 @@ public void SetCosts() { continue; } + // The cost modifier doesn't affect some advanced resources, as defined in static DefModExtension_RotR_RoadDef.allResourcesWithoutModifiers - var costModifierForThisResource = DefModExtension_RotR_RoadDef.allResourcesWithoutModifiers.Contains(resourceName) ? 1 : totalCostModifier; + var costModifierForThisResource = + DefModExtension_RotR_RoadDef.allResourcesWithoutModifiers.Contains(resourceName) + ? 1 + : totalCostModifier; rebate.TryGetValue(resourceName, out var thisRebate); // Minimum cost of anything that's needed is 1 - costs[resourceName] = Math.Max((int)((roadDefExtension.GetCost(resourceName) - thisRebate) * costModifierForThisResource), 1); + costs[resourceName] = + Math.Max( + (int) ((roadDefExtension.GetCost(resourceName) - thisRebate) * + costModifierForThisResource), + 1); left[resourceName] = Math.Max(costs[resourceName], 1f); if (thisRebate > 0) { - s.Add("RoadsOfTheRim_UpgradeRebateDetail".Translate((int)(thisRebate * costModifierForThisResource), resourceName)); + s.Add("RoadsOfTheRim_UpgradeRebateDetail".Translate( + (int) (thisRebate * costModifierForThisResource), resourceName)); } } if (s.Count > 0) { - Messages.Message("RoadsOfTheRim_UpgradeRebate".Translate(parentSite.roadDef.label, string.Join(", ", s.ToArray())), MessageTypeDefOf.PositiveEvent); + Messages.Message( + "RoadsOfTheRim_UpgradeRebate".Translate(parentSite.roadDef.label, + string.Join(", ", s.ToArray())), MessageTypeDefOf.PositiveEvent); } parentSite.UpdateProgressBarMaterial(); @@ -279,13 +317,14 @@ public bool UpdateProgress(float amountOfWork, Caravan caravan = null) ReduceLeft("Work", amountOfWork); - parentSite.UpdateProgressBarMaterial(); + parentSite?.UpdateProgressBarMaterial(); // Work is done if (GetLeft("Work") <= 0) { return FinishWork(caravan); } + return false; } @@ -295,77 +334,84 @@ public bool UpdateProgress(float amountOfWork, Caravan caravan = null) public bool FinishWork(Caravan caravan = null) { var parentSite = parent as RoadConstructionSite; - var fromTile_int = parentSite.Tile; - var toTile_int = parentSite.GetNextLeg().Tile; - Tile fromTile = Find.WorldGrid[fromTile_int]; - Tile toTile = Find.WorldGrid[toTile_int]; - - // Remove lesser roads, they don't deserve to live - if (fromTile.potentialRoads != null) + if (parentSite != null) { - foreach (Tile.RoadLink aLink in fromTile.potentialRoads.ToArray()) + var fromTile_int = parentSite.Tile; + var toTile_int = parentSite.GetNextLeg().Tile; + var fromTile = Find.WorldGrid[fromTile_int]; + var toTile = Find.WorldGrid[toTile_int]; + + // Remove lesser roads, they don't deserve to live + if (fromTile.potentialRoads != null) { - if (aLink.neighbor == toTile_int & RoadsOfTheRim.IsRoadBetter(parentSite.roadDef, aLink.road)) + foreach (var aLink in fromTile.potentialRoads.ToArray()) { - fromTile.potentialRoads.Remove(aLink); + if ((aLink.neighbor == toTile_int) & RoadsOfTheRim.IsRoadBetter(parentSite.roadDef, aLink.road)) + { + fromTile.potentialRoads.Remove(aLink); + } } } - } - else - { - fromTile.potentialRoads = new List(); - } + else + { + fromTile.potentialRoads = new List(); + } - if (toTile.potentialRoads != null) - { - foreach (Tile.RoadLink aLink in toTile.potentialRoads.ToArray()) + if (toTile.potentialRoads != null) { - if (aLink.neighbor == parentSite.Tile & RoadsOfTheRim.IsRoadBetter(parentSite.roadDef, aLink.road)) + foreach (var aLink in toTile.potentialRoads.ToArray()) { - toTile.potentialRoads.Remove(aLink); + if ((aLink.neighbor == parentSite.Tile) & + RoadsOfTheRim.IsRoadBetter(parentSite.roadDef, aLink.road)) + { + toTile.potentialRoads.Remove(aLink); + } } } - } - else - { - toTile.potentialRoads = new List(); - } + else + { + toTile.potentialRoads = new List(); + } - // Add the road to fromTile & toTile - fromTile.potentialRoads.Add(new Tile.RoadLink { neighbor = toTile_int, road = parentSite.roadDef }); - toTile.potentialRoads.Add(new Tile.RoadLink { neighbor = fromTile_int, road = parentSite.roadDef }); - try - { - Find.World.renderer.SetDirty(); - Find.World.renderer.SetDirty(); - Find.WorldPathGrid.RecalculatePerceivedMovementDifficultyAt(fromTile_int); - Find.WorldPathGrid.RecalculatePerceivedMovementDifficultyAt(toTile_int); - } - catch (Exception e) - { - RoadsOfTheRim.DebugLog("[RotR] Exception : ", e); + // Add the road to fromTile & toTile + fromTile.potentialRoads.Add(new Tile.RoadLink {neighbor = toTile_int, road = parentSite.roadDef}); + toTile.potentialRoads.Add(new Tile.RoadLink {neighbor = fromTile_int, road = parentSite.roadDef}); + try + { + Find.World.renderer.SetDirty(); + Find.World.renderer.SetDirty(); + Find.WorldPathGrid.RecalculatePerceivedMovementDifficultyAt(fromTile_int); + Find.WorldPathGrid.RecalculatePerceivedMovementDifficultyAt(toTile_int); + } + catch (Exception e) + { + RoadsOfTheRim.DebugLog("[RotR] Exception : ", e); + } } // The Construction site and the caravan can move to the next leg - RoadConstructionLeg nextLeg = parentSite.GetNextLeg(); + var nextLeg = parentSite?.GetNextLeg(); if (nextLeg == null) { return true; } + var CurrentTile = parentSite.Tile; parentSite.Tile = nextLeg.Tile; - RoadConstructionLeg nextNextLeg = nextLeg.Next; + var nextNextLeg = nextLeg.Next; // TO DO Here : Check if there's an existing road that is the same or better as the one being built. If there is, skip the next leg if (nextNextLeg != null) { nextNextLeg.Previous = null; SetCosts(); - parentSite.MoveWorkersToNextLeg(CurrentTile); // Move any caravans working on this site to the next leg, and delay faction help if any + parentSite.MoveWorkersToNextLeg( + CurrentTile); // Move any caravans working on this site to the next leg, and delay faction help if any } else { EndConstruction(caravan); // We have built the last leg. Notify & remove the site } + Find.World.worldObjects.Remove(nextLeg); return true; @@ -375,9 +421,15 @@ public void EndConstruction(Caravan caravan = null) { var parentSite = parent as RoadConstructionSite; // On the last leg, send letter & remove the construction site + if (parentSite == null) + { + return; + } + Find.LetterStack.ReceiveLetter( "RoadsOfTheRim_RoadBuilt".Translate(), - "RoadsOfTheRim_RoadBuiltLetterText".Translate(parentSite.roadDef.label, caravan != null ? (TaggedString)caravan.Label : "RoadsOfTheRim_RoadBuiltByAlly".Translate()), + "RoadsOfTheRim_RoadBuiltLetterText".Translate(parentSite.roadDef.label, + caravan != null ? (TaggedString) caravan.Label : "RoadsOfTheRim_RoadBuiltByAlly".Translate()), LetterDefOf.PositiveEvent, new GlobalTargetInfo(parentSite.Tile) ); @@ -392,26 +444,36 @@ public string ProgressDescription() { var parentSite = parent as RoadConstructionSite; var stringBuilder = new StringBuilder(); - stringBuilder.Append("RoadsOfTheRim_ConstructionSiteDescription_Main".Translate(string.Format("{0:P1}", GetPercentageDone("Work")))); + stringBuilder.Append( + "RoadsOfTheRim_ConstructionSiteDescription_Main".Translate($"{GetPercentageDone("Work"):P1}")); // Description of ally's help, if any - if (parentSite.helpFromFaction != null) + if (parentSite?.helpFromFaction != null) { - stringBuilder.Append("RoadsOfTheRim_ConstructionSiteDescription_Help".Translate(parentSite.helpFromFaction.Name, (int)parentSite.helpAmount, string.Format("{0:0.0}", parentSite.helpWorkPerTick))); + stringBuilder.Append("RoadsOfTheRim_ConstructionSiteDescription_Help".Translate( + parentSite.helpFromFaction.Name, (int) parentSite.helpAmount, + $"{parentSite.helpWorkPerTick:0.0}")); if (parentSite.helpFromTick > Find.TickManager.TicksGame) { - stringBuilder.Append("RoadsOfTheRim_ConstructionSiteDescription_HelpStartsWhen".Translate(string.Format("{0:0.00}", (parentSite.helpFromTick - Find.TickManager.TicksGame) / (float)GenDate.TicksPerDay))); + stringBuilder.Append("RoadsOfTheRim_ConstructionSiteDescription_HelpStartsWhen".Translate( + $"{(parentSite.helpFromTick - Find.TickManager.TicksGame) / (float) GenDate.TicksPerDay:0.00}")); } } // Show total cost modifiers var totalCostModifier = 0f; - stringBuilder.Append(CostModifersDescription(parentSite.Tile, parentSite.GetNextLeg().Tile, ref totalCostModifier)); + if (parentSite == null) + { + return stringBuilder.ToString(); + } + + stringBuilder.Append(CostModifersDescription(parentSite.Tile, parentSite.GetNextLeg().Tile, + ref totalCostModifier)); var AllCaravansHere = new List(); Find.WorldObjects.GetPlayerControlledCaravansAt(parentSite.Tile, AllCaravansHere); var ISR2G = 0; - foreach (Caravan c in AllCaravansHere) + foreach (var c in AllCaravansHere) { var caravanISR2G = c.GetComponent().UseISR2G(); if (caravanISR2G > ISR2G) @@ -427,23 +489,29 @@ public string ProgressDescription() { continue; } + stringBuilder.AppendLine(); var ISR2Gmsg = ""; if (ISR2G > 0) { if (resourceName == "Work") { - ISR2Gmsg = ISR2G == 1 ? "RoadsOfTheRim_ConstructionSiteDescription_ISR2Gwork".Translate() : "RoadsOfTheRim_ConstructionSiteDescription_AISR2Gwork".Translate(); + ISR2Gmsg = ISR2G == 1 + ? "RoadsOfTheRim_ConstructionSiteDescription_ISR2Gwork".Translate() + : "RoadsOfTheRim_ConstructionSiteDescription_AISR2Gwork".Translate(); } else if (DefModExtension_RotR_RoadDef.GetInSituModifier(resourceName, ISR2G)) { - ISR2Gmsg = ISR2G == 1 ? "RoadsOfTheRim_ConstructionSiteDescription_ISR2GFree".Translate() : "RoadsOfTheRim_ConstructionSiteDescription_AISR2GFree".Translate(); + ISR2Gmsg = ISR2G == 1 + ? "RoadsOfTheRim_ConstructionSiteDescription_ISR2GFree".Translate() + : "RoadsOfTheRim_ConstructionSiteDescription_AISR2GFree".Translate(); } } stringBuilder.Append("RoadsOfTheRim_ConstructionSiteDescription_Resource".Translate( GetNiceResourceName(resourceName), - string.Format(resourceName == "Work" ? "{0:##.00}" : "{0:##}", GetLeft(resourceName)), // Only Work should be shown with 2 decimals + string.Format(resourceName == "Work" ? "{0:##.00}" : "{0:##}", + GetLeft(resourceName)), // Only Work should be shown with 2 decimals GetCost(resourceName), ISR2Gmsg )); @@ -455,10 +523,11 @@ public string ProgressDescription() private string GetNiceResourceName(string defName) { var defThing = DefDatabase.GetNamedSilentFail(defName); - if(defThing == null || string.IsNullOrEmpty(defThing.label)) + if (defThing == null || string.IsNullOrEmpty(defThing.label)) { return defName; } + return defThing.label; } @@ -469,8 +538,10 @@ public float PercentageDone() public override void PostExposeData() { - Scribe_Collections.Look(ref costs, "RotR_site_costs", LookMode.Value, LookMode.Value, ref costs_Keys, ref costs_Values); - Scribe_Collections.Look(ref left, "RotR_site_left", LookMode.Value, LookMode.Value, ref left_Keys, ref left_Values); + Scribe_Collections.Look(ref costs, "RotR_site_costs", LookMode.Value, LookMode.Value, ref costs_Keys, + ref costs_Values); + Scribe_Collections.Look(ref left, "RotR_site_left", LookMode.Value, LookMode.Value, ref left_Keys, + ref left_Values); } } -} +} \ No newline at end of file