public MobType(XElement xElement)
            : base(xElement)
        {
            try
            {
                Name  = GetAttributeAs <string>("Name", false, ConstrainAs.StringNonEmpty, null) ?? string.Empty;
                Entry = GetAttributeAsNullable <int>("Entry", false, ConstrainAs.MobId, null)
                        ?? GetAttributeAsNullable <int>("Id", false, ConstrainAs.MobId, null)
                        ?? 0;

                if (Entry == 0)
                {
                    QBCLog.Error(QBCLog.BuildMessageWithContext(Element,
                                                                "Attribute '{1}' is required, but was not provided.",
                                                                Environment.NewLine,
                                                                "Entry"));
                    IsAttributeProblem = true;
                }

                HandleAttributeProblem();
            }

            catch (Exception except)
            {
                if (Query.IsExceptionReportingNeeded(except))
                {
                    QBCLog.Exception(except, "PROFILE PROBLEM with \"{0}\"", xElement.ToString());
                }
                IsAttributeProblem = true;
            }
        }
        public T[] GetAttributeAsArray <T>(string attributeName, bool isAttributeRequired, IConstraintChecker <T> constraints, string[] attributeNameAliases,
                                           char[] separatorCharacters)
        {
            // Vector3 are triples, so requires special handling...
            if (typeof(T) == typeof(Vector3))
            {
                return((T[])UtilGetAttributeAsVector3s(attributeName, isAttributeRequired, attributeNameAliases));
            }


            constraints         = constraints ?? new ConstrainTo.Anything <T>();
            separatorCharacters = separatorCharacters ?? new[] { ' ', ',', ';' };

            bool   isError    = false;
            string keyName    = UtilLocateKey(isAttributeRequired, attributeName, attributeNameAliases);
            var    resultList = new List <T>();

            if ((keyName == null) || !Attributes.ContainsKey(keyName))
            {
                resultList.Clear();
                return(resultList.ToArray());
            }

            // We 'continue' even if problems are encountered...
            // By doing this, the profile writer can see all his mistakes at once, rather than being
            // nickel-and-dimed to death with error messages.
            foreach (string listEntry in Attributes[keyName].Split(separatorCharacters, StringSplitOptions.RemoveEmptyEntries))
            {
                T tmpResult;

                try
                { tmpResult = UtilTo <T>(keyName, listEntry); }

                catch (Exception)
                {
                    isError = true;
                    continue;
                }

                string constraintViolationMessage = constraints.Check(keyName, tmpResult);
                if (constraintViolationMessage != null)
                {
                    QBCLog.Error(QBCLog.BuildMessageWithContext(Element, constraintViolationMessage));
                    isError = true;
                    continue;
                }

                resultList.Add(tmpResult);
            }

            if (isError)
            {
                resultList.Clear();
                IsAttributeProblem = true;
            }

            return(resultList.ToArray());
        }
        public T[] GetNumberedAttributesAsArray <T>(string baseName, int countRequired, IConstraintChecker <T> constraints, IEnumerable <string> aliasBaseNames)
        {
            bool isError    = false;
            bool isVector3  = (typeof(T) == typeof(Vector3));
            var  resultList = new List <T>();

            // Search for primary names first --
            // We 'continue' even if problems are encountered.  By doing this, the profile writer can see
            // all his mistakes at once, rather than being nickel-and-dimed to death with error messages.
            var primaryAttributeNames = from attributeName in Attributes.Keys
                                        where UtilIsNumberedAttribute(baseName, attributeName, isVector3)
                                        select attributeName;

            foreach (var numberedAttributeName in primaryAttributeNames)
            {
                isError |= UtilAddToNumberedAttributeToArray <T>(numberedAttributeName, constraints, resultList);
            }

            // Search using alias names --
            // We 'continue' even if problems are encountered.  By doing this, the profile writer can see
            // all his mistakes at once, rather than being nickel-and-dimed to death with error messages.
            if (aliasBaseNames != null)
            {
                var aliasAttributeNames = from aliasBaseName in aliasBaseNames
                                          from attributeName in Attributes.Keys
                                          where UtilIsNumberedAttribute(aliasBaseName, attributeName, isVector3)
                                          select attributeName;

                foreach (var numberedAttributeName in aliasAttributeNames)
                {
                    isError |= UtilAddToNumberedAttributeToArray <T>(numberedAttributeName, constraints, resultList);
                }
            }


            if (resultList.Count < countRequired)
            {
                QBCLog.Error(QBCLog.BuildMessageWithContext(Element,
                                                            "The attribute '{1}N' must be provided at least {2} times (saw it '{3}' times).{0}"
                                                            + "(E.g., ButtonText1, ButtonText2, ButtonText3, ...){0}"
                                                            + "Please modify to supply {2} attributes with a base name of '{1}'.",
                                                            Environment.NewLine,
                                                            baseName,
                                                            countRequired,
                                                            resultList.Count));
                isError = true;
            }


            if (isError)
            {
                resultList.Clear();
                IsAttributeProblem = true;
            }

            return(resultList.ToArray());
        }
        private void UtilReportUnrecognizedAttributes()
        {
            var unrecognizedAttributes = (from attributeName in Attributes.Keys
                                          where !_recognizedAttributes.Contains(attributeName)
                                          orderby attributeName
                                          select attributeName);

            foreach (string attributeName in unrecognizedAttributes)
            {
                QBCLog.Warning(QBCLog.BuildMessageWithContext(Element,
                                                              "Attribute '{1}' is not a recognized attribute--ignoring it.",
                                                              Environment.NewLine,
                                                              attributeName));
            }
        }
        private object UtilGetAttributeAs <T>(string attributeName, bool isAttributeRequired, IConstraintChecker <T> constraints, string[] attributeNameAliases)
        {
            Type concreteType = typeof(T);

            // Vector3 are a triple of attributes, so requires special handling...
            if (concreteType == typeof(Vector3))
            {
                return(UtilGetXYZAttributesAsVector3(attributeName, isAttributeRequired, attributeNameAliases));
            }


            constraints = constraints ?? new ConstrainTo.Anything <T>();

            string keyName = UtilLocateKey(isAttributeRequired, attributeName, attributeNameAliases);

            if ((keyName == null) || !Attributes.ContainsKey(keyName))
            {
                return(null);
            }

            T      tmpResult;
            string valueAsString = Attributes[keyName];

            try
            { tmpResult = UtilTo <T>(keyName, valueAsString); }
            catch (Exception)
            {
                IsAttributeProblem = true;
                return(null);
            }

            string constraintViolationMessage = constraints.Check(keyName, tmpResult);

            if (constraintViolationMessage != null)
            {
                QBCLog.Error(QBCLog.BuildMessageWithContext(Element, constraintViolationMessage));
                IsAttributeProblem = true;
                return(null);
            }

            return(tmpResult);
        }
        private string UtilLocateKey(bool isAttributeRequired, string primaryName, string[] aliasNames)
        {
            // Register keys as recognized
            UtilRecognizeAttributeNames(primaryName, aliasNames);

            // Make sure the key was only specified once --
            // The 'dictionary' nature of Args assures that a key name will only be in the dictionary once.
            // However, if the key has been renamed, and an alias maintained for backward-compatibility,
            // then the user could specify the primary key name and one or more aliases as attributes.
            // If all the aliases provided the same value, then it is harmless, but we don't make the
            // distinction.  Instead, we encourage the user to use the preferred name of the key.  This
            // eliminates any possibility of the user specifying conflicting values for the 'same' attribute.
            if (UtilCountKeyNames(primaryName, aliasNames) > 1)
            {
                var keyNames = new List <string> {
                    primaryName
                };

                keyNames.AddRange(aliasNames);
                keyNames.Sort();

                QBCLog.Error(QBCLog.BuildMessageWithContext(Element,
                                                            "The attributes [{1}] are aliases for each other, and thus mutually exclusive.{0}"
                                                            + "Please specify the attribute by its preferred name '{2}'.",
                                                            Environment.NewLine,
                                                            ("'" + string.Join("', '", keyNames.ToArray()) + "'"),
                                                            primaryName));
                IsAttributeProblem = true;
                return(null);
            }


            // Prefer the primary name...
            if (!string.IsNullOrEmpty(primaryName) && Attributes.ContainsKey(primaryName))
            {
                return(primaryName);
            }

            if (aliasNames != null)
            {
                string keyName = (from aliasName in aliasNames
                                  where !string.IsNullOrEmpty(aliasName) && Attributes.ContainsKey(aliasName)
                                  select aliasName).FirstOrDefault();

                if (!string.IsNullOrEmpty(keyName))
                {
                    QBCLog.Warning(QBCLog.BuildMessageWithContext(Element,
                                                                  "Found attribute via its alias name '{1}'.{0}"
                                                                  + "Please update to use its primary name '{2}', instead.",
                                                                  Environment.NewLine,
                                                                  keyName,
                                                                  primaryName));
                    return(keyName);
                }
            }


            // Attribute is required, but cannot be located...
            if (isAttributeRequired)
            {
                QBCLog.Error(QBCLog.BuildMessageWithContext(Element,
                                                            "Attribute '{1}' is required, but was not provided.",
                                                            Environment.NewLine,
                                                            primaryName));
                IsAttributeProblem = true;
            }

            return(null);
        }
        private T UtilTo <T>(string attributeName, string attributeValueAsString)
        {
            Type concreteType = typeof(T);

            // Booleans require special handling...
            if (concreteType == typeof(bool))
            {
                int tmpInt;

                if (int.TryParse(attributeValueAsString, NumberStyles.Integer, CultureInfo.InvariantCulture, out tmpInt))
                {
                    attributeValueAsString = (tmpInt != 0) ? "true" : "false";

                    QBCLog.Warning(QBCLog.BuildMessageWithContext(Element,
                                                                  "Attribute's '{1}' value was provided as an integer (saw '{2}')--a boolean was expected.{0}"
                                                                  + "The integral value '{2}' was converted to Boolean({3}).{0}"
                                                                  + "Please update to provide '{3}' for this value.",
                                                                  Environment.NewLine,
                                                                  attributeName,
                                                                  tmpInt,
                                                                  attributeValueAsString));
                }

                // Fall through for normal boolean conversion
            }


            // Enums require special handling...
            else if (concreteType.IsEnum)
            {
                T tmpValue = default(T);

                try
                {
                    tmpValue = (T)Enum.Parse(concreteType, attributeValueAsString);

                    if (!Enum.IsDefined(concreteType, tmpValue))
                    {
                        throw new ArgumentException();
                    }

                    // If the provided value is a number instead of Enum name, ask the profile writer to fix it...
                    // This is not fatal, so we let it go without flagging IsAttributeProblem.
                    int tmpInt;
                    if (int.TryParse(attributeValueAsString, NumberStyles.Integer, CultureInfo.InvariantCulture, out tmpInt))
                    {
                        QBCLog.Warning(QBCLog.BuildMessageWithContext(Element,
                                                                      "The '{1}' attribute's value '{2}' has been implicitly converted"
                                                                      + " to the corresponding enumeration '{3}'.{0}"
                                                                      + "Please use the enumeration name '{3}' instead of a number.",
                                                                      Environment.NewLine,
                                                                      attributeName,
                                                                      tmpInt,
                                                                      tmpValue.ToString()));
                    }
                }
                catch (Exception)
                {
                    QBCLog.Error(QBCLog.BuildMessageWithContext(Element,
                                                                "The value '{1}' is not a member of the {2} enumeration."
                                                                + "  Allowed values: {3}",
                                                                Environment.NewLine,
                                                                attributeValueAsString,
                                                                concreteType.Name,
                                                                string.Join(", ", Enum.GetNames(concreteType))));
                    throw;
                }

                return(tmpValue);
            }



            try
            { return((T)Convert.ChangeType(attributeValueAsString, concreteType, CultureInfo.InvariantCulture)); }
            catch (Exception except)
            {
                QBCLog.Error(QBCLog.BuildMessageWithContext(Element,
                                                            "The '{1}' attribute's value (saw '{2}') is malformed. ({3})",
                                                            Environment.NewLine,
                                                            attributeName,
                                                            attributeValueAsString,
                                                            except.GetType().Name));
                throw;
            }
        }
        private object UtilGetAttributeAsVector3s(string attributeName, bool isAttributeRequired, string[] attributeNameAliases)
        {
            bool           isError   = false;
            string         keyName   = UtilLocateKey(isAttributeRequired, attributeName, attributeNameAliases);
            List <Vector3> pointList = new List <Vector3>();

            char[] separatorCoordinate = { ' ', ',' };
            char[] separatorTriplet    = { '|', ';' };


            if ((keyName == null) || !Attributes.ContainsKey(keyName))
            {
                pointList.Clear();
                return(pointList.ToArray());
            }


            foreach (string tripletAsString in Attributes[keyName].Split(separatorTriplet, StringSplitOptions.RemoveEmptyEntries))
            {
                string[] coordinatesAsString = tripletAsString.Split(separatorCoordinate, StringSplitOptions.RemoveEmptyEntries);

                if (coordinatesAsString.Length != 3)
                {
                    QBCLog.Error(QBCLog.BuildMessageWithContext(Element,
                                                                "The '{1}' attribute's value contribution (saw '{2}')"
                                                                + " doesn't have three coordinates (counted {3}).{0}"
                                                                + "Expect entries of the form \"x1,y1,z1 | x2,y2,z2 | x3,...\", or \"x1,y1,z1; x2,y2,z2; x3,...\"",
                                                                Environment.NewLine,
                                                                keyName,
                                                                tripletAsString,
                                                                coordinatesAsString.Length));
                    isError = true;
                    continue;
                }

                double?tmpValueX = null;
                try { tmpValueX = UtilTo <double>(keyName, coordinatesAsString[0]); }
                catch (Exception) { isError = true; }

                double?tmpValueY = null;
                try { tmpValueY = UtilTo <double>(keyName, coordinatesAsString[1]); }
                catch (Exception) { isError = true; }

                double?tmpValueZ = null;
                try { tmpValueZ = UtilTo <double>(keyName, coordinatesAsString[2]); }
                catch (Exception) { isError = true; }

                if (tmpValueX.HasValue && tmpValueY.HasValue && tmpValueZ.HasValue)
                {
                    pointList.Add(new Vector3((float)tmpValueX.Value, (float)tmpValueY.Value, (float)tmpValueZ.Value));
                }
            }

            if (isError)
            {
                pointList.Clear();
                IsAttributeProblem = true;
            }

            return(pointList.ToArray());
        }