summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/Command.cs12
-rw-r--r--src/SMAPI/Framework/CommandManager.cs31
-rw-r--r--src/SMAPI/Framework/Content/AssetDataForImage.cs41
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs68
-rw-r--r--src/SMAPI/Framework/ContentPack.cs18
-rw-r--r--src/SMAPI/Framework/DeprecationManager.cs49
-rw-r--r--src/SMAPI/Framework/DeprecationWarning.cs38
-rw-r--r--src/SMAPI/Framework/Events/EventManager.cs400
-rw-r--r--src/SMAPI/Framework/Events/ManagedEvent.cs24
-rw-r--r--src/SMAPI/Framework/Events/ManagedEventBase.cs12
-rw-r--r--src/SMAPI/Framework/Events/ModDisplayEvents.cs93
-rw-r--r--src/SMAPI/Framework/Events/ModEvents.cs16
-rw-r--r--src/SMAPI/Framework/Events/ModGameLoopEvents.cs82
-rw-r--r--src/SMAPI/Framework/Events/ModInputEvents.cs24
-rw-r--r--src/SMAPI/Framework/Events/ModMultiplayerEvents.cs43
-rw-r--r--src/SMAPI/Framework/Events/ModPlayerEvents.cs43
-rw-r--r--src/SMAPI/Framework/Events/ModSpecialisedEvents.cs36
-rw-r--r--src/SMAPI/Framework/Events/ModWorldEvents.cs42
-rw-r--r--src/SMAPI/Framework/IModMetadata.cs30
-rw-r--r--src/SMAPI/Framework/InternalExtensions.cs12
-rw-r--r--src/SMAPI/Framework/ModHelpers/CommandHelper.cs15
-rw-r--r--src/SMAPI/Framework/ModHelpers/DataHelper.cs166
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModHelper.cs26
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs8
-rw-r--r--src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs42
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs11
-rw-r--r--src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs8
-rw-r--r--src/SMAPI/Framework/ModLoading/ModMetadata.cs42
-rw-r--r--src/SMAPI/Framework/ModLoading/ModResolver.cs23
-rw-r--r--src/SMAPI/Framework/ModLoading/ModWarning.cs8
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/VirtualEntryCallRemover.cs90
-rw-r--r--src/SMAPI/Framework/ModRegistry.cs5
-rw-r--r--src/SMAPI/Framework/Models/SConfig.cs8
-rw-r--r--src/SMAPI/Framework/Monitor.cs15
-rw-r--r--src/SMAPI/Framework/Networking/MessageType.cs26
-rw-r--r--src/SMAPI/Framework/Networking/ModMessageModel.cs72
-rw-r--r--src/SMAPI/Framework/Networking/MultiplayerPeer.cs84
-rw-r--r--src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs30
-rw-r--r--src/SMAPI/Framework/Networking/RemoteContextModModel.cs15
-rw-r--r--src/SMAPI/Framework/Networking/RemoteContextModel.cs24
-rw-r--r--src/SMAPI/Framework/Networking/SGalaxyNetClient.cs52
-rw-r--r--src/SMAPI/Framework/Networking/SGalaxyNetServer.cs63
-rw-r--r--src/SMAPI/Framework/Networking/SLidgrenClient.cs50
-rw-r--r--src/SMAPI/Framework/Networking/SLidgrenServer.cs65
-rw-r--r--src/SMAPI/Framework/SCore.cs1361
-rw-r--r--src/SMAPI/Framework/SGame.cs544
-rw-r--r--src/SMAPI/Framework/SModHooks.cs34
-rw-r--r--src/SMAPI/Framework/SMultiplayer.cs484
-rw-r--r--src/SMAPI/Framework/Singleton.cs10
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs2
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs2
-rw-r--r--src/SMAPI/Framework/StateTracking/PlayerTracker.cs20
-rw-r--r--src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs10
53 files changed, 3906 insertions, 623 deletions
diff --git a/src/SMAPI/Framework/Command.cs b/src/SMAPI/Framework/Command.cs
index 943e018d..8c9df47d 100644
--- a/src/SMAPI/Framework/Command.cs
+++ b/src/SMAPI/Framework/Command.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
namespace StardewModdingAPI.Framework
{
@@ -8,8 +8,8 @@ namespace StardewModdingAPI.Framework
/*********
** Accessor
*********/
- /// <summary>The friendly name for the mod that registered the command.</summary>
- public string ModName { get; }
+ /// <summary>The mod that registered the command (or <c>null</c> if registered by SMAPI).</summary>
+ public IModMetadata Mod { get; }
/// <summary>The command name, which the user must type to trigger it.</summary>
public string Name { get; }
@@ -25,13 +25,13 @@ namespace StardewModdingAPI.Framework
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- /// <param name="modName">The friendly name for the mod that registered the command.</param>
+ /// <param name="mod">The mod that registered the command (or <c>null</c> if registered by SMAPI).</param>
/// <param name="name">The command name, which the user must type to trigger it.</param>
/// <param name="documentation">The human-readable documentation shown when the player runs the built-in 'help' command.</param>
/// <param name="callback">The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user.</param>
- public Command(string modName, string name, string documentation, Action<string, string[]> callback)
+ public Command(IModMetadata mod, string name, string documentation, Action<string, string[]> callback)
{
- this.ModName = modName;
+ this.Mod = mod;
this.Name = name;
this.Documentation = documentation;
this.Callback = callback;
diff --git a/src/SMAPI/Framework/CommandManager.cs b/src/SMAPI/Framework/CommandManager.cs
index f9651ed9..aabe99c3 100644
--- a/src/SMAPI/Framework/CommandManager.cs
+++ b/src/SMAPI/Framework/CommandManager.cs
@@ -19,7 +19,7 @@ namespace StardewModdingAPI.Framework
** Public methods
*********/
/// <summary>Add a console command.</summary>
- /// <param name="modName">The friendly mod name for this instance.</param>
+ /// <param name="mod">The mod adding the command (or <c>null</c> for a SMAPI command).</param>
/// <param name="name">The command name, which the user must type to trigger it.</param>
/// <param name="documentation">The human-readable documentation shown when the player runs the built-in 'help' command.</param>
/// <param name="callback">The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user.</param>
@@ -27,7 +27,7 @@ namespace StardewModdingAPI.Framework
/// <exception cref="ArgumentNullException">The <paramref name="name"/> or <paramref name="callback"/> is null or empty.</exception>
/// <exception cref="FormatException">The <paramref name="name"/> is not a valid format.</exception>
/// <exception cref="ArgumentException">There's already a command with that name.</exception>
- public void Add(string modName, string name, string documentation, Action<string, string[]> callback, bool allowNullCallback = false)
+ public void Add(IModMetadata mod, string name, string documentation, Action<string, string[]> callback, bool allowNullCallback = false)
{
name = this.GetNormalisedName(name);
@@ -44,7 +44,7 @@ namespace StardewModdingAPI.Framework
throw new ArgumentException(nameof(callback), $"Can't register the '{name}' command because there's already a command with that name.");
// add command
- this.Commands.Add(name, new Command(modName, name, documentation, callback));
+ this.Commands.Add(name, new Command(mod, name, documentation, callback));
}
/// <summary>Get a command by its unique name.</summary>
@@ -65,19 +65,30 @@ namespace StardewModdingAPI.Framework
.OrderBy(p => p.Name);
}
- /// <summary>Trigger a command.</summary>
- /// <param name="input">The raw command input.</param>
- /// <returns>Returns whether a matching command was triggered.</returns>
- public bool Trigger(string input)
+ /// <summary>Try to parse a raw line of user input into an executable command.</summary>
+ /// <param name="input">The raw user input.</param>
+ /// <param name="name">The parsed command name.</param>
+ /// <param name="args">The parsed command arguments.</param>
+ /// <param name="command">The command which can handle the input.</param>
+ /// <returns>Returns true if the input was successfully parsed and matched to a command; else false.</returns>
+ public bool TryParse(string input, out string name, out string[] args, out Command command)
{
+ // ignore if blank
if (string.IsNullOrWhiteSpace(input))
+ {
+ name = null;
+ args = null;
+ command = null;
return false;
+ }
- string[] args = this.ParseArgs(input);
- string name = args[0];
+ // parse input
+ args = this.ParseArgs(input);
+ name = this.GetNormalisedName(args[0]);
args = args.Skip(1).ToArray();
- return this.Trigger(name, args);
+ // get command
+ return this.Commands.TryGetValue(name, out command);
}
/// <summary>Trigger a command.</summary>
diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs
index 5c7b87de..f970762a 100644
--- a/src/SMAPI/Framework/Content/AssetDataForImage.cs
+++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs
@@ -8,6 +8,14 @@ namespace StardewModdingAPI.Framework.Content
internal class AssetDataForImage : AssetData<Texture2D>, IAssetDataForImage
{
/*********
+ ** Properties
+ *********/
+ /// <summary>The minimum value to consider non-transparent.</summary>
+ /// <remarks>On Linux/Mac, fully transparent pixels may have an alpha up to 4 for some reason.</remarks>
+ private const byte MinOpacity = 5;
+
+
+ /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
@@ -53,13 +61,40 @@ namespace StardewModdingAPI.Framework.Content
// merge data in overlay mode
if (patchMode == PatchMode.Overlay)
{
+ // get target data
+ Color[] targetData = new Color[pixelCount];
+ target.GetData(0, targetArea, targetData, 0, pixelCount);
+
+ // merge pixels
Color[] newData = new Color[targetArea.Value.Width * targetArea.Value.Height];
target.GetData(0, targetArea, newData, 0, newData.Length);
for (int i = 0; i < sourceData.Length; i++)
{
- Color pixel = sourceData[i];
- if (pixel.A > 4) // not transparent (note: on Linux/Mac, fully transparent pixels may have an alpha up to 4 for some reason)
- newData[i] = pixel;
+ Color above = sourceData[i];
+ Color below = targetData[i];
+
+ // shortcut transparency
+ if (above.A < AssetDataForImage.MinOpacity)
+ continue;
+ if (below.A < AssetDataForImage.MinOpacity)
+ {
+ newData[i] = above;
+ continue;
+ }
+
+ // merge pixels
+ // This performs a conventional alpha blend for the pixels, which are already
+ // premultiplied by the content pipeline. The formula is derived from
+ // https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/.
+ // Note: don't use named arguments here since they're different between
+ // Linux/Mac and Windows.
+ float alphaBelow = 1 - (above.A / 255f);
+ newData[i] = new Color(
+ (int)(above.R + (below.R * alphaBelow)), // r
+ (int)(above.G + (below.G * alphaBelow)), // g
+ (int)(above.B + (below.B * alphaBelow)), // b
+ Math.Max(above.A, below.A) // a
+ );
}
sourceData = newData;
}
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index 24ce69ea..ed76a925 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -165,63 +165,25 @@ namespace StardewModdingAPI.Framework.ContentManagers
return file;
}
- /// <summary>Premultiply a texture's alpha values to avoid transparency issues in the game. This is only possible if the game isn't currently drawing.</summary>
+ /// <summary>Premultiply a texture's alpha values to avoid transparency issues in the game.</summary>
/// <param name="texture">The texture to premultiply.</param>
/// <returns>Returns a premultiplied texture.</returns>
- /// <remarks>Based on <a href="https://gist.github.com/Layoric/6255384">code by Layoric</a>.</remarks>
+ /// <remarks>Based on <a href="https://gamedev.stackexchange.com/a/26037">code by David Gouveia</a>.</remarks>
private Texture2D PremultiplyTransparency(Texture2D texture)
{
- // validate
- if (Context.IsInDrawLoop)
- throw new NotSupportedException("Can't load a PNG file while the game is drawing to the screen. Make sure you load content outside the draw loop.");
-
- // process texture
- SpriteBatch spriteBatch = Game1.spriteBatch;
- GraphicsDevice gpu = Game1.graphics.GraphicsDevice;
- using (RenderTarget2D renderTarget = new RenderTarget2D(Game1.graphics.GraphicsDevice, texture.Width, texture.Height))
- {
- // create blank render target to premultiply
- gpu.SetRenderTarget(renderTarget);
- gpu.Clear(Color.Black);
-
- // multiply each color by the source alpha, and write just the color values into the final texture
- spriteBatch.Begin(SpriteSortMode.Immediate, new BlendState
- {
- ColorDestinationBlend = Blend.Zero,
- ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue,
- AlphaDestinationBlend = Blend.Zero,
- AlphaSourceBlend = Blend.SourceAlpha,
- ColorSourceBlend = Blend.SourceAlpha
- });
- spriteBatch.Draw(texture, texture.Bounds, Color.White);
- spriteBatch.End();
-
- // copy the alpha values from the source texture into the final one without multiplying them
- spriteBatch.Begin(SpriteSortMode.Immediate, new BlendState
- {
- ColorWriteChannels = ColorWriteChannels.Alpha,
- AlphaDestinationBlend = Blend.Zero,
- ColorDestinationBlend = Blend.Zero,
- AlphaSourceBlend = Blend.One,
- ColorSourceBlend = Blend.One
- });
- spriteBatch.Draw(texture, texture.Bounds, Color.White);
- spriteBatch.End();
-
- // release GPU
- gpu.SetRenderTarget(null);
-
- // extract premultiplied data
- Color[] data = new Color[texture.Width * texture.Height];
- renderTarget.GetData(data);
-
- // unset texture from GPU to regain control
- gpu.Textures[0] = null;
-
- // update texture with premultiplied data
- texture.SetData(data);
- }
-
+ // Textures loaded by Texture2D.FromStream are already premultiplied on Linux/Mac, even
+ // though the XNA documentation explicitly says otherwise. That's a glitch in MonoGame
+ // fixed in newer versions, but the game uses a bundled version that will always be
+ // affected. See https://github.com/MonoGame/MonoGame/issues/4820 for more info.
+ if (Constants.TargetPlatform != GamePlatform.Windows)
+ return texture;
+
+ // premultiply pixels
+ Color[] data = new Color[texture.Width * texture.Height];
+ texture.GetData(data);
+ for (int i = 0; i < data.Length; i++)
+ data[i] = Color.FromNonPremultiplied(data[i].ToVector4());
+ texture.SetData(data);
return texture;
}
}
diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs
index 62d8b80d..49285388 100644
--- a/src/SMAPI/Framework/ContentPack.cs
+++ b/src/SMAPI/Framework/ContentPack.cs
@@ -51,14 +51,32 @@ namespace StardewModdingAPI.Framework
/// <typeparam name="TModel">The model type.</typeparam>
/// <param name="path">The file path relative to the contnet directory.</param>
/// <returns>Returns the deserialised model, or <c>null</c> if the file doesn't exist or is empty.</returns>
+ /// <exception cref="InvalidOperationException">The <paramref name="path"/> is not relative or contains directory climbing (../).</exception>
public TModel ReadJsonFile<TModel>(string path) where TModel : class
{
+ if (!PathUtilities.IsSafeRelativePath(path))
+ throw new InvalidOperationException($"You must call {nameof(IContentPack)}.{nameof(this.ReadJsonFile)} with a relative path.");
+
path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path));
return this.JsonHelper.ReadJsonFileIfExists(path, out TModel model)
? model
: null;
}
+ /// <summary>Save data to a JSON file in the content pack's folder.</summary>
+ /// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
+ /// <param name="path">The file path relative to the mod folder.</param>
+ /// <param name="data">The arbitrary data to save.</param>
+ /// <exception cref="InvalidOperationException">The <paramref name="path"/> is not relative or contains directory climbing (../).</exception>
+ public void WriteJsonFile<TModel>(string path, TModel data) where TModel : class
+ {
+ if (!PathUtilities.IsSafeRelativePath(path))
+ throw new InvalidOperationException($"You must call {nameof(IContentPack)}.{nameof(this.WriteJsonFile)} with a relative path.");
+
+ path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path));
+ this.JsonHelper.WriteJsonFile(path, data);
+ }
+
/// <summary>Load content from the content pack folder (if not already cached), and return it. When loading a <c>.png</c> file, this must be called outside the game's draw loop.</summary>
/// <typeparam name="T">The expected data type. The main supported types are <see cref="Map"/>, <see cref="Texture2D"/>, and dictionaries; other types may be supported by the game's content pipeline.</typeparam>
/// <param name="key">The local path to a content file relative to the content pack folder.</param>
diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs
index 7a824a05..0fde67ee 100644
--- a/src/SMAPI/Framework/DeprecationManager.cs
+++ b/src/SMAPI/Framework/DeprecationManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
namespace StardewModdingAPI.Framework
{
@@ -18,6 +19,9 @@ namespace StardewModdingAPI.Framework
/// <summary>Tracks the installed mods.</summary>
private readonly ModRegistry ModRegistry;
+ /// <summary>The queued deprecation warnings to display.</summary>
+ private readonly IList<DeprecationWarning> QueuedWarnings = new List<DeprecationWarning>();
+
/*********
** Public methods
@@ -51,29 +55,40 @@ namespace StardewModdingAPI.Framework
if (!this.MarkWarned(source ?? "<unknown>", nounPhrase, version))
return;
- // build message
- string message = $"{source ?? "An unknown mod"} uses deprecated code ({nounPhrase} is deprecated since SMAPI {version}).";
- if (source == null)
- message += $"{Environment.NewLine}{Environment.StackTrace}";
+ // queue warning
+ this.QueuedWarnings.Add(new DeprecationWarning(source, nounPhrase, version, severity));
+ }
- // log message
- switch (severity)
+ /// <summary>Print any queued messages.</summary>
+ public void PrintQueued()
+ {
+ foreach (DeprecationWarning warning in this.QueuedWarnings.OrderBy(p => p.ModName).ThenBy(p => p.NounPhrase))
{
- case DeprecationLevel.Notice:
- this.Monitor.Log(message, LogLevel.Trace);
- break;
+ // build message
+ string message = $"{warning.ModName ?? "An unknown mod"} uses deprecated code ({warning.NounPhrase} is deprecated since SMAPI {warning.Version}).";
+ if (warning.ModName == null)
+ message += $"{Environment.NewLine}{Environment.StackTrace}";
+
+ // log message
+ switch (warning.Level)
+ {
+ case DeprecationLevel.Notice:
+ this.Monitor.Log(message, LogLevel.Trace);
+ break;
- case DeprecationLevel.Info:
- this.Monitor.Log(message, LogLevel.Debug);
- break;
+ case DeprecationLevel.Info:
+ this.Monitor.Log(message, LogLevel.Debug);
+ break;
- case DeprecationLevel.PendingRemoval:
- this.Monitor.Log(message, LogLevel.Warn);
- break;
+ case DeprecationLevel.PendingRemoval:
+ this.Monitor.Log(message, LogLevel.Warn);
+ break;
- default:
- throw new NotSupportedException($"Unknown deprecation level '{severity}'");
+ default:
+ throw new NotSupportedException($"Unknown deprecation level '{warning.Level}'.");
+ }
}
+ this.QueuedWarnings.Clear();
}
/// <summary>Mark a deprecation warning as already logged.</summary>
diff --git a/src/SMAPI/Framework/DeprecationWarning.cs b/src/SMAPI/Framework/DeprecationWarning.cs
new file mode 100644
index 00000000..25415012
--- /dev/null
+++ b/src/SMAPI/Framework/DeprecationWarning.cs
@@ -0,0 +1,38 @@
+namespace StardewModdingAPI.Framework
+{
+ /// <summary>A deprecation warning for a mod.</summary>
+ internal class DeprecationWarning
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The affected mod's display name.</summary>
+ public string ModName { get; }
+
+ /// <summary>A noun phrase describing what is deprecated.</summary>
+ public string NounPhrase { get; }
+
+ /// <summary>The SMAPI version which deprecated it.</summary>
+ public string Version { get; }
+
+ /// <summary>The deprecation level for the affected code.</summary>
+ public DeprecationLevel Level { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="modName">The affected mod's display name.</param>
+ /// <param name="nounPhrase">A noun phrase describing what is deprecated.</param>
+ /// <param name="version">The SMAPI version which deprecated it.</param>
+ /// <param name="level">The deprecation level for the affected code.</param>
+ public DeprecationWarning(string modName, string nounPhrase, string version, DeprecationLevel level)
+ {
+ this.ModName = modName;
+ this.NounPhrase = nounPhrase;
+ this.Version = version;
+ this.Level = level;
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs
index 168ddde0..b9d1c453 100644
--- a/src/SMAPI/Framework/Events/EventManager.cs
+++ b/src/SMAPI/Framework/Events/EventManager.cs
@@ -12,55 +12,148 @@ namespace StardewModdingAPI.Framework.Events
** Events (new)
*********/
/****
+ ** Display
+ ****/