#region ================== Namespaces using System; using System.IO; using System.Globalization; using SlimDX; using CodeImp.DoomBuilder.GZBuilder.Data; using CodeImp.DoomBuilder.Geometry; #endregion namespace CodeImp.DoomBuilder.GZBuilder.GZDoom { internal sealed class ModeldefStructure { private const int MAX_MODELS = 4; //maximum models per modeldef entry, zero-based private bool parsingfinished; internal ModelData ModelData; internal bool ParsingFinished { get { return parsingfinished; } } internal bool Parse(ModeldefParser parser, string classname) { #region ================== Vars string[] textureNames = new string[MAX_MODELS]; string[] modelNames = new string[MAX_MODELS]; string[] frameNames = new string[MAX_MODELS]; int[] frameIndices = new int[MAX_MODELS]; bool[] modelsUsed = new bool[MAX_MODELS]; string path = ""; Vector3 scale = new Vector3(1, 1, 1); Vector3 offset = new Vector3(); float angleOffset = 0; float pitchOffset = 0; float rollOffset = 0; bool inheritactorpitch = false; bool inheritactorroll = false; string token; #endregion //read modeldef structure contents parsingfinished = false; while(!parsingfinished && parser.SkipWhitespace(true)) { token = parser.ReadToken(); if(!string.IsNullOrEmpty(token)) { token = parser.StripTokenQuotes(token).ToLowerInvariant(); //ANYTHING can be quoted... switch(token) { #region ================== Path case "path": parser.SkipWhitespace(true); path = parser.StripTokenQuotes(parser.ReadToken(false)).Replace("/", "\\"); // Don't skip newline if(string.IsNullOrEmpty(path)) { parser.ReportError("Expected model path, but got '" + token + "'"); return false; } break; #endregion #region ================== Model case "model": parser.SkipWhitespace(true); //model index int index; token = parser.StripTokenQuotes(parser.ReadToken()); if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out index)) { // Not numeric! parser.ReportError("Expected model index, but got '" + token + "'"); return false; } if(index >= MAX_MODELS) { parser.ReportError("GZDoom doesn't allow more than " + MAX_MODELS + " models per MODELDEF entry"); return false; } parser.SkipWhitespace(true); //model path token = parser.StripTokenQuotes(parser.ReadToken(false)).ToLowerInvariant(); // Don't skip newline if(string.IsNullOrEmpty(token)) { parser.ReportError("Expected model name"); return false; } //check invalid path chars if(!parser.CheckInvalidPathChars(token)) return false; //check extension string modelext = Path.GetExtension(token); if(string.IsNullOrEmpty(modelext)) { parser.ReportError("Model '" + token + "' won't be loaded. Models without extension are not supported by GZDoom"); return false; } if(modelext != ".md3" && modelext != ".md2") { parser.ReportError("Model '" + token + "' won't be loaded. Only MD2 and MD3 models are supported"); return false; } //GZDoom allows models with identical modelIndex, it uses the last one encountered modelNames[index] = Path.Combine(path, token); break; #endregion #region ================== Skin case "skin": parser.SkipWhitespace(true); //skin index int skinIndex; token = parser.StripTokenQuotes(parser.ReadToken()); if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out skinIndex)) { // Not numeric! parser.ReportError("Expected skin index, but got '" + token + "'"); return false; } if(skinIndex >= MAX_MODELS) { parser.ReportError("GZDoom doesn't allow more than " + MAX_MODELS + " skins per MODELDEF entry"); return false; } parser.SkipWhitespace(true); //skin path token = parser.StripTokenQuotes(parser.ReadToken(false)).ToLowerInvariant(); // Don't skip newline if(string.IsNullOrEmpty(token)) { parser.ReportError("Skin path required"); return false; } //check invalid path chars if(!parser.CheckInvalidPathChars(token)) return false; //check extension string texext = Path.GetExtension(token); if(Array.IndexOf(ModelData.SUPPORTED_TEXTURE_EXTENSIONS, texext) == -1) { parser.ReportError("Image format '" + texext + "' is not supported"); return false; } //GZDoom allows skins with identical modelIndex, it uses the last one encountered textureNames[skinIndex] = Path.Combine(path, token); break; #endregion #region ================== Scale case "scale": parser.SkipWhitespace(true); token = parser.StripTokenQuotes(parser.ReadToken()); if(!parser.ReadSignedFloat(token, ref scale.Y)) { // Not numeric! parser.ReportError("Expected Scale X value, but got '" + token + "'"); return false; } parser.SkipWhitespace(true); token = parser.StripTokenQuotes(parser.ReadToken()); if(!parser.ReadSignedFloat(token, ref scale.X)) { // Not numeric! parser.ReportError("Expected Scale Y value, but got '" + token + "'"); return false; } parser.SkipWhitespace(true); token = parser.StripTokenQuotes(parser.ReadToken()); if(!parser.ReadSignedFloat(token, ref scale.Z)) { // Not numeric! parser.ReportError("Expected Scale Z value, but got '" + token + "'"); return false; } break; #endregion #region ================== Offset case "offset": parser.SkipWhitespace(true); token = parser.StripTokenQuotes(parser.ReadToken()); if(!parser.ReadSignedFloat(token, ref offset.X)) { // Not numeric! parser.ReportError("Expected Offset X value, but got '" + token + "'"); return false; } parser.SkipWhitespace(true); token = parser.StripTokenQuotes(parser.ReadToken()); if(!parser.ReadSignedFloat(token, ref offset.Y)) { // Not numeric! parser.ReportError("Expected Offset Y value, but got '" + token + "'"); return false; } parser.SkipWhitespace(true); token = parser.StripTokenQuotes(parser.ReadToken()); if(!parser.ReadSignedFloat(token, ref offset.Z)) { // Not numeric! parser.ReportError("Expected Offset Z value, but got '" + token + "'"); return false; } break; #endregion #region ================== ZOffset case "zoffset": parser.SkipWhitespace(true); token = parser.StripTokenQuotes(parser.ReadToken()); if(!parser.ReadSignedFloat(token, ref offset.Z)) { // Not numeric! parser.ReportError("Expected ZOffset value, but got '" + token + "'"); return false; } break; #endregion #region ================== AngleOffset case "angleoffset": parser.SkipWhitespace(true); token = parser.StripTokenQuotes(parser.ReadToken()); if(!parser.ReadSignedFloat(token, ref angleOffset)) { // Not numeric! parser.ReportError("Expected AngleOffset value, but got '" + token + "'"); return false; } break; #endregion #region ================== PitchOffset case "pitchoffset": parser.SkipWhitespace(true); token = parser.StripTokenQuotes(parser.ReadToken()); if(!parser.ReadSignedFloat(token, ref pitchOffset)) { // Not numeric! parser.ReportError("Expected PitchOffset value, but got '" + token + "'"); return false; } break; #endregion #region ================== RollOffset case "rolloffset": parser.SkipWhitespace(true); token = parser.StripTokenQuotes(parser.ReadToken()); if(!parser.ReadSignedFloat(token, ref rollOffset)) { // Not numeric! parser.ReportError("Expected RollOffset value, but got '" + token + "'"); return false; } break; #endregion #region ================== InheritActorPitch case "inheritactorpitch": inheritactorpitch = true; break; #endregion #region ================== InheritActorRoll case "inheritactorroll": inheritactorroll = true; break; #endregion #region ================== Frame / FrameIndex case "frameindex": case "frame": //parsed all required fields. if got more than one model - find which one(s) should be displayed if(modelNames.GetLength(0) > 1) { string spriteLump = null; string spriteFrame = null; //step back parser.DataStream.Seek(-token.Length - 1, SeekOrigin.Current); //here we check which models are used in first encountered lump and frame while(parser.SkipWhitespace(true)) { token = parser.StripTokenQuotes(parser.ReadToken()).ToLowerInvariant(); if(token == "frameindex" || token == "frame") { bool frameIndex = (token == "frameindex"); parser.SkipWhitespace(true); //should be sprite lump token = parser.StripTokenQuotes(parser.ReadToken()).ToLowerInvariant(); if(string.IsNullOrEmpty(spriteLump)) { spriteLump = token; } else if(spriteLump != token) //got another lump { for(int i = 0; i < modelsUsed.Length; i++) { if(!modelsUsed[i]) { modelNames[i] = null; textureNames[i] = null; } } break; } parser.SkipWhitespace(true); //should be sprite frame token = parser.StripTokenQuotes(parser.ReadToken()).ToLowerInvariant(); if(string.IsNullOrEmpty(spriteFrame)) { spriteFrame = token; } else if(spriteFrame != token) //got another frame { for(int i = 0; i < modelsUsed.Length; i++) { if(!modelsUsed[i]) { modelNames[i] = null; textureNames[i] = null; } } break; } parser.SkipWhitespace(true); //should be model index token = parser.StripTokenQuotes(parser.ReadToken()); int modelIndex; if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out modelIndex)) { // Not numeric! parser.ReportError("Expected model index, but got '" + token + "'"); return false; } if(modelIndex >= MAX_MODELS) { parser.ReportError("GZDoom doesn't allow more than " + MAX_MODELS + " models per MODELDEF entry"); return false; } if(modelNames[modelIndex] == null) { parser.ReportError("Model index doesn't correspond to any defined model"); return false; } modelsUsed[modelIndex] = true; parser.SkipWhitespace(true); // Should be frame name or index token = parser.StripTokenQuotes(parser.ReadToken()); if(frameIndex) { int frame = 0; if(!parser.ReadSignedInt(token, ref frame)) { // Not numeric! parser.ReportError("Expected model frame index, but got '" + token + "'"); return false; } // Skip the model if frame index is -1 if(frame == -1) modelsUsed[modelIndex] = false; else frameIndices[modelIndex] = frame; } else { if(string.IsNullOrEmpty(token)) { // Missing! parser.ReportError("Expected model frame name"); return false; } frameNames[modelIndex] = token.ToLowerInvariant(); } } else { //must be "}", step back parser.DataStream.Seek(-token.Length - 1, SeekOrigin.Current); break; } } } parsingfinished = true; break; #endregion } } } // Find closing brace, then quit while(parser.SkipWhitespace(true)) { token = parser.ReadToken(); if(string.IsNullOrEmpty(token) || token == "}") break; } // Bail out when got errors or no models are used if(Array.IndexOf(modelsUsed, true) == -1) { parser.ReportError("No models are used by '" + classname + "'"); return false; } // Classname is set in ModeldefParser ModelData = new ModelData(); ModelData.InheritActorPitch = inheritactorpitch; ModelData.InheritActorRoll = inheritactorroll; Matrix moffset = Matrix.Translation(offset.Y, -offset.X, offset.Z); // Things are complicated in GZDoom... Matrix mrotation = Matrix.RotationY(-Angle2D.DegToRad(rollOffset)) * Matrix.RotationX(-Angle2D.DegToRad(pitchOffset)) * Matrix.RotationZ(Angle2D.DegToRad(angleOffset)); ModelData.SetTransform(mrotation, moffset, scale); for(int i = 0; i < modelNames.Length; i++) { if(!string.IsNullOrEmpty(modelNames[i]) && modelsUsed[i]) { ModelData.TextureNames.Add(string.IsNullOrEmpty(textureNames[i]) ? string.Empty : textureNames[i].ToLowerInvariant()); ModelData.ModelNames.Add(modelNames[i].ToLowerInvariant()); ModelData.FrameNames.Add(frameNames[i]); ModelData.FrameIndices.Add(frameIndices[i]); } } if(ModelData.ModelNames.Count == 0) { parser.ReportError("'" + classname + "' has no models"); return false; } return true; } } }