From 83c2368d74c1ed7db4d376e583900da1fa126d13 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Sun, 15 Sep 2024 14:38:21 -0500 Subject: [PATCH 1/3] Trivial coding style changes --- .../Win32RegistryConfiguration.cs | 4 +-- Core/Games/KerbalSpaceProgram.cs | 4 +-- Core/Games/KerbalSpaceProgram2.cs | 4 +-- GUI/Controls/ModInfoTabs/Versions.cs | 3 +- GUI/Util.cs | 32 +++++++++---------- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/Core/Configuration/Win32RegistryConfiguration.cs b/Core/Configuration/Win32RegistryConfiguration.cs index a50a2def7..32b7cddd5 100644 --- a/Core/Configuration/Win32RegistryConfiguration.cs +++ b/Core/Configuration/Win32RegistryConfiguration.cs @@ -179,12 +179,12 @@ public void SetAuthToken(string host, string? token) /// /// Not implemented because the Windows registry is deprecated /// - public string[] GlobalInstallFilters { get; set; } = new string[] { }; + public string[] GlobalInstallFilters { get; set; } = Array.Empty(); /// /// Not implemented because the Windows registry is deprecated /// - public string?[] PreferredHosts { get; set; } = new string?[] { }; + public string?[] PreferredHosts { get; set; } = Array.Empty(); /// /// Not implemented because the Windows registry is deprecated diff --git a/Core/Games/KerbalSpaceProgram.cs b/Core/Games/KerbalSpaceProgram.cs index c636086ae..c0f88e582 100644 --- a/Core/Games/KerbalSpaceProgram.cs +++ b/Core/Games/KerbalSpaceProgram.cs @@ -213,14 +213,14 @@ is Stream s ?.Builds ?.Select(b => GameVersion.Parse(b.Value)) .ToArray() - ?? new GameVersion[] { }; + ?? Array.Empty(); public GameVersion[] ParseBuildsJson(JToken json) => json.ToObject() ?.Builds ?.Select(b => GameVersion.Parse(b.Value)) .ToArray() - ?? new GameVersion[] { }; + ?? Array.Empty(); public GameVersion? DetectVersion(DirectoryInfo where) => ServiceLocator.Container diff --git a/Core/Games/KerbalSpaceProgram2.cs b/Core/Games/KerbalSpaceProgram2.cs index 3a4a60632..3c97642b9 100644 --- a/Core/Games/KerbalSpaceProgram2.cs +++ b/Core/Games/KerbalSpaceProgram2.cs @@ -161,11 +161,11 @@ public GameVersion[] EmbeddedGameVersions is Stream s ? JsonConvert.DeserializeObject(new StreamReader(s).ReadToEnd()) : null) - ?? new GameVersion[] { }; + ?? Array.Empty(); public GameVersion[] ParseBuildsJson(JToken json) => json.ToObject() - ?? new GameVersion[] { }; + ?? Array.Empty(); public GameVersion DetectVersion(DirectoryInfo where) => VersionFromAssembly(Path.Combine(where.FullName, diff --git a/GUI/Controls/ModInfoTabs/Versions.cs b/GUI/Controls/ModInfoTabs/Versions.cs index ffec1a78f..5d9c75ebe 100644 --- a/GUI/Controls/ModInfoTabs/Versions.cs +++ b/GUI/Controls/ModInfoTabs/Versions.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.ComponentModel; using System.Collections.Generic; @@ -226,7 +227,7 @@ private ListViewItem[] getItems(GUIMod gmod, List versions) return items; } - return new ListViewItem[] { }; + return Array.Empty(); } [ForbidGUICalls] diff --git a/GUI/Util.cs b/GUI/Util.cs index 27103529b..3f0cc74e6 100644 --- a/GUI/Util.cs +++ b/GUI/Util.cs @@ -306,26 +306,26 @@ private static Color AddColors(Color a, Color b) a.G + b.G, a.B + b.B); - public static Bitmap LerpBitmaps(Bitmap a, Bitmap b, float amount) + public static Bitmap LerpBitmaps(Bitmap a, Bitmap b, + float amount) + => amount <= 0 ? a + : amount >= 1 ? b + : MergeBitmaps(a, b, + // Note pixA and pixB are swapped because our blend function + // pretends 'amount' is the first argument's alpha channel, + // so 0 -> third param by itself + (pixA, pixB) => AlphaBlendWith(pixB, amount, pixA)); + + private static Bitmap MergeBitmaps(Bitmap a, Bitmap b, + Func how) { - if (amount <= 0) - { - return a; - } - if (amount >= 1) - { - return b; - } var c = new Bitmap(a); - for (int y = 0; y < c.Height; ++y) + foreach (var y in Enumerable.Range(0, c.Height)) { - for (int x = 0; x < c.Width; ++x) + foreach (var x in Enumerable.Range(0, c.Width)) { - // Note a and b are swapped because our blend function pretends 'amount' - // is the first argument's alpha channel, so 0 -> third param by itself - c.SetPixel(x, y, AlphaBlendWith(b.GetPixel(x, y), - amount, - a.GetPixel(x, y))); + c.SetPixel(x, y, how(a.GetPixel(x, y), + b.GetPixel(x, y))); } } return c; From f318885c6e2bf260be46f8d9c00145c83c973726 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Thu, 12 Sep 2024 12:15:58 -0500 Subject: [PATCH 2/3] Auto set compat of new instances based on Unity updates --- Core/GameInstance.cs | 5 +++ Core/Games/IGame.cs | 1 + Core/Games/KerbalSpaceProgram.cs | 22 ++++++++++ Core/Games/KerbalSpaceProgram2.cs | 6 +++ Tests/Core/Games/KerbalSpaceProgram2Tests.cs | 35 ++++++++++++++++ Tests/Core/Games/KerbalSpaceProgramTests.cs | 44 ++++++++++++++++++++ 6 files changed, 113 insertions(+) create mode 100644 Tests/Core/Games/KerbalSpaceProgram2Tests.cs create mode 100644 Tests/Core/Games/KerbalSpaceProgramTests.cs diff --git a/Core/GameInstance.cs b/Core/GameInstance.cs index 42a99a16f..715662136 100644 --- a/Core/GameInstance.cs +++ b/Core/GameInstance.cs @@ -164,6 +164,11 @@ private void LoadCompatibleVersions() GameVersion.TryParse(compatibleGameVersions.GameVersionWhenWritten, out GameVersion? mainVer); GameVersionWhenCompatibleVersionsWereStored = mainVer; } + else if (Version() is GameVersion gv) + { + _compatibleVersions = game.DefaultCompatibleVersions(gv) + .ToList(); + } } private string CompatibleGameVersionsFile() diff --git a/Core/Games/IGame.cs b/Core/Games/IGame.cs index 7669ce0ab..1d7488e03 100644 --- a/Core/Games/IGame.cs +++ b/Core/Games/IGame.cs @@ -43,6 +43,7 @@ public interface IGame GameVersion[] EmbeddedGameVersions { get; } GameVersion[] ParseBuildsJson(JToken json); GameVersion? DetectVersion(DirectoryInfo where); + GameVersion[] DefaultCompatibleVersions(GameVersion installedVersion); string CompatibleVersionsFile { get; } string[] InstanceAnchorFiles { get; } diff --git a/Core/Games/KerbalSpaceProgram.cs b/Core/Games/KerbalSpaceProgram.cs index c0f88e582..05f433cdd 100644 --- a/Core/Games/KerbalSpaceProgram.cs +++ b/Core/Games/KerbalSpaceProgram.cs @@ -233,6 +233,28 @@ public GameVersion[] ParseBuildsJson(JToken json) ? verFromReadme : null; + public GameVersion[] DefaultCompatibleVersions(GameVersion installedVersion) + // 1.2.9 was a prerelease that broke compatibility with previous 1.2 releases + => installedVersion.Major == 1 && installedVersion.Minor == 2 && installedVersion.Patch == 9 + ? Array.Empty() + : UnityBreakVersions.FirstOrDefault(brk => brk <= installedVersion) + is GameVersion latestUnityBreak + ? MinorVersionsInRange(latestUnityBreak, installedVersion) + : new GameVersion[] + { + new GameVersion(installedVersion.Major, + installedVersion.Minor) + }; + + private static readonly GameVersion[] UnityBreakVersions = + new int[] {8, 4, 2, 1}.Select(minor => new GameVersion(1, minor)) + .ToArray(); + + private static GameVersion[] MinorVersionsInRange(GameVersion min, GameVersion max) + => Enumerable.Range(min.Minor, max.Minor - min.Minor + 1) + .Select(minor => new GameVersion(min.Major, minor)) + .ToArray(); + public string CompatibleVersionsFile => "compatible_ksp_versions.json"; public string[] InstanceAnchorFiles => diff --git a/Core/Games/KerbalSpaceProgram2.cs b/Core/Games/KerbalSpaceProgram2.cs index 3c97642b9..efd4e5578 100644 --- a/Core/Games/KerbalSpaceProgram2.cs +++ b/Core/Games/KerbalSpaceProgram2.cs @@ -202,6 +202,12 @@ public GameVersion DetectVersion(DirectoryInfo where) ? v : null; + public GameVersion[] DefaultCompatibleVersions(GameVersion installedVersion) + // KSP2 didn't last long enough to break compatibility :~( + => Enumerable.Range(1, 2) + .Select(minor => new GameVersion(0, minor)) + .ToArray(); + public string CompatibleVersionsFile => "compatible_game_versions.json"; public string[] InstanceAnchorFiles => diff --git a/Tests/Core/Games/KerbalSpaceProgram2Tests.cs b/Tests/Core/Games/KerbalSpaceProgram2Tests.cs new file mode 100644 index 000000000..084f6c6c2 --- /dev/null +++ b/Tests/Core/Games/KerbalSpaceProgram2Tests.cs @@ -0,0 +1,35 @@ +using NUnit.Framework; + +using CKAN.Versioning; +using CKAN.Games.KerbalSpaceProgram2; +using System.Linq; + +namespace Tests.Core.Games +{ + [TestFixture] + public class KerbalSpaceProgram2Tests + { + [Test, TestCase("0.1.0.0", + new string[] { "0.1", "0.2" }), + TestCase("0.1.1.0", + new string[] { "0.1", "0.2" }), + TestCase("0.2.0.0", + new string[] { "0.1", "0.2" }), + TestCase("0.2.2.0", + new string[] { "0.1", "0.2" }), + ] + public void DefaultCompatibleVersions_RealVersion_CorrectRange(string installedVersion, + string[] correctCompatVersions) + { + // Arrange + var game = new KerbalSpaceProgram2(); + + // Act + var compat = game.DefaultCompatibleVersions(GameVersion.Parse(installedVersion)); + + // Assert + CollectionAssert.AreEqual(correctCompatVersions.Select(GameVersion.Parse), + compat); + } + } +} diff --git a/Tests/Core/Games/KerbalSpaceProgramTests.cs b/Tests/Core/Games/KerbalSpaceProgramTests.cs new file mode 100644 index 000000000..d047fb265 --- /dev/null +++ b/Tests/Core/Games/KerbalSpaceProgramTests.cs @@ -0,0 +1,44 @@ +using NUnit.Framework; + +using CKAN.Versioning; +using CKAN.Games.KerbalSpaceProgram; +using System.Linq; + +namespace Tests.Core.Games +{ + [TestFixture] + public class KerbalSpaceProgramTests + { + [Test, TestCase("1.12.5", + new string[] { "1.8", "1.9", "1.10", "1.11", "1.12" }), + TestCase("1.12.0", + new string[] { "1.8", "1.9", "1.10", "1.11", "1.12" }), + TestCase("1.10.1", + new string[] { "1.8", "1.9", "1.10" }), + TestCase("1.8.0", + new string[] { "1.8" }), + TestCase("1.7.3", + new string[] { "1.4", "1.5", "1.6", "1.7" }), + TestCase("1.5.1", + new string[] { "1.4", "1.5" }), + TestCase("1.3.1", + new string[] { "1.2", "1.3" }), + TestCase("1.2.9", + new string[] { }), + TestCase("1.0.5", + new string[] { "1.0" })] + public void DefaultCompatibleVersions_RealVersion_CorrectRange(string installedVersion, + string[] correctCompatVersions) + { + // Arrange + var game = new KerbalSpaceProgram(); + + // Act + var compat = game.DefaultCompatibleVersions(GameVersion.Parse(installedVersion)); + + // Assert + CollectionAssert.AreEqual(correctCompatVersions.Select(GameVersion.Parse), + compat); + } + } +} From 76c831692cdfed0847b06bbc634bdf217b32cae5 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Thu, 12 Sep 2024 13:05:48 -0500 Subject: [PATCH 3/3] Alert user when defaulting compatibility --- .../CompatibleGameVersionsDialog.Designer.cs | 60 ++++++++++++++----- GUI/Dialogs/CompatibleGameVersionsDialog.cs | 50 ++++++++++++---- GUI/Properties/Resources.resx | 4 +- 3 files changed, 88 insertions(+), 26 deletions(-) diff --git a/GUI/Dialogs/CompatibleGameVersionsDialog.Designer.cs b/GUI/Dialogs/CompatibleGameVersionsDialog.Designer.cs index db7790470..8ca619236 100644 --- a/GUI/Dialogs/CompatibleGameVersionsDialog.Designer.cs +++ b/GUI/Dialogs/CompatibleGameVersionsDialog.Designer.cs @@ -30,6 +30,8 @@ private void InitializeComponent() { this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new SingleAssemblyComponentResourceManager(typeof(CompatibleGameVersionsDialog)); + this.MessageLabel = new System.Windows.Forms.Label(); + this.MainContentsPanel = new System.Windows.Forms.Panel(); this.InstancePathLabel = new System.Windows.Forms.Label(); this.ActualInstancePathLabel = new System.Windows.Forms.Label(); this.GameVersionLabel = new System.Windows.Forms.Label(); @@ -45,8 +47,45 @@ private void InitializeComponent() this.WarningLabel = new System.Windows.Forms.Label(); this.CancelChooseCompatibleVersionsButton = new System.Windows.Forms.Button(); this.SaveButton = new System.Windows.Forms.Button(); + this.MainContentsPanel.SuspendLayout(); this.SuspendLayout(); // + // MessageLabel + // + this.MessageLabel.Dock = System.Windows.Forms.DockStyle.Top; + this.MessageLabel.Font = new System.Drawing.Font(System.Drawing.SystemFonts.DefaultFont.Name, 12, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Pixel); + this.MessageLabel.BackColor = System.Drawing.SystemColors.Window; + this.MessageLabel.ForeColor = System.Drawing.SystemColors.WindowText; + this.MessageLabel.Location = new System.Drawing.Point(0, 0); + this.MessageLabel.Margin = new System.Windows.Forms.Padding(8, 6, 8, 6); + this.MessageLabel.Padding = new System.Windows.Forms.Padding(8, 6, 8, 6); + this.MessageLabel.Name = "MessageLabel"; + this.MessageLabel.Size = new System.Drawing.Size(60, 13); + this.MessageLabel.TabIndex = 0; + this.MessageLabel.Visible = false; + resources.ApplyResources(this.MessageLabel, "MessageLabel"); + // + // MainContentsPanel + // + this.MainContentsPanel.Controls.Add(this.InstancePathLabel); + this.MainContentsPanel.Controls.Add(this.ActualInstancePathLabel); + this.MainContentsPanel.Controls.Add(this.GameVersionLabel); + this.MainContentsPanel.Controls.Add(this.ActualGameVersionLabel); + this.MainContentsPanel.Controls.Add(this.ListHeaderLabel); + this.MainContentsPanel.Controls.Add(this.SelectedVersionsCheckedListBox); + this.MainContentsPanel.Controls.Add(this.ClearSelectionButton); + this.MainContentsPanel.Controls.Add(this.AddVersionLabel); + this.MainContentsPanel.Controls.Add(this.AddVersionToListTextBox); + this.MainContentsPanel.Controls.Add(this.AddVersionToListButton); + this.MainContentsPanel.Controls.Add(this.WildcardExplanationLabel); + this.MainContentsPanel.Controls.Add(this.FutureUpdatesLabel); + this.MainContentsPanel.Controls.Add(this.WarningLabel); + this.MainContentsPanel.Controls.Add(this.CancelChooseCompatibleVersionsButton); + this.MainContentsPanel.Controls.Add(this.SaveButton); + this.MainContentsPanel.Dock = System.Windows.Forms.DockStyle.Fill; + this.MainContentsPanel.Name = "MainContentsPanel"; + this.MainContentsPanel.Size = new System.Drawing.Size(443, 383); + // // InstancePathLabel // this.InstancePathLabel.AutoSize = true; @@ -216,21 +255,8 @@ private void InitializeComponent() this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.CancelChooseCompatibleVersionsButton; this.ClientSize = new System.Drawing.Size(443, 383); - this.Controls.Add(this.InstancePathLabel); - this.Controls.Add(this.ActualInstancePathLabel); - this.Controls.Add(this.GameVersionLabel); - this.Controls.Add(this.ActualGameVersionLabel); - this.Controls.Add(this.ListHeaderLabel); - this.Controls.Add(this.SelectedVersionsCheckedListBox); - this.Controls.Add(this.ClearSelectionButton); - this.Controls.Add(this.AddVersionLabel); - this.Controls.Add(this.AddVersionToListTextBox); - this.Controls.Add(this.AddVersionToListButton); - this.Controls.Add(this.WildcardExplanationLabel); - this.Controls.Add(this.FutureUpdatesLabel); - this.Controls.Add(this.WarningLabel); - this.Controls.Add(this.CancelChooseCompatibleVersionsButton); - this.Controls.Add(this.SaveButton); + this.Controls.Add(this.MainContentsPanel); + this.Controls.Add(this.MessageLabel); this.Icon = EmbeddedImages.AppIcon; this.MaximizeBox = false; this.MinimizeBox = false; @@ -240,12 +266,16 @@ private void InitializeComponent() this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Shown += new System.EventHandler(this.CompatibleGameVersionsDialog_Shown); resources.ApplyResources(this, "$this"); + this.MainContentsPanel.ResumeLayout(false); + this.MainContentsPanel.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); } #endregion + private System.Windows.Forms.Label MessageLabel; + private System.Windows.Forms.Panel MainContentsPanel; private System.Windows.Forms.Label InstancePathLabel; private System.Windows.Forms.Label ActualInstancePathLabel; private System.Windows.Forms.Label GameVersionLabel; diff --git a/GUI/Dialogs/CompatibleGameVersionsDialog.cs b/GUI/Dialogs/CompatibleGameVersionsDialog.cs index 66019ad80..548a7383d 100644 --- a/GUI/Dialogs/CompatibleGameVersionsDialog.cs +++ b/GUI/Dialogs/CompatibleGameVersionsDialog.cs @@ -67,21 +67,51 @@ protected override void OnHelpButtonClicked(CancelEventArgs evt) evt.Cancel = Util.TryOpenWebPage(HelpURLs.CompatibleGameVersions); } + private void ShowMessage(string msg) + { + if (string.IsNullOrEmpty(msg)) + { + MessageLabel.Text = ""; + MessageLabel.Visible = false; + } + else + { + MessageLabel.Text = msg; + MessageLabel.Visible = true; + MessageLabel.Height = Util.LabelStringHeight(CreateGraphics(), + MessageLabel); + Height += MessageLabel.Height; + } + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + if (MessageLabel.Visible) + { + MessageLabel.Height = Util.LabelStringHeight(CreateGraphics(), + MessageLabel); + } + } + private void CompatibleGameVersionsDialog_Shown(object? sender, EventArgs? e) { if (_inst.CompatibleVersionsAreFromDifferentGameVersion) { CancelChooseCompatibleVersionsButton.Visible = false; - ActualGameVersionLabel.Text = string.Format( - Properties.Resources.CompatibleGameVersionsDialogVersionDetails, - _inst.Version(), - _inst.GameVersionWhenCompatibleVersionsWereStored); - ActualGameVersionLabel.ForeColor = Color.Red; - MessageBox.Show(this, - Properties.Resources.CompatibleGameVersionsDialogGameUpdatedDetails, - Properties.Resources.CompatibleGameVersionsDialogGameUpdatedTitle, - MessageBoxButtons.OK, - MessageBoxIcon.Information); + if (_inst.GameVersionWhenCompatibleVersionsWereStored == null) + { + ShowMessage(Properties.Resources.CompatibleGameVersionsDialogDefaultedDetails); + } + else + { + ActualGameVersionLabel.Text = string.Format( + Properties.Resources.CompatibleGameVersionsDialogVersionDetails, + _inst.Version(), + _inst.GameVersionWhenCompatibleVersionsWereStored); + ActualGameVersionLabel.ForeColor = Color.Red; + ShowMessage(Properties.Resources.CompatibleGameVersionsDialogGameUpdatedDetails); + } } } diff --git a/GUI/Properties/Resources.resx b/GUI/Properties/Resources.resx index 7f4a7b5b5..4e225eb1f 100644 --- a/GUI/Properties/Resources.resx +++ b/GUI/Properties/Resources.resx @@ -136,7 +136,9 @@ Create junction points (Windows) or symbolic links (Unix) to stock directories instead of copying them. If you choose this option, DO NOT move or delete the old instance!! <NONE> - Game Version Updated + Default compatibility has been set based on your installed game version. These settings should bring the greatest number of working mods to the most users, but it's still possible that some older mods might misbehave. + +If you want to be cautious, click "Clear selection" to uncheck all the boxes. The game has been updated since you last reviewed your compatible game versions. Please make sure that settings are correct. {0} (previous game version: {1}) Version has invalid format