using System; using System.Collections.Generic; using System.Globalization; using System.Text; using FlaxEngine; // https://web.archive.org/web/20160316213335/http://forums.ubergames.net/topic/2658-understanding-the-quake-3-map-format/ // https://web.archive.org/web/20210228125854/https://forums.thedarkmod.com/index.php?/topic/15668-plugin-request-save-map-in-quake-3-format/ namespace Game { public struct MapFacePlane { public Vector3 v1, v2, v3; public string texture; public Vector2 offset; public float rotation; public Vector2 scale; public int contentFlags, surfaceFlags, surfaceValue; } public class MapBrush { public MapFacePlane[] planes; } public class MapPatch { public string name; } public struct PatchVertex { public Vector3 v; public Vector2 uv; } public class MapEntity { public Dictionary properties = new Dictionary(); public List entities = new List(); public List brushes = new List(); public List patches = new List(); } public static class MapParser { static public MapEntity Parse(byte[] data) { if (data.Length == 0) return null; MapEntity rootEntity = new MapEntity(); MapEntity currentEntity = rootEntity; int level = 0; int index = 0; //for (int i=0; i 2) // throw new Exception("Failed to parse .map file: unexpected entity found at line " + lineNumber.ToString()); } while (++index < data.Length); return rootEntity; } static void ParseComment(byte[] data, ref int index) { for (; index < data.Length; index++) { if (data[index] == '\n') break; } } static void ParseEntity(MapEntity currentEntity, byte[] data, ref int index) { bool entityParsed = false; do { char c = (char) data[index]; char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0; switch (c) { case '\n': case '\r': break; case '/': if (c1 == '/') ParseComment(data, ref index); else throw new Exception("unexpected character: '" + c + "'"); break; // "name" "value" case '"': { string propName = ParseQuotedString(data, ref index); string propValue = ParseQuotedString(data, ref index); if (currentEntity.properties.ContainsKey(propName)) throw new Exception("Failed to parse .map file: multiple properties defined for " + propName + " at line ?");// + lineNumber.ToString()); currentEntity.properties.Add(propName, propValue); break; } // brush case '{': { index++; for (; index < data.Length; index++) { if (data[index] != ' ' && data[index] != '\r' && data[index] != '\n') break; index++; } if (index >= data.Length) break; if (data[index] == '(') currentEntity.brushes.Add(ParseBrush(data, ref index)); else if (char.IsLetter((char)data[index]) || char.IsNumber((char)data[index])) currentEntity.patches.Add(ParsePatch(data, ref index)); break; } case '}': { entityParsed = true; break; } default: throw new Exception("unsupported character: '" + c + "'"); } } while (index++ < data.Length && !entityParsed); } static string ParseQuotedString(byte[] data, ref int index) { StringBuilder sb = new StringBuilder(); index++; for (; index < data.Length; index++) { if (data[index] == '"') break; sb.Append((char)data[index]); } index++; while (index < data.Length) { if (data[index] != ' ') break; index++; } return sb.ToString(); } static string ParseString(byte[] data, ref int index) { StringBuilder sb = new StringBuilder(); for (; index < data.Length; index++) { if (data[index] == ' ' || data[index] == '\r' || data[index] == '\n') break; sb.Append((char)data[index]); } index++; while (index < data.Length) { if (data[index] != ' ') break; index++; } return sb.ToString(); } static float ParseFloat(byte[] data, ref int index) { string fs = ParseString(data, ref index); if (float.TryParse(fs, NumberStyles.Float, CultureInfo.InvariantCulture, out float value)) return value; //else if (float.TryParse(fs, CultureInfo.InvariantCulture, out intValue)) // return intValue; else throw new Exception("failed to ParseFloat: " + fs); } static int ParseInt(byte[] data, ref int index) { string fs = ParseString(data, ref index); if (int.TryParse(fs, out int value)) return value; else throw new Exception("failed to ParseInt: " + fs); } static Vector3 ParseVector3(byte[] data, ref int index) { return new Vector3( ParseFloat(data, ref index), ParseFloat(data, ref index), ParseFloat(data, ref index) ); } static Vector2 ParseVector2(byte[] data, ref int index) { return new Vector2( ParseFloat(data, ref index), ParseFloat(data, ref index) ); } static Vector3 ParsePlaneVector3(byte[] data, ref int index) { index++; while (index < data.Length) { if (data[index] != ' ') break; index++; } Vector3 vector = ParseVector3(data, ref index); while (index < data.Length) { if (data[index] == ')') break; index++; } index++; while (index < data.Length) { if (data[index] != ' ') break; index++; } return vector; } static MapBrush ParseBrush(byte[] data, ref int index) { MapBrush brush = new MapBrush(); List planes = new List(6); bool brushParsed = false; do { char c = (char) data[index]; //char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0; switch (c) { case '\r': case '\n': break; // brush face // ( ) ( ) ( ) case '(': { MapFacePlane plane; plane.v1 = ParsePlaneVector3(data, ref index); plane.v2 = ParsePlaneVector3(data, ref index); plane.v3 = ParsePlaneVector3(data, ref index); plane.texture = ParseString(data, ref index); plane.offset = ParseVector2(data, ref index); plane.rotation = ParseFloat(data, ref index); plane.scale = ParseVector2(data, ref index); plane.contentFlags = ParseInt(data, ref index); plane.surfaceFlags = ParseInt(data, ref index); plane.surfaceValue = ParseInt(data, ref index); planes.Add(plane); break; } case '}': { brushParsed = true; break; } default: if (char.IsLetter(c) || char.IsNumber(c)) { // patch name } throw new Exception("unsupported character: '" + c + "'"); } } while (index++ < data.Length && !brushParsed); brush.planes = planes.ToArray(); return brush; } static MapPatch ParsePatch(byte[] data, ref int index) { MapPatch patch = new MapPatch(); patch.name = ParseString(data, ref index); bool patchParsed = false; do { char c = (char) data[index]; //char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0; switch (c) { case '\r': case '\n': break; case '{': { index++; ParsePatchInner(patch, data, ref index); break; } case '}': { patchParsed = true; break; } default: throw new Exception("unsupported character: '" + c + "'"); } } while (index++ < data.Length && !patchParsed); return patch; } // unfinished and untested static void ParsePatchInner(MapPatch patch, byte[] data, ref int index) { string shaderName = ParseString(data, ref index); while (index < data.Length) { if (data[index] == '(') break; index++; } index++; int width = ParseInt(data, ref index); int height = ParseInt(data, ref index); int dummy1 = ParseInt(data, ref index); int dummy2 = ParseInt(data, ref index); int dummy3 = ParseInt(data, ref index); while (index < data.Length) { if (data[index] == ')') break; index++; } index++; while (index < data.Length) { if (data[index] == '(') break; index++; } index++; PatchVertex[] vertices = new PatchVertex[width * height]; bool verticesParsed = false; int vertexIndex = 0; do { char c = (char) data[index]; //char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0; switch (c) { case '\r': case '\n': break; case '(': { index++; for (int iw = 0; iw < width; iw++) { while (index < data.Length) { if (data[index] == '(') break; index++; } index++; while (index < data.Length) { if (data[index] != ' ') break; index++; } vertices[vertexIndex].v = ParseVector3(data, ref index); vertices[vertexIndex].uv = new Vector2(ParseFloat(data, ref index), ParseFloat(data, ref index)); vertexIndex++; while (index < data.Length) { if (data[index] == ')') break; index++; } index++; } break; } case '}': { verticesParsed = true; break; } default: throw new Exception("unsupported character: '" + c + "'"); } } while (index++ < data.Length && !verticesParsed); } } }