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

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

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

            // make names unique
            int i = 0;

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

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

                patch.LogName = $"{contentPack.Manifest.Name} > {patch.LogName}";
            }
        }
        /// <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);
        }
예제 #6
0
 /*********
 ** 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;
 }
예제 #7
0
 /*********
 ** 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;
 }
예제 #8
0
        /*********
        ** 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);
        }
예제 #9
0
 /*********
 ** 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();
 }
예제 #10
0
 /*********
 ** 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;
 }
예제 #11
0
 /*********
 ** 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;
 }
예제 #12
0
        /*********
        ** 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);
        }
예제 #14
0
        /*********
        ** 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);
        }
예제 #15
0
        /*********
        ** 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);
        }
예제 #16
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);
        }
예제 #17
0
        /*********
        ** 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);
        }
예제 #18
0
        /// <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);
        }
예제 #19
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="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);
        }
예제 #20
0
 /*********
 ** 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)
 {
 }
예제 #21
0
        /*********
        ** 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);
        }
예제 #22
0
        /// <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}"));
            }
        }
예제 #23
0
        /// <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}"));
            }
        }
예제 #24
0
 /*********
 ** 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;
 }
예제 #25
0
        /*********
        ** 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);
        }
예제 #26
0
        /// <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}"));
            }
        }
예제 #27
0
 public DataProvider(ManagedContentPack managed)
 {
     this.Managed = managed;
 }
예제 #28
0
 public LegacyDataProvider(ManagedContentPack managed, bool paranoid = false)
 {
     this.Managed  = managed;
     this.paranoid = paranoid;
     this.Monitor  = managed.Monitor;
 }
예제 #29
0
        /*********
        ** 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);
        }
예제 #30
0
 /*********
 ** 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
         )
 {
 }