From 03c76299c3a5d87776393d6f1a6cc6eaedefcaf5 Mon Sep 17 00:00:00 2001 From: Chris F Date: Sun, 23 Oct 2022 21:38:20 +0200 Subject: [PATCH 1/6] Version 1.4 rework --- mod-structure/1.4/Assemblies/HeatMap.dll | Bin 0 -> 20480 bytes .../English/Keyed/FALCHM_LanguageData.xml | 47 +- src/HeatMap/HeatMap.cs | 551 +++++++++++------- src/HeatMap/HeatMap.csproj | 220 +------ src/HeatMap/HeatMapHelper.cs | 133 +++++ src/HeatMap/HeatMapKeyBindings.cs | 16 + src/HeatMap/Main.cs | 347 ----------- src/HeatMap/MapInterface_Detour.cs | 32 - src/HeatMap/MapTemperature_Detour.cs | 92 +++ src/HeatMap/PlaySettings_Detour.cs | 23 - src/HeatMap/Properties/AssemblyInfo.cs | 5 +- src/HeatMap/packages.config | 5 - 12 files changed, 632 insertions(+), 839 deletions(-) create mode 100644 mod-structure/1.4/Assemblies/HeatMap.dll create mode 100644 src/HeatMap/HeatMapHelper.cs create mode 100644 src/HeatMap/HeatMapKeyBindings.cs delete mode 100644 src/HeatMap/Main.cs delete mode 100644 src/HeatMap/MapInterface_Detour.cs create mode 100644 src/HeatMap/MapTemperature_Detour.cs delete mode 100644 src/HeatMap/PlaySettings_Detour.cs delete mode 100644 src/HeatMap/packages.config diff --git a/mod-structure/1.4/Assemblies/HeatMap.dll b/mod-structure/1.4/Assemblies/HeatMap.dll new file mode 100644 index 0000000000000000000000000000000000000000..27d6293142c17feae752592cefe60bd666acf9af GIT binary patch literal 20480 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 + 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 From 851cb89ada46b1271e8378255e66d706837c3de9 Mon Sep 17 00:00:00 2001 From: Chris F Date: Sun, 23 Oct 2022 21:47:41 +0200 Subject: [PATCH 2/6] Added 1.4 as supported version in About.xml (totally forgot that) --- mod-structure/About/About.xml | 1 + 1 file changed, 1 insertion(+) 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
  • From 090ba73fdbe973e83f2e30a123216c1d45a97d43 Mon Sep 17 00:00:00 2001 From: Chris F Date: Mon, 24 Oct 2022 23:54:21 +0200 Subject: [PATCH 3/6] Added settings for overlay hues; reworked how overlay colors are generated for both standard overlay and custom overlay (not done yet: language data missing and more testing required) --- mod-structure/1.4/Assemblies/HeatMap.dll | Bin 20480 -> 24576 bytes .../English/Keyed/FALCHM_LanguageData.xml | 22 +- src/HeatMap/ColorHelper.cs | 106 ++++++++++ src/HeatMap/HeatMap.cs | 137 +++++++++---- src/HeatMap/HeatMap.csproj | 1 + src/HeatMap/HeatMapHelper.cs | 193 +++++++----------- 6 files changed, 289 insertions(+), 170 deletions(-) create mode 100644 src/HeatMap/ColorHelper.cs diff --git a/mod-structure/1.4/Assemblies/HeatMap.dll b/mod-structure/1.4/Assemblies/HeatMap.dll index 27d6293142c17feae752592cefe60bd666acf9af..10b78460c339455c6790b24bee7c0e26b49c4ad5 100644 GIT binary patch literal 24576 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 Zfdz)FzgzKL@k`nN2IKvoAr@KS{{w{|n!x}7 literal 20480 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 Override vanilla overlay Use Heat Map's overlay instead of the vanilla overlay. -(default: on) +(default: {default}) Only overlay indoor areas Only show the overlay for indoors areas. -(default: on) +(default: {default}) Opacity of overlay Reduce this value to make the overlay more transparent. -(default: 30%) +(default: {default}%) Overlay update delay 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) +(default: {default}) Show outdoor thermometer Displays a widget in the top right hand corner of the UI showing current outdoor temperature. -(default: on) +(default: {default}) Opacity of thermometer Reduce this value to make the outdoor temperature thermometer background color more transparent. -(default: 30%) +(default: {default}%) Lock thermometer position Locks the thermometer widget in place, disabling shift-click dragging. -(default: off) +(default: {default}) 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. -(default: 70) +(default: {default}) 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. -(default: 8) +(default: {default}) Show temperature over rooms When overlay is on, this will also display the room temperature over the centre of the room. -(default: on) +(default: {default}) 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 -(default: off) +(default: {default}) Custom range min Blue end of gradient. Temperatures below this will be blue. Use current temperature units diff --git a/src/HeatMap/ColorHelper.cs b/src/HeatMap/ColorHelper.cs new file mode 100644 index 0000000..5f9f6df --- /dev/null +++ b/src/HeatMap/ColorHelper.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Verse; + +namespace HeatMap +{ + public class ColorHelper + { + /// + /// Calculate a color from a color gradient between two colors + /// + /// Gradient start color + /// Gradient end color + /// Position in color gradient; can be equal to return + /// Number of steps in the color gradient + /// Alpha-Channel + /// + public static Color Gradient(Color start, Color end, float pos, float steps, float a = 1f) + { + if (steps <= 0) + return new Color(end.r, end.g, end.b, a); + + pos %= steps + 1; + + var rMin = start.r; + var rMax = end.r; + var gMin = start.g; + var gMax = end.g; + var bMin = start.b; + var bMax = end.b; + + var rAverage = rMin + ((rMax - rMin) * pos / steps); + var gAverage = gMin + ((gMax - gMin) * pos / steps); + var bAverage = bMin + ((bMax - bMin) * pos / steps); + + return new Color(rAverage, gAverage, bAverage, a); + } + + /// + /// Creates a color gradient using and + /// + /// Colors to be used for the gradient with relative weighting factor; relatively higher factors will make the color be used for more steps + /// Steps with stepwidth; longer stepwidth advances through the gradient faster + /// Alpha-Channel + /// + public static List Gradient(IList> colors, IList steps, float a = 1f) + { + if (!(colors?.Count > 0)) + { + Log.Warning("Colors must have at least 1 element"); + return new List { Color.white }; + } + + if (!(steps?.Count > 0)) + { + Log.Warning("Steps must have at least 1 element"); + return new List { Color.white }; + } + + var stepsTotal = 0f; + foreach (var step in steps) + stepsTotal += step; + if (stepsTotal == 0) + { + Log.Warning("Steps total must be greater than 0"); + return new List { Color.white }; + } + + var total = 0f; + for (int i = 0; i < colors.Count - 1; i++) + total += colors[i].Item2; + + var offset = 0f; + var list = new List>(); + foreach (var item in colors) + { + list.Add(new Tuple(item.Item1, offset)); + offset += stepsTotal * (item.Item2 / total); + } + + var output = new List(); + var pos = 0f; + for (int i = 0, j = 0; i < list.Count - 1; i++) + { + var item = list[i]; + var next = list[i + 1]; + while (pos < next.Item2) + { + output.Add(Gradient( + item.Item1, + next.Item1, + pos - item.Item2, + next.Item2 - item.Item2, + a)); + pos += steps[j++]; + } + } + output.Add(colors.Last().Item1); + return output; + } + } +} diff --git a/src/HeatMap/HeatMap.cs b/src/HeatMap/HeatMap.cs index 0558235..21cced5 100644 --- a/src/HeatMap/HeatMap.cs +++ b/src/HeatMap/HeatMap.cs @@ -39,9 +39,14 @@ public class HeatMap : HugsLib.ModBase private SettingHandle _showTemperatureOverRooms; + public const int GradientSteps = 5; + private readonly SettingHandle[] _gradientHue = new SettingHandle[GradientSteps]; + private SettingHandle _useCustomRange; private SettingHandle _customRangeMin; private SettingHandle _customRangeMax; + private SettingHandle _customRangeComfortableMin; + private SettingHandle _customRangeComfortableMax; public bool OverrideVanillaOverlay => @@ -56,6 +61,17 @@ public class HeatMap : HugsLib.ModBase _customRangeMin; public int CustomRangeMax => _customRangeMax; + public int CustomRangeComfortableMin => + _customRangeComfortableMin; + public int CustomRangeComfortableMax => + _customRangeComfortableMax; + + public Color GetGradientColor(int index) + { + if (index >= 0 && index < _gradientHue.Length) + return Color.HSVToRGB(_gradientHue[index] / 360f, 1f, 1f); + return Color.black; + } public HeatMap() @@ -180,29 +196,29 @@ public override void DefsLoaded() _overrideVanillaOverlay = Settings.GetHandle( "overrideVanillaOverlay", "FALCHM.OverrideVanillaOverlay".Translate(), - "FALCHM.OverrideVanillaOverlayDesc".Translate(), + "FALCHM.OverrideVanillaOverlayDesc".Translate(new NamedArgument(true, "default")), true); - _overrideVanillaOverlay.ValueChanged += - val => ResetAll(); + _overrideVanillaOverlay.ValueChanged += val => ResetAll(); _showIndoorsOnly = Settings.GetHandle( "showRoomsOnly", "FALCHM.ShowIndoorsOnly".Translate(), - "FALCHM.ShowIndoorsOnlyDesc".Translate(), + "FALCHM.ShowIndoorsOnlyDesc".Translate(new NamedArgument(true, "default")), true); - _showIndoorsOnly.ValueChanged += - val => ResetAll(); + _showIndoorsOnly.ValueChanged += val => ResetAll(); _opacity = Settings.GetHandle( - "opacity", "FALCHM.OverlayOpacity".Translate(), - "FALCHM.OverlayOpacityDesc".Translate(), 30, + "opacity", + "FALCHM.OverlayOpacity".Translate(), + "FALCHM.OverlayOpacityDesc".Translate(new NamedArgument(30, "default")), + 30, Validators.IntRangeValidator(0, 100)); - _opacity.ValueChanged += - val => ResetAll(); + _opacity.ValueChanged += val => ResetAll(); - _updateDelay = Settings.GetHandle("updateDelay", + _updateDelay = Settings.GetHandle( + "updateDelay", "FALCHM.UpdateDelay".Translate(), - "FALCHM.UpdateDelayDesc".Translate(), + "FALCHM.UpdateDelayDesc".Translate(new NamedArgument(100, "default")), 100, Validators.IntRangeValidator(1, 9999)); @@ -210,83 +226,119 @@ public override void DefsLoaded() _showOutdoorThermometer = Settings.GetHandle( "showOutdoorThermometer", "FALCHM.ShowOutDoorThermometer".Translate(), - "FALCHM.ShowOutDoorThermometerDesc".Translate(), + "FALCHM.ShowOutDoorThermometerDesc".Translate(new NamedArgument(true, "default")), true); _outdoorThermometerOpacity = Settings.GetHandle( "outdoorThermometerOpacity", "FALCHM.ThermometerOpacity".Translate(), - "FALCHM.ThermometerOpacityDesc".Translate(), + "FALCHM.ThermometerOpacityDesc".Translate(new NamedArgument(30, "default")), 30, Validators.IntRangeValidator(1, 100)); - _outdoorThermometerOpacity.ValueChanged += - val => _temperatureTextureCache.Clear(); + _outdoorThermometerOpacity.ValueChanged += val => _temperatureTextureCache.Clear(); _outdoorThermometerFixed = Settings.GetHandle( "outdoorThermometerFixed", "FALCHM.ThermometerFixed".Translate(), - "FALCHM.ThermometerFixedDesc".Translate(), + "FALCHM.ThermometerFixedDesc".Translate(new NamedArgument(false, "default")), false); _outdoorThermometerRight = Settings.GetHandle( "outdoorThermometerRight", "FALCHM.ThermometerRight".Translate(), - "FALCHM.ThermometerRightDesc".Translate(), + "FALCHM.ThermometerRightDesc".Translate(new NamedArgument(8f + _boxSize, "default")), 8f + _boxSize); _outdoorThermometerTop = Settings.GetHandle( "outdoorThermometerTop", "FALCHM.ThermometerTop".Translate(), - "FALCHM.ThermometerTopDesc".Translate(), + "FALCHM.ThermometerTopDesc".Translate(new NamedArgument(8f, "default")), 8f); _showTemperatureOverRooms = Settings.GetHandle( "showTemperatureOverRooms", "FALCHM.ShowTemperatureOverRooms".Translate(), - "FALCHM.ShowTemperatureOverRoomsDesc".Translate(), + "FALCHM.ShowTemperatureOverRoomsDesc".Translate(new NamedArgument(true, "default")), true); + var gradientValidator = Validators.FloatRangeValidator(0f, 360f); + for (int i = 0; i < GradientSteps; i++) + { + _gradientHue[i] = Settings.GetHandle( + $"gradientHue{i}", + $"FALCHM.GradientHue{i}".Translate(), + $"FALCHM.GradientHueDesc".Translate(new NamedArgument(240f - 60f * i, "default")), + 240f - 60f * i, // 240 = blue, 180 = cyan, 120 = green, 60 = yellow, 0 = red + gradientValidator); + _gradientHue[i].ValueChanged += val => ResetAll(); + } + + _useCustomRange = Settings.GetHandle( "useCustomeRange", "FALCHM.UseCustomeRange".Translate(), - "FALCHM.UseCustomeRangeDesc".Translate(), + "FALCHM.UseCustomeRangeDesc".Translate(new NamedArgument(false, "default")), false); - _useCustomRange.ValueChanged += - val => ResetAll(); + _useCustomRange.ValueChanged += val => ResetAll(); + - _customRangeMin = Settings.GetHandle("customRangeMin", "Unused", "Unused", 0); - _customRangeMax = Settings.GetHandle("customRangeMax", "Unused", "Unused", 40); + (var mappedRange, var minComfortTemp, var maxComfortTemp) = HeatMapHelper.GetComfortTemperatureRanges(); + _customRangeMin = Settings.GetHandle("customRangeMin", "Unused", "Unused", mappedRange.min); _customRangeMin.VisibilityPredicate = () => false; + + _customRangeMax = Settings.GetHandle("customRangeMax", "Unused", "Unused", mappedRange.max); _customRangeMax.VisibilityPredicate = () => false; + _customRangeComfortableMin = Settings.GetHandle("customRangeComfortableMin", "Unused", "Unused", minComfortTemp); + _customRangeComfortableMin.VisibilityPredicate = () => false; + + _customRangeComfortableMax = Settings.GetHandle("customRangeComfortableMax", "Unused", "Unused", maxComfortTemp); + _customRangeComfortableMax.VisibilityPredicate = () => false; + var customRangeValidator = Validators.IntRangeValidator( - (int)GenTemperature.CelsiusTo(-100, Prefs.TemperatureMode), - (int)GenTemperature.CelsiusTo(100, Prefs.TemperatureMode)); + (int)GenTemperature.CelsiusTo(-273f, Prefs.TemperatureMode), + (int)GenTemperature.CelsiusTo(1000f, Prefs.TemperatureMode)); - var customRangeMin = Settings.GetHandle( + var customRangeMin = Settings.GetHandle( "customRangeMinPlaceholder", "FALCHM.CustomRangeMin".Translate(), - $"{"FALCHM.CustomRangeMinDesc".Translate()} ({Prefs.TemperatureMode.ToStringHuman()})", - (int)GenTemperature.CelsiusTo(_customRangeMin, Prefs.TemperatureMode), - customRangeValidator); - + $"{"FALCHM.CustomRangeMinDesc".Translate(new NamedArgument(mappedRange.min, "default"))} ({Prefs.TemperatureMode.ToStringHuman()})", + validator: customRangeValidator); customRangeMin.Unsaved = true; customRangeMin.VisibilityPredicate = () => _useCustomRange; + customRangeMin.Value = (int)GenTemperature.CelsiusTo(_customRangeMin, Prefs.TemperatureMode); - var customRangeMax = Settings.GetHandle( + var customRangeMax = Settings.GetHandle( "customRangeMaxPlaceholder", "FALCHM.CustomRangeMax".Translate(), - $"{"FALCHM.CustomRangeMaxDesc".Translate()} ({Prefs.TemperatureMode.ToStringHuman()})", - (int)GenTemperature.CelsiusTo(_customRangeMax, Prefs.TemperatureMode), - customRangeValidator); - + $"{"FALCHM.CustomRangeMaxDesc".Translate(new NamedArgument(mappedRange.max, "default"))} ({Prefs.TemperatureMode.ToStringHuman()})", + validator: customRangeValidator); customRangeMax.Unsaved = true; customRangeMax.VisibilityPredicate = () => _useCustomRange; + customRangeMax.Value = (int)GenTemperature.CelsiusTo(_customRangeMax, Prefs.TemperatureMode); + + var customRangeComfortableMin = Settings.GetHandle( + "customRangeComfortableMinPlaceholder", + "FALCHM.CustomRangeComfortableMin".Translate(), + $"{"FALCHM.CustomRangeComfortableMinDesc".Translate(new NamedArgument(minComfortTemp, "default"))} ({Prefs.TemperatureMode.ToStringHuman()})", + validator: customRangeValidator); + customRangeComfortableMin.Unsaved = true; + customRangeComfortableMin.VisibilityPredicate = () => _useCustomRange; + customRangeComfortableMin.Value = (int)GenTemperature.CelsiusTo(_customRangeComfortableMin, Prefs.TemperatureMode); + + var customRangeComfortableMax = Settings.GetHandle( + "customRangeComfortableMaxPlaceholder", + "FALCHM.CustomRangeComfortableMax".Translate(), + $"{"FALCHM.CustomRangeComfortableMaxDesc".Translate(new NamedArgument(maxComfortTemp, "default"))} ({Prefs.TemperatureMode.ToStringHuman()})", + validator: customRangeValidator); + customRangeComfortableMax.Unsaved = true; + customRangeComfortableMax.VisibilityPredicate = () => _useCustomRange; + customRangeComfortableMax.Value = (int)GenTemperature.CelsiusTo(_customRangeComfortableMax, Prefs.TemperatureMode); customRangeMin.ValueChanged += val => @@ -297,7 +349,6 @@ public override void DefsLoaded() _customRangeMin.Value = ConvertToCelcius(customRangeMin); ResetAll(); }; - customRangeMax.ValueChanged += val => { if (customRangeMin >= customRangeMax) @@ -306,6 +357,16 @@ public override void DefsLoaded() _customRangeMax.Value = ConvertToCelcius(customRangeMax); ResetAll(); }; + customRangeComfortableMin.ValueChanged += val => + { + _customRangeComfortableMin.Value = ConvertToCelcius(customRangeComfortableMin); + ResetAll(); + }; + customRangeComfortableMax.ValueChanged += val => + { + _customRangeComfortableMax.Value = ConvertToCelcius(customRangeComfortableMax); + ResetAll(); + }; } public void ResetAll() diff --git a/src/HeatMap/HeatMap.csproj b/src/HeatMap/HeatMap.csproj index 2ab60b4..9496dd9 100644 --- a/src/HeatMap/HeatMap.csproj +++ b/src/HeatMap/HeatMap.csproj @@ -29,6 +29,7 @@ + diff --git a/src/HeatMap/HeatMapHelper.cs b/src/HeatMap/HeatMapHelper.cs index 53df5ed..792fd7b 100644 --- a/src/HeatMap/HeatMapHelper.cs +++ b/src/HeatMap/HeatMapHelper.cs @@ -9,125 +9,76 @@ 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)]; - } - } + 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() + { + var minComfortTemp = HeatMap.Instance.CustomRangeComfortableMin; + var maxComfortTemp = HeatMap.Instance.CustomRangeComfortableMax; + + MappedTemperatureRange = new IntRange(HeatMap.Instance.CustomRangeMin, HeatMap.Instance.CustomRangeMax); + MappedColors = CreateColorGradient(MappedTemperatureRange, minComfortTemp, maxComfortTemp); + } + + private static void CreateComfortMap() + { + (var mappedRange, var minComfortTemp, var maxComfortTemp) = GetComfortTemperatureRanges(); + + MappedTemperatureRange = mappedRange; + MappedColors = CreateColorGradient(MappedTemperatureRange, minComfortTemp, maxComfortTemp); + } + + private static Color[] CreateColorGradient(IntRange range, int minComfortTemp, int maxComfortTemp) + { + var mappedColorCount = MappedTemperatureRange.max - MappedTemperatureRange.min; + + var gradientColors = new List>(); + for (int i = 0; i < HeatMap.GradientSteps; i++) + gradientColors.Add(new Tuple(HeatMap.Instance.GetGradientColor(i), 1f)); + + var gradientSteps = new List(); + for (int i = 0, t = range.min; i < mappedColorCount; i++, t++) + gradientSteps.Add((t >= minComfortTemp - 1 && t <= maxComfortTemp) ? 4 : 1); + + return ColorHelper.Gradient(gradientColors, gradientSteps).ToArray(); + } + + public static (IntRange mappedRange, int minComfortable, int maxComfortable) GetComfortTemperatureRanges() + { + var human = ThingDefOf.Human; + var minComfortTemp = (int)human.GetStatValueAbstract(StatDefOf.ComfyTemperatureMin) + 3; + var maxComfortTemp = (int)human.GetStatValueAbstract(StatDefOf.ComfyTemperatureMax) - 3; + + var comfortDoubleRange = (maxComfortTemp - minComfortTemp) * 2; + var mappedTemperatureRange = new IntRange( + minComfortTemp - comfortDoubleRange, maxComfortTemp + comfortDoubleRange); + + return (mappedTemperatureRange, minComfortTemp, maxComfortTemp); + } + + 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)]; + } + } } From d00be5d3d1c73f9eaaf7a75067aeecfd1b67285c Mon Sep 17 00:00:00 2001 From: Chris F Date: Tue, 25 Oct 2022 00:58:24 +0200 Subject: [PATCH 4/6] Added language data; renamed "Comfortable" to "Comfort" --- HeatMap.xlsx | Bin 0 -> 13760 bytes mod-structure/1.4/Assemblies/HeatMap.dll | Bin 24576 -> 25088 bytes .../English/Keyed/FALCHM_LanguageData.xml | 42 +++++++++- src/HeatMap/HeatMap.cs | 73 +++++++++--------- src/HeatMap/HeatMapHelper.cs | 8 +- 5 files changed, 80 insertions(+), 43 deletions(-) create mode 100644 HeatMap.xlsx diff --git a/HeatMap.xlsx b/HeatMap.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..19741e7060c1899098a6a6dc35cb07cbb2de6e3f GIT binary patch literal 13760 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 K5(w+BfBy$77dBJ? literal 0 HcmV?d00001 diff --git a/mod-structure/1.4/Assemblies/HeatMap.dll b/mod-structure/1.4/Assemblies/HeatMap.dll index 10b78460c339455c6790b24bee7c0e26b49c4ad5..623e695aec63bb371e192cdf07f19bda3a738e44 100644 GIT binary patch delta 6410 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 b-29fZ$xpHtbNsgsUf8F_y5lp}ROb9&LJu%x delta 6209 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 N(P2&cJ83Dl{~IR<2fP3P diff --git a/mod-structure/Languages/English/Keyed/FALCHM_LanguageData.xml b/mod-structure/Languages/English/Keyed/FALCHM_LanguageData.xml index 72fc2a0..a9e8a46 100644 --- a/mod-structure/Languages/English/Keyed/FALCHM_LanguageData.xml +++ b/mod-structure/Languages/English/Keyed/FALCHM_LanguageData.xml @@ -21,7 +21,7 @@ (default: {default}) 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 colour more transparent. (default: {default}%) Lock thermometer position @@ -40,17 +40,51 @@ When overlay is on, this will also display the room temperature over the centre of the room. (default: {default}) + Colour gradient hue {index} + Defines which colours will be used for the overlay's colour gradient. Colours are given as hue in degree. +(default: {default}) + +- Colours: +0°/360° = red +60° = yellow +120° = green +180° = cyan +240° = blue +300° = magenta + +Each degree of temperature between min/max (default: {min}/{max}) has a relative base step width on the gradient of 1, except for the values between min/max comfort temperature (default: {comfortMin}/{comfortMax}), which have a step width of 4. +Each degree of these comfort temperatures takes up four times as much of the colour gradient as one outside of the comfort levels. +For example, using default settings, green will be used for fewer degrees of temperature than red or blue. + 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, custom temperature boundaries for the colour gradient can be set. (default: {default}) Custom range min - Blue end of gradient. Temperatures below this will be blue. Use current temperature units + Lower temperature limit of the colour gradient. +Temperatures below this will be coloured using colour gradient hue 0, default: blue. +Uses current temperature units +(default: {default}) Custom range max - Red end of gradient. Temperatures above this will be red. Use current temperature units + Upper temperature limit of the colour gradient. +Temperatures above this will be coloured using colour gradient hue 4, default: red. +Uses current temperature units +(default: {default}) + + Custom comfort range min + Lower comfort temperature. +Between min-max comfort temperature the colour changes four times as quickly as outside these limits, making small changes more noticeable. +Uses current temperature units +(default: {default}) + + Custom comfort range max + Upper comfort temperature. +Between min-max comfort temperature the colour changes four times as quickly as outside these limits, making small changes more noticeable. +Uses current temperature units +(default: {default}) Outdoor temperature. Click to toggle heat map overlay. diff --git a/src/HeatMap/HeatMap.cs b/src/HeatMap/HeatMap.cs index 21cced5..1084de3 100644 --- a/src/HeatMap/HeatMap.cs +++ b/src/HeatMap/HeatMap.cs @@ -45,8 +45,8 @@ public class HeatMap : HugsLib.ModBase private SettingHandle _useCustomRange; private SettingHandle _customRangeMin; private SettingHandle _customRangeMax; - private SettingHandle _customRangeComfortableMin; - private SettingHandle _customRangeComfortableMax; + private SettingHandle _customRangeComfortMin; + private SettingHandle _customRangeComfortMax; public bool OverrideVanillaOverlay => @@ -61,10 +61,10 @@ public class HeatMap : HugsLib.ModBase _customRangeMin; public int CustomRangeMax => _customRangeMax; - public int CustomRangeComfortableMin => - _customRangeComfortableMin; - public int CustomRangeComfortableMax => - _customRangeComfortableMax; + public int CustomRangeComfortMin => + _customRangeComfortMin; + public int CustomRangeComfortMax => + _customRangeComfortMax; public Color GetGradientColor(int index) { @@ -262,15 +262,21 @@ public override void DefsLoaded() "FALCHM.ShowTemperatureOverRoomsDesc".Translate(new NamedArgument(true, "default")), true); + (var mappedRange, var minComfortTemp, var maxComfortTemp) = HeatMapHelper.GetComfortTemperatureRanges(); var gradientValidator = Validators.FloatRangeValidator(0f, 360f); for (int i = 0; i < GradientSteps; i++) { _gradientHue[i] = Settings.GetHandle( $"gradientHue{i}", - $"FALCHM.GradientHue{i}".Translate(), - $"FALCHM.GradientHueDesc".Translate(new NamedArgument(240f - 60f * i, "default")), - 240f - 60f * i, // 240 = blue, 180 = cyan, 120 = green, 60 = yellow, 0 = red + $"FALCHM.GradientHue".Translate(new NamedArgument(i, "index")), + $"FALCHM.GradientHueDesc".Translate( + new NamedArgument(240f - 60f * i, "default"), + new NamedArgument(mappedRange.min, "min"), + new NamedArgument(mappedRange.max, "max"), + new NamedArgument(minComfortTemp, "comfortMin"), + new NamedArgument(maxComfortTemp, "comfortMax")), + 240f - 60f * i, // 0° = red, 60° = yellow, 120° = green, 180° = cyan, 240° = blue, 300° = magenta; standard begins at blue (low) and ends at red (high) gradientValidator); _gradientHue[i].ValueChanged += val => ResetAll(); } @@ -284,20 +290,17 @@ public override void DefsLoaded() _useCustomRange.ValueChanged += val => ResetAll(); - - (var mappedRange, var minComfortTemp, var maxComfortTemp) = HeatMapHelper.GetComfortTemperatureRanges(); - _customRangeMin = Settings.GetHandle("customRangeMin", "Unused", "Unused", mappedRange.min); _customRangeMin.VisibilityPredicate = () => false; _customRangeMax = Settings.GetHandle("customRangeMax", "Unused", "Unused", mappedRange.max); _customRangeMax.VisibilityPredicate = () => false; - _customRangeComfortableMin = Settings.GetHandle("customRangeComfortableMin", "Unused", "Unused", minComfortTemp); - _customRangeComfortableMin.VisibilityPredicate = () => false; + _customRangeComfortMin = Settings.GetHandle("customRangeComfortMin", "Unused", "Unused", minComfortTemp); + _customRangeComfortMin.VisibilityPredicate = () => false; - _customRangeComfortableMax = Settings.GetHandle("customRangeComfortableMax", "Unused", "Unused", maxComfortTemp); - _customRangeComfortableMax.VisibilityPredicate = () => false; + _customRangeComfortMax = Settings.GetHandle("customRangeComfortMax", "Unused", "Unused", maxComfortTemp); + _customRangeComfortMax.VisibilityPredicate = () => false; var customRangeValidator = Validators.IntRangeValidator( @@ -322,23 +325,23 @@ public override void DefsLoaded() customRangeMax.VisibilityPredicate = () => _useCustomRange; customRangeMax.Value = (int)GenTemperature.CelsiusTo(_customRangeMax, Prefs.TemperatureMode); - var customRangeComfortableMin = Settings.GetHandle( - "customRangeComfortableMinPlaceholder", - "FALCHM.CustomRangeComfortableMin".Translate(), - $"{"FALCHM.CustomRangeComfortableMinDesc".Translate(new NamedArgument(minComfortTemp, "default"))} ({Prefs.TemperatureMode.ToStringHuman()})", + var customRangeComfortMin = Settings.GetHandle( + "customRangeComfortMinPlaceholder", + "FALCHM.CustomRangeComfortMin".Translate(), + $"{"FALCHM.CustomRangeComfortMinDesc".Translate(new NamedArgument(minComfortTemp, "default"))} ({Prefs.TemperatureMode.ToStringHuman()})", validator: customRangeValidator); - customRangeComfortableMin.Unsaved = true; - customRangeComfortableMin.VisibilityPredicate = () => _useCustomRange; - customRangeComfortableMin.Value = (int)GenTemperature.CelsiusTo(_customRangeComfortableMin, Prefs.TemperatureMode); - - var customRangeComfortableMax = Settings.GetHandle( - "customRangeComfortableMaxPlaceholder", - "FALCHM.CustomRangeComfortableMax".Translate(), - $"{"FALCHM.CustomRangeComfortableMaxDesc".Translate(new NamedArgument(maxComfortTemp, "default"))} ({Prefs.TemperatureMode.ToStringHuman()})", + customRangeComfortMin.Unsaved = true; + customRangeComfortMin.VisibilityPredicate = () => _useCustomRange; + customRangeComfortMin.Value = (int)GenTemperature.CelsiusTo(_customRangeComfortMin, Prefs.TemperatureMode); + + var customRangeComfortMax = Settings.GetHandle( + "customRangeComfortMaxPlaceholder", + "FALCHM.CustomRangeComfortMax".Translate(), + $"{"FALCHM.CustomRangeComfortMaxDesc".Translate(new NamedArgument(maxComfortTemp, "default"))} ({Prefs.TemperatureMode.ToStringHuman()})", validator: customRangeValidator); - customRangeComfortableMax.Unsaved = true; - customRangeComfortableMax.VisibilityPredicate = () => _useCustomRange; - customRangeComfortableMax.Value = (int)GenTemperature.CelsiusTo(_customRangeComfortableMax, Prefs.TemperatureMode); + customRangeComfortMax.Unsaved = true; + customRangeComfortMax.VisibilityPredicate = () => _useCustomRange; + customRangeComfortMax.Value = (int)GenTemperature.CelsiusTo(_customRangeComfortMax, Prefs.TemperatureMode); customRangeMin.ValueChanged += val => @@ -357,14 +360,14 @@ public override void DefsLoaded() _customRangeMax.Value = ConvertToCelcius(customRangeMax); ResetAll(); }; - customRangeComfortableMin.ValueChanged += val => + customRangeComfortMin.ValueChanged += val => { - _customRangeComfortableMin.Value = ConvertToCelcius(customRangeComfortableMin); + _customRangeComfortMin.Value = ConvertToCelcius(customRangeComfortMin); ResetAll(); }; - customRangeComfortableMax.ValueChanged += val => + customRangeComfortMax.ValueChanged += val => { - _customRangeComfortableMax.Value = ConvertToCelcius(customRangeComfortableMax); + _customRangeComfortMax.Value = ConvertToCelcius(customRangeComfortMax); ResetAll(); }; } diff --git a/src/HeatMap/HeatMapHelper.cs b/src/HeatMap/HeatMapHelper.cs index 792fd7b..01b5563 100644 --- a/src/HeatMap/HeatMapHelper.cs +++ b/src/HeatMap/HeatMapHelper.cs @@ -24,8 +24,8 @@ public static void RegenerateColorMap() private static void CreateCustomMap() { - var minComfortTemp = HeatMap.Instance.CustomRangeComfortableMin; - var maxComfortTemp = HeatMap.Instance.CustomRangeComfortableMax; + var minComfortTemp = HeatMap.Instance.CustomRangeComfortMin; + var maxComfortTemp = HeatMap.Instance.CustomRangeComfortMax; MappedTemperatureRange = new IntRange(HeatMap.Instance.CustomRangeMin, HeatMap.Instance.CustomRangeMax); MappedColors = CreateColorGradient(MappedTemperatureRange, minComfortTemp, maxComfortTemp); @@ -49,12 +49,12 @@ private static Color[] CreateColorGradient(IntRange range, int minComfortTemp, i var gradientSteps = new List(); for (int i = 0, t = range.min; i < mappedColorCount; i++, t++) - gradientSteps.Add((t >= minComfortTemp - 1 && t <= maxComfortTemp) ? 4 : 1); + gradientSteps.Add((t >= minComfortTemp - 1 && t <= maxComfortTemp) ? 4f : 1f); return ColorHelper.Gradient(gradientColors, gradientSteps).ToArray(); } - public static (IntRange mappedRange, int minComfortable, int maxComfortable) GetComfortTemperatureRanges() + public static (IntRange mappedRange, int comfortTempMin, int comfortTempMax) GetComfortTemperatureRanges() { var human = ThingDefOf.Human; var minComfortTemp = (int)human.GetStatValueAbstract(StatDefOf.ComfyTemperatureMin) + 3; From 2ceef6aa0e26220c8499ba60a04c7f878146ccdc Mon Sep 17 00:00:00 2001 From: Chris F Date: Tue, 25 Oct 2022 20:30:07 +0200 Subject: [PATCH 5/6] Small text adjustment, should be done now --- .../Languages/English/Keyed/FALCHM_LanguageData.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mod-structure/Languages/English/Keyed/FALCHM_LanguageData.xml b/mod-structure/Languages/English/Keyed/FALCHM_LanguageData.xml index a9e8a46..50dd0f6 100644 --- a/mod-structure/Languages/English/Keyed/FALCHM_LanguageData.xml +++ b/mod-structure/Languages/English/Keyed/FALCHM_LanguageData.xml @@ -52,9 +52,9 @@ 240° = blue 300° = magenta -Each degree of temperature between min/max (default: {min}/{max}) has a relative base step width on the gradient of 1, except for the values between min/max comfort temperature (default: {comfortMin}/{comfortMax}), which have a step width of 4. -Each degree of these comfort temperatures takes up four times as much of the colour gradient as one outside of the comfort levels. -For example, using default settings, green will be used for fewer degrees of temperature than red or blue. +Each degree of temperature between min/max (default: {min} to {max} °C) has a relative gradient step width of 1, except for the values between min/max comfort temperature (default: {comfortMin} to {comfortMax} °C), which have a step width of 4. +I.e. each degree of the comfort temperature range takes up four times as much on the colour gradient as one outside of the comfort levels. +For example when using default settings green will be applied to a smaller temperature range than red or blue. Use custom range When off, the colour gradient automatically maps to the human comfort range as defined by the game. From fd9585d3dd8a825bd354c48ce0a9361ae6d722c3 Mon Sep 17 00:00:00 2001 From: "Anuradha (Anu) Dissanayake" Date: Mon, 29 Apr 2024 22:01:17 +1200 Subject: [PATCH 6/6] Update for 1.5 --- mod-structure/1.5/Assemblies/HeatMap.dll | Bin 0 -> 25088 bytes mod-structure/About/About.xml | 1 + src/HeatMap/Properties/AssemblyInfo.cs | 5 +++-- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 mod-structure/1.5/Assemblies/HeatMap.dll diff --git a/mod-structure/1.5/Assemblies/HeatMap.dll b/mod-structure/1.5/Assemblies/HeatMap.dll new file mode 100644 index 0000000000000000000000000000000000000000..8e33c6ddd49713b63ac1ce92e6a1ff01375d4d28 GIT binary patch literal 25088 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 literal 0 HcmV?d00001 diff --git a/mod-structure/About/About.xml b/mod-structure/About/About.xml index 6c2c4d2..b356590 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.5
  • 1.4
  • 1.3
  • 1.2
  • diff --git a/src/HeatMap/Properties/AssemblyInfo.cs b/src/HeatMap/Properties/AssemblyInfo.cs index e9ec3df..f04daa5 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.4.18.0")] -[assembly: AssemblyFileVersion("1.4.18.0")] +[assembly: AssemblyVersion("1.5.19.0")] +[assembly: AssemblyFileVersion("1.5.19.0")] +