Skip to content

Commit 821167e

Browse files
committed
Merge branch 'develop' into stable
2 parents da22197 + 13c4c1c commit 821167e

22 files changed

Lines changed: 291 additions & 250 deletions

File tree

.github/workflows/build-smapi.yml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,18 +94,6 @@ jobs:
9494
path: 'bin/SMAPI-${{env.VERSION}}-installer.zip'
9595
if-no-files-found: 'error'
9696

97-
- name: Create GitHub release
98-
uses: ncipollo/release-action@v1
99-
if: github.ref_type == 'tag'
100-
with:
101-
artifacts: 'bin/SMAPI-${{env.VERSION}}-installer.zip'
102-
token: '${{secrets.GITHUB_TOKEN}}'
103-
tag: '${{github.ref_name}}'
104-
name: '${{env.VERSION}}'
105-
body: |
106-
Draft.
107-
draft: true
108-
10997
- name: Generate artifact attestations
11098
uses: actions/attest-build-provenance@v2
11199
if: github.ref_type == 'tag'

build/common.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ repo. It imports the other MSBuild files as needed.
77
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
88
<PropertyGroup>
99
<!--set general build properties -->
10-
<Version>4.5.1</Version>
10+
<Version>4.5.2</Version>
1111
<Product>SMAPI</Product>
1212
<LangVersion>latest</LangVersion>
1313
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>

build/scripts/prepare-install-package.ps1

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
#
88
#
99

10-
. "$PSScriptRoot/lib/in-place-regex.ps1"
11-
1210

1311
##########
1412
## Read arguments

docs/README.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,26 +52,25 @@ developers and other modders!
5252
* [Technical docs](technical/smapi.md)
5353

5454
## Translating SMAPI
55-
SMAPI rarely shows text in-game, so it only has a few translations. Contributions are welcome! See
56-
[Modding:Translations](https://stardewvalleywiki.com/Modding:Translations) on the wiki for help
57-
contributing translations.
55+
Contributions are welcome! See [Modding:Translations](https://stardewvalleywiki.com/Modding:Translations)
56+
on the wiki for help contributing translations.
5857

5958
locale | status
6059
----------- | :----------------
61-
default | [partly translated](../src/SMAPI/i18n/default.json)
60+
default | [original text](../src/SMAPI/i18n/default.json)
6261
Chinese | ↻ [partly translated](../src/SMAPI/i18n/zh.json)
63-
French | [partly translated](../src/SMAPI/i18n/fr.json)
62+
French | [fully translated](../src/SMAPI/i18n/fr.json)
6463
German | ↻ [partly translated](../src/SMAPI/i18n/de.json)
6564
Hungarian | ↻ [partly translated](../src/SMAPI/i18n/hu.json)
66-
Indonesian | [partly translated](../src/SMAPI/i18n/id.json)
65+
Indonesian | [fully translated](../src/SMAPI/i18n/id.json)
6766
Italian | ↻ [partly translated](../src/SMAPI/i18n/it.json)
6867
Japanese | ↻ [partly translated](../src/SMAPI/i18n/ja.json)
6968
Korean | ↻ [partly translated](../src/SMAPI/i18n/ko.json)
7069
[Polish] | ↻ [partly translated](../src/SMAPI/i18n/pl.json)
7170
Portuguese | ✓ [fully translated](../src/SMAPI/i18n/pt.json)
72-
Russian | [partly translated](../src/SMAPI/i18n/ru.json)
73-
Spanish | [partly translated](../src/SMAPI/i18n/es.json)
74-
[Thai] | [partly translated](../src/SMAPI/i18n/th.json)
71+
Russian | [fully translated](../src/SMAPI/i18n/ru.json)
72+
Spanish | [partly translated](../src/SMAPI/i18n/es.json)
73+
[Thai] | [partly translated](../src/SMAPI/i18n/th.json)
7574
Turkish | ↻ [partly translated](../src/SMAPI/i18n/tr.json)
7675
[Ukrainian] | ↻ [partly translated](../src/SMAPI/i18n/uk.json)
7776

docs/release-notes.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
[README](README.md)
22

33
# Release notes
4+
## 4.5.2
5+
Released 14 March 2026 for Stardew Valley 1.6.14 or later.
6+
7+
* For players:
8+
* Improved performance a bit.
9+
* Fixed the Linux/macOS installer not saving the color scheme correctly in 4.5.0+.
10+
* Fixed typo in config UI text (thanks to QuentiumYT!).
11+
* Improved translations. Thanks to dekthaiinchina (updated Thai), dewanggatrustha (updated Indonesian), QuentiumYT (updated French), Timur13240
12+
(updated Russian), and vlcoo (updated Spanish)!
13+
14+
* For mod authors:
15+
* Fixed input API ignoring controller overrides when there's no controller plugged in (thanks to spacechase0!).
16+
* Fixed asset propagation for the farmhouse map not updating the farmhouse fridge position.
17+
418
## 4.5.1
5-
Released 25 January 2026 for Stardew Valley 1.6.14 or later.
19+
Released 25 January 2026 for Stardew Valley 1.6.14 or later. See [build attestation](https://github.com/Pathoschild/SMAPI/attestations/17385144).
620

721
* For players:
822
* Fixed error installing SMAPI 4.5.0 on Linux/macOS.

src/SMAPI.Installer/InteractiveInstaller.cs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
using System.IO;
77
using System.Linq;
88
using System.Reflection;
9+
using Newtonsoft.Json.Linq;
910
using StardewModdingApi.Installer.Enums;
1011
using StardewModdingAPI.Installer.Framework;
1112
using StardewModdingAPI.Internal.ConsoleWriting;
1213
using StardewModdingAPI.Toolkit;
1314
using StardewModdingAPI.Toolkit.Framework;
1415
using StardewModdingAPI.Toolkit.Framework.GameScanning;
1516
using StardewModdingAPI.Toolkit.Framework.ModScanning;
17+
using StardewModdingAPI.Toolkit.Serialization;
1618
using StardewModdingAPI.Toolkit.Utilities;
1719

1820
namespace StardewModdingApi.Installer;
@@ -501,10 +503,10 @@ public void Run(string[] args)
501503
// set SMAPI's color scheme if defined
502504
if (scheme != MonitorColorScheme.AutoDetect)
503505
{
504-
string text = File
505-
.ReadAllText(paths.ApiConfigPath)
506-
.Replace(@"""UseScheme"": ""AutoDetect""", $@"""UseScheme"": ""{scheme}""");
507-
File.WriteAllText(paths.ApiConfigPath, text);
506+
this.SaveUserSettings(paths.ApiUserConfigPath, new Dictionary<string, object>
507+
{
508+
["ConsoleColorScheme"] = scheme.ToString()
509+
});
508510
}
509511
}
510512
}
@@ -559,6 +561,33 @@ private string GetDisplayText(MonitorColorScheme scheme)
559561
}
560562
}
561563

564+
/// <summary>Save the given options to a SMAPI internal config file.</summary>
565+
/// <param name="filePath">The file path to edit.</param>
566+
/// <param name="settings">The settings to add or overwrite.</param>
567+
[SuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "This isn't an issue here, since we're only using JToken and object.")]
568+
private void SaveUserSettings(string filePath, Dictionary<string, object> settings)
569+
{
570+
JsonHelper jsonHelper = new();
571+
572+
// merge into existing settings
573+
Dictionary<string, JToken> saveSettings = settings.ToDictionary(p => p.Key, p => JToken.FromObject(p.Value));
574+
try
575+
{
576+
if (jsonHelper.ReadJsonFileIfExists(filePath, out Dictionary<string, JToken>? fileSettings))
577+
{
578+
foreach ((string key, JToken value) in fileSettings)
579+
saveSettings.TryAdd(key, value);
580+
}
581+
}
582+
catch (Exception ex)
583+
{
584+
this.PrintWarning($"Couldn't parse your SMAPI settings file. Replacing it with a default version.\n\nTechnical details:\n{ex}");
585+
}
586+
587+
// save file
588+
jsonHelper.WriteJsonFile(filePath, saveSettings);
589+
}
590+
562591
/// <summary>Print a message without formatting.</summary>
563592
/// <param name="text">The text to print.</param>
564593
private void PrintPlain(string text)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"Name": "Console Commands",
33
"Author": "SMAPI",
4-
"Version": "4.5.1",
4+
"Version": "4.5.2",
55
"Description": "Adds SMAPI console commands that let you manipulate the game.",
66
"UniqueId": "SMAPI.ConsoleCommands",
77
"EntryDll": "ConsoleCommands.dll",
8-
"MinimumApiVersion": "4.5.1"
8+
"MinimumApiVersion": "4.5.2"
99
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"Name": "Save Backup",
33
"Author": "SMAPI",
4-
"Version": "4.5.1",
4+
"Version": "4.5.2",
55
"Description": "Automatically backs up all your saves once per day into its folder.",
66
"UniqueId": "SMAPI.SaveBackup",
77
"EntryDll": "SaveBackup.dll",
8-
"MinimumApiVersion": "4.5.1"
8+
"MinimumApiVersion": "4.5.2"
99
}

src/SMAPI/Constants.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ internal static class EarlyConstants
4949
internal static int? LogScreenId { get; set; }
5050

5151
/// <summary>SMAPI's current raw semantic version.</summary>
52-
internal static string RawApiVersion = "4.5.1";
52+
internal static string RawApiVersion = "4.5.2";
5353
}
5454

5555
/// <summary>Contains SMAPI's constants and assumptions.</summary>

src/SMAPI/Framework/Input/GamePadStateBuilder.cs

Lines changed: 42 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Collections.Generic;
2-
using System.Diagnostics.CodeAnalysis;
32
using System.Linq;
43
using Microsoft.Xna.Framework;
54
using Microsoft.Xna.Framework.Input;
@@ -22,7 +21,7 @@ internal class GamePadStateBuilder : IInputStateBuilder<GamePadStateBuilder, Gam
2221
private GamePadState? State;
2322

2423
/// <summary>The current button states.</summary>
25-
private readonly IDictionary<SButton, ButtonState>? ButtonStates;
24+
private readonly Dictionary<SButton, ButtonState> ButtonStates = [];
2625

2726
/// <summary>The left trigger value.</summary>
2827
private float LeftTrigger;
@@ -37,62 +36,58 @@ internal class GamePadStateBuilder : IInputStateBuilder<GamePadStateBuilder, Gam
3736
private Vector2 RightStickPos;
3837

3938

40-
/*********
41-
** Accessors
42-
*********/
43-
/// <summary>Whether the gamepad is currently connected.</summary>
44-
[MemberNotNullWhen(true, nameof(GamePadStateBuilder.ButtonStates))]
45-
public bool IsConnected { get; }
46-
47-
4839
/*********
4940
** Public methods
5041
*********/
51-
/// <summary>Construct an instance.</summary>
52-
/// <param name="state">The initial state.</param>
53-
public GamePadStateBuilder(GamePadState state)
42+
/// <inheritdoc />
43+
public void Reset(GamePadState state)
5444
{
5545
this.State = state;
56-
this.IsConnected = state.IsConnected;
57-
58-
if (!this.IsConnected)
59-
return;
6046

61-
GamePadDPad pad = state.DPad;
62-
GamePadButtons buttons = state.Buttons;
63-
GamePadTriggers triggers = state.Triggers;
64-
GamePadThumbSticks sticks = state.ThumbSticks;
65-
this.ButtonStates = new Dictionary<SButton, ButtonState>
47+
if (state.IsConnected)
6648
{
67-
[SButton.DPadUp] = pad.Up,
68-
[SButton.DPadDown] = pad.Down,
69-
[SButton.DPadLeft] = pad.Left,
70-
[SButton.DPadRight] = pad.Right,
71-
72-
[SButton.ControllerA] = buttons.A,
73-
[SButton.ControllerB] = buttons.B,
74-
[SButton.ControllerX] = buttons.X,
75-
[SButton.ControllerY] = buttons.Y,
76-
[SButton.LeftStick] = buttons.LeftStick,
77-
[SButton.RightStick] = buttons.RightStick,
78-
[SButton.LeftShoulder] = buttons.LeftShoulder,
79-
[SButton.RightShoulder] = buttons.RightShoulder,
80-
[SButton.ControllerBack] = buttons.Back,
81-
[SButton.ControllerStart] = buttons.Start,
82-
[SButton.BigButton] = buttons.BigButton
83-
};
84-
this.LeftTrigger = triggers.Left;
85-
this.RightTrigger = triggers.Right;
86-
this.LeftStickPos = sticks.Left;
87-
this.RightStickPos = sticks.Right;
49+
GamePadDPad pad = state.DPad;
50+
GamePadButtons buttons = state.Buttons;
51+
GamePadTriggers triggers = state.Triggers;
52+
GamePadThumbSticks sticks = state.ThumbSticks;
53+
54+
var states = this.ButtonStates;
55+
states.Clear();
56+
states[SButton.DPadUp] = pad.Up;
57+
states[SButton.DPadDown] = pad.Down;
58+
states[SButton.DPadLeft] = pad.Left;
59+
states[SButton.DPadRight] = pad.Right;
60+
states[SButton.ControllerA] = buttons.A;
61+
states[SButton.ControllerB] = buttons.B;
62+
states[SButton.ControllerX] = buttons.X;
63+
states[SButton.ControllerY] = buttons.Y;
64+
states[SButton.LeftStick] = buttons.LeftStick;
65+
states[SButton.RightStick] = buttons.RightStick;
66+
states[SButton.LeftShoulder] = buttons.LeftShoulder;
67+
states[SButton.RightShoulder] = buttons.RightShoulder;
68+
states[SButton.ControllerBack] = buttons.Back;
69+
states[SButton.ControllerStart] = buttons.Start;
70+
states[SButton.BigButton] = buttons.BigButton;
71+
72+
this.LeftTrigger = triggers.Left;
73+
this.RightTrigger = triggers.Right;
74+
this.LeftStickPos = sticks.Left;
75+
this.RightStickPos = sticks.Right;
76+
}
77+
else
78+
{
79+
this.ButtonStates.Clear();
80+
81+
this.LeftTrigger = 0;
82+
this.RightTrigger = 0;
83+
this.LeftStickPos = Vector2.Zero;
84+
this.RightStickPos = Vector2.Zero;
85+
}
8886
}
8987

9088
/// <inheritdoc />
9189
public GamePadStateBuilder OverrideButtons(IDictionary<SButton, SButtonState> overrides)
9290
{
93-
if (!this.IsConnected)
94-
return this;
95-
9691
foreach (var pair in overrides)
9792
{
9893
bool changed = true;
@@ -138,10 +133,7 @@ public GamePadStateBuilder OverrideButtons(IDictionary<SButton, SButtonState> ov
138133

139134
// buttons
140135
default:
141-
if (this.ButtonStates.ContainsKey(pair.Key))
142-
this.ButtonStates[pair.Key] = isDown ? ButtonState.Pressed : ButtonState.Released;
143-
else
144-
changed = false;
136+
this.ButtonStates[pair.Key] = isDown ? ButtonState.Pressed : ButtonState.Released;
145137
break;
146138
}
147139

@@ -155,9 +147,6 @@ public GamePadStateBuilder OverrideButtons(IDictionary<SButton, SButtonState> ov
155147
/// <inheritdoc />
156148
public IEnumerable<SButton> GetPressedButtons()
157149
{
158-
if (!this.IsConnected)
159-
yield break;
160-
161150
// buttons
162151
foreach (Buttons button in this.GetPressedGamePadButtons())
163152
yield return button.ToSButton();
@@ -213,9 +202,6 @@ public GamePadState GetState()
213202
/// <summary>Get the pressed gamepad buttons.</summary>
214203
private IEnumerable<Buttons> GetPressedGamePadButtons()
215204
{
216-
if (!this.IsConnected)
217-
yield break;
218-
219205
foreach (var pair in this.ButtonStates)
220206
{
221207
if (pair.Value == ButtonState.Pressed && pair.Key.TryGetController(out Buttons button))

0 commit comments

Comments
 (0)