Exemple #1
0
        /// <summary>Set a unique name for all patches in a content pack.</summary>
        /// <param name="contentPack">The content pack.</param>
        /// <param name="patches">The patches to name.</param>
        private void NamePatches(ManagedContentPack contentPack, PatchConfig[] patches)
        {
            // add default log names
            foreach (PatchConfig patch in patches)
            {
                if (string.IsNullOrWhiteSpace(patch.LogName))
                {
                    patch.LogName = $"{patch.Action} {patch.Target}";
                }
            }

            // detect duplicate names
            InvariantHashSet duplicateNames = new InvariantHashSet(
                from patch in patches
                group patch by patch.LogName into nameGroup
                where nameGroup.Count() > 1
                select nameGroup.Key
                );

            // make names unique
            int i = 0;

            foreach (PatchConfig patch in patches)
            {
                i++;

                if (duplicateNames.Contains(patch.LogName))
                {
                    patch.LogName = $"entry #{i} ({patch.LogName})";
                }

                patch.LogName = $"{contentPack.Manifest.Name} > {patch.LogName}";
            }
        }
Exemple #2
0
        /// <summary>Validate that the provided input argument is valid.</summary>
        /// <param name="input">The input argument, if applicable.</param>
        /// <param name="error">The validation error, if any.</param>
        /// <returns>Returns whether validation succeeded.</returns>
        public bool TryValidateInput(ITokenString input, out string error)
        {
            // validate input
            if (input.IsMeaningful())
            {
                // check if input allowed
                if (!this.AllowsInput)
                {
                    error = $"invalid input argument ({input}), token {this.Name} doesn't allow input.";
                    return(false);
                }

                // check value
                InvariantHashSet validInputs = this.GetValidInputs();
                if (validInputs?.Any() == true)
                {
                    if (!validInputs.Contains(input.Value))
                    {
                        error = $"invalid input argument ({(input.Raw != input.Value ? $"{input.Raw} => {input.Value}" : input.Value)}) for {this.Name} token, expected any of {string.Join(", ", validInputs)}";
                        return(false);
                    }
                }
            }

            // no issues found
            error = null;
            return(true);
        }
Exemple #3
0
        /// <inheritdoc />
        public virtual bool TryValidateInput(IInputArguments input, out string error)
        {
            if (input.IsReady)
            {
                // validate positional arguments
                if (input.HasPositionalArgs)
                {
                    // check if input allowed
                    if (!this.AllowsPositionalInput)
                    {
                        error = $"invalid input arguments ({input.TokenString}), token {this.Name} doesn't allow input.";
                        return(false);
                    }

                    // check argument count
                    if (input.PositionalArgs.Length > this.MaxPositionalArgs)
                    {
                        error = $"invalid input arguments ({input.TokenString}), token {this.Name} doesn't allow more than {this.MaxPositionalArgs} argument{(this.MaxPositionalArgs == 1 ? "" : "s")}.";
                        return(false);
                    }

                    // check values
                    InvariantHashSet validInputs = this.GetValidPositionalArgs();
                    if (validInputs?.Any() == true)
                    {
                        if (input.PositionalArgs.Any(arg => !validInputs.Contains(arg)))
                        {
                            string raw    = input.TokenString.Raw;
                            string parsed = input.TokenString.Value;
                            error = $"invalid input arguments ({(raw != parsed ? $"{raw} => {parsed}" : parsed)}) for {this.Name} token, expected any of '{string.Join("', '", validInputs.OrderByIgnoreCase(p => p))}'";
                            return(false);
                        }
                    }
                }

                // validate named arguments
                if (input.HasNamedArgs)
                {
                    if (this.ValidNamedArguments != null)
                    {
                        if (!this.ValidNamedArguments.Any())
                        {
                            error = $"invalid named argument '{input.NamedArgs.First().Key}' for {this.Name} token, which does not accept any named arguments.";
                            return(false);
                        }

                        string invalidKey = (from arg in input.NamedArgs where !this.ValidNamedArguments.Contains(arg.Key) select arg.Key).FirstOrDefault();
                        if (invalidKey != null)
                        {
                            error = $"invalid named argument '{invalidKey}' for {this.Name} token, expected any of '{string.Join("', '", this.ValidNamedArguments.OrderByIgnoreCase(p => p))}'";
                            return(false);
                        }
                    }
                }
            }

            // no issues found
            error = null;
            return(true);
        }
Exemple #4
0
        /// <summary>Validate whether a token referenced in a content pack field is valid.</summary>
        /// <param name="lexToken">The lexical token reference to check.</param>
        /// <param name="assumeModIds">Mod IDs to assume are installed for purposes of token validation.</param>
        /// <param name="error">An error phrase indicating why validation failed (if applicable).</param>
        public bool TryValidateToken(LexTokenToken lexToken, InvariantHashSet assumeModIds, out string error)
        {
            // token doesn't exist
            IToken token = this.Context.GetToken(lexToken.Name, enforceContext: false);

            if (token == null)
            {
                // special case: requires a missing mod that's checked via HasMod
                string requiredModId = lexToken.GetProviderModId();
                if (assumeModIds?.Contains(requiredModId) == true && !this.InstalledMods.Contains(requiredModId))
                {
                    error = null;
                    return(true);
                }

                // else error
                error = $"'{lexToken}' can't be used as a token because that token could not be found.";
                return(false);
            }

            // token requires dependency
            if (token is ModProvidedToken modToken && !this.ForMod.HasDependency(modToken.Mod.UniqueID, canBeOptional: false))
            {
                if (assumeModIds == null || !assumeModIds.Contains(modToken.Mod.UniqueID))
                {
                    error = $"'{lexToken}' can't be used because it's provided by {modToken.Mod.Name} (ID: {modToken.Mod.UniqueID}), but {this.ForMod.Name} doesn't list it as a dependency and the patch doesn't have an immutable {ConditionType.HasMod} condition for that mod.";
                    return(false);
                }
            }

            // no error found
            error = null;
            return(true);
        }
        /// <inheritdoc />
        public virtual IEnumerable <string> GetValues(IInputArguments input)
        {
            // get values
            var values = this.Values.GetValues(input);

            // apply contains
            if (input.ReservedArgs.TryGetValue(InputArguments.ContainsKey, out IInputArgumentValue rawSearch))
            {
                InvariantHashSet search = new InvariantHashSet(rawSearch.Parsed.Select(this.NormalizeValue));
                bool             match  = search.Any() && values.Any(value => search.Contains(value));
                values = new[] { match.ToString() };
            }

            return(values);
        }
        /// <summary>Update the current context.</summary>
        /// <param name="globalChangedTokens">The global token values which changed, or <c>null</c> to update all tokens.</param>
        public void UpdateContext(InvariantHashSet globalChangedTokens = null)
        {
            // update global tokens
            foreach (IToken token in this.GlobalContext.Tokens.Values)
            {
                if (token.IsMutable && globalChangedTokens?.Contains(token.Name) != false)
                {
                    token.UpdateContext(this);
                }
            }

            // update mod contexts
            foreach (ModTokenContext localContext in this.LocalTokens.Values)
            {
                localContext.UpdateContext(this, globalChangedTokens);
            }
        }
Exemple #7
0
        /// <summary>Validate that the provided values are valid for the input argument (regardless of whether they match).</summary>
        /// <param name="input">The input argument, if applicable.</param>
        /// <param name="values">The values to validate.</param>
        /// <param name="error">The validation error, if any.</param>
        /// <returns>Returns whether validation succeeded.</returns>
        public bool TryValidateValues(ITokenString input, InvariantHashSet values, out string error)
        {
            if (!this.TryValidateInput(input, out error))
            {
                return(false);
            }

            // default validation
            {
                InvariantHashSet validValues = this.GetAllowedValues(input);
                if (validValues?.Any() == true)
                {
                    string[] invalidValues = values
                                             .Where(p => !validValues.Contains(p))
                                             .Distinct()
                                             .ToArray();
                    if (invalidValues.Any())
                    {
                        error = $"invalid values ({string.Join(", ", invalidValues)}); expected one of {string.Join(", ", validValues)}";
                        return(false);
                    }
                }
            }

            // custom validation
            foreach (string value in values)
            {
                if (!this.TryValidate(input, value, out error))
                {
                    return(false);
                }
            }

            // no issues found
            error = null;
            return(true);
        }
Exemple #8
0
        /// <summary>Handle the 'patch summary' command.</summary>
        /// <param name="args">The subcommand arguments.</param>
        /// <returns>Returns whether the command was handled.</returns>
        private bool HandleSummary(string[] args)
        {
            StringBuilder  output = new StringBuilder();
            LogPathBuilder path   = new LogPathBuilder("console command");

            // parse arguments
            bool showFull  = false;
            var  forModIds = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

            foreach (string arg in args)
            {
                // flags
                if (arg.Equals("full", StringComparison.OrdinalIgnoreCase))
                {
                    showFull = true;
                    continue;
                }

                // for mod ID
                forModIds.Add(arg);
            }

            // truncate token values if needed
            string GetTruncatedTokenValues(IEnumerable <string> values)
            {
                const int    maxLength       = 200;
                const string truncatedSuffix = "... (use `patch summary full` to see other values)";

                string valueStr = string.Join(", ", values);

                return(showFull || valueStr.Length <= maxLength
                    ? valueStr
                    : $"{valueStr.Substring(0, maxLength - truncatedSuffix.Length)}{truncatedSuffix}");
            }

            // add condition summary
            output.AppendLine();
            output.AppendLine("=====================");
            output.AppendLine("==  Global tokens  ==");
            output.AppendLine("=====================");
            {
                // get data
                var tokensByProvider =
                    (
                        from token in this.TokenManager.GetTokens(enforceContext: false)
                        let inputArgs = token.GetAllowedInputArguments().ToArray()
                                        let rootValues = !token.RequiresInput ? token.GetValues(InputArguments.Empty).ToArray() : new string[0]
                                                         let isMultiValue =
                            inputArgs.Length > 1 ||
                            rootValues.Length > 1 ||
                            (inputArgs.Length == 1 && token.GetValues(new InputArguments(new LiteralString(inputArgs[0], path.With(token.Name, "input")))).Count() > 1)
                            let mod = (token as ModProvidedToken)?.Mod
                                      orderby isMultiValue, token.Name // single-value tokens first, then alphabetically
                        select new { Mod = mod, Token = token }
                    )
                    .GroupBy(p => p.Mod?.Name?.Trim())
                    .OrderBy(p => p.Key) // default tokens (key is null), then tokens added by other mods
                    .ToArray();
                int labelWidth = Math.Max(tokensByProvider.Max(group => group.Max(p => p.Token.Name.Length)), "token name".Length);

                // group by provider mod (if any)
                foreach (var tokenGroup in tokensByProvider)
                {
                    if (tokenGroup.Key != null && forModIds.Any() && !forModIds.Contains(tokenGroup.First().Mod.UniqueID))
                    {
                        continue;
                    }

                    // print mod name
                    output.AppendLine($"   {tokenGroup.Key ?? "Content Patcher"}:");
                    output.AppendLine();

                    // print table header
                    output.AppendLine($"      {"token name".PadRight(labelWidth)} | value");
                    output.AppendLine($"      {"".PadRight(labelWidth, '-')} | -----");

                    // print tokens
                    foreach (IToken token in tokenGroup.Select(p => p.Token))
                    {
                        output.Append($"      {token.Name.PadRight(labelWidth)} | ");

                        if (!token.IsReady)
                        {
                            output.AppendLine("[ ] n/a");
                        }
                        else if (token.RequiresInput)
                        {
                            InvariantHashSet allowedInputs = token.GetAllowedInputArguments();
                            if (allowedInputs.Any())
                            {
                                bool isFirst = true;
                                foreach (string input in allowedInputs.OrderByIgnoreCase(input => input))
                                {
                                    if (isFirst)
                                    {
                                        output.Append("[X] ");
                                        isFirst = false;
                                    }
                                    else
                                    {
                                        output.Append($"      {"".PadRight(labelWidth, ' ')} |     ");
                                    }

                                    output.AppendLine($":{input}: {GetTruncatedTokenValues(token.GetValues(new InputArguments(new LiteralString(input, path.With(token.Name, "input")))))}");
                                }
                            }
                            else
                            {
                                output.AppendLine("[X] (token returns a dynamic value)");
                            }
                        }
                        else
                        {
                            output.AppendLine("[X] " + GetTruncatedTokenValues(token.GetValues(InputArguments.Empty).OrderByIgnoreCase(p => p)));
                        }
                    }

                    output.AppendLine();
                }
            }

            // add patch summary
            var patches = this.GetAllPatches()
                          .Where(p => !forModIds.Any() || forModIds.Contains(p.ContentPack.Manifest.UniqueID))
                          .GroupByIgnoreCase(p => p.ContentPack.Manifest.Name)
                          .OrderByIgnoreCase(p => p.Key);

            output.AppendLine(
                "=====================\n"
                + "== Content patches ==\n"
                + "=====================\n"
                + "The following patches were loaded. For each patch:\n"
                + "  - 'loaded' shows whether the patch is loaded and enabled (see details for the reason if not).\n"
                + "  - 'conditions' shows whether the patch matches with the current conditions (see details for the reason if not). If this is unexpectedly false, check (a) the conditions above and (b) your Where field.\n"
                + "  - 'applied' shows whether the target asset was loaded and patched. If you expected it to be loaded by this point but it's false, double-check (a) that the game has actually loaded the asset yet, and (b) your Targets field is correct.\n"
                + (forModIds.Any() ? $"\n(Filtered to content pack ID{(forModIds.Count > 1 ? "s" : "")}: {string.Join(", ", forModIds.OrderByIgnoreCase(p => p))}.)\n" : "")
                + "\n"
                );
            foreach (IGrouping <string, PatchInfo> patchGroup in patches)
            {
                ModTokenContext tokenContext = this.TokenManager.TrackLocalTokens(patchGroup.First().ContentPack);
                output.AppendLine($"{patchGroup.Key}:");
                output.AppendLine("".PadRight(patchGroup.Key.Length + 1, '-'));

                // print tokens
                {
                    var tokens =
                        (
                            // get non-global tokens
                            from IToken token in tokenContext.GetTokens(enforceContext: false)
                            where token.Scope != null

                            // get input arguments
                            let validInputs = token.IsReady && token.RequiresInput
                                ? token.GetAllowedInputArguments().Select(p => new LiteralString(p, path.With(patchGroup.Key, token.Name, $"input '{p}'"))).AsEnumerable <ITokenString>()
                                : new ITokenString[] { null }
                            from ITokenString input in validInputs

                            where !token.RequiresInput || validInputs.Any() // don't show tokens which can't be represented

                            // select display data
                            let result = new
                    {
                        Name = token.RequiresInput ? $"{token.Name}:{input}" : token.Name,
                        Values = token.IsReady ? token.GetValues(new InputArguments(input)).ToArray() : new string[0],
                        IsReady = token.IsReady
                    }
                            orderby result.Name
                            select result
                        )
                        .ToArray();
                    if (tokens.Any())
                    {
                        int labelWidth = Math.Max(tokens.Max(p => p.Name.Length), "token name".Length);

                        output.AppendLine();
                        output.AppendLine("   Local tokens:");

                        output.AppendLine($"      {"token name".PadRight(labelWidth)} | value");
                        output.AppendLine($"      {"".PadRight(labelWidth, '-')} | -----");

                        foreach (var token in tokens)
                        {
                            output.AppendLine($"      {token.Name.PadRight(labelWidth)} | [{(token.IsReady ? "X" : " ")}] {GetTruncatedTokenValues(token.Values)}");
                        }
                    }
                }

                // print patches
                output.AppendLine();
                output.AppendLine("   Patches:");
                output.AppendLine("      loaded  | conditions | applied | name + details");
                output.AppendLine("      ------- | ---------- | ------- | --------------");
                foreach (PatchInfo patch in patchGroup.OrderBy(p => p, new PatchDisplaySortComparer()))
                {
                    // log checkbox and patch name
                    output.Append($"      [{(patch.IsLoaded ? "X" : " ")}]     | [{(patch.MatchesContext ? "X" : " ")}]        | [{(patch.IsApplied ? "X" : " ")}]     | {patch.PathWithoutContentPackPrefix}");

                    // log target value if different from name
                    {
                        // get patch values
                        string rawIdentifyingPath = PathUtilities.NormalizePath(patch.ParsedType == PatchType.Include
                            ? patch.RawFromAsset
                            : patch.RawTargetAsset
                                                                                );
                        var parsedIdentifyingPath = patch.ParsedType == PatchType.Include
                            ? patch.ParsedFromAsset
                            : patch.ParsedTargetAsset;

                        // get raw name if different
                        // (ignore differences in whitespace, capitalization, and path separators)
                        string rawValue = !PathUtilities.NormalizePath(patch.PathWithoutContentPackPrefix.ToString().Replace(" ", "")).ContainsIgnoreCase(rawIdentifyingPath?.Replace(" ", ""))
                            ? $"{patch.ParsedType?.ToString() ?? patch.RawType} {rawIdentifyingPath}"
                            : null;

                        // get parsed value
                        string parsedValue = patch.MatchesContext && parsedIdentifyingPath?.HasAnyTokens == true
                            ? PathUtilities.NormalizePath(parsedIdentifyingPath.Value)
                            : null;

                        // format
                        if (rawValue != null || parsedValue != null)
                        {
                            output.Append(" (");
                            if (rawValue != null)
                            {
                                output.Append(rawValue);
                                if (parsedValue != null)
                                {
                                    output.Append(" ");
                                }
                            }
                            if (parsedValue != null)
                            {
                                output.Append($"=> {parsedValue}");
                            }
                            output.Append(")");
                        }
                    }

                    // log reason not applied
                    string errorReason = this.GetReasonNotLoaded(patch);
                    if (errorReason != null)
                    {
                        output.Append($"  // {errorReason}");
                    }

                    // log common issues if not applied
                    if (errorReason == null && patch.IsLoaded && !patch.IsApplied && patch.ParsedTargetAsset.IsMeaningful())
                    {
                        string assetName = patch.ParsedTargetAsset.Value;

                        List <string> issues = new List <string>();
                        if (this.AssetNameWithContentPattern.IsMatch(assetName))
                        {
                            issues.Add("shouldn't include 'Content/' prefix");
                        }
                        if (this.AssetNameWithExtensionPattern.IsMatch(assetName))
                        {
                            var match = this.AssetNameWithExtensionPattern.Match(assetName);
                            issues.Add($"shouldn't include '{match.Captures[0]}' extension");
                        }
                        if (this.AssetNameWithLocalePattern.IsMatch(assetName))
                        {
                            issues.Add("shouldn't include language code (use conditions instead)");
                        }

                        if (issues.Any())
                        {
                            output.Append($" // hint: asset name may be incorrect ({string.Join("; ", issues)}).");
                        }
                    }

                    // log update rate issues
                    if (patch.Patch != null)
                    {
                        foreach (var pair in this.TokenManager.TokensWithSpecialUpdateRates)
                        {
                            UpdateRate rate       = pair.Item1;
                            string     label      = pair.Item2;
                            var        tokenNames = pair.Item3;

                            if (!patch.Patch.UpdateRate.HasFlag(rate))
                            {
                                var tokensUsed = new InvariantHashSet(patch.Patch.GetTokensUsed());

                                string[] locationTokensUsed = tokenNames.Where(p => tokensUsed.Contains(p)).ToArray();
                                if (locationTokensUsed.Any())
                                {
                                    output.Append($" // hint: patch uses {label}, but doesn't set \"{nameof(PatchConfig.Update)}\": \"{rate}\".");
                                }
                            }
                        }
                    }

                    // end line
                    output.AppendLine();
                }

                // print patch effects
                {
                    IDictionary <string, InvariantHashSet> effectsByPatch = new Dictionary <string, InvariantHashSet>(StringComparer.OrdinalIgnoreCase);
                    foreach (PatchInfo patch in patchGroup)
                    {
                        if (!patch.IsApplied || patch.Patch == null)
                        {
                            continue;
                        }

                        string[] changeLabels = patch.GetChangeLabels().ToArray();
                        if (!changeLabels.Any())
                        {
                            continue;
                        }

                        if (!effectsByPatch.TryGetValue(patch.ParsedTargetAsset.Value, out InvariantHashSet effects))
                        {
                            effectsByPatch[patch.ParsedTargetAsset.Value] = effects = new InvariantHashSet();
                        }

                        effects.AddMany(patch.GetChangeLabels());
                    }

                    output.AppendLine();
                    if (effectsByPatch.Any())
                    {
                        int maxAssetNameWidth = Math.Max("asset name".Length, effectsByPatch.Max(p => p.Key.Length));

                        output.AppendLine("   Current changes:");
                        output.AppendLine($"      asset name{"".PadRight(maxAssetNameWidth - "asset name".Length)} | changes");
                        output.AppendLine($"      ----------{"".PadRight(maxAssetNameWidth - "----------".Length, '-')} | -------");

                        foreach (var pair in effectsByPatch.OrderBy(p => p.Key, StringComparer.OrdinalIgnoreCase))
                        {
                            output.AppendLine($"      {pair.Key}{"".PadRight(maxAssetNameWidth - pair.Key.Length)} | {string.Join("; ", pair.Value.OrderBy(p => p, StringComparer.OrdinalIgnoreCase))}");
                        }
                    }
                    else
                    {
                        output.AppendLine("   No current changes.");
                    }
                }

                // add blank line between groups
                output.AppendLine();
            }

            this.Monitor.Log(output.ToString(), LogLevel.Debug);
            return(true);
        }
Exemple #9
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));
                });
            }
        }
        /// <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.OrderByIgnoreCase(p => p.AssetName).ThenByIgnoreCase(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.GroupByIgnoreCase(p => p.AssetName)
                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.OrderByIgnoreCase(p => p))}");
                contentHelper.InvalidateCache(asset =>
                {
                    this.VerboseLog($"      [{(reloadAssetNames.Contains(asset.AssetName) ? "X" : " ")}] reload {asset.AssetName}");
                    return(reloadAssetNames.Contains(asset.AssetName));
                });
            }
        }
Exemple #11
0
        /// <summary>Validate that the provided values are valid for the input argument (regardless of whether they match).</summary>
        /// <param name="input">The input argument, if applicable.</param>
        /// <param name="values">The values to validate.</param>
        /// <param name="error">The validation error, if any.</param>
        /// <returns>Returns whether validation succeeded.</returns>
        public bool TryValidate(string input, InvariantHashSet values, out string error)
        {
            // parse data
            KeyValuePair <string, string>[] pairs = this.GetInputValuePairs(input, values).ToArray();

            // restrict to allowed input
            if (this.AllowsInput)
            {
                InvariantHashSet validInputs = this.GetValidInputs();
                if (validInputs != null)
                {
                    string[] invalidInputs =
                        (
                            from pair in pairs
                            where pair.Key != null && !validInputs.Contains(pair.Key)
                            select pair.Key
                        )
                        .Distinct()
                        .ToArray();
                    if (invalidInputs.Any())
                    {
                        error = $"invalid input arguments ({string.Join(", ", invalidInputs)}), expected any of {string.Join(", ", validInputs)}";
                        return(false);
                    }
                }
            }

            // restrict to allowed values
            {
                InvariantHashSet validValues = this.GetAllowedValues(input);
                if (validValues?.Any() == true)
                {
                    string[] invalidValues =
                        (
                            from pair in pairs
                            where !validValues.Contains(pair.Value)
                            select pair.Value
                        )
                        .Distinct()
                        .ToArray();
                    if (invalidValues.Any())
                    {
                        error = $"invalid values ({string.Join(", ", invalidValues)}); expected one of {string.Join(", ", validValues)}";
                        return(false);
                    }
                }
            }

            // custom validation
            foreach (KeyValuePair <string, string> pair in pairs)
            {
                if (!this.TryValidate(pair.Key, pair.Value, out error))
                {
                    return(false);
                }
            }

            // no issues found
            error = null;
            return(true);
        }
Exemple #12
0
        /// <summary>Perform custom validation.</summary>
        /// <param name="name">The token name to validate.</param>
        /// <param name="values">The values to validate.</param>
        /// <param name="error">The validation error, if any.</param>
        /// <returns>Returns whether validation succeeded.</returns>
        public bool TryValidate(TokenName name, InvariantHashSet values, out string error)
        {
            // parse data
            KeyValuePair <TokenName, string>[] pairs = this.GetSubkeyValuePairsFor(name, values).ToArray();

            // restrict to allowed subkeys
            if (this.CanHaveSubkeys)
            {
                InvariantHashSet validKeys = this.GetAllowedSubkeys();
                if (validKeys != null)
                {
                    string[] invalidSubkeys =
                        (
                            from pair in pairs
                            where pair.Key.Subkey != null && !validKeys.Contains(pair.Key.Subkey)
                            select pair.Key.Subkey
                        )
                        .Distinct()
                        .ToArray();
                    if (invalidSubkeys.Any())
                    {
                        error = $"invalid subkeys ({string.Join(", ", invalidSubkeys)}); expected one of {string.Join(", ", validKeys)}";
                        return(false);
                    }
                }
            }

            // restrict to allowed values
            {
                InvariantHashSet validValues = this.GetAllowedValues(name);
                if (validValues?.Any() == true)
                {
                    string[] invalidValues =
                        (
                            from pair in pairs
                            where !validValues.Contains(pair.Value)
                            select pair.Value
                        )
                        .Distinct()
                        .ToArray();
                    if (invalidValues.Any())
                    {
                        error = $"invalid values ({string.Join(", ", invalidValues)}); expected one of {string.Join(", ", validValues)}";
                        return(false);
                    }
                }
            }

            // custom validation
            foreach (KeyValuePair <TokenName, string> pair in pairs)
            {
                if (!this.Values.TryValidate(pair.Key.Subkey, new InvariantHashSet {
                    pair.Value
                }, out error))
                {
                    return(false);
                }
            }

            // no issues found
            error = null;
            return(true);
        }
        public bool Contains(string search, params string[] values)
        {
            InvariantHashSet set = new InvariantHashSet(values);

            return(set.Contains(search));
        }