diff options
| author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2018-12-16 17:25:58 -0500 |
|---|---|---|
| committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2018-12-16 17:25:58 -0500 |
| commit | b214a76965d98ca785b64b490533b6bf66371a48 (patch) | |
| tree | 76017f8b5762d9cb4e179d32a6b44270e6726051 | |
| parent | 13ed6decf55a7fd72c34b965397011d3012cb9cc (diff) | |
| parent | fd0af5f3c149629b91bbe1651a7bda9564b860eb (diff) | |
| download | SMAPI-b214a76965d98ca785b64b490533b6bf66371a48.tar.gz SMAPI-b214a76965d98ca785b64b490533b6bf66371a48.tar.bz2 SMAPI-b214a76965d98ca785b64b490533b6bf66371a48.zip | |
Merge branch 'develop' into stable
49 files changed, 481 insertions, 285 deletions
diff --git a/build/GlobalAssemblyInfo.cs b/build/GlobalAssemblyInfo.cs index c2841c72..15ae2ecc 100644 --- a/build/GlobalAssemblyInfo.cs +++ b/build/GlobalAssemblyInfo.cs @@ -1,5 +1,5 @@ using System.Reflection; [assembly: AssemblyProduct("SMAPI")] -[assembly: AssemblyVersion("2.9.1")] -[assembly: AssemblyFileVersion("2.9.1")] +[assembly: AssemblyVersion("2.9.2")] +[assembly: AssemblyFileVersion("2.9.2")] diff --git a/build/prepare-install-package.targets b/build/prepare-install-package.targets index 3a870bd2..cd758e96 100644 --- a/build/prepare-install-package.targets +++ b/build/prepare-install-package.targets @@ -33,7 +33,6 @@ <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(TargetDir)\windows-exe-config.xml" DestinationFiles="$(PackagePath)\internal\$(PlatformName)-install.exe.config" /> <!--copy bundle files--> - <Copy SourceFiles="$(TargetDir)\unix-launcher.sh" DestinationFiles="$(PackagePath)\bundle\StardewModdingAPI" /> <Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.exe" DestinationFolder="$(PackagePath)\bundle" /> <Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.pdb" DestinationFolder="$(PackagePath)\bundle" /> <Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.xml" DestinationFolder="$(PackagePath)\bundle" /> @@ -50,9 +49,10 @@ <Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.pdb" DestinationFolder="$(PackagePath)\bundle\smapi-internal" /> <Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.xml" DestinationFolder="$(PackagePath)\bundle\smapi-internal" /> <Copy SourceFiles="@(CompiledMods)" DestinationFolder="$(PackagePath)\bundle\Mods\%(RecursiveDir)" /> - <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(TargetDir)\windows-exe-config.xml" DestinationFiles="$(PackagePath)\bundle\StardewModdingAPI.exe.config" /> + <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(TargetDir)\unix-launcher.sh" DestinationFiles="$(PackagePath)\bundle\StardewModdingAPI" /> <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Numerics.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" /> <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Runtime.Caching.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" /> + <Copy Condition="$(OS) == 'Windows_NT'" SourceFiles="$(TargetDir)\windows-exe-config.xml" DestinationFiles="$(PackagePath)\bundle\StardewModdingAPI.exe.config" /> <!-- fix errors on Linux/Mac (sample: https://log.smapi.io/mMdFUpgB) --> <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(RootPath)\build\lib\System.Numerics.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" /> diff --git a/docs/README.md b/docs/README.md index b8e3b50b..e4220de2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ -**SMAPI** is an open-source modding API for [Stardew Valley](https://stardewvalley.net/) that lets -you play the game with mods. It's safely installed alongside the game's executable, and doesn't -change any of your game files. It serves eight main purposes: +**SMAPI** is an open-source modding framework and API for [Stardew Valley](https://stardewvalley.net/) +that lets you play the game with mods. It's safely installed alongside the game's executable, and +doesn't change any of your game files. It serves eight main purposes: 1. **Load mods into the game.** _SMAPI loads mods when the game is starting up so they can interact with it. (Code mods aren't diff --git a/docs/release-notes.md b/docs/release-notes.md index 9047ae88..95de15ec 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,4 +1,24 @@ # Release notes +## 2.9.2 +* For players: + * SMAPI now prevents invalid items from crashing the game on hover. + * Fixed some multiplayer features broken when connecting via Steam friends. + * Fixed cryptic error message when the game isn't installed correctly. + * Fixed error when a mod makes invalid changes to an NPC schedule. + * Fixed game launch errors logged as `SMAPI` instead of `game`. + * Fixed Windows installer adding unneeded Unix launcher to game folder. + +* For modders: + * Moved content pack methods into a new [content pack API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Content_Packs). + * Fixed invalid NPC data propagated when a mod changes NPC dispositions. + * Fixed `Display.RenderedWorld` event broken in SMAPI 2.9.1. + * **Deprecations:** + * The `assetData.AsDictionary<TKey, TValue>().Set` methods are now deprecated. Mods should access the `Data` property directly instead. + * The content pack methods directly on `helper` are now deprecated. Mods should use `helper.ContentPacks` instead. + +* For SMAPI developers: + * Added SMAPI 3.0 readiness to mod API data. + ## 2.9.1 * For players: * Fixed crash in SMAPI 2.9 when constructing certain buildings. diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index fa977039..3d539967 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "2.9.1", + "Version": "2.9.2", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "2.9.1" + "MinimumApiVersion": "2.9.2" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index 1875adca..20935880 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "2.9.1", + "Version": "2.9.2", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "2.9.1" + "MinimumApiVersion": "2.9.2" } diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs index 0bd71d26..4e3aaed3 100644 --- a/src/SMAPI.Web/Startup.cs +++ b/src/SMAPI.Web/Startup.cs @@ -165,6 +165,7 @@ namespace StardewModdingAPI.Web redirects.Add(new RedirectToUrlRule(@"^/3\.0\.?$", "https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_3.0")); redirects.Add(new RedirectToUrlRule(@"^/docs\.?$", "https://stardewvalleywiki.com/Modding:Index")); redirects.Add(new RedirectToUrlRule(@"^/install\.?$", "https://stardewvalleywiki.com/Modding:Player_Guide/Getting_Started#Install_SMAPI")); + redirects.Add(new RedirectToUrlRule(@"^/troubleshoot(.*)$", "https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting$1")); // redirect legacy canimod.com URLs var wikiRedirects = new Dictionary<string, string[]> diff --git a/src/SMAPI.Web/ViewModels/ModModel.cs b/src/SMAPI.Web/ViewModels/ModModel.cs index 5c1840fc..f1a52f98 100644 --- a/src/SMAPI.Web/ViewModels/ModModel.cs +++ b/src/SMAPI.Web/ViewModels/ModModel.cs @@ -31,6 +31,12 @@ namespace StardewModdingAPI.Web.ViewModels /// <summary>The compatibility status for the beta version of the game.</summary> public ModCompatibilityModel BetaCompatibility { get; set; } + /// <summary>Whether the mod is ready for the upcoming SMAPI 3.0.</summary> + public string Smapi3Status { get; set; } + + /// <summary>A URL related to the <see cref="Smapi3Status"/>.</summary> + public string Smapi3Url { get; set; } + /// <summary>Links to the available mod pages.</summary> public ModLinkModel[] ModPages { get; set; } @@ -59,6 +65,8 @@ namespace StardewModdingAPI.Web.ViewModels this.SourceUrl = this.GetSourceUrl(entry); this.Compatibility = new ModCompatibilityModel(entry.Compatibility); this.BetaCompatibility = entry.BetaCompatibility != null ? new ModCompatibilityModel(entry.BetaCompatibility) : null; + this.Smapi3Status = entry.Smapi3Status.ToString().ToLower(); + this.Smapi3Url = entry.Smapi3Url; this.ModPages = this.GetModPageUrls(entry).ToArray(); this.Warnings = entry.Warnings; this.Slug = entry.Anchor; diff --git a/src/SMAPI.Web/Views/Mods/Index.cshtml b/src/SMAPI.Web/Views/Mods/Index.cshtml index a49a24d9..a6c94cf1 100644 --- a/src/SMAPI.Web/Views/Mods/Index.cshtml +++ b/src/SMAPI.Web/Views/Mods/Index.cshtml @@ -45,7 +45,10 @@ </div> </div> <div id="mod-count" v-show="showAdvanced"> - <span v-if="visibleStats.total > 0">{{visibleStats.total}} mods shown ({{Math.round((visibleStats.compatible + visibleStats.workaround) / visibleStats.total * 100)}}% compatible or have a workaround, {{Math.round((visibleStats.soon + visibleStats.broken) / visibleStats.total * 100)}}% broken, {{Math.round(visibleStats.abandoned / visibleStats.total * 100)}}% obsolete).</span> + <div v-if="visibleStats.total > 0"> + {{visibleStats.total}} mods shown ({{Math.round((visibleStats.compatible + visibleStats.workaround) / visibleStats.total * 100)}}% compatible or have a workaround, {{Math.round((visibleStats.soon + visibleStats.broken) / visibleStats.total * 100)}}% broken, {{Math.round(visibleStats.abandoned / visibleStats.total * 100)}}% obsolete).<br /> + SMAPI 3.0 (upcoming): {{Math.round(visibleStats.smapi3_ok / visibleStats.total * 100)}}% ready, {{Math.round(visibleStats.smapi3_soon / visibleStats.total * 100)}}% soon, {{Math.round(visibleStats.smapi3_broken / visibleStats.total * 100)}}% broken, {{Math.round(visibleStats.smapi3_unknown / visibleStats.total * 100)}}% unknown. + </div> <span v-else>No matching mods found.</span> </div> <table class="wikitable" id="mod-list"> @@ -57,11 +60,12 @@ <th>compatibility</th> <th v-show="showAdvanced">broke in</th> <th v-show="showAdvanced">code</th> + <th v-show="showAdvanced"><a href="http://smapi.io/3.0">3.0</a></th> <th> </th> </tr> </thead> <tbody> - <tr v-for="mod in mods" :key="mod.Name" v-bind:id="mod.Slug" :key="mod.Slug" v-bind:data-status="mod.BetaCompatibility != null ? mod.BetaCompatibility.Status : mod.Compatibility.Status" v-show="mod.Visible"> + <tr v-for="mod in mods" :key="mod.Name" v-bind:id="mod.Slug" :key="mod.Slug" v-bind:data-status="mod.LatestCompatibility.Status" v-show="mod.Visible"> <td> {{mod.Name}} <small class="mod-alt-names" v-if="mod.AlternateNames">(aka {{mod.AlternateNames}})</small> @@ -83,11 +87,19 @@ </div> <div v-for="(warning, i) in mod.Warnings">⚠ {{warning}}</div> </td> - <td class="mod-broke-in" v-html="mod.BetaCompatibility ? mod.BetaCompatibility.BrokeIn : mod.Compatibility.BrokeIn" v-show="showAdvanced"></td> + <td class="mod-broke-in" v-html="mod.LatestCompatibility.BrokeIn" v-show="showAdvanced"></td> <td v-show="showAdvanced"> <span v-if="mod.SourceUrl"><a v-bind:href="mod.SourceUrl">source</a></span> <span v-else class="mod-closed-source">no source</span> </td> + <td v-show="showAdvanced"> + <template v-if="mod.LatestCompatibility.Status == 'ok' || mod.LatestCompatibility.Status == 'unofficial' || mod.Smapi3Status == 'ok' || mod.Smapi3Status == 'soon'"> + <small v-if="mod.Smapi3Status == 'ok'">✓</small> + <small v-else-if="mod.Smapi3Status == 'broken'">✖</small> + <small v-else-if="mod.Smapi3Status == 'soon' && mod.Smapi3Url"><a v-bind:href="mod.Smapi3Url">↻ soon</a></small> + <small v-else>↻ {{mod.Smapi3Status}}</small> + </template> + </td> <td> <small><a v-bind:href="'#' + mod.Slug">#</a></small> </td> diff --git a/src/SMAPI.Web/wwwroot/Content/js/mods.js b/src/SMAPI.Web/wwwroot/Content/js/mods.js index f7a8501e..28992908 100644 --- a/src/SMAPI.Web/wwwroot/Content/js/mods.js +++ b/src/SMAPI.Web/wwwroot/Content/js/mods.js @@ -11,7 +11,11 @@ smapi.modList = function (mods) { soon: 0, broken: 0, abandoned: 0, - invalid: 0 + invalid: 0, + smapi3_unknown: 0, + smapi3_ok: 0, + smapi3_broken: 0, + smapi3_soon: 0 }; var data = { mods: mods, @@ -88,6 +92,28 @@ smapi.modList = function (mods) { id: "show-custom", value: true } + }, + "SMAPI 3.0": { + ok: { + label: "ready", + id: "show-smapi-3-ready", + value: true + }, + soon: { + label: "soon", + id: "show-smapi-3-soon", + value: true + }, + broken: { + label: "broken", + id: "show-smapi-3-broken", + value: true + }, + unknown: { + label: "unknown", + id: "show-smapi-3-unknown", + value: true + } } }, search: "" @@ -98,6 +124,9 @@ smapi.modList = function (mods) { // set initial visibility mod.Visible = true; + // set overall compatibility + mod.LatestCompatibility = mod.BetaCompatibility || mod.Compatibility; + // concatenate searchable text mod.SearchableText = [mod.Name, mod.AlternateNames, mod.Author, mod.AlternateAuthors, mod.Compatibility.Summary, mod.BrokeIn]; if (mod.Compatibility.UnofficialVersion) @@ -154,6 +183,7 @@ smapi.modList = function (mods) { if (mod.Visible) { stats.total++; stats[this.getCompatibilityGroup(mod)]++; + stats["smapi3_" + mod.Smapi3Status]++; } } }, @@ -175,10 +205,14 @@ smapi.modList = function (mods) { return false; // check status - var status = (mod.BetaCompatibility || mod.Compatibility).Status; + var status = mod.LatestCompatibility.Status; if (filters.status[status] && !filters.status[status].value) return false; + // check SMAPI 3.0 compatibility + if (filters["SMAPI 3.0"][mod.Smapi3Status] && !filters["SMAPI 3.0"][mod.Smapi3Status].value) + return false; + // check download sites var ignoreSites = []; @@ -219,7 +253,7 @@ smapi.modList = function (mods) { * @returns {string} The compatibility group (one of 'compatible', 'workaround', 'soon', 'broken', 'abandoned', or 'invalid'). */ getCompatibilityGroup: function (mod) { - var status = (mod.BetaCompatibility || mod.Compatibility).Status; + var status = mod.LatestCompatibility.Status; switch (status) { // obsolete case "abandoned": diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 959ab3c7..13c30032 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -29,7 +29,7 @@ namespace StardewModdingAPI ** Public ****/ /// <summary>SMAPI's current semantic version.</summary> - public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.9.1"); + public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.9.2"); /// <summary>The minimum supported version of Stardew Valley.</summary> public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.3.32"); diff --git a/src/SMAPI/Events/ContentEvents.cs b/src/SMAPI/Events/ContentEvents.cs index 99369cae..1a2dd526 100644 --- a/src/SMAPI/Events/ContentEvents.cs +++ b/src/SMAPI/Events/ContentEvents.cs @@ -15,9 +15,6 @@ namespace StardewModdingAPI.Events /// <summary>The core event manager.</summary> private static EventManager EventManager; - /// <summary>Manages deprecation warnings.</summary> - private static DeprecationManager DeprecationManager; - /********* ** Events @@ -27,7 +24,7 @@ namespace StardewModdingAPI.Events { add { - ContentEvents.DeprecationManager.WarnForOldEvents(); + SCore.DeprecationManager.WarnForOldEvents(); ContentEvents.EventManager.Legacy_LocaleChanged.Add(value); } remove => ContentEvents.EventManager.Legacy_LocaleChanged.Remove(value); @@ -39,11 +36,9 @@ namespace StardewModdingAPI.Events *********/ /// <summary>Initialise the events.</summary> /// <param name="eventManager">The core event manager.</param> - /// <param name="deprecationManager">Manages deprecation warnings.</param> - internal static void Init(EventManager eventManager, DeprecationManager deprecationManager) + internal static void Init(EventManager eventManager) { ContentEvents.EventManager = eventManager; - ContentEvents.DeprecationManager = deprecationManager; } } } diff --git a/src/SMAPI/Events/ControlEvents.cs b/src/SMAPI/Events/ControlEvents.cs index 5626ff81..be849f95 100644 --- a/src/SMAPI/Events/ControlEvents.cs +++ b/src/SMAPI/Events/ControlEvents.cs @@ -16,9 +16,6 @@ namespace StardewModdingAPI.Events /// <summary>The core event manager.</summary> private static EventManager EventManager; - /// <summary>Manages deprecation warnings.</summary> - private static DeprecationManager DeprecationManager; - /********* ** Events @@ -28,7 +25,7 @@ namespace StardewModdingAPI.Events { add { - ControlEvents.DeprecationManager.WarnForOldEvents(); + SCore.DeprecationManager.WarnForOldEvents(); ControlEvents.EventManager.Legacy_KeyboardChanged.Add(value); } remove => ControlEvents.EventManager.Legacy_KeyboardChanged.Remove(value); @@ -39,7 +36,7 @@ namespace StardewModdingAPI.Events { add { - ControlEvents.DeprecationManager.WarnForOldEvents(); + SCore.DeprecationManager.WarnForOldEvents(); ControlEvents.EventManager.Legacy_KeyPressed.Add(value); } remove => ControlEvents.EventManager.Legacy_KeyPressed.Remove(value); @@ -50,7 +47,7 @@ namespace StardewModdingAPI.Events { add { - ControlEvents.DeprecationManager.WarnForOldEvents(); + SCore.DeprecationManager.WarnForOldEvents(); ControlEvents.EventManager.Legacy_KeyReleased.Add(value); } remove => ControlEvents.EventManager.Legacy_KeyReleased.Remove(value); @@ -61,7 +58,7 @@ namespace StardewModdingAPI.Events { add { - ControlEvents.DeprecationManager.WarnForOldEvents(); + SCore.DeprecationManager.WarnForOldEvents(); ControlEvents.EventManager.Legacy_MouseChanged.Add(value); } remove => ControlEvents.EventManager.Legacy_MouseChanged.Remove(value); @@ -72,7 +69,7 @@ namespace StardewModdingAPI.Events { add { - ControlEvents.DeprecationManager.WarnForOldEvents(); + SCore.DeprecationManager.WarnForOldEvents(); ControlEvents.EventManager.Legacy_ControllerButtonPressed.Add(value); } remove => ControlEvents.EventManager.Legacy_ControllerButtonPressed.Remove(value); @@ -83,7 +80,7 @@ namespace StardewModdingAPI.Events { add { - ControlEvents.DeprecationManager.WarnForOldEvents(); + SCore.DeprecationManager.WarnForOldEvents(); ControlEvents.EventManager.Legacy_ControllerButtonReleased.Add(value); } remove => ControlEvents.EventManager.Legacy_ControllerButtonReleased.Remove(value); @@ -94,7 +91,7 @@ namespace StardewModdingAPI.Events { add { - ControlEvents.DeprecationManager.WarnForOldEvents(); + SCore.DeprecationManager.WarnForOldEvents(); ControlEvents.EventManager.Legacy_ControllerTriggerPressed.Add(value); } remove => ControlEvents.EventManager.Legacy_ControllerTriggerPressed.Remove(value); @@ -105,7 +102,7 @@ namespace StardewModdingAPI.Events { add { - ControlEvents.DeprecationManager.WarnForOldEvents(); + SCore.DeprecationManager.WarnForOldEvents(); ControlEvents.EventManager.Legacy_ControllerTriggerReleased.Add(value); } remove => ControlEvents.EventManager.Legacy_ControllerTriggerReleased.Remove(value); @@ -117,11 +114,9 @@ namespace StardewModdingAPI.Events *********/ /// <summary>Initialise the events.</summary> /// <param name="eventManager">The core event manager.</param> - /// <param name="deprecationManager">Manages deprecation warnings.</param> - internal static void Init(EventManager eventManager, DeprecationManager deprecationManager) + internal static void Init(EventManager eventManager) { ControlEvents.EventManager = eventManager; - ControlEvents.DeprecationManager = deprecationManager; } } } diff --git a/src/SMAPI/Events/GameEvents.cs b/src/SMAPI/Events/GameEvents.cs index 39b77f99..6069a185 100644 --- a/src/SMAPI/Events/GameEvents.cs +++ b/src/SMAPI/Events/GameEvents.cs @@ -15,9 +15,6 @@ namespace StardewModdingAPI.Events /// <summary>The core event manager.</summary> private static EventManager EventManager; - /// <summary>Manages deprecation warnings.</summary> - private static DeprecationManager DeprecationManager; - /********* ** Events @@ -27,7 +24,7 @@ namespace StardewModdingAPI.Events { add { - GameEvents.DeprecationManager.WarnForOldEvents(); + SCore.DeprecationManager.WarnForOldEvents(); GameEvents.EventManager.Legacy_UpdateTick.Add(value); } remove => GameEvents.EventManager.Legacy_UpdateTick.Remove(value); @@ -38,7 +35,7 @@ namespace StardewModdingAPI.Events { add { - GameEvents.DeprecationManager.WarnForOldEvents(); + SCore.DeprecationManager.WarnForOldEvents(); GameEvents.EventManager.Legacy_SecondUpdateTick.Add(value); } remove => GameEvents.EventManager.Legacy_SecondUpdateTick.Remove(value); @@ -49,7 +46,7 @@ namespace StardewModdingAPI.Events { add { - |
