diff --git a/mod-structure/1.4/Assemblies/HeatMap.dll b/mod-structure/1.4/Assemblies/HeatMap.dll new file mode 100644 index 0000000..27d6293 Binary files /dev/null and b/mod-structure/1.4/Assemblies/HeatMap.dll differ diff --git a/mod-structure/About/About.xml b/mod-structure/About/About.xml index 735b79a..6c2c4d2 100644 --- a/mod-structure/About/About.xml +++ b/mod-structure/About/About.xml @@ -5,6 +5,7 @@ Falconne Adds an overlay toggle button to show a temperature based colour gradient over indoor areas: green for the human comfort zone, getting bluer and redder for colder and hotter rooms respectively. +
  • 1.4
  • 1.3
  • 1.2
  • 1.1
  • diff --git a/mod-structure/Languages/English/Keyed/FALCHM_LanguageData.xml b/mod-structure/Languages/English/Keyed/FALCHM_LanguageData.xml index 7f79ca7..aa8b9c7 100644 --- a/mod-structure/Languages/English/Keyed/FALCHM_LanguageData.xml +++ b/mod-structure/Languages/English/Keyed/FALCHM_LanguageData.xml @@ -1,36 +1,50 @@ + Override vanilla overlay + Use Heat Map's overlay instead of the vanilla overlay. +(default: on) + + Only overlay indoor areas + Only show the overlay for indoors areas. +(default: on) + Opacity of overlay - Reduce this value to make the overlay more transparent. + Reduce this value to make the overlay more transparent. +(default: 30%) Overlay update delay - Number of ticks delay between overlay updates while game is unpaused. Lower numbers provide smoother updates, but may affect performance on low end machines. + Number of ticks delay between widget and temperature text updates while game is unpaused. Lower numbers provide smoother updates, but may affect performance on low end machines. +(default: 100) Show outdoor thermometer - Displays a widget in the top right hand corner of the UI showing current outdoor temperature. + Displays a widget in the top right hand corner of the UI showing current outdoor temperature. +(default: on) Opacity of thermometer - Reduce this value to make the outdoor temperature thermometer background color more transparent. + Reduce this value to make the outdoor temperature thermometer background color more transparent. +(default: 30%) Lock thermometer position - Locks the thermometer widget in place, disabling shift-click dragging. + Locks the thermometer widget in place, disabling shift-click dragging. +(default: off) Right margin of thermometer - Changes the margin between the thermometer widget and the right of the screen. The widget can be dragged using shift-click while ingame. + Changes the margin between the thermometer widget and the right of the screen. The widget can be dragged using shift-click while ingame. +(default: 70) Top margin of thermometer - Changes the margin between the thermometer widget and the top of the screen. The widget can be dragged using shift-click while ingame. + Changes the margin between the thermometer widget and the top of the screen. The widget can be dragged using shift-click while ingame. +(default: 8) - Outdoor temperature. -Click to toggle heat map overlay. -Shift-click and drag to move the widget, the position can be locked in the mod settings. -This widget can be disabled in mod settings. - Show Heat Map + Show temperature over rooms + When overlay is on, this will also display the room temperature over the centre of the room. +(default: on) Use custom range When off, the colour gradient automatically maps to the human comfort range as defined by the game. -When on, allows you to set a custom boundary for the blue and red ends of the gradient, in current temperature units +When on, allows you to set a custom boundary for the blue and red ends of the gradient, in current temperature units +(default: off) Custom range min Blue end of gradient. Temperatures below this will be blue. Use current temperature units @@ -38,6 +52,9 @@ When on, allows you to set a custom boundary for the blue and red ends of the gr Custom range max Red end of gradient. Temperatures above this will be red. Use current temperature units - Show temperature over rooms - When overlay is on, this will also display the room temperature over the centre of the room. + Outdoor temperature. +Click to toggle heat map overlay. +Shift-click and drag to move the widget, the position can be locked in the mod settings. +This widget can be disabled in mod settings. + Show Heat Map diff --git a/src/HeatMap/HeatMap.cs b/src/HeatMap/HeatMap.cs index c3479f7..0558235 100644 --- a/src/HeatMap/HeatMap.cs +++ b/src/HeatMap/HeatMap.cs @@ -1,215 +1,336 @@ -using System; -using RimWorld; -using UnityEngine; -using Verse; - -namespace HeatMap -{ - public class HeatMap : ICellBoolGiver - { - public HeatMap() - { - if (Main.Instance.ShouldUseCustomRange()) - CreateCustomMap(); - else - CreateComfortMap(); - } - - public void CreateCustomMap() - { - _mappedTemperatureRange = new IntRange( - Main.Instance.GetCustomRangeMin(), Main.Instance.GetCustomRangeMax()); - - var mappedColorCount = _mappedTemperatureRange.max - _mappedTemperatureRange.min; - _mappedColors = new Color[mappedColorCount]; - - var delta = 2f / (mappedColorCount - 1); - var channelR = -1f; - var channelG = 0f; - var channelB = 1f; - var greenRising = true; - - for (var i = 0; i < mappedColorCount - 1; i++) - { - var realR = Math.Min(channelR, 1f); - realR = Math.Max(realR, 0f); - - var realG = Math.Min(channelG, 1f); - realG = Math.Max(realG, 0f); - - var realB = Math.Min(channelB, 1f); - realB = Math.Max(realB, 0f); - - _mappedColors[i] = new Color(realR, realG, realB); - - if (channelG >= 1f) - greenRising = false; - - channelR += delta; - channelG += greenRising ? delta : -delta; - channelB -= delta; - } - - // Force high end to be red (or else if the temperature range is an even number, - // the green channel will not go down to zero in above loop). - _mappedColors[mappedColorCount - 1] = Color.red; - } - - public void CreateComfortMap() - { - var minComfortTemp = (int)ThingDefOf.Human.GetStatValueAbstract(StatDefOf.ComfyTemperatureMin) + 3; - var maxComfortTemp = (int)ThingDefOf.Human.GetStatValueAbstract(StatDefOf.ComfyTemperatureMax) - 3; - - // Narrow down the green range to a quarter scale, to make boundary temps stand out more. - - var comfortDoubleRange = (maxComfortTemp - minComfortTemp) * 2; - _mappedTemperatureRange = new IntRange( - minComfortTemp - comfortDoubleRange, maxComfortTemp + comfortDoubleRange); - - var mappedColorCount = _mappedTemperatureRange.max - _mappedTemperatureRange.min; - _mappedColors = new Color[mappedColorCount]; - - var channelDelta = 1f / comfortDoubleRange; - var channelR = -2f; - var channelG = 0f; - var channelB = 2f; - var greenRising = true; - - var mappingTemperature = _mappedTemperatureRange.min; - for (var i = 0; i < mappedColorCount - 1; i++, mappingTemperature++) - { - var realR = Math.Min(channelR, 1f); - realR = Math.Max(realR, 0f); - - var realG = Math.Min(channelG, 1f); - realG = Math.Max(realG, 0f); - - var realB = Math.Min(channelB, 1f); - realB = Math.Max(realB, 0f); - - _mappedColors[i] = new Color(realR, realG, realB); - - if (channelG >= 2f) - greenRising = false; - - var delta = channelDelta; - if (mappingTemperature >= minComfortTemp - 1 && - mappingTemperature <= maxComfortTemp) - { - delta *= 4; - } - - channelR += delta; - channelG += greenRising ? delta : -delta; - channelB -= delta; - } - - // Force high end to be red (or else if the temperature range is an even number, - // the green channel will not go down to zero in above loop). - _mappedColors[mappedColorCount - 1] = Color.red; - } - - public CellBoolDrawer Drawer - { - get - { - if (_drawerInt == null) - { - var map = Find.CurrentMap; - _drawerInt = new CellBoolDrawer(this, map.Size.x, map.Size.z, - Main.Instance.GetConfiguredOpacity()); - } - return _drawerInt; - } - } - - public bool GetCellBool(int index) - { - var map = Find.CurrentMap; - if (map.fogGrid.IsFogged(index)) - return false; - - var room = map.cellIndices.IndexToCell(index).GetRoom(map); - - if (room != null && !room.PsychologicallyOutdoors) - { - _nextColor = GetColorForTemperature(room.Temperature); - return true; - } - - return false; - } - - public int GetIndexForTemperature(float temperature) - { - // These two checks are probably not needed due to array index boundary checks - // below, but too worried to remove them now. - if (temperature <= _mappedTemperatureRange.min) - { - return 0; - } - - if (temperature >= _mappedTemperatureRange.max) - { - return _mappedColors.Length - 1; - } - - var colorMapIndex = (int)temperature - _mappedTemperatureRange.min; - if (colorMapIndex <= 0) - { - return 0; - } - - if (colorMapIndex >= _mappedColors.Length) - { - return _mappedColors.Length - 1; - } - - return colorMapIndex; - - } - - public Color GetColorForTemperature(float temperature) - { - return _mappedColors[GetIndexForTemperature(temperature)]; - } - - public Color GetCellExtraColor(int index) - { - return _nextColor; - } - - public Color Color => Color.white; - - public void Update(int updateDelay) - { - if (Main.Instance.ShowHeatMap) - { - Drawer.MarkForDraw(); - var tick = Find.TickManager.TicksGame; - if (tick >= _nextUpdateTick) - { - Drawer.SetDirty(); - _nextUpdateTick = tick + updateDelay; - } - Drawer.CellBoolDrawerUpdate(); - } - } - - public void Reset() - { - _drawerInt = null; - _nextUpdateTick = 0; - } - - private CellBoolDrawer _drawerInt; - - private IntRange _mappedTemperatureRange; - - private Color[] _mappedColors; - - private Color _nextColor; - - private int _nextUpdateTick; - } -} \ No newline at end of file +using HugsLib.Settings; +using HugsLib.Utils; +using RimWorld; +using RimWorld.Planet; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; +using Verse; + +namespace HeatMap +{ + public class HeatMap : HugsLib.ModBase + { + internal new ModLogger Logger => base.Logger; + + internal static HeatMap Instance { get; private set; } + + public override string ModIdentifier => "HeatMap"; + + public RoomTemperatureDisplayer TemperatureDisplayer { get; } = new RoomTemperatureDisplayer(); + + private const float _boxSize = 62f; + private bool _draggingThermometer = false; + private float _dragThermometerRight = 0f; + private float _dragThermometerTop = 0f; + + private readonly Dictionary _temperatureTextureCache = new Dictionary(); + + private SettingHandle _overrideVanillaOverlay; + private SettingHandle _showIndoorsOnly; + private SettingHandle _opacity; + private SettingHandle _updateDelay; + + private SettingHandle _showOutdoorThermometer; + private SettingHandle _outdoorThermometerOpacity; + private SettingHandle _outdoorThermometerFixed; + private SettingHandle _outdoorThermometerRight; + private SettingHandle _outdoorThermometerTop; + + private SettingHandle _showTemperatureOverRooms; + + private SettingHandle _useCustomRange; + private SettingHandle _customRangeMin; + private SettingHandle _customRangeMax; + + + public bool OverrideVanillaOverlay => + _overrideVanillaOverlay; + public bool ShowIndoorsOnly => + _showIndoorsOnly; + public float OverlayOpacity => + _opacity / 100f; + public bool ShouldUseCustomRange => + _useCustomRange; + public int CustomRangeMin => + _customRangeMin; + public int CustomRangeMax => + _customRangeMax; + + + public HeatMap() + { + Instance = this; + } + + public void UpdateOutdoorThermometer() + { + if (!_showOutdoorThermometer) + return; + + var right = Mathf.Clamp(_draggingThermometer ? _dragThermometerRight : _outdoorThermometerRight, _boxSize, UI.screenWidth); + var top = Mathf.Clamp(_draggingThermometer ? _dragThermometerTop : _outdoorThermometerTop, 0, UI.screenHeight - _boxSize); + var outRect = new Rect(UI.screenWidth - right, top, _boxSize, _boxSize); + if (TutorSystem.AdaptiveTrainingEnabled && Find.PlaySettings.showLearningHelper) + { + if (typeof(LearningReadout).GetField("windowRect", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(Find.Tutor.learningReadout) is Rect helpRect + && helpRect.Overlaps(outRect) == true) + outRect.x = helpRect.x - _boxSize - 5f; + } + + if (!_outdoorThermometerFixed && Event.current.isMouse) + { + switch (Event.current.type) + { + case EventType.MouseDown: + if (Mouse.IsOver(outRect) && Event.current.modifiers == EventModifiers.Shift) + { + Event.current.Use(); + + _dragThermometerRight = _outdoorThermometerRight.Value; + _dragThermometerTop = _outdoorThermometerTop.Value; + + _draggingThermometer = true; + } + break; + case EventType.MouseDrag: + if (_draggingThermometer) + { + Event.current.Use(); + + _dragThermometerRight -= Event.current.delta.x; + _dragThermometerRight = Mathf.Clamp(_dragThermometerRight, _boxSize, UI.screenWidth); + _dragThermometerTop += Event.current.delta.y; + _dragThermometerTop = Mathf.Clamp(_dragThermometerTop, 0, UI.screenHeight - _boxSize); + //outRect = new Rect(outRect.x - _dragThermometerRight, outRect.y + _dragThermometerTop, _boxSize, _boxSize); // repositioning is processed on next update + } + break; + case EventType.MouseUp: + if (_draggingThermometer) + { + Event.current.Use(); + + _outdoorThermometerRight.Value = _dragThermometerRight; + _outdoorThermometerTop.Value = _dragThermometerTop; + + _draggingThermometer = false; + } + break; + } + } + + var temperature = Find.CurrentMap.mapTemperature.OutdoorTemp; + var textureIndex = HeatMapHelper.GetIndexForTemperature(temperature); + if (!_temperatureTextureCache.ContainsKey(textureIndex)) + { + var backColor = HeatMapHelper.GetColorForTemperature(temperature); + backColor.a = _outdoorThermometerOpacity / 100f; + _temperatureTextureCache[textureIndex] = SolidColorMaterials.NewSolidColorTexture(backColor); + } + GUI.DrawTexture(outRect, _temperatureTextureCache[textureIndex]); + GUI.DrawTexture(outRect, Resources.DisplayBoder); + + var temperatureForDisplay = temperature.ToStringTemperature("F0"); + Text.Font = GameFont.Medium; + Text.Anchor = TextAnchor.MiddleCenter; + GUI.color = Color.white; + Widgets.Label(outRect, temperatureForDisplay); + + if (Widgets.ButtonInvisible(outRect)) + Find.PlaySettings.showTemperatureOverlay = !Find.PlaySettings.showTemperatureOverlay; + + TooltipHandler.TipRegion(outRect, "FALCHM.ThermometerTooltip".Translate()); + + Text.Anchor = TextAnchor.UpperLeft; + } + + public override void OnGUI() + { + if (Current.ProgramState != ProgramState.Playing + || Find.CurrentMap == null + || WorldRendererUtility.WorldRenderedNow) + return; + + UpdateOutdoorThermometer(); + if (Find.PlaySettings.showTemperatureOverlay && _showTemperatureOverRooms) + { + TemperatureDisplayer.Update(_updateDelay); + TemperatureDisplayer.OnGUI(); + } + + if (Event.current.type != EventType.KeyDown || Event.current.keyCode == KeyCode.None) + return; + + if (HeatMapKeyBingings.ToggleHeatMap.JustPressed) + { + if (WorldRendererUtility.WorldRenderedNow) + return; + + Find.PlaySettings.showTemperatureOverlay = !Find.PlaySettings.showTemperatureOverlay; + } + } + + public override void WorldLoaded() + { + ResetAll(); + } + + public override void DefsLoaded() + { + _overrideVanillaOverlay = Settings.GetHandle( + "overrideVanillaOverlay", + "FALCHM.OverrideVanillaOverlay".Translate(), + "FALCHM.OverrideVanillaOverlayDesc".Translate(), + true); + _overrideVanillaOverlay.ValueChanged += + val => ResetAll(); + + _showIndoorsOnly = Settings.GetHandle( + "showRoomsOnly", + "FALCHM.ShowIndoorsOnly".Translate(), + "FALCHM.ShowIndoorsOnlyDesc".Translate(), + true); + _showIndoorsOnly.ValueChanged += + val => ResetAll(); + + _opacity = Settings.GetHandle( + "opacity", "FALCHM.OverlayOpacity".Translate(), + "FALCHM.OverlayOpacityDesc".Translate(), 30, + Validators.IntRangeValidator(0, 100)); + _opacity.ValueChanged += + val => ResetAll(); + + _updateDelay = Settings.GetHandle("updateDelay", + "FALCHM.UpdateDelay".Translate(), + "FALCHM.UpdateDelayDesc".Translate(), + 100, + Validators.IntRangeValidator(1, 9999)); + + + _showOutdoorThermometer = Settings.GetHandle( + "showOutdoorThermometer", + "FALCHM.ShowOutDoorThermometer".Translate(), + "FALCHM.ShowOutDoorThermometerDesc".Translate(), + true); + + _outdoorThermometerOpacity = Settings.GetHandle( + "outdoorThermometerOpacity", + "FALCHM.ThermometerOpacity".Translate(), + "FALCHM.ThermometerOpacityDesc".Translate(), + 30, + Validators.IntRangeValidator(1, 100)); + _outdoorThermometerOpacity.ValueChanged += + val => _temperatureTextureCache.Clear(); + + _outdoorThermometerFixed = Settings.GetHandle( + "outdoorThermometerFixed", + "FALCHM.ThermometerFixed".Translate(), + "FALCHM.ThermometerFixedDesc".Translate(), + false); + + _outdoorThermometerRight = Settings.GetHandle( + "outdoorThermometerRight", + "FALCHM.ThermometerRight".Translate(), + "FALCHM.ThermometerRightDesc".Translate(), + 8f + _boxSize); + + _outdoorThermometerTop = Settings.GetHandle( + "outdoorThermometerTop", + "FALCHM.ThermometerTop".Translate(), + "FALCHM.ThermometerTopDesc".Translate(), + 8f); + + + _showTemperatureOverRooms = Settings.GetHandle( + "showTemperatureOverRooms", + "FALCHM.ShowTemperatureOverRooms".Translate(), + "FALCHM.ShowTemperatureOverRoomsDesc".Translate(), + true); + + + _useCustomRange = Settings.GetHandle( + "useCustomeRange", + "FALCHM.UseCustomeRange".Translate(), + "FALCHM.UseCustomeRangeDesc".Translate(), + false); + _useCustomRange.ValueChanged += + val => ResetAll(); + + + _customRangeMin = Settings.GetHandle("customRangeMin", "Unused", "Unused", 0); + _customRangeMax = Settings.GetHandle("customRangeMax", "Unused", "Unused", 40); + + _customRangeMin.VisibilityPredicate = () => false; + _customRangeMax.VisibilityPredicate = () => false; + + + var customRangeValidator = Validators.IntRangeValidator( + (int)GenTemperature.CelsiusTo(-100, Prefs.TemperatureMode), + (int)GenTemperature.CelsiusTo(100, Prefs.TemperatureMode)); + + var customRangeMin = Settings.GetHandle( + "customRangeMinPlaceholder", + "FALCHM.CustomRangeMin".Translate(), + $"{"FALCHM.CustomRangeMinDesc".Translate()} ({Prefs.TemperatureMode.ToStringHuman()})", + (int)GenTemperature.CelsiusTo(_customRangeMin, Prefs.TemperatureMode), + customRangeValidator); + + customRangeMin.Unsaved = true; + customRangeMin.VisibilityPredicate = () => _useCustomRange; + + var customRangeMax = Settings.GetHandle( + "customRangeMaxPlaceholder", + "FALCHM.CustomRangeMax".Translate(), + $"{"FALCHM.CustomRangeMaxDesc".Translate()} ({Prefs.TemperatureMode.ToStringHuman()})", + (int)GenTemperature.CelsiusTo(_customRangeMax, Prefs.TemperatureMode), + customRangeValidator); + + customRangeMax.Unsaved = true; + customRangeMax.VisibilityPredicate = () => _useCustomRange; + + + customRangeMin.ValueChanged += val => + { + if (customRangeMax <= customRangeMin) + customRangeMax.Value = customRangeMin + 1; + + _customRangeMin.Value = ConvertToCelcius(customRangeMin); + ResetAll(); + }; + + customRangeMax.ValueChanged += val => + { + if (customRangeMin >= customRangeMax) + customRangeMin.Value = customRangeMax - 1; + + _customRangeMax.Value = ConvertToCelcius(customRangeMax); + ResetAll(); + }; + } + + public void ResetAll() + { + HeatMapHelper.RegenerateColorMap(); + TemperatureDisplayer.Reset(); + _temperatureTextureCache.Clear(); + + Find.CurrentMap?.mapTemperature?.Drawer?.SetDirty(); + } + + private static int ConvertToCelcius(int value) + { + switch (Prefs.TemperatureMode) + { + default: + case TemperatureDisplayMode.Celsius: + return value; + + case TemperatureDisplayMode.Kelvin: + return value - 273; + + case TemperatureDisplayMode.Fahrenheit: + return (int)((value - 32) / 1.8f); + } + } + } +} diff --git a/src/HeatMap/HeatMap.csproj b/src/HeatMap/HeatMap.csproj index d9e7cce..2ab60b4 100644 --- a/src/HeatMap/HeatMap.csproj +++ b/src/HeatMap/HeatMap.csproj @@ -23,212 +23,16 @@ false - - ..\packages\Lib.Harmony.2.1.0\lib\net472\0Harmony.dll - - - ..\..\ThirdParty\Assembly-CSharp.dll - - - ..\packages\UnlimitedHugs.Rimworld.HugsLib.9.0.0\lib\net472\HugsLib.dll - - - - - - ..\..\ThirdParty\Unity.TextMeshPro.dll - - - False - ..\..\ThirdParty\UnityEngine.dll - - - ..\..\ThirdParty\UnityEngine.AccessibilityModule.dll - - - ..\..\ThirdParty\UnityEngine.AIModule.dll - - - ..\..\ThirdParty\UnityEngine.AndroidJNIModule.dll - - - ..\..\ThirdParty\UnityEngine.AnimationModule.dll - - - ..\..\ThirdParty\UnityEngine.ARModule.dll - - - ..\..\ThirdParty\UnityEngine.AssetBundleModule.dll - - - ..\..\ThirdParty\UnityEngine.AudioModule.dll - - - ..\..\ThirdParty\UnityEngine.ClothModule.dll - - - ..\..\ThirdParty\UnityEngine.ClusterInputModule.dll - - - ..\..\ThirdParty\UnityEngine.ClusterRendererModule.dll - - - ..\..\ThirdParty\UnityEngine.CoreModule.dll - - - ..\..\ThirdParty\UnityEngine.CrashReportingModule.dll - - - ..\..\ThirdParty\UnityEngine.DirectorModule.dll - - - ..\..\ThirdParty\UnityEngine.DSPGraphModule.dll - - - ..\..\ThirdParty\UnityEngine.GameCenterModule.dll - - - ..\..\ThirdParty\UnityEngine.GridModule.dll - - - ..\..\ThirdParty\UnityEngine.HotReloadModule.dll - - - ..\..\ThirdParty\UnityEngine.ImageConversionModule.dll - - - ..\..\ThirdParty\UnityEngine.IMGUIModule.dll - - - ..\..\ThirdParty\UnityEngine.InputLegacyModule.dll - - - ..\..\ThirdParty\UnityEngine.InputModule.dll - - - ..\..\ThirdParty\UnityEngine.JSONSerializeModule.dll - - - ..\..\ThirdParty\UnityEngine.LocalizationModule.dll - - - ..\..\ThirdParty\UnityEngine.ParticleSystemModule.dll - - - ..\..\ThirdParty\UnityEngine.PerformanceReportingModule.dll - - - ..\..\ThirdParty\UnityEngine.Physics2DModule.dll - - - ..\..\ThirdParty\UnityEngine.PhysicsModule.dll - - - ..\..\ThirdParty\UnityEngine.ProfilerModule.dll - - - ..\..\ThirdParty\UnityEngine.ScreenCaptureModule.dll - - - ..\..\ThirdParty\UnityEngine.SharedInternalsModule.dll - - - ..\..\ThirdParty\UnityEngine.SpriteMaskModule.dll - - - ..\..\ThirdParty\UnityEngine.SpriteShapeModule.dll - - - ..\..\ThirdParty\UnityEngine.StreamingModule.dll - - - ..\..\ThirdParty\UnityEngine.SubstanceModule.dll - - - ..\..\ThirdParty\UnityEngine.TerrainModule.dll - - - ..\..\ThirdParty\UnityEngine.TerrainPhysicsModule.dll - - - ..\..\ThirdParty\UnityEngine.TextCoreModule.dll - - - ..\..\ThirdParty\UnityEngine.TextRenderingModule.dll - - - ..\..\ThirdParty\UnityEngine.TilemapModule.dll - - - ..\..\ThirdParty\UnityEngine.TLSModule.dll - - - ..\..\ThirdParty\UnityEngine.UI.dll - - - ..\..\ThirdParty\UnityEngine.UIElementsModule.dll - - - ..\..\ThirdParty\UnityEngine.UIModule.dll - - - ..\..\ThirdParty\UnityEngine.UmbraModule.dll - - - ..\..\ThirdParty\UnityEngine.UNETModule.dll - - - ..\..\ThirdParty\UnityEngine.UnityAnalyticsModule.dll - - - ..\..\ThirdParty\UnityEngine.UnityConnectModule.dll - - - ..\..\ThirdParty\UnityEngine.UnityTestProtocolModule.dll - - - ..\..\ThirdParty\UnityEngine.UnityWebRequestAssetBundleModule.dll - - - ..\..\ThirdParty\UnityEngine.UnityWebRequestAudioModule.dll - - - ..\..\ThirdParty\UnityEngine.UnityWebRequestModule.dll - - - ..\..\ThirdParty\UnityEngine.UnityWebRequestTextureModule.dll - - - ..\..\ThirdParty\UnityEngine.UnityWebRequestWWWModule.dll - - - ..\..\ThirdParty\UnityEngine.VehiclesModule.dll - - - ..\..\ThirdParty\UnityEngine.VFXModule.dll - - - ..\..\ThirdParty\UnityEngine.VideoModule.dll - - - ..\..\ThirdParty\UnityEngine.VRModule.dll - - - ..\..\ThirdParty\UnityEngine.WindModule.dll - - - ..\..\ThirdParty\UnityEngine.XRModule.dll - - - - + + + @@ -279,7 +83,23 @@ - + + 2.0.1 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + 1.4.3525 + + + 2.2.2 + + + 9.0.1 + + + + diff --git a/src/HeatMap/HeatMapHelper.cs b/src/HeatMap/HeatMapHelper.cs new file mode 100644 index 0000000..53df5ed --- /dev/null +++ b/src/HeatMap/HeatMapHelper.cs @@ -0,0 +1,133 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Verse; + +namespace HeatMap +{ + public static class HeatMapHelper + { + internal static IntRange MappedTemperatureRange; + internal static Color[] MappedColors; + + public static void RegenerateColorMap() + { + if (HeatMap.Instance.ShouldUseCustomRange) + CreateCustomMap(); + else + CreateComfortMap(); + } + + private static void CreateCustomMap() + { + MappedTemperatureRange = new IntRange( + HeatMap.Instance.CustomRangeMin, HeatMap.Instance.CustomRangeMax); + + var mappedColorCount = MappedTemperatureRange.max - MappedTemperatureRange.min; + MappedColors = new Color[mappedColorCount]; + + var delta = 2f / (mappedColorCount - 1); + var channelR = -1f; + var channelG = 0f; + var channelB = 1f; + var greenRising = true; + + for (var i = 0; i < mappedColorCount - 1; i++) + { + var realR = Math.Min(channelR, 1f); + realR = Math.Max(realR, 0f); + + var realG = Math.Min(channelG, 1f); + realG = Math.Max(realG, 0f); + + var realB = Math.Min(channelB, 1f); + realB = Math.Max(realB, 0f); + + MappedColors[i] = new Color(realR, realG, realB); + + if (channelG >= 1f) + greenRising = false; + + channelR += delta; + channelG += greenRising ? delta : -delta; + channelB -= delta; + } + + // Force high end to be red (or else if the temperature range is an even number, + // the green channel will not go down to zero in above loop). + MappedColors[mappedColorCount - 1] = Color.red; + } + + private static void CreateComfortMap() + { + var minComfortTemp = (int)ThingDefOf.Human.GetStatValueAbstract(StatDefOf.ComfyTemperatureMin) + 3; + var maxComfortTemp = (int)ThingDefOf.Human.GetStatValueAbstract(StatDefOf.ComfyTemperatureMax) - 3; + + // Narrow down the green range to a quarter scale, to make boundary temps stand out more. + + var comfortDoubleRange = (maxComfortTemp - minComfortTemp) * 2; + MappedTemperatureRange = new IntRange( + minComfortTemp - comfortDoubleRange, maxComfortTemp + comfortDoubleRange); + + var mappedColorCount = MappedTemperatureRange.max - MappedTemperatureRange.min; + MappedColors = new Color[mappedColorCount]; + + var channelDelta = 1f / comfortDoubleRange; + var channelR = -2f; + var channelG = 0f; + var channelB = 2f; + var greenRising = true; + + var mappingTemperature = MappedTemperatureRange.min; + for (var i = 0; i < mappedColorCount - 1; i++, mappingTemperature++) + { + var realR = Math.Min(channelR, 1f); + realR = Math.Max(realR, 0f); + + var realG = Math.Min(channelG, 1f); + realG = Math.Max(realG, 0f); + + var realB = Math.Min(channelB, 1f); + realB = Math.Max(realB, 0f); + + MappedColors[i] = new Color(realR, realG, realB); + + if (channelG >= 2f) + greenRising = false; + + var delta = channelDelta; + if (mappingTemperature >= minComfortTemp - 1 && + mappingTemperature <= maxComfortTemp) + { + delta *= 4; + } + + channelR += delta; + channelG += greenRising ? delta : -delta; + channelB -= delta; + } + + // Force high end to be red (or else if the temperature range is an even number, + // the green channel will not go down to zero in above loop). + MappedColors[mappedColorCount - 1] = Color.red; + } + + public static int GetIndexForTemperature(float temperature) + { + var colorMapIndex = (int)temperature - MappedTemperatureRange.min; + if (colorMapIndex < 0) + colorMapIndex = 0; + else if (colorMapIndex >= MappedColors.Length) + colorMapIndex = MappedColors.Length - 1; + return colorMapIndex; + } + public static Color GetColorForTemperature(float temperature) + { + return MappedColors[GetIndexForTemperature(temperature)]; + } + } +} diff --git a/src/HeatMap/HeatMapKeyBindings.cs b/src/HeatMap/HeatMapKeyBindings.cs new file mode 100644 index 0000000..6aada24 --- /dev/null +++ b/src/HeatMap/HeatMapKeyBindings.cs @@ -0,0 +1,16 @@ +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Verse; + +namespace HeatMap +{ + [DefOf] + public static class HeatMapKeyBingings + { + public static KeyBindingDef ToggleHeatMap; + } +} diff --git a/src/HeatMap/Main.cs b/src/HeatMap/Main.cs deleted file mode 100644 index eeeac9a..0000000 --- a/src/HeatMap/Main.cs +++ /dev/null @@ -1,347 +0,0 @@ -using HugsLib.Settings; -using HugsLib.Utils; -using RimWorld; -using RimWorld.Planet; -using System.Collections.Generic; -using System.Reflection; -using UnityEngine; -using Verse; - -namespace HeatMap -{ - [DefOf] - public static class HeatMapKeyBingings - { - public static KeyBindingDef ToggleHeatMap; - } - - public class Main : HugsLib.ModBase - { - public Main() - { - Instance = this; - } - - public void UpdateHeatMap() - { - if (_heatMap == null) - _heatMap = new HeatMap(); - - if (_opacity > 0) - _heatMap.Update(_updateDelay); - } - - public void UpdateOutdoorThermometer() - { - if (!_showOutdoorThermometer) - return; - - if (_heatMap == null) - return; - - var right = Mathf.Clamp(_draggingThermometer ? _dragThermometerRight : _outdoorThermometerRight, _boxSize, UI.screenWidth); - var top = Mathf.Clamp(_draggingThermometer ? _dragThermometerTop : _outdoorThermometerTop, 0, UI.screenHeight - _boxSize); - var outRect = new Rect(UI.screenWidth - right, top, _boxSize, _boxSize); - if (TutorSystem.AdaptiveTrainingEnabled && Find.PlaySettings.showLearningHelper) - { - if (typeof(LearningReadout).GetField("windowRect", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(Find.Tutor.learningReadout) is Rect helpRect - && helpRect.Overlaps(outRect) == true) - outRect.x = helpRect.x - _boxSize - 5f; - } - - if (!_outdoorThermometerFixed && Event.current.isMouse) - { - switch (Event.current.type) - { - case EventType.MouseDown: - if (Mouse.IsOver(outRect) && Event.current.modifiers == EventModifiers.Shift) - { - Event.current.Use(); - - _dragThermometerRight = _outdoorThermometerRight.Value; - _dragThermometerTop = _outdoorThermometerTop.Value; - - _draggingThermometer = true; - } - break; - case EventType.MouseDrag: - if (_draggingThermometer) - { - Event.current.Use(); - - _dragThermometerRight -= Event.current.delta.x; - _dragThermometerRight = Mathf.Clamp(_dragThermometerRight, _boxSize, UI.screenWidth); - _dragThermometerTop += Event.current.delta.y; - _dragThermometerTop = Mathf.Clamp(_dragThermometerTop, 0, UI.screenHeight - _boxSize); - //outRect = new Rect(outRect.x - _dragThermometerRight, outRect.y + _dragThermometerTop, _boxSize, _boxSize); // repositioning is processed on next update - } - break; - case EventType.MouseUp: - if (_draggingThermometer) - { - Event.current.Use(); - - _outdoorThermometerRight.Value = _dragThermometerRight; - _outdoorThermometerTop.Value = _dragThermometerTop; - - _draggingThermometer = false; - } - break; - } - } - - var temperature = Find.CurrentMap.mapTemperature.OutdoorTemp; - var textureIndex = _heatMap.GetIndexForTemperature(temperature); - if (!_temperatureTextureCache.ContainsKey(textureIndex)) - { - var backColor = _heatMap.GetColorForTemperature(temperature); - backColor.a = _outdoorThermometerOpacity / 100f; - _temperatureTextureCache[textureIndex] = SolidColorMaterials.NewSolidColorTexture(backColor); - } - GUI.DrawTexture(outRect, _temperatureTextureCache[textureIndex]); - GUI.DrawTexture(outRect, Resources.DisplayBoder); - - var temperatureForDisplay = temperature.ToStringTemperature("F0"); - Text.Font = GameFont.Medium; - Text.Anchor = TextAnchor.MiddleCenter; - GUI.color = Color.white; - Widgets.Label(outRect, temperatureForDisplay); - - if (Widgets.ButtonInvisible(outRect)) - { - ShowHeatMap = !ShowHeatMap; - } - TooltipHandler.TipRegion(outRect, "FALCHM.ThermometerTooltip".Translate()); - - Text.Anchor = TextAnchor.UpperLeft; - } - - public override void OnGUI() - { - if (Current.ProgramState != ProgramState.Playing || - Find.CurrentMap == null || - WorldRendererUtility.WorldRenderedNow || - _heatMap == null) - { - return; - } - - UpdateOutdoorThermometer(); - if (ShowHeatMap && _showTemperatureOverRooms) - { - TemperatureDisplayer.Update(_updateDelay); - TemperatureDisplayer.OnGUI(); - } - - if (Event.current.type != EventType.KeyDown || Event.current.keyCode == KeyCode.None) - { - return; - } - - if (HeatMapKeyBingings.ToggleHeatMap.JustPressed) - { - if (WorldRendererUtility.WorldRenderedNow) - { - return; - } - ShowHeatMap = !ShowHeatMap; - } - } - - public override void WorldLoaded() - { - ResetAll(); - } - - public override void DefsLoaded() - { - _opacity = Settings.GetHandle( - "opacity", "FALCHM.OverlayOpacity".Translate(), - "FALCHM.OverlayOpacityDesc".Translate(), 30, - Validators.IntRangeValidator(0, 100)); - - _opacity.ValueChanged += val => { _heatMap?.Reset(); }; - - _updateDelay = Settings.GetHandle("updateDelay", - "FALCHM.UpdateDelay".Translate(), - "FALCHM.UpdateDelayDesc".Translate(), - 100, - Validators.IntRangeValidator(1, 9999)); - - - _showOutdoorThermometer = Settings.GetHandle( - "showOutdoorThermometer", - "FALCHM.ShowOutDoorThermometer".Translate(), - "FALCHM.ShowOutDoorThermometerDesc".Translate(), - true); - - _outdoorThermometerOpacity = Settings.GetHandle( - "outdoorThermometerOpacity", - "FALCHM.ThermometerOpacity".Translate(), - "FALCHM.ThermometerOpacityDesc".Translate(), - 30, - Validators.IntRangeValidator(1, 100)); - _outdoorThermometerOpacity.ValueChanged += val => { _temperatureTextureCache.Clear(); }; - - _outdoorThermometerFixed = Settings.GetHandle( - "outdoorThermometerFixed", - "FALCHM.ThermometerFixed".Translate(), - "FALCHM.ThermometerFixedDesc".Translate(), - false); - - _outdoorThermometerRight = Settings.GetHandle( - "outdoorThermometerRight", - "FALCHM.ThermometerRight".Translate(), - "FALCHM.ThermometerRightDesc".Translate(), - 8f + _boxSize); - - _outdoorThermometerTop = Settings.GetHandle( - "outdoorThermometerTop", - "FALCHM.ThermometerTop".Translate(), - "FALCHM.ThermometerTopDesc".Translate(), - 8f); - - - _showTemperatureOverRooms = Settings.GetHandle( - "showTemperatureOverRooms", - "FALCHM.ShowTemperatureOverRooms".Translate(), - "FALCHM.ShowTemperatureOverRoomsDesc".Translate(), - true); - - - _useCustomRange = Settings.GetHandle( - "useCustomeRange", - "FALCHM.UseCustomeRange".Translate(), - "FALCHM.UseCustomeRangeDesc".Translate(), - false); - _useCustomRange.ValueChanged += val => { ResetAll(); }; - - - _customRangeMin = Settings.GetHandle("customRangeMin", "Unused", "Unused", 0); - _customRangeMax = Settings.GetHandle("customRangeMax", "Unused", "Unused", 40); - - _customRangeMin.VisibilityPredicate = () => false; - _customRangeMax.VisibilityPredicate = () => false; - - - var customRangeValidator = Validators.IntRangeValidator( - (int)GenTemperature.CelsiusTo(-100, Prefs.TemperatureMode), - (int)GenTemperature.CelsiusTo(100, Prefs.TemperatureMode)); - - var customRangeMin = Settings.GetHandle( - "customRangeMinPlaceholder", - "FALCHM.CustomRangeMin".Translate(), - $"{"FALCHM.CustomRangeMinDesc".Translate()} ({Prefs.TemperatureMode.ToStringHuman()})", - (int)GenTemperature.CelsiusTo(_customRangeMin, Prefs.TemperatureMode), - customRangeValidator); - - customRangeMin.Unsaved = true; - customRangeMin.VisibilityPredicate = () => _useCustomRange; - - var customRangeMax = Settings.GetHandle( - "customRangeMaxPlaceholder", - "FALCHM.CustomRangeMax".Translate(), - $"{"FALCHM.CustomRangeMaxDesc".Translate()} ({Prefs.TemperatureMode.ToStringHuman()})", - (int)GenTemperature.CelsiusTo(_customRangeMax, Prefs.TemperatureMode), - customRangeValidator); - - customRangeMax.Unsaved = true; - customRangeMax.VisibilityPredicate = () => _useCustomRange; - - - customRangeMin.ValueChanged += val => - { - if (customRangeMax <= customRangeMin) - customRangeMax.Value = customRangeMin + 1; - - _customRangeMin.Value = ConvertToCelcius(customRangeMin); - ResetAll(); - }; - - - customRangeMax.ValueChanged += val => - { - if (customRangeMin >= customRangeMax) - customRangeMin.Value = customRangeMax - 1; - - _customRangeMax.Value = ConvertToCelcius(customRangeMax); - ResetAll(); - }; - } - - public bool ShouldUseCustomRange() - { - return _useCustomRange; - } - - public int GetCustomRangeMin() - { - return _customRangeMin; - } - - public int GetCustomRangeMax() - { - return _customRangeMax; - } - - public float GetConfiguredOpacity() - { - return _opacity / 100f; - } - - public void ResetAll() - { - _heatMap = null; - TemperatureDisplayer.Reset(); - _temperatureTextureCache.Clear(); - } - - private static int ConvertToCelcius(int value) - { - switch (Prefs.TemperatureMode) - { - case TemperatureDisplayMode.Celsius: - return value; - - case TemperatureDisplayMode.Kelvin: - return value - 273; - - default: - return (int)((value - 32) / 1.8f); - } - } - - internal new ModLogger Logger => base.Logger; - - internal static Main Instance { get; private set; } - - public override string ModIdentifier => "HeatMap"; - - public bool ShowHeatMap; - - public RoomTemperatureDisplayer TemperatureDisplayer { get; } = new RoomTemperatureDisplayer(); - - private HeatMap _heatMap; - private const float _boxSize = 62f; - private bool _draggingThermometer = false; - private float _dragThermometerRight = 0f; - private float _dragThermometerTop = 0f; - - private readonly Dictionary _temperatureTextureCache = new Dictionary(); - - private SettingHandle _opacity; - private SettingHandle _updateDelay; - - private SettingHandle _showOutdoorThermometer; - private SettingHandle _outdoorThermometerOpacity; - private SettingHandle _outdoorThermometerFixed; - private SettingHandle _outdoorThermometerRight; - private SettingHandle _outdoorThermometerTop; - - private SettingHandle _showTemperatureOverRooms; - - private SettingHandle _useCustomRange; - private SettingHandle _customRangeMin; - private SettingHandle _customRangeMax; - } -} diff --git a/src/HeatMap/MapInterface_Detour.cs b/src/HeatMap/MapInterface_Detour.cs deleted file mode 100644 index 0138a24..0000000 --- a/src/HeatMap/MapInterface_Detour.cs +++ /dev/null @@ -1,32 +0,0 @@ -using HarmonyLib; -using RimWorld; -using RimWorld.Planet; -using Verse; - -namespace HeatMap -{ - [HarmonyPatch(typeof(MapInterface), "MapInterfaceUpdate")] - public static class MapInterface_MapInterfaceUpdate_Detour - { - [HarmonyPostfix] - static void Postfix() - { - if (Find.CurrentMap == null || WorldRendererUtility.WorldRenderedNow) - { - return; - } - - Main.Instance.UpdateHeatMap(); - } - } - - [HarmonyPatch(typeof(MapInterface), "Notify_SwitchedMap")] - internal static class MapInterface_Notify_SwitchedMap_Detour - { - [HarmonyPostfix] - static void Postfix() - { - Main.Instance.ResetAll(); - } - } -} \ No newline at end of file diff --git a/src/HeatMap/MapTemperature_Detour.cs b/src/HeatMap/MapTemperature_Detour.cs new file mode 100644 index 0000000..251758d --- /dev/null +++ b/src/HeatMap/MapTemperature_Detour.cs @@ -0,0 +1,92 @@ +using HarmonyLib; +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Verse; + +namespace HeatMap +{ + [HarmonyPatch(typeof(MapTemperature), "GetColorForTemperature")] + public static class MapTemperature_GetColorForTemperature_Patch + { + [HarmonyPrefix] + static bool Prefix(ref Color __result, float temperature) + { + // use vanilla overlay + if (!HeatMap.Instance.OverrideVanillaOverlay) + return true; // execute original + + // use HeatMap's overlay + __result = HeatMapHelper.GetColorForTemperature(temperature); + return false; // skip the original + } + } + + [HarmonyPatch(typeof(MapTemperature), "get_Drawer")] + public static class MapTemperature_get_Drawer_Patch + { + [HarmonyPostfix] + static void Postfix(ref CellBoolDrawer __result) + { + // check if opacity changed + var opacity = HeatMap.Instance.OverlayOpacity; + if (__result?.opacity != opacity) + { + // set drawer opacity + __result.opacity = opacity; + + // Material must be set to null to regenerate it, which applies the opacity + __result.material = null; + } + } + } + + [HarmonyPatch(typeof(MapTemperature), "GetCellBool")] + public static class MapTemperature_GetCellBool_Patch + { + [HarmonyTranspiler] + static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + var output = new List(); + foreach (var instruction in instructions) + { + // override everything past GetRooms + if (instruction.opcode == OpCodes.Call + && instruction.operand is MethodInfo methodInfo + && methodInfo.Name == nameof(GridsUtility.GetRoom)) + break; + + // keep everything before GetRooms + output.Add(instruction); + } + + // call static System.Boolean HeatMap.MapTemperature_GetCellBool_Patch::Check(Verse.IntVec3 intVec, Verse.Map map) + output.Add( + new CodeInstruction(OpCodes.Call, + typeof(MapTemperature_GetCellBool_Patch).GetMethod(nameof(MapTemperature_GetCellBool_Patch.CheckRoom), BindingFlags.Static | BindingFlags.NonPublic))); + // ret NULL + output.Add(new CodeInstruction(OpCodes.Ret)); + + // output the changed IL code + return output; + } + + /// + /// Checks if tile is valid and if it is outdoors, check if outdoors temperature should get an overlay + /// + /// + /// + /// + private static bool CheckRoom(IntVec3 intVec, Map map) + { + var room = intVec.GetRoom(map); + return room != null && (!room.PsychologicallyOutdoors || !HeatMap.Instance.ShowIndoorsOnly); + } + } +} diff --git a/src/HeatMap/PlaySettings_Detour.cs b/src/HeatMap/PlaySettings_Detour.cs deleted file mode 100644 index 0e99472..0000000 --- a/src/HeatMap/PlaySettings_Detour.cs +++ /dev/null @@ -1,23 +0,0 @@ -using HarmonyLib; -using RimWorld; -using Verse; - -namespace HeatMap -{ - [HarmonyPatch(typeof(PlaySettings), "DoPlaySettingsGlobalControls")] - public static class PlaySettings_Detour - { - [HarmonyPostfix] - static void PostFix(WidgetRow row, bool worldView) - { - if (worldView) - return; - - if (row == null || Resources.Icon == null) - return; - - row.ToggleableIcon(ref Main.Instance.ShowHeatMap, Resources.Icon, - "FALCHM.ShowHeatMap".Translate(), SoundDefOf.Mouseover_ButtonToggle); - } - } -} \ No newline at end of file diff --git a/src/HeatMap/Properties/AssemblyInfo.cs b/src/HeatMap/Properties/AssemblyInfo.cs index 6305ec1..e9ec3df 100644 --- a/src/HeatMap/Properties/AssemblyInfo.cs +++ b/src/HeatMap/Properties/AssemblyInfo.cs @@ -31,8 +31,9 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.3.18.0")] -[assembly: AssemblyFileVersion("1.3.18.0")] +[assembly: AssemblyVersion("1.4.18.0")] +[assembly: AssemblyFileVersion("1.4.18.0")] + diff --git a/src/HeatMap/packages.config b/src/HeatMap/packages.config deleted file mode 100644 index e422788..0000000 --- a/src/HeatMap/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file