diff options
| author | Jesse Plamondon-Willard <github@jplamondonw.com> | 2017-05-19 18:04:57 -0400 |
|---|---|---|
| committer | Jesse Plamondon-Willard <github@jplamondonw.com> | 2017-05-19 18:04:57 -0400 |
| commit | 16281fb58944e7e829b184b014e27822c91c9f43 (patch) | |
| tree | e9db3b9943d61163a87190c4293673a002d17da1 | |
| parent | c84310dfebafd3085dc418f3620154f9934865de (diff) | |
| parent | cbb1777ba00f581b428e61a0f7245a87ac53cf09 (diff) | |
| download | SMAPI-16281fb58944e7e829b184b014e27822c91c9f43.tar.gz SMAPI-16281fb58944e7e829b184b014e27822c91c9f43.tar.bz2 SMAPI-16281fb58944e7e829b184b014e27822c91c9f43.zip | |
Merge branch 'develop' into stable
48 files changed, 2766 insertions, 1407 deletions
@@ -59,35 +59,38 @@ section isn't relevant to you; see the previous sections to use or create mods._ ### Compiling from source Using an official SMAPI release is recommended for most users. -If you'd like to compile SMAPI from source, you can do that on any platform using -[Visual Studio](https://www.visualstudio.com/vs/community/) or [MonoDevelop](http://www.monodevelop.com/). -SMAPI uses build configuration derived from the [crosswiki mod config](https://github.com/Pathoschild/Stardew.ModBuildConfig#readme) -to detect your current OS automatically and load the correct references. Compile output will be -placed in a `bin` folder at the root of the git repository. +SMAPI uses some C# 7 code, so you'll need at least +[Visual Studio 2017](https://www.visualstudio.com/vs/community/) on Windows, +[MonoDevelop 7.0](http://www.monodevelop.com/) on Linux, +[Visual Studio 2017 for Mac](https://www.visualstudio.com/vs/visual-studio-mac/), or an equivalent +IDE to compile it. It uses build configuration derived from the +[crossplatform mod config](https://github.com/Pathoschild/Stardew.ModBuildConfig#readme) to detect +your current OS automatically and load the correct references. Compile output will be placed in a +`bin` folder at the root of the git repository. ### Debugging a local build Rebuilding the solution in debug mode will copy the SMAPI files into your game folder. Starting -the `StardewModdingAPI` project with debugging will launch SMAPI with the debugger attached, so you -can intercept errors and step through the code being executed. +the `StardewModdingAPI` project with debugging from Visual Studio (on Mac or Windows) will launch +SMAPI with the debugger attached, so you can intercept errors and step through the code being +executed. This doesn't work in MonoDevelop on Linux, unfortunately. ### Preparing a release To prepare a crossplatform SMAPI release, you'll need to compile it on two platforms. See -_[crossplatforming a SMAPI mod](http://canimod.com/guides/crossplatforming-a-smapi-mod#preparing-a-mod-release)_ -for the first-time setup. For simplicity, all paths are relative to the root of the repository (the -folder containing `src`). +[crossplatforming info](http://stardewvalleywiki.com/Modding:Creating_a_SMAPI_mod#Test_on_all_platforms) +on the wiki for the first-time setup. 1. Update the version number in `GlobalAssemblyInfo.cs` and `Constants::Version`. Make sure you use a [semantic version](http://semver.org). Recommended format: build type | format | example :--------- | :-------------------------------- | :------ - dev build | `<version>-alpha.<timestamp>` | `1.0.0-alpha.20171230` - beta | `<version>-beta.<incrementing ID>`| `1.0.0-beta`, `1.0.0-beta.2`, … - release | `<version>` | `1.0.0` + dev build | `<version>-alpha.<timestamp>` | `1.0-alpha.20171230` + prerelease | `<version>-prerelease.<ID>` | `1.0-prerelease.2` + release | `<version>` | `1.0` 2. In Windows: 1. Rebuild the solution in _Release_ mode. - 2. Rename `bin/Packaged` to `SMAPI <version>` (e.g. `SMAPI 1.6`). + 2. Rename `bin/Packaged` to `SMAPI <version>` (e.g. `SMAPI 1.0`). 2. Transfer the `SMAPI <version>` folder to Linux or Mac. _This adds the installer executable and Windows files. We'll do the rest in Linux or Mac, since we need to set Unix file permissions that Windows won't save._ @@ -110,7 +113,8 @@ folder containing `src`). StardewModdingAPI.AssemblyRewriters.dll StardewModdingAPI.config.json StardewModdingAPI.exe - StardewModdingAPI.exe.mdb + StardewModdingAPI.pdb + StardewModdingAPI.xml steam_appid.txt System.Numerics.dll System.Runtime.Caching.dll @@ -129,7 +133,7 @@ folder containing `src`). 5. Copy & paste the `SMAPI <version>` folder as `SMAPI <version> for developers`. 6. In the `SMAPI <version>` folder... * edit `internal/Mono/StardewModdingAPI.config.json` and - `internal/Windows/StardewModdingAPI.config.json` to disable developer mode; + `internal/Windows/StardewModdingAPI.config.json` to disable developer mode; * delete `internal/Windows/StardewModdingAPI.xml`. 7. Compress the two folders into `SMAPI <version>.zip` and `SMAPI <version> for developers.zip`. @@ -143,6 +147,7 @@ field | purpose `DeveloperMode` | Default `false` (except in _SMAPI for developers_ releases). Whether to enable features intended for mod developers. Currently this only makes `TRACE`-level messages appear in the console. `CheckForUpdates` | Default `true`. Whether SMAPI should check for a newer version when you load the game. If a new version is available, a small message will appear in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background. `ModCompatibility` | A list of mod versions SMAPI should consider compatible or broken regardless of whether it detects incompatible code. Each record can be set to `AssumeCompatible` or `AssumeBroken`. Changing this field is not recommended and may destabilise your game. +`VerboseLogging` | Whether SMAPI should log more information about the game context. ### Command-line arguments SMAPI recognises the following command-line arguments. These are intended for internal use or @@ -152,3 +157,13 @@ argument | purpose -------- | ------- `--log-path "path"` | The relative or absolute path of the log file SMAPI should write. `--no-terminal` | SMAPI won't write anything to the console window. (Messages will still be written to the log file.) + +### Compile flags +SMAPI uses a small number of conditional compilation constants, which you can set by editing the +`<DefineConstants>` element in `StardewModdingAPI.csproj`. Supported constants: + +flag | purpose +---- | ------- +`EXPERIMENTAL` | Enables preview features that aren't officially released yet. +`SMAPI_FOR_WINDOWS` | Indicates that SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`. + diff --git a/release-notes.md b/release-notes.md index af535895..641071e5 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,29 @@ For mod developers: images). --> +## 1.13 +See [log](https://github.com/Pathoschild/SMAPI/compare/1.12...1.13). + +For players: +* SMAPI now recovers better from mod draw errors and detects when the error is irrecoverable. +* SMAPI now recovers automatically from errors in the game loop when possible. +* SMAPI now remembers if your game crashed and offers help next time you launch it. +* Fixed installer sometimes finding redundant game paths. +* Fixed save events not being raised after the first day on Linux/Mac. +* Fixed error on Linux/Mac when a mod loads a PNG immediately after the save is loaded. +* Updated mod compatibility list for Stardew Valley 1.2. + +For mod developers: +* Added a `Context.IsWorldReady` flag for mods to use. + _<small>This indicates whether a save is loaded and the world is finished initialising, which starts at the same point that `SaveEvents.AfterLoad` and `TimeEvents.AfterDayStarted` are raised. This is mainly useful for events which can be raised before the world is loaded (like update tick).</small>_ +* Added a `debug` console command which lets you run the game's debug commands (e.g. `debug warp FarmHouse 1 1` warps you to the farmhouse). +* Added basic context info to logs to simplify troubleshooting. +* Added a `Mod.Dispose` method which can be overriden to clean up before exit. This method isn't guaranteed to be called on every exit. +* Deprecated mods that don't have a `Name`, `Version`, or `UniqueID` in their manifest. These will be required in SMAPI 2.0. +* Deprecated `GameEvents.GameLoaded` and `GameEvents.FirstUpdateTick`. You can move any affected code into your mod's `Entry` method. +* Fixed maps not recognising custom tilesheets added through the SMAPI content API. +* Internal refactoring for upcoming features. + ## 1.12 See [log](https://github.com/Pathoschild/SMAPI/compare/1.11...1.12). @@ -40,6 +63,7 @@ For mod developers: * Added a content API which loads custom textures/maps/data from the mod's folder (`.xnb` or `.png` format) or game content. * `Console.Out` messages are now written to the log file. * `Monitor.ExitGameImmediately` now aborts SMAPI initialisation and events more quickly. +* Fixed value-changed events being raised when the player loads a save due to values being initialised. ## 1.10 See [log](https://github.com/Pathoschild/SMAPI/compare/1.9...1.10). diff --git a/src/.editorconfig b/src/.editorconfig index 3037884e..132fe6cb 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -11,6 +11,8 @@ indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true +[*.json] +indent_size = 2 ########## ## C# formatting diff --git a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj index 775de9f2..e25b201e 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj +++ b/src/StardewModdingAPI.AssemblyRewriters/StardewModdingAPI.AssemblyRewriters.csproj @@ -3,7 +3,7 @@ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <Platform Condition=" '$(Platform)' == '' ">x86</Platform> <ProjectGuid>{10DB0676-9FC1-4771-A2C8-E2519F091E49}</ProjectGuid> <OutputType>Library</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder> @@ -12,7 +12,7 @@ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> @@ -21,7 +21,7 @@ <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release\</OutputPath> @@ -29,24 +29,6 @@ <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> - <DebugSymbols>true</DebugSymbols> - <OutputPath>bin\x86\Debug\</OutputPath> - <DefineConstants>DEBUG;TRACE;SMAPI_FOR_WINDOWS</DefineConstants> - <DebugType>full</DebugType> - <PlatformTarget>x86</PlatformTarget> - <ErrorReport>prompt</ErrorReport> - <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> - <OutputPath>bin\x86\Release\</OutputPath> - <DefineConstants>TRACE;SMAPI_FOR_WINDOWS</DefineConstants> - <Optimize>true</Optimize> - <DebugType>pdbonly</DebugType> - <PlatformTarget>x86</PlatformTarget> - <ErrorReport>prompt</ErrorReport> - <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> <ItemGroup> <Reference Include="Mono.Cecil, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL"> <HintPath>..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll</HintPath> diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index 86e3d38a..01f7a01f 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -27,32 +27,39 @@ namespace StardewModdingApi.Installer switch (platform) { case Platform.Mono: - // Linux - yield return $"{Environment.GetEnvironmentVariable("HOME")}/GOG Games/Stardew Valley/game"; - yield return $"{Environment.GetEnvironmentVariable("HOME")}/.local/share/Steam/steamapps/common/Stardew Valley"; - yield return $"{Environment.GetEnvironmentVariable("HOME")}/.steam/steam/steamapps/common/Stardew Valley"; - - // Mac - yield return "/Applications/Stardew Valley.app/Contents/MacOS"; - yield return $"{Environment.GetEnvironmentVariable("HOME")}/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS"; + { + string home = Environment.GetEnvironmentVariable("HOME"); + + // Linux + yield return $"{home}/GOG Games/Stardew Valley/game"; + yield return Directory.Exists($"{home}/.steam/steam/steamapps/common/Stardew Valley") + ? $"{home}/.steam/steam/steamapps/common/Stardew Valley" + : $"{home}/.local/share/Steam/steamapps/common/Stardew Valley"; + + // Mac + yield return "/Applications/Stardew Valley.app/Contents/MacOS"; + yield return $"{home}/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS"; + } break; case Platform.Windows: - // Windows - yield return @"C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley"; - yield return @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley"; - - // Windows registry - IDictionary<string, string> registryKeys = new Dictionary<string, string> - { - [@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150"] = "InstallLocation", // Steam - [@"SOFTWARE\WOW6432Node\GOG.com\Games\1453375253"] = "PATH", // GOG on 64-bit Windows - }; - foreach (var pair in registryKeys) { - string path = this.GetLocalMachineRegistryValue(pair.Key, pair.Value); - if (!string.IsNullOrWhiteSpace(path)) - yield return path; + // Windows + yield return @"C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley"; + yield return @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley"; + + // Windows registry + IDictionary<string, string> registryKeys = new Dictionary<string, string> + { + [@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150"] = "InstallLocation", // Steam + [@"SOFTWARE\WOW6432Node\GOG.com\Games\1453375253"] = "PATH", // GOG on 64-bit Windows + }; + foreach (var pair in registryKeys) + { + string path = this.GetLocalMachineRegistryValue(pair.Key, pair.Value); + if (!string.IsNullOrWhiteSpace(path)) + yield return path; + } } break; @@ -511,7 +518,7 @@ namespace StardewModdingApi.Installer // get installed paths DirectoryInfo[] defaultPaths = ( - from path in this.GetDefaultInstallPaths(platform).Distinct() + from path in this.GetDefaultInstallPaths(platform).Distinct(StringComparer.InvariantCultureIgnoreCase) let dir = new DirectoryInfo(path) where dir.Exists && dir.EnumerateFiles(executableFilename).Any() select dir diff --git a/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj b/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj index 366e1c6e..765364dc 100644 --- a/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj +++ b/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj @@ -3,7 +3,7 @@ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <Platform Condition=" '$(Platform)' == '' ">x86</Platform> <ProjectGuid>{443DDF81-6AAF-420A-A610-3459F37E5575}</ProjectGuid> <OutputType>Exe</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder> @@ -13,8 +13,8 @@ <FileAlignment>512</FileAlignment> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <PlatformTarget>AnyCPU</PlatformTarget> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' "> + <PlatformTarget>x86</PlatformTarget> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> @@ -23,8 +23,8 @@ <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <PlatformTarget>AnyCPU</PlatformTarget> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> + <PlatformTarget>x86</PlatformTarget> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>$(SolutionDir)\..\bin\Release\Installer</OutputPath> diff --git a/src/StardewModdingAPI.Tests/Framework/Sample.cs b/src/StardewModdingAPI.Tests/Framework/Sample.cs new file mode 100644 index 00000000..10006f1e --- /dev/null +++ b/src/StardewModdingAPI.Tests/Framework/Sample.cs @@ -0,0 +1,30 @@ +using System; + +namespace StardewModdingAPI.Tests.Framework +{ + /// <summary>Provides sample values for unit testing.</summary> + internal static class Sample + { + /********* + ** Properties + *********/ + /// <summary>A random number generator.</summary> + private static readonly Random Random = new Random(); + + + /********* + ** Properties + *********/ + /// <summary>Get a sample string.</summary> + public static string String() + { + return Guid.NewGuid().ToString("N"); + } + + /// <summary>Get a sample integer.</summary> + public static int Int() + { + return Sample.Random.Next(); + } + } +} diff --git a/src/StardewModdingAPI.Tests/ModResolverTests.cs b/src/StardewModdingAPI.Tests/ModResolverTests.cs new file mode 100644 index 00000000..efa6fa06 --- /dev/null +++ b/src/StardewModdingAPI.Tests/ModResolverTests.cs @@ -0,0 +1,399 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Moq; +using Newtonsoft.Json; +using NUnit.Framework; +using StardewModdingAPI.Framework.Models; +using StardewModdingAPI.Framework.ModLoading; +using StardewModdingAPI.Framework.Serialisation; +using StardewModdingAPI.Tests.Framework; + +namespace StardewModdingAPI.Tests +{ + [TestFixture] + public class ModResolverTests + { + /********* + ** Unit tests + *********/ + /**** + ** ReadManifests + ****/ + [Test(Description = "Assert that the resolver correctly returns an empty list if there are no mods installed.")] + public void ReadBasicManifest_NoMods_ReturnsEmptyList() + { + // arrange + string rootFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(rootFolder); + + // act + IModMetadata[] mods = new ModResolver().ReadManifests(rootFolder, new JsonHelper(), new ModCompatibility[0]).ToArray(); + + // assert + Assert.AreEqual(0, mods.Length, 0, $"Expected to find zero manifests, found {mods.Length} instead."); + } + + [Test(Description = "Assert that the resolver correctly returns a failed metadata if there's an empty mod folder.")] + public void ReadBasicManifest_EmptyModFolder_ReturnsFailedManifest() + { + // arrange + string rootFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); + string modFolder = Path.Combine(rootFolder, Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(modFolder); + + // act + IModMetadata[] mods = new ModResolver().ReadManifests(rootFolder, new JsonHelper(), new ModCompatibility[0]).ToArray(); + IModMetadata mod = mods.FirstOrDefault(); + + // assert + Assert.AreEqual(1, mods.Length, 0, $"Expected to find one manifest, found {mods.Length} instead."); + Assert.AreEqual(ModMetadataStatus.Failed, mod.Status, "The mod metadata was not marked failed."); + Assert.IsNotNull(mod.Error, "The mod metadata did not have an error message set."); + } + + [Test(Description = "Assert that the resolver correctly reads manifest data from a randomised file.")] + public void ReadBasicManifest_CanReadFile() + { + // create manifest data + IDictionary<string, object> originalDependency = new Dictionary<string, object> + { + [nameof(IManifestDependency.UniqueID)] = Sample.String() + }; + IDictionary<string, object> original = new Dictionary<string, object> + { + [nameof(IManifest.Name)] = Sample.String(), + [nameof(IManifest.Author)] = Sample.String(), + [nameof(IManifest.Version)] = new SemanticVersion(Sample.Int(), Sample.Int(), Sample.Int(), Sample.String()), + [nameof(IManifest.Description)] = Sample.String(), + [nameof(IManifest.UniqueID)] = $"{Sample.String()}.{Sample.String()}", + [nameof(IManifest.EntryDll)] = $"{Sample.String()}.dll", + [nameof(IManifest.MinimumApiVersion)] = $"{Sample.Int()}.{Sample.Int()}-{Sample.String()}", +#if EXPERIMENTAL + [nameof(IManifest.Dependencies)] = new[] { originalDependency }, +#endif + ["ExtraString"] = Sample.String(), + ["ExtraInt"] = Sample.Int() + }; + + // write to filesystem + string rootFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); + string modFolder = Path.Combine(rootFolder, Guid.NewGuid().ToString("N")); + string filename = Path.Combine(modFolder, "manifest.json"); + Directory.CreateDirectory(modFolder); + File.WriteAllText(filename, JsonConvert.SerializeObject(original)); + + // act + IModMetadata[] mods = new ModResolver().ReadManifests(rootFolder, new JsonHelper(), new ModCompatibility[0]).ToArray(); + IModMetadata mod = mods.FirstOrDefault(); + + // assert + Assert.AreEqual(1, mods.Length, 0, "Expected to find one manifest."); + Assert.IsNotNull(mod, "The loaded manifest shouldn't be null."); + Assert.AreEqual(null, mod.Compatibility, "The compatibility record should be null since we didn't provide one."); + Assert.AreEqual(modFolder, mod.DirectoryPath, "The directory path doesn't match."); + Assert.AreEqual(ModMetadataStatus.Found, mod.Stat |
