585 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			585 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | import json | ||
|  | from . import entityData | ||
|  | 
 | ||
|  | 
 | ||
|  | WARP_TYPE_IDS = {0xE1, 0xE2, 0xE3, 0xBA, 0xA8, 0xBE, 0xCB, 0xC2, 0xC6} | ||
|  | ALT_ROOM_OVERLAYS = {"Alt06": 0x1040, "Alt0E": 0x1090, "Alt1B": 0x10E0, "Alt2B": 0x1130, "Alt79": 0x1180, "Alt8C": 0x11D0} | ||
|  | 
 | ||
|  | 
 | ||
|  | class RoomEditor: | ||
|  |     def __init__(self, rom, room=None): | ||
|  |         assert room is not None | ||
|  |         self.room = room | ||
|  |         self.entities = [] | ||
|  |         self.objects = [] | ||
|  |         self.tileset_index = None | ||
|  |         self.palette_index = None | ||
|  |         self.attribset = None | ||
|  | 
 | ||
|  |         if isinstance(room, int): | ||
|  |             entities_raw = rom.entities[room] | ||
|  |             idx = 0 | ||
|  |             while entities_raw[idx] != 0xFF: | ||
|  |                 x = entities_raw[idx] & 0x0F | ||
|  |                 y = entities_raw[idx] >> 4 | ||
|  |                 id = entities_raw[idx + 1] | ||
|  |                 self.entities.append((x, y, id)) | ||
|  |                 idx += 2 | ||
|  |             assert idx == len(entities_raw) - 1 | ||
|  | 
 | ||
|  |         if isinstance(room, str): | ||
|  |             if room in rom.rooms_overworld_top: | ||
|  |                 objects_raw = rom.rooms_overworld_top[room] | ||
|  |             elif room in rom.rooms_overworld_bottom: | ||
|  |                 objects_raw = rom.rooms_overworld_bottom[room] | ||
|  |             elif room in rom.rooms_indoor_a: | ||
|  |                 objects_raw = rom.rooms_indoor_a[room] | ||
|  |             else: | ||
|  |                 assert False, "Failed to find alt room: %s" % (room) | ||
|  |         else: | ||
|  |             if room < 0x080: | ||
|  |                 objects_raw = rom.rooms_overworld_top[room] | ||
|  |             elif room < 0x100: | ||
|  |                 objects_raw = rom.rooms_overworld_bottom[room - 0x80] | ||
|  |             elif room < 0x200: | ||
|  |                 objects_raw = rom.rooms_indoor_a[room - 0x100] | ||
|  |             elif room < 0x300: | ||
|  |                 objects_raw = rom.rooms_indoor_b[room - 0x200] | ||
|  |             else: | ||
|  |                 objects_raw = rom.rooms_color_dungeon[room - 0x300] | ||
|  | 
 | ||
|  |         self.animation_id = objects_raw[0] | ||
|  |         self.floor_object = objects_raw[1] | ||
|  |         idx = 2 | ||
|  |         while objects_raw[idx] != 0xFE: | ||
|  |             x = objects_raw[idx] & 0x0F | ||
|  |             y = objects_raw[idx] >> 4 | ||
|  |             if y == 0x08:  # horizontal | ||
|  |                 count = x | ||
|  |                 x = objects_raw[idx + 1] & 0x0F | ||
|  |                 y = objects_raw[idx + 1] >> 4 | ||
|  |                 self.objects.append(ObjectHorizontal(x, y, objects_raw[idx + 2], count)) | ||
|  |                 idx += 3 | ||
|  |             elif y == 0x0C: # vertical | ||
|  |                 count = x | ||
|  |                 x = objects_raw[idx + 1] & 0x0F | ||
|  |                 y = objects_raw[idx + 1] >> 4 | ||
|  |                 self.objects.append(ObjectVertical(x, y, objects_raw[idx + 2], count)) | ||
|  |                 idx += 3 | ||
|  |             elif y == 0x0E:  # warp | ||
|  |                 self.objects.append(ObjectWarp(objects_raw[idx] & 0x0F, objects_raw[idx + 1], objects_raw[idx + 2], objects_raw[idx + 3], objects_raw[idx + 4])) | ||
|  |                 idx += 5 | ||
|  |             else: | ||
|  |                 self.objects.append(Object(x, y, objects_raw[idx + 1])) | ||
|  |                 idx += 2 | ||
|  |         if room is not None: | ||
|  |             assert idx == len(objects_raw) - 1 | ||
|  | 
 | ||
|  |         if isinstance(room, int) and room < 0x0CC: | ||
|  |             self.overlay = rom.banks[0x26][room * 80:room * 80+80] | ||
|  |         elif isinstance(room, int) and room < 0x100: | ||
|  |             self.overlay = rom.banks[0x27][(room - 0xCC) * 80:(room - 0xCC) * 80 + 80] | ||
|  |         elif room in ALT_ROOM_OVERLAYS: | ||
|  |             self.overlay = rom.banks[0x27][ALT_ROOM_OVERLAYS[room]:ALT_ROOM_OVERLAYS[room] + 80] | ||
|  |         else: | ||
|  |             self.overlay = None | ||
|  | 
 | ||
|  |     def store(self, rom, new_room_nr=None): | ||
|  |         if new_room_nr is None: | ||
|  |             new_room_nr = self.room | ||
|  |         objects_raw = bytearray([self.animation_id, self.floor_object]) | ||
|  |         for obj in self.objects: | ||
|  |             objects_raw += obj.export() | ||
|  |         objects_raw += bytearray([0xFE]) | ||
|  | 
 | ||
|  |         if isinstance(new_room_nr, str): | ||
|  |             if new_room_nr in rom.rooms_overworld_top: | ||
|  |                 rom.rooms_overworld_top[new_room_nr] = objects_raw | ||
|  |             elif new_room_nr in rom.rooms_overworld_bottom: | ||
|  |                 rom.rooms_overworld_bottom[new_room_nr] = objects_raw | ||
|  |             elif new_room_nr in rom.rooms_indoor_a: | ||
|  |                 rom.rooms_indoor_a[new_room_nr] = objects_raw | ||
|  |             else: | ||
|  |                 assert False, "Failed to find alt room: %s" % (new_room_nr) | ||
|  |         elif new_room_nr < 0x080: | ||
|  |             rom.rooms_overworld_top[new_room_nr] = objects_raw | ||
|  |         elif new_room_nr < 0x100: | ||
|  |             rom.rooms_overworld_bottom[new_room_nr - 0x80] = objects_raw | ||
|  |         elif new_room_nr < 0x200: | ||
|  |             rom.rooms_indoor_a[new_room_nr - 0x100] = objects_raw | ||
|  |         elif new_room_nr < 0x300: | ||
|  |             rom.rooms_indoor_b[new_room_nr - 0x200] = objects_raw | ||
|  |         else: | ||
|  |             rom.rooms_color_dungeon[new_room_nr - 0x300] = objects_raw | ||
|  | 
 | ||
|  |         if isinstance(new_room_nr, int) and new_room_nr < 0x100: | ||
|  |             if self.tileset_index is not None: | ||
|  |                 rom.banks[0x3F][0x3F00 + new_room_nr] = self.tileset_index & 0xFF | ||
|  |             if self.attribset is not None: | ||
|  |                 # With a tileset, comes metatile gbc data that we need to store a proper bank+pointer. | ||
|  |                 rom.banks[0x1A][0x2476 + new_room_nr] = self.attribset[0] | ||
|  |                 rom.banks[0x1A][0x1E76 + new_room_nr*2] = self.attribset[1] & 0xFF | ||
|  |                 rom.banks[0x1A][0x1E76 + new_room_nr*2+1] = self.attribset[1] >> 8 | ||
|  |             if self.palette_index is not None: | ||
|  |                 rom.banks[0x21][0x02ef + new_room_nr] = self.palette_index | ||
|  | 
 | ||
|  |         if isinstance(new_room_nr, int): | ||
|  |             entities_raw = bytearray() | ||
|  |             for entity in self.entities: | ||
|  |                 entities_raw += bytearray([entity[0] | entity[1] << 4, entity[2]]) | ||
|  |             entities_raw += bytearray([0xFF]) | ||
|  |             rom.entities[new_room_nr] = entities_raw | ||
|  | 
 | ||
|  |             if new_room_nr < 0x0CC: | ||
|  |                 rom.banks[0x26][new_room_nr * 80:new_room_nr * 80 + 80] = self.overlay | ||
|  |             elif new_room_nr < 0x100: | ||
|  |                 rom.banks[0x27][(new_room_nr - 0xCC) * 80:(new_room_nr - 0xCC) * 80 + 80] = self.overlay | ||
|  |         elif new_room_nr in ALT_ROOM_OVERLAYS: | ||
|  |             rom.banks[0x27][ALT_ROOM_OVERLAYS[new_room_nr]:ALT_ROOM_OVERLAYS[new_room_nr] + 80] = self.overlay | ||
|  | 
 | ||
|  |     def addEntity(self, x, y, type_id): | ||
|  |         self.entities.append((x, y, type_id)) | ||
|  | 
 | ||
|  |     def removeEntities(self, type_id): | ||
|  |         self.entities = list(filter(lambda e: e[2] != type_id, self.entities)) | ||
|  | 
 | ||
|  |     def hasEntity(self, type_id): | ||
|  |         return any(map(lambda e: e[2] == type_id, self.entities)) | ||
|  | 
 | ||
|  |     def changeObject(self, x, y, new_type): | ||
|  |         for obj in self.objects: | ||
|  |             if obj.x == x and obj.y == y: | ||
|  |                 obj.type_id = new_type | ||
|  |                 if self.overlay is not None: | ||
|  |                     self.overlay[x + y * 10] = new_type | ||
|  | 
 | ||
|  |     def removeObject(self, x, y): | ||
|  |         self.objects = list(filter(lambda obj: obj.x != x or obj.y != y, self.objects)) | ||
|  | 
 | ||
|  |     def moveObject(self, x, y, new_x, new_y): | ||
|  |         for obj in self.objects: | ||
|  |             if obj.x == x and obj.y == y: | ||
|  |                 if self.overlay is not None: | ||
|  |                     self.overlay[x + y * 10] = self.floor_object | ||
|  |                     self.overlay[new_x + new_y * 10] = obj.type_id | ||
|  |                 obj.x = new_x | ||
|  |                 obj.y = new_y | ||
|  | 
 | ||
|  |     def getWarps(self): | ||
|  |         return list(filter(lambda obj: isinstance(obj, ObjectWarp), self.objects)) | ||
|  | 
 | ||
|  |     def updateOverlay(self, preserve_floor=False): | ||
|  |         if self.overlay is None: | ||
|  |             return | ||
|  |         if not preserve_floor: | ||
|  |             for n in range(80): | ||
|  |                 self.overlay[n] = self.floor_object | ||
|  |         for obj in self.objects: | ||
|  |             if isinstance(obj, ObjectHorizontal): | ||
|  |                 for n in range(obj.count): | ||
|  |                     self.overlay[obj.x + n + obj.y * 10] = obj.type_id | ||
|  |             elif isinstance(obj, ObjectVertical): | ||
|  |                 for n in range(obj.count): | ||
|  |                     self.overlay[obj.x + n * 10 + obj.y * 10] = obj.type_id | ||
|  |             elif not isinstance(obj, ObjectWarp): | ||
|  |                 self.overlay[obj.x + obj.y * 10] = obj.type_id | ||
|  | 
 | ||
|  |     def loadFromJson(self, filename): | ||
|  |         self.objects = [] | ||
|  |         self.entities = [] | ||
|  |         self.animation_id = 0 | ||
|  |         self.tileset_index = 0x0F | ||
|  |         self.palette_index = 0x01 | ||
|  |          | ||
|  |         data = json.load(open(filename)) | ||
|  |          | ||
|  |         for prop in data.get("properties", []): | ||
|  |             if prop["name"] == "palette": | ||
|  |                 self.palette_index = int(prop["value"], 16) | ||
|  |             elif prop["name"] == "tileset": | ||
|  |                 self.tileset_index = int(prop["value"], 16) | ||
|  |             elif prop["name"] == "animationset": | ||
|  |                 self.animation_id = int(prop["value"], 16) | ||
|  |             elif prop["name"] == "attribset": | ||
|  |                 bank, _, addr = prop["value"].partition(":") | ||
|  |                 self.attribset = (int(bank, 16), int(addr, 16) + 0x4000) | ||
|  | 
 | ||
|  |         tiles = [0] * 80 | ||
|  |         for layer in data["layers"]: | ||
|  |             if "data" in layer: | ||
|  |                 for n in range(80): | ||
|  |                     if layer["data"][n] > 0: | ||
|  |                         tiles[n] = (layer["data"][n] - 1) & 0xFF | ||
|  |             if "objects" in layer: | ||
|  |                 for obj in layer["objects"]: | ||
|  |                     x = int((obj["x"] + obj["width"] / 2) // 16) | ||
|  |                     y = int((obj["y"] + obj["height"] / 2) // 16) | ||
|  |                     if obj["type"] == "warp": | ||
|  |                         warp_type, map_nr, room, x, y = obj["name"].split(":") | ||
|  |                         self.objects.append(ObjectWarp(int(warp_type), int(map_nr, 16), int(room, 16) & 0xFF, int(x, 16), int(y, 16))) | ||
|  |                     elif obj["type"] == "entity": | ||
|  |                         type_id = entityData.NAME.index(obj["name"]) | ||
|  |                         self.addEntity(x, y, type_id) | ||
|  |                     elif obj["type"] == "hidden_tile": | ||
|  |                         self.objects.append(Object(x, y, int(obj["name"], 16))) | ||
|  |         self.buildObjectList(tiles, reduce_size=True) | ||
|  |         return data | ||
|  | 
 | ||
|  |     def getTileArray(self): | ||
|  |         if self.room < 0x100: | ||
|  |             tiles = [self.floor_object] * 80 | ||
|  |         else: | ||
|  |             tiles = [self.floor_object & 0x0F] * 80 | ||
|  |         def objHSize(type_id): | ||
|  |             if type_id == 0xF5: | ||
|  |                 return 2 | ||
|  |             return 1 | ||
|  |         def objVSize(type_id): | ||
|  |             if type_id == 0xF5: | ||
|  |                 return 2 | ||
|  |             return 1 | ||
|  |         def getObject(x, y): | ||
|  |             x, y = (x & 15), (y & 15) | ||
|  |             if x < 10 and y < 8: | ||
|  |                 return tiles[x + y * 10] | ||
|  |             return 0 | ||
|  |         if self.room < 0x100: | ||
|  |             def placeObject(x, y, type_id): | ||
|  |                 if type_id == 0xF5: | ||
|  |                     if getObject(x, y) in (0x1B, 0x28, 0x29, 0x83, 0x90): | ||
|  |                         placeObject(x, y, 0x29) | ||
|  |                     else: | ||
|  |                         placeObject(x, y, 0x25) | ||
|  |                     if getObject(x + 1, y) in (0x1B, 0x27, 0x82, 0x86, 0x8A, 0x90, 0x2A): | ||
|  |                         placeObject(x + 1, y, 0x2A) | ||
|  |                     else: | ||
|  |                         placeObject(x + 1, y, 0x26) | ||
|  |                     if getObject(x, y + 1) in (0x26, 0x2A): | ||
|  |                         placeObject(x, y + 1, 0x2A) | ||
|  |                     elif getObject(x, y + 1) == 0x90: | ||
|  |                         placeObject(x, y + 1, 0x82) | ||
|  |                     else: | ||
|  |                         placeObject(x, y + 1, 0x27) | ||
|  |                     if getObject(x + 1, y + 1) in (0x25, 0x29): | ||
|  |                         placeObject(x + 1, y + 1, 0x29) | ||
|  |                     elif getObject(x + 1, y + 1) == 0x90: | ||
|  |                         placeObject(x + 1, y + 1, 0x83) | ||
|  |                     else: | ||
|  |                         placeObject(x + 1, y + 1, 0x28) | ||
|  |                 elif type_id == 0xF6:  # two door house | ||
|  |                     placeObject(x + 0, y, 0x55) | ||
|  |                     placeObject(x + 1, y, 0x5A) | ||
|  |                     placeObject(x + 2, y, 0x5A) | ||
|  |                     placeObject(x + 3, y, 0x5A) | ||
|  |                     placeObject(x + 4, y, 0x56) | ||
|  |                     placeObject(x + 0, y + 1, 0x57) | ||
|  |                     placeObject(x + 1, y + 1, 0x59) | ||
|  |                     placeObject(x + 2, y + 1, 0x59) | ||
|  |                     placeObject(x + 3, y + 1, 0x59) | ||
|  |                     placeObject(x + 4, y + 1, 0x58) | ||
|  |                     placeObject(x + 0, y + 2, 0x5B) | ||
|  |                     placeObject(x + 1, y + 2, 0xE2) | ||
|  |                     placeObject(x + 2, y + 2, 0x5B) | ||
|  |                     placeObject(x + 3, y + 2, 0xE2) | ||
|  |                     placeObject(x + 4, y + 2, 0x5B) | ||
|  |                 elif type_id == 0xF7:  # large house | ||
|  |                     placeObject(x + 0, y, 0x55) | ||
|  |                     placeObject(x + 1, y, 0x5A) | ||
|  |                     placeObject(x + 2, y, 0x56) | ||
|  |                     placeObject(x + 0, y + 1, 0x57) | ||
|  |                     placeObject(x + 1, y + 1, 0x59) | ||
|  |                     placeObject(x + 2, y + 1, 0x58) | ||
|  |                     placeObject(x + 0, y + 2, 0x5B) | ||
|  |                     placeObject(x + 1, y + 2, 0xE2) | ||
|  |                     placeObject(x + 2, y + 2, 0x5B) | ||
|  |                 elif type_id == 0xF8:  # catfish | ||
|  |                     placeObject(x + 0, y, 0xB6) | ||
|  |                     placeObject(x + 1, y, 0xB7) | ||
|  |                     placeObject(x + 2, y, 0x66) | ||
|  |                     placeObject(x + 0, y + 1, 0x67) | ||
|  |                     placeObject(x + 1, y + 1, 0xE3) | ||
|  |                     placeObject(x + 2, y + 1, 0x68) | ||
|  |                 elif type_id == 0xF9:  # palace door | ||
|  |                     placeObject(x + 0, y, 0xA4) | ||
|  |                     placeObject(x + 1, y, 0xA5) | ||
|  |                     placeObject(x + 2, y, 0xA6) | ||
|  |                     placeObject(x + 0, y + 1, 0xA7) | ||
|  |                     placeObject(x + 1, y + 1, 0xE3) | ||
|  |                     placeObject(x + 2, y + 1, 0xA8) | ||
|  |                 elif type_id == 0xFA:  # stone pig head | ||
|  |                     placeObject(x + 0, y, 0xBB) | ||
|  |                     placeObject(x + 1, y, 0xBC) | ||
|  |                     placeObject(x + 0, y + 1, 0xBD) | ||
|  |                     placeObject(x + 1, y + 1, 0xBE) | ||
|  |                 elif type_id == 0xFB:  # palmtree | ||
|  |                     if x == 15: | ||
|  |                         placeObject(x + 1, y + 1, 0xB7) | ||
|  |                         placeObject(x + 1, y + 2, 0xCE) | ||
|  |                     else: | ||
|  |                         placeObject(x + 0, y, 0xB6) | ||
|  |                         placeObject(x + 0, y + 1, 0xCD) | ||
|  |                         placeObject(x + 1, y + 0, 0xB7) | ||
|  |                         placeObject(x + 1, y + 1, 0xCE) | ||
|  |                 elif type_id == 0xFC:  # square "hill with hole" (seen near lvl4 entrance) | ||
|  |                     placeObject(x + 0, y, 0x2B) | ||
|  |                     placeObject(x + 1, y, 0x2C) | ||
|  |                     placeObject(x + 2, y, 0x2D) | ||
|  |                     placeObject(x + 0, y + 1, 0x37) | ||
|  |                     placeObject(x + 1, y + 1, 0xE8) | ||
|  |                     placeObject(x + 2, y + 1, 0x38) | ||
|  |                     placeObject(x - 1, y + 2, 0x0A) | ||
|  |                     placeObject(x + 0, y + 2, 0x33) | ||
|  |                     placeObject(x + 1, y + 2, 0x2F) | ||
|  |                     placeObject(x + 2, y + 2, 0x34) | ||
|  |                     placeObject(x + 0, y + 3, 0x0A) | ||
|  |                     placeObject(x + 1, y + 3, 0x0A) | ||
|  |                     placeObject(x + 2, y + 3, 0x0A) | ||
|  |                     placeObject(x + 3, y + 3, 0x0A) | ||
|  |                 elif type_id == 0xFD:  # small house | ||
|  |                     placeObject(x + 0, y, 0x52) | ||
|  |                     placeObject(x + 1, y, 0x52) | ||
|  |                     placeObject(x + 2, y, 0x52) | ||
|  |                     placeObject(x + 0, y + 1, 0x5B) | ||
|  |                     placeObject(x + 1, y + 1, 0xE2) | ||
|  |                     placeObject(x + 2, y + 1, 0x5B) | ||
|  |                 else: | ||
|  |                     x, y = (x & 15), (y & 15) | ||
|  |                     if x < 10 and y < 8: | ||
|  |                         tiles[x + y * 10] = type_id | ||
|  |         else: | ||
|  |             def placeObject(x, y, type_id): | ||
|  |                 x, y = (x & 15), (y & 15) | ||
|  |                 if type_id == 0xEC:  # key door | ||
|  |                     placeObject(x, y, 0x2D) | ||
|  |                     placeObject(x + 1, y, 0x2E) | ||
|  |                 elif type_id == 0xED: | ||
|  |                     placeObject(x, y, 0x2F) | ||
|  |                     placeObject(x + 1, y, 0x30) | ||
|  |                 elif type_id == 0xEE: | ||
|  |                     placeObject(x, y, 0x31) | ||
|  |                     placeObject(x, y + 1, 0x32) | ||
|  |                 elif type_id == 0xEF: | ||
|  |                     placeObject(x, y, 0x33) | ||
|  |                     placeObject(x, y + 1, 0x34) | ||
|  |                 elif type_id == 0xF0:  # closed door | ||
|  |                     placeObject(x, y, 0x35) | ||
|  |                     placeObject(x + 1, y, 0x36) | ||
|  |                 elif type_id == 0xF1: | ||
|  |                     placeObject(x, y, 0x37) | ||
|  |                     placeObject(x + 1, y, 0x38) | ||
|  |                 elif type_id == 0xF2: | ||
|  |                     placeObject(x, y, 0x39) | ||
|  |                     placeObject(x, y + 1, 0x3A) | ||
|  |                 elif type_id == 0xF3: | ||
|  |                     placeObject(x, y, 0x3B) | ||
|  |                     placeObject(x, y + 1, 0x3C) | ||
|  |                 elif type_id == 0xF4:  # open door | ||
|  |                     placeObject(x, y, 0x43) | ||
|  |                     placeObject(x + 1, y, 0x44) | ||
|  |                 elif type_id == 0xF5: | ||
|  |                     placeObject(x, y, 0x8C) | ||
|  |                     placeObject(x + 1, y, 0x08) | ||
|  |                 elif type_id == 0xF6: | ||
|  |                     placeObject(x, y, 0x09) | ||
|  |                     placeObject(x, y + 1, 0x0A) | ||
|  |                 elif type_id == 0xF7: | ||
|  |                     placeObject(x, y, 0x0B) | ||
|  |                     placeObject(x, y + 1, 0x0C) | ||
|  |                 elif type_id == 0xF8:  # boss door | ||
|  |                     placeObject(x, y, 0xA4) | ||
|  |                     placeObject(x + 1, y, 0xA5) | ||
|  |                 elif type_id == 0xF9:  # stairs door | ||
|  |                     placeObject(x, y, 0xAF) | ||
|  |                     placeObject(x + 1, y, 0xB0) | ||
|  |                 elif type_id == 0xFA:  # flipwall | ||
|  |                     placeObject(x, y, 0xB1) | ||
|  |                     placeObject(x + 1, y, 0xB2) | ||
|  |                 elif type_id == 0xFB:  # one way arrow | ||
|  |                     placeObject(x, y, 0x45) | ||
|  |                     placeObject(x + 1, y, 0x46) | ||
|  |                 elif type_id == 0xFC:  # entrance | ||
|  |                     placeObject(x + 0, y, 0xB3) | ||
|  |                     placeObject(x + 1, y, 0xB4) | ||
|  |                     placeObject(x + 2, y, 0xB4) | ||
|  |                     placeObject(x + 3, y, 0xB5) | ||
|  |                     placeObject(x + 0, y + 1, 0xB6) | ||
|  |                     placeObject(x + 1, y + 1, 0xB7) | ||
|  |                     placeObject(x + 2, y + 1, 0xB8) | ||
|  |                     placeObject(x + 3, y + 1, 0xB9) | ||
|  |                     placeObject(x + 0, y + 2, 0xBA) | ||
|  |                     placeObject(x + 1, y + 2, 0xBB) | ||
|  |                     placeObject(x + 2, y + 2, 0xBC) | ||
|  |                     placeObject(x + 3, y + 2, 0xBD) | ||
|  |                 elif type_id == 0xFD:  # entrance | ||
|  |                     placeObject(x, y, 0xC1) | ||
|  |                     placeObject(x + 1, y, 0xC2) | ||
|  |                 else: | ||
|  |                     if x < 10 and y < 8: | ||
|  |                         tiles[x + y * 10] = type_id | ||
|  | 
 | ||
|  |             def addWalls(flags): | ||
|  |                 for x in range(0, 10): | ||
|  |                     if flags & 0b0010: | ||
|  |                         placeObject(x, 0, 0x21) | ||
|  |                     if flags & 0b0001: | ||
|  |                         placeObject(x, 7, 0x22) | ||
|  |                 for y in range(0, 8): | ||
|  |                     if flags & 0b1000: | ||
|  |                         placeObject(0, y, 0x23) | ||
|  |                     if flags & 0b0100: | ||
|  |                         placeObject(9, y, 0x24) | ||
|  |                 if flags & 0b1000 and flags & 0b0010: | ||
|  |                     placeObject(0, 0, 0x25) | ||
|  |                 if flags & 0b0100 and flags & 0b0010: | ||
|  |                     placeObject(9, 0, 0x26) | ||
|  |                 if flags & 0b1000 and flags & 0b0001: | ||
|  |                     placeObject(0, 7, 0x27) | ||
|  |                 if flags & 0b0100 and flags & 0b0001: | ||
|  |                     placeObject(9, 7, 0x28) | ||
|  | 
 | ||
|  |             if self.floor_object & 0xF0 == 0x00: | ||
|  |                 addWalls(0b1111) | ||
|  |             if self.floor_object & 0xF0 == 0x10: | ||
|  |                 addWalls(0b1101) | ||
|  |             if self.floor_object & 0xF0 == 0x20: | ||
|  |                 addWalls(0b1011) | ||
|  |             if self.floor_object & 0xF0 == 0x30: | ||
|  |                 addWalls(0b1110) | ||
|  |             if self.floor_object & 0xF0 == 0x40: | ||
|  |                 addWalls(0b0111) | ||
|  |             if self.floor_object & 0xF0 == 0x50: | ||
|  |                 addWalls(0b1001) | ||
|  |             if self.floor_object & 0xF0 == 0x60: | ||
|  |                 addWalls(0b0101) | ||
|  |             if self.floor_object & 0xF0 == 0x70: | ||
|  |                 addWalls(0b0110) | ||
|  |             if self.floor_object & 0xF0 == 0x80: | ||
|  |                 addWalls(0b1010) | ||
|  |         for obj in self.objects: | ||
|  |             if isinstance(obj, ObjectWarp): | ||
|  |                 pass | ||
|  |             elif isinstance(obj, ObjectHorizontal): | ||
|  |                 for n in range(0, obj.count): | ||
|  |                     placeObject(obj.x + n * objHSize(obj.type_id), obj.y, obj.type_id) | ||
|  |             elif isinstance(obj, ObjectVertical): | ||
|  |                 for n in range(0, obj.count): | ||
|  |                     placeObject(obj.x, obj.y + n * objVSize(obj.type_id), obj.type_id) | ||
|  |             else: | ||
|  |                 placeObject(obj.x, obj.y, obj.type_id) | ||
|  |         return tiles | ||
|  | 
 | ||
|  |     def buildObjectList(self, tiles, *, reduce_size=False): | ||
|  |         self.objects = [obj for obj in self.objects if isinstance(obj, ObjectWarp)] | ||
|  |         tiles = tiles.copy() | ||
|  |         if self.overlay: | ||
|  |             for n in range(80): | ||
|  |                 self.overlay[n] = tiles[n] | ||
|  |                 if reduce_size: | ||
|  |                     if tiles[n] in {0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, | ||
|  |                                     0x33, 0x34, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, | ||
|  |                                     0x48, 0x49, 0x4B, 0x4C, 0x4E, | ||
|  |                                     0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F}: | ||
|  |                         tiles[n] = 0x3A  # Solid tiles | ||
|  |                     if tiles[n] in {0x08, 0x09, 0x0C, 0x44, | ||
|  |                                     0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF}: | ||
|  |                         tiles[n] = 0x04  # Open tiles | ||
|  | 
 | ||
|  |         is_overworld = isinstance(self.room, str) or self.room < 0x100 | ||
|  |         counts = {} | ||
|  |         for n in tiles: | ||
|  |             if n < 0x0F or is_overworld: | ||
|  |                 counts[n] = counts.get(n, 0) + 1 | ||
|  |         self.floor_object = max(counts, key=counts.get) | ||
|  |         for y in range(8) if is_overworld else range(1, 7): | ||
|  |             for x in range(10) if is_overworld else range(1, 9): | ||
|  |                 if tiles[x + y * 10] == self.floor_object: | ||
|  |                     tiles[x + y * 10] = -1 | ||
|  |         for y in range(8): | ||
|  |             for x in range(10): | ||
|  |                 obj = tiles[x + y * 10] | ||
|  |                 if obj == -1: | ||
|  |                     continue | ||
|  |                 w = 1 | ||
|  |                 h = 1 | ||
|  |                 while x + w < 10 and tiles[x + w + y * 10] == obj: | ||
|  |                     w += 1 | ||
|  |                 while y + h < 8 and tiles[x + (y + h) * 10] == obj: | ||
|  |                     h += 1 | ||
|  |                 if obj in {0xE1, 0xE2, 0xE3, 0xBA, 0xC6}:  # Entrances should never be horizontal/vertical lists | ||
|  |                     w = 1 | ||
|  |                     h = 1 | ||
|  |                 if w > h: | ||
|  |                     for n in range(w): | ||
|  |                         tiles[x + n + y * 10] = -1 | ||
|  |                     self.objects.append(ObjectHorizontal(x, y, obj, w)) | ||
|  |                 elif h > 1: | ||
|  |                     for n in range(h): | ||
|  |                         tiles[x + (y + n) * 10] = -1 | ||
|  |                     self.objects.append(ObjectVertical(x, y, obj, h)) | ||
|  |                 else: | ||
|  |                     self.objects.append(Object(x, y, obj)) | ||
|  | 
 | ||
|  | 
 | ||
|  | class Object: | ||
|  |     def __init__(self, x, y, type_id): | ||
|  |         self.x = x | ||
|  |         self.y = y | ||
|  |         self.type_id = type_id | ||
|  | 
 | ||
|  |     def export(self): | ||
|  |         return bytearray([self.x | (self.y << 4), self.type_id]) | ||
|  | 
 | ||
|  |     def __repr__(self): | ||
|  |         return "%s:%d,%d:%02X" % (self.__class__.__name__, self.x, self.y, self.type_id) | ||
|  | 
 | ||
|  | 
 | ||
|  | class ObjectHorizontal(Object): | ||
|  |     def __init__(self, x, y, type_id, count): | ||
|  |         super().__init__(x, y, type_id) | ||
|  |         self.count = count | ||
|  | 
 | ||
|  |     def export(self): | ||
|  |         return bytearray([0x80 | self.count, self.x | (self.y << 4), self.type_id]) | ||
|  | 
 | ||
|  |     def __repr__(self): | ||
|  |         return "%s:%d,%d:%02Xx%d" % (self.__class__.__name__, self.x, self.y, self.type_id, self.count) | ||
|  | 
 | ||
|  | 
 | ||
|  | class ObjectVertical(Object): | ||
|  |     def __init__(self, x, y, type_id, count): | ||
|  |         super().__init__(x, y, type_id) | ||
|  |         self.count = count | ||
|  | 
 | ||
|  |     def export(self): | ||
|  |         return bytearray([0xC0 | self.count, self.x | (self.y << 4), self.type_id]) | ||
|  | 
 | ||
|  |     def __repr__(self): | ||
|  |         return "%s:%d,%d:%02Xx%d" % (self.__class__.__name__, self.x, self.y, self.type_id, self.count) | ||
|  | 
 | ||
|  | 
 | ||
|  | class ObjectWarp(Object): | ||
|  |     def __init__(self, warp_type, map_nr, room_nr, target_x, target_y): | ||
|  |         super().__init__(None, None, None) | ||
|  |         if warp_type > 0: | ||
|  |             # indoor map | ||
|  |             if map_nr == 0xff: | ||
|  |                 room_nr += 0x300  # color dungeon | ||
|  |             elif 0x06 <= map_nr < 0x1A: | ||
|  |                 room_nr += 0x200  # indoor B | ||
|  |             else: | ||
|  |                 room_nr += 0x100  # indoor A | ||
|  |         self.warp_type = warp_type | ||
|  |         self.room = room_nr | ||
|  |         self.map_nr = map_nr | ||
|  |         self.target_x = target_x | ||
|  |         self.target_y = target_y | ||
|  | 
 | ||
|  |     def export(self): | ||
|  |         return bytearray([0xE0 | self.warp_type, self.map_nr, self.room & 0xFF, self.target_x, self.target_y]) | ||
|  | 
 | ||
|  |     def copy(self): | ||
|  |         return ObjectWarp(self.warp_type, self.map_nr, self.room & 0xFF, self.target_x, self.target_y) | ||
|  | 
 | ||
|  |     def __repr__(self): | ||
|  |         return "%s:%d:%03x:%02x:%d,%d" % (self.__class__.__name__, self.warp_type, self.room, self.map_nr, self.target_x, self.target_y) |