示例#1
0
        /// <summary>Read the configuration file for a content pack.</summary>
        /// <param name="contentPack">The content pack.</param>
        /// <param name="rawSchema">The raw config schema from the mod's <c>content.json</c>.</param>
        public InvariantDictionary <ConfigField> Read(ManagedContentPack contentPack, InvariantDictionary <ConfigSchemaFieldConfig> rawSchema)
        {
            InvariantDictionary <ConfigField> config = this.LoadConfigSchema(rawSchema, logWarning: (field, reason) => this.LogWarning(contentPack, $"{nameof(ContentConfig.ConfigSchema)} field '{field}'", reason));

            this.LoadConfigValues(contentPack, config, logWarning: (field, reason) => this.LogWarning(contentPack, $"{this.Filename} > {field}", reason));
            return(config);
        }
示例#2
0
        /// <summary>Parse a string which can contain tokens, and validate that it's valid.</summary>
        /// <param name="rawValue">The raw string which may contain tokens.</param>
        /// <param name="config">The player configuration.</param>
        /// <param name="error">An error phrase indicating why parsing failed (if applicable).</param>
        /// <param name="parsed">The parsed value.</param>
        private bool TryParseTokenString(string rawValue, InvariantDictionary <ConfigField> config, out string error, out TokenStringBuilder parsed)
        {
            // parse
            TokenStringBuilder builder = new TokenStringBuilder(rawValue, config);

            // validate unknown tokens
            if (builder.InvalidTokens.Any())
            {
                parsed = null;
                error  = $"found unknown tokens: {string.Join(", ", builder.InvalidTokens.OrderBy(p => p))}";
                return(false);
            }

            // validate config tokens
            foreach (string key in builder.ConfigTokens)
            {
                ConfigField field = config[key];
                if (field.AllowMultiple)
                {
                    parsed = null;
                    error  = $"token {{{{{key}}}}} can't be used because that config field allows multiple values.";
                    return(false);
                }
            }

            // looks OK
            parsed = builder;
            error  = null;
            return(true);
        }
示例#3
0
        /// <inheritdoc />
        public override void Handle(string[] args)
        {
            InvariantDictionary <ICommand> commands = this.GetCommands();

            // build output
            StringBuilder help = new();

            if (!args.Any())
            {
                help.AppendLine(
                    $"The '{this.RootName}' command is the entry point for {this.ModName} commands. You use it by specifying a more "
                    + $"specific command (like '{GenericHelpCommand.CommandName}' in '{this.RootName} {GenericHelpCommand.CommandName}'). Here are the available commands:\n\n"
                    );
                foreach (var entry in commands.OrderBy(p => p.Key, HumanSortComparer.DefaultIgnoreCase))
                {
                    help.AppendLine(entry.Value.Description);
                    help.AppendLine();
                    help.AppendLine();
                }
            }
            else if (commands.TryGetValue(args[0], out ICommand? command))
            {
                help.AppendLine(command.Description);
            }
            else
            {
                help.AppendLine($"Unknown command '{this.RootName} {args[0]}'. Type '{this.RootName} {GenericHelpCommand.CommandName}' for available commands.");
            }

            // write output
            this.Monitor.Log(help.ToString().Trim(), LogLevel.Info);
        }
示例#4
0
        /// <summary>Load config values from the content pack.</summary>
        /// <param name="contentPack">The content pack whose config file to read.</param>
        /// <param name="config">The config schema.</param>
        /// <param name="logWarning">The callback to invoke on each validation warning, passed the field name and reason respectively.</param>
        private void LoadConfigValues(IContentPack contentPack, InvariantDictionary <ConfigField> config, Action <string, string> logWarning)
        {
            if (!config.Any())
            {
                return;
            }

            // read raw config
            InvariantDictionary <InvariantHashSet> configValues = new InvariantDictionary <InvariantHashSet>(
                from entry in (contentPack.ReadJsonFile <InvariantDictionary <string> >(this.Filename) ?? new InvariantDictionary <string>())
                let key                         = entry.Key.Trim()
                                      let value = this.ParseCommaDelimitedField(entry.Value)
                                                  select new KeyValuePair <string, InvariantHashSet>(key, value)
                );

            // remove invalid values
            foreach (string key in configValues.Keys.ExceptIgnoreCase(config.Keys).ToArray())
            {
                logWarning(key, "no such field supported by this content pack.");
                configValues.Remove(key);
            }

            // inject default values
            foreach (string key in config.Keys)
            {
                ConfigField field = config[key];
                if (!configValues.TryGetValue(key, out InvariantHashSet values) || (!field.AllowBlank && !values.Any()))
                {
                    configValues[key] = field.DefaultValues;
                }
            }

            // parse each field
            foreach (string key in config.Keys)
            {
                // set value
                ConfigField field = config[key];
                field.Value = configValues[key];

                // validate allow-multiple
                if (!field.AllowMultiple && field.Value.Count > 1)
                {
                    logWarning(key, "field only allows a single value.");
                    field.Value = field.DefaultValues;
                    continue;
                }

                // validate allow-values
                if (field.AllowValues.Any())
                {
                    string[] invalidValues = field.Value.ExceptIgnoreCase(field.AllowValues).ToArray();
                    if (invalidValues.Any())
                    {
                        logWarning(key,
                                   $"found invalid values ({string.Join(", ", invalidValues)}), expected: {string.Join(", ", field.AllowValues)}.");
                        field.Value = field.DefaultValues;
                    }
                }
            }
        }
示例#5
0
        public void GetPermutations()
        {
            // arrange
            InvariantDictionary <InvariantHashSet> values = new InvariantDictionary <InvariantHashSet>
            {
                ["a"] = new InvariantHashSet {
                    "a1", "a2", "a3"
                },
                ["b"] = new InvariantHashSet {
                    "b1", "b2", "b3"
                },
                ["c"] = new InvariantHashSet {
                    "c1", "c2", "c3"
                }
            };

            // act
            IEnumerable <InvariantDictionary <string> > permutations = new ConditionFactory().GetPermutations(values);

            // assert
            IEnumerable <string> actual   = permutations.Select(permutation => "(" + this.SortAndCommaDelimit(permutation.Values.OrderBy(p => p)) + ")");
            IList <string>       expected = new List <string>();

            for (int a = 1; a <= 3; a++)
            {
                for (int b = 1; b <= 3; b++)
                {
                    for (int c = 1; c <= 3; c++)
                    {
                        expected.Add($"(a{a}, b{b}, c{c})");
                    }
                }
            }
            this.SortAndCommaDelimit(actual).Should().Be(this.SortAndCommaDelimit(expected));
        }
 public int Indexer_Count(params string[] keys)
 {
     InvariantDictionary<bool> dict = new InvariantDictionary<bool>();
     foreach (string key in keys)
         dict[key] = true;
     return dict.Count;
 }
示例#7
0
        /// <summary>Get every permutation of the given values.</summary>
        /// <param name="values">The possible values.</param>
        public IEnumerable <InvariantDictionary <string> > GetPermutations(InvariantDictionary <InvariantHashSet> values)
        {
            // no permutations possible
            if (!values.Any())
            {
                return(new InvariantDictionary <string> [0]);
            }

            // recursively find permutations
            InvariantDictionary <string> curPermutation = new InvariantDictionary <string>();

            IEnumerable <InvariantDictionary <string> > GetPermutations(string[] keyQueue)
            {
                if (!keyQueue.Any())
                {
                    yield return(new InvariantDictionary <string>(curPermutation));

                    yield break;
                }

                string key = keyQueue[0];

                foreach (string value in values[key])
                {
                    curPermutation[key] = value;
                    foreach (var permutation in GetPermutations(keyQueue.Skip(1).ToArray()))
                    {
                        yield return(permutation);
                    }
                }
            }

            return(GetPermutations(values.Keys.ToArray()));
        }
示例#8
0
        /// <inheritdoc />
        public override void Handle(string[] args)
        {
            InvariantDictionary <ICommand> commands = this.GetCommands();

            // build output
            StringBuilder help = new();

            if (!args.Any())
            {
                help.AppendLine(
                    "The 'patch' command is the entry point for Content Patcher commands. These are "
                    + "intended for troubleshooting and aren't intended for players. You use it by specifying a more "
                    + "specific command (like 'help' in 'patch help'). Here are the available commands:\n\n"
                    );
                foreach (var entry in commands.OrderByHuman(p => p.Key))
                {
                    help.AppendLine(entry.Value.Description);
                    help.AppendLine();
                    help.AppendLine();
                }
            }
            else if (commands.TryGetValue(args[0], out ICommand command))
            {
                help.AppendLine(command.Description);
            }
            else
            {
                help.AppendLine($"Unknown command 'patch {args[0]}'. Type 'patch help' for available commands.");
            }

            // write output
            this.Monitor.Log(help.ToString().Trim(), LogLevel.Info);
        }
示例#9
0
        /// <summary>Find any <see cref="PatchType.Load"/> patches which conflict with the given patch, taking conditions into account.</summary>
        /// <param name="newPatch">The new patch to check.</param>
        public InvariantDictionary <IPatch> GetConflictingLoaders(IPatch newPatch)
        {
            InvariantDictionary <IPatch> conflicts = new InvariantDictionary <IPatch>();

            if (newPatch.Type == PatchType.Load)
            {
                foreach (string assetName in this.GetPossibleAssetNames(newPatch))
                {
                    if (!this.LoaderCache.TryGetValue(assetName, out ISet <IPatch> otherPatches))
                    {
                        continue;
                    }

                    foreach (IPatch otherPatch in otherPatches)
                    {
                        if (this.ConditionFactory.CanConditionsOverlap(newPatch.Conditions, otherPatch.Conditions))
                        {
                            conflicts[assetName] = otherPatch;
                            break;
                        }
                    }
                }
            }

            return(conflicts);
        }
示例#10
0
        /// <summary>Parse arguments from a tokenized string.</summary>
        /// <param name="input">The tokenized string to parse.</param>
        /// <param name="positionalSegment">The raw input argument segment containing positional arguments, after parsing tokens but before splitting into individual arguments.</param>
        /// <param name="positionalArgs">The positional arguments.</param>
        /// <param name="namedArgs">The named arguments.</param>
        /// <param name="reservedArgs">The named arguments handled by Content Patcher.</param>
        /// <param name="reservedArgsList">An ordered list of the <paramref name="reservedArgs"/>, including duplicate args.</param>
        private static void Parse(ITokenString input, out string positionalSegment, out string[] positionalArgs, out IDictionary <string, IInputArgumentValue> namedArgs, out IDictionary <string, IInputArgumentValue> reservedArgs, out IList <KeyValuePair <string, IInputArgumentValue> > reservedArgsList)
        {
            InputArguments.GetRawArguments(input, out positionalSegment, out InvariantDictionary <string> rawNamedArgs);

            // get value separator
            if (!rawNamedArgs.TryGetValue(InputArguments.InputSeparatorKey, out string inputSeparator) || string.IsNullOrWhiteSpace(inputSeparator))
            {
                inputSeparator = ",";
            }

            // parse args
            positionalArgs   = positionalSegment.SplitValuesNonUnique(separator: inputSeparator).ToArray();
            namedArgs        = new InvariantDictionary <IInputArgumentValue>();
            reservedArgs     = new InvariantDictionary <IInputArgumentValue>();
            reservedArgsList = new List <KeyValuePair <string, IInputArgumentValue> >();
            foreach (var arg in rawNamedArgs)
            {
                var values = new InputArgumentValue(arg.Value, arg.Value.SplitValuesNonUnique(separator: inputSeparator).ToArray());

                if (InputArguments.ReservedArgKeys.Contains(arg.Key))
                {
                    reservedArgs[arg.Key] = values;
                    reservedArgsList.Add(new KeyValuePair <string, IInputArgumentValue>(arg.Key, values));
                }
                else
                {
                    namedArgs[arg.Key] = values;
                }
            }
        }
示例#11
0
        /// <summary>Add warps to the map.</summary>
        /// <param name="target">The target map to change.</param>
        /// <param name="errors">The errors indexed by warp string/</param>
        private void ApplyWarps(Map target, out IDictionary <string, string> errors)
        {
            errors = new InvariantDictionary <string>();

            // build new warp string
            List <string> validWarps = new List <string>(this.AddWarps.Length);

            foreach (string?warp in this.AddWarps.Select(p => p.Value))
            {
                if (!this.ValidateWarp(warp, out string?error))
                {
                    errors[warp ?? "<null>"] = error;
                    continue;
                }

                validWarps.Add(warp);
            }

            // prepend to map property
            if (validWarps.Any())
            {
                string prevWarps = target.Properties.TryGetValue("Warp", out PropertyValue? rawWarps)
                    ? rawWarps.ToString()
                    : "";
                string newWarps = string.Join(" ", validWarps);

                target.Properties["Warp"] = $"{newWarps} {prevWarps}".Trim(); // prepend so warps added later 'overwrite' in case of conflict
            }
        }
示例#12
0
        /*********
        ** Properties
        *********/
        /// <summary>Parse a raw config schema for a content pack.</summary>
        /// <param name="rawSchema">The raw config schema.</param>
        /// <param name="logWarning">The callback to invoke on each validation warning, passed the field name and reason respectively.</param>
        private InvariantDictionary <ConfigField> LoadConfigSchema(InvariantDictionary <ConfigSchemaFieldConfig> rawSchema, Action <string, string> logWarning)
        {
            InvariantDictionary <ConfigField> schema = new InvariantDictionary <ConfigField>();

            if (rawSchema == null || !rawSchema.Any())
            {
                return(schema);
            }

            foreach (string key in rawSchema.Keys)
            {
                ConfigSchemaFieldConfig field = rawSchema[key];

                // validate key
                if (Enum.TryParse(key, true, out ConditionKey conditionKey))
                {
                    logWarning(key, $"can't use {conditionKey} as a config field, because it's a reserved condition name.");
                    continue;
                }

                // read allowed values
                InvariantHashSet allowValues = this.ParseCommaDelimitedField(field.AllowValues);
                if (!allowValues.Any())
                {
                    logWarning(key, $"no {nameof(ConfigSchemaFieldConfig.AllowValues)} specified.");
                    continue;
                }

                // read default values
                InvariantHashSet defaultValues = this.ParseCommaDelimitedField(field.Default);
                {
                    // inject default
                    if (!defaultValues.Any() && !field.AllowBlank)
                    {
                        defaultValues = new InvariantHashSet(allowValues.First());
                    }

                    // validate values
                    string[] invalidValues = defaultValues.Except(allowValues).ToArray();
                    if (invalidValues.Any())
                    {
                        logWarning(key, $"default values '{string.Join(", ", invalidValues)}' are not allowed according to {nameof(ConfigSchemaFieldConfig.AllowBlank)}.");
                        continue;
                    }

                    // validate allow multiple
                    if (!field.AllowMultiple && defaultValues.Count > 1)
                    {
                        logWarning(key, $"can't have multiple default values because {nameof(ConfigSchemaFieldConfig.AllowMultiple)} is false.");
                        continue;
                    }
                }

                // add to schema
                schema[key] = new ConfigField(allowValues, defaultValues, field.AllowBlank, field.AllowMultiple);
            }

            return(schema);
        }
示例#13
0
        /// <summary>Construct a token string and permanently apply config values.</summary>
        public TokenString Build()
        {
            TokenString tokenString = new TokenString(this.RawValue, this.ConditionTokens, TokenStringBuilder.TokenPattern);
            InvariantDictionary <string> configValues = new InvariantDictionary <string>(this.Config.ToDictionary(p => p.Key, p => p.Value.Value.FirstOrDefault()));

            tokenString.ApplyPermanently(configValues);
            return(tokenString);
        }
        public bool ContainsKey(string search, params string[] keys)
        {
            InvariantDictionary<bool> dict = new InvariantDictionary<bool>();
            foreach (string key in keys)
                dict.Add(key, true);

            return dict.ContainsKey(search);
        }
示例#15
0
        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="rootName">The name of the root command.</param>
        /// <param name="modName">The human-readable name of the mod shown in the root command's help description.</param>
        /// <param name="commands">The console commands recognized by the mod through the root command.</param>
        /// <param name="monitor">Encapsulates monitoring and logging.</param>
        public GenericCommandHandler(string rootName, string modName, IEnumerable <ICommand> commands, IMonitor monitor)
        {
            this.RootName = rootName;
            this.ModName  = modName;
            this.Monitor  = monitor;

            this.Commands = new InvariantDictionary <ICommand>(commands.ToDictionary(p => p.Name));
            this.Commands[GenericHelpCommand.CommandName] = new GenericHelpCommand(rootName, modName, monitor, () => this.Commands);
        }
示例#16
0
 /*********
 ** Public methods
 *********/
 /// <summary>Construct an instance.</summary>
 /// <param name="format">The format version.</param>
 /// <param name="dynamicTokens">The user-defined tokens whose values may depend on other tokens.</param>
 /// <param name="aliasTokenNames">The user-defined alias token names.</param>
 /// <param name="customLocations">The custom locations to add to the game.</param>
 /// <param name="changes">The changes to make.</param>
 /// <param name="configSchema">The schema for the <c>config.json</c> file (if any).</param>
 public ContentConfig(ISemanticVersion?format, DynamicTokenConfig?[]?dynamicTokens, InvariantDictionary <string?>?aliasTokenNames, CustomLocationConfig?[]?customLocations, PatchConfig?[]?changes, InvariantDictionary <ConfigSchemaFieldConfig?>?configSchema)
 {
     this.Format          = format;
     this.DynamicTokens   = dynamicTokens ?? Array.Empty <DynamicTokenConfig>();
     this.AliasTokenNames = aliasTokenNames ?? new InvariantDictionary <string?>();
     this.CustomLocations = customLocations ?? Array.Empty <CustomLocationConfig>();
     this.Changes         = changes ?? Array.Empty <PatchConfig>();
     this.ConfigSchema    = configSchema ?? new InvariantDictionary <ConfigSchemaFieldConfig?>();
 }
        public void Add_FailsIfDuplicate()
        {
            // ReSharper disable once CollectionNeverQueried.Local
            InvariantDictionary <bool> dict = new InvariantDictionary <bool> {
                ["a"] = true
            };

            Assert.Throws <ArgumentException>(() => dict.Add("A", false));
        }
示例#18
0
 /// <summary>Get a new string with tokens substituted.</summary>
 /// <param name="raw">The raw string before token substitution.</param>
 /// <param name="tokens">The token values to apply.</param>
 private string Apply(string raw, InvariantDictionary <string> tokens)
 {
     return(this.TokenPattern.Replace(raw, match =>
     {
         string key = match.Groups[1].Value.Trim();
         return tokens.TryGetValue(key, out string value)
             ? value
             : match.Value;
     }));
 }
示例#19
0
 protected void BuildRelatedFIeldQueries(EntityQueryBuilder qBuilder)
 {
     _relatedFieldDataQryList = new InvariantDictionary <string>();
     foreach (var rel in this.Relations)
     {
         if (rel.Type == EntityRelationType.ManyToMany)
         {
             var childE = EntityMetaData.Get(rel.ChildName);
             _relatedFieldDataQryList.Add(rel.ParentRefField.Name, qBuilder.PrepareRelatedFieldDataQueries(rel, childE));
         }
     }
 }
示例#20
0
        /****
        ** Condition parsing
        ****/
        /// <summary>Normalise and parse the given condition values.</summary>
        /// <param name="raw">The raw condition values to normalise.</param>
        /// <param name="conditions">The normalised conditions.</param>
        /// <param name="error">An error message indicating why normalisation failed.</param>
        public bool TryParseConditions(InvariantDictionary <string> raw, out ConditionDictionary conditions, out string error)
        {
            // no conditions
            if (raw == null || !raw.Any())
            {
                conditions = this.ConditionFactory.BuildEmpty();
                error      = null;
                return(true);
            }

            // parse conditions
            conditions = this.ConditionFactory.BuildEmpty();
            foreach (KeyValuePair <string, string> pair in raw)
            {
                // parse condition key
                if (!Enum.TryParse(pair.Key, true, out ConditionKey key))
                {
                    error      = $"'{pair.Key}' isn't a valid condition; must be one of {string.Join(", ", this.ConditionFactory.GetValidConditions())}";
                    conditions = null;
                    return(false);
                }

                // parse values
                InvariantHashSet values = this.ParseCommaDelimitedField(pair.Value);
                if (!values.Any())
                {
                    error      = $"{key} can't be empty";
                    conditions = null;
                    return(false);
                }

                // restrict to allowed values
                InvariantHashSet validValues = new InvariantHashSet(this.ConditionFactory.GetValidValues(key));
                {
                    string[] invalidValues = values.Except(validValues, StringComparer.InvariantCultureIgnoreCase).ToArray();
                    if (invalidValues.Any())
                    {
                        error      = $"invalid {key} values ({string.Join(", ", invalidValues)}); expected one of {string.Join(", ", validValues)}";
                        conditions = null;
                        return(false);
                    }
                }

                // create condition
                conditions[key] = new Condition(key, values);
            }

            // return parsed conditions
            error = null;
            return(true);
        }
示例#21
0
        /// <summary>Parse a boolean value from a string which can contain tokens, and validate that it's valid.</summary>
        /// <param name="rawValue">The raw string which may contain tokens.</param>
        /// <param name="config">The player configuration.</param>
        /// <param name="error">An error phrase indicating why parsing failed (if applicable).</param>
        /// <param name="parsed">The parsed value.</param>
        private bool TryParseBoolean(string rawValue, InvariantDictionary <ConfigField> config, out string error, out bool parsed)
        {
            parsed = false;

            // parse tokens
            if (!this.TryParseTokenString(rawValue, config, out error, out TokenStringBuilder builder))
            {
                return(false);
            }

            // validate tokens
            if (builder.HasAnyTokens)
            {
                // validate: no condition tokens allowed
                if (builder.ConditionTokens.Any())
                {
                    error = $"can't use condition tokens in a boolean field ({string.Join(", ", builder.ConditionTokens.OrderBy(p => p))}).";
                    return(false);
                }

                // validate config tokens
                if (builder.ConfigTokens.Any())
                {
                    // max one tokem
                    if (builder.ConfigTokens.Count > 1)
                    {
                        error = "can't use multiple tokens.";
                        return(false);
                    }

                    // field isn't boolean
                    string      key   = builder.ConfigTokens.First();
                    ConfigField field = config[key];
                    if (field.AllowValues.Except(new[] { "true", "false" }, StringComparer.InvariantCultureIgnoreCase).Any())
                    {
                        error = $"can't use {{{{{key}}}}} because that config field isn't restricted to 'true' or 'false'.";
                        return(false);
                    }
                }
            }

            // parse value
            TokenString tokenString = builder.Build();

            if (!bool.TryParse(tokenString.Raw, out parsed))
            {
                error = $"can't parse {tokenString.Raw} as a true/false value.";
                return(false);
            }
            return(true);
        }
示例#22
0
        private void PrepareErrorModel(EntityModelBase recordModel, ref UIFormModel model)
        {
            var fields = new InvariantDictionary <UIFormField>();

            foreach (var fData in recordModel.GetInvalidFields())
            {
                fields.Add(fData.Field.ViewName.ToUpper(), new UIFormField()
                {
                    WidgetId = fData.Field.ViewName, ErrorMessage = fData.ErrorMessage
                });
            }

            model.Widgets = fields;
        }
示例#23
0
        public static InvariantDictionary <IEnumerable <DbObject> > Query(InvariantDictionary <string> sqls, object param = null, IDbTransaction trans = null)
        {
            InvariantDictionary <IEnumerable <DbObject> > rows = new InvariantDictionary <IEnumerable <DbObject> >();

            using (IDbConnection dbConnection = Connection)
            {
                dbConnection.Open();
                foreach (var q in sqls)
                {
                    var data = dbConnection.Query <dynamic>(q.Value, param, trans);
                    rows.Add(q.Key, ConvertToDbObjectData(data));
                }
            }

            return(rows);
        }
示例#24
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);
        }
示例#25
0
        /// <summary>Get the raw positional and named argument strings.</summary>
        /// <param name="input">The tokenized string to parse.</param>
        /// <param name="rawPositional">The positional arguments string.</param>
        /// <param name="rawNamed">The named argument string.</param>
        private static void GetRawArguments(ITokenString input, out string rawPositional, out InvariantDictionary <string> rawNamed)
        {
            // get token text
            string raw = input?.IsReady == true
                ? input.Value
                : input?.Raw;

            raw = raw?.Trim() ?? string.Empty;

            // split into positional and named segments
            string positionalSegment;
            string namedSegment;

            {
                int splitIndex = raw.IndexOf("|", StringComparison.Ordinal);
                if (splitIndex == -1)
                {
                    positionalSegment = raw;
                    namedSegment      = string.Empty;
                }
                else
                {
                    string[] parts = raw.Split(new[] { '|' }, 2);
                    positionalSegment = parts[0].Trim();
                    namedSegment      = parts[1].Trim();
                }
            }

            // extract raw arguments
            rawPositional = positionalSegment;
            rawNamed      = new InvariantDictionary <string>();
            foreach (string arg in namedSegment.SplitValuesNonUnique(separator: "|"))
            {
                string[] parts = arg.Split(new[] { '=' }, 2);

                if (parts.Length == 1)
                {
                    rawNamed[arg] = string.Empty;
                }
                else
                {
                    string key   = parts[0].Trim();
                    string value = parts[1].Trim();
                    rawNamed[key] = value;
                }
            }
        }
 /*********
 ** Public methods
 *********/
 /// <summary>Construct an instance.</summary>
 /// <param name="modRegistry">An API for fetching metadata about loaded mods.</param>
 /// <param name="monitor">Encapsulates monitoring and logging.</param>
 /// <param name="manifest">The mod manifest.</param>
 /// <param name="parseCommaDelimitedField">The Generic Mod Config Menu integration.</param>
 /// <param name="config">The config model.</param>
 /// <param name="saveAndApply">Save and apply the current config model.</param>
 public GenericModConfigMenuIntegrationForContentPack(IModRegistry modRegistry, IMonitor monitor, IManifest manifest, Func <string, InvariantHashSet> parseCommaDelimitedField, InvariantDictionary <ConfigField> config, Action saveAndApply)
 {
     this.Config     = config;
     this.ConfigMenu = new GenericModConfigMenuIntegration <InvariantDictionary <ConfigField> >(
         modRegistry: modRegistry,
         monitor: monitor,
         consumerManifest: manifest,
         getConfig: () => config,
         reset: () =>
     {
         this.Reset();
         saveAndApply();
     },
         saveAndApply
         );
     this.ParseCommaDelimitedField = parseCommaDelimitedField;
 }
示例#27
0
        /// <summary>Save the configuration file for a content pack.</summary>
        /// <param name="contentPack">The content pack.</param>
        /// <param name="config">The configuration to save.</param>
        /// <param name="modHelper">The mod helper through which to save the file.</param>
        public void Save(IContentPack contentPack, InvariantDictionary <ConfigField> config, IModHelper modHelper)
        {
            string configPath = Path.Combine(contentPack.DirectoryPath, this.Filename);

            // save if settings valid
            if (config.Any())
            {
                InvariantDictionary <string> data = new InvariantDictionary <string>(config.ToDictionary(p => p.Key, p => string.Join(", ", p.Value.Value)));
                modHelper.WriteJsonFile(configPath, data);
            }

            // delete if no settings
            else if (File.Exists(configPath))
            {
                File.Delete(configPath);
            }
        }
示例#28
0
        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="screenManager">Manages state for each screen.</param>
        /// <param name="monitor">Encapsulates monitoring and logging.</param>
        /// <param name="contentHelper">Provides an API for managing content assets.</param>
        /// <param name="contentPacks">The loaded content packs.</param>
        /// <param name="getContext">Get the current token context.</param>
        /// <param name="updateContext">A callback which immediately updates the current condition context.</param>
        public CommandHandler(PerScreen <ScreenManager> screenManager, IMonitor monitor, IContentHelper contentHelper, LoadedContentPack[] contentPacks, Func <string, IContext> getContext, Action updateContext)
        {
            this.Monitor = monitor;

            this.Commands = new InvariantDictionary <ICommand>(
                new ICommand[]
            {
                new DumpCommand(
                    monitor: this.Monitor,
                    getPatchManager: () => screenManager.Value.PatchManager
                    ),
                new ExportCommand(
                    monitor: this.Monitor
                    ),
                new HelpCommand(
                    monitor: this.Monitor,
                    getCommands: () => this.Commands
                    ),
                new InvalidateCommand(
                    monitor: this.Monitor,
                    contentHelper: contentHelper
                    ),
                new ParseCommand(
                    monitor: this.Monitor,
                    getContext: getContext
                    ),
                new ReloadCommand(
                    monitor: this.Monitor,
                    getPatchLoader: () => screenManager.Value.PatchLoader,
                    contentPacks: contentPacks,
                    updateContext: updateContext
                    ),
                new SummaryCommand(
                    monitor: this.Monitor,
                    getPatchManager: () => screenManager.Value.PatchManager,
                    getTokenManager: () => screenManager.Value.TokenManager,
                    getCustomLocationLoader: () => screenManager.Value.CustomLocationManager
                    ),
                new UpdateCommand(
                    monitor: this.Monitor,
                    updateContext: updateContext
                    )
            }
                .ToDictionary(p => p.Name)
                );
        }
示例#29
0
        /*********
        ** Private methods
        *********/
        /****
        ** Commands
        ****/
        /// <summary>Handle the 'patch help' command.</summary>
        /// <param name="args">The subcommand arguments.</param>
        /// <returns>Returns whether the command was handled.</returns>
        private bool HandleHelp(string[] args)
        {
            // generate command info
            var helpEntries = new InvariantDictionary <string>
            {
                ["help"]    = $"{this.CommandName} help\n   Usage: {this.CommandName} help\n   Lists all available {this.CommandName} commands.\n\n   Usage: {this.CommandName} help <cmd>\n   Provides information for a specific {this.CommandName} command.\n   - cmd: The {this.CommandName} command name.",
                ["dump"]    = $"{this.CommandName} dump\n   Usage: {this.CommandName} dump order\n   Lists every loaded patch in their apply order. When two patches edit the same asset, they'll be applied in the apply order.",
                ["export"]  = $"{this.CommandName} export\n   Usage: {this.CommandName} export \"<asset name>\"\n   Saves a copy of an asset (including any changes from mods like Content Patcher) to the game folder. The asset name should be the target without the locale or extension, like \"Characters/Abigail\" if you want to export the value of 'Content/Characters/Abigail.xnb'.",
                ["parse"]   = $"{this.CommandName} parse\n   usage: {this.CommandName} parse \"value\"\n   Parses the given token string and shows the result. For example, `{this.CommandName} parse \"assets/{{{{Season}}}}.png\" will show a value like \"assets/Spring.png\".\n\n{this.CommandName} parse \"value\" \"content-pack.id\"\n   Parses the given token string and shows the result, using tokens available to the specified content pack (using the ID from the content pack's manifest.json). For example, `{this.CommandName} parse \"assets/{{{{CustomToken}}}}.png\" \"Pathoschild.ExampleContentPack\".",
                ["reload"]  = $"{this.CommandName} reload\n   Usage: {this.CommandName} reload \"<content pack ID>\"\n   Reloads the patches of the content.json of a content pack. Config schema changes and dynamic token changes are unsupported.",
                ["summary"] = $"{this.CommandName} summary\n   Usage: {this.CommandName} summary\n   Shows a summary of the current conditions and loaded patches.\n\n  Usage: {this.CommandName} summary \"<content pack ID>\"\n Show a summary of the current conditions, and loaded patches for the given content pack.",
                ["update"]  = $"{this.CommandName} update\n   Usage: {this.CommandName} update\n   Immediately refreshes the condition context and rechecks all patches."
            };

            // build output
            StringBuilder help = new StringBuilder();

            if (!args.Any())
            {
                help.AppendLine(
                    $"The '{this.CommandName}' command is the entry point for Content Patcher commands. These are "
                    + "intended for troubleshooting and aren't intended for players. You use it by specifying a more "
                    + $"specific command (like 'help' in '{this.CommandName} help'). Here are the available commands:\n\n"
                    );
                foreach (var entry in helpEntries.OrderByIgnoreCase(p => p.Key))
                {
                    help.AppendLine(entry.Value);
                    help.AppendLine();
                    help.AppendLine();
                }
            }
            else if (helpEntries.TryGetValue(args[0], out string entry))
            {
                help.AppendLine(entry);
            }
            else
            {
                help.AppendLine($"Unknown command '{this.CommandName} {args[0]}'. Type '{this.CommandName} help' for available commands.");
            }

            // write output
            this.Monitor.Log(help.ToString().Trim(), LogLevel.Debug);

            return(true);
        }
示例#30
0
        static UtilityFunctions()
        {
            _binder = new InvariantDictionary <Function>();

            _binder.Add("tovalue", ToValue);

            _binder.Add("parseint", ParseInt);
            _binder.Add("parsedecimal", ParseDecimal);
            _binder.Add("parsedatetime", ParseDateTime);
            _binder.Add("parsebool", ParseBool);

            _binder.Add("isint", IsInt);
            _binder.Add("isdecimal", IsDecimal);
            _binder.Add("isdateTime", IsDateTime);
            _binder.Add("isbool", IsBool);

            _binder.Add("concat", Concat);
        }