summaryrefslogtreecommitdiff
path: root/src/SMAPI
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI')
-rw-r--r--src/SMAPI/Framework/IModMetadata.cs6
-rw-r--r--src/SMAPI/Framework/Input/GamePadStateBuilder.cs17
-rw-r--r--src/SMAPI/Framework/Input/MouseStateBuilder.cs23
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs1
-rw-r--r--src/SMAPI/Framework/ModLoading/ModMetadata.cs11
-rw-r--r--src/SMAPI/Framework/Networking/SGalaxyNetServer.cs29
-rw-r--r--src/SMAPI/Framework/Networking/SLidgrenServer.cs31
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/AlertContext.cs2
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/AlertEntry.cs2
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/PeakEntry.cs2
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounterEntry.cs2
-rw-r--r--src/SMAPI/Framework/Rendering/SDisplayDevice.cs13
-rw-r--r--src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs2
-rw-r--r--src/SMAPI/Framework/SCore.cs130
-rw-r--r--src/SMAPI/Framework/SGame.cs2
-rw-r--r--src/SMAPI/Framework/Serialization/ColorConverter.cs2
-rw-r--r--src/SMAPI/Framework/Serialization/PointConverter.cs2
-rw-r--r--src/SMAPI/Framework/Serialization/RectangleConverter.cs2
-rw-r--r--src/SMAPI/Framework/Serialization/Vector2Converter.cs2
19 files changed, 155 insertions, 126 deletions
diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs
index 37927482..1231b494 100644
--- a/src/SMAPI/Framework/IModMetadata.cs
+++ b/src/SMAPI/Framework/IModMetadata.cs
@@ -112,9 +112,9 @@ namespace StardewModdingAPI.Framework
/// <summary>Whether the mod has at least one valid update key set.</summary>
bool HasValidUpdateKeys();
- /// <summary>Get whether the mod has a given warning and it hasn't been suppressed in the <see cref="DataRecord"/>.</summary>
- /// <param name="warning">The warning to check.</param>
- bool HasUnsuppressWarning(ModWarning warning);
+ /// <summary>Get whether the mod has any of the given warnings which haven't been suppressed in the <see cref="DataRecord"/>.</summary>
+ /// <param name="warnings">The warnings to check.</param>
+ bool HasUnsuppressedWarnings(params ModWarning[] warnings);
/// <summary>Get a relative path which includes the root folder name.</summary>
string GetRelativePathWithRoot();
diff --git a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs
index 36622066..2657fd12 100644
--- a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs
+++ b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs
@@ -205,16 +205,13 @@ namespace StardewModdingAPI.Framework.Input
/// <summary>Get the equivalent state.</summary>
public GamePadState GetState()
{
- if (this.State == null)
- {
- this.State = new GamePadState(
- leftThumbStick: this.LeftStickPos,
- rightThumbStick: this.RightStickPos,
- leftTrigger: this.LeftTrigger,
- rightTrigger: this.RightTrigger,
- buttons: this.GetButtonBitmask() // MonoGame requires one bitmask here; don't specify multiple values
- );
- }
+ this.State ??= new GamePadState(
+ leftThumbStick: this.LeftStickPos,
+ rightThumbStick: this.RightStickPos,
+ leftTrigger: this.LeftTrigger,
+ rightTrigger: this.RightTrigger,
+ buttons: this.GetButtonBitmask() // MonoGame requires one bitmask here; don't specify multiple values
+ );
return this.State.Value;
}
diff --git a/src/SMAPI/Framework/Input/MouseStateBuilder.cs b/src/SMAPI/Framework/Input/MouseStateBuilder.cs
index 59956feb..1cc16ca9 100644
--- a/src/SMAPI/Framework/Input/MouseStateBuilder.cs
+++ b/src/SMAPI/Framework/Input/MouseStateBuilder.cs
@@ -89,19 +89,16 @@ namespace StardewModdingAPI.Framework.Input
/// <summary>Get the equivalent state.</summary>
public MouseState GetState()
{
- if (this.State == null)
- {
- this.State = new MouseState(
- x: this.X,
- y: this.Y,
- scrollWheel: this.ScrollWheelValue,
- leftButton: this.ButtonStates[SButton.MouseLeft],
- middleButton: this.ButtonStates[SButton.MouseMiddle],
- rightButton: this.ButtonStates[SButton.MouseRight],
- xButton1: this.ButtonStates[SButton.MouseX1],
- xButton2: this.ButtonStates[SButton.MouseX2]
- );
- }
+ this.State ??= new MouseState(
+ x: this.X,
+ y: this.Y,
+ scrollWheel: this.ScrollWheelValue,
+ leftButton: this.ButtonStates[SButton.MouseLeft],
+ middleButton: this.ButtonStates[SButton.MouseMiddle],
+ rightButton: this.ButtonStates[SButton.MouseRight],
+ xButton1: this.ButtonStates[SButton.MouseX1],
+ xButton2: this.ButtonStates[SButton.MouseX2]
+ );
return this.State.Value;
}
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
index d9b4af1b..5218938f 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -127,7 +127,6 @@ namespace StardewModdingAPI.Framework.ModLoading
{
if (!oneAssembly)
this.Monitor.Log($" Loading {assembly.File.Name} (rewritten)...", LogLevel.Trace);
-
using MemoryStream outStream = new MemoryStream();
assembly.Definition.Write(outStream);
byte[] bytes = outStream.ToArray();
diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs
index 0e90362e..30701552 100644
--- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs
+++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs
@@ -215,13 +215,14 @@ namespace StardewModdingAPI.Framework.ModLoading
return this.GetUpdateKeys(validOnly: true).Any();
}
- /// <summary>Get whether the mod has a given warning and it hasn't been suppressed in the <see cref="DataRecord"/>.</summary>
- /// <param name="warning">The warning to check.</param>
- public bool HasUnsuppressWarning(ModWarning warning)
+ /// <summary>Get whether the mod has any of the given warnings which haven't been suppressed in the <see cref="IModMetadata.DataRecord"/>.</summary>
+ /// <param name="warnings">The warnings to check.</param>
+ public bool HasUnsuppressedWarnings(params ModWarning[] warnings)
{
- return
+ return warnings.Any(warning =>
this.Warnings.HasFlag(warning)
- && (this.DataRecord?.DataRecord == null || !this.DataRecord.DataRecord.SuppressWarnings.HasFlag(warning));
+ && (this.DataRecord?.DataRecord == null || !this.DataRecord.DataRecord.SuppressWarnings.HasFlag(warning))
+ );
}
/// <summary>Get a relative path which includes the root folder name.</summary>
diff --git a/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs b/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs
index 7dbfa767..ac9cf313 100644
--- a/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs
+++ b/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs
@@ -45,23 +45,22 @@ namespace StardewModdingAPI.Framework.Networking
[SuppressMessage("ReSharper", "AccessToDisposedClosure", Justification = "The callback is invoked synchronously.")]
protected override void onReceiveMessage(GalaxyID peer, Stream messageStream)
{
- using (IncomingMessage message = new IncomingMessage())
- using (BinaryReader reader = new BinaryReader(messageStream))
+ using IncomingMessage message = new IncomingMessage();
+ using BinaryReader reader = new BinaryReader(messageStream);
+
+ message.Read(reader);
+ ulong peerID = peer.ToUint64(); // note: GalaxyID instances get reused, so need to store the underlying ID instead
+ this.OnProcessingMessage(message, outgoing => this.SendMessageToPeerID(peerID, outgoing), () =>
{
- message.Read(reader);
- ulong peerID = peer.ToUint64(); // note: GalaxyID instances get reused, so need to store the underlying ID instead
- this.OnProcessingMessage(message, outgoing => this.SendMessageToPeerID(peerID, outgoing), () =>
+ if (this.peers.ContainsLeft(message.FarmerID) && (long)this.peers[message.FarmerID] == (long)peerID)
+ this.gameServer.processIncomingMessage(message);
+ else if (message.MessageType == StardewValley.Multiplayer.playerIntroduction)
{
- if (this.peers.ContainsLeft(message.FarmerID) && (long)this.peers[message.FarmerID] == (long)peerID)
- this.gameServer.processIncomingMessage(message);
- else if (message.MessageType == StardewValley.Multiplayer.playerIntroduction)
- {
- NetFarmerRoot farmer = this.Multiplayer.readFarmer(message.Reader);
- GalaxyID capturedPeer = new GalaxyID(peerID);
- this.gameServer.checkFarmhandRequest(Convert.ToString(peerID), this.getConnectionId(peer), farmer, msg => this.sendMessage(capturedPeer, msg), () => this.peers[farmer.Value.UniqueMultiplayerID] = capturedPeer.ToUint64());
- }
- });
- }
+ NetFarmerRoot farmer = this.Multiplayer.readFarmer(message.Reader);
+ GalaxyID capturedPeer = new GalaxyID(peerID);
+ this.gameServer.checkFarmhandRequest(Convert.ToString(peerID), this.getConnectionId(peer), farmer, msg => this.sendMessage(capturedPeer, msg), () => this.peers[farmer.Value.UniqueMultiplayerID] = capturedPeer.ToUint64());
+ }
+ });
}
/// <summary>Send a message to a remote peer.</summary>
diff --git a/src/SMAPI/Framework/Networking/SLidgrenServer.cs b/src/SMAPI/Framework/Networking/SLidgrenServer.cs
index f2c61917..05c8b872 100644
--- a/src/SMAPI/Framework/Networking/SLidgrenServer.cs
+++ b/src/SMAPI/Framework/Networking/SLidgrenServer.cs
@@ -44,25 +44,24 @@ namespace StardewModdingAPI.Framework.Networking
{
// add hook to call multiplayer core
NetConnection peer = rawMessage.SenderConnection;
- using (IncomingMessage message = new IncomingMessage())
- using (Stream readStream = new NetBufferReadStream(rawMessage))
- using (BinaryReader reader = new BinaryReader(readStream))
+ using IncomingMessage message = new IncomingMessage();
+ using Stream readStream = new NetBufferReadStream(rawMessage);
+ using BinaryReader reader = new BinaryReader(readStream);
+
+ while (rawMessage.LengthBits - rawMessage.Position >= 8)
{
- while (rawMessage.LengthBits - rawMessage.Position >= 8)
+ message.Read(reader);
+ NetConnection connection = rawMessage.SenderConnection; // don't pass rawMessage into context because it gets reused
+ this.OnProcessingMessage(message, outgoing => this.sendMessage(connection, outgoing), () =>
{
- message.Read(reader);
- NetConnection connection = rawMessage.SenderConnection; // don't pass rawMessage into context because it gets reused
- this.OnProcessingMessage(message, outgoing => this.sendMessage(connection, outgoing), () =>
+ if (this.peers.ContainsLeft(message.FarmerID) && this.peers[message.FarmerID] == peer)
+ this.gameServer.processIncomingMessage(message);
+ else if (message.MessageType == StardewValley.Multiplayer.playerIntroduction)
{
- if (this.peers.ContainsLeft(message.FarmerID) && this.peers[message.FarmerID] == peer)
- this.gameServer.processIncomingMessage(message);
- else if (message.MessageType == StardewValley.Multiplayer.playerIntroduction)
- {
- NetFarmerRoot farmer = this.Multiplayer.readFarmer(message.Reader);
- this.gameServer.checkFarmhandRequest("", this.getConnectionId(rawMessage.SenderConnection), farmer, msg => this.sendMessage(peer, msg), () => this.peers[farmer.Value.UniqueMultiplayerID] = peer);
- }
- });
- }
+ NetFarmerRoot farmer = this.Multiplayer.readFarmer(message.Reader);
+ this.gameServer.checkFarmhandRequest("", this.getConnectionId(rawMessage.SenderConnection), farmer, msg => this.sendMessage(peer, msg), () => this.peers[farmer.Value.UniqueMultiplayerID] = peer);
+ }
+ });
}
}
}
diff --git a/src/SMAPI/Framework/PerformanceMonitoring/AlertContext.cs b/src/SMAPI/Framework/PerformanceMonitoring/AlertContext.cs
index 01197f74..af630055 100644
--- a/src/SMAPI/Framework/PerformanceMonitoring/AlertContext.cs
+++ b/src/SMAPI/Framework/PerformanceMonitoring/AlertContext.cs
@@ -1,7 +1,7 @@
namespace StardewModdingAPI.Framework.PerformanceMonitoring
{
/// <summary>The context for an alert.</summary>
- internal struct AlertContext
+ internal readonly struct AlertContext
{
/*********
** Accessors
diff --git a/src/SMAPI/Framework/PerformanceMonitoring/AlertEntry.cs b/src/SMAPI/Framework/PerformanceMonitoring/AlertEntry.cs
index f5b80189..d5a0b343 100644
--- a/src/SMAPI/Framework/PerformanceMonitoring/AlertEntry.cs
+++ b/src/SMAPI/Framework/PerformanceMonitoring/AlertEntry.cs
@@ -1,7 +1,7 @@
namespace StardewModdingAPI.Framework.PerformanceMonitoring
{
/// <summary>A single alert entry.</summary>
- internal struct AlertEntry
+ internal readonly struct AlertEntry
{
/*********
** Accessors
diff --git a/src/SMAPI/Framework/PerformanceMonitoring/PeakEntry.cs b/src/SMAPI/Framework/PerformanceMonitoring/PeakEntry.cs
index cff502ad..1746e358 100644
--- a/src/SMAPI/Framework/PerformanceMonitoring/PeakEntry.cs
+++ b/src/SMAPI/Framework/PerformanceMonitoring/PeakEntry.cs
@@ -3,7 +3,7 @@ using System;
namespace StardewModdingAPI.Framework.PerformanceMonitoring
{
/// <summary>A peak invocation time.</summary>
- internal struct PeakEntry
+ internal readonly struct PeakEntry
{
/*********
** Accessors
diff --git a/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounterEntry.cs b/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounterEntry.cs
index 8adbd88d..18cca628 100644
--- a/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounterEntry.cs
+++ b/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounterEntry.cs
@@ -3,7 +3,7 @@ using System;
namespace StardewModdingAPI.Framework.PerformanceMonitoring
{
/// <summary>A single performance counter entry.</summary>
- internal struct PerformanceCounterEntry
+ internal readonly struct PerformanceCounterEntry
{
/*********
** Accessors
diff --git a/src/SMAPI/Framework/Rendering/SDisplayDevice.cs b/src/SMAPI/Framework/Rendering/SDisplayDevice.cs
index 382949bf..85e69ae6 100644
--- a/src/SMAPI/Framework/Rendering/SDisplayDevice.cs
+++ b/src/SMAPI/Framework/Rendering/SDisplayDevice.cs
@@ -2,7 +2,6 @@ using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
-using StardewValley;
using xTile.Dimensions;
using xTile.Layers;
using xTile.ObjectModel;
@@ -14,23 +13,13 @@ namespace StardewModdingAPI.Framework.Rendering
internal class SDisplayDevice : SXnaDisplayDevice
{
/*********
- ** Fields
- *********/
- /// <summary>The origin to use when rotating tiles.</summary>
- private readonly Vector2 RotationOrigin;
-
-
- /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="contentManager">The content manager through which to load tiles.</param>
/// <param name="graphicsDevice">The graphics device with which to render tiles.</param>
public SDisplayDevice(ContentManager contentManager, GraphicsDevice graphicsDevice)
- : base(contentManager, graphicsDevice)
- {
- this.RotationOrigin = new Vector2((Game1.tileSize * Game1.pixelZoom) / 2f);
- }
+ : base(contentManager, graphicsDevice) { }
/// <summary>Draw a tile to the screen.</summary>
/// <param name="tile">The tile to draw.</param>
diff --git a/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs b/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs
index d4f62b4f..121e53bc 100644
--- a/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs
+++ b/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs
@@ -10,7 +10,7 @@ using xTile.Layers;
using xTile.Tiles;
using Rectangle = xTile.Dimensions.Rectangle;
-namespace StardewModdingAPI.Framework
+namespace StardewModdingAPI.Framework.Rendering
{
/// <summary>A map display device which reimplements the default logic.</summary>
/// <remarks>This is an exact copy of <see cref="XnaDisplayDevice"/>, except that private fields are protected and all methods are virtual.</remarks>
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index de9c955d..cd292bfc 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -1129,67 +1129,115 @@ namespace StardewModdingAPI.Framework
// log warnings
if (modsWithWarnings.Any())
{
- // issue block format logic
- void LogWarningGroup(ModWarning warning, LogLevel logLevel, string heading, params string[] blurb)
- {
- IModMetadata[] matches = modsWithWarnings
- .Where(mod => mod.HasUnsuppressWarning(warning))
- .ToArray();
- if (!matches.Any())
- return;
-
- this.Monitor.Log(" " + heading, logLevel);
- this.Monitor.Log(" " + "".PadRight(50, '-'), logLevel);
- foreach (string line in blurb)
- this.Monitor.Log(" " + line, logLevel);
- this.Monitor.Newline();
- foreach (IModMetadata match in matches)
- this.Monitor.Log($" - {match.DisplayName}", logLevel);
- this.Monitor.Newline();
- }
-
- // supported issues
- LogWarningGroup(ModWarning.BrokenCodeLoaded, LogLevel.Error, "Broken mods",
+ // broken code
+ this.LogModWarningGroup(modsWithWarnings, ModWarning.BrokenCodeLoaded, LogLevel.Error, "Broken mods",
"These mods have broken code, but you configured SMAPI to load them anyway. This may cause bugs,",
"errors, or crashes in-game."
);
- LogWarningGroup(ModWarning.ChangesSaveSerializer, LogLevel.Warn, "Changed save serializer",
+
+ // changes serializer
+ this.LogModWarningGroup(modsWithWarnings, ModWarning.ChangesSaveSerializer, LogLevel.Warn, "Changed save serializer",
"These mods change the save serializer. They may corrupt your save files, or make them unusable if",
"you uninstall these mods."
);
- if (this.Settings.ParanoidWarnings)
- {
- LogWarningGroup(ModWarning.AccessesConsole, LogLevel.Warn, "Accesses the console directly",
- "These mods directly access the SMAPI console, and you enabled paranoid warnings. (Note that this may be",
- "legitimate and innocent usage; this warning is meaningless without further investigation.)"
- );
- LogWarningGroup(ModWarning.AccessesFilesystem, LogLevel.Warn, "Accesses filesystem directly",
- "These mods directly access the filesystem, and you enabled paranoid warnings. (Note that this may be",
- "legitimate and innocent usage; this warning is meaningless without further investigation.)"
- );
- LogWarningGroup(ModWarning.AccessesShell, LogLevel.Warn, "Accesses shell/process directly",
- "These mods directly access the OS shell or processes, and you enabled paranoid warnings. (Note that",
- "this may be legitimate and innocent usage; this warning is meaningless without further investigation.)"
- );
- }
- LogWarningGroup(ModWarning.PatchesGame, LogLevel.Info, "Patched game code",
+
+ // patched game code
+ this.LogModWarningGroup(modsWithWarnings, ModWarning.PatchesGame, LogLevel.Info, "Patched game code",
"These mods directly change the game code. They're more likely to cause errors or bugs in-game; if",
"your game has issues, try removing these first. Otherwise you can ignore this warning."
);
- LogWarningGroup(ModWarning.UsesUnvalidatedUpdateTick, LogLevel.Info, "Bypassed safety checks",
+
+ // unvalidated update tick
+ this.LogModWarningGroup(modsWithWarnings, ModWarning.UsesUnvalidatedUpdateTick, LogLevel.Info, "Bypassed safety checks",
"These mods bypass SMAPI's normal safety checks, so they're more likely to cause errors or save",
"corruption. If your game has issues, try removing these first."
);
- LogWarningGroup(ModWarning.NoUpdateKeys, LogLevel.Debug, "No update keys",
+
+ // paranoid warnings
+ if (this.Settings.ParanoidWarnings)
+ {
+ this.LogModWarningGroup(
+ modsWithWarnings,
+ match: mod => mod.HasUnsuppressedWarnings(ModWarning.AccessesConsole, ModWarning.AccessesFilesystem, ModWarning.AccessesShell),
+ level: LogLevel.Debug,
+ heading: "Direct system access",
+ blurb: new[]
+ {
+ "You enabled paranoid warnings and these mods directly access the filesystem, shells/processes, or",
+ "SMAPI console. (This is usually legitimate and innocent usage; this warning is only useful for",
+ "further investigation.)"
+ },
+ modLabel: mod =>
+ {
+ List<string> labels = new List<string>();
+ if (mod.HasUnsuppressedWarnings(ModWarning.AccessesConsole))
+ labels.Add("console");
+ if (mod.HasUnsuppressedWarnings(ModWarning.AccessesFilesystem))
+ labels.Add("files");
+ if (mod.HasUnsuppressedWarnings(ModWarning.AccessesShell))
+ labels.Add("shells/processes");
+
+ return $"{mod.DisplayName} ({string.Join(", ", labels)})";
+ }
+ );
+ }
+
+ // no update keys
+ this.LogModWarningGroup(modsWithWarnings, ModWarning.NoUpdateKeys, LogLevel.Debug, "No update keys",
"These mods have no update keys in their manifest. SMAPI may not notify you about updates for these",
"mods. Consider notifying the mod authors about this problem."
);
- LogWarningGroup(ModWarning.UsesDynamic, LogLevel.Debug, "Not crossplatform",
+
+ // not crossplatform
+ this.LogModWarningGroup(modsWithWarnings, ModWarning.UsesDynamic, LogLevel.Debug, "Not crossplatform",
"These mods use the 'dynamic' keyword, and won't work on Linux/Mac."
);
}
}
+ /// <summary>Write a mod warning group to the console and log.</summary>
+ /// <param name="mods">The mods to search.</param>
+ /// <param name="match">Matches mods to include in the warning group.</param>
+ /// <param name="level">The log level for the logged messages.</param>
+ /// <param name="heading">A brief heading label for the group.</param>
+ /// <param name="blurb">A detailed explanation of the warning, split into lines.</param>
+ /// <param name="modLabel">Formats the mod label, or <c>null</c> to use the <see cref="IModMetadata.DisplayName"/>.</param>
+ private void LogModWarningGroup(IModMetadata[] mods, Func<IModMetadata, bool> match, LogLevel level, string heading, string[] blurb, Func<IModMetadata, string> modLabel = null)
+ {
+ // get matching mods
+ string[] modLabels = mods
+ .Where(match)
+ .Select(mod => modLabel?.Invoke(mod) ?? mod.DisplayName)
+ .OrderBy(p => p)
+ .ToArray();
+ if (!modLabels.Any())
+ return;
+
+ // log header/blurb
+ this.Monitor.Log(" " + heading, level);
+ this.Monitor.Log(" " + "".PadRight(50, '-'), level);
+ foreach (string line in blurb)
+ this.Monitor.Log(" " + line, level);
+ this.Monitor.Newline();
+
+ // log mod list
+ foreach (string label in modLabels)
+ this.Monitor.Log($" - {label}", level);
+
+ this.Monitor.Newline();
+ }
+
+ /// <summary>Write a mod warning group to the console and log.</summary>
+ /// <param name="mods">The mods to search.</param>
+ /// <param name="warning">The mod warning to match.</param>
+ /// <param name="level">The log level for the logged messages.</param>
+ /// <param name="heading">A brief heading label for the group.</param>
+ /// <param name="blurb">A detailed explanation of the warning, split into lines.</param>
+ void LogModWarningGroup(IModMetadata[] mods, ModWarning warning, LogLevel level, string heading, params string[] blurb)
+ {
+ this.LogModWarningGroup(mods, mod => mod.HasUnsuppressedWarnings(warning), level, heading, blurb);
+ }
+
/// <summary>Load a mod's entry class.</summary>
/// <param name="modAssembly">The mod assembly.</param>
/// <param name="mod">The loaded instance.</param>
diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs
index 2a30b595..82db5857 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -1310,7 +1310,7 @@ namespace StardewModdingAPI.Framework
}
Game1.drawPlayerHeldObject(Game1.player);
}
- label_139:
+ label_139:
if ((Game1.player.UsingTool || Game1.pickingTool) && Game1.player.CurrentTool != null && ((!Game1.player.CurrentTool.Name.Equals("Seeds") || Game1.pickingTool) && (Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.getStandingX(), (int)Game1.player.Position.Y - 38), Game1.viewport.Size) != null && Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.getStandingX(), Game1.player.getStandingY()), Game1.viewport.Size) == null)))
Game1.drawTool(Game1.player);
if (Game1.currentLocation.Map.GetLayer("AlwaysFront") != null)
diff --git a/src/SMAPI/Framework/Serialization/ColorConverter.cs b/src/SMAPI/Framework/Serialization/ColorConverter.cs
index 19979981..7315f1a5 100644
--- a/src/SMAPI/Framework/Serialization/ColorConverter.cs
+++ b/src/SMAPI/Framework/Serialization/ColorConverter.cs
@@ -35,7 +35,7 @@ namespace StardewModdingAPI.Framework.Serialization
{
string[] parts = str.Split(',');
if (parts.Length != 4)
- throw new SParseException($"Can't parse {typeof(Color).Name} from invalid value '{str}' (path: {path}).");
+ throw new SParseException($"Can't parse {nameof(Color)} from invalid value '{str}' (path: {path}).");
int r = Convert.ToInt32(parts[0]);
int g = Convert.ToInt32(parts[1]);
diff --git a/src/SMAPI/Framework/Serialization/PointConverter.cs b/src/SMAPI/Framework/Serialization/PointConverter.cs
index 3481c9b2..6cf795dc 100644
--- a/src/SMAPI/Framework/Serialization/PointConverter.cs
+++ b/src/SMAPI/Framework/Serialization/PointConverter.cs
@@ -33,7 +33,7 @@ namespace StardewModdingAPI.Framework.Serialization
{
string[] parts = str.Split(',');
if (parts.Length != 2)
- throw new SParseException($"Can't parse {typeof(Point).Name} from invalid value '{str}' (path: {path}).");
+ throw new SParseException($"Can't parse {nameof(Point)} from invalid value '{str}' (path: {path}).");
int x = Convert.ToInt32(parts[0]);
int y = Convert.ToInt32(parts[1]);
diff --git a/src/SMAPI/Framework/Serialization/RectangleConverter.cs b/src/SMAPI/Framework/Serialization/RectangleConverter.cs
index fbb2e253..a5780d8a 100644
--- a/src/SMAPI/Framework/Serialization/RectangleConverter.cs
+++ b/src/SMAPI/Framework/Serialization/RectangleConverter.cs
@@ -39,7 +39,7 @@ namespace StardewModdingAPI.Framework.Serialization
var match = Regex.Match(str, @"^\{X:(?<x>\d+) Y:(?<y>\d+) Width:(?<width>\d+) Height:(?<height>\d+)\}$", RegexOptions.IgnoreCase);
if (!match.Success)
- throw new SParseException($"Can't parse {typeof(Rectangle).Name} from invalid value '{str}' (path: {path}).");
+ throw new SParseException($"Can't parse {nameof(Rectangle)} from invalid value '{str}' (path: {path}).");
int x = Convert.ToInt32(match.Groups["x"].Value);
int y = Convert.ToInt32(match.Groups["y"].Value);
diff --git a/src/SMAPI/Framework/Serialization/Vector2Converter.cs b/src/SMAPI/Framework/Serialization/Vector2Converter.cs
index 1d9b08e0..3e2ab776 100644
--- a/src/SMAPI/Framework/Serialization/Vector2Converter.cs
+++ b/src/SMAPI/Framework/Serialization/Vector2Converter.cs
@@ -33,7 +33,7 @@ namespace StardewModdingAPI.Framework.Serialization
{
string[] parts = str.Split(',');
if (parts.Length != 2)
- throw new SParseException($"Can't parse {typeof(Vector2).Name} from invalid value '{str}' (path: {path}).");
+ throw new SParseException($"Can't parse {nameof(Vector2)} from invalid value '{str}' (path: {path}).");
float x = Convert.ToSingle(parts[0]);
float y = Convert.ToSingle(parts[1]);