diff options
-rw-r--r-- | pokemontools/map_editor.py | 624 |
1 files changed, 378 insertions, 246 deletions
diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 47b7d95..184d3bb 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -1,6 +1,7 @@ import os import sys import logging +import argparse from Tkinter import ( Tk, @@ -11,9 +12,14 @@ from Tkinter import ( HORIZONTAL, RIGHT, LEFT, + TOP, + BOTTOM, + BOTH, Y, X, + N, S, E, W, TclError, + Menu, ) import tkFileDialog @@ -31,10 +37,18 @@ from PIL import ( ) import gfx +import wram import preprocessor import configuration config = configuration.Config() + +def config_open(self, filename): + return open(os.path.join(self.path, filename)) + +configuration.Config.open = config_open + + def setup_logging(): """ Temporary function that configures logging to go straight to console. @@ -47,6 +61,54 @@ def setup_logging(): root.addHandler(console) root.setLevel(logging.DEBUG) + +def read_incbin_in_file(label, filename='main.asm', config=config): + asm = config.open(filename).read() + return read_incbin(asm, label) + +def read_incbin(asm, label): + incbin = asm_at_label(asm, label) + filename = read_header_macros_2( + incbin, + [('filename', 'INCBIN')] + )[0]['filename'] + filename = filename.split('"')[1] + return filename + + +def red_gfx_name(tset): + if type(tset) is int: + return [ + 'overworld', + 'redshouse1', + 'mart', + 'forest', + 'redshouse2', + 'dojo', + 'pokecenter', + 'gym', + 'house', + 'forestgate', + 'museum', + 'underground', + 'gate', + 'ship', + 'shipport', + 'cemetery', + 'interior', + 'cavern', + 'lobby', + 'mansion', + 'lab', + 'club', + 'facility', + 'plateau', + ][tset] + + elif type(tset) is str: + return tset.lower().replace('_', '') + + def configure_for_pokered(config=config): """ Sets default configuration values for pokered. These should eventually be @@ -57,18 +119,14 @@ def configure_for_pokered(config=config): "map_dir": os.path.join(config.path, 'maps/'), "gfx_dir": os.path.join(config.path, 'gfx/tilesets/'), - "to_gfx_name": lambda x : '%.2x' % x, - "block_dir": os.path.join(config.path, 'gfx/blocksets/'), - "block_ext": '.bst', + "to_gfx_name": red_gfx_name, + "block_dir": os.path.join(config.path, 'gfx/blocksets/'), # not used + "block_ext": '.bst', # not used "palettes_on": False, - "asm_path": os.path.join(config.path, 'main.asm'), - "constants_filename": os.path.join(config.path, 'constants.asm'), - "header_path": os.path.join(config.path, 'main.asm'), - "time_of_day": 1, } return attrs @@ -93,7 +151,7 @@ def configure_for_pokecrystal(config=config): "asm_dir": os.path.join(config.path, 'maps/'), - "constants_filename": os.path.join(os.path.join(config.path, "constants/"), 'map_constants.asm'), + "constants_filename": os.path.join(config.path, 'constants.asm'), "header_dir": os.path.join(config.path, 'maps/'), @@ -122,23 +180,22 @@ def configure_for_version(version, config=config): return config def get_constants(config=config): - constants = {} - lines = open(config.constants_filename, 'r').readlines() - for line in lines: - if ' EQU ' in line: - name, value = [s.strip() for s in line.split(' EQU ')] - constants[name] = eval(value.split(';')[0].replace('$','0x').replace('%','0b')) - config.constants = constants - return constants + bss = wram.BSSReader() + bss.read_bss_sections(open(config.constants_filename).readlines()) + config.constants = bss.constants + return config.constants + class Application(Frame): def __init__(self, master=None, config=config): self.config = config self.log = logging.getLogger("{0}.{1}".format(self.__class__.__name__, id(self))) - self.display_connections = False + self.display_connections = True + Frame.__init__(self, master) - self.grid() + self.pack(fill=BOTH, expand=True) Style().configure("TFrame", background="#444") + self.paint_tile = 1 self.init_ui() @@ -147,7 +204,7 @@ class Application(Frame): self.button_frame = Frame(self) self.button_frame.grid(row=0, column=0, columnspan=2) self.map_frame = Frame(self) - self.map_frame.grid(row=1, column=0, padx=5, pady=5) + self.map_frame.grid(row=1, column=0, padx=5, pady=5, sticky=N+S+E+W) self.picker_frame = Frame(self) self.picker_frame.grid(row=1, column=1) @@ -156,6 +213,14 @@ class Application(Frame): self.button_new["command"] = self.new_map self.button_new.grid(row=0, column=0, padx=2) + self.menubar = Menu(self) + + menu = Menu(self.menubar, tearoff=0) + self.menubar.add_cascade(label="File", menu=menu) + menu.add_command(label="New") + menu.add_command(label="Open") + menu.add_command(label="Save") + self.open = Button(self.button_frame) self.open["text"] = "Open" self.open["command"] = self.open_map @@ -179,10 +244,9 @@ class Application(Frame): def new_map(self): self.map_name = None self.init_map() - self.map.blockdata_filename = os.path.join(self.config.map_dir, 'newmap.blk') - self.map.blockdata = bytearray([self.paint_tile] * 20 * 20) - self.map.width = 20 - self.map.height = 20 + self.map.map.blockdata = bytearray([self.paint_tile] * 20 * 20) + self.map.map.width = 20 + self.map.map.height = 20 self.draw_map() self.init_picker() @@ -194,20 +258,22 @@ class Application(Frame): def save_map(self): if hasattr(self, 'map'): - if self.map.blockdata_filename: - filename = tkFileDialog.asksaveasfilename(initialfile=self.map.blockdata_filename) - with open(filename, 'wb') as save: - save.write(self.map.blockdata) - self.log.info('blockdata saved as {}'.format(self.map.blockdata_filename)) + if self.map.map.blk_path: + initial = self.map.map.blk_path else: - self.log.info('dunno how to save this') + initial = self.config.path + filename = tkFileDialog.asksaveasfilename(initialfile=initial) + if filename: + with open(filename, 'wb') as save: + save.write(self.map.map.blockdata) + self.log.info('blockdata saved as {}'.format(filename)) else: self.log.info('nothing to save') def init_map(self): if hasattr(self, 'map'): self.map.kill_canvas() - self.map = Map(self.map_frame, self.map_name, config=self.config) + self.map = MapRenderer(self.config, parent=self.map_frame, name=self.map_name) self.init_map_connections() def draw_map(self): @@ -218,20 +284,21 @@ class Application(Frame): self.map.canvas.bind('<B1-Motion>', self.paint) def init_picker(self): - self.current_tile = Map(self.button_frame, tileset_id=self.map.tileset_id, config=self.config) - self.current_tile.blockdata = [self.paint_tile] - self.current_tile.width = 1 - self.current_tile.height = 1 + """This should really be its own class.""" + self.current_tile = MapRenderer(self.config, parent=self.button_frame, tileset=Tileset(id=self.map.map.tileset.id)) + self.current_tile.map.blockdata = [self.paint_tile] + self.current_tile.map.width = 1 + self.current_tile.map.height = 1 self.current_tile.init_canvas() self.current_tile.draw() self.current_tile.canvas.grid(row=0, column=4, padx=4) if hasattr(self, 'picker'): self.picker.kill_canvas() - self.picker = Map(self, tileset_id=self.map.tileset_id, config=self.config) - self.picker.blockdata = range(len(self.picker.tileset.blocks)) - self.picker.width = 4 - self.picker.height = len(self.picker.blockdata) / self.picker.width + self.picker = MapRenderer(self.config, parent=self, tileset=Tileset(id=self.map.map.tileset.id)) + self.picker.map.blockdata = range(len(self.picker.map.tileset.blocks)) + self.picker.map.width = 4 + self.picker.map.height = len(self.picker.map.blockdata) / self.picker.map.width self.picker.init_canvas(self.picker_frame) if hasattr(self.picker_frame, 'vbar'): @@ -242,7 +309,10 @@ class Application(Frame): self.picker.canvas.config(scrollregion=(0,0,self.picker.canvas_width, self.picker.canvas_height)) self.map_frame.update() - self.picker.canvas.config(height=self.map_frame.winfo_height()) + + # overwriting a property is probably a bad idea + self.picker.canvas_height = self.map_frame.winfo_height() + self.picker.canvas.config(yscrollcommand=self.picker_frame.vbar.set) self.picker.canvas.pack(side=LEFT, expand=True) @@ -262,128 +332,100 @@ class Application(Frame): def pick_block(self, event): - block_x = int(self.picker.canvas.canvasx(event.x)) / (self.picker.tileset.block_width * self.picker.tileset.tile_width) - block_y = int(self.picker.canvas.canvasy(event.y)) / (self.picker.tileset.block_height * self.picker.tileset.tile_height) - i = block_y * self.picker.width + block_x - self.paint_tile = self.picker.blockdata[i] + block_x = int(self.picker.canvas.canvasx(event.x)) / (self.picker.map.tileset.block_width * self.picker.map.tileset.tile_width) + block_y = int(self.picker.canvas.canvasy(event.y)) / (self.picker.map.tileset.block_height * self.picker.map.tileset.tile_height) + i = block_y * self.picker.map.width + block_x + self.paint_tile = self.picker.map.blockdata[i] - self.current_tile.blockdata = [self.paint_tile] + self.current_tile.map.blockdata = [self.paint_tile] self.current_tile.draw() def paint(self, event): - block_x = event.x / (self.map.tileset.block_width * self.map.tileset.tile_width) - block_y = event.y / (self.map.tileset.block_height * self.map.tileset.tile_height) - i = block_y * self.map.width + block_x - if 0 <= i < len(self.map.blockdata): - self.map.blockdata[i] = self.paint_tile + block_x = event.x / (self.map.map.tileset.block_width * self.map.map.tileset.tile_width) + block_y = event.y / (self.map.map.tileset.block_height * self.map.map.tileset.tile_height) + i = block_y * self.map.map.width + block_x + if 0 <= i < len(self.map.map.blockdata): + self.map.map.blockdata[i] = self.paint_tile self.map.draw_block(block_x, block_y) def init_map_connections(self): if not self.display_connections: return - for direction in self.map.connections.keys(): + + for direction in self.map.map.connections.keys(): + if direction in self.connections.keys(): if hasattr(self.connections[direction], 'canvas'): self.connections[direction].kill_canvas() - if self.map.connections[direction] == {}: + + if self.map.map.connections[direction] == {}: self.connections[direction] = {} continue - self.connections[direction] = Map(self, self.map.connections[direction]['map_name'], config=self.config) + self.connections[direction] = MapRenderer(self.config, parent=self, name=self.map.map.connections[direction]['map_name']) + + attrs = self.map.map.connections[direction] if direction in ['north', 'south']: - x1 = 0 - y1 = 0 - x2 = x1 + eval(self.map.connections[direction]['strip_length'], self.config.constants) + if direction == 'north': + x1 = 0 + if self.config.version == 'red': + y1 = eval(attrs['other_height'], self.config.constants) - 3 + elif self.config.version == 'crystal': + y1 = eval(attrs['map'] + '_HEIGHT', self.config.constants) - 3 + else: # south + x1 = 0 + y1 = 0 + x2 = x1 + eval(attrs['strip_length'], self.config.constants) y2 = y1 + 3 - else: # east, west - x1 = 0 - y1 = 0 + else: + if direction == 'east': + x1 = 0 + y1 = 0 + else: # west + x1 = -3 + y1 = 1 x2 = x1 + 3 - y2 = y1 + eval(self.map.connections[direction]['strip_length'], self.config.constants) + y2 = y1 + eval(attrs['strip_length'], self.config.constants) - self.connections[direction].crop(x1, y1, x2, y2) self.connections[direction].init_canvas(self.map_frame) - self.connections[direction].canvas.pack(side={'west':LEFT,'east':RIGHT}[direction]) + self.connections[direction].canvas.pack(side={'north':TOP, 'south':BOTTOM, 'west':LEFT,'east':RIGHT}[direction]) + self.connections[direction].map.crop(x1, y1, x2, y2) self.connections[direction].draw() -class Map: - def __init__(self, parent, name=None, width=20, height=20, tileset_id=2, blockdata_filename=None, config=config): - self.parent = parent - - self.name = name - +class MapRenderer: + def __init__(self, config=config, **kwargs): self.config = config - self.log = logging.getLogger("{0}.{1}".format(self.__class__.__name__, id(self))) + self.__dict__.update(kwargs) + self.map = Map(**kwargs) - self.blockdata_filename = blockdata_filename - if not self.blockdata_filename and self.name: - self.blockdata_filename = os.path.join(self.config.map_dir, self.name + '.blk') - elif not self.blockdata_filename: - self.blockdata_filename = '' + @property + def canvas_width(self): + return self.map.width * self.map.block_width - asm_filename = '' - if self.name: - if self.config.asm_dir is not None: - asm_filename = os.path.join(self.config.asm_dir, self.name + '.asm') - elif self.config.asm_path is not None: - asm_filename = self.config.asm_path - - if os.path.exists(asm_filename): - for props in [map_header(self.name, config=self.config), second_map_header(self.name, config=self.config)]: - self.__dict__.update(props) - self.asm = open(asm_filename, 'r').read() - self.events = event_header(self.asm, self.name) - self.scripts = script_header(self.asm, self.name) - - self.tileset_id = eval(self.tileset_id, self.config.constants) - - self.width = eval(self.width, self.config.constants) - self.height = eval(self.height, self.config.constants) - - else: - self.width = width - self.height = height - self.tileset_id = tileset_id - - if self.blockdata_filename: - self.blockdata = bytearray(open(self.blockdata_filename, 'rb').read()) - else: - self.blockdata = [] - - self.tileset = Tileset(self.tileset_id, config=self.config) + @property + def canvas_height(self): + return self.map.height * self.map.block_height def init_canvas(self, parent=None): if parent == None: parent = self.parent - if not hasattr(self, 'canvas'): - self.canvas_width = self.width * 32 - self.canvas_height = self.height * 32 - self.canvas = Canvas(parent, width=self.canvas_width, height=self.canvas_height) - self.canvas.xview_moveto(0) - self.canvas.yview_moveto(0) + if hasattr(self, 'canvas'): + pass + else: + self.canvas = Canvas(parent) + self.canvas.xview_moveto(0) + self.canvas.yview_moveto(0) def kill_canvas(self): if hasattr(self, 'canvas'): self.canvas.destroy() - def crop(self, x1, y1, x2, y2): - blockdata = self.blockdata - start = y1 * self.width + x1 - width = x2 - x1 - height = y2 - y1 - self.blockdata = [] - for y in xrange(height): - for x in xrange(width): - self.blockdata += [blockdata[start + y * self.width + x]] - self.blockdata = bytearray(self.blockdata) - self.width = width - self.height = height - def draw(self): - for i in xrange(len(self.blockdata)): - block_x = i % self.width - block_y = i / self.width + self.canvas.configure(width=self.canvas_width, height=self.canvas_height) + for i in xrange(len(self.map.blockdata)): + block_x = i % self.map.width + block_y = i / self.map.width self.draw_block(block_x, block_y) def draw_block(self, block_x, block_y): @@ -392,25 +434,102 @@ class Map: index, indey = 4, 4 # Draw one block (4x4 tiles) - block = self.blockdata[block_y * self.width + block_x] - for j, tile in enumerate(self.tileset.blocks[block]): + block = self.map.blockdata[block_y * self.map.width + block_x] + + # Ignore nonexistent blocks. + if block >= len(self.map.tileset.blocks): return + + for j, tile in enumerate(self.map.tileset.blocks[block]): try: # Tile gfx are split in half to make vram mapping easier if tile >= 0x80: tile -= 0x20 - tile_x = block_x * 32 + (j % 4) * 8 - tile_y = block_y * 32 + (j / 4) * 8 - self.canvas.create_image(index + tile_x, indey + tile_y, image=self.tileset.tiles[tile]) + tile_x = block_x * self.map.block_width + (j % 4) * 8 + tile_y = block_y * self.map.block_height + (j / 4) * 8 + self.canvas.create_image(index + tile_x, indey + tile_y, image=self.map.tileset.tiles[tile]) except: pass + def crop(self, *args, **kwargs): + self.map.crop(*args, **kwargs) + self.draw() -class Tileset: - def __init__(self, tileset_id=0, config=config): + +class Map: + width = 20 + height = 20 + block_width = 32 + block_height = 32 + + def __init__(self, config=config, **kwargs): + self.parent = None + self.name = '' + self.blk_path = '' + self.tileset = Tileset(config=config) + self.blockdata = [] + self.connections = {'north': {}, 'south': {}, 'west': {}, 'east': {}} + + self.__dict__.update(kwargs) self.config = config + self.log = logging.getLogger("{0}.{1}".format(self.__class__.__name__, id(self))) - self.id = tileset_id + if not self.blk_path and self.name: + self.blk_path = os.path.join(self.config.map_dir, self.name + '.blk') + if os.path.exists(self.blk_path) and self.blockdata == []: + self.blockdata = bytearray(open(self.blk_path).read()) + + if self.config.version == 'red': + if self.name: + attrs = map_header(self.name, config=self.config) + self.tileset = Tileset(id=attrs['tileset_id'], config=self.config) + self.height = eval(attrs['height'], self.config.constants) + self.width = eval(attrs['width'], self.config.constants) + self.connections = attrs['connections'] + + elif self.config.version == 'crystal': + + asm_filename = '' + if self.name: + asm_filename = os.path.join(self.config.asm_dir, self.name + '.asm') + + if os.path.exists(asm_filename): + for props in [ + map_header(self.name, config=self.config), + second_map_header(self.name, config=self.config) + ]: + self.__dict__.update(props) + + self.asm = open(asm_filename, 'r').read() + self.events = event_header(self.asm, self.name) + self.scripts = script_header(self.asm, self.name) + + self.tileset = Tileset(id=self.tileset_id, config=self.config) + + self.width = eval(self.width, self.config.constants) + self.height = eval(self.height, self.config.constants) + + def crop(self, x1=0, y1=0, x2=None, y2=None): + if x2 is None: x2 = self.width + if y2 is None: y2 = self.height + start = y1 * self.width + x1 + width = x2 - x1 + height = y2 - y1 + blockdata = [] + for y in xrange(height): + index = start + y * self.width + blockdata.extend( self.blockdata[index : index + width] ) + self.blockdata = bytearray(blockdata) + self.width = width + self.height = height + + +class Tileset: + def __init__(self, config=config, **kwargs): + if config.version == 'red': + self.id = 0 + elif config.version == 'crystal': + self.id = 2 self.tile_width = 8 self.tile_height = 8 @@ -419,6 +538,12 @@ class Tileset: self.alpha = 255 + self.__dict__.update(kwargs) + self.id = eval(str(self.id), config.constants) + + self.config = config + self.log = logging.getLogger("{0}.{1}".format(self.__class__.__name__, id(self))) + if self.config.palettes_on: self.get_palettes() self.get_palette_map() @@ -426,18 +551,22 @@ class Tileset: self.get_blocks() self.get_tiles() + def read_header(self): + if self.config.version == 'red': + tileset_headers = self.config.open('data/tileset_headers.asm').readlines() + tileset_header = map(str.strip, tileset_headers[self.id + 1].split('\ttileset')[1].split(',')) + return tileset_header + def get_tileset_gfx_filename(self): filename = None if self.config.version == 'red': - tileset_defs = open(os.path.join(self.config.path, 'main.asm'), 'r').read() - incbin = asm_at_label(tileset_defs, 'Tset%.2X_GFX' % self.id) - self.log.debug(incbin) - filename = read_header_macros(incbin, ['filename'], ['INCBIN'])[0][0].replace('"','').replace('.2bpp','.png') + gfx_label = self.read_header()[1] + filename = read_incbin_in_file(gfx_label, filename='main.asm', config=self.config) + filename = filename.replace('.2bpp','.png') filename = os.path.join(self.config.path, filename) - self.log.debug(filename) - if not filename: + if not filename: # last resort filename = os.path.join( self.config.gfx_dir, self.config.to_gfx_name(self.id) + '.png' @@ -478,10 +607,16 @@ class Tileset: return tile def get_blocks(self): - filename = os.path.join( - self.config.block_dir, - self.config.to_gfx_name(self.id) + self.config.block_ext - ) + if self.config.version == 'crystal': + filename = os.path.join( + self.config.block_dir, + self.config.to_gfx_name(self.id) + self.config.block_ext + ) + + elif self.config.version == 'red': + block_label = self.read_header()[0] + filename = read_incbin_in_file(block_label, 'main.asm', config=self.config) + self.blocks = [] block_length = self.block_width * self.block_height blocks = bytearray(open(filename, 'rb').read()) @@ -523,55 +658,40 @@ def get_available_maps(config=config): def map_header(name, config=config): if config.version == 'crystal': headers = open(os.path.join(config.header_dir, 'map_headers.asm'), 'r').read() - label = name + '_MapHeader' - header = asm_at_label(headers, label) - macros = [ 'db', 'db', 'db', 'dw', 'db', 'db', 'db', 'db' ] + label = name + header = asm_at_label(headers, '\tmap_header ' + label, colon=',') attributes = [ - 'bank', - 'tileset_id', - 'permission', - 'second_map_header', - 'world_map_location', - 'music', - 'time_of_day', - 'fishing_group', + ('label', 'map_header'), + ('tileset_id', 'db'), + ('permission', 'db'), + ('world_map_location', 'db'), + ('music', 'db'), + ('time_of_day', 'db'), + ('fishing_group', 'db'), ] - values, l = read_header_macros(header, attributes, macros) - attrs = dict(zip(attributes, values)) + attrs, l = read_header_macros_2(header, attributes) return attrs elif config.version == 'red': - headers = open(config.header_path, 'r').read() - - # there has to be a better way to do this - lower_label = name + '_h' - i = headers.lower().find(lower_label) - if i == -1: - return {} - label = headers[i:i+len(lower_label)] - - header = asm_at_label(headers, label) - macros = [ 'db', 'db', 'db', 'dw', 'dw', 'dw', 'db' ] + header = config.open('data/mapHeaders/{0}.asm'.format(name)).read() + header = split_comments(header.split('\n')) attributes = [ - 'tileset_id', - 'height', - 'width', - 'blockdata_label', - 'text_label', - 'script_label', - 'which_connections', + ('tileset_id', 'db'), + ('height', 'db'), + ('width', 'db'), + ('blockdata_label', 'dw'), + ('text_label', 'dw'), + ('script_label', 'dw'), + ('which_connections', 'db'), ] - values, l = read_header_macros(header, attributes, macros) - attrs = dict(zip(attributes, values)) + attrs, l = read_header_macros_2(header, attributes) + attrs['connections'], l = connections(attrs['which_connections'], header, l, config=config) - macros = [ 'dw' ] - attributes = [ - 'object_label', - ] - values, l = read_header_macros(header[l:], attributes, macros) - attrs.update(dict(zip(attributes, values))) + attributes = [('object_label', 'dw')] + more_attrs, l = read_header_macros_2(header[l:], attributes) + attrs.update(more_attrs) return attrs @@ -580,24 +700,23 @@ def map_header(name, config=config): def second_map_header(name, config=config): if config.version == 'crystal': headers = open(os.path.join(config.header_dir, 'second_map_headers.asm'), 'r').read() - label = name + '_SecondMapHeader' - header = asm_at_label(headers, label) - macros = [ 'db', 'db', 'db', 'db', 'dw', 'db', 'dw', 'dw', 'db' ] + label = '\tmap_header_2 ' + name + header = asm_at_label(headers, label, colon=',') + attributes = [ - 'border_block', - 'height', - 'width', - 'blockdata_bank', - 'blockdata_label', - 'script_header_bank', - 'script_header_label', - 'map_event_header_label', - 'which_connections', + ('second_label', 'map_header_2'), + ('dimension_base', 'db'), + ('border_block', 'db'), + ('which_connections', 'db'), ] - values, l = read_header_macros(header, attributes, macros) - attrs = dict(zip(attributes, values)) - attrs['connections'], l = connections(attrs['which_connections'], header, l) + attrs, l = read_header_macros_2(header, attributes) + + # hack to use dimension constants, eventually dimensions will be here for real + attrs['height'] = attrs['dimension_base'] + '_HEIGHT' + attrs['width'] = attrs['dimension_base'] + '_WIDTH' + + attrs['connections'], l = connections(attrs['which_connections'], header, l, config=config) return attrs return {} @@ -606,39 +725,46 @@ def connections(which_connections, header, l=0, config=config): directions = { 'north': {}, 'south': {}, 'west': {}, 'east': {} } if config.version == 'crystal': - macros = [ 'db', 'db' ] attributes = [ - 'map_group', - 'map_no', + ('map', 'map'), + ('strip_pointer', 'dw'), + ('strip_destination', 'dw'), + ('strip_length', 'db'), + ('map_width', 'db'), + ('y_offset', 'db'), + ('x_offset', 'db'), + ('window', 'dw'), ] elif config.version == 'red': - macros = [ 'db' ] - attributes = [ - 'map_id', - ] - - macros += [ 'dw', 'dw', 'db', 'db', 'db', 'db', 'dw' ] - attributes += [ - 'strip_pointer', - 'strip_destination', - 'strip_length', - 'map_width', - 'y_offset', - 'x_offset', - 'window', - ] - for d in directions.keys(): + conn_attrs = { + 'north': ['map_id', 'other_width', 'other_height', 'x_offset', 'strip_offset', 'strip_length', 'other_blocks'], + 'south': ['map_id', 'other_width', 'x_offset', 'strip_offset', 'strip_length', 'other_blocks', 'width', 'height'], + 'east': ['map_id', 'other_width', 'y_offset', 'strip_offset', 'strip_length', 'other_blocks', 'width'], + 'west': ['map_id', 'other_width', 'y_offset', 'strip_offset', 'strip_length', 'other_blocks', 'width'], + } + + for d in ['north', 'south', 'west', 'east']: if d.upper() in which_connections: - values, l = read_header_macros(header, attributes, macros) - header = header[l:] - directions[d] = dict(zip(attributes, values)) + if config.version == 'crystal': - directions[d]['map_name'] = directions[d]['map_group'].replace('GROUP_', '').title().replace('_','') + attrs, l2 = read_header_macros_2(header[l:], attributes) + l += l2 + directions[d] = attrs + directions[d]['map_name'] = directions[d]['map'].title().replace('_','') + elif config.version == 'red': - directions[d]['map_name'] = directions[d]['map_id'].title().replace('_','') + attrs, l2 = read_header_macros_2(header[l:], zip(conn_attrs[d], [d.upper() + '_MAP_CONNECTION'] * len(conn_attrs[d]))) + l += l2 + directions[d] = attrs + directions[d]['map_name'] = directions[d]['map_id'].lower().replace('_','') + return directions, l +def read_header_macros_2(header, attributes): + values, l = read_header_macros(header, [x[0] for x in attributes], [x[1] for x in attributes]) + return dict(zip([x[0] for x in attributes], values)), l + def read_header_macros(header, attributes, macros): values = [] i = 0 @@ -660,49 +786,55 @@ def script_header(asm, name): return {} def macro_values(line, macro): - values = line[line.find(macro) + len(macro):].split(',') + values = macro.join(line.split(macro)[1:]).split(',') + #values = line[line.find(macro) + len(macro):].split(',') values = [v.replace('$','0x').strip() for v in values] if values[0] == 'w': # dbw values = values[1:] return values -def asm_at_label(asm, label): - label_def = label + ':' +def asm_at_label(asm, label, colon=':'): + label_def = label + colon lines = asm.split('\n') - for line in lines: - if line.startswith(label_def): - lines = lines[lines.index(line):] - lines[0] = lines[0][len(label_def):] + for i, line in enumerate(lines): + if label_def in line: + lines = lines[i:] break - # go until the next label + return split_comments(lines) + +def split_comments(lines): content = [] for line in lines: l, comment = preprocessor.separate_comment(line + '\n') - if ':' in l: - break + # skip over labels? this should be in macro_values + while ':' in l: + l = l[l.index(':') + 1:] content += [[l, comment]] return content + def main(config=config): """ - Launches the map editor. + Creates an application instance. """ root = Tk() - root.wm_title("MAP EDITOR") + root.columnconfigure(0, weight=1) + root.wm_title("ayy lmap") app = Application(master=root, config=config) + return app - try: - app.mainloop() - except KeyboardInterrupt: - pass - - try: - root.destroy() - except TclError: - pass - -if __name__ == "__main__": +def init(config=config, version='crystal'): + """ + Launches a map editor instance. + """ setup_logging() - config = configure_for_version("crystal", config) + configure_for_version(version, config) get_constants(config=config) - main(config=config) + return main(config=config) + +if __name__ == "__main__": + ap = argparse.ArgumentParser() + ap.add_argument('version', nargs='?', default='crystal') + args = ap.parse_args() + app = init(config=config, version=args.version) + app.mainloop() |