diff options
author | yenatch <yenatch@gmail.com> | 2014-10-02 23:22:03 -0400 |
---|---|---|
committer | yenatch <yenatch@gmail.com> | 2014-10-02 23:22:03 -0400 |
commit | 5cce99014065cdd2e25551b625b1783d97546abd (patch) | |
tree | 3dfd07a863e6db94eebb9b0c681c79ae279ae232 | |
parent | 82b78e5c7969aebb797f35a58577dd0afb2c2f38 (diff) |
Fix the map editor.
The map editor can now be invoked in ipython:
import extras.pokemontools.map_editor as ed
app = ed.init()
Then <app> can be modified on the fly to make up for lacking functionality.
Also see app.map.crop().
Now works with all maps in both Red and Crystal.
The version defaults to Crystal. To use with pokered, invoke from the command line:
python extras/pokemontools/map_editor.py red
Or in ipython:
import extras.pokemontools.map_editor as ed
app = ed.init(version='red')
Also displays connections, but they're unaligned.
-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() |