EotB – Väggar

Nu är det dags att börja ge oss på väggarna i Eye of the Beholder. I tidigare artikel lyckas vi inte koda av grafiken till just väggarna.

Det börjar bli så pass mycket material så vi hade kunnat skriva en bok om hur spelet, och för den delen gamla spel, konstruerats. Kanske är det de generationer som är uppvuxna på 80- och 90-talet som kommer att sitta på ålderdomshemmet och koda retrospel? Jämför med hembygdsföreningar så kanske vi sitter i samma lokaler där vi en gång hade ”copy party” och diskuterar vilket RPG som var bäst till Amiga. Vem vet?

Information

Westwood studios som gjorde Eye of the Beholder (1990) gjorde många andra spel, t.ex. Dune II, Lands of Lore m.fl. Till dessa använde ofta samma tekniker som  t.ex. .PAK-filer. Därför är dessa format mer kända än formatet för väggarna i Eye of the Beholder. Ju djupare vi gräver i Eye of the Beholder, desto svårare blir det att hitta information om de specifika formaten som bara används i Eye of the Beholder.

Lands of Lore: The Throne of Chaos (1993)

Dune II (1992)

Efter att ha utförligt sökt på internet så hittar vi att .VCN och .VMP-filerna hör ihop. Grafiken ska finnas i .VCN-filen och .VMP-filen ska på något vis tala om hur grafiken ska sättas ihop. Mer än så hittar vi inte. Vi hittar några kodfiler assembler till Amiga, tyvärr okommenterade. När vi precis är på väg att ge upp så hittar vi spår av informationen vi söker via web.archive.org.

Det finns tack och lov projekt som sparar undan hela siter för arkivering. Via dessa projekt, som web.archive.org, kan man hitta gamla versioner av hemsidor eller t.ex. hemsidor som inte längre finns. En riktig tidskapsel. Eter att ha återfunnit den ”förlorade” informationen så kan vi fortsätta vårt arbete.

Palett .PAL

I tidigare artikel gick vi igenom olika palett-filer av type .COL. Viktigt att nämna är att de 5 .PAL-filer som finns i spelet; BRICK.PAL (pak-fil #3), BLUE.PAL (pak-fil #4), GREEN.PAL & XANATHA.PAL (pak-fil #5) samt DROW.PAL (pak-fil #6) är de paletter som används i själva spelandet.

Ett tag hade vi problem med dessa filer då värdena indikerade 8-bit färger. För .COL-filerna så var alla värden mindre eller lika med 63, alltså 6-bit. Här helt plötsligt kunde vi hitta värde upp till 255, 8-bit. Men om vi tolkade färgerna som 8-bit så blev resultatet inte bra. Om vi å andra sidan tolkar färgerna som 6-bit och helt enkelt ”slänger” de översta två bitarna så blir resultatet korrekt! Varför det finns extra bitar satt har vi inte lyckats få någon förklaring till ännu. Kanske är det någon slags markering för att göra palett-effekter när t.ex. monster tar skada eller liknande, vem det?

KOBOLD.CPS (8-bit färger)

KOBOLD.CPS (6-bit färger)

För att hantera palett på liknande sätt som övrig kod så valde vi att skapa klassen PaletteFile.

PaletteFile.cs
using System.Collections.Generic;
using System.Drawing;

namespace PakExtract
{
    class PaletteFile
    {
        public List<Color> Colors { get; set; }

        private PaletteFile(byte[] data)
        {
            Colors = new List<Color>();

            for (int i = 0; i < data.Length / 3; i++)
            {
                Colors.Add(Color.FromArgb(
                    (byte)((data[i * 3] * 255) / 63),
                    (byte)((data[i * 3 + 1] * 255) / 63),
                    (byte)((data[i * 3 + 2] * 255) / 63)));
            }
        }

        public static PaletteFile FromData(byte[] data)
        {
            return new PaletteFile(data);
        }
    }
}

VCN-filer

Till att börja med så är VCN-filen CPS-kodad fastän det inte är en egentlig bild. Så först måste vi avkoda CPS. Därför förväntade vi oss först en bild men såg bara omkastad information (förra artikeln).

VCN-filen hör ihop med motsvarande .PAL-fil samt .VMP-fil. Alltså BRICK.PAL, BRICK.VCN och BRICK.VMP hör ihop.

VCN-filer visar sig vara en uppsättning mindre byggblock av pixlar där varje block är 8×8 pixlar. För att spara plats har man valt att använda en palett ”ovanpå” befintlig palett. Man har valt att använda två stycken 16-färger paletter; en till bakgrunden av banan och en palett till väggarna. På så vis kan man lagra varje pixel som endast 4-bitar, alltså 2 pixlar per byte. Därför blir vare 8×8 pixels ruta endast 4×8 byte stor.

Men för att hitta rätt färg måste man ta pixel-värdet från blocket, kolla mot vägg-paletten, som i sin tur ger rätt index till färgen i .PAL-filen. Med andra ord lite omständligt. Filen är uppbyggs som följer;

Datatyp Namn Kommentar
UInt16 NrOfBlocks Antalet 8×8 block som följer efter filhuvudet. Egentligen 4×8 bytes per block.
byte[16] BackdropLookup Uppslag till vilka färger i .PAL-filen som ska användas till bakgrunden.
byte[16] WallLookup Uppslag till vilka färger i .PAL-filen som ska användas till väggarna.
byte[4,8]*NrOfBlocks Block… NrOfBlocks antal upprepningar av Block.

Vi kan nu skapa en klass som kan läsa in all denna information.

VcnFile.cs
using System.Collections.Generic;

namespace PakExtract
{
    class VcnFile
    {
        public int NrOfBlocks { get; private set; }
        public List<byte[,]> Blocks { get; set; }
        public byte[] BackdropLookup { get; set; }
        public byte[] WallLookup { get; set; }

        private VcnFile(byte[] data)
        {
            int index = 0;
            Blocks = new List<byte[,]>();
            NrOfBlocks = data[index++] + (data[index++] << 8);

            BackdropLookup = new byte[16];
            WallLookup = new byte[16];

            for (int i = 0; i < 16; i++)
                BackdropLookup[i] = data[index++];

            for (int i = 0; i < 16; i++)
                WallLookup[i] = data[index++];

            for(int i=0; i<NrOfBlocks; i++)
            {
                var block = new byte[8, 8];
                for(int x=0; x< 32; x++)
                {
                    var tmp = data[index++];
                    block[(x * 2) % 8, (x * 2) / 8] = (byte)(tmp >> 4);
                    block[((x * 2) % 8) + 1, (x * 2) / 8] = (byte)(tmp & 0xF);
                }

                Blocks.Add(block);
            }
        }

        public static VcnFile FromData(byte[] data)
        {
            return new VcnFile(data);
        }
    }
}

Vi vet inte hur blocken i VCN-filen ska användas än men vi kan ändå testa att ruta ut 8×8 block med BRICK.VCN och BRICK.PAL som fyller 320×200 pixlar (inte alla block i filen) bara för att testa lite.

BRICK.VCN ritad som bild med ORB.COL

Block från BRICK.VCN ritad med BackdropLookup via BRICK.PAL

Block från BRICK.VCN ritad med WallLookup via BRICK.PAL

Framsteg! Det syns nu tydligt att vi än så länge lyckats få ut något vettigt av BRICK.VCN. Det syns också att blocken som vi ritar ut hamnar ”huller om buller”. Med BackdropLookup som palett så syns det att de första blocken för korrekta färger. Med WallLookup syns att övriga block, troligtvis väggarna, får korrekta färger.

VMP-filer

VMP-filen ska på något vis tala om för oss hur alla 8×8 block ska sättas ihop till något vettigt. Vår information säger oss att filen är uppbyggd som:

Datatyp Namn Kommentar
UInt16 Header Oklart vad den används till. Troligtvis hur många väggtyper som finns i filen.
UInt16 [22,15] BackgroundTiles Alla block som ska användas till bakgrunden. Den är alltså 22×15 block stor.
UInt16 [101] Padding Utfyllnad för att få filhuvudet 431*UInt16 stort.
UInt16[431]*NrOfWallTypes WallType… Block för att beskriva en sorts vägg.

Varje UInt16 block som anges innehåller mer än bara vilket block från VCN-filen som ska användas.

  • Bit 15, IsLimit. Har något att göra med om när blocket möter golvet eller taket.
  • Bit 14, IsFlipped. Anger om blocket ska vändas horisontellt vid uppritning.
  • Bit 0-13, Index för vilket block som ska användas i VCN-filen.

Vi börjar med att skapa en klass som kan läsa in den första informationen om bakgrunden.

VmpFile.cs
namespace PakExtract
{
    class VmpFile
    {
        public int Header { get; set; }
        public int[,] BackgroundTiles { get; set; }
        public int[] Padding { get; set; }

        private VmpFile(byte[] data)
        {
            int index = 0;
            Header = data[index++] + (data[index++] << 8);
            BackgroundTiles = new int[22, 15];
            Padding = new int[101];

            for(int y=0; y<15; y++)
                for(int x=0; x<22; x++)
                    BackgroundTiles[x, y] = data[index++] + (data[index++] << 8);

            for(int i=0; i<101; i++)
                Padding[i] = data[index++] + (data[index++] << 8);
        }

        public static VmpFile FromData(byte[] data)
        {
            return new VmpFile(data);
        }
    }
}

Avslutning

Givet den information vi nu har så gör vi en testklass som försöker rendera bakgrunden.

BlockBuilder.cs
using System.Diagnostics;
using System.Drawing;

namespace PakExtract
{
    class BlockBuilder
    {
        public static Bitmap DrawBackdrop(VcnFile vcn, VmpFile vmp, PaletteFile pal)
        {
            Bitmap bmp = new Bitmap(320, 200);

            for (int y = 0; y < 15; y++)
                for (int x = 0; x < 22; x++)
                {
                    var tile = vmp.BackgroundTiles[x, y];
                    bool isLimit = (tile & 0b10000000_00000000) > 0;
                    bool isFlipped = (tile & 0b01000000_00000000) >0;

                    if (isLimit || isFlipped)
                        Debug.WriteLine(isLimit + " " + isFlipped);

                    var block = vcn.Blocks[tile & 0x3fff];
                    for (int y2 = 0; y2 < 8; y2++)
                        for (int x2 = 0; x2 < 8; x2++)
                        {
                            var val = block[x2, y2];
                            var col = pal.Colors[vcn.BackdropLookup[val]];
                            bmp.SetPixel(x * 8 + x2, y * 8 + y2, col);
                        }
                }

            return bmp;
        }
    }
}

Det blir lyckat. Bilderna som är 22×15 = 176×120 pixlar kan du se nedan.

BRICK.VMP

BLUE.VMP

GREEN.VMP

Hoppas du tyckte det var intressant. I nästa artikel ska vi försöka återskapa väggarna!

Nästa artikel

EotB - Rendera väggar

Uppritning av alla väggtyper.

Scroll to top