Animated Battle BGs
This commit is contained in:
parent
69306daf92
commit
7fe8fb7351
36
ff5reader.py
36
ff5reader.py
|
@ -110,9 +110,6 @@ with open(filename_jp_ff6, 'rb') as file:
|
|||
ROM_FF6jp = file.read()
|
||||
print(len(ROM_FF6jp), filename_jp_ff6)
|
||||
|
||||
stringlist_headers = ['Address', 'ID', 'Name']
|
||||
imglist_headers = stringlist_headers + ['Img', 'Name JP', 'Img JP']
|
||||
|
||||
|
||||
class FF5Reader(QMainWindow):
|
||||
'''
|
||||
|
@ -120,7 +117,6 @@ class FF5Reader(QMainWindow):
|
|||
'''
|
||||
def __init__(self):
|
||||
QMainWindow.__init__(self, None)
|
||||
global glyph_sprites_en_large, glyph_sprites_en_small, glyph_sprites_jp_small, glyph_sprites_jp_large, glyph_sprites_kanji, glyph_sprites_jp_dialogue
|
||||
perfcount()
|
||||
print('Generating Glyphs')
|
||||
self.glyph_sprites = {
|
||||
|
@ -133,6 +129,9 @@ class FF5Reader(QMainWindow):
|
|||
make_string_img_list = functools.partial(_make_string_img_list, **self.glyph_sprites)
|
||||
perfcount()
|
||||
|
||||
stringlist_headers = ['Address', 'ID', 'Name']
|
||||
imglist_headers = stringlist_headers + ['Img', 'Name JP', 'Img JP']
|
||||
|
||||
print('Generating Strings')
|
||||
zone_names = make_string_img_list(0x107000, 2, 0x100, start_str=0x270000, start_jp_str=0x107200, indirect=True, large=True)
|
||||
items = make_string_img_list(0x111380, 9, 256)
|
||||
|
@ -180,14 +179,14 @@ class FF5Reader(QMainWindow):
|
|||
zone_headers = ['Address'] + [z[0] for z in zone_structure]
|
||||
zone_data = [parse_struct(ROM_en, 0x0E9C00 + (i*0x1A), zone_structure) for i in range(const.zone_count)]
|
||||
|
||||
battle_bg_structure = [('ImageID', 1, None),
|
||||
('ColorID 1', 1, None),
|
||||
('ColorID 2', 1, None),
|
||||
('TerrainID', 1, None),
|
||||
('TerrainFlipID', 1, None),
|
||||
(hex(5, 1), 1, None),
|
||||
('AnimationID', 1, None),
|
||||
('PaletteCycleID',1, None),]
|
||||
battle_bg_structure = [('Tileset', 1, None),
|
||||
('Palette 1', 1, None),
|
||||
('Palette 2', 1, None),
|
||||
('Tilemap', 1, None),
|
||||
('TilemapFlip', 1, None),
|
||||
(hex(5, 1), 1, None),
|
||||
('Animation', 1, None),
|
||||
('PaletteCycle',1, None),]
|
||||
battle_bg_headers = ['Address'] + [z[0] for z in battle_bg_structure]
|
||||
battle_bg_data = [parse_struct(ROM_jp, 0x14BA21 + (i*8), battle_bg_structure) for i in range(34)]
|
||||
|
||||
|
@ -223,9 +222,7 @@ class FF5Reader(QMainWindow):
|
|||
|
||||
perfcount()
|
||||
print('Generating map tiles')
|
||||
|
||||
worldmap_palettes = [generate_palette(ROM_jp, 0x0FFCC0+(i*0x100), length=0x160, transparent=True) for i in range(3)]
|
||||
|
||||
world_tiles = [make_worldmap_tiles(ROM_jp, 0x0FF0C0+(i*0x300), 0x1B8000+(i*0x2000), 0x0FF9C0+(i*0x100)) for i in range(3)]
|
||||
worldpixmaps = [make_worldmap_pixmap(ROM_jp, i, 0x0FFCC0+(t*0x100), world_tiles[t]) for i, t in enumerate([0, 1, 0, 2, 2])]
|
||||
world_tiles_pixmaps = []
|
||||
|
@ -267,6 +264,7 @@ class FF5Reader(QMainWindow):
|
|||
perfcount()
|
||||
|
||||
|
||||
print('Creating Qt Widgets')
|
||||
self.gamewidget = QTabWidget()
|
||||
self.ff4widget = QTabWidget()
|
||||
self.ff5widget = QTabWidget()
|
||||
|
@ -311,8 +309,8 @@ class FF5Reader(QMainWindow):
|
|||
structs_tab.addTab(make_table(enemy_sprite_headers, enemy_sprite_data, True), 'Enemy Sprites')
|
||||
|
||||
strings_tab.addTab(make_table(imglist_headers, items, row_labels=False), 'Items')
|
||||
strings_tab.addTab(make_table(imglist_headers, magics, row_labels=False), 'Magics')
|
||||
strings_tab.addTab(make_table(imglist_headers, more_magics, row_labels=False), 'More Magics')
|
||||
strings_tab.addTab(make_table(imglist_headers, magics+more_magics, row_labels=False), 'Magics')
|
||||
#strings_tab.addTab(make_table(imglist_headers, more_magics, row_labels=False), 'More Magics')
|
||||
strings_tab.addTab(make_table(imglist_headers, enemy_names, row_labels=False), 'Enemy Names')
|
||||
strings_tab.addTab(make_table(imglist_headers, character_names, row_labels=False), 'Character Names')
|
||||
strings_tab.addTab(make_table(imglist_headers, job_names, row_labels=False), 'Job Names')
|
||||
|
@ -336,6 +334,7 @@ class FF5Reader(QMainWindow):
|
|||
self.main_widget.setMinimumSize(800,600)
|
||||
self.setCentralWidget(self.main_widget)
|
||||
self.show()
|
||||
perfcount()
|
||||
|
||||
def _string_decode(self):
|
||||
string = ''.join(self.decoder_input.text().split())
|
||||
|
@ -349,7 +348,10 @@ class FF5Reader(QMainWindow):
|
|||
self.decoder_layout.addWidget(img)
|
||||
self.decoder_input.setText('')
|
||||
|
||||
|
||||
'''
|
||||
The painting logic here needs to be moved into includes.snestile at some point.
|
||||
Once that is done, these functions will be moved to includes.snes which should not have qt dependencies.
|
||||
'''
|
||||
def make_string_img_small(bytestring, glyphs, jp=False):
|
||||
'''
|
||||
JP version is not as fancy as this with dakuten, it just puts them on the row above and clips.
|
||||
|
|
|
@ -82,6 +82,31 @@ if not monofont.fixedPitch():
|
|||
monofont.setFamily('Monospace')
|
||||
|
||||
|
||||
class Label(QLabel):
|
||||
def __init__(self, *kwargs):
|
||||
super().__init__(*kwargs)
|
||||
self.pixmaps = []
|
||||
self.pixmap_index = 0
|
||||
self.timer = QtCore.QTimer()
|
||||
self.timer.timeout.connect(self._cycle_pixmap)
|
||||
|
||||
def setContent(self, content, scale=1, strip=True):
|
||||
if isinstance(content, QPixmap):
|
||||
self.setPixmap(content.scaled(content.size() * scale))
|
||||
elif isinstance(content, list) and isinstance(content[0], QPixmap):
|
||||
self.pixmaps = [c.scaled(c.size() * scale) for c in content[:-1]]
|
||||
self.setPixmap(self.pixmaps[0])
|
||||
self.timer.start(content[-1]*1000/60)
|
||||
else:
|
||||
if strip:
|
||||
content = content.strip()
|
||||
self.setText(content)
|
||||
|
||||
def _cycle_pixmap(self):
|
||||
self.pixmap_index = (self.pixmap_index+1)%len(self.pixmaps)
|
||||
self.setPixmap(self.pixmaps[self.pixmap_index])
|
||||
|
||||
|
||||
def table_size_to_contents(table):
|
||||
# Stupid hack to get table to size correctly
|
||||
table.hide()
|
||||
|
@ -144,9 +169,9 @@ def make_pixmap_table(items, cols=16, scale=4, large=False):
|
|||
for i, item in enumerate(items):
|
||||
if isinstance(item, QWidget):
|
||||
table.setCellWidget(i // cols, i % cols, item)
|
||||
elif isinstance(item, QPixmap):
|
||||
lab = QLabel()
|
||||
lab.setPixmap(item.scaled(item.size() * scale))
|
||||
else:
|
||||
lab = Label()
|
||||
lab.setContent(item, scale=scale)
|
||||
lab.setAlignment(QtCore.Qt.AlignCenter)
|
||||
table.setCellWidget(i // cols, i % cols, lab)
|
||||
table_size_to_contents(table)
|
||||
|
@ -161,11 +186,8 @@ def stack_labels(*items):
|
|||
l.setSpacing(0)
|
||||
l.setContentsMargins(0, 0, 0, 0)
|
||||
for item in items:
|
||||
lab = QLabel()
|
||||
if isinstance(item, QPixmap):
|
||||
lab.setPixmap(item)
|
||||
else:
|
||||
lab.setText(item.strip())
|
||||
lab = Label()
|
||||
lab.setContent(item)
|
||||
lab.setAlignment(QtCore.Qt.AlignCenter)
|
||||
lab.setMargin(0)
|
||||
l.addWidget(lab)
|
||||
|
|
138
includes/snes.py
138
includes/snes.py
|
@ -214,7 +214,7 @@ def make_field_map_tile_pixmap(rom, id, st_tiles, st_minitiles):
|
|||
canvas.draw_pixmap(0, 48, st_minitiles[minitile].pixmap(p))
|
||||
return canvas.pixmap()
|
||||
|
||||
def decompress_battle_terrain(rom, address):
|
||||
def decompress_battle_tilemap(rom, address):
|
||||
'''
|
||||
Decompresses the tilemap for a battle background.
|
||||
Battle BGs use a type of RLE with 2byte repeat and 1byte incremental repeat.
|
||||
|
@ -248,7 +248,7 @@ def decompress_battle_terrain(rom, address):
|
|||
output[1::2] = [i&0xDF for i in o2[:length//2]]
|
||||
return bytes(output)
|
||||
|
||||
def apply_battle_terrain_flips(rom, id, battle_terrain):
|
||||
def apply_battle_tilemap_flips(rom, id, battle_terrain):
|
||||
if id==0xFF:
|
||||
return battle_terrain
|
||||
ptr = indirect(rom, 0x14C736+(id*2))+0x140000
|
||||
|
@ -271,7 +271,30 @@ def apply_battle_terrain_flips(rom, id, battle_terrain):
|
|||
output[i*2+1] |= (buffer[i] << 6)
|
||||
return bytes(output)
|
||||
|
||||
def make_tilemap_pixmap(tilemap, tiles, palettes, tile_adjust=0):
|
||||
def make_tilemap_canvas(tilemap, tiles, tile_adjust=0, pal_adjust=-1):
|
||||
'''
|
||||
Battle bg is 64x64 map size, 8x8 tile size
|
||||
4bpp tiles
|
||||
'''
|
||||
canvas = Canvas_Indexed(64, 64)
|
||||
for i in range(len(tilemap)//2):
|
||||
a, b = tilemap[i*2:(i+1)*2]
|
||||
tile_index = a|((b & 0x02) << 8)
|
||||
p = (b & 0x1C) >> 2
|
||||
priority = (b & 0x20) >> 5
|
||||
h_flip = (b & 0x40) >> 6
|
||||
v_flip = (b & 0x80) >> 7
|
||||
|
||||
x = (i % 32) + 32*((i//1024) % 2)
|
||||
y = (i //32) - 32*((i//1024) % 2)
|
||||
try:
|
||||
tile = tiles[(tile_index+tile_adjust)%0x80]
|
||||
canvas.draw_tile(x, y, tile, h_flip, v_flip, p+pal_adjust)
|
||||
except BaseException as e:
|
||||
print(e, p, hex(tile_index,2), hex(tile_adjust,2), hex(tile_index+tile_adjust,2))
|
||||
return canvas
|
||||
|
||||
def make_tilemap_pixmap(tilemap, tiles, palettes, tile_adjust=0, pal_adjust=-1):
|
||||
'''
|
||||
Battle bg is 64x64 map size, 8x8 tile size
|
||||
4bpp tiles
|
||||
|
@ -288,7 +311,7 @@ def make_tilemap_pixmap(tilemap, tiles, palettes, tile_adjust=0):
|
|||
x = (i % 32) + 32*((i//1024) % 2)
|
||||
y = (i //32) - 32*((i//1024) % 2)
|
||||
try:
|
||||
palette = palettes[p]
|
||||
palette = palettes[p+pal_adjust]
|
||||
tile = tiles[(tile_index+tile_adjust)%0x80]
|
||||
tile.setColorTable(palette)
|
||||
tile_px = QPixmap.fromImage(tile)
|
||||
|
@ -301,45 +324,108 @@ def make_battle_backgrounds(rom):
|
|||
'''
|
||||
21 pointers in memory for the compressed data of the tilesets.
|
||||
Most of these are not unique, and only a subset of the resulting block is used.
|
||||
The block appears to get DMA'd to 0x0400 in VRAM
|
||||
|
||||
Terrain gets DMA'd to 0x2000 (size 0x500) in VRAM from 0x7f0000 in RAM
|
||||
'''
|
||||
palettes = [generate_palette(rom, 0x14BB31+(i*0x20)) for i in range(84)]
|
||||
battle_bgs = []
|
||||
for i in range(34):
|
||||
bg = {
|
||||
'tiles_id': rom[0x14BA21+(i*8)],
|
||||
'tileset_id': rom[0x14BA21+(i*8)],
|
||||
'pal1_id': rom[0x14BA22+(i*8)],
|
||||
'pal2_id': rom[0x14BA23+(i*8)],
|
||||
'terrain_id': rom[0x14BA24+(i*8)],
|
||||
'terrain_flips_id': rom[0x14BA25+(i*8)],
|
||||
'tilemap_id': rom[0x14BA24+(i*8)],
|
||||
'tilemap_flips_id': rom[0x14BA25+(i*8)],
|
||||
'tilecycle_id': rom[0x14BA27+(i*8)],
|
||||
'palcycle_id': rom[0x14BA28+(i*8)],
|
||||
}
|
||||
bg['palette'] = [palettes[0], palettes[bg['pal1_id']], palettes[bg['pal2_id']]]
|
||||
bg['palette'] = palettes[bg['pal1_id']] + palettes[bg['pal2_id']]
|
||||
battle_bgs.append(bg)
|
||||
|
||||
tiles_pointer_start = 0x184196
|
||||
tiles_RAM_pointer_start = 0x184157
|
||||
tiles_pointers = [indirect(rom, tiles_pointer_start+(i*3), length=3)-0xC00000 for i in range(21)]
|
||||
tiles_raw = [decompress_lzss(rom, p) for p in tiles_pointers]
|
||||
tiles_skips = [indirect(rom, tiles_RAM_pointer_start+(i*3), length=3)-0x7FC000 for i in range(21)]
|
||||
tiles = []
|
||||
for raw, skip in zip(tiles_raw, tiles_skips):
|
||||
tileset_pointer_start = 0x184196
|
||||
tileset_RAM_pointer_start = 0x184157
|
||||
tileset_pointers = [indirect(rom, tileset_pointer_start+(i*3), length=3)-0xC00000 for i in range(21)]
|
||||
tileset_raw = [decompress_lzss(rom, p) for p in tileset_pointers]
|
||||
tileset_skips = [indirect(rom, tileset_RAM_pointer_start+(i*3), length=3)-0x7FC000 for i in range(21)]
|
||||
tileset = []
|
||||
for raw, skip in zip(tileset_raw, tileset_skips):
|
||||
r = raw[skip:]
|
||||
tiles.append([create_tile_indexed(r[i*32:(i+1)*32]) for i in range(len(r)//32)])
|
||||
tileset.append([create_tile_indexed(r[i*32:(i+1)*32]) for i in range(len(r)//32)])
|
||||
|
||||
terrain_pointer_start = 0x14C86D
|
||||
terrain_pointers = [indirect(rom, terrain_pointer_start+(i*2))+0x140000 for i in range(28)]
|
||||
terrains = [decompress_battle_terrain(rom, p) for p in terrain_pointers]
|
||||
tilemap_pointer_start = 0x14C86D
|
||||
tilemap_pointers = [indirect(rom, tilemap_pointer_start+(i*2))+0x140000 for i in range(28)]
|
||||
tilemaps = [decompress_battle_tilemap(rom, p) for p in tilemap_pointers]
|
||||
|
||||
animation_ptr_start = 0x14C5B1
|
||||
animation_ptrs = [indirect(rom, animation_ptr_start+(i*2))+0x140000 for i in range(8)]
|
||||
animations = []
|
||||
for ptr in animation_ptrs:
|
||||
a = []
|
||||
for i in range(ptr, ptr+200):
|
||||
b = rom[i]
|
||||
if b == 0xFF:
|
||||
break
|
||||
a.append(b)
|
||||
a = [(i, j) for i, j in zip(a[0::2], a[1::2])]
|
||||
animations.append(a)
|
||||
animation_time = 15 # Frames before changing
|
||||
|
||||
pal_cycle_ptr_start = 0x14C6CD
|
||||
pal_cycle_ptrs = [indirect(rom, pal_cycle_ptr_start+(i*2))+0x140000 for i in range(3)]
|
||||
pal_cycles = []
|
||||
for ptr in pal_cycle_ptrs:
|
||||
a = []
|
||||
for i in range(ptr, ptr+100):
|
||||
b = rom[i]
|
||||
if b == 0xFF:
|
||||
break
|
||||
a.append(b)
|
||||
pal_cycles.append(a)
|
||||
|
||||
def make_pals(bg):
|
||||
p_cycle = pal_cycles[bg['palcycle_id']]
|
||||
p1 = bg['pal1_id']
|
||||
p2 = bg['pal2_id']
|
||||
pals = []
|
||||
for p in p_cycle:
|
||||
if p & 0x80:
|
||||
p2 = min(p & 0x7F, len(palettes)-1)
|
||||
else:
|
||||
p1 = min(p, len(palettes)-1)
|
||||
pals.append(palettes[p1] + palettes[p2])
|
||||
return pals
|
||||
|
||||
canvases = []
|
||||
pixmaps = []
|
||||
for bg in battle_bgs:
|
||||
terrain = apply_battle_terrain_flips(rom, bg['terrain_flips_id'], terrains[bg['terrain_id']])
|
||||
pixmaps.append(make_tilemap_pixmap(terrain, tiles[bg['tiles_id']], bg['palette']))
|
||||
#[make_tilemap_pixmap(terrains[5], tiles[2], palettes)]
|
||||
tilemap = apply_battle_tilemap_flips(rom, bg['tilemap_flips_id'], tilemaps[bg['tilemap_id']])
|
||||
if bg['tilecycle_id'] > 0:
|
||||
tss = [[t for t in tileset[bg['tileset_id']]] for i in range(4)]
|
||||
for i, tile2 in animations[bg['tilecycle_id']]:
|
||||
frame = i >> 6
|
||||
tile = i & 0x3F
|
||||
tss[frame][tile] = tileset[bg['tileset_id']][tile2]
|
||||
canvases.append([make_tilemap_canvas(tilemap, ts) for ts in tss])
|
||||
if bg['palcycle_id'] < 3:
|
||||
pals = make_pals(bg)
|
||||
pl = len(pals)
|
||||
cl = (animation_time*4)
|
||||
px = [canvases[-1][0].pixmap(pals[0], True)]
|
||||
i = 1
|
||||
while (i%pl != 0) or (i%cl != 0):
|
||||
px.append(canvases[-1][(i//animation_time)%4].pixmap(pals[i%pl], True))
|
||||
i += 1
|
||||
pixmaps.append(px + [1])
|
||||
else:
|
||||
pixmaps.append([c.pixmap(bg['palette'], True) for c in canvases[-1]]+[animation_time])
|
||||
else:
|
||||
canvases.append(make_tilemap_canvas(tilemap, tileset[bg['tileset_id']]))
|
||||
if bg['palcycle_id'] < 3:
|
||||
pals = make_pals(bg)
|
||||
pixmaps.append([canvases[-1].pixmap(p, True) for p in pals]+[1])
|
||||
else:
|
||||
pixmaps.append(canvases[-1].pixmap(bg['palette'], True))
|
||||
return pixmaps
|
||||
|
||||
def get_zone_tiles_start(rom, id):
|
||||
def get_zone_tileset_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.
|
||||
|
|
|
@ -25,14 +25,14 @@ skip_pyqt5 = "PYQT4" in os.environ
|
|||
if not skip_pyqt5:
|
||||
try:
|
||||
from PyQt5 import QtGui
|
||||
from PyQt5.QtGui import QImage, QPixmap, QColor, QPainter
|
||||
from PyQt5.QtGui import QImage, QPixmap, QColor, QPainter, QTransform
|
||||
pyqt_version = 5
|
||||
except ImportError:
|
||||
print("Missing PyQt5, trying PyQt4...")
|
||||
if pyqt_version == 0:
|
||||
try:
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4.QtGui import QImage, QPixmap, QColor, QPainter
|
||||
from PyQt4.QtGui import QImage, QPixmap, QColor, QPainter, QTransform
|
||||
pyqt_version = 4
|
||||
except ImportError:
|
||||
print("Missing PyQt4 dependencies")
|
||||
|
@ -253,9 +253,13 @@ class Canvas_Indexed:
|
|||
self.max_col = 1
|
||||
self.max_row = 1
|
||||
|
||||
def draw_tile(self, col, row, image):
|
||||
def draw_tile(self, col, row, image, h_flip=False, v_flip=False, palette=0):
|
||||
image = image.mirrored(h_flip, v_flip)
|
||||
imgbits = image.bits()
|
||||
imgbits.setsize(image.byteCount())
|
||||
if palette:
|
||||
p = palette<<4
|
||||
imgbits[:] = bytes([int(i[0])|p for i in imgbits])
|
||||
x = col*self.tilesize
|
||||
y = row*self.tilesize
|
||||
start = x + y*self.width
|
||||
|
|
Loading…
Reference in New Issue