From b8a05f8648738c90bac2955ec3c170e6a9012424 Mon Sep 17 00:00:00 2001 From: Luke Hubmayer-Werner Date: Fri, 23 Mar 2018 20:11:45 +1030 Subject: [PATCH] World Map generation --- ff5reader.py | 97 ++++++++++++++++++++++++++++++++++++++++++++++++---- snestile.py | 41 ++++++++++++++-------- 2 files changed, 118 insertions(+), 20 deletions(-) diff --git a/ff5reader.py b/ff5reader.py index 6116f4d..4e5bfba 100755 --- a/ff5reader.py +++ b/ff5reader.py @@ -8,7 +8,7 @@ import os from struct import unpack from itertools import chain from array import array -from snestile import generate_glyphs, generate_glyphs_large, generate_palette, create_tile, create_tile_indexed, create_tile_mode7_compressed +from snestile import generate_glyphs, generate_glyphs_large, generate_palette, create_tile, create_tile_indexed, create_tile_mode7_compressed, create_tile_mode7_compressed_indexed from snestile import Canvas, Canvas_Indexed from snestile import bg_color, bg_trans import const @@ -209,9 +209,16 @@ class FF5Reader(QMainWindow): perfcount() print('Generating map tiles') - worldmap_tiles = make_world_map_tiles(ROM_jp, 0x1B8000, 0x0FF9C0, 0x0FFCC0) - worldmap_tiles += make_world_map_tiles(ROM_jp, 0x1BA000, 0x0FFAC0, 0x0FFDC0) - worldmap_tiles += make_world_map_tiles(ROM_jp, 0x1BC000, 0x0FFBC0, 0x0FFEC0, length=128) + world_tiles = [make_worldmap_tiles(ROM_jp, 0x0FF0C0+(i*0x300), 0x1B8000+(i*0x2000), 0x0FF9C0+(i*0x100), length=l) for i, l in enumerate([256, 256, 256])] + worldpixmaps = [make_worldmap_pixmap(ROM_jp, 0, 0x0FFCC0+(0*0x100), world_tiles[0]), + make_worldmap_pixmap(ROM_jp, 1, 0x0FFCC0+(1*0x100), world_tiles[1]), + make_worldmap_pixmap(ROM_jp, 2, 0x0FFCC0+(0*0x100), world_tiles[0]), + make_worldmap_pixmap(ROM_jp, 3, 0x0FFCC0+(2*0x100), world_tiles[2]), + make_worldmap_pixmap(ROM_jp, 4, 0x0FFCC0+(2*0x100), world_tiles[2])] + + worldmap_tiles = make_worldmap_subtiles_pixmap(ROM_jp, 0x1B8000, 0x0FF9C0, 0x0FFCC0) + worldmap_tiles += make_worldmap_subtiles_pixmap(ROM_jp, 0x1BA000, 0x0FFAC0, 0x0FFDC0) + worldmap_tiles += make_worldmap_subtiles_pixmap(ROM_jp, 0x1BC000, 0x0FFBC0, 0x0FFEC0, length=128) perfcount() field_tiles = make_all_field_tiles(ROM_jp) field_minitiles = make_all_field_minitiles(ROM_jp) @@ -257,6 +264,7 @@ class FF5Reader(QMainWindow): sprites_tab.addTab(make_pixmap_table(glyph_sprites_jp_large, scale=2), 'Glyphs (Large JP)') sprites_tab.addTab(make_pixmap_table(glyph_sprites_kanji, scale=2), 'Glyphs (Kanji)') sprites_tab.addTab(make_pixmap_table(worldmap_tiles, cols=16, scale=4), 'Worldmap Tiles') + sprites_tab.addTab(make_pixmap_table(worldpixmaps, cols=1, scale=1, large=True), 'Worldmaps') sprites_tab.addTab(make_pixmap_table(fieldmap_tiles, cols=8, scale=2), 'Fieldmap Tiles') sprites_tab.addTab(make_pixmap_table(self.battle_strips, cols=22, scale=2), 'Character Battle Sprites') sprites_tab.addTab(make_pixmap_table(status_strips, cols=22, scale=2), 'Status Sprites') @@ -361,7 +369,27 @@ def make_enemy_sprites(rom): return sprites -def make_world_map_tiles(rom, tiles_address, lut_address, palette_address, length=0x100): +def make_worldmap_subtiles(rom, tiles_address, lut_address, length=0x100): + subtiles = [] + for i in range(length): + pal_index = rom[lut_address+i]//16 + subtiles.append(create_tile_mode7_compressed_indexed(rom[tiles_address+i*32:tiles_address+i*32+32], pal_index)) + return subtiles + +def stitch_worldmap_tiles(rom, subtiles, offset=0x0FF0C0): + tiles = [] + for i in range(0xC0): + canvas = Canvas_Indexed(2, 2) + for j in range(4): + k = indirect(rom, offset+(j*0xC0)+i, length=1) + canvas.draw_tile(j%2, j//2, subtiles[k]) + tiles.append(canvas.image) + return tiles + +def make_worldmap_tiles(rom, tiles_address, subtiles_address, lut_address, length=0x100): + return stitch_worldmap_tiles(rom, make_worldmap_subtiles(rom, subtiles_address, lut_address, length=length), tiles_address) + +def make_worldmap_subtiles_pixmap(rom, tiles_address, lut_address, palette_address, length=0x100): tiles = [] palettes = [generate_palette(rom, palette_address+i*32, transparent=True) for i in range(16)] for i in range(length): @@ -369,6 +397,44 @@ def make_world_map_tiles(rom, tiles_address, lut_address, palette_address, lengt tiles.append(create_tile_mode7_compressed(rom[tiles_address+i*32:tiles_address+i*32+32], palette)) return tiles +def make_worldmap_chunk(rom, id, length=256): + i = indirect(rom, 0x0FE000+(id*2)) + 0x070000 + if id > 0x433: + i += 0x010000 + mountains = [0x0C, 0x1C, 0x2C] + chunk = [] + while len(chunk) < length: + j = indirect(rom, i, 1) + if j >= 0xC0: + k = j-0xBF + i += 1 + j = indirect(rom, i, 1) + chunk += [j]*k + elif j in mountains: + chunk += [j, j+1, j+2] + else: + chunk.append(j) + i += 1 + return chunk + +def make_worldmap_chunk_pixmap(rom, id, palette_address, tiles): + chunk = make_worldmap_chunk(rom, id) + palette = generate_palette(rom, palette_address, length=0x320, transparent=True) + canvas = Canvas_Indexed(len(chunk), 1, tilesize=16) + for i, c in enumerate(chunk): + canvas.draw_tile(i, 0, tiles[c]) + return canvas.pixmap(palette) + +def make_worldmap_pixmap(rom, map_id, palette_address, tiles): + id_offset = map_id*256 + palette = generate_palette(rom, palette_address, length=0x320, transparent=True) + canvas = Canvas_Indexed(256, 256, tilesize=16) + for j in range(256): + chunk = make_worldmap_chunk(rom, j+id_offset) + for i, c in enumerate(chunk): + canvas.draw_tile(i, j, tiles[c]) + return canvas.pixmap(palette) + def make_field_tiles(rom, id): tiles_address = indirect(rom, 0x1C2D84 + id*4, length=4) + 0x1C2E24 return [create_tile_indexed(rom[tiles_address+i*32:tiles_address+i*32+32]) for i in range(256)] @@ -433,6 +499,22 @@ def make_battle_strip(rom, palette_address, tile_address, num_tiles, bpp=4): battle_strip.draw_pixmap(j%2, j//2, create_tile(rom[offset:offset+b], palette)) return battle_strip.pixmap() +def get_zone_tiles_start(rom, id): + i1 = indirect(rom, 0x0E59C0+(id*2))+7 + i2 = indirect(rom, 0x0E59C2+i1, 1) + # There is a divergent path here based on the value. Other things appear to be affected by this. + if i2 > 0x67: + i3 = ((i2 - 0x67) << 11) + 0x1A0000 + elif i2 > 0x52: + i3 = ((i2 - 0x52) << 9) + 0x1A0000 + elif i2 > 0x4B: + i3 = ((i2 - 0x4B) << 11) + 0x1AC800 + elif i2 > 0x32: + i3 = ((i2 - 0x32) << 10) + 0x1A0000 + else: + i3 = (i2 << 9) + 0x1A0000 + return i3 + def make_character_battle_sprites_ff4(rom): tile_address = 0xD0000 @@ -761,11 +843,14 @@ def make_table(headers, items, sortable=False, row_labels=True, scale=2): return table -def make_pixmap_table(items, cols=16, scale=4): +def make_pixmap_table(items, cols=16, scale=4, large=False): rows = divceil(len(items), cols) rd = hex_length(rows-1)+1 cd = hex_length(cols-1) table = QTableWidget(rows, cols) + if large: + table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) + table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) table.setVerticalHeaderLabels([hex(v*cols, rd) for v in range(rows)]) table.setHorizontalHeaderLabels([hex(v, cd) for v in range(cols)]) for i, item in enumerate(items): diff --git a/snestile.py b/snestile.py index 4e54169..eca6a54 100644 --- a/snestile.py +++ b/snestile.py @@ -60,6 +60,23 @@ def create_tile_indexed(data): imgbits[:64] = tile return img +def create_tile_mode7_indexed(data): + # Each byte is a pixel. 8bit palette. + tile = array('B', range(64)) + tile = data[:64] + img = QImage(8, 8, QImage.Format_Indexed8) + imgbits = img.bits() + imgbits.setsize(img.byteCount()) + imgbits[:64] = tile + return img + +def create_tile_mode7_compressed_indexed(data, pal_index): + # Each byte is two pixels i.e. 0xEF is Mode 7 0xF 0xE + # Palette is externally determined by LUT, to be padded to the first hex digit + pal = pal_index << 4 + newdata = b''.join([bytes([pal|(j%16), pal|(j//16)]) for j in data]) + return create_tile_mode7_indexed(newdata) + def create_tile(data, palette=[0x00000080, 0xFFFFFFFF]): ''' Creates a QPixmap of a SNES tile. DO NOT USE OUTSIDE OF QApplication CONTEXT @@ -70,13 +87,8 @@ def create_tile(data, palette=[0x00000080, 0xFFFFFFFF]): def create_tile_mode7(data, palette): # Each byte is a pixel. 8bit palette. - tile = array('B', range(64)) - tile = data[:64] - img = QImage(8, 8, QImage.Format_Indexed8) + img = create_tile_mode7_indexed(data) img.setColorTable(palette) - imgbits = img.bits() - imgbits.setsize(img.byteCount()) - imgbits[:64] = tile return QPixmap.fromImage(img) def create_tile_mode7_compressed(data, palette): @@ -212,9 +224,10 @@ class Canvas: class Canvas_Indexed: - def __init__(self, cols, rows, color=0): - self.image = QImage(8*cols, 8*rows, QImage.Format_Indexed8) - self.width = 8*cols + def __init__(self, cols, rows, color=0, tilesize=8): + self.image = QImage(tilesize*cols, tilesize*rows, QImage.Format_Indexed8) + self.width = tilesize*cols + self.tilesize = tilesize self.image.fill(0) self.imgbits = self.image.bits() self.imgbits.setsize(self.image.byteCount()) @@ -224,18 +237,18 @@ class Canvas_Indexed: def draw_tile(self, col, row, image): imgbits = image.bits() imgbits.setsize(image.byteCount()) - x = col*8 - y = row*8 + x = col*self.tilesize + y = row*self.tilesize start = x + y*self.width - for i in range(8): + for i in range(self.tilesize): offset = i*self.width - self.imgbits[start+offset:start+offset+8] = imgbits[i*8:i*8+8] + self.imgbits[start+offset:start+offset+self.tilesize] = imgbits[i*self.tilesize:(i+1)*self.tilesize] self.max_col = max(col, self.max_col) self.max_row = max(row, self.max_row) def pixmap(self, palette, trim=False): if trim: - img = self.image.copy(0, 0, self.max_col*8+8, self.max_row*8+8) + img = self.image.copy(0, 0, (self.max_col+1)*self.tilesize, (self.max_row+1)*self.tilesize) img.setColorTable(palette) return QPixmap.fromImage(img) self.image.setColorTable(palette)