示例#1
0
        public void GetApplicablePermutationsForTheseConditions()
        {
            // arrange
            ConditionFactory       factory = new ConditionFactory();
            HashSet <ConditionKey> keys    = new HashSet <ConditionKey> {
                ConditionKey.DayOfWeek, ConditionKey.Season
            };
            ConditionDictionary conditions = factory.BuildEmpty();

            // act
            IEnumerable <IDictionary <ConditionKey, string> > permutations = factory.GetApplicablePermutationsForTheseConditions(keys, conditions);

            // assert
            IEnumerable <string> actual   = permutations.Select(permutation => "(" + this.SortAndCommaDelimit(permutation.Select(p => $"{p.Key}:{p.Value}")) + ")");
            List <string>        expected = new List <string>();

            foreach (DayOfWeek dayOfWeek in this.DaysOfWeek)
            {
                foreach (string season in this.Seasons)
                {
                    expected.Add($"({ConditionKey.DayOfWeek}:{dayOfWeek}, {ConditionKey.Season}:{season})");
                }
            }

            this.SortAndCommaDelimit(actual).Should().Be(this.SortAndCommaDelimit(expected));
        }
示例#2
0
        /// <summary>
        /// 取得要引用的條件
        /// </summary>
        /// <param name="AParamName">條件參數名稱</param>
        /// <param name="AParamValue">條件參數值</param>
        /// <returns>條件語法內容</returns>
        public virtual string getCondition(string AParamName, string AParamValue, bool bReplaceKeyword)
        {
            if (AParamName != null && ConditionDictionary.ContainsKey(AParamName))
            {
                if ((AParamName == "MingTai") ||
                    (AParamName == "Aegon") ||
                    (AParamName == "PcaLife") ||
                    (AParamName == "EmployeeSerNoTree") ||
                    (AParamName == "EmployeeTree") ||
                    (AParamName == "DBGSernoNotIn") ||
                    (AParamName == "DBGSernoIn") ||
                    (AParamName == "RTATree") ||
                    (AParamName == "CAPTree") ||
                    (AParamName == "RstStatusIn") ||
                    (AParamName == "MutileTree") ||
                    (AParamName == "JobIDIn") ||
                    (AParamName == "CampaignSerNoIn") ||
                    (AParamName == "EmployeeBossTree") ||
                    (AParamName == "CAPTree") ||
                    (AParamName == "RTACategory2In") ||
                    (AParamName == "RosterAliasSerNoIn") ||
                    (AParamName == "StatusIn"))
                {
#if (Encrypt)
                    foreach (string field in _encryptFields)
                    {
                        if (ConditionDictionary[AParamName].ToString().IndexOf(field) > -1)
                        {
                            return(((string)ConditionDictionary[AParamName]).Replace("?", AParamValue == null ? "" : Encrypt(AParamValue)));
                        }
                    }
                    return(((string)ConditionDictionary[AParamName]).Replace("?", AParamValue == null ? "" : AParamValue));
#else
                    return(((string)ConditionDictionary[AParamName]).Replace("?", AParamValue == null ? "" : AParamValue));
#endif
                }
                else
                {
#if (Encrypt)
                    foreach (string field in _encryptFields)
                    {
                        if (ConditionDictionary[AParamName].ToString().IndexOf(field) > -1)
                        {
                            return(((string)ConditionDictionary[AParamName]).Replace("?", AParamValue == null ? "" : Encrypt(AParamValue)));
                        }
                    }
                    return(((string)ConditionDictionary[AParamName]).Replace("?", AParamValue == null ? "" : AParamValue));
#else
                    return(((string)ConditionDictionary[AParamName]).Replace("?", AParamValue == null ? "" : ((bReplaceKeyword) ? AParamValue.Replace("'", "''") : AParamValue)));
#endif
                }
            }
            else
            {
                return("");
            }
        }
示例#3
0
        public void CanConditionsOverlap_WithImpliedConditions()
        {
            // arrange
            ConditionFactory    factory = new ConditionFactory();
            ConditionDictionary left    = factory.BuildEmpty();
            ConditionDictionary right   = factory.BuildEmpty();

            // act
            bool canOverlap = factory.CanConditionsOverlap(left, right);

            // assert
            canOverlap.Should().BeTrue();
        }
示例#4
0
        public string GetPossibleStrings_WithMultipleTokens(string raw, params ConditionKey[] conditionKeys)
        {
            // arrange
            ConditionFactory    factory    = new ConditionFactory();
            TokenString         tokenStr   = new TokenString(raw, new HashSet <ConditionKey>(conditionKeys), TokenStringBuilder.TokenPattern);
            ConditionDictionary conditions = factory.BuildEmpty();

            // act
            IEnumerable <string> actual = factory.GetPossibleStrings(tokenStr, conditions);

            // assert
            return(this.SortAndCommaDelimit(actual));
        }
示例#5
0
        /****
        ** Condition parsing
        ****/
        /// <summary>Normalise and parse the given condition values.</summary>
        /// <param name="raw">The raw condition values to normalise.</param>
        /// <param name="conditions">The normalised conditions.</param>
        /// <param name="error">An error message indicating why normalisation failed.</param>
        public bool TryParseConditions(InvariantDictionary <string> raw, out ConditionDictionary conditions, out string error)
        {
            // no conditions
            if (raw == null || !raw.Any())
            {
                conditions = this.ConditionFactory.BuildEmpty();
                error      = null;
                return(true);
            }

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

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

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

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

            // return parsed conditions
            error = null;
            return(true);
        }
示例#6
0
 /// <summary>
 /// 取得要引用的條件
 /// </summary>
 /// <param name="AParamName">條件參數名稱</param>
 /// <param name="AParamValue">條件參數值</param>
 /// <returns>條件語法內容</returns>
 public virtual string getCondition(string AParamName, string AParamValue)
 {
     if (ConditionDictionary.ContainsKey(AParamName))
     {
         if (((string)ConditionDictionary[AParamName]).IndexOf("'?'") > 0)
         {
             ((string)ConditionDictionary[AParamName]).Replace("'?'", "N'?'");
         }
         return(((string)ConditionDictionary[AParamName]).Replace("?", AParamValue));
     }
     else
     {
         return("");
     }
 }
示例#7
0
        public void GetPossibleStrings_WithOneToken(ConditionKey condition)
        {
            // arrange
            ConditionFactory factory  = new ConditionFactory();
            TokenString      tokenStr = new TokenString("{{" + condition + "}}", new HashSet <ConditionKey> {
                condition
            }, TokenStringBuilder.TokenPattern);
            ConditionDictionary conditions = factory.BuildEmpty();

            // act
            IEnumerable <string> actual = factory.GetPossibleStrings(tokenStr, conditions);

            // assert
            this.SortAndCommaDelimit(actual).Should().Be(this.CommaDelimitedValues[condition]);
        }
示例#8
0
        /// <summary>Get whether two sets of conditions can potentially both match in some contexts.</summary>
        /// <param name="left">The first set of conditions to compare.</param>
        /// <param name="right">The second set of conditions to compare.</param>
        public bool CanConditionsOverlap(ConditionDictionary left, ConditionDictionary right)
        {
            IDictionary <ConditionKey, InvariantHashSet> leftValues  = this.GetPossibleTokenisableValues(left);
            IDictionary <ConditionKey, InvariantHashSet> rightValues = this.GetPossibleTokenisableValues(right);

            foreach (ConditionKey key in this.GetTokenisableConditions())
            {
                if (!leftValues[key].Intersect(rightValues[key], StringComparer.InvariantCultureIgnoreCase).Any())
                {
                    return(false);
                }
            }

            return(true);
        }
示例#9
0
        /// <summary>Get whether two sets of conditions can potentially both match in some contexts.</summary>
        /// <param name="left">The first set of conditions to compare.</param>
        /// <param name="right">The second set of conditions to compare.</param>
        public bool CanConditionsOverlap(ConditionDictionary left, ConditionDictionary right)
        {
            IDictionary <ConditionKey, InvariantHashSet> leftValues  = this.GetPossibleValues(left);
            IDictionary <ConditionKey, InvariantHashSet> rightValues = this.GetPossibleValues(right);

            foreach (ConditionKey key in this.GetValidConditions())
            {
                if (!leftValues[key].Intersect(rightValues[key]).Any())
                {
                    return(false);
                }
            }

            return(true);
        }
示例#10
0
        public void GetPossibleValues_WithOnlyImpliedConditions()
        {
            // arrange
            ConditionFactory    factory    = new ConditionFactory();
            ConditionDictionary conditions = factory.BuildEmpty();

            // act
            IDictionary <ConditionKey, InvariantHashSet> possibleValues = factory.GetPossibleTokenisableValues(conditions);

            // assert
            foreach (ConditionKey key in this.TokenisableConditions)
            {
                this.SortAndCommaDelimit(possibleValues[key]).Should().Be(this.CommaDelimitedValues[key], $"should match for {key}");
            }
        }
示例#11
0
        public void CanConditionsOverlap_WhenSeasonsOverlap()
        {
            // arrange
            ConditionFactory    factory = new ConditionFactory();
            ConditionDictionary left    = factory.BuildEmpty();
            ConditionDictionary right   = factory.BuildEmpty();

            left.Add(ConditionKey.Season, new[] { "Spring", "SUMMer" }); // should match case-insensitively
            right.Add(ConditionKey.Season, new[] { "Summer", "Fall" });

            // act
            bool canOverlap = factory.CanConditionsOverlap(left, right);

            // assert
            canOverlap.Should().BeTrue();
        }
示例#12
0
        public void CanConditionsOverlap_WhenDaysAndDaysOfWeekDistinct()
        {
            // arrange
            ConditionFactory    factory = new ConditionFactory();
            ConditionDictionary left    = factory.BuildEmpty();
            ConditionDictionary right   = factory.BuildEmpty();

            left.Add(ConditionKey.Day, new[] { "1", "2" });
            right.Add(ConditionKey.DayOfWeek, new[] { DayOfWeek.Wednesday.ToString(), DayOfWeek.Thursday.ToString() });

            // act
            bool canOverlap = factory.CanConditionsOverlap(left, right);

            // assert
            canOverlap.Should().BeFalse();
        }
示例#13
0
        /// <summary>Get all possible values of a tokenable string.</summary>
        /// <param name="tokenable">The tokenable string.</param>
        /// <param name="conditions">The conditions for which to filter permutations.</param>
        public IEnumerable <string> GetPossibleStrings(TokenString tokenable, ConditionDictionary conditions)
        {
            // no tokens: return original string
            if (!tokenable.ConditionTokens.Any())
            {
                yield return(tokenable.Raw);

                yield break;
            }

            // yield token permutations
            foreach (IDictionary <ConditionKey, string> permutation in this.GetApplicablePermutationsForTheseConditions(tokenable.ConditionTokens, conditions))
            {
                yield return(tokenable.GetStringWithTokens(permutation));
            }
        }
示例#14
0
        public void CanConditionsOverlap_WhenWeathersDistinct()
        {
            // arrange
            ConditionFactory    factory = new ConditionFactory();
            ConditionDictionary left    = factory.BuildEmpty();
            ConditionDictionary right   = factory.BuildEmpty();

            left.Add(ConditionKey.Weather, new[] { Weather.Rain.ToString(), Weather.Snow.ToString() });
            right.Add(ConditionKey.Weather, new[] { Weather.Storm.ToString(), Weather.Sun.ToString() });

            // act
            bool canOverlap = factory.CanConditionsOverlap(left, right);

            // assert
            canOverlap.Should().BeFalse();
        }
示例#15
0
        public void CanConditionsOverlap_WhenSeasonsDistinct()
        {
            // arrange
            ConditionFactory    factory = new ConditionFactory();
            ConditionDictionary left    = factory.BuildEmpty();
            ConditionDictionary right   = factory.BuildEmpty();

            left.Add(ConditionKey.Season, new[] { "Spring", "Summer" });
            right.Add(ConditionKey.Season, new[] { "Fall", "Winter" });

            // act
            bool canOverlap = factory.CanConditionsOverlap(left, right);

            // assert
            canOverlap.Should().BeFalse();
        }
示例#16
0
        /// <summary>Get all possible values of the given conditions, taking into account the interactions between them (e.g. day one isn't possible without mondays).</summary>
        /// <param name="conditions">The conditions to check.</param>
        public IDictionary <ConditionKey, InvariantHashSet> GetPossibleValues(ConditionDictionary conditions)
        {
            // get applicable values
            IDictionary <ConditionKey, InvariantHashSet> values = new Dictionary <ConditionKey, InvariantHashSet>();

            foreach (ConditionKey key in this.GetValidConditions())
            {
                values[key] = new InvariantHashSet(conditions.GetImpliedValues(key));
            }

            // filter days per days-of-week
            if (values[ConditionKey.DayOfWeek].Count < this.GetValidValues(ConditionKey.DayOfWeek).Count())
            {
                // get covered days
                HashSet <string> possibleDays = new HashSet <string>();
                foreach (string str in values[ConditionKey.DayOfWeek])
                {
                    DayOfWeek dayOfWeek = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), str);
                    foreach (int day in this.GetDaysFor(dayOfWeek))
                    {
                        possibleDays.Add(day.ToString());
                    }
                }

                // remove non-covered days
                values[ConditionKey.Day].RemoveWhere(p => !possibleDays.Contains(p));
            }

            // filter days-of-week per days
            if (values[ConditionKey.Day].Count < this.GetValidValues(ConditionKey.Day).Count())
            {
                // get covered days of week
                HashSet <string> possibleDaysOfWeek = new HashSet <string>();
                foreach (string str in values[ConditionKey.Day])
                {
                    int day = int.Parse(str);
                    possibleDaysOfWeek.Add(this.GetDayOfWeekFor(day).ToString());
                }

                // remove non-covered days of week
                values[ConditionKey.DayOfWeek].RemoveWhere(p => !possibleDaysOfWeek.Contains(p));
            }

            return(values);
        }
示例#17
0
        public void GetPossibleValues_WithImpliedConditionsAndRestrictedDaysOfWeek()
        {
            // arrange
            ConditionFactory    factory    = new ConditionFactory();
            ConditionDictionary conditions = factory.BuildEmpty();

            conditions.Add(ConditionKey.DayOfWeek, new[] { DayOfWeek.Tuesday.ToString(), DayOfWeek.Wednesday.ToString() });

            // act
            IDictionary <ConditionKey, InvariantHashSet> possibleValues = factory.GetPossibleTokenisableValues(conditions);

            // assert
            this.SortAndCommaDelimit(possibleValues[ConditionKey.Day]).Should().Be("10, 16, 17, 2, 23, 24, 3, 9", $"should match for {ConditionKey.Day}");
            this.SortAndCommaDelimit(possibleValues[ConditionKey.DayOfWeek]).Should().Be($"{DayOfWeek.Tuesday}, {DayOfWeek.Wednesday}", $"should match for {ConditionKey.DayOfWeek}");
            this.SortAndCommaDelimit(possibleValues[ConditionKey.Language]).Should().Be(this.CommaDelimitedValues[ConditionKey.Language], $"should match for {ConditionKey.Language}");
            this.SortAndCommaDelimit(possibleValues[ConditionKey.Season]).Should().Be(this.CommaDelimitedValues[ConditionKey.Season], $"should match for {ConditionKey.Season}");
            this.SortAndCommaDelimit(possibleValues[ConditionKey.Weather]).Should().Be(this.CommaDelimitedValues[ConditionKey.Weather], $"should match for {ConditionKey.Weather}");
        }
示例#18
0
        public void GetPossibleValues_WithSubsetForEachCondition()
        {
            // arrange
            ConditionFactory    factory    = new ConditionFactory();
            ConditionDictionary conditions = factory.BuildEmpty();

            conditions.Add(ConditionKey.Day, new[] { "1" /*Monday*/, "2" /*Tuesday*/, "17" /*Wednesday*/, "26" /*Friday*/, "28" /*Sunday*/ });
            conditions.Add(ConditionKey.DayOfWeek, new[] { DayOfWeek.Monday.ToString(), DayOfWeek.Thursday.ToString(), DayOfWeek.Saturday.ToString(), DayOfWeek.Sunday.ToString() });
            conditions.Add(ConditionKey.Language, new[] { LocalizedContentManager.LanguageCode.en.ToString(), LocalizedContentManager.LanguageCode.pt.ToString() });
            conditions.Add(ConditionKey.Season, new[] { "Spring", "Fall" });
            conditions.Add(ConditionKey.Weather, new InvariantHashSet {
                Weather.Rain.ToString(), Weather.Sun.ToString()
            });

            // act
            IDictionary <ConditionKey, InvariantHashSet> possibleValues = factory.GetPossibleTokenisableValues(conditions);

            // assert
            this.SortAndCommaDelimit(possibleValues[ConditionKey.Day]).Should().Be("1, 28", $"should match for {ConditionKey.Day}");
            this.SortAndCommaDelimit(possibleValues[ConditionKey.DayOfWeek]).Should().Be($"{DayOfWeek.Monday}, {DayOfWeek.Sunday}", $"should match for {ConditionKey.DayOfWeek}");
            this.SortAndCommaDelimit(possibleValues[ConditionKey.Language]).Should().Be($"{LocalizedContentManager.LanguageCode.en}, {LocalizedContentManager.LanguageCode.pt}", $"should match for {ConditionKey.Language}");
            this.SortAndCommaDelimit(possibleValues[ConditionKey.Season]).Should().Be("Fall, Spring", $"should match for {ConditionKey.Season}");
            this.SortAndCommaDelimit(possibleValues[ConditionKey.Weather]).Should().Be($"{Weather.Rain}, {Weather.Sun}", $"should match for {ConditionKey.Weather}");
        }
示例#19
0
 /*********
 ** Public methods
 *********/
 /// <summary>Construct an instance.</summary>
 /// <param name="logName">A unique name for this patch shown in log messages.</param>
 /// <param name="assetLoader">Handles loading assets from content packs.</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, AssetLoader assetLoader, IContentPack contentPack, TokenString assetName, ConditionDictionary conditions, TokenString fromLocalAsset, Rectangle fromArea, Rectangle toArea, PatchMode patchMode, IMonitor monitor, Func <string, string> normaliseAssetName)
     : base(logName, PatchType.EditImage, assetLoader, 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;
 }
示例#20
0
        /// <summary>Get the filtered permutations of conditions for the given keys.</summary>
        /// <param name="keys">The condition keys to include.</param>
        /// <param name="conditions">The conditions for which to filter permutations.</param>
        public IEnumerable <IDictionary <ConditionKey, string> > GetApplicablePermutationsForTheseConditions(ISet <ConditionKey> keys, ConditionDictionary conditions)
        {
            // get possible values for given conditions
            IDictionary <ConditionKey, InvariantHashSet> possibleValues = this.GetPossibleTokenisableValues(conditions);

            // restrict permutations to relevant keys
            foreach (ConditionKey key in possibleValues.Keys.ToArray())
            {
                if (!keys.Contains(key))
                {
                    possibleValues.Remove(key);
                }
            }

            // get permutations
            var rawValues = new InvariantDictionary <InvariantHashSet>(possibleValues.ToDictionary(p => p.Key.ToString(), p => p.Value));

            foreach (InvariantDictionary <string> permutation in this.GetPermutations(rawValues))
            {
                yield return(permutation.ToDictionary(p => ConditionKey.Parse(p.Key), p => p.Value));
            }
        }
示例#21
0
        /****
        ** Condition parsing
        ****/
        /// <summary>Normalise and parse the given condition values.</summary>
        /// <param name="raw">The raw condition values to normalise.</param>
        /// <param name="formatVersion">The format version specified by the content pack.</param>
        /// <param name="latestFormatVersion">The latest format version.</param>
        /// <param name="conditions">The normalised conditions.</param>
        /// <param name="error">An error message indicating why normalisation failed.</param>
        public bool TryParseConditions(InvariantDictionary <string> raw, ISemanticVersion formatVersion, ISemanticVersion latestFormatVersion, out ConditionDictionary conditions, out string error)
        {
            // no conditions
            if (raw == null || !raw.Any())
            {
                conditions = this.ConditionFactory.BuildEmpty();
                error      = null;
                return(true);
            }

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

                // check compatibility
                if (formatVersion.IsOlderThan("1.4"))
                {
                    if (key == ConditionKey.DayEvent || key == ConditionKey.HasFlag || key == ConditionKey.HasSeenEvent || key == ConditionKey.Spouse)
                    {
                        error      = $"{key} isn't available with format version {formatVersion} (change the {nameof(ContentConfig.Format)} field to {latestFormatVersion} to use newer features)";
                        conditions = null;
                        return(false);
                    }
                }

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

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

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

            // return parsed conditions
            error = null;
            return(true);
        }
示例#22
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;
 }
示例#23
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;
 }
示例#24
0
        /// <summary>Prepare a local asset file for a patch to use.</summary>
        /// <param name="pack">The content pack being loaded.</param>
        /// <param name="path">The asset path in the content patch.</param>
        /// <param name="config">The config values to apply.</param>
        /// <param name="conditions">The conditions to apply.</param>
        /// <param name="error">The error reason if preparing the asset fails.</param>
        /// <param name="tokenedPath">The parsed value.</param>
        /// <param name="shouldPreload">Whether to preload assets if needed.</param>
        /// <returns>Returns whether the local asset was successfully prepared.</returns>
        private bool TryPrepareLocalAsset(ManagedContentPack pack, string path, InvariantDictionary <ConfigField> config, ConditionDictionary conditions, out string error, out TokenString tokenedPath, bool shouldPreload)
        {
            // normalise raw value
            path = this.NormaliseLocalAssetPath(pack, path);
            if (path == null)
            {
                error       = $"must set the {nameof(PatchConfig.FromFile)} field for this action type.";
                tokenedPath = null;
                return(false);
            }

            // tokenise
            if (!this.TryParseTokenString(path, config, out string tokenError, out TokenStringBuilder builder))
            {
                error       = $"the {nameof(PatchConfig.FromFile)} is invalid: {tokenError}";
                tokenedPath = null;
                return(false);
            }
            tokenedPath = builder.Build();

            // preload & validate possible file paths
            InvariantHashSet missingFiles = new InvariantHashSet();

            foreach (string localKey in this.ConditionFactory.GetPossibleStrings(tokenedPath, conditions))
            {
                if (!pack.FileExists(localKey))
                {
                    missingFiles.Add(localKey);
                }
                else if (shouldPreload)
                {
                    pack.PreloadIfNeeded(localKey);
                }
            }
            if (missingFiles.Any())
            {
                error = tokenedPath.ConditionTokens.Any() || missingFiles.Count > 1
                    ? $"{nameof(PatchConfig.FromFile)} '{path}' matches files which don't exist ({string.Join(", ", missingFiles.OrderBy(p => p))})."
                    : $"{nameof(PatchConfig.FromFile)} '{path}' matches a file which doesn't exist.";
                tokenedPath = null;
                return(false);
            }

            // looks OK
            error = null;
            return(true);
        }
示例#25
0
        /// <summary>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="checkOnly">Prepare the asset info and check if it's valid, but don't actually preload the asset.</param>
        /// <returns>Returns whether the local asset was successfully prepared.</returns>
        private bool TryPrepareLocalAsset(IContentPack pack, string path, InvariantDictionary <ConfigField> config, ConditionDictionary conditions, out string error, out TokenString tokenedPath, bool checkOnly)
        {
            // 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();

            // validate all possible files exist
            // + preload PNG assets to avoid load-in-draw-loop error
            InvariantHashSet missingFiles = new InvariantHashSet();

            foreach (string localKey in this.ConditionFactory.GetPossibleStrings(tokenedPath, conditions))
            {
                // check-only mode
                if (checkOnly)
                {
                    if (!this.AssetLoader.FileExists(pack, localKey))
                    {
                        missingFiles.Add(localKey);
                    }
                    continue;
                }

                // else preload
                try
                {
                    if (this.AssetLoader.PreloadIfNeeded(pack, localKey))
                    {
                        this.VerboseLog($"      preloaded {localKey}.");
                    }
                }
                catch (FileNotFoundException)
                {
                    missingFiles.Add(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);
        }
示例#26
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;
 }
示例#27
0
        /// <summary>Normalise and parse the given condition values.</summary>
        /// <param name="raw">The raw condition values to normalise.</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="conditions">The normalised conditions.</param>
        /// <param name="error">An error message indicating why normalisation failed.</param>
        private bool TryParseConditions(InvariantDictionary <string> raw, IContext tokenContext, IMigration migrator, out ConditionDictionary conditions, out string error)
        {
            conditions = new ConditionDictionary();

            // no conditions
            if (raw == null || !raw.Any())
            {
                error = null;
                return(true);
            }

            // parse conditions
            Lexer lexer = new Lexer();

            foreach (KeyValuePair <string, string> pair in raw)
            {
                // parse condition key
                ILexToken[] lexTokens = lexer.ParseBits(pair.Key, impliedBraces: true).ToArray();
                if (lexTokens.Length != 1 || !(lexTokens[0] is LexTokenToken lexToken) || lexToken.PipedTokens.Any())
                {
                    error      = $"'{pair.Key}' isn't a valid token name";
                    conditions = null;
                    return(false);
                }
                TokenName name = new TokenName(lexToken.Name, lexToken.InputArg?.Text);

                // apply migrations
                if (!migrator.TryMigrate(ref name, out error))
                {
                    conditions = null;
                    return(false);
                }

                // get token
                IToken token = tokenContext.GetToken(name, enforceContext: false);
                if (token == null)
                {
                    error      = $"'{pair.Key}' isn't a valid condition; must be one of {string.Join(", ", tokenContext.GetTokens(enforceContext: false).Select(p => p.Name).OrderBy(p => p))}";
                    conditions = null;
                    return(false);
                }

                // validate subkeys
                if (!token.CanHaveSubkeys)
                {
                    if (name.HasSubkey())
                    {
                        error      = $"{name.Key} conditions don't allow subkeys (:)";
                        conditions = null;
                        return(false);
                    }
                }
                else if (token.RequiresSubkeys)
                {
                    if (!name.HasSubkey())
                    {
                        error      = $"{name.Key} conditions must specify a token subkey (see readme for usage)";
                        conditions = null;
                        return(false);
                    }
                }

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

                // validate token keys & values
                if (!token.TryValidate(name, values, out string customError))
                {
                    error      = $"invalid {name} condition: {customError}";
                    conditions = null;
                    return(false);
                }

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

            // return parsed conditions
            error = null;
            return(true);
        }
示例#28
0
        /// <summary>Normalise and parse the given condition values.</summary>
        /// <param name="raw">The raw condition values to normalise.</param>
        /// <param name="tokenContext">The tokens available for this content pack.</param>
        /// <param name="formatVersion">The format version specified by the content pack.</param>
        /// <param name="latestFormatVersion">The latest format version.</param>
        /// <param name="minumumTokenVersions">The minimum format versions for newer condition types.</param>
        /// <param name="conditions">The normalised conditions.</param>
        /// <param name="error">An error message indicating why normalisation failed.</param>
        private bool TryParseConditions(InvariantDictionary <string> raw, IContext tokenContext, ISemanticVersion formatVersion, ISemanticVersion latestFormatVersion, InvariantDictionary <ISemanticVersion> minumumTokenVersions, out ConditionDictionary conditions, out string error)
        {
            conditions = new ConditionDictionary();

            // no conditions
            if (raw == null || !raw.Any())
            {
                error = null;
                return(true);
            }

            // parse conditions
            foreach (KeyValuePair <string, string> pair in raw)
            {
                // parse condition key
                if (!TokenName.TryParse(pair.Key, out TokenName name))
                {
                    error      = $"'{pair.Key}' isn't a valid token name";
                    conditions = null;
                    return(false);
                }

                // get token
                IToken token = tokenContext.GetToken(name, enforceContext: false);
                if (token == null)
                {
                    error      = $"'{pair.Key}' isn't a valid condition; must be one of {string.Join(", ", tokenContext.GetTokens(enforceContext: false).Select(p => p.Name).OrderBy(p => p))}";
                    conditions = null;
                    return(false);
                }

                // validate subkeys
                if (!token.CanHaveSubkeys)
                {
                    if (name.HasSubkey())
                    {
                        error      = $"{name.Key} conditions don't allow subkeys (:)";
                        conditions = null;
                        return(false);
                    }
                }
                else if (token.RequiresSubkeys)
                {
                    if (!name.HasSubkey())
                    {
                        error      = $"{name.Key} conditions must specify a token subkey (see readme for usage)";
                        conditions = null;
                        return(false);
                    }
                }

                // check compatibility
                if (minumumTokenVersions.TryGetValue(name.Key, out ISemanticVersion minVersion) && minVersion.IsNewerThan(formatVersion))
                {
                    error      = $"{name} isn't available with format version {formatVersion} (change the {nameof(ContentConfig.Format)} field to {latestFormatVersion} to use newer features)";
                    conditions = null;
                    return(false);
                }

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

                // restrict to allowed values
                InvariantHashSet rawValidValues = token.GetAllowedValues(name);
                if (rawValidValues?.Any() == true)
                {
                    InvariantHashSet validValues = new InvariantHashSet(rawValidValues);
                    {
                        string[] invalidValues = values.Except(validValues, StringComparer.InvariantCultureIgnoreCase).ToArray();
                        if (invalidValues.Any())
                        {
                            error      = $"invalid {name} values ({string.Join(", ", invalidValues)}); expected one of {string.Join(", ", validValues)}";
                            conditions = null;
                            return(false);
                        }
                    }
                }

                // perform custom validation
                if (!token.TryCustomValidation(values, out string customError))
                {
                    error      = $"invalid {name} values: {customError}";
                    conditions = null;
                    return(false);
                }

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

            // return parsed conditions
            error = null;
            return(true);
        }
示例#29
0
        /****
        ** Condition parsing
        ****/
        /// <summary>Normalise and parse the given condition values.</summary>
        /// <param name="raw">The raw condition values to normalise.</param>
        /// <param name="formatVersion">The format version specified by the content pack.</param>
        /// <param name="latestFormatVersion">The latest format version.</param>
        /// <param name="conditions">The normalised conditions.</param>
        /// <param name="error">An error message indicating why normalisation failed.</param>
        public bool TryParseConditions(InvariantDictionary <string> raw, ISemanticVersion formatVersion, ISemanticVersion latestFormatVersion, out ConditionDictionary conditions, out string error)
        {
            // no conditions
            if (raw == null || !raw.Any())
            {
                conditions = this.ConditionFactory.BuildEmpty();
                error      = null;
                return(true);
            }

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

                // validate types which require an ID
                if (this.TypesRequireID.Contains(key.Type) && string.IsNullOrWhiteSpace(key.ForID))
                {
                    error      = $"{key.Type} conditions must specify a separate ID (see readme for usage)";
                    conditions = null;
                    return(false);
                }

                // check compatibility
                foreach (var versionPair in this.MinimumVersions)
                {
                    if (formatVersion.IsOlderThan(versionPair.Key) && versionPair.Value.Contains(key.Type))
                    {
                        error      = $"{key} isn't available with format version {formatVersion} (change the {nameof(ContentConfig.Format)} field to {latestFormatVersion} to use newer features)";
                        conditions = null;
                        return(false);
                    }
                }

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

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

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

            // return parsed conditions
            error = null;
            return(true);
        }
示例#30
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();
 }