Ultima I Town Map Format

From ModdingWiki
Jump to: navigation, search
Ultima I Town Map Format
Ultima I Town Map Format.png
Format typeMap/level
Map type2D tile-based
Layer count1
Tile size (pixels)8×8
Viewport (pixels)38×18
Games

Ultima I stores the maps for all of its towns and castles in a single file called tcd.bin. Each map is displayed in its entirety on the screen. The map data contains a lookup for which town tiles should be displayed at each location as well as which hidden floor tiles are used so the game knows which shop you're in. It does not store NPCs or where the player enters the map, these are determined by the EXE.

File Format

There are 10 maps in the file, each map is 38 tiles wide and 18 tiles tall (684 bytes). The map data is stored sideways (top-to-bottom, left-to-right). Each byte represents a square on the map. If the value is between 0x00 and 0x33, the number represents a lookup id in the CGATown.bin or EGATown.bin file and the tile is drawn as a graphic. If the value is 0x34 or greater, the tile is drawn as a blank floor, but the value tells the game what type of shop the player is in for when you transact.

Data type Name Description
BYTE[684] tile Map data.
... 10 maps.

Tile Data

This is a lookup of all the values that the map can store.

Id Tile
0x00 Solid Wall
0x01 Floor
0x02 Water
0x03 Water Corner - Top-Left
0x04 Water Corner - Bottom-Left
0x05 Water Corner - Top-Right
0x06 Water Corner - Bottom-Right
0x07 Water Diagonal - Bottom-Right
0x08 Water Diagonal - Bottom-Left
0x09 Tree - Small
0x0A Tree - Big
0x0B Counter - Left-Right
0x0C Counter - Top-Bottom
0x0D Counter - Top
0x0E Counter - Bottom
0x0F Counter - Left
0x10 Counter - Right
0x11 Person - Guard
0x12 Person - Player
0x13 Person - Jester 1
0x14 Person - King
0x15 Person - Merchant
0x16 Person - Prisoner
0x17 Brick Wall
0x18-0x31 Letters A-Z
0x32 Person - Jester 2
0x33 Floor - Water Floor 1
0x34 Floor - Water Floor 2
0x35 Floor - Water Floor 3
0x36 Floor - Armoury 1
0x37 Floor - Armoury 2
0x38 Floor - Grocery 1
0x39 Floor - Grocery 2
0x3A Floor - Weaponry 1
0x3B Floor - Weaponry 2
0x3C Floor - Magic / Prison Cell 1
0x3D Floor - Pub or Inn / Prison Cell 2
0x3E Floor - Transportation / Throne Room
0x3F Floor - Between Grocery and Pub
  • Values 0x11-0x16 are not used in any map files because the maps don't store NPC locations. They are added to the maps at run-time.
  • Value 0x3F is only used in in the map for Moon/Linda/Dextron. Perhaps this is a bug?

Source Code

Town Viewer

This FreeBASIC program displays all of the maps in the game with their appropriate tiles.

' Draws the Towns of Ultima 1 in CGA and EGA mode.
#include once "fbgfx.bi"
 
Dim As fb.Image Ptr TileCGA(0 To 50)
Dim As fb.Image Ptr TileEGA(0 To 50)
 
Dim As UByte X
Dim As UByte Y
Dim As UByte Pixel
Dim As UByte TileNumber
Dim As UShort OffsetX
Dim As UShort OffsetY
Dim As UByte Offset
 
' === Load CGA Tiles ===
Screen 1
Color 0, 1      ' Low intensity cyan/magenta/white palette.
 
Dim As UByte Pixels
Dim As UByte ColorBlock
 
Open "H:\DOS\Ultima\CGATown.bin" For Binary As #1
 
OffsetX = 0
OffsetY = 0
For TileNumber = 0 To 50
    For Y = 0 To 7
        For X = 0 To 1
            Get #1, , Pixels
 
            ' Determine the four pixels from this one byte.
            ColorBlock = 7
            For Offset = 0 To 3
                If Bit(Pixels, ColorBlock) = -1 Then
                    If Bit(Pixels, ColorBlock - 1) = -1 Then
                        Pixel = 3
                    Else
                        Pixel = 2
                    End If
                Else
                    If Bit(Pixels, ColorBlock - 1) = -1 Then
                        Pixel = 1
                    Else
                        Pixel = 0
                    End If
                End If
 
                ' Plot the pixel.
                PSet((X * 4) + Offset, Y), Pixel
                ColorBlock = ColorBlock - 2
            Next Offset
        Next X
    Next Y
 
    TileCGA(TileNumber) = ImageCreate(8, 8)
    Get (0, 0)-(7, 7), TileCGA(TileNumber)
Next TileNumber
 
Close #1
 
' === Load EGA Tiles ===
Screen 7
 
Open "H:\DOS\Ultima\EGATown.bin" For Binary As #1
 
Dim As UByte Blue
Dim As UByte Green
Dim As UByte Red
Dim As UByte Intensity
 
OffsetX = 0
OffsetY = 0
For TileNumber = 0 To 50
    For Y = 0 To 7
        ' The EGA file stores a row of 8 pixels per 4 bytes.
	' The byte breakdown is: Blue, Green, Red, Intensity
 
        Get #1, , Blue
        Get #1, , Green
        Get #1, , Red
        Get #1, , Intensity
 
        Offset = 7
		For X = 0 To 7
			Pixel = 0
			If Bit(Blue, Offset) = -1 Then
				Pixel = Pixel + 1
			End If
			If Bit(Green, Offset) = -1 Then
				Pixel = Pixel + 2
			End If
			If Bit(Red, Offset) = -1 Then
				Pixel = Pixel + 4
			End If
			If Bit(Intensity, Offset) = -1 Then
				Pixel = Pixel + 8
			End If
 
			PSet(X, Y), Pixel
 
            Offset = Offset - 1
        Next X
    Next Y
 
    TileEGA(TileNumber) = ImageCreate(8, 8)
    Get (0, 0)-(7, 7), TileEGA(TileNumber)
Next TileNumber
 
Close #1
 
' === Draw All Maps ===
Dim As UByte TileId
Dim As UByte Maps
Dim As UByte Display
 
For Display = 0 To 1
    If Display = 0 Then
        Screen 1            ' CGA
        Color 0, 1
    Else
        Screen 7            ' EGA
    End If
 
    Open "H:\DOS\Ultima\TCD.bin" For Binary As #1
 
    For Maps = 0 To 9
        CLS
        For X = 0 To 37
            For Y = 0 To 17
                Get #1, , TileId
 
                If TileId < 50 Then
                    If Display = 0 Then
                        Put (X * 8 + 8, Y * 8 + 8), TileCGA(TileId)
                    Else
                        Put (X * 8 + 8, Y * 8 + 8), TileEGA(TileId)
                    End If
                Else
                    If Display = 0 Then
                        Put (X * 8 + 8, Y * 8 + 8), TileCGA(1)
                    Else
                        Put (X * 8 + 8, Y * 8 + 8), TileEGA(1)
                    End If
                End If
            Next Y
        Next X
 
        Sleep
    Next Maps
 
    Close #1
Next Display

Credits

This map format was reverse engineered by TheAlmightyGuru. If you find this information helpful in a project you're working on, please give credit where credit is due. (A link back to this wiki would be nice too!)