summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/LogParser.cs2
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs32
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs3
-rw-r--r--src/SMAPI.Web/Views/LogParser/Index.cshtml122
-rw-r--r--src/SMAPI.Web/wwwroot/Content/css/log-parser.css34
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs27
-rw-r--r--src/SMAPI/Metadata/CoreAssetPropagator.cs64
7 files changed, 176 insertions, 108 deletions
diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
index 7fc8f958..0efa62c5 100644
--- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
+++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
@@ -200,6 +200,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
log.GameVersion = match.Groups["gameVersion"].Value;
log.OperatingSystem = match.Groups["os"].Value;
smapiMod.OverrideVersion(log.ApiVersion);
+
+ log.ApiVersionParsed = smapiMod.GetParsedVersion();
}
// mod path line
diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs b/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs
index 4b80a830..557f08ff 100644
--- a/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs
+++ b/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs
@@ -1,4 +1,6 @@
+using System;
using System.Diagnostics.CodeAnalysis;
+using StardewModdingAPI.Toolkit;
namespace StardewModdingAPI.Web.Framework.LogParsing.Models
{
@@ -6,6 +8,13 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
public class LogModInfo
{
/*********
+ ** Private fields
+ *********/
+ /// <summary>The parsed mod version, if valid.</summary>
+ private Lazy<ISemanticVersion?> ParsedVersionImpl;
+
+
+ /*********
** Accessors
*********/
/// <summary>The mod name.</summary>
@@ -68,7 +77,6 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
{
this.Name = name;
this.Author = author;
- this.Version = version;
this.Description = description;
this.UpdateVersion = updateVersion;
this.UpdateLink = updateLink;
@@ -82,6 +90,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
this.IsContentPack = !string.IsNullOrWhiteSpace(this.ContentPackFor);
this.IsCodeMod = !this.IsContentPack;
}
+
+ this.OverrideVersion(version);
}
/// <summary>Add an update alert for this mod.</summary>
@@ -95,9 +105,29 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
/// <summary>Override the version number, for cases like SMAPI itself where the version is only known later during parsing.</summary>
/// <param name="version">The new mod version.</param>
+ [MemberNotNull(nameof(LogModInfo.Version), nameof(LogModInfo.ParsedVersionImpl))]
public void OverrideVersion(string version)
{
this.Version = version;
+ this.ParsedVersionImpl = new Lazy<ISemanticVersion?>(this.ParseVersion);
+ }
+
+ /// <summary>Get the semantic version for this mod, if it's valid.</summary>
+ public ISemanticVersion? GetParsedVersion()
+ {
+ return this.ParsedVersionImpl.Value;
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Get the semantic version for this mod, if it's valid.</summary>
+ public ISemanticVersion? ParseVersion()
+ {
+ return !string.IsNullOrWhiteSpace(this.Version) && SemanticVersion.TryParse(this.Version, out ISemanticVersion? version)
+ ? version
+ : null;
}
}
}
diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs b/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs
index 6951e434..3f649199 100644
--- a/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs
+++ b/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs
@@ -28,6 +28,9 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
/// <summary>The SMAPI version.</summary>
public string? ApiVersion { get; set; }
+ /// <summary>The parsed SMAPI version, if it's valid.</summary>
+ public ISemanticVersion? ApiVersionParsed { get; set; }
+
/// <summary>The game version.</summary>
public string? GameVersion { get; set; }
diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml
index 33239a2b..b824b768 100644
--- a/src/SMAPI.Web/Views/LogParser/Index.cshtml
+++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml
@@ -8,24 +8,29 @@
@{
ViewData["Title"] = "SMAPI log parser";
+ // get log info
ParsedLog? log = Model!.ParsedLog;
-
IDictionary<string, LogModInfo[]> contentPacks = Model.GetContentPacksByMod();
+ ISet<int> screenIds = new HashSet<int>(log?.Messages.Select(p => p.ScreenId) ?? Array.Empty<int>());
+
+ // detect suggested fixes
+ LogModInfo[] outdatedMods = log?.Mods.Where(mod => mod.HasUpdate).ToArray() ?? Array.Empty<LogModInfo>();
+ LogModInfo? errorHandler = log?.Mods.FirstOrDefault(p => p.IsCodeMod && p.Name == "Error Handler");
+ bool hasOlderErrorHandler = errorHandler?.GetParsedVersion() is not null && log?.ApiVersionParsed is not null && log.ApiVersionParsed.IsNewerThan(errorHandler.GetParsedVersion());
+
+ // get filters
IDictionary<string, bool> defaultFilters = Enum
.GetValues<LogLevel>()
.ToDictionary(level => level.ToString().ToLower(), level => level != LogLevel.Trace);
-
IDictionary<int, string> logLevels = Enum
.GetValues<LogLevel>()
.ToDictionary(level => (int)level, level => level.ToString().ToLower());
-
IDictionary<int, string> logSections = Enum
.GetValues<LogSection>()
.ToDictionary(section => (int)section, section => section.ToString());
+ // get form
string curPageUrl = this.Url.PlainAction("Index", "LogParser", new { id = Model.PasteID }, absoluteUrl: true)!;
-
- ISet<int> screenIds = new HashSet<int>(log?.Messages.Select(p => p.ScreenId) ?? Array.Empty<int>());
}
@section Head {
@@ -34,7 +39,7 @@
<meta name="robots" content="noindex" />
}
<link rel="stylesheet" href="~/Content/css/file-upload.css" />
- <link rel="stylesheet" href="~/Content/css/log-parser.css?r=20220409" />
+ <link rel="stylesheet" href="~/Content/css/log-parser.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tabbyjs@12.0.3/dist/css/tabby-ui-vertical.min.css" />
<script src="https://cdn.jsdelivr.net/npm/tabbyjs@12.0.3" crossorigin="anonymous"></script>
@@ -69,7 +74,7 @@
</text>
}
</script>
-
+
<script>
$(function() {
smapi.logParser(
@@ -158,7 +163,7 @@ else if (log?.IsValid == true)
<div id="os-instructions">
<div>
<ul data-tabs>
- @foreach (Platform platform in new[] {Platform.Android, Platform.Linux, Platform.Mac, Platform.Windows})
+ @foreach (Platform platform in new[] { Platform.Android, Platform.Linux, Platform.Mac, Platform.Windows })
{
@if (platform == Platform.Windows)
{
@@ -237,55 +242,66 @@ else if (log?.IsValid == true)
@if (log?.IsValid == true)
{
<div id="output">
- @if (log.Mods.Any(mod => mod.HasUpdate))
+ @if (outdatedMods.Any() || errorHandler is null || hasOlderErrorHandler)
{
<h2>Suggested fixes</h2>
<ul id="fix-list">
- <li>
- Consider updating these mods to fix problems:
+ @if (errorHandler is null)
+ {
+ <li class="important">You don't have the <strong>Error Handler</strong> mod installed. This automatically prevents many game or mod errors. You can <a href="https://stardewvalleywiki.com/Modding:Player_Guide#Install_SMAPI">reinstall SMAPI</a> to re-add it.</li>
+ }
+ @if (hasOlderErrorHandler)
+ {
+ <li>Your <strong>Error Handler</strong> mod is older than SMAPI. You may be missing some game/mod error fixes. You can <a href="https://stardewvalleywiki.com/Modding:Player_Guide#Install_SMAPI">reinstall SMAPI</a> to update it.</li>
+ }
+ @if (outdatedMods.Any())
+ {
+ <li>
+ Consider updating these mods to fix problems:
- <table id="updates" class="table">
- @foreach (LogModInfo mod in log.Mods.Where(mod => (mod.HasUpdate && !mod.IsContentPack) || (contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList) && contentPackList.Any(pack => pack.HasUpdate))))
- {
- <tr class="mod-entry">
- <td>
- <strong class=@(!mod.HasUpdate ? "hidden" : "")>@mod.Name</strong>
- @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList))
- {
- <div class="content-packs">
- @foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate))
- {
- <text>+ @contentPack.Name</text><br />
- }
- </div>
- }
- </td>
- <td>
- @if (mod.HasUpdate)
- {
- <a href="@mod.UpdateLink" target="_blank">
- @(mod.Version == null ? mod.UpdateVersion : $"{mod.Version} → {mod.UpdateVersion}")
- </a>
- }
- else
- {
- <text>&nbsp;</text>
- }
+ <table id="updates" class="table">
+ @foreach (LogModInfo mod in log.Mods.Where(mod => (mod.HasUpdate && !mod.IsContentPack) || (contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList) && contentPackList.Any(pack => pack.HasUpdate))))
+ {
+ <tr class="mod-entry">
+ <td>
+ <strong class=@(!mod.HasUpdate ? "hidden" : "")>@mod.Name</strong>
+ @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList))
+ {
+ <div class="content-packs">
+ @foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate))
+ {
+ <text>+ @contentPack.Name</text><br />
+ }
+ </div>
+ }
+ </td>
+ <td>
+ @if (mod.HasUpdate)
+ {
+ <a href="@mod.UpdateLink" target="_blank">
+ @(mod.Version == null ? mod.UpdateVersion : $"{mod.Version} → {mod.UpdateVersion}")
+ </a>
+ }
+ else
+ {
+ <text>&nbsp;</text>
+ }
- @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out contentPackList))
- {
- <div>
- @foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate))
- {
- <a href="@contentPack.UpdateLink" target="_blank">@contentPack.Version → @contentPack.UpdateVersion</a><br />
- }
- </div>
- }
- </td>
- </tr>
- }
- </table>
- </li>
+ @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out contentPackList))
+ {
+ <div>
+ @foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate))
+ {
+ <a href="@contentPack.UpdateLink" target="_blank">@contentPack.Version → @contentPack.UpdateVersion</a><br />
+ }
+ </div>
+ }
+ </td>
+ </tr>
+ }
+ </table>
+ </li>
+ }
</ul>
}
@@ -434,7 +450,7 @@ else if (log?.IsValid == true)
<div>
This website uses JavaScript to display a filterable table. To view this log, please enable JavaScript or <a href="@this.Url.PlainAction("Index", "LogParser", new { id = Model.PasteID, format = LogViewFormat.RawView })">view the raw log</a>.
</div>
- <br/>
+ <br />
</noscript>
<log-table>
diff --git a/src/SMAPI.Web/wwwroot/Content/css/log-parser.css b/src/SMAPI.Web/wwwroot/Content/css/log-parser.css
index 1d457e35..f136a96f 100644
--- a/src/SMAPI.Web/wwwroot/Content/css/log-parser.css
+++ b/src/SMAPI.Web/wwwroot/Content/css/log-parser.css
@@ -54,6 +54,36 @@ table caption {
}
/*********
+** Suggested fixes
+*********/
+#fix-list {
+ padding-left: 1em;
+ margin-bottom: 2em;
+}
+
+#fix-list li {
+ padding: 0.5em;
+ background: #FFC;
+ border: 1px solid #880;
+ border-radius: 5px;
+ list-style-type: none;
+}
+
+#fix-list li:not(:last-child) {
+ margin-bottom: 0.5em;
+}
+
+#fix-list li.important {
+ background: #FCC;
+ border-color: #800;
+}
+
+#fix-list li::before {
+ content: "⚠ ";
+}
+
+
+/*********
** Log metadata & filters
*********/
.table, #filters {
@@ -84,10 +114,6 @@ table caption {
min-height: 1.3em;
}
-#fix-list {
- margin-bottom: 2em;
-}
-
#updates {
min-width: 10em;
}
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index 8c5d0f84..f3cf05d9 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
@@ -47,9 +46,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>If a map tilesheet's image source has no file extensions, the file extensions to check for in the local mod folder.</summary>
private static readonly string[] LocalTilesheetExtensions = { ".png", ".xnb" };
- /// <summary>A lookup of image file paths to whether they have PyTK scaling information.</summary>
- private static readonly Dictionary<string, bool> IsPyTkScaled = new(StringComparer.OrdinalIgnoreCase);
-
/*********
** Accessors
@@ -211,24 +207,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
if (ModContentManager.EnablePyTkLegacyMode)
{
- if (!ModContentManager.IsPyTkScaled.TryGetValue(file.FullName, out bool isScaled))
- {
- string? dirPath = file.DirectoryName;
- string fileName = $"{Path.GetFileNameWithoutExtension(file.Name)}.pytk.json";
-
- string path = dirPath is not null
- ? Path.Combine(dirPath, fileName)
- : fileName;
-
- ModContentManager.IsPyTkScaled[file.FullName] = isScaled = File.Exists(path);
- }
-
- asRawData = !isScaled;
- if (!asRawData)
- this.Monitor.LogOnce("Enabled compatibility mode for PyTK scaled textures. This won't cause any issues, but may impact performance.", LogLevel.Warn);
+ // PyTK intercepts Texture2D file loads to rescale them (e.g. for HD portraits),
+ // but doesn't support IRawTextureData loads yet. We can't just check if the
+ // current file has a '.pytk.json' rescale file though, since PyTK may still
+ // rescale it if the original asset or another edit gets rescaled.
+ asRawData = false;
+ this.Monitor.LogOnce("Enabled compatibility mode for PyTK 1.23.0 or earlier. This won't cause any issues, but may impact performance.", LogLevel.Warn);
}
- else
- asRawData = true;
}
// load
diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs
index b783b2b9..1b4d1737 100644
--- a/src/SMAPI/Metadata/CoreAssetPropagator.cs
+++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs
@@ -164,6 +164,7 @@ namespace StardewModdingAPI.Metadata
var content = this.MainContentManager;
string key = assetName.BaseName;
changedWarpRoutes = false;
+ bool changed = false;
/****
** Special case: current map tilesheet
@@ -175,7 +176,10 @@ namespace StardewModdingAPI.Metadata
foreach (TileSheet tilesheet in Game1.currentLocation.map.TileSheets)
{
if (this.IsSameBaseName(assetName, tilesheet.ImageSource))
+ {
Game1.mapDisplayDevice.LoadTileSheet(tilesheet);
+ changed = true;
+ }
}
}
@@ -184,8 +188,6 @@ namespace StardewModdingAPI.Metadata
****/
if (type == typeof(Map))
{
- bool anyChanged = false;
-
if (!ignoreWorld)
{
foreach (LocationInfo info in this.GetLocationsWithInfo())
@@ -206,12 +208,12 @@ namespace StardewModdingAPI.Metadata
var newWarps = GetWarpSet(location);
changedWarpRoutes = changedWarpRoutes || oldWarps.Count != newWarps.Count || oldWarps.Any(p => !newWarps.Contains(p));
- anyChanged = true;
+ changed = true;
}
}
}
- return anyChanged;
+ return changed;
}
/****
@@ -223,7 +225,7 @@ namespace StardewModdingAPI.Metadata
** Animals
****/
case "animals/horse":
- return !ignoreWorld && this.UpdatePetOrHorseSprites<Horse>(assetName);
+ return changed | (!ignoreWorld && this.UpdatePetOrHorseSprites<Horse>(assetName));
/****
** Buildings
@@ -239,7 +241,7 @@ namespace StardewModdingAPI.Metadata
Farm farm = Game1.getFarm();
farm?.ApplyHousePaint();
- return removedFromCache || farm != null;
+ return changed | (removedFromCache || farm != null);
}
/****
@@ -253,7 +255,7 @@ namespace StardewModdingAPI.Metadata
case "characters/farmer/farmer_base_bald":
case "characters/farmer/farmer_girl_base":
case "characters/farmer/farmer_girl_base_bald":
- return !ignoreWorld && this.UpdatePlayerSprites(assetName);
+ return changed | (!ignoreWorld && this.UpdatePlayerSprites(assetName));
case "characters/farmer/hairstyles": // Game1.LoadContent
FarmerRenderer.hairStylesTexture = this.LoadTexture(key);
@@ -305,10 +307,10 @@ namespace StardewModdingAPI.Metadata
return true;
case "data/farmanimals": // FarmAnimal constructor
- return !ignoreWorld && this.UpdateFarmAnimalData();
+ return changed | (!ignoreWorld && this.UpdateFarmAnimalData());
case "data/hairdata": // Farmer.GetHairStyleMetadataFile
- return this.UpdateHairData();
+ return changed | this.UpdateHairData();
case "data/movies": // MovieTheater.GetMovieData
case "data/moviesreactions": // MovieTheater.GetMovieReactions
@@ -316,7 +318,7 @@ namespace StardewModdingAPI.Metadata
return true;
case "data/npcdispositions": // NPC constructor
- return !ignoreWorld && this.UpdateNpcDispositions(content, assetName);
+ return changed | (!ignoreWorld && this.UpdateNpcDispositions(content, assetName));
case "data/npcgifttastes": // Game1.LoadContent
Game1.NPCGiftTastes = content.Load<Dictionary<string, string>>(key);
@@ -428,7 +430,7 @@ namespace StardewModdingAPI.Metadata
return true;
case "loosesprites/suspensionbridge": // SuspensionBridge constructor
- return !ignoreWorld && this.UpdateSuspensionBridges(content, assetName);
+ return changed | (!ignoreWorld && this.UpdateSuspensionBridges(content, assetName));
/****
** Content\Maps
@@ -456,16 +458,16 @@ namespace StardewModdingAPI.Metadata
return true;
}
}
- return false;
+ return changed;
case "minigames/titlebuttons": // TitleMenu
- return this.UpdateTitleButtons(content, assetName);
+ return changed | this.UpdateTitleButtons(content, assetName);
/****
** Content\Strings
****/
case "strings/stringsfromcsfiles":
- return this.UpdateStringsFromCsFiles(content);
+ return changed | this.UpdateStringsFromCsFiles(content);
/****
** Content\TileSheets
@@ -490,7 +492,7 @@ namespace StardewModdingAPI.Metadata
return true;
case "tilesheets/critters": // Critter constructor
- return !ignoreWorld && this.UpdateCritterTextures(assetName);
+ return changed | (!ignoreWorld && this.UpdateCritterTextures(assetName));
case "tilesheets/crops": // Game1.LoadContent
Game1.cropSpriteSheet = content.Load<Texture2D>(key);
@@ -559,27 +561,27 @@ namespace StardewModdingAPI.Metadata
return true;
case "terrainfeatures/mushroom_tree": // from Tree
- return !ignoreWorld && this.UpdateTreeTextures(Tree.mushroomTree);
+ return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.mushroomTree));
case "terrainfeatures/tree_palm": // from Tree
- return !ignoreWorld && this.UpdateTreeTextures(Tree.palmTree);
+ return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.palmTree));
case "terrainfeatures/tree1_fall": // from Tree
case "terrainfeatures/tree1_spring": // from Tree
case "terrainfeatures/tree1_summer": // from Tree
case "terrainfeatures/tree1_winter": // from Tree
- return !ignoreWorld && this.UpdateTreeTextures(Tree.bushyTree);
+ return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.bushyTree));
case "terrainfeatures/tree2_fall": // from Tree
case "terrainfeatures/tree2_spring": // from Tree
case "terrainfeatures/tree2_summer": // from Tree
case "terrainfeatures/tree2_winter": // from Tree
- return !ignoreWorld && this.UpdateTreeTextures(Tree.leafyTree);
+ return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.leafyTree));
case "terrainfeatures/tree3_fall": // from Tree
case "terrainfeatures/tree3_spring": // from Tree
case "terrainfeatures/tree3_winter": // from Tree
- return !ignoreWorld && this.UpdateTreeTextures(Tree.pineTree);
+ return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.pineTree));
}
/****
@@ -588,25 +590,29 @@ namespace StardewModdingAPI.Metadata
if (!ignoreWorld)
{
// dynamic textures
- if (assetName.StartsWith("animals/cat"))
- return this.UpdatePetOrHorseSprites<Cat>(assetName);
- if (assetName.StartsWith("animals/dog"))
- return this.UpdatePetOrHorseSprites<Dog>(assetName);
if (assetName.IsDirectlyUnderPath("Animals"))
- return this.UpdateFarmAnimalSprites(assetName);
+ {
+ if (assetName.StartsWith("animals/cat"))
+ return changed | this.UpdatePetOrHorseSprites<Cat>(assetName);
+
+ if (assetName.StartsWith("animals/dog"))
+ return changed | this.UpdatePetOrHorseSprites<Dog>(assetName);
+
+ return changed | this.UpdateFarmAnimalSprites(assetName);
+ }
if (assetName.IsDirectlyUnderPath("Buildings"))
- return this.UpdateBuildings(assetName);
+ return changed | this.UpdateBuildings(assetName);
if (assetName.StartsWith("LooseSprites/Fence"))
- return this.UpdateFenceTextures(assetName);
+ return changed | this.UpdateFenceTextures(assetName);
// dynamic data
if (assetName.IsDirectlyUnderPath("Characters/Dialogue"))
- return this.UpdateNpcDialogue(assetName);
+ return changed | this.UpdateNpcDialogue(assetName);
if (assetName.IsDirectlyUnderPath("Characters/schedules"))
- return this.UpdateNpcSchedules(assetName);
+ return changed | this.UpdateNpcSchedules(assetName);
}
return false;