Esempio n. 1
0
        /// <summary>Update the current context.</summary>
        /// <param name="contentHelper">The content helper which manages game assets.</param>
        /// <param name="language">The current language.</param>
        /// <param name="date">The current in-game date (if applicable).</param>
        /// <param name="weather">The current in-game weather (if applicable).</param>
        /// <param name="spouse">The current player's internal spouse name (if applicable).</param>
        /// <param name="dayEvent">The day event (e.g. wedding or festival) occurring today (if applicable).</param>
        /// <param name="seenEvents">The event IDs which the player has seen.</param>
        /// <param name="mailFlags">The mail flags set for the player.</param>
        /// <param name="friendships">The current player's friendship details.</param>
        public void UpdateContext(IContentHelper contentHelper, LocalizedContentManager.LanguageCode language, SDate date, Weather?weather, string dayEvent, string spouse, int[] seenEvents, string[] mailFlags, IEnumerable <KeyValuePair <string, Friendship> > friendships)
        {
            this.VerboseLog("Propagating context...");

            // update context
            this.ConditionContext.Set(language: language, date: date, weather: weather, dayEvent: dayEvent, spouse: spouse, seenEvents: seenEvents, mailFlags: mailFlags, friendships: friendships);
            IDictionary <ConditionKey, string> tokenisableConditions = this.ConditionContext.GetSingleValueConditions();

            // update patches
            InvariantHashSet reloadAssetNames = new InvariantHashSet();
            string           prevAssetName    = null;

            foreach (IPatch patch in this.Patches.OrderBy(p => p.AssetName).ThenBy(p => p.LogName))
            {
                // log asset name
                if (this.Verbose && prevAssetName != patch.AssetName)
                {
                    this.VerboseLog($"   {patch.AssetName}:");
                    prevAssetName = patch.AssetName;
                }

                // track old values
                string wasAssetName = patch.AssetName;
                bool   wasApplied   = patch.MatchesContext;

                // update patch
                bool changed     = patch.UpdateContext(this.ConditionContext, tokenisableConditions);
                bool shouldApply = patch.MatchesContext;

                // track patches to reload
                bool reload = (wasApplied && changed) || (!wasApplied && shouldApply);
                if (reload)
                {
                    patch.IsApplied = false;
                    if (wasApplied)
                    {
                        reloadAssetNames.Add(wasAssetName);
                    }
                    if (shouldApply)
                    {
                        reloadAssetNames.Add(patch.AssetName);
                    }
                }

                // log change
                if (this.Verbose)
                {
                    IList <string> changes = new List <string>();
                    if (wasApplied != shouldApply)
                    {
                        changes.Add(shouldApply ? "enabled" : "disabled");
                    }
                    if (wasAssetName != patch.AssetName)
                    {
                        changes.Add($"target: {wasAssetName} => {patch.AssetName}");
                    }
                    string changesStr = string.Join(", ", changes);

                    this.VerboseLog($"      [{(shouldApply ? "X" : " ")}] {patch.LogName}: {(changes.Any() ? changesStr : "OK")}");
                }
            }

            // rebuild asset name lookup
            this.PatchesByCurrentTarget = new InvariantDictionary <HashSet <IPatch> >(
                from patchGroup in this.Patches.GroupBy(p => p.AssetName, StringComparer.InvariantCultureIgnoreCase)
                let key                         = patchGroup.Key
                                      let value = new HashSet <IPatch>(patchGroup)
                                                  select new KeyValuePair <string, HashSet <IPatch> >(key, value)
                );

            // reload assets if needed
            if (reloadAssetNames.Any())
            {
                this.VerboseLog($"   reloading {reloadAssetNames.Count} assets: {string.Join(", ", reloadAssetNames.OrderBy(p => p))}");
                contentHelper.InvalidateCache(asset =>
                {
                    this.VerboseLog($"      [{(reloadAssetNames.Contains(asset.AssetName) ? "X" : " ")}] reload {asset.AssetName}");
                    return(reloadAssetNames.Contains(asset.AssetName));
                });
            }
        }
Esempio n. 2
0
        /// <summary>Prepare a local asset file for a patch to use.</summary>
        /// <param name="pack">The content pack being loaded.</param>
        /// <param name="path">The asset path in the content patch.</param>
        /// <param name="config">The config values to apply.</param>
        /// <param name="conditions">The conditions to apply.</param>
        /// <param name="error">The error reason if preparing the asset fails.</param>
        /// <param name="tokenedPath">The parsed value.</param>
        /// <param name="shouldPreload">Whether to preload assets if needed.</param>
        /// <returns>Returns whether the local asset was successfully prepared.</returns>
        private bool TryPrepareLocalAsset(ManagedContentPack pack, string path, InvariantDictionary <ConfigField> config, ConditionDictionary conditions, out string error, out TokenString tokenedPath, bool shouldPreload)
        {
            // normalise raw value
            path = this.NormaliseLocalAssetPath(pack, path);
            if (path == null)
            {
                error       = $"must set the {nameof(PatchConfig.FromFile)} field for this action type.";
                tokenedPath = null;
                return(false);
            }

            // tokenise
            if (!this.TryParseTokenString(path, config, out string tokenError, out TokenStringBuilder builder))
            {
                error       = $"the {nameof(PatchConfig.FromFile)} is invalid: {tokenError}";
                tokenedPath = null;
                return(false);
            }
            tokenedPath = builder.Build();

            // preload & validate possible file paths
            InvariantHashSet missingFiles = new InvariantHashSet();

            foreach (string localKey in this.ConditionFactory.GetPossibleStrings(tokenedPath, conditions))
            {
                if (!pack.FileExists(localKey))
                {
                    missingFiles.Add(localKey);
                }
                else if (shouldPreload)
                {
                    pack.PreloadIfNeeded(localKey);
                }
            }
            if (missingFiles.Any())
            {
                error = tokenedPath.ConditionTokens.Any() || missingFiles.Count > 1
                    ? $"{nameof(PatchConfig.FromFile)} '{path}' matches files which don't exist ({string.Join(", ", missingFiles.OrderBy(p => p))})."
                    : $"{nameof(PatchConfig.FromFile)} '{path}' matches a file which doesn't exist.";
                tokenedPath = null;
                return(false);
            }

            // looks OK
            error = null;
            return(true);
        }
Esempio n. 3
0
        /// <summary>Prepare a local asset file for a patch to use.</summary>
        /// <param name="pack">The content pack being loaded.</param>
        /// <param name="path">The asset path in the content patch.</param>
        /// <param name="config">The config values to apply.</param>
        /// <param name="conditions">The conditions to apply.</param>
        /// <param name="error">The error reason if preparing the asset fails.</param>
        /// <param name="tokenedPath">The parsed value.</param>
        /// <param name="checkOnly">Prepare the asset info and check if it's valid, but don't actually preload the asset.</param>
        /// <returns>Returns whether the local asset was successfully prepared.</returns>
        private bool TryPrepareLocalAsset(IContentPack pack, string path, InvariantDictionary <ConfigField> config, ConditionDictionary conditions, out string error, out TokenString tokenedPath, bool checkOnly)
        {
            // normalise raw value
            path = this.NormaliseLocalAssetPath(pack, path);
            if (path == null)
            {
                error       = $"must set the {nameof(PatchConfig.FromFile)} field for this action type.";
                tokenedPath = null;
                return(false);
            }

            // tokenise
            if (!this.TryParseTokenString(path, config, out string tokenError, out TokenStringBuilder builder))
            {
                error       = $"the {nameof(PatchConfig.FromFile)} is invalid: {tokenError}";
                tokenedPath = null;
                return(false);
            }
            tokenedPath = builder.Build();

            // validate all possible files exist
            // + preload PNG assets to avoid load-in-draw-loop error
            InvariantHashSet missingFiles = new InvariantHashSet();

            foreach (string localKey in this.ConditionFactory.GetPossibleStrings(tokenedPath, conditions))
            {
                // check-only mode
                if (checkOnly)
                {
                    if (!this.AssetLoader.FileExists(pack, localKey))
                    {
                        missingFiles.Add(localKey);
                    }
                    continue;
                }

                // else preload
                try
                {
                    if (this.AssetLoader.PreloadIfNeeded(pack, localKey))
                    {
                        this.VerboseLog($"      preloaded {localKey}.");
                    }
                }
                catch (FileNotFoundException)
                {
                    missingFiles.Add(localKey);
                }
            }
            if (missingFiles.Any())
            {
                error = tokenedPath.ConditionTokens.Any() || missingFiles.Count > 1
                    ? $"{nameof(PatchConfig.FromFile)} '{path}' matches files which don't exist ({string.Join(", ", missingFiles.OrderBy(p => p))})."
                    : $"{nameof(PatchConfig.FromFile)} '{path}' matches a file which doesn't exist.";
                tokenedPath = null;
                return(false);
            }

            // looks OK
            error = null;
            return(true);
        }
Esempio n. 4
0
        /// <summary>Update the current context.</summary>
        /// <param name="contentHelper">The content helper through which to invalidate assets.</param>
        public void UpdateContext(IContentHelper contentHelper)
        {
            this.VerboseLog("Propagating context...");

            // update patches
            InvariantHashSet reloadAssetNames = new InvariantHashSet();
            string           prevAssetName    = null;

            foreach (IPatch patch in this.Patches.OrderBy(p => p.AssetName).ThenBy(p => p.LogName))
            {
                // log asset name
                if (this.Verbose && prevAssetName != patch.AssetName)
                {
                    this.VerboseLog($"   {patch.AssetName}:");
                    prevAssetName = patch.AssetName;
                }

                // track old values
                string wasAssetName = patch.AssetName;
                bool   wasApplied   = patch.MatchesContext;

                // update patch
                IContext tokenContext = this.TokenManager.TrackLocalTokens(patch.ContentPack.Pack);
                bool     changed      = patch.UpdateContext(tokenContext);
                bool     shouldApply  = patch.MatchesContext;

                // track patches to reload
                bool reload = (wasApplied && changed) || (!wasApplied && shouldApply);
                if (reload)
                {
                    patch.IsApplied = false;
                    if (wasApplied)
                    {
                        reloadAssetNames.Add(wasAssetName);
                    }
                    if (shouldApply)
                    {
                        reloadAssetNames.Add(patch.AssetName);
                    }
                }

                // log change
                if (this.Verbose)
                {
                    IList <string> changes = new List <string>();
                    if (wasApplied != shouldApply)
                    {
                        changes.Add(shouldApply ? "enabled" : "disabled");
                    }
                    if (wasAssetName != patch.AssetName)
                    {
                        changes.Add($"target: {wasAssetName} => {patch.AssetName}");
                    }
                    string changesStr = string.Join(", ", changes);

                    this.VerboseLog($"      [{(shouldApply ? "X" : " ")}] {patch.LogName}: {(changes.Any() ? changesStr : "OK")}");
                }

                // warn for invalid load patch
                if (patch is LoadPatch loadPatch && patch.MatchesContext && !patch.ContentPack.FileExists(loadPatch.LocalAsset.Value))
                {
                    this.Monitor.Log($"Patch error: {patch.LogName} has a {nameof(PatchConfig.FromFile)} which matches non-existent file '{loadPatch.LocalAsset.Value}'.", LogLevel.Error);
                }
            }

            // rebuild asset name lookup
            this.PatchesByCurrentTarget = new InvariantDictionary <HashSet <IPatch> >(
                from patchGroup in this.Patches.GroupBy(p => p.AssetName, StringComparer.InvariantCultureIgnoreCase)
                let key                         = patchGroup.Key
                                      let value = new HashSet <IPatch>(patchGroup)
                                                  select new KeyValuePair <string, HashSet <IPatch> >(key, value)
                );

            // reload assets if needed
            if (reloadAssetNames.Any())
            {
                this.VerboseLog($"   reloading {reloadAssetNames.Count} assets: {string.Join(", ", reloadAssetNames.OrderBy(p => p))}");
                contentHelper.InvalidateCache(asset =>
                {
                    this.VerboseLog($"      [{(reloadAssetNames.Contains(asset.AssetName) ? "X" : " ")}] reload {asset.AssetName}");
                    return(reloadAssetNames.Contains(asset.AssetName));
                });
            }
        }