/// <summary> /// Loads and verify content packs. /// </summary> /// <returns></returns> public void LoadContentPacks(IEnumerable <IContentPack> contentPacks) { this.monitor.Log("Loading content packs ..."); // Try to load content packs and their's patches foreach (var pack in contentPacks) { try { var managedPack = new ManagedContentPack(pack, this.monitor, this.paranoid); managedPack.Load(); this.packs.Add(managedPack); } catch (ContentPackException e) { this.monitor.Log($"Unable to load content pack `{pack.Manifest.Name}`:", LogLevel.Error); this.monitor.Log($" {e.Message}", LogLevel.Error); } } this.monitor.Log($"Loaded {this.packs.Count} content packs:", LogLevel.Info); this.packs.ForEach(mp => this.monitor.Log($" {mp.Pack.Manifest.Name} {mp.Pack.Manifest.Version} by {mp.Pack.Manifest.Author}", LogLevel.Info)); this.CheckCurrentFormat(this.packs); this.CheckForUsingReplacers(this.packs); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="logName">A unique name for this patch shown in log messages.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="assetName">The normalised asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="records">The data records to edit.</param> /// <param name="fields">The data fields to edit.</param> /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="normaliseAssetName">Normalise an asset name.</param> public EditDataPatch(string logName, ManagedContentPack contentPack, TokenString assetName, ConditionDictionary conditions, IDictionary <string, string> records, IDictionary <string, IDictionary <int, string> > fields, IMonitor monitor, Func <string, string> normaliseAssetName) : base(logName, PatchType.EditData, contentPack, assetName, conditions, normaliseAssetName) { this.Records = records; this.Fields = fields; this.Monitor = monitor; }
/// <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}"; } } // make names unique within content pack foreach (var patchGroup in patches.GroupBy(p => p.LogName, StringComparer.InvariantCultureIgnoreCase).Where(p => p.Count() > 1)) { int i = 0; foreach (var patch in patchGroup) { patch.LogName += $" #{++i}"; } } // prefix with content pack name foreach (var patch in patches) { patch.LogName = $"{contentPack.Manifest.Name} > {patch.LogName}"; } }
/// <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}"; } }
/// <summary> /// Loads and verify content packs. /// </summary> /// <returns></returns> public void LoadContentPacks(IEnumerable <IContentPack> contentPacks) { this.monitor.Log("Loading content packs ..."); // Try to load content packs and their's patches foreach (var pack in contentPacks) { try { var managedPack = new ManagedContentPack(pack, this.monitor); managedPack.Load(); if (!this.allowLegacyPacks && managedPack.IsLegacy()) { this.monitor.Log($"Content pack `{pack.Manifest.Name}` (format version {managedPack.FormatVersion}) was skipped: Loading of legacy content packs (format version 1.2 and older) is disabled for security reasons. If you want load this content pack, allow legacy content packs in config file.", LogLevel.Error); continue; } this.packs.Add(managedPack); } catch (ContentPackException e) { this.monitor.Log($"Unable to load content pack `{pack.Manifest.Name}`:", LogLevel.Error); this.monitor.Log($" {e.Message}", LogLevel.Error); } } this.monitor.Log($"Loaded {this.packs.Count} content packs:", LogLevel.Info); this.packs.ForEach(mp => this.monitor.Log($" {mp.Pack.Manifest.Name} {mp.Pack.Manifest.Version} by {mp.Pack.Manifest.Author}", LogLevel.Info)); this.CheckCurrentFormat(this.packs); this.CheckForUsingReplacers(this.packs); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="logName">A unique name for this patch shown in log messages.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="assetName">The normalised asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="fromAsset">The asset key to load from the content pack instead.</param> /// <param name="fromArea">The map area from which to read tiles.</param> /// <param name="toArea">The map area to overwrite.</param> /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="normaliseAssetName">Normalise an asset name.</param> public EditMapPatch(string logName, ManagedContentPack contentPack, ITokenString assetName, IEnumerable <Condition> conditions, ITokenString fromAsset, Rectangle fromArea, Rectangle toArea, IMonitor monitor, Func <string, string> normaliseAssetName) : base(logName, PatchType.EditMap, contentPack, assetName, conditions, normaliseAssetName, fromAsset: fromAsset) { this.FromArea = fromArea != Rectangle.Empty ? fromArea : null as Rectangle?; this.ToArea = toArea != Rectangle.Empty ? toArea : null as Rectangle?; this.Monitor = monitor; }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="logName">A unique name for this patch shown in log messages.</param> /// <param name="type">The raw patch type.</param> /// <param name="assetName">The raw asset name to intercept.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="reasonDisabled">The reason this patch is disabled.</param> public DisabledPatch(string logName, string type, string assetName, ManagedContentPack contentPack, string reasonDisabled) { this.LogName = logName; this.Type = type; this.ContentPack = contentPack; this.AssetName = assetName; this.ReasonDisabled = reasonDisabled; }
/********* ** Private methods *********/ /// <summary>Get the patch name shown in log messages, without the content pack prefix.</summary> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="logName">The unique patch name shown in log messages.</param> private string GetShortName(ManagedContentPack contentPack, string logName) { string prefix = contentPack.Manifest.Name + " > "; return(logName.StartsWith(prefix) ? logName.Substring(prefix.Length) : logName); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="logName">A unique name for this patch shown in log messages.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="assetName">The normalised asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="records">The data records to edit.</param> /// <param name="fields">The data fields to edit.</param> /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="normaliseAssetName">Normalise an asset name.</param> public EditDataPatch(string logName, ManagedContentPack contentPack, TokenString assetName, ConditionDictionary conditions, IEnumerable <EditDataPatchRecord> records, IEnumerable <EditDataPatchField> fields, IMonitor monitor, Func <string, string> normaliseAssetName) : base(logName, PatchType.EditData, contentPack, assetName, conditions, normaliseAssetName) { this.Records = records.ToArray(); this.Fields = fields.ToArray(); this.Monitor = monitor; this.MutableTokenStrings = this.GetTokenStrings(this.Records, this.Fields).Where(str => str.Tokens.Any()).ToArray(); }
/********* ** Protected methods *********/ /// <summary>Construct an instance.</summary> /// <param name="logName">A unique name for this patch shown in log messages.</param> /// <param name="type">The patch type.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="assetName">The normalised asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="normaliseAssetName">Normalise an asset name.</param> protected Patch(string logName, PatchType type, ManagedContentPack contentPack, TokenString assetName, ConditionDictionary conditions, Func <string, string> normaliseAssetName) { this.LogName = logName; this.Type = type; this.ContentPack = contentPack; this.TokenableAssetName = assetName; this.Conditions = conditions; this.NormaliseAssetName = normaliseAssetName; }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="logName">A unique name for this patch shown in log messages.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="assetName">The normalised asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="fromLocalAsset">The asset key to load from the content pack instead.</param> /// <param name="fromArea">The sprite area from which to read an image.</param> /// <param name="toArea">The sprite area to overwrite.</param> /// <param name="patchMode">Indicates how the image should be patched.</param> /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="normaliseAssetName">Normalise an asset name.</param> public EditImagePatch(string logName, ManagedContentPack contentPack, TokenString assetName, ConditionDictionary conditions, TokenString fromLocalAsset, Rectangle fromArea, Rectangle toArea, PatchMode patchMode, IMonitor monitor, Func <string, string> normaliseAssetName) : base(logName, PatchType.EditImage, contentPack, assetName, conditions, normaliseAssetName) { this.FromLocalAsset = fromLocalAsset; this.FromArea = fromArea != Rectangle.Empty ? fromArea : null as Rectangle?; this.ToArea = toArea != Rectangle.Empty ? toArea : null as Rectangle?; this.PatchMode = patchMode; this.Monitor = monitor; }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="logName">A unique name for this patch shown in log messages.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="assetName">The normalized asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="fromAsset">The asset key to load from the content pack instead.</param> /// <param name="fromArea">The map area from which to read tiles.</param> /// <param name="toArea">The map area to overwrite.</param> /// <param name="mapProperties">The map property to change when editing a map, if any.</param> /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="normalizeAssetName">Normalize an asset name.</param> public EditMapPatch(string logName, ManagedContentPack contentPack, IManagedTokenString assetName, IEnumerable <Condition> conditions, IManagedTokenString fromAsset, TokenRectangle fromArea, TokenRectangle toArea, IEnumerable <EditMapPatchProperty> mapProperties, IMonitor monitor, Func <string, string> normalizeAssetName) : base(logName, PatchType.EditMap, contentPack, assetName, conditions, normalizeAssetName, fromAsset: fromAsset) { this.FromArea = fromArea; this.ToArea = toArea; this.MapProperties = mapProperties?.ToArray() ?? new EditMapPatchProperty[0]; this.Monitor = monitor; this.Contextuals .Add(fromArea) .Add(toArea); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="logName">A unique name for this patch shown in log messages.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="assetName">The normalized asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="fromAsset">The asset key to load from the content pack instead.</param> /// <param name="fromArea">The sprite area from which to read an image.</param> /// <param name="toArea">The sprite area to overwrite.</param> /// <param name="patchMode">Indicates how the image should be patched.</param> /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="normalizeAssetName">Normalize an asset name.</param> public EditImagePatch(string logName, ManagedContentPack contentPack, IManagedTokenString assetName, IEnumerable <Condition> conditions, IManagedTokenString fromAsset, TokenRectangle fromArea, TokenRectangle toArea, PatchMode patchMode, IMonitor monitor, Func <string, string> normalizeAssetName) : base(logName, PatchType.EditImage, contentPack, assetName, conditions, normalizeAssetName, fromAsset: fromAsset) { this.FromArea = fromArea; this.ToArea = toArea; this.PatchMode = patchMode; this.Monitor = monitor; this.Contextuals .Add(fromArea) .Add(toArea); }
/********* ** Protected methods *********/ /// <summary>Construct an instance.</summary> /// <param name="logName">A unique name for this patch shown in log messages.</param> /// <param name="type">The patch type.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="assetName">The normalized asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="normalizeAssetName">Normalize an asset name.</param> /// <param name="fromAsset">The normalized asset key from which to load the local asset (if applicable), including tokens.</param> protected Patch(string logName, PatchType type, ManagedContentPack contentPack, IManagedTokenString assetName, IEnumerable <Condition> conditions, Func <string, string> normalizeAssetName, IManagedTokenString fromAsset = null) { this.LogName = logName; this.Type = type; this.ContentPack = contentPack; this.ManagedRawTargetAsset = assetName; this.Conditions = conditions.ToArray(); this.NormalizeAssetNameImpl = normalizeAssetName; this.PrivateContext = new LocalContext(scope: this.ContentPack.Manifest.UniqueID); this.ManagedRawFromAsset = fromAsset; this.Contextuals .Add(this.Conditions) .Add(assetName); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="logName">A unique name for this patch shown in log messages.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="assetName">The normalised asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="records">The data records to edit.</param> /// <param name="fields">The data fields to edit.</param> /// <param name="moveRecords">The records to reorder, if the target is a list asset.</param> /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="normaliseAssetName">Normalise an asset name.</param> public EditDataPatch(string logName, ManagedContentPack contentPack, ITokenString assetName, IEnumerable <Condition> conditions, IEnumerable <EditDataPatchRecord> records, IEnumerable <EditDataPatchField> fields, IEnumerable <EditDataPatchMoveRecord> moveRecords, IMonitor monitor, Func <string, string> normaliseAssetName) : base(logName, PatchType.EditData, contentPack, assetName, conditions, normaliseAssetName) { // set fields this.Records = records.ToArray(); this.Fields = fields.ToArray(); this.MoveRecords = moveRecords.ToArray(); this.Monitor = monitor; // track contextuals this.Contextuals .Add(this.Records) .Add(this.Fields) .Add(this.MoveRecords) .Add(this.Conditions); }
/// <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); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="logName">A unique name for this patch shown in log messages.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="assetName">The normalized asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="fromFile">The normalized asset key from which to load entries (if applicable), including tokens.</param> /// <param name="records">The data records to edit.</param> /// <param name="fields">The data fields to edit.</param> /// <param name="moveRecords">The records to reorder, if the target is a list asset.</param> /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="normalizeAssetName">Normalize an asset name.</param> /// <param name="tryParseFields">Parse the data change fields for an <see cref="PatchType.EditData"/> patch.</param> public EditDataPatch(string logName, ManagedContentPack contentPack, IManagedTokenString assetName, IEnumerable <Condition> conditions, IManagedTokenString fromFile, IEnumerable <EditDataPatchRecord> records, IEnumerable <EditDataPatchField> fields, IEnumerable <EditDataPatchMoveRecord> moveRecords, IMonitor monitor, Func <string, string> normalizeAssetName, TryParseFieldsDelegate tryParseFields) : base(logName, PatchType.EditData, contentPack, assetName, conditions, normalizeAssetName, fromAsset: fromFile) { // set fields this.Records = records?.ToArray(); this.Fields = fields?.ToArray(); this.MoveRecords = moveRecords?.ToArray(); this.Monitor = monitor; this.TryParseFields = tryParseFields; // track contextuals this.Contextuals .Add(this.Records) .Add(this.Fields) .Add(this.MoveRecords) .Add(this.Conditions); }
/// <summary>Get a normalised file path relative to the content pack folder.</summary> /// <param name="contentPack">The content pack.</param> /// <param name="path">The relative asset path.</param> private string NormaliseLocalAssetPath(ManagedContentPack contentPack, string path) { // normalise asset name if (string.IsNullOrWhiteSpace(path)) { return(null); } string newPath = this.Helper.Content.NormaliseAssetName(path); // add .xnb extension if needed (it's stripped from asset names) string fullPath = contentPack.GetFullPath(newPath); if (!File.Exists(fullPath)) { if (File.Exists($"{fullPath}.xnb") || Path.GetExtension(path) == ".xnb") { newPath += ".xnb"; } } return(newPath); }
/// <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="tokenContext">The tokens available for this content pack.</param> /// <param name="migrator">The migrator which validates and migrates content pack data.</param> /// <param name="error">The error reason if preparing the asset fails.</param> /// <param name="tokenedPath">The parsed value.</param> /// <returns>Returns whether the local asset was successfully prepared.</returns> private bool TryPrepareLocalAsset(ManagedContentPack pack, string path, IContext tokenContext, IMigration migrator, out string error, out TokenString tokenedPath) { // 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, tokenContext, migrator, out string tokenError, out tokenedPath)) { error = $"the {nameof(PatchConfig.FromFile)} is invalid: {tokenError}"; tokenedPath = null; return(false); } // looks OK error = null; return(true); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="logName">A unique name for this patch shown in log messages.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="assetName">The normalised asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="localAsset">The asset key to load from the content pack instead.</param> /// <param name="normaliseAssetName">Normalise an asset name.</param> public LoadPatch(string logName, ManagedContentPack contentPack, ITokenString assetName, IEnumerable <Condition> conditions, ITokenString localAsset, Func <string, string> normaliseAssetName) : base(logName, PatchType.Load, contentPack, assetName, conditions, normaliseAssetName, fromAsset: localAsset) { }
/********* ** Protected methods *********/ /// <summary>Construct an instance.</summary> /// <param name="path">The path to the patch from the root content file.</param> /// <param name="type">The patch type.</param> /// <param name="assetName">The normalized asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="updateRate">When the patch should be updated.</param> /// <param name="normalizeAssetName">Normalize an asset name.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="parentPatch">The parent <see cref="PatchType.Include"/> patch for which this patch was loaded, if any.</param> /// <param name="fromAsset">The normalized asset key from which to load the local asset (if applicable), including tokens.</param> protected Patch(LogPathBuilder path, PatchType type, IManagedTokenString assetName, IEnumerable <Condition> conditions, UpdateRate updateRate, ManagedContentPack contentPack, IPatch parentPatch, Func <string, string> normalizeAssetName, IManagedTokenString fromAsset = null) { this.Path = path; this.Type = type; this.ManagedRawTargetAsset = assetName; this.Conditions = conditions.ToArray(); this.UpdateRate = updateRate; this.NormalizeAssetNameImpl = normalizeAssetName; this.PrivateContext = new LocalContext(scope: contentPack.Manifest.UniqueID); this.ManagedRawFromAsset = fromAsset; this.ContentPack = contentPack; this.ParentPatch = parentPatch; this.Contextuals .Add(this.Conditions) .Add(assetName) .Add(fromAsset); this.ManuallyUpdatedTokens.Add(assetName); this.ManuallyUpdatedTokens.Add(fromAsset); }
/// <summary>Load one patch from a content pack's <c>content.json</c> file.</summary> /// <param name="pack">The content pack being loaded.</param> /// <param name="entry">The change to load.</param> /// <param name="tokenContext">The tokens available for this content pack.</param> /// <param name="migrator">The migrator which validates and migrates content pack data.</param> /// <param name="logSkip">The callback to invoke with the error reason if loading it fails.</param> private bool LoadPatch(ManagedContentPack pack, PatchConfig entry, IContext tokenContext, IMigration migrator, Action <string> logSkip) { bool TrackSkip(string reason, bool warn = true) { this.PatchManager.AddPermanentlyDisabled(new DisabledPatch(entry.LogName, entry.Action, entry.Target, pack, reason)); if (warn) { logSkip(reason); } return(false); } try { // normalise patch fields if (entry.When == null) { entry.When = new InvariantDictionary <string>(); } // parse action if (!Enum.TryParse(entry.Action, true, out PatchType action)) { return(TrackSkip(string.IsNullOrWhiteSpace(entry.Action) ? $"must set the {nameof(PatchConfig.Action)} field." : $"invalid {nameof(PatchConfig.Action)} value '{entry.Action}', expected one of: {string.Join(", ", Enum.GetNames(typeof(PatchType)))}." )); } // parse target asset TokenString assetName; { if (string.IsNullOrWhiteSpace(entry.Target)) { return(TrackSkip($"must set the {nameof(PatchConfig.Target)} field.")); } if (!this.TryParseTokenString(entry.Target, tokenContext, migrator, out string error, out assetName)) { return(TrackSkip($"the {nameof(PatchConfig.Target)} is invalid: {error}")); } } // parse 'enabled' bool enabled = true; { if (entry.Enabled != null && !this.TryParseEnabled(entry.Enabled, tokenContext, migrator, out string error, out enabled)) { return(TrackSkip($"invalid {nameof(PatchConfig.Enabled)} value '{entry.Enabled}': {error}")); } } // parse conditions ConditionDictionary conditions; { if (!this.TryParseConditions(entry.When, tokenContext, migrator, out conditions, out string error)) { return(TrackSkip($"the {nameof(PatchConfig.When)} field is invalid: {error}.")); } } // get patch instance IPatch patch; switch (action) { // load asset case PatchType.Load: { // init patch if (!this.TryPrepareLocalAsset(pack, entry.FromFile, tokenContext, migrator, out string error, out TokenString fromAsset)) { return(TrackSkip(error)); } patch = new LoadPatch(entry.LogName, pack, assetName, conditions, fromAsset, this.Helper.Content.NormaliseAssetName); } break; // edit data case PatchType.EditData: { // validate if (entry.Entries == null && entry.Fields == null) { return(TrackSkip($"either {nameof(PatchConfig.Entries)} or {nameof(PatchConfig.Fields)} must be specified for a '{action}' change.")); } if (entry.Entries != null && entry.Entries.Any(p => p.Value != null && p.Value.Trim() == "")) { return(TrackSkip($"the {nameof(PatchConfig.Entries)} can't contain empty values.")); } if (entry.Fields != null && entry.Fields.Any(p => p.Value == null || p.Value.Any(n => n.Value == null))) { return(TrackSkip($"the {nameof(PatchConfig.Fields)} can't contain empty values.")); } // parse entries List <EditDataPatchRecord> entries = new List <EditDataPatchRecord>(); if (entry.Entries != null) { foreach (KeyValuePair <string, string> pair in entry.Entries) { if (!this.TryParseTokenString(pair.Key, tokenContext, migrator, out string keyError, out TokenString key)) { return(TrackSkip($"{nameof(PatchConfig.Entries)} > '{key}' key is invalid: {keyError}.")); } if (!this.TryParseTokenString(pair.Value, tokenContext, migrator, out string error, out TokenString value)) { return(TrackSkip($"{nameof(PatchConfig.Entries)} > '{key}' value is invalid: {error}.")); } entries.Add(new EditDataPatchRecord(key, value)); } } // parse fields List <EditDataPatchField> fields = new List <EditDataPatchField>(); if (entry.Fields != null) { foreach (KeyValuePair <string, IDictionary <int, string> > recordPair in entry.Fields) { if (!this.TryParseTokenString(recordPair.Key, tokenContext, migrator, out string keyError, out TokenString key)) { return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} is invalid: {keyError}.")); } foreach (var fieldPair in recordPair.Value) { int field = fieldPair.Key; if (!this.TryParseTokenString(fieldPair.Value, tokenContext, migrator, out string valueError, out TokenString value)) { return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} > field {field} is invalid: {valueError}.")); } if (value.Raw?.Contains("/") == true) { return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} > field {field} is invalid: value can't contain field delimiter character '/'.")); } fields.Add(new EditDataPatchField(key, field, value)); } } } // save patch = new EditDataPatch(entry.LogName, pack, assetName, conditions, entries, fields, this.Monitor, this.Helper.Content.NormaliseAssetName); } break; // edit image case PatchType.EditImage: { // read patch mode PatchMode patchMode = PatchMode.Replace; if (!string.IsNullOrWhiteSpace(entry.PatchMode) && !Enum.TryParse(entry.PatchMode, true, out patchMode)) { return(TrackSkip($"the {nameof(PatchConfig.PatchMode)} is invalid. Expected one of these values: [{string.Join(", ", Enum.GetNames(typeof(PatchMode)))}].")); } // save if (!this.TryPrepareLocalAsset(pack, entry.FromFile, tokenContext, migrator, out string error, out TokenString fromAsset)) { return(TrackSkip(error)); } patch = new EditImagePatch(entry.LogName, pack, assetName, conditions, fromAsset, entry.FromArea, entry.ToArea, patchMode, this.Monitor, this.Helper.Content.NormaliseAssetName); } break; default: return(TrackSkip($"unsupported patch type '{action}'.")); } // skip if not enabled // note: we process the patch even if it's disabled, so any errors are caught by the modder instead of only failing after the patch is enabled. if (!enabled) { return(TrackSkip($"{nameof(PatchConfig.Enabled)} is false.", warn: false)); } // save patch this.PatchManager.Add(patch); return(true); } catch (Exception ex) { return(TrackSkip($"error reading info. Technical details:\n{ex}")); } }
/// <summary>Load one patch from a content pack's <c>content.json</c> file.</summary> /// <param name="pack">The content pack being loaded.</param> /// <param name="contentConfig">The content pack's config.</param> /// <param name="entry">The change to load.</param> /// <param name="config">The content pack's config values.</param> /// <param name="latestFormatVersion">The latest format version.</param> /// <param name="logSkip">The callback to invoke with the error reason if loading it fails.</param> private bool LoadPatch(ManagedContentPack pack, ContentConfig contentConfig, PatchConfig entry, InvariantDictionary <ConfigField> config, ISemanticVersion latestFormatVersion, Action <string> logSkip) { bool TrackSkip(string reason, bool warn = true) { this.PatchManager.AddPermanentlyDisabled(new DisabledPatch(entry.LogName, entry.Action, entry.Target, pack, reason)); if (warn) { logSkip(reason); } return(false); } try { // normalise patch fields if (entry.When == null) { entry.When = new InvariantDictionary <string>(); } // parse action if (!Enum.TryParse(entry.Action, true, out PatchType action)) { return(TrackSkip(string.IsNullOrWhiteSpace(entry.Action) ? $"must set the {nameof(PatchConfig.Action)} field." : $"invalid {nameof(PatchConfig.Action)} value '{entry.Action}', expected one of: {string.Join(", ", Enum.GetNames(typeof(PatchType)))}." )); } // parse target asset TokenString assetName; { if (string.IsNullOrWhiteSpace(entry.Target)) { return(TrackSkip($"must set the {nameof(PatchConfig.Target)} field.")); } if (!this.TryParseTokenString(entry.Target, config, out string error, out TokenStringBuilder builder)) { return(TrackSkip($"the {nameof(PatchConfig.Target)} is invalid: {error}")); } assetName = builder.Build(); } // parse 'enabled' bool enabled = true; { if (entry.Enabled != null && !this.TryParseBoolean(entry.Enabled, config, out string error, out enabled)) { return(TrackSkip($"invalid {nameof(PatchConfig.Enabled)} value '{entry.Enabled}': {error}")); } } // apply config foreach (string key in config.Keys) { if (entry.When.TryGetValue(key, out string values)) { InvariantHashSet expected = this.PatchManager.ParseCommaDelimitedField(values); if (!expected.Intersect(config[key].Value, StringComparer.InvariantCultureIgnoreCase).Any()) { return(TrackSkip($"disabled by config {key} (needs '{string.Join(", ", expected)}', found '{string.Join(", ", config[key].Value)}').", warn: false)); } entry.When.Remove(key); } } // parse conditions ConditionDictionary conditions; { if (!this.PatchManager.TryParseConditions(entry.When, contentConfig.Format, latestFormatVersion, out conditions, out string error)) { return(TrackSkip($"the {nameof(PatchConfig.When)} field is invalid: {error}.")); } } // validate conditions if (action == PatchType.Load) { ConditionKey[] tokenisable = this.ConditionFactory.GetTokenisableConditions().ToArray(); ConditionKey[] invalid = conditions.Keys.Except(tokenisable).ToArray(); if (invalid.Any()) { return(TrackSkip($"can't use these conditions with {nameof(PatchConfig.Action)} {PatchType.Load} ({string.Join(", ", invalid)})")); } } // get patch instance IPatch patch; switch (action) { // load asset case PatchType.Load: { // init patch if (!this.TryPrepareLocalAsset(pack, entry.FromFile, config, conditions, out string error, out TokenString fromAsset, shouldPreload: true)) { return(TrackSkip(error)); } patch = new LoadPatch(entry.LogName, pack, assetName, conditions, fromAsset, this.Helper.Content.NormaliseAssetName); // detect conflicting loaders if (enabled) { InvariantDictionary <IPatch> conflicts = this.PatchManager.GetConflictingLoaders(patch); if (conflicts.Any()) { IEnumerable <string> conflictNames = ( from conflict in conflicts orderby conflict.Key select $"'{conflict.Value.LogName}' already loads {conflict.Key}" ); return(TrackSkip( $"{nameof(entry.Target)} '{patch.TokenableAssetName.Raw}' conflicts with other load patches ({string.Join(", ", conflictNames)}). Each file can only be loaded by one patch, unless their conditions can never overlap.")); } } } break; // edit data case PatchType.EditData: { // validate if (entry.Entries == null && entry.Fields == null) { return(TrackSkip($"either {nameof(PatchConfig.Entries)} or {nameof(PatchConfig.Fields)} must be specified for a '{action}' change.")); } if (entry.Entries != null && entry.Entries.Any(p => p.Value != null && p.Value.Trim() == "")) { return(TrackSkip($"the {nameof(PatchConfig.Entries)} can't contain empty values.")); } if (entry.Fields != null && entry.Fields.Any(p => p.Value == null || p.Value.Any(n => n.Value == null))) { return(TrackSkip($"the {nameof(PatchConfig.Fields)} can't contain empty values.")); } // save patch = new EditDataPatch(entry.LogName, pack, assetName, conditions, entry.Entries, entry.Fields, this.Monitor, this.Helper.Content.NormaliseAssetName); } break; // edit image case PatchType.EditImage: { // read patch mode PatchMode patchMode = PatchMode.Replace; if (!string.IsNullOrWhiteSpace(entry.PatchMode) && !Enum.TryParse(entry.PatchMode, true, out patchMode)) { return(TrackSkip($"the {nameof(PatchConfig.PatchMode)} is invalid. Expected one of these values: [{string.Join(", ", Enum.GetNames(typeof(PatchMode)))}].")); } // save if (!this.TryPrepareLocalAsset(pack, entry.FromFile, config, conditions, out string error, out TokenString fromAsset, shouldPreload: true)) { return(TrackSkip(error)); } patch = new EditImagePatch(entry.LogName, pack, assetName, conditions, fromAsset, entry.FromArea, entry.ToArea, patchMode, this.Monitor, this.Helper.Content.NormaliseAssetName); } break; default: return(TrackSkip($"unsupported patch type '{action}'.")); } // only apply patch when its tokens are available HashSet <ConditionKey> tokensUsed = new HashSet <ConditionKey>(patch.GetTokensUsed()); foreach (ConditionKey key in tokensUsed) { if (!patch.Conditions.ContainsKey(key)) { patch.Conditions.Add(key, patch.Conditions.GetValidValues(key)); } } // skip if not enabled // note: we process the patch even if it's disabled, so any errors are caught by the modder instead of only failing after the patch is enabled. if (!enabled) { return(TrackSkip($"{nameof(PatchConfig.Enabled)} is false.", warn: false)); } // save patch this.PatchManager.Add(patch); return(true); } catch (Exception ex) { return(TrackSkip($"error reading info. Technical details:\n{ex}")); } }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="logName">A unique name for this patch shown in log messages.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="assetName">The normalised asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="localAsset">The asset key to load from the content pack instead.</param> /// <param name="normaliseAssetName">Normalise an asset name.</param> public LoadPatch(string logName, ManagedContentPack contentPack, TokenString assetName, ConditionDictionary conditions, TokenString localAsset, Func <string, string> normaliseAssetName) : base(logName, PatchType.Load, contentPack, assetName, conditions, normaliseAssetName) { this.LocalAsset = localAsset; }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="path">The path to the patch from the root content file.</param> /// <param name="assetName">The normalized asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="fromAsset">The asset key to load from the content pack instead.</param> /// <param name="fromArea">The sprite area from which to read an image.</param> /// <param name="toArea">The sprite area to overwrite.</param> /// <param name="patchMode">Indicates how the image should be patched.</param> /// <param name="updateRate">When the patch should be updated.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="parentPatch">The parent patch for which this patch was loaded, if any.</param> /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="normalizeAssetName">Normalize an asset name.</param> public EditImagePatch(LogPathBuilder path, IManagedTokenString assetName, IEnumerable <Condition> conditions, IManagedTokenString fromAsset, TokenRectangle fromArea, TokenRectangle toArea, PatchMode patchMode, UpdateRate updateRate, ManagedContentPack contentPack, IPatch parentPatch, IMonitor monitor, Func <string, string> normalizeAssetName) : base( path: path, type: PatchType.EditImage, assetName: assetName, conditions: conditions, normalizeAssetName: normalizeAssetName, fromAsset: fromAsset, updateRate: updateRate, contentPack: contentPack, parentPatch: parentPatch ) { this.FromArea = fromArea; this.ToArea = toArea; this.PatchMode = patchMode; this.Monitor = monitor; this.Contextuals .Add(fromArea) .Add(toArea); }
/// <summary>Load one patch from a content pack's <c>content.json</c> file.</summary> /// <param name="pack">The content pack being loaded.</param> /// <param name="entry">The change to load.</param> /// <param name="tokenContext">The tokens available for this content pack.</param> /// <param name="migrator">The migrator which validates and migrates content pack data.</param> /// <param name="logSkip">The callback to invoke with the error reason if loading it fails.</param> private bool LoadPatch(ManagedContentPack pack, PatchConfig entry, IContext tokenContext, IMigration migrator, Action <string> logSkip) { bool TrackSkip(string reason, bool warn = true) { reason = reason.TrimEnd('.', ' '); this.PatchManager.AddPermanentlyDisabled(new DisabledPatch(entry.LogName, entry.Action, entry.Target, pack, reason)); if (warn) { logSkip(reason + '.'); } return(false); } try { // normalise patch fields if (entry.When == null) { entry.When = new InvariantDictionary <string>(); } // parse action if (!Enum.TryParse(entry.Action, true, out PatchType action)) { return(TrackSkip(string.IsNullOrWhiteSpace(entry.Action) ? $"must set the {nameof(PatchConfig.Action)} field" : $"invalid {nameof(PatchConfig.Action)} value '{entry.Action}', expected one of: {string.Join(", ", Enum.GetNames(typeof(PatchType)))}" )); } // parse target asset ITokenString assetName; { if (string.IsNullOrWhiteSpace(entry.Target)) { return(TrackSkip($"must set the {nameof(PatchConfig.Target)} field")); } if (!this.TryParseStringTokens(entry.Target, tokenContext, migrator, out string error, out assetName)) { return(TrackSkip($"the {nameof(PatchConfig.Target)} is invalid: {error}")); } } // parse 'enabled' bool enabled = true; { if (entry.Enabled != null && !this.TryParseEnabled(entry.Enabled, tokenContext, migrator, out string error, out enabled)) { return(TrackSkip($"invalid {nameof(PatchConfig.Enabled)} value '{entry.Enabled}': {error}")); } } // parse conditions IList <Condition> conditions; { if (!this.TryParseConditions(entry.When, tokenContext, migrator, out conditions, out string error)) { return(TrackSkip($"the {nameof(PatchConfig.When)} field is invalid: {error}")); } } // get patch instance IPatch patch; switch (action) { // load asset case PatchType.Load: { // init patch if (!this.TryPrepareLocalAsset(pack, entry.FromFile, tokenContext, migrator, out string error, out ITokenString fromAsset)) { return(TrackSkip(error)); } patch = new LoadPatch(entry.LogName, pack, assetName, conditions, fromAsset, this.Helper.Content.NormaliseAssetName); } break; // edit data case PatchType.EditData: { // validate if (entry.Entries == null && entry.Fields == null && entry.MoveEntries == null) { return(TrackSkip($"one of {nameof(PatchConfig.Entries)}, {nameof(PatchConfig.Fields)}, or {nameof(PatchConfig.MoveEntries)} must be specified for an '{action}' change")); } // parse entries List <EditDataPatchRecord> entries = new List <EditDataPatchRecord>(); if (entry.Entries != null) { foreach (KeyValuePair <string, JToken> pair in entry.Entries) { if (!this.TryParseStringTokens(pair.Key, tokenContext, migrator, out string keyError, out ITokenString key)) { return(TrackSkip($"{nameof(PatchConfig.Entries)} > '{key}' key is invalid: {keyError}")); } if (!this.TryParseJsonTokens(pair.Value, tokenContext, migrator, out string error, out TokenisableJToken value)) { return(TrackSkip($"{nameof(PatchConfig.Entries)} > '{key}' value is invalid: {error}")); } entries.Add(new EditDataPatchRecord(key, value)); } } // parse fields List <EditDataPatchField> fields = new List <EditDataPatchField>(); if (entry.Fields != null) { foreach (KeyValuePair <string, IDictionary <string, JToken> > recordPair in entry.Fields) { // parse entry key if (!this.TryParseStringTokens(recordPair.Key, tokenContext, migrator, out string keyError, out ITokenString key)) { return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} is invalid: {keyError}")); } // parse fields foreach (var fieldPair in recordPair.Value) { // parse field key if (!this.TryParseStringTokens(fieldPair.Key, tokenContext, migrator, out string fieldError, out ITokenString fieldKey)) { return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} > field {fieldPair.Key} key is invalid: {fieldError}")); } // parse value if (!this.TryParseJsonTokens(fieldPair.Value, tokenContext, migrator, out string valueError, out TokenisableJToken value)) { return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} > field {fieldKey} is invalid: {valueError}")); } if (value?.Value is JValue jValue && jValue.Value <string>()?.Contains("/") == true) { return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} > field {fieldKey} is invalid: value can't contain field delimiter character '/'")); } fields.Add(new EditDataPatchField(key, fieldKey, value)); } } } // parse move entries List <EditDataPatchMoveRecord> moveEntries = new List <EditDataPatchMoveRecord>(); if (entry.MoveEntries != null) { foreach (PatchMoveEntryConfig moveEntry in entry.MoveEntries) { // validate string[] targets = new[] { moveEntry.BeforeID, moveEntry.AfterID, moveEntry.ToPosition }; if (string.IsNullOrWhiteSpace(moveEntry.ID)) { return(TrackSkip($"{nameof(PatchConfig.MoveEntries)} > move entry is invalid: must specify an {nameof(PatchMoveEntryConfig.ID)} value")); } if (targets.All(string.IsNullOrWhiteSpace)) { return(TrackSkip($"{nameof(PatchConfig.MoveEntries)} > entry '{moveEntry.ID}' is invalid: must specify one of {nameof(PatchMoveEntryConfig.ToPosition)}, {nameof(PatchMoveEntryConfig.BeforeID)}, or {nameof(PatchMoveEntryConfig.AfterID)}")); } if (targets.Count(p => !string.IsNullOrWhiteSpace(p)) > 1) { return(TrackSkip($"{nameof(PatchConfig.MoveEntries)} > entry '{moveEntry.ID}' is invalid: must specify only one of {nameof(PatchMoveEntryConfig.ToPosition)}, {nameof(PatchMoveEntryConfig.BeforeID)}, and {nameof(PatchMoveEntryConfig.AfterID)}")); } // parse IDs if (!this.TryParseStringTokens(moveEntry.ID, tokenContext, migrator, out string idError, out ITokenString moveId)) { return(TrackSkip($"{nameof(PatchConfig.MoveEntries)} > entry '{moveEntry.ID}' > {nameof(PatchMoveEntryConfig.ID)} is invalid: {idError}")); } if (!this.TryParseStringTokens(moveEntry.BeforeID, tokenContext, migrator, out string beforeIdError, out ITokenString beforeId)) { return(TrackSkip($"{nameof(PatchConfig.MoveEntries)} > entry '{moveEntry.ID}' > {nameof(PatchMoveEntryConfig.BeforeID)} is invalid: {beforeIdError}")); } if (!this.TryParseStringTokens(moveEntry.AfterID, tokenContext, migrator, out string afterIdError, out ITokenString afterId)) { return(TrackSkip($"{nameof(PatchConfig.MoveEntries)} > entry '{moveEntry.ID}' > {nameof(PatchMoveEntryConfig.AfterID)} is invalid: {afterIdError}")); } // parse position MoveEntryPosition toPosition = MoveEntryPosition.None; if (!string.IsNullOrWhiteSpace(moveEntry.ToPosition) && (!Enum.TryParse(moveEntry.ToPosition, true, out toPosition) || toPosition == MoveEntryPosition.None)) { return(TrackSkip($"{nameof(PatchConfig.MoveEntries)} > entry '{moveEntry.ID}' > {nameof(PatchMoveEntryConfig.ToPosition)} is invalid: must be one of {nameof(MoveEntryPosition.Bottom)} or {nameof(MoveEntryPosition.Top)}")); } // create move entry moveEntries.Add(new EditDataPatchMoveRecord(moveId, beforeId, afterId, toPosition)); } } // save patch = new EditDataPatch(entry.LogName, pack, assetName, conditions, entries, fields, moveEntries, this.Monitor, this.Helper.Content.NormaliseAssetName); } break; // edit image case PatchType.EditImage: { // read patch mode PatchMode patchMode = PatchMode.Replace; if (!string.IsNullOrWhiteSpace(entry.PatchMode) && !Enum.TryParse(entry.PatchMode, true, out patchMode)) { return(TrackSkip($"the {nameof(PatchConfig.PatchMode)} is invalid. Expected one of these values: [{string.Join(", ", Enum.GetNames(typeof(PatchMode)))}]")); } // save if (!this.TryPrepareLocalAsset(pack, entry.FromFile, tokenContext, migrator, out string error, out ITokenString fromAsset)) { return(TrackSkip(error)); } patch = new EditImagePatch(entry.LogName, pack, assetName, conditions, fromAsset, entry.FromArea, entry.ToArea, patchMode, this.Monitor, this.Helper.Content.NormaliseAssetName); } break; // edit map case PatchType.EditMap: { // read map asset if (!this.TryPrepareLocalAsset(pack, entry.FromFile, tokenContext, migrator, out string error, out ITokenString fromAsset)) { return(TrackSkip(error)); } // validate if (entry.ToArea == Rectangle.Empty) { return(TrackSkip($"must specify {nameof(entry.ToArea)} (use \"Action\": \"Load\" if you want to replace the whole map file)")); } // save patch = new EditMapPatch(entry.LogName, pack, assetName, conditions, fromAsset, entry.FromArea, entry.ToArea, this.Monitor, this.Helper.Content.NormaliseAssetName); } break; default: return(TrackSkip($"unsupported patch type '{action}'")); } // skip if not enabled // note: we process the patch even if it's disabled, so any errors are caught by the modder instead of only failing after the patch is enabled. if (!enabled) { return(TrackSkip($"{nameof(PatchConfig.Enabled)} is false", warn: false)); } // save patch this.PatchManager.Add(patch); return(true); } catch (Exception ex) { return(TrackSkip($"error reading info. Technical details:\n{ex}")); } }
public DataProvider(ManagedContentPack managed) { this.Managed = managed; }
public LegacyDataProvider(ManagedContentPack managed, bool paranoid = false) { this.Managed = managed; this.paranoid = paranoid; this.Monitor = managed.Monitor; }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="path">The path to the patch from the root content file.</param> /// <param name="assetName">The normalized asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="fromFile">The normalized asset key from which to load entries (if applicable), including tokens.</param> /// <param name="records">The data records to edit.</param> /// <param name="fields">The data fields to edit.</param> /// <param name="moveRecords">The records to reorder, if the target is a list asset.</param> /// <param name="updateRate">When the patch should be updated.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="parentPatch">The parent patch for which this patch was loaded, if any.</param> /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="normalizeAssetName">Normalize an asset name.</param> /// <param name="tryParseFields">Parse the data change fields for an <see cref="PatchType.EditData"/> patch.</param> public EditDataPatch(LogPathBuilder path, IManagedTokenString assetName, IEnumerable <Condition> conditions, IManagedTokenString fromFile, IEnumerable <EditDataPatchRecord> records, IEnumerable <EditDataPatchField> fields, IEnumerable <EditDataPatchMoveRecord> moveRecords, UpdateRate updateRate, ManagedContentPack contentPack, IPatch parentPatch, IMonitor monitor, Func <string, string> normalizeAssetName, TryParseFieldsDelegate tryParseFields) : base( path: path, type: PatchType.EditData, assetName: assetName, conditions: conditions, updateRate: updateRate, contentPack: contentPack, parentPatch: parentPatch, normalizeAssetName: normalizeAssetName, fromAsset: fromFile ) { // set fields this.Records = records?.ToArray(); this.Fields = fields?.ToArray(); this.MoveRecords = moveRecords?.ToArray(); this.Monitor = monitor; this.TryParseFields = tryParseFields; // track contextuals this.Contextuals .Add(this.Records) .Add(this.Fields) .Add(this.MoveRecords) .Add(this.Conditions); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="path">The path to the patch from the root content file.</param> /// <param name="assetName">The normalized asset name to intercept.</param> /// <param name="localAsset">The asset key to load from the content pack instead.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="updateRate">When the patch should be updated.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="parentPatch">The parent patch for which this patch was loaded, if any.</param> /// <param name="normalizeAssetName">Normalize an asset name.</param> public LoadPatch(LogPathBuilder path, IManagedTokenString assetName, IManagedTokenString localAsset, IEnumerable <Condition> conditions, UpdateRate updateRate, ManagedContentPack contentPack, IPatch parentPatch, Func <string, string> normalizeAssetName) : base( path: path, type: PatchType.Load, assetName: assetName, conditions: conditions, updateRate: updateRate, contentPack: contentPack, parentPatch: parentPatch, normalizeAssetName: normalizeAssetName, fromAsset: localAsset ) { }