summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoryenatch <yenatch@gmail.com>2014-10-02 23:22:03 -0400
committeryenatch <yenatch@gmail.com>2014-10-02 23:22:03 -0400
commit5cce99014065cdd2e25551b625b1783d97546abd (patch)
tree3dfd07a863e6db94eebb9b0c681c79ae279ae232
parent82b78e5c7969aebb797f35a58577dd0afb2c2f38 (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.py624
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()