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