#region ================== Copyright (c) 2007 Pascal vd Heiden /* * Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com * This program is released under GNU General Public License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #endregion #region ================== Namespaces using System; using System.Collections; using System.Collections.Generic; using System.IO; using CodeImp.DoomBuilder.Map; using CodeImp.DoomBuilder.Geometry; using CodeImp.DoomBuilder.Types; #endregion namespace CodeImp.DoomBuilder.IO { internal class DoomMapSetIO : MapSetIO { #region ================== Constants #endregion #region ================== Constructor / Disposer // Constructor public DoomMapSetIO(WAD wad, MapManager manager) : base(wad, manager) { } #endregion #region ================== Properties public override int MaxSidedefs { get { return ushort.MaxValue; } } public override int MaxVertices { get { return ushort.MaxValue; } } public override int MaxLinedefs { get { return ushort.MaxValue; } } public override int MaxSectors { get { return ushort.MaxValue; } } public override int MaxThings { get { return int.MaxValue; } } public override int MinTextureOffset { get { return short.MinValue; } } public override int MaxTextureOffset { get { return short.MaxValue; } } public override int VertexDecimals { get { return 0; } } public override string DecimalsFormat { get { return "0"; } } public override bool HasLinedefTag { get { return true; } } public override bool HasThingTag { get { return false; } } public override bool HasThingAction { get { return false; } } public override bool HasCustomFields { get { return false; } } public override bool HasThingHeight { get { return false; } } public override bool HasActionArgs { get { return false; } } public override bool HasMixedActivations { get { return false; } } public override bool HasPresetActivations { get { return false; } } public override bool HasBuiltInActivations { get { return true; } } public override bool HasNumericLinedefFlags { get { return true; } } public override bool HasNumericThingFlags { get { return true; } } public override bool HasNumericLinedefActivations { get { return true; } } public override int MaxTag { get { return ushort.MaxValue; } } public override int MinTag { get { return ushort.MinValue; } } public override int MaxAction { get { return ushort.MaxValue; } } public override int MinAction { get { return ushort.MinValue; } } public override int MaxArgument { get { return 0; } } public override int MinArgument { get { return 0; } } public override int MaxEffect { get { return ushort.MaxValue; } } public override int MinEffect { get { return ushort.MinValue; } } public override int MaxBrightness { get { return short.MaxValue; } } public override int MinBrightness { get { return short.MinValue; } } public override int MaxThingType { get { return ushort.MaxValue; } } public override int MinThingType { get { return ushort.MinValue; } } public override float MaxCoordinate { get { return short.MaxValue; } } public override float MinCoordinate { get { return short.MinValue; } } public override int MaxThingAngle { get { return short.MaxValue; } } public override int MinThingAngle { get { return short.MinValue; } } public override Dictionary> UIFields { get { return null; } } //mxd #endregion #region ================== Reading // This reads a map from the file and returns a MapSet public override MapSet Read(MapSet map, string mapname) { Dictionary vertexlink; Dictionary sectorlink; // Find the index where first map lump begins int firstindex = wad.FindLumpIndex(mapname) + 1; // Read vertices vertexlink = ReadVertices(map, firstindex); // Read sectors sectorlink = ReadSectors(map, firstindex); // Read linedefs and sidedefs ReadLinedefs(map, firstindex, vertexlink, sectorlink); // Read things ReadThings(map, firstindex); // Remove unused vertices map.RemoveUnusedVertices(); // Return result; return map; } // This reads the THINGS from WAD file private void ReadThings(MapSet map, int firstindex) { MemoryStream mem; BinaryReader reader; int num, i, x, y, type, flags, angle; Dictionary stringflags; Thing t; // Get the lump from wad file Lump lump = wad.FindLump("THINGS", firstindex); if(lump == null) throw new Exception("Could not find required lump THINGS!"); // Prepare to read the items mem = new MemoryStream(lump.Stream.ReadAllBytes()); num = (int)lump.Stream.Length / 10; reader = new BinaryReader(mem); // Read items from the lump map.SetCapacity(0, 0, 0, 0, map.Things.Count + num); for(i = 0; i < num; i++) { // Read properties from stream x = reader.ReadInt16(); y = reader.ReadInt16(); angle = reader.ReadInt16(); type = reader.ReadUInt16(); flags = reader.ReadUInt16(); // Make string flags stringflags = new Dictionary(); foreach(KeyValuePair f in manager.Config.ThingFlags) { int fnum; if(int.TryParse(f.Key, out fnum)) stringflags[f.Key] = ((flags & fnum) == fnum); } // Create new item t = map.CreateThing(); t.Update(type, x, y, 0, angle, stringflags, 0, 0, new int[Thing.NUM_ARGS]); } // Done mem.Dispose(); } // This reads the VERTICES from WAD file // Returns a lookup table with indices private Dictionary ReadVertices(MapSet map, int firstindex) { MemoryStream mem; Dictionary link; BinaryReader reader; int num, i, x, y; Vertex v; // Get the lump from wad file Lump lump = wad.FindLump("VERTEXES", firstindex); if(lump == null) throw new Exception("Could not find required lump VERTEXES!"); // Prepare to read the items mem = new MemoryStream(lump.Stream.ReadAllBytes()); num = (int)lump.Stream.Length / 4; reader = new BinaryReader(mem); // Create lookup table link = new Dictionary(num); // Read items from the lump map.SetCapacity(map.Vertices.Count + num, 0, 0, 0, 0); for(i = 0; i < num; i++) { // Read properties from stream x = reader.ReadInt16(); y = reader.ReadInt16(); // Create new item v = map.CreateVertex(new Vector2D((float)x, (float)y)); // Add it to the lookup table link.Add(i, v); } // Done mem.Dispose(); // Return lookup table return link; } // This reads the SECTORS from WAD file // Returns a lookup table with indices private Dictionary ReadSectors(MapSet map, int firstindex) { MemoryStream mem; Dictionary link; BinaryReader reader; int num, i, hfloor, hceil, bright, special, tag; string tfloor, tceil; Sector s; // Get the lump from wad file Lump lump = wad.FindLump("SECTORS", firstindex); if(lump == null) throw new Exception("Could not find required lump SECTORS!"); // Prepare to read the items mem = new MemoryStream(lump.Stream.ReadAllBytes()); num = (int)lump.Stream.Length / 26; reader = new BinaryReader(mem); // Create lookup table link = new Dictionary(num); // Read items from the lump map.SetCapacity(0, 0, 0, map.Sectors.Count + num, 0); for(i = 0; i < num; i++) { // Read properties from stream hfloor = reader.ReadInt16(); hceil = reader.ReadInt16(); tfloor = Lump.MakeNormalName(reader.ReadBytes(8), WAD.ENCODING); tceil = Lump.MakeNormalName(reader.ReadBytes(8), WAD.ENCODING); bright = reader.ReadInt16(); special = reader.ReadUInt16(); tag = reader.ReadUInt16(); // Create new item s = map.CreateSector(); s.Update(hfloor, hceil, tfloor, tceil, special, tag, bright); // Add it to the lookup table link.Add(i, s); } // Done mem.Dispose(); // Return lookup table return link; } // This reads the LINEDEFS and SIDEDEFS from WAD file private void ReadLinedefs(MapSet map, int firstindex, Dictionary vertexlink, Dictionary sectorlink) { MemoryStream linedefsmem, sidedefsmem; BinaryReader readline, readside; Lump linedefslump, sidedefslump; int num, numsides, i, offsetx, offsety, v1, v2; int s1, s2, flags, action, tag, sc; Dictionary stringflags; string thigh, tmid, tlow; Linedef l; Sidedef s; // Get the linedefs lump from wad file linedefslump = wad.FindLump("LINEDEFS", firstindex); if(linedefslump == null) throw new Exception("Could not find required lump LINEDEFS!"); // Get the sidedefs lump from wad file sidedefslump = wad.FindLump("SIDEDEFS", firstindex); if(sidedefslump == null) throw new Exception("Could not find required lump SIDEDEFS!"); // Prepare to read the items linedefsmem = new MemoryStream(linedefslump.Stream.ReadAllBytes()); sidedefsmem = new MemoryStream(sidedefslump.Stream.ReadAllBytes()); num = (int)linedefslump.Stream.Length / 14; numsides = (int)sidedefslump.Stream.Length / 30; readline = new BinaryReader(linedefsmem); readside = new BinaryReader(sidedefsmem); // Read items from the lump map.SetCapacity(0, map.Linedefs.Count + num, map.Sidedefs.Count + numsides, 0, 0); for(i = 0; i < num; i++) { // Read properties from stream v1 = readline.ReadUInt16(); v2 = readline.ReadUInt16(); flags = readline.ReadUInt16(); action = readline.ReadUInt16(); tag = readline.ReadUInt16(); s1 = readline.ReadUInt16(); s2 = readline.ReadUInt16(); // Make string flags stringflags = new Dictionary(); foreach(string f in manager.Config.SortedLinedefFlags) { int fnum; if(int.TryParse(f, out fnum)) stringflags[f] = ((flags & fnum) == fnum); } // Create new linedef if(vertexlink.ContainsKey(v1) && vertexlink.ContainsKey(v2)) { // Check if not zero-length if(Vector2D.ManhattanDistance(vertexlink[v1].Position, vertexlink[v2].Position) > 0.0001f) { l = map.CreateLinedef(vertexlink[v1], vertexlink[v2]); l.Update(stringflags, 0, tag, action, new int[Linedef.NUM_ARGS]); l.UpdateCache(); // Line has a front side? if(s1 != ushort.MaxValue) { // Read front sidedef if((s1 * 30L) <= (sidedefsmem.Length - 30L)) { sidedefsmem.Seek(s1 * 30, SeekOrigin.Begin); offsetx = readside.ReadInt16(); offsety = readside.ReadInt16(); thigh = Lump.MakeNormalName(readside.ReadBytes(8), WAD.ENCODING); tlow = Lump.MakeNormalName(readside.ReadBytes(8), WAD.ENCODING); tmid = Lump.MakeNormalName(readside.ReadBytes(8), WAD.ENCODING); sc = readside.ReadUInt16(); // Create front sidedef if(sectorlink.ContainsKey(sc)) { s = map.CreateSidedef(l, true, sectorlink[sc]); s.Update(offsetx, offsety, thigh, tmid, tlow); } else { General.ErrorLogger.Add(ErrorType.Warning, "Sidedef " + s1 + " references invalid sector " + sc + ". Sidedef has been removed."); } } else { General.ErrorLogger.Add(ErrorType.Warning, "Linedef references invalid sidedef " + s1 + ". Sidedef has been removed."); } } // Line has a back side? if(s2 != ushort.MaxValue) { // Read back sidedef if((s2 * 30L) <= (sidedefsmem.Length - 30L)) { sidedefsmem.Seek(s2 * 30, SeekOrigin.Begin); offsetx = readside.ReadInt16(); offsety = readside.ReadInt16(); thigh = Lump.MakeNormalName(readside.ReadBytes(8), WAD.ENCODING); tlow = Lump.MakeNormalName(readside.ReadBytes(8), WAD.ENCODING); tmid = Lump.MakeNormalName(readside.ReadBytes(8), WAD.ENCODING); sc = readside.ReadUInt16(); // Create back sidedef if(sectorlink.ContainsKey(sc)) { s = map.CreateSidedef(l, false, sectorlink[sc]); s.Update(offsetx, offsety, thigh, tmid, tlow); } else { General.ErrorLogger.Add(ErrorType.Warning, "Sidedef " + s2 + " references invalid sector " + sc + ". Sidedef has been removed."); } } else { General.ErrorLogger.Add(ErrorType.Warning, "Linedef " + i + " references invalid sidedef " + s2 + ". Sidedef has been removed."); } } } else { General.ErrorLogger.Add(ErrorType.Warning, "Linedef " + i + " is zero-length. Linedef has been removed."); } } else { General.ErrorLogger.Add(ErrorType.Warning, "Linedef " + i + " references one or more invalid vertices. Linedef has been removed."); } } // Done linedefsmem.Dispose(); sidedefsmem.Dispose(); } #endregion #region ================== Writing // This writes a MapSet to the file public override void Write(MapSet map, string mapname, int position) { Dictionary vertexids = new Dictionary(); Dictionary sidedefids = new Dictionary(); Dictionary sectorids = new Dictionary(); // First index everything foreach(Vertex v in map.Vertices) vertexids.Add(v, vertexids.Count); foreach(Sidedef sd in map.Sidedefs) sidedefids.Add(sd, sidedefids.Count); foreach(Sector s in map.Sectors) sectorids.Add(s, sectorids.Count); // Write lumps to wad (note the backwards order because they // are all inserted at position+1 when not found) WriteSectors(map, position, manager.Config.MapLumpNames); WriteVertices(map, position, manager.Config.MapLumpNames); WriteSidedefs(map, position, manager.Config.MapLumpNames, sectorids); WriteLinedefs(map, position, manager.Config.MapLumpNames, sidedefids, vertexids); WriteThings(map, position, manager.Config.MapLumpNames); } // This writes the THINGS to WAD file private void WriteThings(MapSet map, int position, IDictionary maplumps) { MemoryStream mem; BinaryWriter writer; Lump lump; int insertpos; int flags; // Create memory to write to mem = new MemoryStream(); writer = new BinaryWriter(mem, WAD.ENCODING); // Go for all things foreach(Thing t in map.Things) { // Convert flags flags = 0; foreach(KeyValuePair f in t.Flags) { int fnum; if(f.Value && int.TryParse(f.Key, out fnum)) flags |= fnum; } // Write properties to stream writer.Write((Int16)t.Position.x); writer.Write((Int16)t.Position.y); writer.Write((Int16)t.AngleDoom); writer.Write((UInt16)t.Type); writer.Write((UInt16)flags); } // Find insert position and remove old lump insertpos = MapManager.RemoveSpecificLump(wad, "THINGS", position, MapManager.TEMP_MAP_HEADER, maplumps); if(insertpos == -1) insertpos = position + 1; if(insertpos > wad.Lumps.Count) insertpos = wad.Lumps.Count; // Create the lump from memory lump = wad.Insert("THINGS", insertpos, (int)mem.Length); lump.Stream.Seek(0, SeekOrigin.Begin); mem.WriteTo(lump.Stream); mem.Flush(); } // This writes the VERTEXES to WAD file private void WriteVertices(MapSet map, int position, IDictionary maplumps) { MemoryStream mem; BinaryWriter writer; Lump lump; int insertpos; // Create memory to write to mem = new MemoryStream(); writer = new BinaryWriter(mem, WAD.ENCODING); // Go for all vertices foreach(Vertex v in map.Vertices) { // Write properties to stream writer.Write((Int16)(int)Math.Round(v.Position.x)); writer.Write((Int16)(int)Math.Round(v.Position.y)); } // Find insert position and remove old lump insertpos = MapManager.RemoveSpecificLump(wad, "VERTEXES", position, MapManager.TEMP_MAP_HEADER, maplumps); if(insertpos == -1) insertpos = position + 1; if(insertpos > wad.Lumps.Count) insertpos = wad.Lumps.Count; // Create the lump from memory lump = wad.Insert("VERTEXES", insertpos, (int)mem.Length); lump.Stream.Seek(0, SeekOrigin.Begin); mem.WriteTo(lump.Stream); mem.Flush(); } // This writes the LINEDEFS to WAD file private void WriteLinedefs(MapSet map, int position, IDictionary maplumps, IDictionary sidedefids, IDictionary vertexids) { MemoryStream mem; BinaryWriter writer; Lump lump; ushort sid; int insertpos; int flags; // Create memory to write to mem = new MemoryStream(); writer = new BinaryWriter(mem, WAD.ENCODING); // Go for all lines foreach(Linedef l in map.Linedefs) { // Convert flags flags = 0; foreach(KeyValuePair f in l.Flags) { int fnum; if(f.Value && int.TryParse(f.Key, out fnum)) flags |= fnum; } // Write properties to stream writer.Write((UInt16)vertexids[l.Start]); writer.Write((UInt16)vertexids[l.End]); writer.Write((UInt16)flags); writer.Write((UInt16)l.Action); writer.Write((UInt16)l.Tag); // Front sidedef if(l.Front == null) sid = ushort.MaxValue; else sid = (UInt16)sidedefids[l.Front]; writer.Write(sid); // Back sidedef if(l.Back == null) sid = ushort.MaxValue; else sid = (UInt16)sidedefids[l.Back]; writer.Write(sid); } // Find insert position and remove old lump insertpos = MapManager.RemoveSpecificLump(wad, "LINEDEFS", position, MapManager.TEMP_MAP_HEADER, maplumps); if(insertpos == -1) insertpos = position + 1; if(insertpos > wad.Lumps.Count) insertpos = wad.Lumps.Count; // Create the lump from memory lump = wad.Insert("LINEDEFS", insertpos, (int)mem.Length); lump.Stream.Seek(0, SeekOrigin.Begin); mem.WriteTo(lump.Stream); mem.Flush(); } // This writes the SIDEDEFS to WAD file private void WriteSidedefs(MapSet map, int position, IDictionary maplumps, IDictionary sectorids) { MemoryStream mem; BinaryWriter writer; Lump lump; int insertpos; // Create memory to write to mem = new MemoryStream(); writer = new BinaryWriter(mem, WAD.ENCODING); // Go for all sidedefs foreach(Sidedef sd in map.Sidedefs) { // Write properties to stream writer.Write((Int16)sd.OffsetX); writer.Write((Int16)sd.OffsetY); writer.Write(Lump.MakeFixedName(sd.HighTexture, WAD.ENCODING)); writer.Write(Lump.MakeFixedName(sd.LowTexture, WAD.ENCODING)); writer.Write(Lump.MakeFixedName(sd.MiddleTexture, WAD.ENCODING)); writer.Write((UInt16)sectorids[sd.Sector]); } // Find insert position and remove old lump insertpos = MapManager.RemoveSpecificLump(wad, "SIDEDEFS", position, MapManager.TEMP_MAP_HEADER, maplumps); if(insertpos == -1) insertpos = position + 1; if(insertpos > wad.Lumps.Count) insertpos = wad.Lumps.Count; // Create the lump from memory lump = wad.Insert("SIDEDEFS", insertpos, (int)mem.Length); lump.Stream.Seek(0, SeekOrigin.Begin); mem.WriteTo(lump.Stream); mem.Flush(); } // This writes the SECTORS to WAD file private void WriteSectors(MapSet map, int position, IDictionary maplumps) { MemoryStream mem; BinaryWriter writer; Lump lump; int insertpos; // Create memory to write to mem = new MemoryStream(); writer = new BinaryWriter(mem, WAD.ENCODING); // Go for all sectors foreach(Sector s in map.Sectors) { // Write properties to stream writer.Write((Int16)s.FloorHeight); writer.Write((Int16)s.CeilHeight); writer.Write(Lump.MakeFixedName(s.FloorTexture, WAD.ENCODING)); writer.Write(Lump.MakeFixedName(s.CeilTexture, WAD.ENCODING)); writer.Write((Int16)s.Brightness); writer.Write((UInt16)s.Effect); writer.Write((UInt16)s.Tag); } // Find insert position and remove old lump insertpos = MapManager.RemoveSpecificLump(wad, "SECTORS", position, MapManager.TEMP_MAP_HEADER, maplumps); if(insertpos == -1) insertpos = position + 1; if(insertpos > wad.Lumps.Count) insertpos = wad.Lumps.Count; // Create the lump from memory lump = wad.Insert("SECTORS", insertpos, (int)mem.Length); lump.Stream.Seek(0, SeekOrigin.Begin); mem.WriteTo(lump.Stream); mem.Flush(); } #endregion } }