예제 #1
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);
        }
예제 #2
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);
        }