diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs | 43 | ||||
-rw-r--r-- | src/SMAPI.Web/Controllers/LogParserController.cs | 177 | ||||
-rw-r--r-- | src/SMAPI.Web/Framework/Clients/Pastebin/PasteInfo.cs | 10 | ||||
-rw-r--r-- | src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs | 2 | ||||
-rw-r--r-- | src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs | 16 | ||||
-rw-r--r-- | src/SMAPI.Web/SMAPI.Web.csproj | 2 | ||||
-rw-r--r-- | src/SMAPI.Web/Startup.cs | 1 | ||||
-rw-r--r-- | src/SMAPI.Web/ViewModels/LogParserModel.cs | 9 | ||||
-rw-r--r-- | src/SMAPI.Web/Views/LogParser/Index.cshtml | 21 | ||||
-rw-r--r-- | src/SMAPI.Web/appsettings.Development.json | 3 | ||||
-rw-r--r-- | src/SMAPI.Web/appsettings.json | 35 | ||||
-rw-r--r-- | src/SMAPI.Web/wwwroot/Content/css/log-parser.css | 6 | ||||
-rw-r--r-- | src/SMAPI.Web/wwwroot/SMAPI.metadata.json | 37 | ||||
-rw-r--r-- | src/SMAPI/Metadata/CoreAssetPropagator.cs | 53 | ||||
-rw-r--r-- | src/SMAPI/i18n/ru.json | 3 | ||||
-rw-r--r-- | src/SMAPI/i18n/zh.json | 3 |
16 files changed, 371 insertions, 50 deletions
diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index 4d9091b0..0648aa2b 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -5,6 +5,7 @@ using System.Linq; using Microsoft.Xna.Framework; using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData; using StardewValley; +using StardewValley.Menus; using StardewValley.Objects; using StardewValley.Tools; using SObject = StardewValley.Object; @@ -108,7 +109,10 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // spawn main item SObject item; { - SearchableItem main = this.TryCreate(ItemType.Object, id, () => new SObject(id, 1)); + SearchableItem main = this.TryCreate(ItemType.Object, id, () => id == 812 + ? new ColoredObject(id, 1, Color.White) + : new SObject(id, 1) + ); yield return main; item = main?.Item as SObject; } @@ -189,6 +193,43 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework return honey; }); } + + // roe and aged roe (derived from FishPond.GetFishProduce) + else if (id == 812) + { + foreach (var pair in Game1.objectInformation) + { + // get input + SObject input = new SObject(pair.Key, 1); + if (input.Category != SObject.FishCategory) + continue; + Color color = TailoringMenu.GetDyeColor(input) ?? Color.Orange; + + // yield roe + SObject roe = new ColoredObject(812, 1, color) + { + name = $"{input.Name} Roe", + preserve = { Value = SObject.PreserveType.Roe }, + preservedParentSheetIndex = { Value = input.ParentSheetIndex } + }; + roe.Price += input.Price / 2; + yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 6 + 1, roe); + + // aged roe + if (pair.Key != 698) // aged sturgeon roe is caviar, which is a separate item + { + ColoredObject agedRoe = new ColoredObject(447, 1, color) + { + name = $"Aged {input.Name} Roe", + Category = -27, + preserve = { Value = SObject.PreserveType.AgedRoe }, + preservedParentSheetIndex = { Value = input.ParentSheetIndex }, + Price = roe.Price * 2 + }; + yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 6 + 1, agedRoe); + } + } + } } } diff --git a/src/SMAPI.Web/Controllers/LogParserController.cs b/src/SMAPI.Web/Controllers/LogParserController.cs index f7f19cd8..32c45038 100644 --- a/src/SMAPI.Web/Controllers/LogParserController.cs +++ b/src/SMAPI.Web/Controllers/LogParserController.cs @@ -1,6 +1,13 @@ using System; +using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using Amazon.S3; +using Amazon.S3.Model; +using Amazon.S3.Transfer; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using StardewModdingAPI.Toolkit.Utilities; @@ -21,7 +28,10 @@ namespace StardewModdingAPI.Web.Controllers ** Fields *********/ /// <summary>The site config settings.</summary> - private readonly SiteConfig Config; + private readonly SiteConfig SiteConfig; + + /// <summary>The API client settings.</summary> + private readonly ApiClientsConfig ClientsConfig; /// <summary>The underlying Pastebin client.</summary> private readonly IPastebinClient Pastebin; @@ -38,11 +48,13 @@ namespace StardewModdingAPI.Web.Controllers ***/ /// <summary>Construct an instance.</summary> /// <param name="siteConfig">The context config settings.</param> + /// <param name="clientsConfig">The API client settings.</param> /// <param name="pastebin">The Pastebin API client.</param> /// <param name="gzipHelper">The underlying text compression helper.</param> - public LogParserController(IOptions<SiteConfig> siteConfig, IPastebinClient pastebin, IGzipHelper gzipHelper) + public LogParserController(IOptions<SiteConfig> siteConfig, IOptions<ApiClientsConfig> clientsConfig, IPastebinClient pastebin, IGzipHelper gzipHelper) { - this.Config = siteConfig.Value; + this.SiteConfig = siteConfig.Value; + this.ClientsConfig = clientsConfig.Value; this.Pastebin = pastebin; this.GzipHelper = gzipHelper; } @@ -66,8 +78,9 @@ namespace StardewModdingAPI.Web.Controllers PasteInfo paste = await this.GetAsync(id); ParsedLog log = paste.Success ? new LogParser().Parse(paste.Content) - : new ParsedLog { IsValid = false, Error = "Pastebin error: " + paste.Error }; - return this.View("Index", this.GetModel(id).SetResult(log, raw)); + : new ParsedLog { IsValid = false, Error = paste.Error }; + + return this.View("Index", this.GetModel(id, uploadWarning: paste.Warning, expiry: paste.Expiry).SetResult(log, raw)); } /*** @@ -85,15 +98,13 @@ namespace StardewModdingAPI.Web.Controllers // upload log input = this.GzipHelper.CompressString(input); - SavePasteResult result = await this.Pastebin.PostAsync($"SMAPI log {DateTime.UtcNow:s}", input); - - // handle errors - if (!result.Success) - return this.View("Index", this.GetModel(result.ID, uploadError: $"Pastebin error: {result.Error ?? "unknown error"}")); + var uploadResult = await this.TrySaveLog(input); + if (!uploadResult.Succeeded) + return this.View("Index", this.GetModel(null, uploadError: uploadResult.UploadError)); // redirect to view - UriBuilder uri = new UriBuilder(new Uri(this.Config.LogParserUrl)); - uri.Path = uri.Path.TrimEnd('/') + '/' + result.ID; + UriBuilder uri = new UriBuilder(new Uri(this.SiteConfig.LogParserUrl)); + uri.Path = $"{uri.Path.TrimEnd('/')}/{uploadResult.ID}"; return this.Redirect(uri.Uri.ToString()); } @@ -105,19 +116,116 @@ namespace StardewModdingAPI.Web.Controllers /// <param name="id">The Pastebin paste ID.</param> private async Task<PasteInfo> GetAsync(string id) { - PasteInfo response = await this.Pastebin.GetAsync(id); - response.Content = this.GzipHelper.DecompressString(response.Content); - return response; + // get from Amazon S3 + if (Guid.TryParseExact(id, "N", out Guid _)) + { + var credentials = new BasicAWSCredentials(accessKey: this.ClientsConfig.AmazonAccessKey, secretKey: this.ClientsConfig.AmazonSecretKey); + + using (IAmazonS3 s3 = new AmazonS3Client(credentials, RegionEndpoint.GetBySystemName(this.ClientsConfig.AmazonRegion))) + { + try + { + using (GetObjectResponse response = await s3.GetObjectAsync(this.ClientsConfig.AmazonLogBucket, $"logs/{id}")) + using (Stream responseStream = response.ResponseStream) + using (StreamReader reader = new StreamReader(responseStream)) + { + DateTime expiry = response.Expiration.ExpiryDateUtc; + string pastebinError = response.Metadata["x-amz-meta-pastebin-error"]; + string content = this.GzipHelper.DecompressString(reader.ReadToEnd()); + + return new PasteInfo + { + Success = true, + Content = content, + Expiry = expiry, + Warning = pastebinError + }; + } + } + catch (AmazonServiceException ex) + { + return ex.ErrorCode == "NoSuchKey" + ? new PasteInfo { Error = "There's no log with that ID." } + : new PasteInfo { Error = $"Could not fetch that log from AWS S3 ({ex.ErrorCode}: {ex.Message})." }; + } + } + } + + // get from PasteBin + else + { + PasteInfo response = await this.Pastebin.GetAsync(id); + response.Content = this.GzipHelper.DecompressString(response.Content); + return response; + } + } + + /// <summary>Save a log to Pastebin or Amazon S3, if available.</summary> + /// <param name="content">The content to upload.</param> + /// <returns>Returns metadata about the save attempt.</returns> + private async Task<UploadResult> TrySaveLog(string content) + { + // save to PasteBin + string uploadError; + { + SavePasteResult result = await this.Pastebin.PostAsync($"SMAPI log {DateTime.UtcNow:s}", content); + if (result.Success) + return new UploadResult(true, result.ID, null); + + uploadError = $"Pastebin error: {result.Error ?? "unknown error"}"; + } + + // fallback to S3 + try + { + var credentials = new BasicAWSCredentials(accessKey: this.ClientsConfig.AmazonAccessKey, secretKey: this.ClientsConfig.AmazonSecretKey); + + using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(content))) + using (IAmazonS3 s3 = new AmazonS3Client(credentials, RegionEndpoint.GetBySystemName(this.ClientsConfig.AmazonRegion))) + using (TransferUtility uploader = new TransferUtility(s3)) + { + string id = Guid.NewGuid().ToString("N"); + + var uploadRequest = new TransferUtilityUploadRequest + { + BucketName = this.ClientsConfig.AmazonLogBucket, + Key = $"logs/{id}", + InputStream = stream, + Metadata = + { + // note: AWS will lowercase keys and prefix 'x-amz-meta-' + ["smapi-uploaded"] = DateTime.UtcNow.ToString("O"), + ["pastebin-error"] = uploadError + } + }; + + await uploader.UploadAsync(uploadRequest); + + return new UploadResult(true, id, uploadError); + } + } + catch (Exception ex) + { + return new UploadResult(false, null, $"{uploadError}\n{ex.Message}"); + } } /// <summary>Build a log parser model.</summary> /// <param name="pasteID">The paste ID.</param> - /// <param name="uploadError">An error which occurred while uploading the log to Pastebin.</param> - private LogParserModel GetModel(string pasteID, string uploadError = null) + /// <param name="expiry">When the uploaded file will no longer be available.</param> + /// <param name="uploadWarning">A non-blocking warning while uploading the log.</param> + /// <param name="uploadError">An error which occurred while uploading the log.</param> + private LogParserModel GetModel(string pasteID, DateTime? expiry = null, string uploadWarning = null, string uploadError = null) { - string sectionUrl = this.Config.LogParserUrl; + string sectionUrl = this.SiteConfig.LogParserUrl; Platform? platform = this.DetectClientPlatform(); - return new LogParserModel(sectionUrl, pasteID, platform) { UploadError = uploadError }; + + return new LogParserModel(sectionUrl, pasteID, platform) + { + UploadWarning = uploadWarning, + UploadError = uploadError, + Expiry = expiry + }; } /// <summary>Detect the viewer's OS.</summary> @@ -143,5 +251,36 @@ namespace StardewModdingAPI.Web.Controllers return null; } } + + /// <summary>The result of an attempt to upload a file.</summary> + private class UploadResult + { + /********* + ** Accessors + *********/ + /// <summary>Whether the file upload succeeded.</summary> + public bool Succeeded { get; } + + /// <summary>The file ID, if applicable.</summary> + public string ID { get; } + + /// <summary>The upload error, if any.</summary> + public string UploadError { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="succeeded">Whether the file upload succeeded.</param> + /// <param name="id">The file ID, if applicable.</param> + /// <param name="uploadError">The upload error, if any.</param> + public UploadResult(bool succeeded, string id, string uploadError) + { + this.Succeeded = succeeded; + this.ID = id; + this.UploadError = uploadError; + } + } } } diff --git a/src/SMAPI.Web/Framework/Clients/Pastebin/PasteInfo.cs b/src/SMAPI.Web/Framework/Clients/Pastebin/PasteInfo.cs index 955156eb..bb2de356 100644 --- a/src/SMAPI.Web/Framework/Clients/Pastebin/PasteInfo.cs +++ b/src/SMAPI.Web/Framework/Clients/Pastebin/PasteInfo.cs @@ -1,3 +1,5 @@ +using System; + namespace StardewModdingAPI.Web.Framework.Clients.Pastebin { /// <summary>The response for a get-paste request.</summary> @@ -9,7 +11,13 @@ namespace StardewModdingAPI.Web.Framework.Clients.Pastebin /// <summary>The fetched paste content (if <see cref="Success"/> is <c>true</c>).</summary> public string Content { get; set; } - /// <summary>The error message (if saving failed).</summary> + /// <summary>When the file will no longer be available.</summary> + public DateTime? Expiry { get; set; } + + /// <summary>The error message if saving succeeded, but a non-blocking issue was encountered.</summary> + public string Warning { get; set; } + + /// <summary>The error message if saving failed.</summary> public string Error { get; set; } } } diff --git a/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs b/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs index 2e8a8c68..d695aab6 100644 --- a/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs +++ b/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs @@ -62,7 +62,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.Pastebin } catch (Exception ex) { - return new PasteInfo { Error = ex.ToString() }; + return new PasteInfo { Error = $"Pastebin error: {ex}" }; } } diff --git a/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs index 121690c5..7119ef03 100644 --- a/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs +++ b/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs @@ -14,6 +14,22 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels /**** + ** Amazon Web Services + ****/ + /// <summary>The access key for AWS authentication.</summary> + public string AmazonAccessKey { get; set; } + + /// <summary>The secret key for AWS authentication.</summary> + public string AmazonSecretKey { get; set; } + + /// <summary>The AWS region endpoint (like 'us-east-1').</summary> + public string AmazonRegion { get; set; } + + /// <summary>The AWS bucket in which to store temporary uploaded logs.</summary> + public string AmazonLogBucket { get; set; } + + + /**** ** Chucklefish ****/ /// <summary>The base URL for the Chucklefish mod site.</summary> diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj index 8a7ca741..863c586e 100644 --- a/src/SMAPI.Web/SMAPI.Web.csproj +++ b/src/SMAPI.Web/SMAPI.Web.csproj @@ -12,6 +12,7 @@ </ItemGroup> <ItemGroup> + <PackageReference Include="AWSSDK.S3" Version="3.3.108.4" /> <PackageReference Include="Hangfire.AspNetCore" Version="1.7.7" /> <PackageReference Include="Hangfire.Mongo" Version="0.6.5" /> <PackageReference Include="HtmlAgilityPack" Version="1.11.16" /> @@ -21,7 +22,6 @@ <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Rewrite" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" /> - <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.0.1" /> <PackageReference Include="MongoDB.Driver" Version="2.9.3" /> <PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.11" /> <PackageReference Include="Pathoschild.FluentNexus" Version="0.8.0" /> diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs index 8110b696..fc6161b5 100644 --- a/src/SMAPI.Web/Startup.cs +++ b/src/SMAPI.Web/Startup.cs @@ -58,6 +58,7 @@ namespace StardewModdingAPI.Web { // init basic services services + .Configure<ApiClientsConfig>(this.Configuration.GetSection("ApiClients")) .Configure<BackgroundServicesConfig>(this.Configuration.GetSection("BackgroundServices")) .Configure<ModCompatibilityListConfig>(this.Configuration.GetSection("ModCompatibilityList")) .Configure<ModUpdateCheckConfig>(this.Configuration.GetSection("ModUpdateCheck")) diff --git a/src/SMAPI.Web/ViewModels/LogParserModel.cs b/src/SMAPI.Web/ViewModels/LogParserModel.cs index f4c5214b..b06b5b2d 100644 --- a/src/SMAPI.Web/ViewModels/LogParserModel.cs +++ b/src/SMAPI.Web/ViewModels/LogParserModel.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -34,12 +35,18 @@ namespace StardewModdingAPI.Web.ViewModels /// <summary>Whether to show the raw unparsed log.</summary> public bool ShowRaw { get; set; } - /// <summary>An error which occurred while uploading the log to Pastebin.</summary> + /// <summary>A non-blocking warning while uploading the log.</summary> + public string UploadWarning { get; set; } + + /// <summary>An error which occurred while uploading the log.</summary> public string UploadError { get; set; } /// <summary>An error which occurred while parsing the log file.</summary> public string ParseError => this.ParsedLog?.Error; + /// <summary>When the uploaded file will no longer be available.</summary> + public DateTime? Expiry { get; set; } + /********* ** Public methods diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index f98ffdf9..df2ac115 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -1,3 +1,4 @@ +@using Humanizer @using Newtonsoft.Json @using StardewModdingAPI.Toolkit.Utilities @using StardewModdingAPI.Web.Framework.LogParsing.Models @@ -18,7 +19,7 @@ { <meta name="robots" content="noindex" /> } - <link rel="stylesheet" href="~/Content/css/log-parser.css?r=20190515" /> + <link rel="stylesheet" href="~/Content/css/log-parser.css?r=20191127" /> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js" crossorigin="anonymous"></script> <script src="~/Content/js/log-parser.js?r=20190515"></script> @@ -62,6 +63,18 @@ else if (Model.ParsedLog?.IsValid == true) </div> } +@* save warnings *@ +@if (Model.UploadWarning != null || Model.Expiry != null) +{ + <div class="save-metadata" v-pre> + @if (Model.Expiry != null) + { + <text>This log will expire in @((DateTime.UtcNow - Model.Expiry.Value).Humanize()). </text> + } + <!--@Model.UploadWarning--> + </div> +} + @* upload new log *@ @if (Model.ParsedLog == null) { @@ -71,7 +84,7 @@ else if (Model.ParsedLog?.IsValid == true) @foreach (Platform platform in new[] { Platform.Android, Platform.Linux, Platform.Mac, Platform.Windows }) { <li> - <input type="radio" name="os" value="@platform" id="os-@platform" checked="@(Model.DetectedPlatform == platform)"/> + <input type="radio" name="os" value="@platform" id="os-@platform" checked="@(Model.DetectedPlatform == platform)" /> <label for="os-@platform">@platform</label> </li> } @@ -151,7 +164,7 @@ else if (Model.ParsedLog?.IsValid == true) <div class="content-packs"> @foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate)) { - <text>+ @contentPack.Name</text><br/> + <text>+ @contentPack.Name</text><br /> } </div> } @@ -173,7 +186,7 @@ else if (Model.ParsedLog?.IsValid == true) <div> @foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate)) { - <a href="@contentPack.UpdateLink" target="_blank">@contentPack.Version → @contentPack.UpdateVersion</a><br/> + <a href="@contentPack.UpdateLink" target="_blank">@contentPack.Version → @contentPack.UpdateVersion</a><br /> } </div> } diff --git a/src/SMAPI.Web/appsettings.Development.json b/src/SMAPI.Web/appsettings.Development.json index baf7efb7..8e863591 100644 --- a/src/SMAPI.Web/appsettings.Development.json +++ b/src/SMAPI.Web/appsettings.Development.json @@ -18,6 +18,9 @@ }, "ApiClients": { + "AmazonAccessKey": null, + "AmazonSecretKey": null, + "GitHubUsername": null, "GitHubPassword": null, diff --git a/src/SMAPI.Web/appsettings.json b/src/SMAPI.Web/appsettings.json index 674bb672..3b6f8fbd 100644 --- a/src/SMAPI.Web/appsettings.json +++ b/src/SMAPI.Web/appsettings.json @@ -16,17 +16,22 @@ }, "Site": { - "RootUrl": null, // see top note - "ModListUrl": null, // see top note - "LogParserUrl": null, // see top note - "JsonValidatorUrl": null, // see top note - "BetaEnabled": null, // see top note - "BetaBlurb": null // see top note + "RootUrl": null, + "ModListUrl": null, + "LogParserUrl": null, + "JsonValidatorUrl": null, + "BetaEnabled": null, + "BetaBlurb": null }, "ApiClients": { "UserAgent": "SMAPI/{0} (+https://smapi.io)", + "AmazonAccessKey": null, + "AmazonSecretKey": null, + "AmazonRegion": "us-east-1", + "AmazonLogBucket": "smapi-log-parser", + "ChucklefishBaseUrl": "https://community.playstarbound.com", "ChucklefishModPageUrlFormat": "resources/{0}", @@ -34,27 +39,27 @@ "GitHubBaseUrl": "https://api.github.com", "GitHubAcceptHeader": "application/vnd.github.v3+json", - "GitHubUsername": null, // see top note - "GitHubPassword": null, // see top note + "GitHubUsername": null, + "GitHubPassword": null, "ModDropApiUrl": "https://www.moddrop.com/api/mods/data", "ModDropModPageUrl": "https://www.moddrop.com/sdv/mod/{0}", - "NexusApiKey": null, // see top note + "NexusApiKey": null, "NexusBaseUrl": "https://www.nexusmods.com/stardewvalley/", "NexusModUrlFormat": "mods/{0}", "NexusModScrapeUrlFormat": "mods/{0}?tab=files", "PastebinBaseUrl": "https://pastebin.com/", - "PastebinUserKey": null, // see top note - "PastebinDevKey": null // see top note + "PastebinUserKey": null, + "PastebinDevKey": null }, "MongoDB": { - "Host": null, // see top note - "Username": null, // see top note - "Password": null, // see top note - "Database": null // see top note + "Host": null, + "Username": null, + "Password": null, + "Database": null }, "ModCompatibilityList": { diff --git a/src/SMAPI.Web/wwwroot/Content/css/log-parser.css b/src/SMAPI.Web/wwwroot/Content/css/log-parser.css index d5013207..4d4ab326 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/log-parser.css +++ b/src/SMAPI.Web/wwwroot/Content/css/log-parser.css @@ -47,6 +47,12 @@ table caption { background: #FCC; } +.save-metadata { + margin-top: 1em; + font-size: 0.8em; + opacity: 0.3; +} + /********* ** Log metadata & filters *********/ diff --git a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json index b508b033..78918bac 100644 --- a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json +++ b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json @@ -126,6 +126,12 @@ "~ | StatusReasonPhrase": "the animal mood bugs were fixed in Stardew Valley 1.2." }, + "Bee House Flower Range Fix": { + "ID": "kirbylink.beehousefix", + "~ | Status": "Obsolete", + "~ | StatusReasonPhrase": "the bee house flower range was fixed in Stardew Valley 1.4." + }, + "Colored Chests": { "ID": "4befde5c-731c-4853-8e4b-c5cdf946805f", "~ | Status": "Obsolete", @@ -152,19 +158,44 @@ "~1.1.2 | Status": "AssumeBroken" // crashes game on startup }, + "Fix Dice": { + "ID": "ashley.fixdice", + "~1.1.2 | Status": "AssumeBroken" // crashes game on startup + }, + "Grass Growth": { "ID": "bcmpinc.GrassGrowth", - "~1.0 | Status": "AssumeBroken" // runtime Harmony error + "~1.0 | Status": "AssumeBroken" + }, + + "Invite Code Mod": { + "ID": "KOREJJamJar.InviteCodeMod", + "~1.0.1 | Status": "AssumeBroken" }, "Loved Labels": { "ID": "Advize.LovedLabels", - "~2.2.1-unofficial.2-pathoschild | Status": "AssumeBroken" // runtime reflection errors + "~2.2.1-unofficial.2-pathoschild | Status": "AssumeBroken" + }, + + "Neat Additions": { + "ID": "ilyaki.neatadditions", + "~1.0.3 | Status": "AssumeBroken" + }, + + "Remote Fridge Storage": { + "ID": "EternalSoap.RemoteFridgeStorage", + "~1.5 | Status": "AssumeBroken" + }, + + "Stack Everything": { + "ID": "cat.stackeverything", + "~2.15 | Status": "AssumeBroken" }, "Yet Another Harvest With Scythe Mod": { "ID": "bcmpinc.HarvestWithScythe", - "~1.1 | Status": "AssumeBroken" // runtime Harmony error + "~1.1 | Status": "AssumeBroken" }, /********* diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 9bb9a857..1c0a04f0 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -8,6 +8,7 @@ using StardewValley; using StardewValley.BellsAndWhistles; using StardewValley.Buildings; using StardewValley.Characters; +using StardewValley.GameData.Movies; using StardewValley.Locations; using StardewValley.Menus; using StardewValley.Objects; @@ -167,10 +168,6 @@ namespace StardewModdingAPI.Metadata /**** ** Animals ****/ - case "animals\\cat": - return this.ReloadPetOrHorseSprites<Cat>(content, key); - case "animals\\dog": - return this.ReloadPetOrHorseSprites<Dog>(content, key); case "animals\\horse": return this.ReloadPetOrHorseSprites<Horse>(content, key); @@ -189,12 +186,14 @@ namespace StardewModdingAPI.Metadata return true; case "characters\\farmer\\farmer_base": // Farmer + case "characters\\farmer\\farmer_base_bald": if (Game1.player == null || !Game1.player.IsMale) return false; Game1.player.FarmerRenderer = new FarmerRenderer(key, Game1.player); return true; case "characters\\farmer\\farmer_girl_base": // Farmer + case "characters\\farmer\\farmer_girl_bald": if (Game1.player == null || Game1.player.IsMale) return false; Game1.player.FarmerRenderer = new FarmerRenderer(key, Game1.player); @@ -208,6 +207,10 @@ namespace StardewModdingAPI.Metadata FarmerRenderer.hatsTexture = content.Load<Texture2D>(key); return true; + case "characters\\farmer\\pants": // Game1.LoadContent + FarmerRenderer.pantsTexture = content.Load<Texture2D>(key); + return true; + case "characters\\farmer\\shirts": // Game1.LoadContent FarmerRenderer.shirtsTexture = content.Load<Texture2D>(key); return true; @@ -223,6 +226,16 @@ namespace StardewModdingAPI.Metadata Game1.bigCraftablesInformation = content.Load<Dictionary<int, string>>(key); return true; + case "data\\clothinginformation": // Game1.LoadContent + Game1.clothingInformation = content.Load<Dictionary<int, string>>(key); + return true; + + case "data\\concessiontastes": // MovieTheater.GetConcessionTasteForCharacter + this.Reflection + .GetField<List<ConcessionTaste>>(typeof(MovieTheater), "_concessionTastes") + .SetValue(content.Load<List<ConcessionTaste>>(key)); + return true; + case "data\\cookingrecipes": // CraftingRecipe.InitShared CraftingRecipe.cookingRecipes = content.Load<Dictionary<string, string>>(key); return true; @@ -234,6 +247,18 @@ namespace StardewModdingAPI.Metadata case "data\\farmanimals": // FarmAnimal constructor return this.ReloadFarmAnimalData(); + case "data\\moviereactions": // MovieTheater.GetMovieReactions + this.Reflection + .GetField<List<MovieCharacterReaction>>(typeof(MovieTheater), "_genericReactions") + .SetValue(content.Load<List<MovieCharacterReaction>>(key)); + return true; + + case "data\\movies": // MovieTheater.GetMovieData + this.Reflection + .GetField<Dictionary<string, MovieData>>(typeof(MovieTheater), "_movieData") + .SetValue(content.Load<Dictionary<string, MovieData>>(key)); + return true; + case "data\\npcdispositions": // NPC constructor return this.ReloadNpcDispositions(content, key); @@ -241,6 +266,10 @@ namespace StardewModdingAPI.Metadata Game1.NPCGiftTastes = content.Load<Dictionary<string, string>>(key); return true; + case "data\\objectcontexttags": // Game1.LoadContent + Game1.objectContextTags = content.Load<Dictionary<string, string>>(key); + return true; + case "data\\objectinformation": // Game1.LoadContent Game1.objectInformation = content.Load<Dictionary<int, string>>(key); return true; @@ -290,6 +319,14 @@ namespace StardewModdingAPI.Metadata /**** ** Content\LooseSprites ****/ + case "loosesprites\\birds": // Game1.LoadContent + Game1.birdsSpriteSheet = content.Load<Texture2D>(key); + return true; + + case "loosesprites\\concessions": // Game1.LoadContent + Game1.concessionsSpriteSheet = content.Load<Texture2D>(key); + return true; + case "loosesprites\\controllermaps": // Game1.LoadContent Game1.controllerMaps = content.Load<Texture2D>(key); return true; @@ -373,6 +410,10 @@ namespace StardewModdingAPI.Metadata Game1.menuTexture = content.Load<Texture2D>(key); return true; + case "maps\\menutilesuncolored": // Game1.LoadContent + Game1.uncoloredMenuTexture = content.Load<Texture2D>(key); + return true; + case "maps\\springobjects": // Game1.LoadContent Game1.objectSpriteSheet = content.Load<Texture2D>(key); return true; @@ -474,6 +515,10 @@ namespace StardewModdingAPI.Metadata } // dynamic textures + if (this.KeyStartsWith(key, "animals\\cat")) + return this.ReloadPetOrHorseSprites<Cat>(content, key); + if (this.KeyStartsWith(key, "animals\\dog")) + return this.ReloadPetOrHorseSprites<Dog>(content, key); if (this.IsInFolder(key, "Animals")) return this.ReloadFarmAnimalSprites(content, key); diff --git a/src/SMAPI/i18n/ru.json b/src/SMAPI/i18n/ru.json new file mode 100644 index 00000000..a6a242fa --- /dev/null +++ b/src/SMAPI/i18n/ru.json @@ -0,0 +1,3 @@ +{ + "warn.invalid-content-removed": "Недопустимое содержимое было удалено, чтобы предотвратить сбой (см. информацию в консоли SMAPI)" +} diff --git a/src/SMAPI/i18n/zh.json b/src/SMAPI/i18n/zh.json new file mode 100644 index 00000000..bbd6a574 --- /dev/null +++ b/src/SMAPI/i18n/zh.json @@ -0,0 +1,3 @@ +{
+ "warn.invalid-content-removed": "非法内容已移除以防游戏闪退(查看SMAPI控制台获得更多信息)"
+}
|