From 45bf1b094070249255e09b34220bd2b38f9895f2 Mon Sep 17 00:00:00 2001 From: Dwayne Bent Date: Sat, 30 Apr 2016 14:24:11 -0400 Subject: [PATCH 01/10] Add v0.5.2-alpha to CHANGES --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 3b7091d..845b7d2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,5 @@ +## v0.5.2-alpha + ## v0.5.1 ##### Changes - Compatability with KSP 1.1 From 2417fb3db6ae1a43597225ebf696fcd3154dea1a Mon Sep 17 00:00:00 2001 From: Dwayne Bent Date: Sat, 30 Apr 2016 18:57:53 -0400 Subject: [PATCH 02/10] Fix Scheme Label (#62) * Increase width of GUI labels. Fixes #57 * Update CHANGES --- CHANGES.md | 2 ++ Source/HotSpot/HotSpotGuiBehavior.cs | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 845b7d2..893ef46 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,6 @@ ## v0.5.2-alpha +##### Fixes +- "Scheme:" label no longer occupies multiple lines in the GUI. ## v0.5.1 ##### Changes diff --git a/Source/HotSpot/HotSpotGuiBehavior.cs b/Source/HotSpot/HotSpotGuiBehavior.cs index 941726a..f97850b 100644 --- a/Source/HotSpot/HotSpotGuiBehavior.cs +++ b/Source/HotSpot/HotSpotGuiBehavior.cs @@ -303,12 +303,13 @@ private void OnContextTab() private void OnOverlayTab() { + var labelWidth = GUILayout.Width(60); + GUILayout.BeginVertical(); GUILayout.BeginHorizontal(); - GUILayout.Label("Metric:", - new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Bold }, GUILayout.Width(55)); + GUILayout.Label("Metric:", new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Bold }, labelWidth); GUILayout.Label(Config.Instance.Overlay.Metric.LongFriendlyName); GUILayout.FlexibleSpace(); if (GUILayout.Button("Select", GUILayout.Width(50))) @@ -341,8 +342,7 @@ private void OnOverlayTab() GUILayout.BeginHorizontal(); - GUILayout.Label("Scheme:", - new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Bold }, GUILayout.Width(55)); + GUILayout.Label("Scheme:", new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Bold }, labelWidth); GUILayout.Label(Config.Instance.Overlay.GetActiveMetric().GetActiveScheme().FriendlyName); GUILayout.FlexibleSpace(); if (GUILayout.Button("Select", GUILayout.Width(50))) From 07abc2d342e36ccf12d71bcc0fe498b80a996c19 Mon Sep 17 00:00:00 2001 From: Dwayne Bent Date: Sat, 14 May 2016 18:25:15 -0400 Subject: [PATCH 03/10] Build Overhaul (#64) --- .gitignore | 6 +- CHANGES.md => CHANGELOG.md | 30 +- CONTRIBUTING.md | 21 + HotSpot.sln | 12 +- LICENSE.md | 2 +- Source/HotSpot/Properties/AssemblyInfo.cs.in | 21 - .../Properties/AssemblyInfo.cs.in | 17 - Tests/HotSpotTests/packages.config | 9 - appveyor.yml | 40 +- build | 60 + build.cake | 593 +++--- build.example.yml | 2 +- build.ps1 | 92 +- utilities.cake => cake/utilities.cake | 45 +- nuget.config | 9 +- packages.config | 3 +- .../GameData/Configuration}/HotSpot.cfg | 0 .../GameData/Patches}/HotSpotModule.cfg | 0 {Patches => src/GameData/Patches}/KIS.cfg | 0 src/GlobalAssemblyInfo.cs | 11 + .../HotSpot/Compat/Toolbar/ToolbarWrapper.cs | 1788 ++++++++--------- {Source => src}/HotSpot/Config.cs | 0 .../HotSpot/Configuration/AutoBoolean.cs | 0 .../Configuration/ContextMenu/MetricNode.cs | 0 .../HotSpot/Configuration/ContextMenuNode.cs | 0 .../HotSpot/Configuration/DiagnosticsNode.cs | 0 .../Configuration/Gui/AppLauncherNode.cs | 0 .../HotSpot/Configuration/Gui/ToolbarNode.cs | 0 .../HotSpot/Configuration/GuiNode.cs | 0 .../Configuration/Overlay/GradientNode.cs | 0 .../Configuration/Overlay/MetricNode.cs | 0 .../Configuration/Overlay/OnConflict.cs | 0 .../Configuration/Overlay/SchemeNode.cs | 0 .../HotSpot/Configuration/Overlay/StopNode.cs | 0 .../HotSpot/Configuration/OverlayNode.cs | 0 .../Extensions/ConfigNodeExtensions.cs | 0 .../HotSpot/Extensions/PartExtensions.cs | 0 .../HotSpot/Extensions/StringExtensions.cs | 0 {Source => src}/HotSpot/HotSpot.csproj | 20 +- .../HotSpot/HotSpot.csproj.DotSettings | 0 {Source => src}/HotSpot/HotSpotBehavior.cs | 0 {Source => src}/HotSpot/HotSpotGuiBehavior.cs | 0 {Source => src}/HotSpot/HotSpotModule.cs | 0 {Source => src}/HotSpot/Log.cs | 0 .../HotSpot/Model/EvaluatedGradientNode.cs | 0 .../HotSpot/Model/EvaluatedStopNode.cs | 0 {Source => src}/HotSpot/Model/Expression.cs | 0 {Source => src}/HotSpot/Model/Metric.cs | 0 {Source => src}/HotSpot/Model/Unit.cs | 0 {Source => src}/HotSpot/Model/Variable.cs | 0 src/HotSpot/Properties/AssemblyInfo.cs | 6 + .../Reflection/FlightOverlaysExtensions.cs | 0 .../HotSpot/Reflection/PartExtensions.cs | 0 .../HotSpotTests/HotSpotTests.csproj | 45 +- src/HotSpotTests/Properties/AssemblyInfo.cs | 4 + 55 files changed, 1485 insertions(+), 1351 deletions(-) rename CHANGES.md => CHANGELOG.md (91%) create mode 100644 CONTRIBUTING.md delete mode 100644 Source/HotSpot/Properties/AssemblyInfo.cs.in delete mode 100644 Tests/HotSpotTests/Properties/AssemblyInfo.cs.in delete mode 100644 Tests/HotSpotTests/packages.config create mode 100755 build rename utilities.cake => cake/utilities.cake (80%) rename {Configuration => src/GameData/Configuration}/HotSpot.cfg (100%) rename {Patches => src/GameData/Patches}/HotSpotModule.cfg (100%) rename {Patches => src/GameData/Patches}/KIS.cfg (100%) create mode 100644 src/GlobalAssemblyInfo.cs rename {Source => src}/HotSpot/Compat/Toolbar/ToolbarWrapper.cs (97%) rename {Source => src}/HotSpot/Config.cs (100%) rename {Source => src}/HotSpot/Configuration/AutoBoolean.cs (100%) rename {Source => src}/HotSpot/Configuration/ContextMenu/MetricNode.cs (100%) rename {Source => src}/HotSpot/Configuration/ContextMenuNode.cs (100%) rename {Source => src}/HotSpot/Configuration/DiagnosticsNode.cs (100%) rename {Source => src}/HotSpot/Configuration/Gui/AppLauncherNode.cs (100%) rename {Source => src}/HotSpot/Configuration/Gui/ToolbarNode.cs (100%) rename {Source => src}/HotSpot/Configuration/GuiNode.cs (100%) rename {Source => src}/HotSpot/Configuration/Overlay/GradientNode.cs (100%) rename {Source => src}/HotSpot/Configuration/Overlay/MetricNode.cs (100%) rename {Source => src}/HotSpot/Configuration/Overlay/OnConflict.cs (100%) rename {Source => src}/HotSpot/Configuration/Overlay/SchemeNode.cs (100%) rename {Source => src}/HotSpot/Configuration/Overlay/StopNode.cs (100%) rename {Source => src}/HotSpot/Configuration/OverlayNode.cs (100%) rename {Source => src}/HotSpot/Extensions/ConfigNodeExtensions.cs (100%) rename {Source => src}/HotSpot/Extensions/PartExtensions.cs (100%) rename {Source => src}/HotSpot/Extensions/StringExtensions.cs (100%) rename {Source => src}/HotSpot/HotSpot.csproj (81%) rename {Source => src}/HotSpot/HotSpot.csproj.DotSettings (100%) rename {Source => src}/HotSpot/HotSpotBehavior.cs (100%) rename {Source => src}/HotSpot/HotSpotGuiBehavior.cs (100%) rename {Source => src}/HotSpot/HotSpotModule.cs (100%) rename {Source => src}/HotSpot/Log.cs (100%) rename {Source => src}/HotSpot/Model/EvaluatedGradientNode.cs (100%) rename {Source => src}/HotSpot/Model/EvaluatedStopNode.cs (100%) rename {Source => src}/HotSpot/Model/Expression.cs (100%) rename {Source => src}/HotSpot/Model/Metric.cs (100%) rename {Source => src}/HotSpot/Model/Unit.cs (100%) rename {Source => src}/HotSpot/Model/Variable.cs (100%) create mode 100644 src/HotSpot/Properties/AssemblyInfo.cs rename {Source => src}/HotSpot/Reflection/FlightOverlaysExtensions.cs (100%) rename {Source => src}/HotSpot/Reflection/PartExtensions.cs (100%) rename {Tests => src}/HotSpotTests/HotSpotTests.csproj (60%) create mode 100644 src/HotSpotTests/Properties/AssemblyInfo.cs diff --git a/.gitignore b/.gitignore index 1ec6b5d..71febc4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,4 @@ -/Library/KSP/ -/Library/NuGet/ -/Output/ - -AssemblyInfo.cs +/.build/ *.suo *.user diff --git a/CHANGES.md b/CHANGELOG.md similarity index 91% rename from CHANGES.md rename to CHANGELOG.md index 893ef46..b5292b2 100644 --- a/CHANGES.md +++ b/CHANGELOG.md @@ -3,47 +3,47 @@ - "Scheme:" label no longer occupies multiple lines in the GUI. ## v0.5.1 -##### Changes -- Compatability with KSP 1.1 +##### Changed +- Compatability with KSP 1.1. ## v0.5.0 -##### New +##### Added - Support for [Toolbar](http://forum.kerbalspaceprogram.com/index.php?/topic/55420-/) contributed by [nanathan](https://github.com/nanathan). ## v0.4.4 -##### Fixes +##### Fixed - Support for [KIS](http://forum.kerbalspaceprogram.com/threads/113111) stackables. ## v0.4.3 -##### Fixes +##### Fixed - Add aggregate thermal rate metric to overlay configuration. ## v0.4.2 -##### Fixes +##### Fixed - Fix aggregate thermal rate calculation. It was double counting radiative thermal rate. ## v0.4.1 -##### Fixes +##### Fixed - Fix for configuration settings reverting to default after two reloads of KSP. ## v0.4.0 -##### New +##### Added - Configuration settings are now persisted on scene change. ## v0.3.0 -##### New +##### Added - Skin temperature added as a new metric. -##### Changes +##### Changed - *Incompatible:* Configuration structure for temperature metrics has changed. - "Temperature" in context menus abbreviated to "Temp." -##### Fixes +##### Fixed - Fix Internal Thermal Rate metric always being 0kW. ## v0.2.0 -##### New +##### Added - Added thermal rate metrics to both overlays and context menu. These measure the change in thermal energy of a part over time in units of energy/time, i.e. power. There are four discrete thermal rates: internal, conductive, convective, and radiative. There is also a general thermal rate metric which is the combination of the previous @@ -55,14 +55,14 @@ the temperature unit, the overlay metric, and the gradient scheme for the metric. Currently this options are not persisted between loads. -##### Changes +##### Changed - *Incompatible:* Configuration structure has changed significantly. ## v0.1.1 -##### Fixes +##### Fixed - Fix settings being loaded multiple times which would eventually cause the thermal overlays to fail. ## v0.1.0 -##### New +##### Added - Replace thermal overlay gradient colors with more intuitive scheme. - Add display of temperature and max temperature values to part context menu. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3919021 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,21 @@ +# Contributing + +## Copyright + +All code contributions must be made under either the [MIT](LICENSE.md) license or the public domain. All non-code +contributions must be made under the [CC-BY 4.0](http://creativecommons.org/licenses/by/4.0/) license or the public +domain. No copyright assignment is necessary. If your contribution is not public domain add your name and contribution +years to the [LICENSE](LICENSE.md) file. + +## Building + +In order to build the code you should have Microsoft Visual Studio installed on Windows systems or Mono installed on +Linux or Mac systems. + +- Copy `build.example.yml` to `build.yml` or `../HotSpot.build.yml` relative to the project root. + - Edit the variables `ksp_dir` and `ksp_bin` as appropriate. +- From a command line shell, execute `./build` in the project root. + - On Windows, you *must* use PowerShell as your command line shell. + - On Linux and Mac systems `/bin/sh` must be a POSIX-compliant shell. +- If the build was successful you should have a ZIP file in `.build/pkg/Debug`, packaged ready for distribution. +- If you would like to deploy to and execute KSP automatically after a build use `./build run`. diff --git a/HotSpot.sln b/HotSpot.sln index e5af650..97956db 100644 --- a/HotSpot.sln +++ b/HotSpot.sln @@ -1,15 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22823.1 +VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{3C3EA3B3-B34C-4C92-AC7B-031810073BAB}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3C3EA3B3-B34C-4C92-AC7B-031810073BAB}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{FE967A1B-0E46-46D0-B7A2-097B02012880}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotSpot", "src\HotSpot\HotSpot.csproj", "{26BDAE1E-3F27-4A7B-81A1-C461F96A3064}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotSpot", "Source\HotSpot\HotSpot.csproj", "{26BDAE1E-3F27-4A7B-81A1-C461F96A3064}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotSpotTests", "Tests\HotSpotTests\HotSpotTests.csproj", "{277D410A-9E8C-41DA-8235-FB7A9940681B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotSpotTests", "src\HotSpotTests\HotSpotTests.csproj", "{277D410A-9E8C-41DA-8235-FB7A9940681B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -31,6 +29,6 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {26BDAE1E-3F27-4A7B-81A1-C461F96A3064} = {3C3EA3B3-B34C-4C92-AC7B-031810073BAB} - {277D410A-9E8C-41DA-8235-FB7A9940681B} = {FE967A1B-0E46-46D0-B7A2-097B02012880} + {277D410A-9E8C-41DA-8235-FB7A9940681B} = {3C3EA3B3-B34C-4C92-AC7B-031810073BAB} EndGlobalSection EndGlobal diff --git a/LICENSE.md b/LICENSE.md index c5a8fd8..126dc34 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ This software is distributed under the MIT license. - Copyright (c) 2015 Dwayne Bent + Copyright (c) 2015-2016 Dwayne Bent Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Source/HotSpot/Properties/AssemblyInfo.cs.in b/Source/HotSpot/Properties/AssemblyInfo.cs.in deleted file mode 100644 index 02b128b..0000000 --- a/Source/HotSpot/Properties/AssemblyInfo.cs.in +++ /dev/null @@ -1,21 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -[assembly: AssemblyTitle("HotSpot")] -[assembly: AssemblyDescription("Kerbal Space Program mod to add contextual and overlay data.")] -[assembly: AssemblyProduct("HotSpot")] -[assembly: AssemblyCompany("HotSpot Contributors")] -[assembly: AssemblyCopyright("Copyright © 2015")] - -#if DEBUG -[assembly: AssemblyConfiguration("Debug")] -#else -[assembly: AssemblyConfiguration("Release")] -#endif - -[assembly: KSPAssembly("HotSpot", <%VERSION.MAJOR%>, <%VERSION.MINOR%>)] -[assembly: AssemblyVersion("<%VERSION.MAJOR%>.<%VERSION.MINOR%>")] -[assembly: AssemblyFileVersion("<%VERSION.MAJOR%>.<%VERSION.MINOR%>.<%VERSION.PATCH%>")] -[assembly: AssemblyInformationalVersion("<%VERSION%>")] - -[assembly: InternalsVisibleTo("HotSpotTests")] diff --git a/Tests/HotSpotTests/Properties/AssemblyInfo.cs.in b/Tests/HotSpotTests/Properties/AssemblyInfo.cs.in deleted file mode 100644 index 60466a4..0000000 --- a/Tests/HotSpotTests/Properties/AssemblyInfo.cs.in +++ /dev/null @@ -1,17 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyTitle("HotSpotTests")] -[assembly: AssemblyDescription("Tests for HotSpot")] -[assembly: AssemblyProduct("HotSpot")] -[assembly: AssemblyCompany("HotSpot Contributors")] -[assembly: AssemblyCopyright("Copyright © 2015")] - -#if DEBUG -[assembly: AssemblyConfiguration("Debug")] -#else -[assembly: AssemblyConfiguration("Release")] -#endif - -[assembly: AssemblyVersion("<%VERSION.MAJOR%>.<%VERSION.MINOR%>")] -[assembly: AssemblyFileVersion("<%VERSION.MAJOR%>.<%VERSION.MINOR%>.<%VERSION.PATCH%>")] -[assembly: AssemblyInformationalVersion("<%VERSION%>")] diff --git a/Tests/HotSpotTests/packages.config b/Tests/HotSpotTests/packages.config deleted file mode 100644 index 4f52b0d..0000000 --- a/Tests/HotSpotTests/packages.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 801dade..c34a05d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,26 +4,12 @@ configuration: - Debug - Release -install: - ps: | - $kspLibs = "Assembly-CSharp.dll", "Assembly-CSharp-firstpass.dll", "KSPUtil.dll", "UnityEngine.dll", "UnityEngine.UI.dll" - $kspLibsUrl = "http://build.apokee.com/dependencies/ksp/1.1.2.1260" - $kspLibsDir = "$env:APPVEYOR_BUILD_FOLDER/Library/KSP" - - New-Item $kspLibsDir -Type Directory -Force | Out-Null - - foreach ($kspLib in $kspLibs) - { - Start-FileDownload "$kspLibsUrl/$kspLib" -FileName "$kspLibsDir/$kspLib" - } - before_build: ps: | $isMasterBranch = $env:APPVEYOR_REPO_BRANCH -eq "master" $isReleaseConfiguration = $env:CONFIGURATION -eq "Release" $isTagged = $env:APPVEYOR_REPO_TAG -eq "true" $env:BUILD_RELEASE = $isMasterBranch -and $isReleaseConfiguration -and $isTagged - if ($env:BUILD_RELEASE -eq "True") { Add-AppveyorMessage "Building RELEASE version" } else { @@ -33,30 +19,32 @@ before_build: build_script: ps: | if ($env:BUILD_RELEASE -eq "True") { - ./build -configuration="$env:CONFIGURATION" -release=true + ./build --configuration="$env:CONFIGURATION" --release=true } else { - ./build -configuration="$env:CONFIGURATION" + ./build --configuration="$env:CONFIGURATION" } after_build: ps: | - $env:BUILD_VERSION = (cat Output/VERSION) - $env:BUILD_PRELEASE = (cat Output/PRELEASE) - $env:BUILD_CHANGELOG = [System.String]::Join("\n", (cat Output/CHANGELOG)) - + $env:BUILD_VERSION = (cat .build/meta/VERSION) + $env:BUILD_PRERELEASE = (cat .build/meta/PRERELEASE) + $changeLog = (cat .build/meta/CHANGELOG) + if ($chaneLog -eq $null) { + $env:BUILD_CHANGELOG = "" + } else { + $env:BUILD_CHANGELOG = [System.String]::Join("\n", $changeLog) + } $versionMessage = "Version: $env:BUILD_VERSION" - - if ($env:BUILD_PRELEASE -eq "true") { - $versionMessage += " (PRELEASE)" + if ($env:BUILD_PRERELEASE -eq "true") { + $versionMessage += " (PRERELEASE)" } - Add-AppveyorMessage "$versionMessage" Add-AppveyorMessage "Changes:`n$env:BUILD_CHANGELOG" test: off artifacts: - - path: 'Output\Package\$(configuration)\*.zip' + - path: '.build\pkg\$(configuration)\*.zip' name: ZipPackage deploy: @@ -67,6 +55,6 @@ deploy: secure: EmsGaC6JRUeanKOhuSCl3ka+kK0czrAG7LHBaYONuUWfV/rIARRFkwoSKZiZHWTe artifact: ZipPackage draft: false - prerelease: $(BUILD_PRELEASE) + prerelease: $(BUILD_PRERELEASE) on: build_release: True diff --git a/build b/build new file mode 100755 index 0000000..2eb7ab8 --- /dev/null +++ b/build @@ -0,0 +1,60 @@ +#!/bin/sh +set -e + +arg0="" +remainingArgs="" + +if [ $# -gt 0 ]; then + arg0="$1" +fi + +if [ $# -gt 1 ]; then + for i in $(seq 2 $#); do + remainingArgs+="${!i} " + done +fi + +nugetVersion="3.3.0" +useExperimental=false +rootDir=$(dirname $0) +buildDir="$rootDir/.build" +toolsDir="$buildDir/tools" +packagesDir="$buildDir/lib/nuget" +nugetExe="$toolsDir/nuget/$nugetVersion/NuGet.exe" +packagesConfigFile="$rootDir/packages.config" +cakeVersion="$(sed -nE "s|\s*\s*|\1|p" $packagesConfigFile)" +cakeExe="$packagesDir/Cake.$cakeVersion/Cake.exe" + +nugetDir="$(dirname $nugetExe)" +if [ ! -d "$nugetDir" ]; then + mkdir -p "$nugetDir" +fi + +if [ ! -f "$nugetExe" ]; then + curl -L "https://dist.nuget.org/win-x86-commandline/v${nugetVersion}/nuget.exe" --output "$nugetExe" +fi + +mono "$nugetExe" install "$packagesConfigFile" -OutputDirectory "$packagesDir" + +cakeArgs="" + +if [ -n "$arg0" ]; then + case $arg0 in + -*) + cakeArgs+="$arg0 " + ;; + *) + cakeArgs+="--target=$arg0 " + ;; + esac +fi + +if $useExperimental; then + cakeArgs+="--experimental " +fi + +echo "CAKE: mono $cakeExe $cakeArgs $remainingArgs" + +mono "$cakeExe" $cakeArgs $remainingArgs +exit $? + diff --git a/build.cake b/build.cake index f65dfc1..9e42e1a 100644 --- a/build.cake +++ b/build.cake @@ -1,239 +1,354 @@ -#l "utilities.cake" - -using System.Text.RegularExpressions; -using YamlDotNet.Serialization; - -public sealed class BuildConfiguration -{ - [YamlAlias("ksp_dir")] - public string KspDir { get; set; } - - [YamlAlias("ksp_bin")] - public string KspBin { get; set; } - - public string KspPath(params string[] paths) - { - return KspDir == null ? null : System.IO.Path.Combine(KspDir, System.IO.Path.Combine(paths)); - } -} - -var target = Argument("target", "Package"); -var configuration = Argument("configuration", "Debug"); -var release = Argument("release", false); - -var buildConfiguration = GetBuildConfiguration(); - -var solution = GetSolution(); - -var identifier = "HotSpot"; -var outputDirectory = "Output"; -var artworkDirectory = Directory(GetNuGetPackageDirectory("Apokee.Artwork")) + Directory("Content"); -var buildDirectory = Directory($"{outputDirectory}/Build/{configuration}"); -var binDirectory = Directory($"{buildDirectory}/Common/bin"); -var stageDirectory = Directory($"{outputDirectory}/Stage/{configuration}"); -var stageGameDataDirectory = Directory($"{stageDirectory}/GameData"); -var stageModDirectory = Directory($"{stageGameDataDirectory}/{identifier}"); -var deployModDirectory = buildConfiguration.KspPath("GameData", identifier); -var packageDirectory = Directory($"{outputDirectory}/Package/{configuration}"); - -Task("Init") - .Does(() => -{ - var kspLibDirectory = System.IO.Path.Combine("Library", "KSP"); - var kspLibs = new [] - { - "Assembly-CSharp.dll", - "Assembly-CSharp-firstpass.dll", - "KSPUtil.dll", - "UnityEngine.dll", - "UnityEngine.UI.dll" - }; - - CreateDirectory(kspLibDirectory); - - foreach (var kspLib in kspLibs) - { - if (!FileExists(System.IO.Path.Combine(kspLibDirectory, kspLib))) - { - CopyFileToDirectory( - buildConfiguration.KspPath("KSP_Data", "Managed", kspLib), - kspLibDirectory - ); - } - } -}); - -Task("CleanBuild") - .Does(() => -{ - CleanDirectories(new DirectoryPath[] { buildDirectory }); -}); - -Task("CleanStage") - .Does(() => -{ - CleanDirectories(new DirectoryPath[] { stageDirectory }); -}); - -Task("CleanPackage") - .Does(() => -{ - CleanDirectories(new DirectoryPath[] { packageDirectory }); -}); - -Task("CleanDeploy") - .Does(() => -{ - CleanDirectories(new DirectoryPath[] { deployModDirectory }); -}); - -Task("Restore") - .Does(() => -{ - NuGetRestore(solution); -}); - -Task("BuildVersionInfo") - .Does(() => -{ - SemVer buildVersion; - - var changeLog = GetChangeLog(); - var version = changeLog.LatestVersion; - var rev = GetGitRevision(useShort: true); - - if (rev != null && !release) - { - if (version.Build == null) - { - buildVersion = new SemVer(version.Major, version.Minor, version.Patch, version.Pre, rev); - } - else - { - throw new Exception("VERSION already contains build metadata"); - } - } - else - { - buildVersion = version; - } - - System.IO.File.WriteAllText("Output/VERSION", buildVersion); - System.IO.File.WriteAllText("Output/PRELEASE", (buildVersion.Pre != null).ToString().ToLower()); - System.IO.File.WriteAllText("Output/CHANGELOG", changeLog.LatestChanges); -}); - -Task("BuildAssemblyInfo") - .Does(() => -{ - BuildAssemblyInfo($"Source/{identifier}/Properties/AssemblyInfo.cs"); - BuildAssemblyInfo($"Tests/{identifier}Tests/Properties/AssemblyInfo.cs"); -}); - -Task("Build") - .IsDependentOn("CleanBuild") - .IsDependentOn("Init") - .IsDependentOn("Restore") - .IsDependentOn("BuildVersionInfo") - .IsDependentOn("BuildAssemblyInfo") - .Does(() => -{ - MSBuild(solution, s => { s.Configuration = configuration; }); -}); - -Task("Stage") - .IsDependentOn("CleanStage") - .IsDependentOn("Build") - .Does(() => -{ - var pluginsDirectory = $"{stageModDirectory}/Plugins"; - var texturesDirectory = $"{stageModDirectory}/Textures"; - - CreateDirectory(stageGameDataDirectory); - CreateDirectory(stageModDirectory); - CreateDirectory(pluginsDirectory); - CreateDirectory(texturesDirectory); - - CopyFiles($"{binDirectory}/*", pluginsDirectory); - CopyDirectory("Configuration", $"{stageModDirectory}/Configuration"); - CopyDirectory("Patches", $"{stageModDirectory}/Patches"); - CopyFile($"{artworkDirectory}/hotspot-white-38x38.png", $"{texturesDirectory}/AppLauncher.png"); - CopyFile($"{artworkDirectory}/hotspot-white-24x24.png", $"{texturesDirectory}/Toolbar.png"); - CopyFileToDirectory("CHANGES.md", stageModDirectory); - CopyFileToDirectory("LICENSE.md", stageModDirectory); - CopyFileToDirectory("README.md", stageModDirectory); -}); - -Task("Deploy") - .IsDependentOn("Stage") - .IsDependentOn("CleanDeploy") - .Does(() => -{ - CopyDirectory(stageModDirectory, $"{buildConfiguration.KspPath("GameData")}/{identifier}"); -}); - -Task("Run") - .IsDependentOn("Deploy") - .Does(() => -{ - StartProcess(System.IO.Path.Combine(buildConfiguration.KspDir, buildConfiguration.KspBin), new ProcessSettings - { - WorkingDirectory = buildConfiguration.KspDir - }); -}); - -Task("Package") - .IsDependentOn("CleanPackage") - .IsDependentOn("Stage") - .Does(() => -{ - CreateDirectory(packageDirectory); - - Zip(stageDirectory, File($"{packageDirectory}/{identifier}-{GetBuildVersion()}.zip")); -}); - -Task("Version") - .Does(() => -{ - Information(GetVersion()); -}); - -Task("ChangeLog") - .Does(() => -{ - Information(GetChangeLog().LatestChanges); -}); - -RunTarget(target); - -private void BuildAssemblyInfo(string file) -{ - var version = GetBuildVersion(); - - var output = TransformTextFile($"{file}.in") - .WithToken("VERSION", version) - .WithToken("VERSION.MAJOR", version.Major) - .WithToken("VERSION.MINOR", version.Minor) - .WithToken("VERSION.PATCH", version.Patch) - .WithToken("VERSION.PRE", version.Pre) - .WithToken("VERSION.BUILD", version.Build) - .ToString(); - - System.IO.File.WriteAllText(file, output); -} - -private SemVer GetBuildVersion() -{ - return new SemVer(System.IO.File.ReadAllText("Output/VERSION")); -} - -private string GetNuGetPackageDirectory(string package) -{ - return System.IO.Directory - .GetDirectories("Library/NuGet") - .Select(i => new DirectoryInfo(i)) - .Where(i => i.Name.StartsWith(package)) - .OrderByDescending(i => new Version(i.Name.Substring(package.Length + 1, i.Name.Length - package.Length - 1))) - .First() - .FullName; -} +#addin "Cake.FileHelpers" +#l "cake/utilities.cake" + +using System.Text.RegularExpressions; +using YamlDotNet.Serialization; + +public sealed class BuildConfiguration +{ + [YamlMember(Alias = "ksp_dir")] + public string KspDir { get; set; } + + [YamlMember(Alias = "ksp_bin")] + public string KspBin { get; set; } + + public string KspPath(params string[] paths) + { + return KspDir == null ? null : System.IO.Path.Combine(KspDir, System.IO.Path.Combine(paths)); + } +} + +public sealed class Globals +{ + public string ModIdentifier { get; set; } + public string ModName { get; set; } + public string ModCopyrightDates { get; set; } + public string[] KspLibs { get; set; } + public string Target { get; set; } + public string Configuration { get; set; } + public BuildConfiguration BuildConfiguration { get; set; } + public SemVer BuildVersion { get; set; } + public FilePath Solution { get; set; } + public DirectoryPath RootDirectory { get; set; } + public DirectoryPath BuildDirectory { get; set; } + public DirectoryPath BuildLibDirectory { get; set; } + public DirectoryPath BuildLibKspDirectory { get; set; } + public DirectoryPath BuildLibNugetDirectory { get; set; } + public DirectoryPath BuildMetaDirectory { get; set; } + public DirectoryPath BuildPkgDirectory { get; set; } + public DirectoryPath BuildStageDirectory { get; set; } + public DirectoryPath BuildStageGameDataDirectory { get; set; } + public DirectoryPath BuildStageGameDataModDirectory { get; set; } + public DirectoryPath BuildOutDirectory { get; set; } + public DirectoryPath SrcDirectory { get; set; } + public DirectoryPath DeployDirectory { get; set; } +} + +// HACK: Terrible workaround for Mono's script compiler not supporting globals like Roslyn +private Globals getGlobals() +{ + var globals = new Globals(); + + globals.ModIdentifier = "HotSpot"; + globals.ModName = "Hot Spot"; + globals.ModCopyrightDates = "2015-2016"; + globals.KspLibs = new [] + { + "Assembly-CSharp.dll", + "Assembly-CSharp-firstpass.dll", + "KSPUtil.dll", + "UnityEngine.dll", + "UnityEngine.UI.dll" + }; + globals.Target = Argument("target", "Package"); + globals.Configuration = Argument("configuration", "Debug"); + globals.BuildConfiguration = GetBuildConfiguration(); + globals.BuildVersion = GetBuildVersion(); + globals.Solution = GetSolution(); + globals.RootDirectory = Context.Environment.WorkingDirectory; + globals.BuildDirectory = globals.RootDirectory.Combine(".build"); + globals.BuildLibDirectory = globals.BuildDirectory.Combine("lib"); + globals.BuildLibKspDirectory = globals.BuildLibDirectory.Combine("ksp"); + globals.BuildLibNugetDirectory = globals.BuildLibDirectory.Combine("nuget"); + globals.BuildMetaDirectory = globals.BuildDirectory.Combine("meta"); + globals.BuildPkgDirectory = globals.BuildDirectory.Combine("pkg").Combine(globals.Configuration); + globals.BuildStageDirectory = globals.BuildDirectory.Combine("stage"); + globals.BuildStageGameDataDirectory = globals.BuildStageDirectory.Combine("GameData"); + globals.BuildStageGameDataModDirectory = globals.BuildStageGameDataDirectory.Combine(globals.ModIdentifier); + globals.BuildOutDirectory = globals.BuildDirectory.Combine("out"); + globals.SrcDirectory = globals.RootDirectory.Combine("src"); + + var deployDirectory = globals.BuildConfiguration.KspPath("GameData", globals.ModIdentifier); + if (deployDirectory != null) { globals.DeployDirectory = deployDirectory; } + + return globals; +} + +Task("Init") + .IsDependentOn("InitLibKsp"); + +Task("InitLibKsp") + .Does(() => +{ + const string kspLibsUrlBase = "http://build.apokee.com/dependencies/ksp/1.1.2.1260"; + + var globals = getGlobals(); + + CreateDirectory(globals.BuildLibKspDirectory); + + var missingKspLibs = globals.KspLibs.Where(i => !FileExists(globals.BuildLibKspDirectory.CombineWithFilePath(i))); + foreach (var kspLib in missingKspLibs) + { + var destinationKspLibFilePath = globals.BuildLibKspDirectory.CombineWithFilePath(kspLib); + var localKspLib = globals.BuildConfiguration.KspPath("KSP_x64_Data", "Managed", kspLib); + + if (localKspLib != null && FileExists(localKspLib)) + CopyFile(localKspLib, destinationKspLibFilePath); + else + DownloadFile(string.Format("{0}/{1}", kspLibsUrlBase, kspLib), destinationKspLibFilePath); + } +}); + +Task("CleanStage") + .Does(() => +{ + var globals = getGlobals(); + + CleanDirectories(new [] { globals.BuildStageDirectory }); +}); + +Task("CleanPackage") + .Does(() => +{ + var globals = getGlobals(); + + CleanDirectories(new [] { globals.BuildPkgDirectory }); +}); + +Task("CleanDeploy") + .Does(() => +{ + var globals = getGlobals(); + + CleanDirectories(new [] {globals.DeployDirectory }); +}); + +Task("Restore") + .Does(() => +{ + var globals = getGlobals(); + + NuGetRestore(globals.Solution); +}); + +Task("BuildMeta") + .IsDependentOn("BuildAssemblyInfo") + .Does(() => +{ + var globals = getGlobals(); + + CreateDirectory(globals.BuildMetaDirectory); + + FileWriteText( + globals.BuildMetaDirectory.CombineWithFilePath("VERSION"), + globals.BuildVersion + ); + FileWriteText( + globals.BuildMetaDirectory.CombineWithFilePath("PRERELEASE"), + (globals.BuildVersion.Pre != null).ToString().ToLowerInvariant() + ); + FileWriteText( + globals.BuildMetaDirectory.CombineWithFilePath("CHANGELOG"), + GetChangeLog().LatestChanges + ); +}); + +Task("BuildAssemblyInfo") + .IsDependentOn("BuildGlobalAssemblyVersionInfo") + .IsDependentOn("BuildGlobalKspAssemblyVersionInfo"); + +Task("BuildGlobalAssemblyVersionInfo") + .Does(() => +{ + var globals = getGlobals(); + + CreateDirectory(globals.BuildMetaDirectory); + + CreateAssemblyInfo(globals.BuildMetaDirectory.CombineWithFilePath("GlobalAssemblyVersionInfo.cs"), new AssemblyInfoSettings + { + Version = string.Format("{0}.{1}", globals.BuildVersion.Major, globals.BuildVersion.Minor), + FileVersion = string.Format("{0}.{1}.{2}", globals.BuildVersion.Major, globals.BuildVersion.Minor, globals.BuildVersion.Patch), + InformationalVersion = globals.BuildVersion.ToString() + }); +}); + +Task("BuildGlobalKspAssemblyVersionInfo") + .Does(() => +{ + var globals = getGlobals(); + + CreateDirectory(globals.BuildMetaDirectory); + + var sb = new StringBuilder(); + sb.AppendLine("//------------------------------------------------------------------------------"); + sb.AppendLine("// "); + sb.AppendLine("// This code was generated by Cake."); + sb.AppendLine("// "); + sb.AppendLine("//------------------------------------------------------------------------------"); + sb.AppendLine(); + sb.AppendLine( + string.Format(@"[assembly: KSPAssembly(""{0}"", {1}, {2})]", + globals.ModIdentifier, + globals.BuildVersion.Major, + globals.BuildVersion.Minor + ) + ); + + FileWriteText(globals.BuildMetaDirectory.CombineWithFilePath("GlobalAssemblyKspVersionInfo.cs"), sb.ToString()); +}); + +Task("Build") + .IsDependentOn("Init") + .IsDependentOn("Restore") + .IsDependentOn("BuildMeta") + .Does(() => +{ + var globals = getGlobals(); + + DotNetBuild(globals.Solution, s => { s.Configuration = globals.Configuration; }); +}); + +Task("Stage") + .IsDependentOn("CleanStage") + .IsDependentOn("Restore") + .IsDependentOn("Build") + .Does(() => +{ + var globals = getGlobals(); + + var artworkDirectory = GetNuGetPackageDirectory("Apokee.Artwork").Combine("Content"); + var binDirectory = globals + .BuildOutDirectory + .Combine(globals.ModIdentifier) + .Combine(globals.Configuration) + .Combine("bin"); + var srcGameDataDirectory = globals.SrcDirectory.Combine("GameData"); + + var buildStageGameDataModPluginsDirectory = globals.BuildStageGameDataModDirectory.Combine("Plugins"); + var buildStageGameDataModTexturesDirectory = globals.BuildStageGameDataModDirectory.Combine("Textures"); + + CreateDirectory(globals.BuildStageGameDataDirectory); + CreateDirectory(globals.BuildStageGameDataModDirectory); + CreateDirectory(buildStageGameDataModPluginsDirectory); + CreateDirectory(buildStageGameDataModTexturesDirectory); + + CopyFiles( + GetFiles(string.Format("{0}/*", binDirectory)), + buildStageGameDataModPluginsDirectory + ); + CopyDirectory( + srcGameDataDirectory, + globals.BuildStageGameDataModDirectory + ); + CopyFile( + artworkDirectory.CombineWithFilePath("hotspot-white-38x38.png"), + buildStageGameDataModTexturesDirectory.CombineWithFilePath("AppLauncher.png") + ); + CopyFile( + artworkDirectory.CombineWithFilePath("hotspot-white-24x24.png"), + buildStageGameDataModTexturesDirectory.CombineWithFilePath("Toolbar.png") + ); + CopyFileToDirectory("CHANGELOG.md", globals.BuildStageGameDataModDirectory); + CopyFileToDirectory("LICENSE.md", globals.BuildStageGameDataModDirectory); + CopyFileToDirectory("README.md", globals.BuildStageGameDataModDirectory); +}); + +Task("Deploy") + .IsDependentOn("Stage") + .IsDependentOn("CleanDeploy") + .Does(() => +{ + var globals = getGlobals(); + + CopyDirectory(globals.BuildStageGameDataModDirectory, globals.DeployDirectory); +}); + +Task("Run") + .IsDependentOn("Deploy") + .Does(() => +{ + var globals = getGlobals(); + + StartAndReturnProcess(globals.BuildConfiguration.KspPath(globals.BuildConfiguration.KspBin), new ProcessSettings + { + WorkingDirectory = globals.BuildConfiguration.KspDir + }); +}); + +Task("Package") + .IsDependentOn("CleanPackage") + .IsDependentOn("Stage") + .Does(() => +{ + var globals = getGlobals(); + + CreateDirectory(globals.BuildPkgDirectory); + + Zip( + globals.BuildStageDirectory, + globals.BuildPkgDirectory.CombineWithFilePath( + string.Format("{0}-{1}.zip", globals.ModIdentifier, globals.BuildVersion) + ) + ); +}); + +Task("Version") + .Does(() => +{ + Information(GetVersion()); +}); + +Task("ChangeLog") + .Does(() => +{ + Information(GetChangeLog().LatestChanges); +}); + +RunTarget(getGlobals().Target); + +public SemVer GetBuildVersion() +{ + SemVer buildVersion; + + var release = Argument("release", false);; + var changeLog = GetChangeLog(); + var version = changeLog.LatestVersion; + var rev = GetGitRevision(useShort: true); + + if (rev != null && !release) + { + if (version.Build == null) + buildVersion = new SemVer(version.Major, version.Minor, version.Patch, version.Pre, rev); + else + throw new Exception("VERSION already contains build metadata"); + } + else + { + buildVersion = version; + } + + return buildVersion; +} + +private DirectoryPath GetNuGetPackageDirectory(string package) +{ + var globals = getGlobals(); + + return GetDirectories(string.Format("{0}/*", globals.BuildLibNugetDirectory)) + .Where(i => i.GetDirectoryName().StartsWith(package)) + .OrderByDescending(i => { + var directoryName = i.GetDirectoryName(); + return new Version(directoryName.Substring(package.Length + 1, directoryName.Length - package.Length - 1)); + }) + .FirstOrDefault(); +} diff --git a/build.example.yml b/build.example.yml index 3cfb13d..89f53f1 100644 --- a/build.example.yml +++ b/build.example.yml @@ -3,5 +3,5 @@ ksp_dir: null # Executable file to run. -# Example: "KSP.exe" +# Example: "KSP_x64.exe" ksp_bin: null diff --git a/build.ps1 b/build.ps1 index c766631..709a853 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,41 +1,51 @@ -Param ( - [Parameter(Position = 0)] - [string]$Arg0, - - [Parameter(ValueFromRemainingArguments = $true)] - [Object[]]$RemainingArgs -) - -# Globals -# TODO: The experimental flag is necessary after upgrading to VS2015 until the final version of Roslyn is available -# This should presumably occur once VS2015 is final -$UseExperimental = $true -$RootDir = "$PSScriptRoot" -$PackagesConfigFile = "$RootDir/packages.config" -$PackagesDir = "$RootDir/Library/NuGet" -$CakeVersionXPath = "//package[@id='Cake'][1]/@version" -$CakeVersion = (Select-Xml -Xml ([xml](Get-Content $PackagesConfigFile)) -XPath $CakeVersionXPath).Node.Value -$CakeExe = "$PackagesDir/Cake.$CakeVersion/Cake.exe" - -# Install build packages -iex "NuGet install `"$PackagesConfigFile`" -OutputDirectory `"$PackagesDir`"" | - Select-String -NotMatch -Pattern "All packages listed in packages.config are already installed." - -# Build args -$cakeArgs = @() - -if ($Arg0) { - if ($Arg0[0] -eq "-") { - $cakeArgs += "$Arg0" - } else { - $cakeArgs += "-target=$Arg0" - } -} - -if ($UseExperimental) { - $cakeArgs += "-experimental" -} - -# Run Cake -iex "$CakeExe $cakeArgs $RemainingArgs" -exit $LASTEXITCODE +Param ( + [Parameter(Position = 0)] + [string]$Arg0, + + [Parameter(ValueFromRemainingArguments = $true)] + [Object[]]$RemainingArgs +) + +# Globals +$NugetVersion = "3.4.3" +$UseExperimental = $false +$RootDir = "${PSScriptRoot}" +$BuildDir = "${RootDir}/.build" +$ToolsDir = "${BuildDir}/tools" +$PackagesDir = "${BuildDir}/lib/nuget" +$NugetExe = "${ToolsDir}/nuget/${NugetVersion}/NuGet.exe" +$PackagesConfigFile = "${RootDir}/packages.config" +$CakeVersion = (Select-Xml -Xml ([xml](Get-Content $PackagesConfigFile)) -XPath "//package[@id='Cake'][1]/@version").Node.Value +$CakeExe = "${PackagesDir}/Cake.${CakeVersion}/Cake.exe" + +# Download NuGet +$nugetDir = Split-Path $NugetExe -Parent +if (!(Test-Path $nugetDir)) { + mkdir $nugetDir > $null +} + +if (!(Test-Path $NugetExe)) { + (New-Object System.Net.WebClient).DownloadFile("https://dist.nuget.org/win-x86-commandline/v${NugetVersion}/nuget.exe", $NugetExe) +} + +# Install build packages +iex "${NugetExe} install `"${PackagesConfigFile}`" -OutputDirectory `"${PackagesDir}`"" + +# Build args +$cakeArgs = @() + +if ($Arg0) { + if ($Arg0[0] -eq "-") { + $cakeArgs += "${Arg0}" + } else { + $cakeArgs += "--target=${Arg0}" + } +} + +if ($UseExperimental) { + $cakeArgs += "--experimental" +} + +# Run Cake +iex "${CakeExe} ${cakeArgs} ${RemainingArgs}" +exit $LASTEXITCODE diff --git a/utilities.cake b/cake/utilities.cake similarity index 80% rename from utilities.cake rename to cake/utilities.cake index 7c1f2df..5d8622d 100644 --- a/utilities.cake +++ b/cake/utilities.cake @@ -1,6 +1,4 @@ -#r "Library/NuGet/YamlDotNet.3.7.0/lib/net35/YamlDotNet.dll" - -using YamlDotNet.Serialization; +#addin "Cake.Yaml" public T GetBuildConfiguration() where T : new() { @@ -8,14 +6,11 @@ public T GetBuildConfiguration() where T : new() var workingDirectoryName = workingDirectorySegments[workingDirectorySegments.Length - 1]; var configFile = (new [] { "build.yml", String.Format("../{0}.build.yml", workingDirectoryName) }) - .FirstOrDefault(System.IO.File.Exists); + .Select(i => File(i)) + .Select(i => i.Path) + .FirstOrDefault(FileExists); - if (configFile == null) - { - return new T(); - } - - return new Deserializer(ignoreUnmatched: true).Deserialize(new StreamReader(configFile)); + return configFile != null ? DeserializeYamlFromFile(configFile) : new T(); } public string GetSolution() @@ -91,7 +86,11 @@ public string GetGitRevision(bool useShort) var shortOption = useShort ? "--short" : ""; StartProcess(git, - new ProcessSettings { RedirectStandardOutput = true, Arguments = $"rev-parse {shortOption} HEAD"}, + new ProcessSettings + { + RedirectStandardOutput = true, + Arguments = string.Format("rev-parse {0} HEAD", shortOption) + }, out output ); @@ -116,15 +115,15 @@ public SemVer GetVersion() public ChangeLog GetChangeLog() { - return new ChangeLog("CHANGES.md"); + return new ChangeLog("CHANGELOG.md"); } public sealed class ChangeLog { private static readonly Regex VersionPattern = new Regex(@"^## v(?.+)$", RegexOptions.Compiled); - public SemVer LatestVersion { get; } - public string LatestChanges { get; } + public SemVer LatestVersion { get; private set; } + public string LatestChanges { get; private set; } public ChangeLog(string path) { @@ -175,11 +174,11 @@ public sealed class SemVer private string _string; - public uint Major { get; } - public uint Minor { get; } - public uint Patch { get; } - public string Pre { get; } - public string Build { get; } + public uint Major { get; private set; } + public uint Minor { get; private set; } + public uint Patch { get; private set; } + public string Pre { get; private set; } + public string Build { get; private set; } public SemVer(string s) { @@ -207,7 +206,7 @@ public sealed class SemVer } else { - throw new FormatException($"Unable to parse semantic version: {_string}"); + throw new FormatException(string.Format("Unable to parse semantic version: {0}", _string)); } } @@ -219,16 +218,16 @@ public sealed class SemVer Pre = pre; Build = build; - _string = $"{Major}.{Minor}.{Patch}"; + _string = string.Format("{0}.{1}.{2}", Major, Minor, Patch); if (pre != null) { - _string += $"-{Pre}"; + _string += string.Format("-{0}", Pre); } if (build != null) { - _string += $"+{Build}"; + _string += string.Format("+{0}", Build); } } diff --git a/nuget.config b/nuget.config index b90baac..7ae0b14 100644 --- a/nuget.config +++ b/nuget.config @@ -1,13 +1,10 @@  - + - - - - - + + diff --git a/packages.config b/packages.config index 043e8ff..aa15976 100644 --- a/packages.config +++ b/packages.config @@ -1,6 +1,5 @@  - - + diff --git a/Configuration/HotSpot.cfg b/src/GameData/Configuration/HotSpot.cfg similarity index 100% rename from Configuration/HotSpot.cfg rename to src/GameData/Configuration/HotSpot.cfg diff --git a/Patches/HotSpotModule.cfg b/src/GameData/Patches/HotSpotModule.cfg similarity index 100% rename from Patches/HotSpotModule.cfg rename to src/GameData/Patches/HotSpotModule.cfg diff --git a/Patches/KIS.cfg b/src/GameData/Patches/KIS.cfg similarity index 100% rename from Patches/KIS.cfg rename to src/GameData/Patches/KIS.cfg diff --git a/src/GlobalAssemblyInfo.cs b/src/GlobalAssemblyInfo.cs new file mode 100644 index 0000000..46baf63 --- /dev/null +++ b/src/GlobalAssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Reflection; + +[assembly: AssemblyProduct("Hot Spot")] +[assembly: AssemblyCompany("Hot Spot Contributors")] +[assembly: AssemblyCopyright("Copyright © 2015-2016")] + +#if DEBUG +[assembly: AssemblyConfiguration("Debug")] +#else +[assembly: AssemblyConfiguration("Release")] +#endif diff --git a/Source/HotSpot/Compat/Toolbar/ToolbarWrapper.cs b/src/HotSpot/Compat/Toolbar/ToolbarWrapper.cs similarity index 97% rename from Source/HotSpot/Compat/Toolbar/ToolbarWrapper.cs rename to src/HotSpot/Compat/Toolbar/ToolbarWrapper.cs index d3a2886..b8e7cc8 100644 --- a/Source/HotSpot/Compat/Toolbar/ToolbarWrapper.cs +++ b/src/HotSpot/Compat/Toolbar/ToolbarWrapper.cs @@ -1,895 +1,895 @@ -/* -Copyright (c) 2013-2015, Maik Schreiber -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// ReSharper disable All - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using UnityEngine; - -namespace HotSpot.Compat.Toolbar -{ - - - - /**********************************************************\ - * --- DO NOT EDIT BELOW THIS COMMENT --- * - * * - * This file contains classes and interfaces to use the * - * Toolbar Plugin without creating a hard dependency on it. * - * * - * There is nothing in this file that needs to be edited * - * by hand. * - * * - * --- DO NOT EDIT BELOW THIS COMMENT --- * - \**********************************************************/ - - - - /// - /// The global tool bar manager. - /// +/* +Copyright (c) 2013-2015, Maik Schreiber +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// ReSharper disable All + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace HotSpot.Compat.Toolbar +{ + + + + /**********************************************************\ + * --- DO NOT EDIT BELOW THIS COMMENT --- * + * * + * This file contains classes and interfaces to use the * + * Toolbar Plugin without creating a hard dependency on it. * + * * + * There is nothing in this file that needs to be edited * + * by hand. * + * * + * --- DO NOT EDIT BELOW THIS COMMENT --- * + \**********************************************************/ + + + + /// + /// The global tool bar manager. + /// - public partial class ToolbarManager : IToolbarManager - { - /// - /// Whether the Toolbar Plugin is available. - /// - public static bool ToolbarAvailable - { - get - { - if(toolbarAvailable == null) - { - toolbarAvailable = Instance != null; - } - return (bool) toolbarAvailable; - } - } - - /// - /// The global tool bar manager instance. - /// - public static IToolbarManager Instance - { - get - { - if((toolbarAvailable != false) && (instance_ == null)) - { - Type type = ToolbarTypes.getType("Toolbar.ToolbarManager"); - if(type != null) - { - object realToolbarManager = ToolbarTypes.getStaticProperty(type, "Instance").GetValue(null, null); - instance_ = new ToolbarManager(realToolbarManager); - } - } - return instance_; - } - } - } - - #region interfaces - - /// - /// A toolbar manager. - /// - public interface IToolbarManager - { - /// - /// Adds a new button. - /// - /// - /// To replace an existing button, just add a new button using the old button's namespace and ID. - /// Note that the new button will inherit the screen position of the old button. - /// - /// The new button's namespace. This is usually the plugin's name. Must not include special characters like '.' - /// The new button's ID. This ID must be unique across all buttons in the namespace. Must not include special characters like '.' - /// The button created. - IButton add(string ns, string id); - } - - /// - /// Represents a clickable button. - /// - public interface IButton - { - /// - /// The text displayed on the button. Set to null to hide text. - /// - /// - /// The text can be changed at any time to modify the button's appearance. Note that since this will also - /// modify the button's size, this feature should be used sparingly, if at all. - /// - /// - string Text - { - set; - get; - } - - /// - /// The color the button text is displayed with. Defaults to Color.white. - /// - /// - /// The text color can be changed at any time to modify the button's appearance. - /// - Color TextColor - { - set; - get; - } - - /// - /// The path of a texture file to display an icon on the button. Set to null to hide icon. - /// - /// - /// - /// A texture path on a button will have precedence over text. That is, if both text and texture path - /// have been set on a button, the button will show the texture, not the text. - /// - /// - /// The texture size must not exceed 24x24 pixels. - /// - /// - /// The texture path must be relative to the "GameData" directory, and must not specify a file name suffix. - /// Valid example: MyAddon/Textures/icon_mybutton - /// - /// - /// The texture path can be changed at any time to modify the button's appearance. - /// - /// - /// - string TexturePath - { - set; - get; - } - - /// - /// The button's tool tip text. Set to null if no tool tip is desired. - /// - /// - /// Tool Tip Text Should Always Use Headline Style Like This. - /// - string ToolTip - { - set; - get; - } - - /// - /// Whether this button is currently visible or not. Can be used in addition to or as a replacement for . - /// - /// - /// Setting this property to true does not affect the player's ability to hide the button using the configuration. - /// Conversely, setting this property to false does not enable the player to show the button using the configuration. - /// - bool Visible - { - set; - get; - } - - /// - /// Determines this button's visibility. Can be used in addition to or as a replacement for . - /// - /// - /// The return value from IVisibility.Visible is subject to the same rules as outlined for - /// . - /// - IVisibility Visibility - { - set; - get; - } - - /// - /// Whether this button is currently effectively visible or not. This is a combination of - /// and . - /// - /// - /// Note that the toolbar is not visible in certain game scenes, for example the loading screens. This property - /// does not reflect button invisibility in those scenes. In addition, this property does not reflect the - /// player's configuration of the button's visibility. - /// - bool EffectivelyVisible - { - get; - } - - /// - /// Whether this button is currently enabled (clickable) or not. This does not affect the player's ability to - /// position the button on their toolbar. - /// - bool Enabled - { - set; - get; - } - - /// - /// Whether this button is currently "important." Set to false to return to normal button behaviour. - /// - /// - /// - /// This can be used to temporarily force the button to be shown on screen regardless of the toolbar being - /// currently in auto-hidden mode. For example, a button that signals the arrival of a private message in - /// a chat room could mark itself as "important" as long as the message has not been read. - /// - /// - /// Setting this property does not change the appearance of the button. Use to - /// change the button's icon. - /// - /// - /// Setting this property to true does not affect the player's ability to hide the button using the - /// configuration. - /// - /// - /// This feature should be used only sparingly, if at all, since it forces the button to be displayed on - /// screen even when it normally wouldn't. - /// - /// - bool Important - { - set; - get; - } - - /// - /// A drawable that is tied to the current button. This can be anything from a popup menu to - /// an informational window. Set to null to hide the drawable. - /// - IDrawable Drawable - { - set; - get; - } - - /// - /// Event handler that can be registered with to receive "on click" events. - /// - /// - /// - /// IButton button = ... - /// button.OnClick += (e) => { - /// Debug.Log("button clicked, mouseButton: " + e.MouseButton); - /// }; - /// - /// - event ClickHandler OnClick; - - /// - /// Event handler that can be registered with to receive "on mouse enter" events. - /// - /// - /// - /// IButton button = ... - /// button.OnMouseEnter += (e) => { - /// Debug.Log("mouse entered button"); - /// }; - /// - /// - event MouseEnterHandler OnMouseEnter; - - /// - /// Event handler that can be registered with to receive "on mouse leave" events. - /// - /// - /// - /// IButton button = ... - /// button.OnMouseLeave += (e) => { - /// Debug.Log("mouse left button"); - /// }; - /// - /// - event MouseLeaveHandler OnMouseLeave; - - /// - /// Permanently destroys this button so that it is no longer displayed. - /// Should be used when a plugin is stopped to remove leftover buttons. - /// - void Destroy(); - } - - /// - /// A drawable that is tied to a particular button. This can be anything from a popup menu - /// to an informational window. - /// - public interface IDrawable - { - /// - /// Update any information. This is called once per frame. - /// - void Update(); - - /// - /// Draws GUI widgets for this drawable. This is the equivalent to the OnGUI() message in - /// . - /// - /// - /// The drawable will be positioned near its parent toolbar according to the drawable's current - /// width/height. - /// - /// The left/top position of where to draw this drawable. - /// The current width/height of this drawable. - Vector2 Draw(Vector2 position); - } - - #endregion - - #region events - - /// - /// Event describing a click on a button. - /// - public partial class ClickEvent : EventArgs - { - /// - /// The button that has been clicked. - /// - public readonly IButton Button; - - /// - /// The mouse button which the button was clicked with. - /// - /// - /// Is 0 for left mouse button, 1 for right mouse button, and 2 for middle mouse button. - /// - public readonly int MouseButton; - } - - /// - /// An event handler that is invoked whenever a button has been clicked. - /// - /// An event describing the button click. - public delegate void ClickHandler(ClickEvent e); - - /// - /// Event describing the mouse pointer moving about a button. - /// - public abstract partial class MouseMoveEvent - { - /// - /// The button in question. - /// - public readonly IButton button; - } - - /// - /// Event describing the mouse pointer entering a button's area. - /// - public partial class MouseEnterEvent : MouseMoveEvent - { - } - - /// - /// Event describing the mouse pointer leaving a button's area. - /// - public partial class MouseLeaveEvent : MouseMoveEvent - { - } - - /// - /// An event handler that is invoked whenever the mouse pointer enters a button's area. - /// - /// An event describing the mouse pointer entering. - public delegate void MouseEnterHandler(MouseEnterEvent e); - - /// - /// An event handler that is invoked whenever the mouse pointer leaves a button's area. - /// - /// An event describing the mouse pointer leaving. - public delegate void MouseLeaveHandler(MouseLeaveEvent e); - - #endregion - - #region visibility - - /// - /// Determines visibility of a button. - /// - /// - public interface IVisibility - { - /// - /// Whether a button is currently visible or not. - /// - /// - bool Visible - { - get; - } - } - - /// - /// Determines visibility of a button in relation to the currently running game scene. - /// - /// - /// - /// IButton button = ... - /// button.Visibility = new GameScenesVisibility(GameScenes.EDITOR, GameScenes.FLIGHT); - /// - /// - /// - public class GameScenesVisibility : IVisibility - { - public bool Visible - { - get - { - return (bool) visibleProperty.GetValue(realGameScenesVisibility, null); - } - } - - private object realGameScenesVisibility; - private PropertyInfo visibleProperty; - - public GameScenesVisibility(params GameScenes[] gameScenes) - { - Type gameScenesVisibilityType = ToolbarTypes.getType("Toolbar.GameScenesVisibility"); - realGameScenesVisibility = Activator.CreateInstance(gameScenesVisibilityType, new object[] { gameScenes }); - visibleProperty = ToolbarTypes.getProperty(gameScenesVisibilityType, "Visible"); - } - } - - #endregion - - #region drawable - - /// - /// A drawable that draws a popup menu. - /// - public partial class PopupMenuDrawable : IDrawable - { - /// - /// Event handler that can be registered with to receive "any menu option clicked" events. - /// - public event Action OnAnyOptionClicked - { - add - { - onAnyOptionClickedEvent.AddEventHandler(realPopupMenuDrawable, value); - } - remove - { - onAnyOptionClickedEvent.RemoveEventHandler(realPopupMenuDrawable, value); - } - } - - private object realPopupMenuDrawable; - private MethodInfo updateMethod; - private MethodInfo drawMethod; - private MethodInfo addOptionMethod; - private MethodInfo addSeparatorMethod; - private MethodInfo destroyMethod; - private EventInfo onAnyOptionClickedEvent; - - public PopupMenuDrawable() - { - Type popupMenuDrawableType = ToolbarTypes.getType("Toolbar.PopupMenuDrawable"); - realPopupMenuDrawable = Activator.CreateInstance(popupMenuDrawableType, null); - updateMethod = ToolbarTypes.getMethod(popupMenuDrawableType, "Update"); - drawMethod = ToolbarTypes.getMethod(popupMenuDrawableType, "Draw"); - addOptionMethod = ToolbarTypes.getMethod(popupMenuDrawableType, "AddOption"); - addSeparatorMethod = ToolbarTypes.getMethod(popupMenuDrawableType, "AddSeparator"); - destroyMethod = ToolbarTypes.getMethod(popupMenuDrawableType, "Destroy"); - onAnyOptionClickedEvent = ToolbarTypes.getEvent(popupMenuDrawableType, "OnAnyOptionClicked"); - } - - public void Update() - { - updateMethod.Invoke(realPopupMenuDrawable, null); - } - - public Vector2 Draw(Vector2 position) - { - return (Vector2) drawMethod.Invoke(realPopupMenuDrawable, new object[] { position }); - } - - /// - /// Adds a new option to the popup menu. - /// - /// The text of the option. - /// A button that can be used to register clicks on the menu option. - public IButton AddOption(string text) - { - object realButton = addOptionMethod.Invoke(realPopupMenuDrawable, new object[] { text }); - return new Button(realButton, new ToolbarTypes()); - } - - /// - /// Adds a separator to the popup menu. - /// - public void AddSeparator() - { - addSeparatorMethod.Invoke(realPopupMenuDrawable, null); - } - - /// - /// Destroys this drawable. This must always be called before disposing of this drawable. - /// - public void Destroy() - { - destroyMethod.Invoke(realPopupMenuDrawable, null); - } - } - - #endregion - - #region private implementations - - public partial class ToolbarManager : IToolbarManager - { - private static bool? toolbarAvailable = null; - private static IToolbarManager instance_; - - private object realToolbarManager; - private MethodInfo addMethod; - private Dictionary buttons = new Dictionary(); - private ToolbarTypes types = new ToolbarTypes(); - - private ToolbarManager(object realToolbarManager) - { - this.realToolbarManager = realToolbarManager; - - addMethod = ToolbarTypes.getMethod(types.iToolbarManagerType, "add"); - } - - public IButton add(string ns, string id) - { - object realButton = addMethod.Invoke(realToolbarManager, new object[] { ns, id }); - IButton button = new Button(realButton, types); - buttons.Add(realButton, button); - return button; - } - } - - internal class Button : IButton - { - private object realButton; - private ToolbarTypes types; - private Delegate realClickHandler; - private Delegate realMouseEnterHandler; - private Delegate realMouseLeaveHandler; - - internal Button(object realButton, ToolbarTypes types) - { - this.realButton = realButton; - this.types = types; - - realClickHandler = attachEventHandler(types.button.onClickEvent, "clicked", realButton); - realMouseEnterHandler = attachEventHandler(types.button.onMouseEnterEvent, "mouseEntered", realButton); - realMouseLeaveHandler = attachEventHandler(types.button.onMouseLeaveEvent, "mouseLeft", realButton); - } - - private Delegate attachEventHandler(EventInfo @event, string methodName, object realButton) - { - MethodInfo method = GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance); - Delegate d = Delegate.CreateDelegate(@event.EventHandlerType, this, method); - @event.AddEventHandler(realButton, d); - return d; - } - - public string Text - { - set - { - types.button.textProperty.SetValue(realButton, value, null); - } - get - { - return (string) types.button.textProperty.GetValue(realButton, null); - } - } - - public Color TextColor - { - set - { - types.button.textColorProperty.SetValue(realButton, value, null); - } - get - { - return (Color) types.button.textColorProperty.GetValue(realButton, null); - } - } - - public string TexturePath - { - set - { - types.button.texturePathProperty.SetValue(realButton, value, null); - } - get - { - return (string) types.button.texturePathProperty.GetValue(realButton, null); - } - } - - public string ToolTip - { - set - { - types.button.toolTipProperty.SetValue(realButton, value, null); - } - get - { - return (string) types.button.toolTipProperty.GetValue(realButton, null); - } - } - - public bool Visible - { - set - { - types.button.visibleProperty.SetValue(realButton, value, null); - } - get - { - return (bool) types.button.visibleProperty.GetValue(realButton, null); - } - } - - public IVisibility Visibility - { - set - { - object functionVisibility = null; - if(value != null) - { - functionVisibility = Activator.CreateInstance(types.functionVisibilityType, new object[] { new Func(() => value.Visible) }); - } - types.button.visibilityProperty.SetValue(realButton, functionVisibility, null); - visibility_ = value; - } - get - { - return visibility_; - } - } - private IVisibility visibility_; - - public bool EffectivelyVisible - { - get - { - return (bool) types.button.effectivelyVisibleProperty.GetValue(realButton, null); - } - } - - public bool Enabled - { - set - { - types.button.enabledProperty.SetValue(realButton, value, null); - } - get - { - return (bool) types.button.enabledProperty.GetValue(realButton, null); - } - } - - public bool Important - { - set - { - types.button.importantProperty.SetValue(realButton, value, null); - } - get - { - return (bool) types.button.importantProperty.GetValue(realButton, null); - } - } - - public IDrawable Drawable - { - set - { - object functionDrawable = null; - if(value != null) - { - functionDrawable = Activator.CreateInstance(types.functionDrawableType, new object[] { - new Action(() => value.Update()), - new Func((pos) => value.Draw(pos)) - }); - } - types.button.drawableProperty.SetValue(realButton, functionDrawable, null); - drawable_ = value; - } - get - { - return drawable_; - } - } - private IDrawable drawable_; - - public event ClickHandler OnClick; - - private void clicked(object realEvent) - { - if(OnClick != null) - { - OnClick(new ClickEvent(realEvent, this)); - } - } - - public event MouseEnterHandler OnMouseEnter; - - private void mouseEntered(object realEvent) - { - if(OnMouseEnter != null) - { - OnMouseEnter(new MouseEnterEvent(this)); - } - } - - public event MouseLeaveHandler OnMouseLeave; - - private void mouseLeft(object realEvent) - { - if(OnMouseLeave != null) - { - OnMouseLeave(new MouseLeaveEvent(this)); - } - } - - public void Destroy() - { - detachEventHandler(types.button.onClickEvent, realClickHandler, realButton); - detachEventHandler(types.button.onMouseEnterEvent, realMouseEnterHandler, realButton); - detachEventHandler(types.button.onMouseLeaveEvent, realMouseLeaveHandler, realButton); - - types.button.destroyMethod.Invoke(realButton, null); - } - - private void detachEventHandler(EventInfo @event, Delegate d, object realButton) - { - @event.RemoveEventHandler(realButton, d); - } - } - - public partial class ClickEvent : EventArgs - { - internal ClickEvent(object realEvent, IButton button) - { - Type type = realEvent.GetType(); - Button = button; - MouseButton = (int) type.GetField("MouseButton", BindingFlags.Public | BindingFlags.Instance).GetValue(realEvent); - } - } - - public abstract partial class MouseMoveEvent : EventArgs - { - internal MouseMoveEvent(IButton button) - { - this.button = button; - } - } - - public partial class MouseEnterEvent : MouseMoveEvent - { - internal MouseEnterEvent(IButton button) - : base(button) - { - } - } - - public partial class MouseLeaveEvent : MouseMoveEvent - { - internal MouseLeaveEvent(IButton button) - : base(button) - { - } - } - - internal class ToolbarTypes - { - internal readonly Type iToolbarManagerType; - internal readonly Type functionVisibilityType; - internal readonly Type functionDrawableType; - internal readonly ButtonTypes button; - - internal ToolbarTypes() - { - iToolbarManagerType = getType("Toolbar.IToolbarManager"); - functionVisibilityType = getType("Toolbar.FunctionVisibility"); - functionDrawableType = getType("Toolbar.FunctionDrawable"); - - Type iButtonType = getType("Toolbar.IButton"); - button = new ButtonTypes(iButtonType); - } - - internal static Type getType(string name) - { - return AssemblyLoader.loadedAssemblies - .SelectMany(a => a.assembly.GetExportedTypes()) - .SingleOrDefault(t => t.FullName == name); - } - - internal static PropertyInfo getProperty(Type type, string name) - { - return type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); - } - - internal static PropertyInfo getStaticProperty(Type type, string name) - { - return type.GetProperty(name, BindingFlags.Public | BindingFlags.Static); - } - - internal static EventInfo getEvent(Type type, string name) - { - return type.GetEvent(name, BindingFlags.Public | BindingFlags.Instance); - } - - internal static MethodInfo getMethod(Type type, string name) - { - return type.GetMethod(name, BindingFlags.Public | BindingFlags.Instance); - } - } - - internal class ButtonTypes - { - internal readonly Type iButtonType; - internal readonly PropertyInfo textProperty; - internal readonly PropertyInfo textColorProperty; - internal readonly PropertyInfo texturePathProperty; - internal readonly PropertyInfo toolTipProperty; - internal readonly PropertyInfo visibleProperty; - internal readonly PropertyInfo visibilityProperty; - internal readonly PropertyInfo effectivelyVisibleProperty; - internal readonly PropertyInfo enabledProperty; - internal readonly PropertyInfo importantProperty; - internal readonly PropertyInfo drawableProperty; - internal readonly EventInfo onClickEvent; - internal readonly EventInfo onMouseEnterEvent; - internal readonly EventInfo onMouseLeaveEvent; - internal readonly MethodInfo destroyMethod; - - internal ButtonTypes(Type iButtonType) - { - this.iButtonType = iButtonType; - - textProperty = ToolbarTypes.getProperty(iButtonType, "Text"); - textColorProperty = ToolbarTypes.getProperty(iButtonType, "TextColor"); - texturePathProperty = ToolbarTypes.getProperty(iButtonType, "TexturePath"); - toolTipProperty = ToolbarTypes.getProperty(iButtonType, "ToolTip"); - visibleProperty = ToolbarTypes.getProperty(iButtonType, "Visible"); - visibilityProperty = ToolbarTypes.getProperty(iButtonType, "Visibility"); - effectivelyVisibleProperty = ToolbarTypes.getProperty(iButtonType, "EffectivelyVisible"); - enabledProperty = ToolbarTypes.getProperty(iButtonType, "Enabled"); - importantProperty = ToolbarTypes.getProperty(iButtonType, "Important"); - drawableProperty = ToolbarTypes.getProperty(iButtonType, "Drawable"); - onClickEvent = ToolbarTypes.getEvent(iButtonType, "OnClick"); - onMouseEnterEvent = ToolbarTypes.getEvent(iButtonType, "OnMouseEnter"); - onMouseLeaveEvent = ToolbarTypes.getEvent(iButtonType, "OnMouseLeave"); - destroyMethod = ToolbarTypes.getMethod(iButtonType, "Destroy"); - } - } - - #endregion -} + public partial class ToolbarManager : IToolbarManager + { + /// + /// Whether the Toolbar Plugin is available. + /// + public static bool ToolbarAvailable + { + get + { + if(toolbarAvailable == null) + { + toolbarAvailable = Instance != null; + } + return (bool) toolbarAvailable; + } + } + + /// + /// The global tool bar manager instance. + /// + public static IToolbarManager Instance + { + get + { + if((toolbarAvailable != false) && (instance_ == null)) + { + Type type = ToolbarTypes.getType("Toolbar.ToolbarManager"); + if(type != null) + { + object realToolbarManager = ToolbarTypes.getStaticProperty(type, "Instance").GetValue(null, null); + instance_ = new ToolbarManager(realToolbarManager); + } + } + return instance_; + } + } + } + + #region interfaces + + /// + /// A toolbar manager. + /// + public interface IToolbarManager + { + /// + /// Adds a new button. + /// + /// + /// To replace an existing button, just add a new button using the old button's namespace and ID. + /// Note that the new button will inherit the screen position of the old button. + /// + /// The new button's namespace. This is usually the plugin's name. Must not include special characters like '.' + /// The new button's ID. This ID must be unique across all buttons in the namespace. Must not include special characters like '.' + /// The button created. + IButton add(string ns, string id); + } + + /// + /// Represents a clickable button. + /// + public interface IButton + { + /// + /// The text displayed on the button. Set to null to hide text. + /// + /// + /// The text can be changed at any time to modify the button's appearance. Note that since this will also + /// modify the button's size, this feature should be used sparingly, if at all. + /// + /// + string Text + { + set; + get; + } + + /// + /// The color the button text is displayed with. Defaults to Color.white. + /// + /// + /// The text color can be changed at any time to modify the button's appearance. + /// + Color TextColor + { + set; + get; + } + + /// + /// The path of a texture file to display an icon on the button. Set to null to hide icon. + /// + /// + /// + /// A texture path on a button will have precedence over text. That is, if both text and texture path + /// have been set on a button, the button will show the texture, not the text. + /// + /// + /// The texture size must not exceed 24x24 pixels. + /// + /// + /// The texture path must be relative to the "GameData" directory, and must not specify a file name suffix. + /// Valid example: MyAddon/Textures/icon_mybutton + /// + /// + /// The texture path can be changed at any time to modify the button's appearance. + /// + /// + /// + string TexturePath + { + set; + get; + } + + /// + /// The button's tool tip text. Set to null if no tool tip is desired. + /// + /// + /// Tool Tip Text Should Always Use Headline Style Like This. + /// + string ToolTip + { + set; + get; + } + + /// + /// Whether this button is currently visible or not. Can be used in addition to or as a replacement for . + /// + /// + /// Setting this property to true does not affect the player's ability to hide the button using the configuration. + /// Conversely, setting this property to false does not enable the player to show the button using the configuration. + /// + bool Visible + { + set; + get; + } + + /// + /// Determines this button's visibility. Can be used in addition to or as a replacement for . + /// + /// + /// The return value from IVisibility.Visible is subject to the same rules as outlined for + /// . + /// + IVisibility Visibility + { + set; + get; + } + + /// + /// Whether this button is currently effectively visible or not. This is a combination of + /// and . + /// + /// + /// Note that the toolbar is not visible in certain game scenes, for example the loading screens. This property + /// does not reflect button invisibility in those scenes. In addition, this property does not reflect the + /// player's configuration of the button's visibility. + /// + bool EffectivelyVisible + { + get; + } + + /// + /// Whether this button is currently enabled (clickable) or not. This does not affect the player's ability to + /// position the button on their toolbar. + /// + bool Enabled + { + set; + get; + } + + /// + /// Whether this button is currently "important." Set to false to return to normal button behaviour. + /// + /// + /// + /// This can be used to temporarily force the button to be shown on screen regardless of the toolbar being + /// currently in auto-hidden mode. For example, a button that signals the arrival of a private message in + /// a chat room could mark itself as "important" as long as the message has not been read. + /// + /// + /// Setting this property does not change the appearance of the button. Use to + /// change the button's icon. + /// + /// + /// Setting this property to true does not affect the player's ability to hide the button using the + /// configuration. + /// + /// + /// This feature should be used only sparingly, if at all, since it forces the button to be displayed on + /// screen even when it normally wouldn't. + /// + /// + bool Important + { + set; + get; + } + + /// + /// A drawable that is tied to the current button. This can be anything from a popup menu to + /// an informational window. Set to null to hide the drawable. + /// + IDrawable Drawable + { + set; + get; + } + + /// + /// Event handler that can be registered with to receive "on click" events. + /// + /// + /// + /// IButton button = ... + /// button.OnClick += (e) => { + /// Debug.Log("button clicked, mouseButton: " + e.MouseButton); + /// }; + /// + /// + event ClickHandler OnClick; + + /// + /// Event handler that can be registered with to receive "on mouse enter" events. + /// + /// + /// + /// IButton button = ... + /// button.OnMouseEnter += (e) => { + /// Debug.Log("mouse entered button"); + /// }; + /// + /// + event MouseEnterHandler OnMouseEnter; + + /// + /// Event handler that can be registered with to receive "on mouse leave" events. + /// + /// + /// + /// IButton button = ... + /// button.OnMouseLeave += (e) => { + /// Debug.Log("mouse left button"); + /// }; + /// + /// + event MouseLeaveHandler OnMouseLeave; + + /// + /// Permanently destroys this button so that it is no longer displayed. + /// Should be used when a plugin is stopped to remove leftover buttons. + /// + void Destroy(); + } + + /// + /// A drawable that is tied to a particular button. This can be anything from a popup menu + /// to an informational window. + /// + public interface IDrawable + { + /// + /// Update any information. This is called once per frame. + /// + void Update(); + + /// + /// Draws GUI widgets for this drawable. This is the equivalent to the OnGUI() message in + /// . + /// + /// + /// The drawable will be positioned near its parent toolbar according to the drawable's current + /// width/height. + /// + /// The left/top position of where to draw this drawable. + /// The current width/height of this drawable. + Vector2 Draw(Vector2 position); + } + + #endregion + + #region events + + /// + /// Event describing a click on a button. + /// + public partial class ClickEvent : EventArgs + { + /// + /// The button that has been clicked. + /// + public readonly IButton Button; + + /// + /// The mouse button which the button was clicked with. + /// + /// + /// Is 0 for left mouse button, 1 for right mouse button, and 2 for middle mouse button. + /// + public readonly int MouseButton; + } + + /// + /// An event handler that is invoked whenever a button has been clicked. + /// + /// An event describing the button click. + public delegate void ClickHandler(ClickEvent e); + + /// + /// Event describing the mouse pointer moving about a button. + /// + public abstract partial class MouseMoveEvent + { + /// + /// The button in question. + /// + public readonly IButton button; + } + + /// + /// Event describing the mouse pointer entering a button's area. + /// + public partial class MouseEnterEvent : MouseMoveEvent + { + } + + /// + /// Event describing the mouse pointer leaving a button's area. + /// + public partial class MouseLeaveEvent : MouseMoveEvent + { + } + + /// + /// An event handler that is invoked whenever the mouse pointer enters a button's area. + /// + /// An event describing the mouse pointer entering. + public delegate void MouseEnterHandler(MouseEnterEvent e); + + /// + /// An event handler that is invoked whenever the mouse pointer leaves a button's area. + /// + /// An event describing the mouse pointer leaving. + public delegate void MouseLeaveHandler(MouseLeaveEvent e); + + #endregion + + #region visibility + + /// + /// Determines visibility of a button. + /// + /// + public interface IVisibility + { + /// + /// Whether a button is currently visible or not. + /// + /// + bool Visible + { + get; + } + } + + /// + /// Determines visibility of a button in relation to the currently running game scene. + /// + /// + /// + /// IButton button = ... + /// button.Visibility = new GameScenesVisibility(GameScenes.EDITOR, GameScenes.FLIGHT); + /// + /// + /// + public class GameScenesVisibility : IVisibility + { + public bool Visible + { + get + { + return (bool) visibleProperty.GetValue(realGameScenesVisibility, null); + } + } + + private object realGameScenesVisibility; + private PropertyInfo visibleProperty; + + public GameScenesVisibility(params GameScenes[] gameScenes) + { + Type gameScenesVisibilityType = ToolbarTypes.getType("Toolbar.GameScenesVisibility"); + realGameScenesVisibility = Activator.CreateInstance(gameScenesVisibilityType, new object[] { gameScenes }); + visibleProperty = ToolbarTypes.getProperty(gameScenesVisibilityType, "Visible"); + } + } + + #endregion + + #region drawable + + /// + /// A drawable that draws a popup menu. + /// + public partial class PopupMenuDrawable : IDrawable + { + /// + /// Event handler that can be registered with to receive "any menu option clicked" events. + /// + public event Action OnAnyOptionClicked + { + add + { + onAnyOptionClickedEvent.AddEventHandler(realPopupMenuDrawable, value); + } + remove + { + onAnyOptionClickedEvent.RemoveEventHandler(realPopupMenuDrawable, value); + } + } + + private object realPopupMenuDrawable; + private MethodInfo updateMethod; + private MethodInfo drawMethod; + private MethodInfo addOptionMethod; + private MethodInfo addSeparatorMethod; + private MethodInfo destroyMethod; + private EventInfo onAnyOptionClickedEvent; + + public PopupMenuDrawable() + { + Type popupMenuDrawableType = ToolbarTypes.getType("Toolbar.PopupMenuDrawable"); + realPopupMenuDrawable = Activator.CreateInstance(popupMenuDrawableType, null); + updateMethod = ToolbarTypes.getMethod(popupMenuDrawableType, "Update"); + drawMethod = ToolbarTypes.getMethod(popupMenuDrawableType, "Draw"); + addOptionMethod = ToolbarTypes.getMethod(popupMenuDrawableType, "AddOption"); + addSeparatorMethod = ToolbarTypes.getMethod(popupMenuDrawableType, "AddSeparator"); + destroyMethod = ToolbarTypes.getMethod(popupMenuDrawableType, "Destroy"); + onAnyOptionClickedEvent = ToolbarTypes.getEvent(popupMenuDrawableType, "OnAnyOptionClicked"); + } + + public void Update() + { + updateMethod.Invoke(realPopupMenuDrawable, null); + } + + public Vector2 Draw(Vector2 position) + { + return (Vector2) drawMethod.Invoke(realPopupMenuDrawable, new object[] { position }); + } + + /// + /// Adds a new option to the popup menu. + /// + /// The text of the option. + /// A button that can be used to register clicks on the menu option. + public IButton AddOption(string text) + { + object realButton = addOptionMethod.Invoke(realPopupMenuDrawable, new object[] { text }); + return new Button(realButton, new ToolbarTypes()); + } + + /// + /// Adds a separator to the popup menu. + /// + public void AddSeparator() + { + addSeparatorMethod.Invoke(realPopupMenuDrawable, null); + } + + /// + /// Destroys this drawable. This must always be called before disposing of this drawable. + /// + public void Destroy() + { + destroyMethod.Invoke(realPopupMenuDrawable, null); + } + } + + #endregion + + #region private implementations + + public partial class ToolbarManager : IToolbarManager + { + private static bool? toolbarAvailable = null; + private static IToolbarManager instance_; + + private object realToolbarManager; + private MethodInfo addMethod; + private Dictionary buttons = new Dictionary(); + private ToolbarTypes types = new ToolbarTypes(); + + private ToolbarManager(object realToolbarManager) + { + this.realToolbarManager = realToolbarManager; + + addMethod = ToolbarTypes.getMethod(types.iToolbarManagerType, "add"); + } + + public IButton add(string ns, string id) + { + object realButton = addMethod.Invoke(realToolbarManager, new object[] { ns, id }); + IButton button = new Button(realButton, types); + buttons.Add(realButton, button); + return button; + } + } + + internal class Button : IButton + { + private object realButton; + private ToolbarTypes types; + private Delegate realClickHandler; + private Delegate realMouseEnterHandler; + private Delegate realMouseLeaveHandler; + + internal Button(object realButton, ToolbarTypes types) + { + this.realButton = realButton; + this.types = types; + + realClickHandler = attachEventHandler(types.button.onClickEvent, "clicked", realButton); + realMouseEnterHandler = attachEventHandler(types.button.onMouseEnterEvent, "mouseEntered", realButton); + realMouseLeaveHandler = attachEventHandler(types.button.onMouseLeaveEvent, "mouseLeft", realButton); + } + + private Delegate attachEventHandler(EventInfo @event, string methodName, object realButton) + { + MethodInfo method = GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance); + Delegate d = Delegate.CreateDelegate(@event.EventHandlerType, this, method); + @event.AddEventHandler(realButton, d); + return d; + } + + public string Text + { + set + { + types.button.textProperty.SetValue(realButton, value, null); + } + get + { + return (string) types.button.textProperty.GetValue(realButton, null); + } + } + + public Color TextColor + { + set + { + types.button.textColorProperty.SetValue(realButton, value, null); + } + get + { + return (Color) types.button.textColorProperty.GetValue(realButton, null); + } + } + + public string TexturePath + { + set + { + types.button.texturePathProperty.SetValue(realButton, value, null); + } + get + { + return (string) types.button.texturePathProperty.GetValue(realButton, null); + } + } + + public string ToolTip + { + set + { + types.button.toolTipProperty.SetValue(realButton, value, null); + } + get + { + return (string) types.button.toolTipProperty.GetValue(realButton, null); + } + } + + public bool Visible + { + set + { + types.button.visibleProperty.SetValue(realButton, value, null); + } + get + { + return (bool) types.button.visibleProperty.GetValue(realButton, null); + } + } + + public IVisibility Visibility + { + set + { + object functionVisibility = null; + if(value != null) + { + functionVisibility = Activator.CreateInstance(types.functionVisibilityType, new object[] { new Func(() => value.Visible) }); + } + types.button.visibilityProperty.SetValue(realButton, functionVisibility, null); + visibility_ = value; + } + get + { + return visibility_; + } + } + private IVisibility visibility_; + + public bool EffectivelyVisible + { + get + { + return (bool) types.button.effectivelyVisibleProperty.GetValue(realButton, null); + } + } + + public bool Enabled + { + set + { + types.button.enabledProperty.SetValue(realButton, value, null); + } + get + { + return (bool) types.button.enabledProperty.GetValue(realButton, null); + } + } + + public bool Important + { + set + { + types.button.importantProperty.SetValue(realButton, value, null); + } + get + { + return (bool) types.button.importantProperty.GetValue(realButton, null); + } + } + + public IDrawable Drawable + { + set + { + object functionDrawable = null; + if(value != null) + { + functionDrawable = Activator.CreateInstance(types.functionDrawableType, new object[] { + new Action(() => value.Update()), + new Func((pos) => value.Draw(pos)) + }); + } + types.button.drawableProperty.SetValue(realButton, functionDrawable, null); + drawable_ = value; + } + get + { + return drawable_; + } + } + private IDrawable drawable_; + + public event ClickHandler OnClick; + + private void clicked(object realEvent) + { + if(OnClick != null) + { + OnClick(new ClickEvent(realEvent, this)); + } + } + + public event MouseEnterHandler OnMouseEnter; + + private void mouseEntered(object realEvent) + { + if(OnMouseEnter != null) + { + OnMouseEnter(new MouseEnterEvent(this)); + } + } + + public event MouseLeaveHandler OnMouseLeave; + + private void mouseLeft(object realEvent) + { + if(OnMouseLeave != null) + { + OnMouseLeave(new MouseLeaveEvent(this)); + } + } + + public void Destroy() + { + detachEventHandler(types.button.onClickEvent, realClickHandler, realButton); + detachEventHandler(types.button.onMouseEnterEvent, realMouseEnterHandler, realButton); + detachEventHandler(types.button.onMouseLeaveEvent, realMouseLeaveHandler, realButton); + + types.button.destroyMethod.Invoke(realButton, null); + } + + private void detachEventHandler(EventInfo @event, Delegate d, object realButton) + { + @event.RemoveEventHandler(realButton, d); + } + } + + public partial class ClickEvent : EventArgs + { + internal ClickEvent(object realEvent, IButton button) + { + Type type = realEvent.GetType(); + Button = button; + MouseButton = (int) type.GetField("MouseButton", BindingFlags.Public | BindingFlags.Instance).GetValue(realEvent); + } + } + + public abstract partial class MouseMoveEvent : EventArgs + { + internal MouseMoveEvent(IButton button) + { + this.button = button; + } + } + + public partial class MouseEnterEvent : MouseMoveEvent + { + internal MouseEnterEvent(IButton button) + : base(button) + { + } + } + + public partial class MouseLeaveEvent : MouseMoveEvent + { + internal MouseLeaveEvent(IButton button) + : base(button) + { + } + } + + internal class ToolbarTypes + { + internal readonly Type iToolbarManagerType; + internal readonly Type functionVisibilityType; + internal readonly Type functionDrawableType; + internal readonly ButtonTypes button; + + internal ToolbarTypes() + { + iToolbarManagerType = getType("Toolbar.IToolbarManager"); + functionVisibilityType = getType("Toolbar.FunctionVisibility"); + functionDrawableType = getType("Toolbar.FunctionDrawable"); + + Type iButtonType = getType("Toolbar.IButton"); + button = new ButtonTypes(iButtonType); + } + + internal static Type getType(string name) + { + return AssemblyLoader.loadedAssemblies + .SelectMany(a => a.assembly.GetExportedTypes()) + .SingleOrDefault(t => t.FullName == name); + } + + internal static PropertyInfo getProperty(Type type, string name) + { + return type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); + } + + internal static PropertyInfo getStaticProperty(Type type, string name) + { + return type.GetProperty(name, BindingFlags.Public | BindingFlags.Static); + } + + internal static EventInfo getEvent(Type type, string name) + { + return type.GetEvent(name, BindingFlags.Public | BindingFlags.Instance); + } + + internal static MethodInfo getMethod(Type type, string name) + { + return type.GetMethod(name, BindingFlags.Public | BindingFlags.Instance); + } + } + + internal class ButtonTypes + { + internal readonly Type iButtonType; + internal readonly PropertyInfo textProperty; + internal readonly PropertyInfo textColorProperty; + internal readonly PropertyInfo texturePathProperty; + internal readonly PropertyInfo toolTipProperty; + internal readonly PropertyInfo visibleProperty; + internal readonly PropertyInfo visibilityProperty; + internal readonly PropertyInfo effectivelyVisibleProperty; + internal readonly PropertyInfo enabledProperty; + internal readonly PropertyInfo importantProperty; + internal readonly PropertyInfo drawableProperty; + internal readonly EventInfo onClickEvent; + internal readonly EventInfo onMouseEnterEvent; + internal readonly EventInfo onMouseLeaveEvent; + internal readonly MethodInfo destroyMethod; + + internal ButtonTypes(Type iButtonType) + { + this.iButtonType = iButtonType; + + textProperty = ToolbarTypes.getProperty(iButtonType, "Text"); + textColorProperty = ToolbarTypes.getProperty(iButtonType, "TextColor"); + texturePathProperty = ToolbarTypes.getProperty(iButtonType, "TexturePath"); + toolTipProperty = ToolbarTypes.getProperty(iButtonType, "ToolTip"); + visibleProperty = ToolbarTypes.getProperty(iButtonType, "Visible"); + visibilityProperty = ToolbarTypes.getProperty(iButtonType, "Visibility"); + effectivelyVisibleProperty = ToolbarTypes.getProperty(iButtonType, "EffectivelyVisible"); + enabledProperty = ToolbarTypes.getProperty(iButtonType, "Enabled"); + importantProperty = ToolbarTypes.getProperty(iButtonType, "Important"); + drawableProperty = ToolbarTypes.getProperty(iButtonType, "Drawable"); + onClickEvent = ToolbarTypes.getEvent(iButtonType, "OnClick"); + onMouseEnterEvent = ToolbarTypes.getEvent(iButtonType, "OnMouseEnter"); + onMouseLeaveEvent = ToolbarTypes.getEvent(iButtonType, "OnMouseLeave"); + destroyMethod = ToolbarTypes.getMethod(iButtonType, "Destroy"); + } + } + + #endregion +} diff --git a/Source/HotSpot/Config.cs b/src/HotSpot/Config.cs similarity index 100% rename from Source/HotSpot/Config.cs rename to src/HotSpot/Config.cs diff --git a/Source/HotSpot/Configuration/AutoBoolean.cs b/src/HotSpot/Configuration/AutoBoolean.cs similarity index 100% rename from Source/HotSpot/Configuration/AutoBoolean.cs rename to src/HotSpot/Configuration/AutoBoolean.cs diff --git a/Source/HotSpot/Configuration/ContextMenu/MetricNode.cs b/src/HotSpot/Configuration/ContextMenu/MetricNode.cs similarity index 100% rename from Source/HotSpot/Configuration/ContextMenu/MetricNode.cs rename to src/HotSpot/Configuration/ContextMenu/MetricNode.cs diff --git a/Source/HotSpot/Configuration/ContextMenuNode.cs b/src/HotSpot/Configuration/ContextMenuNode.cs similarity index 100% rename from Source/HotSpot/Configuration/ContextMenuNode.cs rename to src/HotSpot/Configuration/ContextMenuNode.cs diff --git a/Source/HotSpot/Configuration/DiagnosticsNode.cs b/src/HotSpot/Configuration/DiagnosticsNode.cs similarity index 100% rename from Source/HotSpot/Configuration/DiagnosticsNode.cs rename to src/HotSpot/Configuration/DiagnosticsNode.cs diff --git a/Source/HotSpot/Configuration/Gui/AppLauncherNode.cs b/src/HotSpot/Configuration/Gui/AppLauncherNode.cs similarity index 100% rename from Source/HotSpot/Configuration/Gui/AppLauncherNode.cs rename to src/HotSpot/Configuration/Gui/AppLauncherNode.cs diff --git a/Source/HotSpot/Configuration/Gui/ToolbarNode.cs b/src/HotSpot/Configuration/Gui/ToolbarNode.cs similarity index 100% rename from Source/HotSpot/Configuration/Gui/ToolbarNode.cs rename to src/HotSpot/Configuration/Gui/ToolbarNode.cs diff --git a/Source/HotSpot/Configuration/GuiNode.cs b/src/HotSpot/Configuration/GuiNode.cs similarity index 100% rename from Source/HotSpot/Configuration/GuiNode.cs rename to src/HotSpot/Configuration/GuiNode.cs diff --git a/Source/HotSpot/Configuration/Overlay/GradientNode.cs b/src/HotSpot/Configuration/Overlay/GradientNode.cs similarity index 100% rename from Source/HotSpot/Configuration/Overlay/GradientNode.cs rename to src/HotSpot/Configuration/Overlay/GradientNode.cs diff --git a/Source/HotSpot/Configuration/Overlay/MetricNode.cs b/src/HotSpot/Configuration/Overlay/MetricNode.cs similarity index 100% rename from Source/HotSpot/Configuration/Overlay/MetricNode.cs rename to src/HotSpot/Configuration/Overlay/MetricNode.cs diff --git a/Source/HotSpot/Configuration/Overlay/OnConflict.cs b/src/HotSpot/Configuration/Overlay/OnConflict.cs similarity index 100% rename from Source/HotSpot/Configuration/Overlay/OnConflict.cs rename to src/HotSpot/Configuration/Overlay/OnConflict.cs diff --git a/Source/HotSpot/Configuration/Overlay/SchemeNode.cs b/src/HotSpot/Configuration/Overlay/SchemeNode.cs similarity index 100% rename from Source/HotSpot/Configuration/Overlay/SchemeNode.cs rename to src/HotSpot/Configuration/Overlay/SchemeNode.cs diff --git a/Source/HotSpot/Configuration/Overlay/StopNode.cs b/src/HotSpot/Configuration/Overlay/StopNode.cs similarity index 100% rename from Source/HotSpot/Configuration/Overlay/StopNode.cs rename to src/HotSpot/Configuration/Overlay/StopNode.cs diff --git a/Source/HotSpot/Configuration/OverlayNode.cs b/src/HotSpot/Configuration/OverlayNode.cs similarity index 100% rename from Source/HotSpot/Configuration/OverlayNode.cs rename to src/HotSpot/Configuration/OverlayNode.cs diff --git a/Source/HotSpot/Extensions/ConfigNodeExtensions.cs b/src/HotSpot/Extensions/ConfigNodeExtensions.cs similarity index 100% rename from Source/HotSpot/Extensions/ConfigNodeExtensions.cs rename to src/HotSpot/Extensions/ConfigNodeExtensions.cs diff --git a/Source/HotSpot/Extensions/PartExtensions.cs b/src/HotSpot/Extensions/PartExtensions.cs similarity index 100% rename from Source/HotSpot/Extensions/PartExtensions.cs rename to src/HotSpot/Extensions/PartExtensions.cs diff --git a/Source/HotSpot/Extensions/StringExtensions.cs b/src/HotSpot/Extensions/StringExtensions.cs similarity index 100% rename from Source/HotSpot/Extensions/StringExtensions.cs rename to src/HotSpot/Extensions/StringExtensions.cs diff --git a/Source/HotSpot/HotSpot.csproj b/src/HotSpot/HotSpot.csproj similarity index 81% rename from Source/HotSpot/HotSpot.csproj rename to src/HotSpot/HotSpot.csproj index 1df326a..ac06b3b 100644 --- a/Source/HotSpot/HotSpot.csproj +++ b/src/HotSpot/HotSpot.csproj @@ -12,8 +12,8 @@ v3.5 512 - ..\..\Output\Build\$(Configuration)\Common\bin\ - ..\..\Output\Build\$(Configuration)\Common\obj\ + ..\..\.build\out\$(AssemblyName)\$(Configuration)\bin\ + ..\..\.build\out\$(AssemblyName)\$(Configuration)\obj\ true @@ -34,29 +34,35 @@ - ..\..\Library\KSP\Assembly-CSharp.dll + ..\..\.build\lib\ksp\Assembly-CSharp.dll False - ..\..\Library\KSP\Assembly-CSharp-firstpass.dll + ..\..\.build\lib\ksp\Assembly-CSharp-firstpass.dll False - ..\..\Library\KSP\KSPUtil.dll + ..\..\.build\lib\ksp\KSPUtil.dll False - ..\..\Library\KSP\UnityEngine.dll + ..\..\.build\lib\ksp\UnityEngine.dll False - ..\..\Library\KSP\UnityEngine.UI.dll + ..\..\.build\lib\ksp\UnityEngine.UI.dll False + + Properties\GlobalAssemblyKspVersionInfo.cs + + + Properties\GlobalAssemblyVersionInfo.cs + diff --git a/Source/HotSpot/HotSpot.csproj.DotSettings b/src/HotSpot/HotSpot.csproj.DotSettings similarity index 100% rename from Source/HotSpot/HotSpot.csproj.DotSettings rename to src/HotSpot/HotSpot.csproj.DotSettings diff --git a/Source/HotSpot/HotSpotBehavior.cs b/src/HotSpot/HotSpotBehavior.cs similarity index 100% rename from Source/HotSpot/HotSpotBehavior.cs rename to src/HotSpot/HotSpotBehavior.cs diff --git a/Source/HotSpot/HotSpotGuiBehavior.cs b/src/HotSpot/HotSpotGuiBehavior.cs similarity index 100% rename from Source/HotSpot/HotSpotGuiBehavior.cs rename to src/HotSpot/HotSpotGuiBehavior.cs diff --git a/Source/HotSpot/HotSpotModule.cs b/src/HotSpot/HotSpotModule.cs similarity index 100% rename from Source/HotSpot/HotSpotModule.cs rename to src/HotSpot/HotSpotModule.cs diff --git a/Source/HotSpot/Log.cs b/src/HotSpot/Log.cs similarity index 100% rename from Source/HotSpot/Log.cs rename to src/HotSpot/Log.cs diff --git a/Source/HotSpot/Model/EvaluatedGradientNode.cs b/src/HotSpot/Model/EvaluatedGradientNode.cs similarity index 100% rename from Source/HotSpot/Model/EvaluatedGradientNode.cs rename to src/HotSpot/Model/EvaluatedGradientNode.cs diff --git a/Source/HotSpot/Model/EvaluatedStopNode.cs b/src/HotSpot/Model/EvaluatedStopNode.cs similarity index 100% rename from Source/HotSpot/Model/EvaluatedStopNode.cs rename to src/HotSpot/Model/EvaluatedStopNode.cs diff --git a/Source/HotSpot/Model/Expression.cs b/src/HotSpot/Model/Expression.cs similarity index 100% rename from Source/HotSpot/Model/Expression.cs rename to src/HotSpot/Model/Expression.cs diff --git a/Source/HotSpot/Model/Metric.cs b/src/HotSpot/Model/Metric.cs similarity index 100% rename from Source/HotSpot/Model/Metric.cs rename to src/HotSpot/Model/Metric.cs diff --git a/Source/HotSpot/Model/Unit.cs b/src/HotSpot/Model/Unit.cs similarity index 100% rename from Source/HotSpot/Model/Unit.cs rename to src/HotSpot/Model/Unit.cs diff --git a/Source/HotSpot/Model/Variable.cs b/src/HotSpot/Model/Variable.cs similarity index 100% rename from Source/HotSpot/Model/Variable.cs rename to src/HotSpot/Model/Variable.cs diff --git a/src/HotSpot/Properties/AssemblyInfo.cs b/src/HotSpot/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..20fb996 --- /dev/null +++ b/src/HotSpot/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: AssemblyTitle("HotSpot")] +[assembly: AssemblyDescription("Kerbal Space Program mod to add contextual and overlay data.")] +[assembly: InternalsVisibleTo("HotSpotTests")] diff --git a/Source/HotSpot/Reflection/FlightOverlaysExtensions.cs b/src/HotSpot/Reflection/FlightOverlaysExtensions.cs similarity index 100% rename from Source/HotSpot/Reflection/FlightOverlaysExtensions.cs rename to src/HotSpot/Reflection/FlightOverlaysExtensions.cs diff --git a/Source/HotSpot/Reflection/PartExtensions.cs b/src/HotSpot/Reflection/PartExtensions.cs similarity index 100% rename from Source/HotSpot/Reflection/PartExtensions.cs rename to src/HotSpot/Reflection/PartExtensions.cs diff --git a/Tests/HotSpotTests/HotSpotTests.csproj b/src/HotSpotTests/HotSpotTests.csproj similarity index 60% rename from Tests/HotSpotTests/HotSpotTests.csproj rename to src/HotSpotTests/HotSpotTests.csproj index cb3befe..c097c82 100644 --- a/Tests/HotSpotTests/HotSpotTests.csproj +++ b/src/HotSpotTests/HotSpotTests.csproj @@ -19,8 +19,8 @@ UnitTest - ..\..\Output\Build\$(Configuration)\Tests\bin\ - ..\..\Output\Build\$(Configuration)\Tests\obj\ + ..\..\.build\out\$(AssemblyName)\$(Configuration)\bin\ + ..\..\.build\out\$(AssemblyName)\$(Configuration)\obj\ true @@ -37,34 +37,6 @@ prompt 4 - - - ..\..\Library\NuGet\FluentAssertions.3.4.1\lib\net45\FluentAssertions.dll - True - - - ..\..\Library\NuGet\FluentAssertions.3.4.1\lib\net45\FluentAssertions.Core.dll - True - - - - - - ..\..\Library\KSP\UnityEngine.dll - - - ..\..\Library\NuGet\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll - True - - - ..\..\Library\NuGet\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll - True - - - ..\..\Library\NuGet\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll - True - - @@ -74,10 +46,15 @@ + + Properties\GlobalAssemblyVersionInfo.cs + - + + C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6\System.dll + @@ -99,12 +76,6 @@ - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - \ No newline at end of file diff --git a/src/HotSpotTests/Properties/AssemblyInfo.cs b/src/HotSpotTests/Properties/AssemblyInfo.cs deleted file mode 100644 index a8045c1..0000000 --- a/src/HotSpotTests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyTitle("HotSpotTests")] -[assembly: AssemblyDescription("Tests for HotSpot")] From beee2feb7b461ba53282898538e11349d9f54557 Mon Sep 17 00:00:00 2001 From: Dwayne Bent Date: Sun, 15 May 2016 06:15:31 -0400 Subject: [PATCH 05/10] Build Cleanup (#66) --- build.cake | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/build.cake b/build.cake index 9e42e1a..8612a96 100644 --- a/build.cake +++ b/build.cake @@ -45,7 +45,7 @@ public sealed class Globals } // HACK: Terrible workaround for Mono's script compiler not supporting globals like Roslyn -private Globals getGlobals() +private Globals GetGlobals() { var globals = new Globals(); @@ -92,7 +92,7 @@ Task("InitLibKsp") { const string kspLibsUrlBase = "http://build.apokee.com/dependencies/ksp/1.1.2.1260"; - var globals = getGlobals(); + var globals = GetGlobals(); CreateDirectory(globals.BuildLibKspDirectory); @@ -112,7 +112,7 @@ Task("InitLibKsp") Task("CleanStage") .Does(() => { - var globals = getGlobals(); + var globals = GetGlobals(); CleanDirectories(new [] { globals.BuildStageDirectory }); }); @@ -120,7 +120,7 @@ Task("CleanStage") Task("CleanPackage") .Does(() => { - var globals = getGlobals(); + var globals = GetGlobals(); CleanDirectories(new [] { globals.BuildPkgDirectory }); }); @@ -128,7 +128,7 @@ Task("CleanPackage") Task("CleanDeploy") .Does(() => { - var globals = getGlobals(); + var globals = GetGlobals(); CleanDirectories(new [] {globals.DeployDirectory }); }); @@ -136,7 +136,7 @@ Task("CleanDeploy") Task("Restore") .Does(() => { - var globals = getGlobals(); + var globals = GetGlobals(); NuGetRestore(globals.Solution); }); @@ -145,7 +145,7 @@ Task("BuildMeta") .IsDependentOn("BuildAssemblyInfo") .Does(() => { - var globals = getGlobals(); + var globals = GetGlobals(); CreateDirectory(globals.BuildMetaDirectory); @@ -170,7 +170,7 @@ Task("BuildAssemblyInfo") Task("BuildGlobalAssemblyVersionInfo") .Does(() => { - var globals = getGlobals(); + var globals = GetGlobals(); CreateDirectory(globals.BuildMetaDirectory); @@ -185,7 +185,7 @@ Task("BuildGlobalAssemblyVersionInfo") Task("BuildGlobalKspAssemblyVersionInfo") .Does(() => { - var globals = getGlobals(); + var globals = GetGlobals(); CreateDirectory(globals.BuildMetaDirectory); @@ -213,7 +213,7 @@ Task("Build") .IsDependentOn("BuildMeta") .Does(() => { - var globals = getGlobals(); + var globals = GetGlobals(); DotNetBuild(globals.Solution, s => { s.Configuration = globals.Configuration; }); }); @@ -224,7 +224,7 @@ Task("Stage") .IsDependentOn("Build") .Does(() => { - var globals = getGlobals(); + var globals = GetGlobals(); var artworkDirectory = GetNuGetPackageDirectory("Apokee.Artwork").Combine("Content"); var binDirectory = globals @@ -268,7 +268,7 @@ Task("Deploy") .IsDependentOn("CleanDeploy") .Does(() => { - var globals = getGlobals(); + var globals = GetGlobals(); CopyDirectory(globals.BuildStageGameDataModDirectory, globals.DeployDirectory); }); @@ -277,7 +277,7 @@ Task("Run") .IsDependentOn("Deploy") .Does(() => { - var globals = getGlobals(); + var globals = GetGlobals(); StartAndReturnProcess(globals.BuildConfiguration.KspPath(globals.BuildConfiguration.KspBin), new ProcessSettings { @@ -290,7 +290,7 @@ Task("Package") .IsDependentOn("Stage") .Does(() => { - var globals = getGlobals(); + var globals = GetGlobals(); CreateDirectory(globals.BuildPkgDirectory); @@ -314,7 +314,7 @@ Task("ChangeLog") Information(GetChangeLog().LatestChanges); }); -RunTarget(getGlobals().Target); +RunTarget(GetGlobals().Target); public SemVer GetBuildVersion() { @@ -342,7 +342,7 @@ public SemVer GetBuildVersion() private DirectoryPath GetNuGetPackageDirectory(string package) { - var globals = getGlobals(); + var globals = GetGlobals(); return GetDirectories(string.Format("{0}/*", globals.BuildLibNugetDirectory)) .Where(i => i.GetDirectoryName().StartsWith(package)) From e14026b784081c6c023615e44cedd7ed138b1420 Mon Sep 17 00:00:00 2001 From: Dwayne Bent Date: Sun, 15 May 2016 10:55:22 -0400 Subject: [PATCH 06/10] Add support for core temperatures (#67) Fixes #45 --- CHANGELOG.md | 17 +- README.md | 13 ++ src/GameData/Configuration/HotSpot.cfg | 134 +++++++++++ .../Configuration/Gui/ContextMenuNode.cs | 31 +++ src/HotSpot/Configuration/GuiNode.cs | 10 +- src/HotSpot/HotSpot.csproj | 1 + src/HotSpot/HotSpotBehavior.cs | 33 ++- src/HotSpot/HotSpotModule.cs | 54 ++++- src/HotSpot/Model/Metric.cs | 216 +++++++++++------- src/HotSpot/Model/Variable.cs | 1 + 10 files changed, 403 insertions(+), 107 deletions(-) create mode 100644 src/HotSpot/Configuration/Gui/ContextMenuNode.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index b5292b2..f849113 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ -## v0.5.2-alpha -##### Fixes +## v0.6.0-alpha +##### Added +- Added core temperature as a new metric for applicable parts. +- Disable stock core temperature display in the part context menu by default. +- Added ideal metric values for applicable metrics (only core temperature as of now). +- Added "Part Ideal" overlay scheme for the core temperature metric which is green when around ideal temperature, + purple and blue when below ideal temperature, and yellow and red when above ideal temperature. + +##### Changed +- Changed temperature formatting: + - Unit symbol is only printed at the very end. + - Only one decimal place is shown instead of two. + - If there is an ideal temperature it is displayed between the current and maximum temperatures. + +##### Fixed - "Scheme:" label no longer occupies multiple lines in the GUI. ## v0.5.1 diff --git a/README.md b/README.md index 765dbf9..6ba6b69 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,19 @@ The *Maximum* value depends on the scheme used: - *Vessel Current* - The maximum current temperature of any part in the vessel. - *Vessel Absolute* - The maximum temperature of any part in the vessel. +#### Ideal Temperature + +The color gradient used for ideal temperatures is as follows: + +- Purple (0K) +- Blue (0.8×*Ideal*) +- Green (*Ideal*) +- Yellow (1.2×*Ideal*) +- Red (*Maximum*) + +Where *Ideal* is the part's "ideal" temperature, e.g. for resource converters its the temperature at which conversion +is maximally efficient, and *Maximum* is the maximum temperature for the current part. + #### Thermal Rate The color gradient used for thermal rates is as follows: diff --git a/src/GameData/Configuration/HotSpot.cfg b/src/GameData/Configuration/HotSpot.cfg index 2bbacda..2e2c00d 100644 --- a/src/GameData/Configuration/HotSpot.cfg +++ b/src/GameData/Configuration/HotSpot.cfg @@ -13,6 +13,11 @@ HOT_SPOT enable = auto texture = HotSpot/Textures/Toolbar } + + CONTEXT_MENU + { + disableStockCoreTemp = true + } } CONTEXT_MENU @@ -100,6 +105,75 @@ HOT_SPOT } } } + + SCHEME + { + name = TemperatureIdealTemplate + + GRADIENT + { + name = LessThanIdeal + max = PartIdeal + onConflict = RemoveEarlier + + STOP + { + name = Minimumm + value = 0 + color = #5c3566 + alpha = 1.0 + } + + STOP + { + name = LessThanIdeal + value = PartIdeal + factor = 0.8 + color = #204a87 + alpha = 1.0 + } + + STOP + { + name = Ideal + value = PartIdeal + color = #73d216 + alpha = 1.0 + } + } + + GRADIENT + { + name = MoreThanIdeal + min = PartIdeal + onConflict = RemoveLater + + STOP + { + name = Ideal + value = PartIdeal + color = #73d216 + alpha = 1.0 + } + + STOP + { + name = MoreThanIdeal + value = PartIdeal + factor = 1.2 + color = #fce94f + alpha = 1.0 + } + + STOP + { + name = Maximum + value = PartAbsoluteMaximum + color = #a40000 + alpha = 1.0 + } + } + } } METRIC @@ -207,6 +281,11 @@ HOT_SPOT @name = TemperatureSkin } + +METRIC[TemperatureTemplate] + { + @name = TemperatureCore + } + +METRIC[ThermalRateTemplate] { @name = ThermalRate @@ -315,6 +394,61 @@ HOT_SPOT } } + +METRIC[TemperatureTemplate] + { + @name = TemperatureCore + %scheme = TemperaturePartAbsolute + + +SCHEME[TemperatureTemplate] + { + @name = TemperaturePartAbsolute + %friendlyName = Part Absolute + + @GRADIENT + { + %max = PartAbsoluteMaximum + } + } + + +SCHEME[TemperatureTemplate] + { + @name = TemperatureVesselCurrent + %friendlyName = Vessel Current + + @GRADIENT + { + %max = VesselCurrentMaximum + } + } + + +SCHEME[TemperatureTemplate] + { + @name = TemperatureVesselAbsolute + %friendlyName = Vessel Absolute + + @GRADIENT + { + %max = VesselAbsoluteMaximum + } + } + + +SCHEME[TemperatureIdealTemplate] + { + @name = TemperaturePartIdeal + %friendlyName = Part Ideal + + @GRADIENT[LessThanIdeal] + { + %min = PartAbsoluteMinimum + } + + @GRADIENT[MoreThanIdeal] + { + %max = PartAbsoluteMaximum + } + } + } + +METRIC[ThermalRateTemplate] { @name = ThermalRate diff --git a/src/HotSpot/Configuration/Gui/ContextMenuNode.cs b/src/HotSpot/Configuration/Gui/ContextMenuNode.cs new file mode 100644 index 0000000..b53270f --- /dev/null +++ b/src/HotSpot/Configuration/Gui/ContextMenuNode.cs @@ -0,0 +1,31 @@ +namespace HotSpot.Configuration.Gui +{ + internal sealed class ContextMenuNode + { + public bool DisableStockCoreTemp { get; } + + public ContextMenuNode(bool disableStockCoreTemp) + { + DisableStockCoreTemp = disableStockCoreTemp; + } + + public bool Save(ConfigNode node) + => false; + + public static ContextMenuNode GetDefault() + => new ContextMenuNode(true); + + public static ContextMenuNode TryParse(ConfigNode node) + { + if (node != null) + { + var disableStockCoreTemp = node.TryParse("disableStockCoreTemp") ?? true; + + return new ContextMenuNode(disableStockCoreTemp); + } + + Log.Warning("Could not parse missing CONTEXT_MENU node"); + return null; + } + } +} diff --git a/src/HotSpot/Configuration/GuiNode.cs b/src/HotSpot/Configuration/GuiNode.cs index 666183f..999bb61 100644 --- a/src/HotSpot/Configuration/GuiNode.cs +++ b/src/HotSpot/Configuration/GuiNode.cs @@ -6,18 +6,20 @@ internal sealed class GuiNode { public AppLauncherNode AppLauncher { get; } public ToolbarNode Toolbar { get; } + public Gui.ContextMenuNode ContextMenu { get; } - private GuiNode(AppLauncherNode appLauncher, ToolbarNode toolbar) + private GuiNode(AppLauncherNode appLauncher, ToolbarNode toolbar, Gui.ContextMenuNode contextMenu) { AppLauncher = appLauncher; Toolbar = toolbar; + ContextMenu = contextMenu; } public bool Save(ConfigNode node) => false; public static GuiNode GetDefault() - => new GuiNode(AppLauncherNode.GetDefault(), ToolbarNode.GetDefault()); + => new GuiNode(AppLauncherNode.GetDefault(), ToolbarNode.GetDefault(), Gui.ContextMenuNode.GetDefault()); public static GuiNode TryParse(ConfigNode node) { @@ -27,8 +29,10 @@ public static GuiNode TryParse(ConfigNode node) ?? AppLauncherNode.GetDefault(); var toolbar = ToolbarNode.TryParse(node.GetNode("TOOLBAR")) ?? ToolbarNode.GetDefault(); + var contextMenu = Gui.ContextMenuNode.TryParse(node.GetNode("CONTEXT_MENU")) + ?? Gui.ContextMenuNode.GetDefault(); - return new GuiNode(appLauncher, toolbar); + return new GuiNode(appLauncher, toolbar, contextMenu); } Log.Warning("Could not parse missing GUI node"); diff --git a/src/HotSpot/HotSpot.csproj b/src/HotSpot/HotSpot.csproj index ac06b3b..5161afe 100644 --- a/src/HotSpot/HotSpot.csproj +++ b/src/HotSpot/HotSpot.csproj @@ -68,6 +68,7 @@ + diff --git a/src/HotSpot/HotSpotBehavior.cs b/src/HotSpot/HotSpotBehavior.cs index 8b6e9e4..62c99c0 100644 --- a/src/HotSpot/HotSpotBehavior.cs +++ b/src/HotSpot/HotSpotBehavior.cs @@ -35,15 +35,30 @@ private static void LateUpdateColor() foreach (var part in vessel.Parts) { - var partVariables = metric.GetPartValues(part); - var partCurrent = metric.GetPartCurrent(part); - - var color = Config - .Instance - .Overlay - .GetActiveMetric() - .GetActiveScheme() - .EvaluateColor(partCurrent, MergeVariables(vesselVariables, partVariables)); + Color? color = null; + + if (metric.IsApplicable(part)) + { + var partVariables = metric.GetPartValues(part); + var partCurrent = metric.GetPartCurrent(part); + + if (partCurrent != null) + { + color = Config + .Instance + .Overlay + .GetActiveMetric() + .GetActiveScheme() + .EvaluateColor(partCurrent.Value, MergeVariables(vesselVariables, partVariables)); + } + else + { + Log.Warning( + $"Received null value for for applicable metric `{metric.Name}` on part " + + $"`{part.name}`." + ); + } + } part.TryGetMaterialColorUpdater()?.Update(color ?? Part.defaultHighlightNone); } diff --git a/src/HotSpot/HotSpotModule.cs b/src/HotSpot/HotSpotModule.cs index 3e3cd5f..f15d546 100644 --- a/src/HotSpot/HotSpotModule.cs +++ b/src/HotSpot/HotSpotModule.cs @@ -8,6 +8,9 @@ public sealed class HotSpotModule : PartModule [KSPField(guiActive = false)] public string TemperatureSkin; + [KSPField(guiActive = false)] + public string TemperatureCore; + [KSPField(guiActive = false)] public string ThermalRate; @@ -23,21 +26,56 @@ public sealed class HotSpotModule : PartModule [KSPField(guiActive = false)] public string ThermalRateRadiative; + public override void OnStart(StartState state) + { + base.OnStart(state); + + if (Config.Instance.Gui.ContextMenu.DisableStockCoreTemp) + DisableStockCoreTempDisplay(); + } + public override void OnUpdate() { - if (HighLogic.LoadedSceneIsFlight) + if (!HighLogic.LoadedSceneIsFlight) return; + + foreach (var metricNode in Config.Instance.ContextMenu.Metrics) { - foreach (var metricNode in Config.Instance.ContextMenu.Metrics) + var metric = metricNode.Name; + + if (metric.IsApplicable(part)) { - var metric = metricNode.Name; - var field = Fields[metric.Name]; + var value = metric.GetPartCurrentString(part, metricNode.Unit); - field.guiName = metric.ShortFriendlyName; - field.guiActive = metricNode.Enable; + if (value != null) + { + var field = Fields[metric.Name]; - var value = metric.GetPartCurrentString(part, metricNode.Unit); + field.guiName = metric.ShortFriendlyName; + field.guiActive = metricNode.Enable; - field.SetValue(value, this); + field.SetValue(value, this); + } + else + { + Log.Warning( + $"Received null value for for applicable metric `{metric.Name}` on part `{part.name}`." + ); + } + } + } + } + + private void DisableStockCoreTempDisplay() + { + var overheatDisplayModule = part.FindModuleImplementing(); + + if (overheatDisplayModule != null) + { + var coreTempDisplayField = overheatDisplayModule.Fields["coreTempDisplay"]; + + if (coreTempDisplayField != null) + { + coreTempDisplayField.guiActive = false; } } } diff --git a/src/HotSpot/Model/Metric.cs b/src/HotSpot/Model/Metric.cs index bbf6163..fdf0b89 100644 --- a/src/HotSpot/Model/Metric.cs +++ b/src/HotSpot/Model/Metric.cs @@ -8,9 +8,10 @@ namespace HotSpot.Model internal sealed class Metric { public static readonly Metric TemperatureInternal = new Metric("TemperatureInternal", - "Temp. [I]", + "Temp [I]", "Internal Temperature", new[] { Unit.Kelvin, Unit.Celsius, Unit.Rankine, Unit.Fahrenheit }, + part => true, vessel => new Dictionary { [Variable.VesselCurrentMinimum] = vessel.Parts.Min(i => i.temperature), @@ -24,46 +25,14 @@ internal sealed class Metric [Variable.PartAbsoluteMaximum] = part.maxTemp }, part => part.temperature, - (part, unit) => - { - double temp; - double maxTemp; - string unitSymbol; - - switch (unit) - { - case Unit.Kelvin: - temp = part.temperature; - maxTemp = part.maxTemp; - unitSymbol = "K"; - break; - case Unit.Rankine: - temp = ConvertKelvinToRankine(part.temperature); - maxTemp = ConvertKelvinToRankine(part.maxTemp); - unitSymbol = "°R"; - break; - case Unit.Celsius: - temp = ConvertKelvinToCelsius(part.temperature); - maxTemp = ConvertKelvinToCelsius(part.maxTemp); - unitSymbol = "°C"; - break; - case Unit.Fahrenheit: - temp = ConvertKelvinToFahrenheit(part.temperature); - maxTemp = ConvertKelvinToFahrenheit(part.maxTemp); - unitSymbol = "°F"; - break; - default: - throw new ArgumentOutOfRangeException(); - } - - return $"{temp:F2}{unitSymbol} / {maxTemp:F2}{unitSymbol}"; - } + (part, unit) => TemperatureToString(part.temperature, part.maxTemp, null, unit) ); public static readonly Metric TemperatureSkin = new Metric("TemperatureSkin", - "Temp. [S]", + "Temp [S]", "Skin Temperature", new[] { Unit.Kelvin, Unit.Celsius, Unit.Rankine, Unit.Fahrenheit }, + part => true, vessel => new Dictionary { [Variable.VesselCurrentMinimum] = vessel.Parts.Min(i => i.skinTemperature), @@ -77,39 +46,68 @@ internal sealed class Metric [Variable.PartAbsoluteMaximum] = part.skinMaxTemp }, part => part.skinTemperature, - (part, unit) => + (part, unit) => TemperatureToString(part.skinTemperature, part.skinMaxTemp, null, unit) + ); + + public static readonly Metric TemperatureCore = new Metric("TemperatureCore", + "Temp [C]", + "Core Temperature", + new[] { Unit.Kelvin, Unit.Celsius, Unit.Rankine, Unit.Fahrenheit }, + part => part.FindModuleImplementing() != null, + vessel => + { + var coreHeatModules = vessel + .Parts + .Select(i => i.FindModuleImplementing()) + .Where(i => i != null) + .ToArray(); + + if (coreHeatModules.Any()) + { + return new Dictionary + { + [Variable.VesselCurrentMinimum] = coreHeatModules.Min(i => i.CoreTemperature), + [Variable.VesselCurrentMaximum] = coreHeatModules.Max(i => i.CoreTemperature), + [Variable.VesselAbsoluteMinimum] = 0, + [Variable.VesselAbsoluteMaximum] = coreHeatModules.Max(i => i.CoreShutdownTemp) + }; + } + else + { + return null; + } + }, + part => { - double temp; - double maxTemp; - string unitSymbol; + var coreHeatModule = part.FindModuleImplementing(); - switch (unit) + if (coreHeatModule != null) { - case Unit.Kelvin: - temp = part.skinTemperature; - maxTemp = part.skinMaxTemp; - unitSymbol = "K"; - break; - case Unit.Rankine: - temp = ConvertKelvinToRankine(part.skinTemperature); - maxTemp = ConvertKelvinToRankine(part.skinMaxTemp); - unitSymbol = "°R"; - break; - case Unit.Celsius: - temp = ConvertKelvinToCelsius(part.skinTemperature); - maxTemp = ConvertKelvinToCelsius(part.skinMaxTemp); - unitSymbol = "°C"; - break; - case Unit.Fahrenheit: - temp = ConvertKelvinToFahrenheit(part.skinTemperature); - maxTemp = ConvertKelvinToFahrenheit(part.skinMaxTemp); - unitSymbol = "°F"; - break; - default: - throw new ArgumentOutOfRangeException(); + return new Dictionary + { + [Variable.PartAbsoluteMinimum] = 0, + [Variable.PartAbsoluteMaximum] = coreHeatModule.CoreShutdownTemp, + [Variable.PartIdeal] = coreHeatModule.CoreTempGoal + }; } + else + { + return null; + } + }, + part => part.FindModuleImplementing()?.CoreTemperature, + (part, unit) => + { + var coreHeatModule = part.FindModuleImplementing(); - return $"{temp:F2}{unitSymbol} / {maxTemp:F2}{unitSymbol}"; + return coreHeatModule != null + ? TemperatureToString( + coreHeatModule.CoreTemperature, + coreHeatModule.CoreShutdownTemp, + coreHeatModule.CoreTempGoal, + unit + ) + : null; } ); @@ -117,6 +115,7 @@ internal sealed class Metric "Thermal Rate", "Thermal Rate", new[] { Unit.Kilowatt }, + part => true, vessel => new Dictionary { [Variable.VesselCurrentMinimum] = vessel.Parts.Min(i => i.GetThermalFlux()), @@ -131,6 +130,7 @@ internal sealed class Metric "Thermal Rate [I]", "Internal Thermal Rate", new[] { Unit.Kilowatt }, + part => true, vessel => new Dictionary { [Variable.VesselCurrentMinimum] = vessel.Parts.Min(i => i.thermalInternalFluxPrevious), @@ -145,6 +145,7 @@ internal sealed class Metric "Thermal Rate [Cd]", "Conductive Thermal Rate", new[] { Unit.Kilowatt }, + part => true, vessel => new Dictionary { [Variable.VesselCurrentMinimum] = vessel.Parts.Min(i => i.thermalConductionFlux), @@ -159,6 +160,7 @@ internal sealed class Metric "Thermal Rate [Cv]", "Convective Thermal Rate", new[] { Unit.Kilowatt }, + part => true, vessel => new Dictionary { [Variable.VesselCurrentMinimum] = vessel.Parts.Min(i => i.thermalConvectionFlux), @@ -173,6 +175,7 @@ internal sealed class Metric "Thermal Rate [R]", "Radiative Thermal Rate", new[] { Unit.Kilowatt }, + part => true, vessel => new Dictionary { [Variable.VesselCurrentMinimum] = vessel.Parts.Min(i => i.thermalRadiationFlux), @@ -183,9 +186,10 @@ internal sealed class Metric (part, unit) => $"{part.thermalRadiationFlux:F2}kW" ); + private readonly Func _isApplicable; private readonly Func> _getVesselValues; private readonly Func> _getPartValues; - private readonly Func _getPartCurrent; + private readonly Func _getPartCurrent; private readonly Func _getPartCurrentString; public string Name { get; } @@ -194,9 +198,10 @@ internal sealed class Metric public Unit[] Units { get; } private Metric(string name, string shortFriendlyName, string longFriendlyName, Unit[] units, + Func isApplicable, Func> getVesselValues, Func> getPartValues, - Func getPartCurrent, + Func getPartCurrent, Func getPartCurrentString ) { @@ -204,31 +209,18 @@ Func getPartCurrentString ShortFriendlyName = shortFriendlyName; LongFriendlyName = longFriendlyName; Units = units; + _isApplicable = isApplicable; _getVesselValues = getVesselValues; _getPartValues = getPartValues; _getPartCurrent = getPartCurrent; _getPartCurrentString = getPartCurrentString; } - public Dictionary GetVesselValues(Vessel vessel) - { - return _getVesselValues(vessel); - } - - public Dictionary GetPartValues(Part part) - { - return _getPartValues(part); - } - - public double GetPartCurrent(Part part) - { - return _getPartCurrent(part); - } - - public string GetPartCurrentString(Part part, Unit unit) - { - return _getPartCurrentString(part, unit); - } + public bool IsApplicable(Part part) => _isApplicable(part); + public Dictionary GetVesselValues(Vessel vessel) => _getVesselValues(vessel); + public Dictionary GetPartValues(Part part) => _getPartValues(part); + public double? GetPartCurrent(Part part) => _getPartCurrent(part); + public string GetPartCurrentString(Part part, Unit unit) => _getPartCurrentString(part, unit); public static Metric Parse(string s) { @@ -271,6 +263,60 @@ private static double ConvertKelvinToFahrenheit(double temp) return temp * (9.0 / 5.0) - 459.67; } + private static string TemperatureToString( + double tempKelvin, + double maxTempKelvin, + double? idealTempKelvin, + Unit unit + ) + { + double temp; + double maxTemp; + double? idealTemp = null; + string unitSymbol; + + switch (unit) + { + case Unit.Kelvin: + temp = tempKelvin; + maxTemp = maxTempKelvin; + if (idealTempKelvin != null) idealTemp = idealTempKelvin; + unitSymbol = "K"; + break; + case Unit.Rankine: + temp = ConvertKelvinToRankine(tempKelvin); + maxTemp = ConvertKelvinToRankine(maxTempKelvin); + if (idealTempKelvin != null) idealTemp = ConvertKelvinToRankine(idealTempKelvin.Value); + unitSymbol = "°R"; + break; + case Unit.Celsius: + temp = ConvertKelvinToCelsius(tempKelvin); + maxTemp = ConvertKelvinToCelsius(maxTempKelvin); + if (idealTempKelvin != null) idealTemp = ConvertKelvinToCelsius(idealTempKelvin.Value); + unitSymbol = "°C"; + break; + case Unit.Fahrenheit: + temp = ConvertKelvinToFahrenheit(tempKelvin); + maxTemp = ConvertKelvinToFahrenheit(maxTempKelvin); + if (idealTempKelvin != null) idealTemp = ConvertKelvinToFahrenheit(idealTempKelvin.Value); + unitSymbol = "°F"; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + var result = $"{temp:F1}"; + + if (idealTemp != null) + { + result += $" / {idealTemp.Value:F1}"; + } + + result += $" / {maxTemp:F1} {unitSymbol}"; + + return result; + } + #endregion } } diff --git a/src/HotSpot/Model/Variable.cs b/src/HotSpot/Model/Variable.cs index 53a6a8e..b704d1c 100644 --- a/src/HotSpot/Model/Variable.cs +++ b/src/HotSpot/Model/Variable.cs @@ -4,6 +4,7 @@ public enum Variable { PartAbsoluteMinimum, PartAbsoluteMaximum, + PartIdeal, VesselCurrentMinimum, VesselCurrentMaximum, VesselAbsoluteMinimum, From f4e2c604ae2eb2857fb4bfb46535aec65914ecbc Mon Sep 17 00:00:00 2001 From: Dwayne Bent Date: Sun, 15 May 2016 12:30:57 -0400 Subject: [PATCH 07/10] Add Skin-Internal Metrics (#68) Fixes #47 --- CHANGELOG.md | 1 + src/GameData/Configuration/HotSpot.cfg | 52 ++++++++++++++++++++++++++ src/HotSpot/HotSpotModule.cs | 36 +++++++++++------- src/HotSpot/Model/Metric.cs | 30 +++++++++++++++ 4 files changed, 105 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f849113..0409e33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## v0.6.0-alpha ##### Added - Added core temperature as a new metric for applicable parts. +- Added skin to internal and internal to skin thermal rate as new metrics. - Disable stock core temperature display in the part context menu by default. - Added ideal metric values for applicable metrics (only core temperature as of now). - Added "Part Ideal" overlay scheme for the core temperature metric which is green when around ideal temperature, diff --git a/src/GameData/Configuration/HotSpot.cfg b/src/GameData/Configuration/HotSpot.cfg index 2e2c00d..3ee2b65 100644 --- a/src/GameData/Configuration/HotSpot.cfg +++ b/src/GameData/Configuration/HotSpot.cfg @@ -310,6 +310,16 @@ HOT_SPOT { @name = ThermalRateRadiative } + + +METRIC[ThermalRateTemplate] + { + @name = ThermalRateSkinToInternal + } + + +METRIC[ThermalRateTemplate] + { + @name = ThermalRateInternalToSkin + } } @OVERLAY @@ -553,5 +563,47 @@ HOT_SPOT } } } + + +METRIC[ThermalRateTemplate] + { + @name = ThermalRateSkinToInternal + %scheme = ThermalRateVesselCurrent + + +SCHEME[ThermalRateTemplate] + { + @name = ThermalRateVesselCurrent + + @GRADIENT[Negative] + { + %min = VesselCurrentMinimum + } + + @GRADIENT[Positive] + { + %max = VesselCurrentMaximum + } + } + } + + +METRIC[ThermalRateTemplate] + { + @name = ThermalRateInternalToSkin + %scheme = ThermalRateVesselCurrent + + +SCHEME[ThermalRateTemplate] + { + @name = ThermalRateVesselCurrent + + @GRADIENT[Negative] + { + %min = VesselCurrentMinimum + } + + @GRADIENT[Positive] + { + %max = VesselCurrentMaximum + } + } + } } } diff --git a/src/HotSpot/HotSpotModule.cs b/src/HotSpot/HotSpotModule.cs index f15d546..972de29 100644 --- a/src/HotSpot/HotSpotModule.cs +++ b/src/HotSpot/HotSpotModule.cs @@ -26,6 +26,12 @@ public sealed class HotSpotModule : PartModule [KSPField(guiActive = false)] public string ThermalRateRadiative; + [KSPField(guiActive = false)] + public string ThermalRateSkinToInternal; + + [KSPField(guiActive = false)] + public string ThermalRateInternalToSkin; + public override void OnStart(StartState state) { base.OnStart(state); @@ -50,10 +56,17 @@ public override void OnUpdate() { var field = Fields[metric.Name]; - field.guiName = metric.ShortFriendlyName; - field.guiActive = metricNode.Enable; - - field.SetValue(value, this); + if (field != null) + { + field.guiName = metric.ShortFriendlyName; + field.guiActive = metricNode.Enable; + + field.SetValue(value, this); + } + else + { + Log.Warning($"Could not find field for metric `{metric.Name}`."); + } } else { @@ -67,17 +80,12 @@ public override void OnUpdate() private void DisableStockCoreTempDisplay() { - var overheatDisplayModule = part.FindModuleImplementing(); + var coreTempDisplayField = part + .FindModuleImplementing() + ?.Fields["coreTempDisplay"]; - if (overheatDisplayModule != null) - { - var coreTempDisplayField = overheatDisplayModule.Fields["coreTempDisplay"]; - - if (coreTempDisplayField != null) - { - coreTempDisplayField.guiActive = false; - } - } + if (coreTempDisplayField != null) + coreTempDisplayField.guiActive = false; } } } diff --git a/src/HotSpot/Model/Metric.cs b/src/HotSpot/Model/Metric.cs index fdf0b89..d3db13f 100644 --- a/src/HotSpot/Model/Metric.cs +++ b/src/HotSpot/Model/Metric.cs @@ -186,6 +186,36 @@ internal sealed class Metric (part, unit) => $"{part.thermalRadiationFlux:F2}kW" ); + public static readonly Metric ThermalRateSkinToInternal = new Metric("ThermalRateSkinToInternal", + "Thermal Rate [S-I]", + "Skin to Internal Thermal Rate", + new[] { Unit.Kilowatt }, + part => true, + vessel => new Dictionary + { + [Variable.VesselCurrentMinimum] = vessel.Parts.Min(i => i.skinToInternalFlux), + [Variable.VesselCurrentMaximum] = vessel.Parts.Max(i => i.skinToInternalFlux) + }, + part => new Dictionary(), + part => part.skinToInternalFlux, + (part, unit) => $"{part.skinToInternalFlux:F2}kW" + ); + + public static readonly Metric ThermalRateInternalToSkin = new Metric("ThermalRateInternalToSkin", + "Thermal Rate [I-S]", + "Internal to Skin Thermal Rate", + new[] { Unit.Kilowatt }, + part => true, + vessel => new Dictionary + { + [Variable.VesselCurrentMinimum] = vessel.Parts.Min(i => -i.skinToInternalFlux), + [Variable.VesselCurrentMaximum] = vessel.Parts.Max(i => -i.skinToInternalFlux) + }, + part => new Dictionary(), + part => -part.skinToInternalFlux, + (part, unit) => $"{-part.skinToInternalFlux:F2}kW" + ); + private readonly Func _isApplicable; private readonly Func> _getVesselValues; private readonly Func> _getPartValues; From 458bef5d276ffa1a1533e3855caecc11808d7249 Mon Sep 17 00:00:00 2001 From: Dwayne Bent Date: Sun, 15 May 2016 16:09:07 -0400 Subject: [PATCH 08/10] Automatic Prefix Selection (#69) Fixes #49 --- CHANGELOG.md | 5 + src/GameData/Configuration/HotSpot.cfg | 3 +- .../Configuration/ContextMenu/MetricNode.cs | 20 +++- src/HotSpot/Extensions/DoubleExtensions.cs | 91 +++++++++++++++++++ src/HotSpot/HotSpot.csproj | 2 + src/HotSpot/HotSpotGuiBehavior.cs | 85 ++++++++++++++++- src/HotSpot/HotSpotModule.cs | 2 +- src/HotSpot/Model/Metric.cs | 72 ++++++++++----- src/HotSpot/Model/Prefix.cs | 27 ++++++ src/HotSpot/Model/Unit.cs | 3 +- 10 files changed, 275 insertions(+), 35 deletions(-) create mode 100644 src/HotSpot/Extensions/DoubleExtensions.cs create mode 100644 src/HotSpot/Model/Prefix.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0409e33..eabf0b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added ideal metric values for applicable metrics (only core temperature as of now). - Added "Part Ideal" overlay scheme for the core temperature metric which is green when around ideal temperature, purple and blue when below ideal temperature, and yellow and red when above ideal temperature. +- Added SI prefix selection for thermal rate metrics. By default, prefix is selected automatically. ##### Changed - Changed temperature formatting: @@ -13,6 +14,10 @@ - Only one decimal place is shown instead of two. - If there is an ideal temperature it is displayed between the current and maximum temperatures. +##### Deprecated +- Unit has changed from `Kilowatt` to `Watt` to more clearly identify what it represents now that prefixes can be + changed. `Kilowatt` will automatically be translated to `Watt` for now. + ##### Fixed - "Scheme:" label no longer occupies multiple lines in the GUI. diff --git a/src/GameData/Configuration/HotSpot.cfg b/src/GameData/Configuration/HotSpot.cfg index 3ee2b65..80cec7f 100644 --- a/src/GameData/Configuration/HotSpot.cfg +++ b/src/GameData/Configuration/HotSpot.cfg @@ -33,7 +33,8 @@ HOT_SPOT { name = ThermalRateTemplate enable = true - unit = Kilowatt + unit = Watt + prefix = auto } } diff --git a/src/HotSpot/Configuration/ContextMenu/MetricNode.cs b/src/HotSpot/Configuration/ContextMenu/MetricNode.cs index 6a7f50d..89ba8d6 100644 --- a/src/HotSpot/Configuration/ContextMenu/MetricNode.cs +++ b/src/HotSpot/Configuration/ContextMenu/MetricNode.cs @@ -8,18 +8,25 @@ internal sealed class MetricNode public Metric Name { get; } public bool Enable { get; set; } public Unit Unit { get; set; } + public Prefix? Prefix { get; set; } - private MetricNode(Metric name, bool enable, Unit unit) + private MetricNode(Metric name, bool enable, Unit unit, Prefix? prefix) { Name = name; Enable = enable; - Unit = unit; + // TODO: Legacy : Remove Kilowatt + Unit = unit == Unit.Kilowatt ? Unit.Watt : unit; + Prefix = prefix; } public bool Save(ConfigNode node) { node.AddValue("%enable", Enable); node.AddValue("%unit", Unit); + if (Prefix != null) + node.AddValue("%prefix", Prefix.Value); + else + node.AddValue("%prefix", "auto"); return true; } @@ -31,10 +38,17 @@ public static MetricNode TryParse(ConfigNode node) var name = Metric.TryParse(node.GetValue("name")); var enable = node.TryParse("enable") ?? true; var unit = node.TryParse("unit"); + Prefix? prefix = null; + + var prefixString = node.GetValue("prefix"); + if (!string.IsNullOrEmpty(prefixString) && prefixString != "auto") + { + prefix = node.TryParse("prefix"); + } if (name != null && unit != null) { - return new MetricNode(name, enable, unit.Value); + return new MetricNode(name, enable, unit.Value, prefix); } } diff --git a/src/HotSpot/Extensions/DoubleExtensions.cs b/src/HotSpot/Extensions/DoubleExtensions.cs new file mode 100644 index 0000000..65b711d --- /dev/null +++ b/src/HotSpot/Extensions/DoubleExtensions.cs @@ -0,0 +1,91 @@ +using System; +using HotSpot.Model; + +namespace HotSpot +{ + internal static class DoubleExtensions + { + private static readonly string[] PrefixStrings = { + "y", + null, + null, + "z", + null, + null, + "a", + null, + null, + "f", + null, + null, + "p", + null, + null, + "n", + null, + null, + "\u03bc", + null, + null, + "m", + "c", + "d", + string.Empty, + "da", + "h", + "k", + null, + null, + "M", + null, + null, + "G", + null, + null, + "T", + null, + null, + "P", + null, + null, + "E", + null, + null, + "Z", + null, + null, + "Y" + }; + + public static Prefix GetBestPrefix(this double value) + { + if (Math.Abs(value) > double.Epsilon) + { + var actualDegree = (int)Math.Floor(Math.Log10(Math.Abs(value))); + var actualDegreeAbs = Math.Abs(actualDegree); + + var nearestDegreeAbs = actualDegreeAbs <= 3 + ? actualDegreeAbs + : Math.Min(actualDegreeAbs - actualDegreeAbs % 3, 24); + + return (Prefix)(Math.Sign(actualDegree) * nearestDegreeAbs); + } + else + { + return Prefix.None; + } + } + + public static double ScaleToPrefix(this double value, Prefix prefix) + { + return value * Math.Pow(10, -(int)prefix); + } + + public static string ToQuantityString(this double value, Prefix? prefix, string unit, string format) + { + prefix = prefix ?? value.GetBestPrefix(); + + return $"{value.ScaleToPrefix(prefix.Value).ToString(format)} {PrefixStrings[(int)prefix + 24]}{unit}"; + } + } +} diff --git a/src/HotSpot/HotSpot.csproj b/src/HotSpot/HotSpot.csproj index 5161afe..9c63e9c 100644 --- a/src/HotSpot/HotSpot.csproj +++ b/src/HotSpot/HotSpot.csproj @@ -72,6 +72,7 @@ + @@ -89,6 +90,7 @@ + diff --git a/src/HotSpot/HotSpotGuiBehavior.cs b/src/HotSpot/HotSpotGuiBehavior.cs index f97850b..0c6cc9d 100644 --- a/src/HotSpot/HotSpotGuiBehavior.cs +++ b/src/HotSpot/HotSpotGuiBehavior.cs @@ -3,6 +3,7 @@ using System.Linq; using HotSpot.Compat.Toolbar; using HotSpot.Configuration; +using HotSpot.Model; using HotSpot.Reflection; using KSP.UI.Screens; using UnityEngine; @@ -25,6 +26,7 @@ public sealed class HotSpotGuiBehavior : MonoBehaviour private Rect _configWindowRect; private ConfigWindowTab _configWindowTabActive = ConfigWindowTab.Context; private readonly Dictionary _configWindowContextShowUnits = new Dictionary(); + private readonly Dictionary _configWindowContextShowPrefixes = new Dictionary(); private bool _configWindowOverlayShowMetrics; private bool _configWindowOverlayShowSchemes; @@ -37,6 +39,7 @@ public void Start() foreach (var metricNode in Config.Instance.ContextMenu.Metrics) { _configWindowContextShowUnits[metricNode.Name.Name] = false; + _configWindowContextShowPrefixes[metricNode.Name.Name] = false; } bool enableToolbar; @@ -264,14 +267,38 @@ private void OnContextTab() metricNode.Enable = GUILayout.Toggle(metricNode.Enable, metricNode.Name.LongFriendlyName); - if (metricNode.Name.Units.Length > 1) + if (metricNode.Name.Units.Length > 1 || metricNode.Name.EnablePrefixSelection) { GUILayout.FlexibleSpace(); - if (GUILayout.Button("Unit")) + if (metricNode.Name.Units.Length > 1) { - _configWindowContextShowUnits[metricNode.Name.Name] = - !_configWindowContextShowUnits[metricNode.Name.Name]; + if (GUILayout.Button("Unit")) + { + _configWindowContextShowUnits[metricNode.Name.Name] = + !_configWindowContextShowUnits[metricNode.Name.Name]; + + // Turn off prefixes if necessary + if (_configWindowContextShowUnits[metricNode.Name.Name]) + { + _configWindowContextShowPrefixes[metricNode.Name.Name] = false; + } + } + } + + if (metricNode.Name.EnablePrefixSelection) + { + if (GUILayout.Button("Prefix")) + { + _configWindowContextShowPrefixes[metricNode.Name.Name] = + !_configWindowContextShowPrefixes[metricNode.Name.Name]; + + // Turn off units if necessary + if (_configWindowContextShowPrefixes[metricNode.Name.Name]) + { + _configWindowContextShowUnits[metricNode.Name.Name] = false; + } + } } } @@ -297,6 +324,56 @@ private void OnContextTab() GUILayout.EndVertical(); } + + if (_configWindowContextShowPrefixes[metricNode.Name.Name]) + { + GUILayout.BeginVertical(); + + var prefixes = new[] + { + "Auto", + Prefix.None.ToString(), + Prefix.Yocto.ToString(), + Prefix.Zepto.ToString(), + Prefix.Atto.ToString(), + Prefix.Femto.ToString(), + Prefix.Pico.ToString(), + Prefix.Nano.ToString(), + Prefix.Micro.ToString(), + Prefix.Milli.ToString(), + Prefix.Centi.ToString(), + Prefix.Deci.ToString(), + Prefix.Deca.ToString(), + Prefix.Hecto.ToString(), + Prefix.Kilo.ToString(), + Prefix.Mega.ToString(), + Prefix.Giga.ToString(), + Prefix.Tera.ToString(), + Prefix.Peta.ToString(), + Prefix.Exa.ToString(), + Prefix.Zetta.ToString(), + Prefix.Yotta.ToString() + }; + + var unitIndex = 0; + for (var i = 0; i < prefixes.Length; i++) + { + if (prefixes[i] == (metricNode.Prefix?.ToString() ?? "Auto")) + { + unitIndex = i; + break; + } + } + + var newUnitIndex = GUILayout.SelectionGrid(unitIndex, prefixes, 2); + + var selectedPrefixString = prefixes[newUnitIndex]; + metricNode.Prefix = selectedPrefixString == "Auto" + ? null + : (Prefix?)Enum.Parse(typeof(Prefix), selectedPrefixString); + + GUILayout.EndVertical(); + } } GUILayout.EndVertical(); } diff --git a/src/HotSpot/HotSpotModule.cs b/src/HotSpot/HotSpotModule.cs index 972de29..7f399a3 100644 --- a/src/HotSpot/HotSpotModule.cs +++ b/src/HotSpot/HotSpotModule.cs @@ -50,7 +50,7 @@ public override void OnUpdate() if (metric.IsApplicable(part)) { - var value = metric.GetPartCurrentString(part, metricNode.Unit); + var value = metric.GetPartCurrentString(part, metricNode.Unit, metricNode.Prefix); if (value != null) { diff --git a/src/HotSpot/Model/Metric.cs b/src/HotSpot/Model/Metric.cs index d3db13f..54245d5 100644 --- a/src/HotSpot/Model/Metric.cs +++ b/src/HotSpot/Model/Metric.cs @@ -11,6 +11,7 @@ internal sealed class Metric "Temp [I]", "Internal Temperature", new[] { Unit.Kelvin, Unit.Celsius, Unit.Rankine, Unit.Fahrenheit }, + false, part => true, vessel => new Dictionary { @@ -25,13 +26,14 @@ internal sealed class Metric [Variable.PartAbsoluteMaximum] = part.maxTemp }, part => part.temperature, - (part, unit) => TemperatureToString(part.temperature, part.maxTemp, null, unit) + (part, unit, prefix) => TemperatureToString(part.temperature, part.maxTemp, null, unit) ); public static readonly Metric TemperatureSkin = new Metric("TemperatureSkin", "Temp [S]", "Skin Temperature", new[] { Unit.Kelvin, Unit.Celsius, Unit.Rankine, Unit.Fahrenheit }, + false, part => true, vessel => new Dictionary { @@ -46,13 +48,14 @@ internal sealed class Metric [Variable.PartAbsoluteMaximum] = part.skinMaxTemp }, part => part.skinTemperature, - (part, unit) => TemperatureToString(part.skinTemperature, part.skinMaxTemp, null, unit) + (part, unit, prefix) => TemperatureToString(part.skinTemperature, part.skinMaxTemp, null, unit) ); public static readonly Metric TemperatureCore = new Metric("TemperatureCore", "Temp [C]", "Core Temperature", new[] { Unit.Kelvin, Unit.Celsius, Unit.Rankine, Unit.Fahrenheit }, + false, part => part.FindModuleImplementing() != null, vessel => { @@ -96,7 +99,7 @@ internal sealed class Metric } }, part => part.FindModuleImplementing()?.CoreTemperature, - (part, unit) => + (part, unit, prefix) => { var coreHeatModule = part.FindModuleImplementing(); @@ -114,7 +117,8 @@ internal sealed class Metric public static readonly Metric ThermalRate = new Metric("ThermalRate", "Thermal Rate", "Thermal Rate", - new[] { Unit.Kilowatt }, + new[] { Unit.Watt }, + true, part => true, vessel => new Dictionary { @@ -123,13 +127,14 @@ internal sealed class Metric }, part => new Dictionary(), part => part.GetThermalFlux(), - (part, unit) => $"{part.GetThermalFlux():F2}kW" + (part, unit, prefix) => (part.GetThermalFlux() * 1000.0).ToQuantityString(prefix, "W", "F2") ); public static readonly Metric ThermalRateInternal = new Metric("ThermalRateInternal", "Thermal Rate [I]", "Internal Thermal Rate", - new[] { Unit.Kilowatt }, + new[] { Unit.Watt }, + true, part => true, vessel => new Dictionary { @@ -138,13 +143,14 @@ internal sealed class Metric }, part => new Dictionary(), part => part.thermalInternalFluxPrevious, - (part, unit) => $"{part.thermalInternalFluxPrevious:F2}kW" + (part, unit, prefix) => (part.thermalInternalFluxPrevious * 1000.0).ToQuantityString(prefix, "W", "F2") ); public static readonly Metric ThermalRateConductive = new Metric("ThermalRateConductive", "Thermal Rate [Cd]", "Conductive Thermal Rate", - new[] { Unit.Kilowatt }, + new[] { Unit.Watt }, + true, part => true, vessel => new Dictionary { @@ -153,13 +159,14 @@ internal sealed class Metric }, part => new Dictionary(), part => part.thermalConductionFlux, - (part, unit) => $"{part.thermalConductionFlux:F2}kW" + (part, unit, prefix) => (part.thermalConductionFlux * 1000.0).ToQuantityString(prefix, "W", "F2") ); public static readonly Metric ThermalRateConvective = new Metric("ThermalRateConvective", "Thermal Rate [Cv]", "Convective Thermal Rate", - new[] { Unit.Kilowatt }, + new[] { Unit.Watt }, + true, part => true, vessel => new Dictionary { @@ -168,13 +175,14 @@ internal sealed class Metric }, part => new Dictionary(), part => part.thermalConvectionFlux, - (part, unit) => $"{part.thermalConvectionFlux:F2}kW" + (part, unit, prefix) => (part.thermalConvectionFlux * 1000.0).ToQuantityString(prefix, "W", "F2") ); public static readonly Metric ThermalRateRadiative = new Metric("ThermalRateRadiative", "Thermal Rate [R]", "Radiative Thermal Rate", - new[] { Unit.Kilowatt }, + new[] { Unit.Watt }, + true, part => true, vessel => new Dictionary { @@ -183,13 +191,14 @@ internal sealed class Metric }, part => new Dictionary(), part => part.thermalRadiationFlux, - (part, unit) => $"{part.thermalRadiationFlux:F2}kW" + (part, unit, prefix) => (part.thermalRadiationFlux * 1000.0).ToQuantityString(prefix, "W", "F2") ); public static readonly Metric ThermalRateSkinToInternal = new Metric("ThermalRateSkinToInternal", "Thermal Rate [S-I]", "Skin to Internal Thermal Rate", - new[] { Unit.Kilowatt }, + new[] { Unit.Watt }, + true, part => true, vessel => new Dictionary { @@ -198,13 +207,14 @@ internal sealed class Metric }, part => new Dictionary(), part => part.skinToInternalFlux, - (part, unit) => $"{part.skinToInternalFlux:F2}kW" + (part, unit, prefix) => (part.skinToInternalFlux * 1000.0).ToQuantityString(prefix, "W", "F2") ); public static readonly Metric ThermalRateInternalToSkin = new Metric("ThermalRateInternalToSkin", "Thermal Rate [I-S]", "Internal to Skin Thermal Rate", - new[] { Unit.Kilowatt }, + new[] { Unit.Watt }, + true, part => true, vessel => new Dictionary { @@ -213,32 +223,35 @@ internal sealed class Metric }, part => new Dictionary(), part => -part.skinToInternalFlux, - (part, unit) => $"{-part.skinToInternalFlux:F2}kW" + (part, unit, prefix) => (-part.skinToInternalFlux * 1000.0).ToQuantityString(prefix, "W", "F2") ); private readonly Func _isApplicable; private readonly Func> _getVesselValues; private readonly Func> _getPartValues; private readonly Func _getPartCurrent; - private readonly Func _getPartCurrentString; + private readonly Func _getPartCurrentString; public string Name { get; } public string ShortFriendlyName { get; } public string LongFriendlyName { get; } public Unit[] Units { get; } + public bool EnablePrefixSelection { get; } - private Metric(string name, string shortFriendlyName, string longFriendlyName, Unit[] units, + private Metric(string name, string shortFriendlyName, string longFriendlyName, + Unit[] units, bool enablePrefixSelection, Func isApplicable, Func> getVesselValues, Func> getPartValues, Func getPartCurrent, - Func getPartCurrentString + Func getPartCurrentString ) { Name = name; ShortFriendlyName = shortFriendlyName; LongFriendlyName = longFriendlyName; Units = units; + EnablePrefixSelection = enablePrefixSelection; _isApplicable = isApplicable; _getVesselValues = getVesselValues; _getPartValues = getPartValues; @@ -246,11 +259,20 @@ Func getPartCurrentString _getPartCurrentString = getPartCurrentString; } - public bool IsApplicable(Part part) => _isApplicable(part); - public Dictionary GetVesselValues(Vessel vessel) => _getVesselValues(vessel); - public Dictionary GetPartValues(Part part) => _getPartValues(part); - public double? GetPartCurrent(Part part) => _getPartCurrent(part); - public string GetPartCurrentString(Part part, Unit unit) => _getPartCurrentString(part, unit); + public bool IsApplicable(Part part) + => _isApplicable(part); + + public Dictionary GetVesselValues(Vessel vessel) + => _getVesselValues(vessel); + + public Dictionary GetPartValues(Part part) + => _getPartValues(part); + + public double? GetPartCurrent(Part part) + => _getPartCurrent(part); + + public string GetPartCurrentString(Part part, Unit unit, Prefix? prefix) + => _getPartCurrentString(part, unit, prefix); public static Metric Parse(string s) { diff --git a/src/HotSpot/Model/Prefix.cs b/src/HotSpot/Model/Prefix.cs new file mode 100644 index 0000000..ce0aafa --- /dev/null +++ b/src/HotSpot/Model/Prefix.cs @@ -0,0 +1,27 @@ +namespace HotSpot.Model +{ + internal enum Prefix : sbyte + { + Yocto = -24, + Zepto = -21, + Atto = -18, + Femto = -15, + Pico = -12, + Nano = -9, + Micro = -6, + Milli = -3, + Centi = -2, + Deci = -1, + None = 0, + Deca = 1, + Hecto = 2, + Kilo = 3, + Mega = 6, + Giga = 9, + Tera = 12, + Peta = 15, + Exa = 18, + Zetta = 21, + Yotta = 24, + } +} diff --git a/src/HotSpot/Model/Unit.cs b/src/HotSpot/Model/Unit.cs index 9b85a3d..008c235 100644 --- a/src/HotSpot/Model/Unit.cs +++ b/src/HotSpot/Model/Unit.cs @@ -1,6 +1,6 @@ namespace HotSpot.Model { - internal enum Unit + internal enum Unit : byte { // TemperatureInternal Kelvin, @@ -9,6 +9,7 @@ internal enum Unit Fahrenheit, // Power + Watt, Kilowatt, } } From 2f8218700598dd18b60c508c393a5a83a8c19345 Mon Sep 17 00:00:00 2001 From: Dwayne Bent Date: Sun, 15 May 2016 16:27:38 -0400 Subject: [PATCH 09/10] Add comment explaining why we use thermalInternalFluxPrevious --- src/HotSpot/Model/Metric.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/HotSpot/Model/Metric.cs b/src/HotSpot/Model/Metric.cs index 54245d5..8fdca0f 100644 --- a/src/HotSpot/Model/Metric.cs +++ b/src/HotSpot/Model/Metric.cs @@ -130,6 +130,7 @@ internal sealed class Metric (part, unit, prefix) => (part.GetThermalFlux() * 1000.0).ToQuantityString(prefix, "W", "F2") ); + // Use thermalInternalFluxPrevious since the current value is always zero at the time we read it. public static readonly Metric ThermalRateInternal = new Metric("ThermalRateInternal", "Thermal Rate [I]", "Internal Thermal Rate", From d24b0528211797bd96785b7fa0c6463c70f63618 Mon Sep 17 00:00:00 2001 From: Dwayne Bent Date: Wed, 18 May 2016 10:28:27 -0400 Subject: [PATCH 10/10] Update version to 0.6.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eabf0b1..5706180 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## v0.6.0-alpha +## v0.6.0 ##### Added - Added core temperature as a new metric for applicable parts. - Added skin to internal and internal to skin thermal rate as new metrics.