Example #1
0
        internal static (ushort, OverlayRotation) ReadSecondaryTexture(BlockStreamReader reader, bool isExtendedFormat)
        {
            var             rawValue = ReadValue <short>(reader, isExtendedFormat ? "OvlTex" : "tmap_num2");
            ushort          index    = (ushort)(rawValue & 0x3FFF);
            OverlayRotation rotation = (OverlayRotation)((rawValue & 0xC000) >> 14);

            return(index, rotation);
        }
Example #2
0
        internal static (uint, int, int) ReadExtendedVariableLight(BlockStreamReader reader)
        {
            var regex = new Regex(@"^    variable light (\d+) (-?\d+) (-?\d+)$");
            var match = regex.Match(reader.ReadLine());

            if (!match.Success)
            {
                throw new InvalidDataException($"Expected variable light at line {reader.LastLineNumber}: '{reader.LastLine}'");
            }
            return(uint.Parse(match.Groups[1].Value), int.Parse(match.Groups[2].Value), int.Parse(match.Groups[3].Value));
        }
Example #3
0
        internal static T ReadValue <T>(BlockStreamReader reader, string valueName)
        {
            var regex = new Regex($"^\\s*{valueName} (-?\\d+)$");
            var match = regex.Match(reader.ReadLine());

            if (!match.Success)
            {
                throw new InvalidDataException($"Expected {valueName} at line {reader.LastLineNumber}: '{reader.LastLine}'");
            }
            return((T)System.ComponentModel.TypeDescriptor.GetConverter(typeof(T))
                   .ConvertFromString(match.Groups[1].Value));
        }
Example #4
0
        internal static uint[] ReadExtendedSideVertexIds(BlockStreamReader reader)
        {
            var regex = new Regex(@"^    vertex ids (\d+) (\d+) (\d+) (\d+)$");
            var match = regex.Match(reader.ReadLine());

            if (!match.Success)
            {
                throw new InvalidDataException($"Expected vertex ids at line {reader.LastLineNumber}: '{reader.LastLine}'");
            }
            object[] groups = new object[match.Groups.Count];
            match.Groups.CopyTo(groups, 0);
            return(Array.ConvertAll(groups.Skip(1).ToArray(), group => uint.Parse((group as Group).Value)));
        }
Example #5
0
        internal static int[] ReadSegmentChildren(BlockStreamReader reader)
        {
            var regex = new Regex(@"^  children (-?\d+) (-?\d+) (-?\d+) (-?\d+) (-?\d+) (-?\d+)$");
            var match = regex.Match(reader.ReadLine());

            if (!match.Success)
            {
                throw new InvalidDataException($"Expected children at line {reader.LastLineNumber}: '{reader.LastLine}'");
            }
            object[] groups = new object[match.Groups.Count];
            match.Groups.CopyTo(groups, 0);
            return(Array.ConvertAll(groups.Skip(1).ToArray(), group => int.Parse((group as Group).Value)));
        }
Example #6
0
        internal static Uvl ReadUvl(BlockStreamReader reader)
        {
            var match = uvlRegex.Match(reader.ReadLine());

            if (!match.Success)
            {
                throw new InvalidDataException($"Expected uvls at line {reader.LastLineNumber}: '{reader.LastLine}'");
            }
            var u = short.Parse(match.Groups[1].Value);
            var v = short.Parse(match.Groups[2].Value);
            var l = ushort.Parse(match.Groups[3].Value);

            return(Uvl.FromRawValues(u, v, l));
        }
Example #7
0
        internal static (FixVector vertexLocation, uint vertexId) ReadVertex(BlockStreamReader reader, bool isExtendedFormat)
        {
            var regex = isExtendedFormat ? extendedVertexRegex : vertexRegex;
            var match = regex.Match(reader.ReadLine());

            if (!match.Success)
            {
                string name = isExtendedFormat ? "Vertex" : "vms_vector";
                throw new InvalidDataException($"Expected {name} at line {reader.LastLineNumber}: '{reader.LastLine}'");
            }

            return(vertexLocation : FixVector.FromRawValues(
                       x : int.Parse(match.Groups[2].Value),
                       y : int.Parse(match.Groups[3].Value),
                       z : int.Parse(match.Groups[4].Value)),
                   vertexId : uint.Parse(match.Groups[1].Value));
        }
Example #8
0
 internal static ushort ReadPrimaryTextureIndex(BlockStreamReader reader, bool isExtendedFormat)
 {
     return(ReadValue <ushort>(reader, isExtendedFormat ? "BaseTex" : "tmap_num"));
 }
Example #9
0
        public static Block CreateFromStream(Stream stream)
        {
            var reader = new BlockStreamReader(stream);

            if (reader.ReadLine() != "DMB_BLOCK_FILE")
            {
                throw new InvalidDataException("DMB block header is missing or incorrect.");
            }

            var segments = new Dictionary <uint, Segment>();
            // We read segment children before we have all the segments, so we need to connect them in a subsequent pass
            var segmentConnections = new Dictionary <uint, int[]>();

            while (!reader.EndOfStream)
            {
                uint segmentId = BlockCommon.ReadValue <uint>(reader, "segment");
                if (segments.ContainsKey(segmentId))
                {
                    throw new InvalidDataException($"Encountered duplicate definition of segment {segmentId} at line {reader.LastLineNumber}.");
                }
                var segment = new Segment(Segment.MaxSides, Segment.MaxVertices);
                segments[segmentId] = segment;

                // Read sides
                for (uint sideNum = 0; sideNum < segment.Sides.Length; sideNum++)
                {
                    int fileSideNum = BlockCommon.ReadValue <int>(reader, "side");
                    if (fileSideNum != sideNum)
                    {
                        throw new InvalidDataException($"Unexpected side number {fileSideNum} at line {reader.LastLineNumber}.");
                    }

                    var side = new Side(segment, sideNum);
                    segment.Sides[sideNum] = side;
                    side.BaseTextureIndex  = BlockCommon.ReadPrimaryTextureIndex(reader, false);
                    (var overlayIndex, var overlayRotation) = BlockCommon.ReadSecondaryTexture(reader, false);
                    side.OverlayTextureIndex = overlayIndex;
                    side.OverlayRotation     = overlayRotation;
                    for (int i = 0; i < side.Uvls.Length; i++)
                    {
                        side.Uvls[i] = BlockCommon.ReadUvl(reader);
                    }
                }

                segmentConnections[segmentId] = BlockCommon.ReadSegmentChildren(reader);

                // Read vertices
                for (uint vertexNum = 0; vertexNum < Segment.MaxVertices; vertexNum++)
                {
                    (var vertexLocation, var fileVertexNum) = BlockCommon.ReadVertex(reader, false);
                    if (fileVertexNum != vertexNum)
                    {
                        throw new InvalidDataException($"Unexpected vertex number {fileVertexNum} at line {reader.LastLineNumber}.");
                    }

                    segment.Vertices[vertexNum] = new LevelVertex(vertexLocation);
                }

                segment.Light     = new Fix(BlockCommon.ReadValue <int>(reader, "static_light"));
                segment.Function  = SegFunction.None;
                segment.MatCenter = null;
            }

            // Now set up segment connections
            foreach (var connection in segmentConnections)
            {
                for (int i = 0; i < connection.Value.Length; i++)
                {
                    var connectedSegmentId = connection.Value[i];
                    if (connectedSegmentId < 0)
                    {
                        continue;
                    }
                    segments[connection.Key].Sides[i].ConnectedSegment = segments[(uint)connectedSegmentId];
                }
            }

            Block block = new Block();

            foreach (Segment segment in segments.Values)
            {
                block.Segments.Add(segment);
            }
            BlockCommon.SetupVertexConnections(block);
            block.RemoveDuplicateVertices();

            return(block);
        }
Example #10
0
        public static ExtendedBlock CreateFromStream(Stream stream)
        {
            var reader = new BlockStreamReader(stream);

            if (reader.ReadLine() != "DMB_EXT_BLOCK_FILE")
            {
                throw new InvalidDataException("DLE block header is missing or incorrect.");
            }

            ExtendedBlock block    = new ExtendedBlock();
            var           segments = new Dictionary <uint, Segment>();
            // We read segment children before we have all the segments, so we need to connect them in a subsequent pass
            var segmentConnections = new Dictionary <uint, int[]>();
            var vertices           = new Dictionary <uint, LevelVertex>();
            // We also read triggers before we have all the segments
            var triggerTargetConnections = new Dictionary <int, List <(int segmentNum, int sideNum)> >();

            while (!reader.EndOfStream)
            {
                uint segmentId = BlockCommon.ReadValue <uint>(reader, "segment");
                if (segments.ContainsKey(segmentId))
                {
                    throw new InvalidDataException($"Encountered duplicate definition of segment {segmentId} at line {reader.LastLineNumber}.");
                }
                var segment = new Segment(Segment.MaxSides, Segment.MaxVertices);
                segments[segmentId] = segment;

                // Read sides
                for (uint sideNum = 0; sideNum < segment.Sides.Length; sideNum++)
                {
                    int fileSideNum = BlockCommon.ReadValue <int>(reader, "side");

                    // Negative side numbers in .blx format indicate animated lights - weird, but that's how it is
                    bool hasVariableLight = fileSideNum < 0;
                    if (hasVariableLight)
                    {
                        fileSideNum = -fileSideNum;
                    }
                    if (fileSideNum != sideNum)
                    {
                        throw new InvalidDataException($"Unexpected side number {fileSideNum} at line {reader.LastLineNumber}.");
                    }

                    var side = new Side(segment, sideNum);
                    segment.Sides[sideNum] = side;

                    // Textures
                    side.BaseTextureIndex = BlockCommon.ReadPrimaryTextureIndex(reader, true);
                    (var overlayIndex, var overlayRotation) = BlockCommon.ReadSecondaryTexture(reader, true);
                    side.OverlayTextureIndex = overlayIndex;
                    side.OverlayRotation     = overlayRotation;
                    for (int i = 0; i < side.Uvls.Length; i++)
                    {
                        side.Uvls[i] = BlockCommon.ReadUvl(reader);
                    }

                    // Vertex mapping
                    uint[] sideVertexIds = BlockCommon.ReadExtendedSideVertexIds(reader);
                    foreach (uint sideVertexId in sideVertexIds)
                    {
                        // BLX_SIDE_VERTEX_ID_NONE (0xFF) = no vertex
                        // D2X-XL uses these for non-cuboid segment support. We don't have that yet - more design work needed
                        if (sideVertexId == BLX_SIDE_VERTEX_ID_NONE)
                        {
                            throw new NotSupportedException($"Found non-quadrilateral side {sideNum} at line {reader.LastLineNumber}, "
                                                            + "which is currently unsupported.");
                        }

                        // Except for non-cuboid segments, side vertex ids appear to match Segment.SideVerts anyway, so we can ignore them
                    }
                    // This is what DLE does here - for future reference

                    /*for (int j = 0; j < 4; j++)
                     *  pSide->m_vertexIdIndex[j] = ubyte(sideVertexIds[j]);
                     * pSide->DetectShape();*/

                    // Animated lights
                    if (hasVariableLight)
                    {
                        (uint mask, int timer, int delay) = BlockCommon.ReadExtendedVariableLight(reader);
                        var light = new AnimatedLight(side);
                        light.Mask           = mask;
                        light.TimeToNextTick = new Fix(timer);
                        light.TickLength     = new Fix(delay);
                        block.AnimatedLights.Add(light);
                        side.AnimatedLight = light;
                    }

                    // Walls and triggers
                    uint wallId = BlockCommon.ReadValue <uint>(reader, "wall");
                    if (wallId != BLX_WALL_ID_NONE)
                    {
                        var wall = new Wall(side);
                        side.Wall = wall;
                        block.Walls.Add(wall);

                        // .blx format includes segment/side numbers for walls - we don't need them
                        // because we derive from context
                        BlockCommon.ReadValue <int>(reader, "segment");
                        BlockCommon.ReadValue <int>(reader, "side");

                        wall.HitPoints      = BlockCommon.ReadValue <int>(reader, "hps");
                        wall.Type           = (WallType)BlockCommon.ReadValue <int>(reader, "type");
                        wall.Flags          = (WallFlags)BlockCommon.ReadValue <byte>(reader, "flags");
                        wall.State          = (WallState)BlockCommon.ReadValue <byte>(reader, "state");
                        wall.DoorClipNumber = (byte)BlockCommon.ReadValue <sbyte>(reader, "clip");
                        wall.Keys           = (WallKeyFlags)BlockCommon.ReadValue <byte>(reader, "keys");
                        wall.CloakOpacity   = BlockCommon.ReadValue <byte>(reader, "cloak");

                        var triggerNum = BlockCommon.ReadValue <byte>(reader, "trigger");

                        // 255 (0xFF) means no trigger on this wall
                        if (triggerNum != 0xFF)
                        {
                            var trigger = new BlxTrigger();
                            wall.Trigger = trigger;
                            block.Triggers.Add(trigger);

                            trigger.Type  = (TriggerType)BlockCommon.ReadValue <byte>(reader, "type");
                            trigger.Flags = BlockCommon.ReadValue <ushort>(reader, "flags");
                            trigger.Value = BlockCommon.ReadValue <int>(reader, "value");
                            trigger.Time  = BlockCommon.ReadValue <int>(reader, "timer");

                            // Trigger targets
                            var triggerTargets = new List <(int segmentNum, int sideNum)>();
                            var targetCount    = BlockCommon.ReadValue <short>(reader, "count");
                            for (int targetNum = 0; targetNum < targetCount; targetNum++)
                            {
                                var targetSegmentNum = BlockCommon.ReadValue <int>(reader, "segment");
                                var targetSideNum    = BlockCommon.ReadValue <int>(reader, "side");
                                triggerTargets.Add((targetSegmentNum, targetSideNum));
                            }
                            if (triggerTargets.Count > 0)
                            {
                                triggerTargetConnections[block.Triggers.Count - 1] = triggerTargets;
                            }
                        }
                    }
                }

                segmentConnections[segmentId] = BlockCommon.ReadSegmentChildren(reader);

                // Read vertices
                /*pSegment->m_nShape = 0;*/ // DLE non-cuboid - for reference
                for (uint vertexNum = 0; vertexNum < Segment.MaxVertices; vertexNum++)
                {
                    (var vertexLocation, var fileVertexNum) = BlockCommon.ReadVertex(reader, true);

                    // Non-cuboid segment support
                    /*pSegment->m_info.vertexIds[i] = fileVertexNum;*/
                    if (fileVertexNum > BLX_MAX_VERTEX_NUM)
                    {
                        throw new NotSupportedException($"Found non-cuboid segment {segmentId} at line {reader.LastLineNumber}, "
                                                        + "which is currently unsupported.");

                        /*pSegment->m_nShape++;
                         * continue;*/
                    }

                    if (!vertices.ContainsKey(fileVertexNum))
                    {
                        vertices[fileVertexNum] = new LevelVertex(vertexLocation);
                    }
                    segment.Vertices[vertexNum] = vertices[fileVertexNum];
                }

                segment.Light    = new Fix(BlockCommon.ReadValue <int>(reader, "static_light"));
                segment.Function = (SegFunction)BlockCommon.ReadValue <byte>(reader, "special");

                var matcenNum = BlockCommon.ReadValue <sbyte>(reader, "matcen_num");
                if (matcenNum != -1)
                {
                    var matcen = new MatCenter(segment);
                    segment.MatCenter = matcen;
                    block.MatCenters.Add(matcen);
                }

                // This is an index into the fuelcen (also used for matcens) array in D1/D2.
                // It's relatively easy to recalculate so we don't store it.
                BlockCommon.ReadValue <sbyte>(reader, "value");

                // Child/wall bitmasks are used internally by DLE but we don't really need them
                // - they can be recalculated
                BlockCommon.ReadValue <byte>(reader, "child_bitmask");
                BlockCommon.ReadValue <byte>(reader, "wall_bitmask");
            }

            // Now set up segment connections
            foreach (var connection in segmentConnections)
            {
                for (int i = 0; i < connection.Value.Length; i++)
                {
                    var connectedSegmentId = connection.Value[i];
                    if (connectedSegmentId < 0)
                    {
                        continue;
                    }
                    segments[connection.Key].Sides[i].ConnectedSegment = segments[(uint)connectedSegmentId];
                }
            }

            foreach (Segment segment in segments.Values)
            {
                block.Segments.Add(segment);
            }

            BlockCommon.SetupVertexConnections(block);

            // Set up trigger target connections
            foreach (var connection in triggerTargetConnections)
            {
                var trigger = block.Triggers[connection.Key];
                for (int targetNum = 0; targetNum < connection.Value.Count; targetNum++)
                {
                    var target     = connection.Value[targetNum];
                    var targetSide = block.Segments[target.segmentNum].Sides[target.sideNum];
                    trigger.Targets.Add(targetSide);
                    if (targetSide.Wall != null)
                    {
                        targetSide.Wall.ControllingTriggers.Add((trigger, (uint)targetNum));
                    }
                }
            }

            return(block);
        }