Ultima II Map Format

From ModdingWiki
Jump to navigation Jump to search
Ultima II Map Format
Ultima II Map Format.png
Format typeMap/level
Map type2D cell-based
Layer count1
Tile size (pixels)16×16
Viewport (pixels)320×160
Cell dimensions64×66 (for planets and towns)
Games

Ultima II: Revenge of the Enchantress uses dozens of maps for planet overworlds and towns. Those maps with numbers ending in 0 are planets, those ending in 1, 2, or 3 are towns, those ending in 4 or 5 are dungeons. Planet and town maps are 64×66 cells, which yields 1,024×1,056 pixels. Dialogue from NPCs is stored in separate files in the Ultima II Talk Format.

The tiles used to draw the map cells are stored in the ULTIMAII.EXE file. Interestingly, the game rewrites the map files as the game runs, saving the location of NPCs and monsters directly in the map files.

File Format

For planets and towns, the map data is a string of 4,224 bytes where each byte represents a tile id. The values in the map match up with the tiles in the ULTIMAII.EXE, only they're all multiplied by 4, so, the tile id from the map file must be divided by 4 to get the correct tile in the tile lookup.

Data type Name Description
UINT8[4,224] Tile Id The tile id for each tile in the map.

Tile Data

Remember that the maps store the tile id ×4. So, a mountain, which is tile id 4, will be stored as 16 (0x10).

Tile Id Tile Description
0 Water
1 Swamp
2 Grass
3 Forest
4 Mountain
5 ?
6 Town
7 Tower
8 Castle
9 Dungeon Entrance
10 Signpost
11 Sea Monster
12 Orc
13 Daemon
14 Devil
15 Balron
16 Minax
17 Horse
18 Ship
19 Airplane
20 Rocket
21 Shield
22 Sword
23 Forcefield
24 Guard
25 Jester
26 Shopkeep
27 ?
28 Road
29 Empty
30 Wall
31 Empty Counter / Space
32-39 A-H
40 I / Door
41-47 J-P
48 Moongate
49-57 R-Z
58 Counter End, Right
59 Counter End, Left
60 Fighter
61 Cleric
62 Mage
63 Thief

The map file does not store which specific NPC, castle, town, dungeon, or signpost is at which location.

Source Code

Map Viewer

This FreeBASIC program displays game maps with the proper tile set.

' Renders the specified Ultima II map.
#include once "fbgfx.bi"

' Change to your Ultima II path, and set the map file you want to view.
Dim As String DataPath = "H:\DOS\Ultima2"
Dim As String MapFile = "mapx20"

Dim As fb.Image Ptr TileCGA(0 To 63)

Dim As UByte X
Dim As UByte Y
Dim As Integer Pixel
Dim As UByte Pixels
Dim As UByte TileNumber
Dim As UShort OffsetX = 0
Dim As UShort OffsetY = 0
Dim As UByte Offset
Dim As UByte ColorBlock

' Graphic tile data is stored in the EXE.
Open DataPath + Chr(92) + "ultimaii.exe" For Binary As #1
Seek 1, 31811      ' Jump to the start of the over world tiles.

ScreenRes 1024, 1056, 32

' Load all of the tiles into memory.
For TileNumber = 0 To 63
    For Y = 0 To 15
        For X = 0 To 3
            ' CGA Linear stores 4 pixels per byte. Bits 7 and 6 determine the 
            ' first color, bits 5 and 4 determined the next color, and so on.
            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 = RGB(170, 170, 170)
                    Else
                        Pixel = RGB(170, 0, 170)
                    End If
                Else
                    If Bit(Pixels, ColorBlock - 1) = -1 Then
                        Pixel = RGB(0, 170, 170)
                    Else
                        Pixel = RGB(0, 0, 0)
                    End If
                End If
    
                ' Plot the pixel.
                PSet((X * 4) + Offset, Y), Pixel
                ColorBlock = ColorBlock - 2
            Next Offset
        Next X
    Next Y

    Get #1, , Pixels
    Get #1, , Pixels

    ' Load the tile into an image buffer.
    TileCGA(TileNumber) = ImageCreate(16, 16)
    Get (0, 0)-(15, 15), TileCGA(TileNumber)
Next TileNumber

Close #1

' Open the map file.
CLS
Open DataPath + Chr(92) + MapFile For Binary As #1

' Load the entire map, and draw the appropriate tiles.
For Y = 0 To 65
    For X = 0 To 63
        Get #1, , TileNumber
        
        TileNumber = TileNumber / 4
        
        Put (X * 16, Y * 16), TileCGA(TileNumber)
    Next X
Next Y

Close #1
Sleep

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!)