Ultima I Town Map Format
Format type | Map/level |
---|---|
Map type | 2D cell-based |
Layer count | 1 |
Tile size (pixels) | 8×8 |
Cell dimensions | 38×18 |
Viewport (cells) | 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 cells wide and 18 cells 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[10][684] | Maps | Map data for 10 maps, with 38×18 maps, storing one byte per cell. |
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!)