/// <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); }
/// <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); }
/// <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); }
/// <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; } } } }
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; }
/// <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())); }
/// <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); }
/// <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); }
/// <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; } } }
/// <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 } }
/********* ** 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); }
/// <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); }
/********* ** 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); }
/********* ** 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)); }
/// <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; })); }
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)); } } }
/**** ** 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); }
/// <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); }
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; }
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); }
/// <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); }
/// <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; }
/// <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); } }
/********* ** 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) ); }
/********* ** 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); }
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); }