UltimateZoneBuilder/Source/Core/ZDoom/ModeldefStructure.cs
2023-06-29 20:37:47 +02:00

615 lines
18 KiB
C#
Executable file

#region ================== Namespaces
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Globalization;
using CodeImp.DoomBuilder.GZBuilder.Data;
using CodeImp.DoomBuilder.Rendering;
#endregion
namespace CodeImp.DoomBuilder.ZDoom
{
internal sealed class ModeldefStructure
{
#region ================== Structs
internal struct FrameStructure
{
public string SpriteName; // Stays here for HashSet duplicate checks
public int ModelIndex;
public int FrameIndex;
public string FrameName;
}
#endregion
#region ================== Variables
private Dictionary<int, string> skinnames;
private Dictionary<int, Dictionary<int, string>> surfaceskinenames;
private Dictionary<int, string> modelnames;
private string path;
private Vector3f scale;
private Vector3f offset;
private Vector3f rotationcenter;
private float angleoffset;
private float pitchoffset;
private float rolloffset;
private bool inheritactorpitch;
private bool useactorpitch;
private bool useactorroll;
private bool userotationcenter;
private Dictionary<string, HashSet<FrameStructure>> frames;
#endregion
#region ================== Properties
public Dictionary<int, string> SkinNames { get { return skinnames; } }
public Dictionary<int, Dictionary<int, string>> SurfaceSkinNames { get { return surfaceskinenames; } }
public Dictionary<int, string> ModelNames { get { return modelnames; } }
public Vector3f Scale { get { return scale; } }
public Vector3f Offset { get { return offset; } }
public Vector3f RotationCenter { get { return rotationcenter; } }
public float AngleOffset { get { return angleoffset; } }
public float PitchOffset { get { return pitchoffset; } }
public float RollOffset { get { return rolloffset; } }
public bool InheritActorPitch { get { return inheritactorpitch; } }
public bool UseActorPitch { get { return useactorpitch; } }
public bool UseActorRoll { get { return useactorroll; } }
public bool UseRotationCenter { get { return userotationcenter; } }
public string DataPath { get { return path; } } // biwa
public Dictionary<string, HashSet<FrameStructure>> Frames { get { return frames; } }
#endregion
#region ================== Constructor
internal ModeldefStructure()
{
path = string.Empty;
skinnames = new Dictionary<int, string>();
modelnames = new Dictionary<int, string>();
frames = new Dictionary<string, HashSet<FrameStructure>>(StringComparer.OrdinalIgnoreCase);
scale = new Vector3f(1.0f, 1.0f, 1.0f);
surfaceskinenames = new Dictionary<int, Dictionary<int, string>>();
}
#endregion
#region ================== Parsing
internal bool Parse(ModeldefParser parser)
{
// Read modeldef structure contents
bool parsingfinished = false;
while(!parsingfinished && parser.SkipWhitespace(true))
{
string token = parser.ReadToken().ToLowerInvariant();
if(string.IsNullOrEmpty(token)) continue;
switch(token)
{
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");
return false;
}
break;
case "model":
parser.SkipWhitespace(true);
// Model index
int index = 0;
token = parser.ReadToken();
if(!parser.ReadSignedInt(token, ref index))
{
// Not numeric!
parser.ReportError("Expected model index, but got \"" + token + "\"");
return false;
}
if(index < 0)
{
// Out of bounds
parser.ReportError("Model index must not be in negative");
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" && modelext != ".3d" && modelext != ".obj" && modelext != ".iqm")
{
parser.ReportError("Model \"" + token + "\" won't be loaded. Only Unreal 3D, MD2, MD3, OBJ and IQM models are supported");
return false;
}
// GZDoom allows models with identical index, it uses the last one encountered
modelnames[index] = Path.Combine(path, token).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
break;
case "skin":
parser.SkipWhitespace(true);
// Skin index
int skinindex = 0;
token = parser.ReadToken();
if(!parser.ReadSignedInt(token, ref skinindex))
{
// Not numeric!
parser.ReportError("Expected skin index, but got \"" + token + "\"");
return false;
}
if(skinindex < 0)
{
// Out of bounds
parser.ReportError("Skin index must not be negative");
return false;
}
parser.SkipWhitespace(true);
// Skin path
token = parser.StripTokenQuotes(parser.ReadToken(false)).ToLowerInvariant(); // Don't skip newline
if(string.IsNullOrEmpty(token))
{
parser.ReportError("Expected skin path");
return false;
}
// Check invalid path chars
if(!parser.CheckInvalidPathChars(token)) return false;
// GZDoom allows skins with identical index, it uses the last one encountered
skinnames[skinindex] = Path.Combine(path, token);
break;
// SurfaceSkin <int modelindex> <int surfaceindex> <string skinfile>
case "surfaceskin":
parser.SkipWhitespace(true);
// Model index
int modelindex = 0;
token = parser.ReadToken();
if(!parser.ReadSignedInt(token, ref modelindex))
{
// Not numeric!
parser.ReportError("Expected model index, but got \"" + token + "\"");
return false;
}
if(modelindex < 0)
{
// Out of bounds
parser.ReportError("Model index must not be negative");
return false;
}
parser.SkipWhitespace(true);
// Surfaceindex index
int surfaceindex = 0;
token = parser.ReadToken();
if(!parser.ReadSignedInt(token, ref surfaceindex))
{
// Not numeric!
parser.ReportError("Expected surface index, but got \"" + token + "\"");
return false;
}
if(surfaceindex < 0)
{
// Out of bounds
parser.ReportError("Surface index must be positive integer");
return false;
}
parser.SkipWhitespace(true);
// Skin path
token = parser.StripTokenQuotes(parser.ReadToken(false)).ToLowerInvariant(); // Don't skip newline
if(string.IsNullOrEmpty(token))
{
parser.ReportError("Expected skin path");
return false;
}
// Check invalid path chars
if(!parser.CheckInvalidPathChars(token)) return false;
// Store
if (!surfaceskinenames.ContainsKey(modelindex))
surfaceskinenames[modelindex] = new Dictionary<int, string>();
surfaceskinenames[modelindex][surfaceindex] = Path.Combine(path, token).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
break;
case "scale":
parser.SkipWhitespace(true);
token = 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.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.ReadToken();
if(!parser.ReadSignedFloat(token, ref scale.Z))
{
// Not numeric!
parser.ReportError("Expected Scale Z value, but got \"" + token + "\"");
return false;
}
break;
case "offset":
parser.SkipWhitespace(true);
token = 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.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.ReadToken();
if(!parser.ReadSignedFloat(token, ref offset.Z))
{
// Not numeric!
parser.ReportError("Expected Offset Z value, but got \"" + token + "\"");
return false;
}
break;
case "zoffset":
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(!parser.ReadSignedFloat(token, ref offset.Z))
{
// Not numeric!
parser.ReportError("Expected ZOffset value, but got \"" + token + "\"");
return false;
}
break;
case "angleoffset":
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(!parser.ReadSignedFloat(token, ref angleoffset))
{
// Not numeric!
parser.ReportError("Expected AngleOffset value, but got \"" + token + "\"");
return false;
}
break;
case "pitchoffset":
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(!parser.ReadSignedFloat(token, ref pitchoffset))
{
// Not numeric!
parser.ReportError("Expected PitchOffset value, but got \"" + token + "\"");
return false;
}
break;
case "rolloffset":
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(!parser.ReadSignedFloat(token, ref rolloffset))
{
// Not numeric!
parser.ReportError("Expected RollOffset value, but got \"" + token + "\"");
return false;
}
break;
case "rotation-center":
parser.SkipWhitespace(true);
token = parser.ReadToken();
if (!parser.ReadSignedFloat(token, ref rotationcenter.X))
{
// Not numeric!
parser.ReportError("Expected rotation center X value, but got \"" + token + "\"");
return false;
}
parser.SkipWhitespace(true);
token = parser.ReadToken();
if (!parser.ReadSignedFloat(token, ref rotationcenter.Y))
{
// Not numeric!
parser.ReportError("Expected rotation center Y value, but got \"" + token + "\"");
return false;
}
parser.SkipWhitespace(true);
token = parser.ReadToken();
if (!parser.ReadSignedFloat(token, ref rotationcenter.Z))
{
// Not numeric!
parser.ReportError("Expected rotation center Z value, but got \"" + token + "\"");
return false;
}
break;
case "useactorpitch":
inheritactorpitch = false;
useactorpitch = true;
break;
case "useactorroll":
useactorroll = true;
break;
case "rotating":
case "userotationcenter":
userotationcenter = true;
break;
case "inheritactorpitch":
inheritactorpitch = true;
useactorpitch = false;
parser.LogWarning("INHERITACTORPITCH flag is deprecated. Consider using USEACTORPITCH flag instead");
break;
case "inheritactorroll":
useactorroll = true;
parser.LogWarning("INHERITACTORROLL flag is deprecated. Consider using USEACTORROLL flag instead");
break;
//FrameIndex <XXXX> <X> <model index> <frame number>
case "frameindex":
// Sprite name
parser.SkipWhitespace(true);
string fispritename = ZDTextParser.StripQuotes(parser.ReadToken());
if(string.IsNullOrEmpty(fispritename))
{
parser.ReportError("Expected sprite name");
return false;
}
if(fispritename.Length != 4)
{
parser.ReportError("Sprite name must be 4 characters long, got \"" + fispritename + "\"");
return false;
}
// Sprite frame
parser.SkipWhitespace(true);
token = ZDTextParser.StripQuotes(parser.ReadToken());
if(string.IsNullOrEmpty(token))
{
parser.ReportError("Expected sprite frame");
return false;
}
if(token.Length != 1)
{
parser.ReportError("Sprite frame must be 1 character long");
return false;
}
// Make full name
fispritename += token;
// Model index
parser.SkipWhitespace(true);
int fimodelindnex = 0;
token = parser.ReadToken();
if(!parser.ReadSignedInt(token, ref fimodelindnex))
{
// Not numeric!
parser.ReportError("Expected model index, but got \"" + token + "\"");
return false;
}
if(fimodelindnex < 0)
{
// Out of bounds
parser.ReportError("Model index must not be negative");
return false;
}
// Frame number
parser.SkipWhitespace(true);
int fiframeindnex = 0;
token = parser.ReadToken();
//INFO: setting frame index to a negative number disables model rendering in GZDoom
if(!parser.ReadSignedInt(token, ref fiframeindnex))
{
// Not numeric!
parser.ReportError("Expected frame index, but got \"" + token + "\"");
return false;
}
// Add to collection
FrameStructure fifs = new FrameStructure { FrameIndex = fiframeindnex, ModelIndex = fimodelindnex, SpriteName = fispritename };
if(!frames.ContainsKey(fispritename))
{
frames.Add(fispritename, new HashSet<FrameStructure>());
frames[fispritename].Add(fifs);
}
else if(frames[fispritename].Contains(fifs))
{
parser.LogWarning("Duplicate FrameIndex definition");
}
else
{
frames[fispritename].Add(fifs);
}
break;
//Frame <XXXX> <X> <model index> <"frame name">
case "frame":
// Sprite name
parser.SkipWhitespace(true);
string spritename = parser.ReadToken();
if(string.IsNullOrEmpty(spritename))
{
parser.ReportError("Expected sprite name");
return false;
}
if(spritename.Length != 4)
{
parser.ReportError("Sprite name must be 4 characters long");
return false;
}
// Sprite frame
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(string.IsNullOrEmpty(token))
{
parser.ReportError("Expected sprite frame");
return false;
}
if(token.Length != 1)
{
parser.ReportError("Sprite frame must be 1 character long");
return false;
}
// Make full name
spritename += token;
// Model index
parser.SkipWhitespace(true);
int modelindnex = 0;
token = parser.ReadToken();
if(!parser.ReadSignedInt(token, ref modelindnex))
{
// Not numeric!
parser.ReportError("Expected model index, but got \"" + token + "\"");
return false;
}
if(modelindnex < 0)
{
// Out of bounds
parser.ReportError("Model index must not be negative");
return false;
}
// Frame name
parser.SkipWhitespace(true);
string framename = parser.StripTokenQuotes(parser.ReadToken());
if(string.IsNullOrEmpty(framename))
{
parser.ReportError("Expected frame name");
return false;
}
// Add to collection
FrameStructure fs = new FrameStructure { FrameName = framename, ModelIndex = modelindnex, SpriteName = spritename };
if(!frames.ContainsKey(spritename))
{
frames.Add(spritename, new HashSet<FrameStructure>());
frames[spritename].Add(fs);
}
else if(frames[spritename].Contains(fs))
{
parser.LogWarning("Duplicate Frame definition");
}
else
{
frames[spritename].Add(fs);
}
break;
case "{":
parser.ReportError("Unexpected scope start");
return false;
// Structure ends here
case "}":
parsingfinished = true;
break;
}
}
// Perform some integrity checks
if(!parsingfinished)
{
parser.ReportError("Unclosed structure scope");
return false;
}
// Any models defined?
if(modelnames.Count == 0)
{
parser.ReportError("Structure doesn't define any models");
return false;
}
foreach(int i in skinnames.Keys.OrderBy(k => k))
{
if (!string.IsNullOrEmpty(skinnames[i]) && !modelnames.ContainsKey(i))
{
parser.ReportError("No model is defined for skin " + i + ":\"" + skinnames[i] + "\"");
return false;
}
}
foreach(int i in surfaceskinenames.Keys.OrderBy(k => k))
{
if (surfaceskinenames[i].Count > 0 && !modelnames.ContainsKey(i))
{
parser.ReportError("No model is defined for surface skin " + i);
return false;
}
}
return true;
}
#endregion
}
}