From 71aef12008057a8db6dc1572f3a1d7310024a6ed Mon Sep 17 00:00:00 2001 From: duncathan Date: Sun, 9 Mar 2025 21:34:20 -0600 Subject: [PATCH 1/5] edit minimap tiles in DLR --- src/mars_patcher/constants/minimap_tiles.py | 722 +++++++++++--------- src/mars_patcher/door_locks.py | 138 +++- src/mars_patcher/misc_patches.py | 4 + src/mars_patcher/patcher.py | 19 +- src/mars_patcher/room_entry.py | 8 + 5 files changed, 544 insertions(+), 347 deletions(-) diff --git a/src/mars_patcher/constants/minimap_tiles.py b/src/mars_patcher/constants/minimap_tiles.py index c0b866b..10c540a 100644 --- a/src/mars_patcher/constants/minimap_tiles.py +++ b/src/mars_patcher/constants/minimap_tiles.py @@ -1,326 +1,400 @@ -# Format -# __ - -# Chunk 1 (tile edges) -# - W: wall -# - D: door -# - S: shortcut -# - x: none - -# Chunk 2 (tile corners) -# - C: corner pixel -# - x: none - -# Chunk 3 (tile content) -# - N: navigation room -# - S: save room -# - R: recharge room -# - H: hidden recharge room -# - D: data room -# - I: item -# - B: boss -# - G: gunship -# - P: gunship edge -# - x: none - -COLORED_DOOR_TILES = { - 0x005: "WDxx_xxxx_x", - 0x006: "DDxx_xxxx_x", - 0x007: "DDxx_xxxC_x", - 0x008: "DDDD_xxxx_x", - 0x009: "DDDx_xxxx_x", - 0x00A: "WDxx_xxxx_x", - 0x00B: "DDxx_xxxx_x", - 0x00C: "DDxx_xxxC_x", - 0x00D: "DDDD_xxxx_x", - 0x00E: "DDDx_xxxx_x", - 0x00F: "WDxx_xxxx_x", - 0x010: "DDxx_xxxx_x", - 0x011: "DDxx_xxxC_x", - 0x012: "DDDD_xxxx_x", - 0x013: "DDDx_xxxx_x", - 0x014: "WDxx_xxxx_x", - 0x015: "DDxx_xxxx_x", - 0x016: "DDxx_xxxC_x", - 0x017: "DDDD_xxxx_x", - 0x018: "DDDx_xxxx_x", - 0x019: "DDDx_xxxx_x", - 0x01A: "DDDx_xxxx_x", - 0x01B: "DDDx_xxxx_x", - 0x01C: "DDDx_xxxx_x", - 0x01D: "DDDx_xxxx_x", - 0x01E: "DDDx_xxxx_x", - 0x01F: "DDDD_xxxx_x", - 0x025: "xDxx_xxxx_x", - 0x026: "xDxx_xCxC_x", - 0x027: "DDWD_xxxx_x", - 0x028: "DDDx_xxxx_x", - 0x029: "DDDW_xxxx_x", - 0x02A: "xDxx_xxxx_x", - 0x02B: "xDxx_xCxC_x", - 0x02C: "DDWD_xxxx_x", - 0x02D: "DDDx_xxxx_x", - 0x02E: "DDDW_xxxx_x", - 0x02F: "xDxx_xxxx_x", - 0x030: "xDxx_xCxC_x", - 0x031: "DDWD_xxxx_x", - 0x032: "DDDx_xxxx_x", - 0x033: "DDDW_xxxx_x", - 0x034: "xDxx_xxxx_x", - 0x035: "xDxx_xCxC_x", - 0x036: "DDWD_xxxx_x", - 0x037: "DDDx_xxxx_x", - 0x038: "DDDW_xxxx_x", - 0x039: "DDDW_xxxx_x", - 0x03A: "DDDW_xxxx_x", - 0x03B: "DDDW_xxxx_x", - 0x03C: "DDDW_xxxx_x", - 0x03D: "DDDW_xxxx_x", - 0x03E: "DDDW_xxxx_x", - 0x03F: "DDDD_xxxx_x", - 0x045: "WDxW_xxxx_x", - 0x046: "DDxD_xxxx_x", - 0x047: "DDxW_xxxx_x", - 0x048: "WDWW_xxxx_x", - 0x049: "WDDW_xxxx_x", - 0x04A: "WDxW_xxxx_x", - 0x04B: "DDxD_xxxx_x", - 0x04C: "DDxW_xxxx_x", - 0x04D: "WDWW_xxxx_x", - 0x04E: "WDDW_xxxx_x", - 0x04F: "WDxW_xxxx_x", - 0x050: "DDxD_xxxx_x", - 0x051: "DDxW_xxxx_x", - 0x052: "WDWW_xxxx_x", - 0x053: "WDDW_xxxx_x", - 0x054: "WDxW_xxxx_x", - 0x055: "DDxD_xxxx_x", - 0x056: "DDxW_xxxx_x", - 0x057: "WDWW_xxxx_x", - 0x058: "WDDW_xxxx_x", - 0x059: "WDDW_xxxx_x", - 0x05A: "WDDW_xxxx_x", - 0x05B: "WDDW_xxxx_x", - 0x05C: "WDDW_xxxx_x", - 0x05D: "WDDW_xxxx_x", - 0x05E: "WDDW_xxxx_x", - 0x05F: "DDDD_xxxx_x", - 0x065: "DDWx_xxxx_x", - 0x066: "WDDx_xxxx_x", - 0x068: "WDWx_xxxx_x", - 0x069: "WDDx_xxxx_x", - 0x06A: "DDWx_xxxx_x", - 0x06B: "WDDx_xxxx_x", - 0x06C: "WDxx_xxxC_x", - 0x06D: "WDWx_xxxx_x", - 0x06E: "WDDx_xxxx_x", - 0x06F: "DDWx_xxxx_x", - 0x070: "WDDx_xxxx_x", - 0x072: "WDWx_xxxx_x", - 0x073: "WDDx_xxxx_x", - 0x074: "DDWx_xxxx_x", - 0x075: "WDDx_xxxx_x", - 0x077: "WDWx_xxxx_x", - 0x078: "WDDx_xxxx_x", - 0x079: "WDDx_xxxx_x", - 0x07A: "WDDx_xxxx_x", - 0x07B: "WDDx_xxxx_x", - 0x07C: "WDDx_xxxx_x", - 0x07D: "WDDx_xxxx_x", - 0x07E: "WDDx_xxxx_x", - 0x07F: "DDDD_xxxx_x", - 0x085: "DDWW_xxxx_x", - 0x086: "xDDx_xxxx_x", - 0x088: "xDWx_xxxx_x", - 0x089: "xDDx_xxxx_x", - 0x08A: "DDWW_xxxx_x", - 0x08B: "xDDx_xxxx_x", - 0x08D: "xDWx_xxxx_x", - 0x08E: "xDDx_xxxx_x", - 0x08F: "DDWW_xxxx_x", - 0x090: "xDDx_xxxx_x", - 0x092: "xDWx_xxxx_x", - 0x093: "xDDx_xxxx_x", - 0x094: "DDWW_xxxx_x", - 0x095: "xDDx_xxxx_x", - 0x096: "DDDD_xxxx_x", - 0x097: "xDWx_xxxx_x", - 0x098: "xDDx_xxxx_x", - 0x099: "xDDx_xxxx_x", - 0x09A: "xDDx_xxxx_x", - 0x09B: "xDDx_xxxx_x", - 0x09C: "xDDx_xxxx_x", - 0x09D: "xDDx_xxxx_x", - 0x09E: "xDDx_xxxx_x", - 0x09F: "DDDD_xxxx_x", - 0x0E6: "WDDW_xxxx_x", - 0x0E7: "DDDW_xxxx_x", - 0x0E8: "DDDD_xxxx_x", - 0x0EA: "DDxx_xxxC_x", - 0x0EB: "WDDW_xxxx_x", - 0x0EC: "DDDW_xxxx_x", - 0x0ED: "DDDD_xxxx_x", - 0x0EF: "DDxx_xxxC_x", - 0x0F0: "WDDW_xxxx_x", - 0x0F1: "DDDW_xxxx_x", - 0x0F2: "DDDD_xxxx_x", - 0x0F4: "DDxx_xxxC_x", - 0x0F5: "WDDW_xxxx_x", - 0x0F6: "DDDW_xxxx_x", - 0x0F7: "DDDD_xxxx_x", - 0x0F8: "WDxx_xxxC_x", - 0x128: "WDDW_xxxx_R", - 0x140: "WDWW_xxxx_R", - 0x141: "WWDW_xxxx_R", - 0x142: "WDDW_xxxx_R", - 0x143: "WDWW_xxxx_R", - 0x144: "WWDW_xxxx_R", - 0x145: "WDDW_xxxx_R", - 0x146: "WDWW_xxxx_R", - 0x147: "WWDW_xxxx_R", - 0x148: "WDDW_xxxx_R", - 0x149: "WDWW_xxxx_R", - 0x14A: "WWDW_xxxx_R", - 0x14B: "WDDW_xxxx_R", - 0x14C: "WDWW_xxxx_N", - 0x14D: "WWDW_xxxx_N", - 0x14E: "WDDW_xxxx_N", - 0x14F: "WDWW_xxxx_N", - 0x150: "WWDW_xxxx_N", - 0x151: "WDDW_xxxx_N", - 0x152: "WDWW_xxxx_N", - 0x153: "WWDW_xxxx_N", - 0x154: "WDDW_xxxx_N", - 0x155: "WDWW_xxxx_N", - 0x156: "WWDW_xxxx_N", - 0x157: "WDDW_xxxx_N", - 0x15E: "WDDW_xxxx_R", - 0x160: "WDWW_xxxx_D", - 0x161: "WWDW_xxxx_D", - 0x162: "WDDW_xxxx_D", - 0x163: "WDWW_xxxx_D", - 0x164: "WWDW_xxxx_D", - 0x165: "WDDW_xxxx_D", - 0x166: "WDWW_xxxx_D", - 0x167: "WWDW_xxxx_D", - 0x168: "WDDW_xxxx_D", - 0x169: "WDWW_xxxx_D", - 0x16A: "WWDW_xxxx_D", - 0x16B: "WDDW_xxxx_D", - 0x17E: "WDDW_xxxx_S", - 0x17F: "WDDW_xxxx_S", - 0x198: "WDWW_xxxx_I", - 0x199: "WDWW_xxxx_O", - 0x19E: "WDWW_xxxx_I", - 0x19F: "WDWW_xxxx_O", - 0x1AC: "WDDW_xxxx_I", - 0x1AD: "WDDW_xxxx_O", -} +from __future__ import annotations + +from enum import Enum +from typing import NamedTuple + +# ruff: noqa: E741 + + +class Edge(Enum): + EMPTY = "x" + WALL = "W" + SHORTCUT = "S" + DOOR = "D" + + # aliases + X = EMPTY + W = WALL + S = SHORTCUT + D = DOOR + + +class ColorDoor(Enum): + BLUE = "B" + GREEN = "G" + YELLOW = "Y" + RED = "R" + + # aliases + B = BLUE + G = GREEN + Y = YELLOW + R = RED + + +# aliases for ease of definition +X = Edge.X +W = Edge.W +S = Edge.S +D = Edge.D +BLUE = ColorDoor.B +GREEN = ColorDoor.G +YELLOW = ColorDoor.Y +RED = ColorDoor.R + + +class Content(Enum): + EMPTY = "x" + NAVIGATION = "N" + SAVE = "S" + RECHARGE = "R" + HIDDEN_RECHARGE = "H" + DATA = "D" + ITEM = "I" + OBTAINED_ITEM = "O" + BOSS = "B" + GUNSHIP = "G" + GUNSHIP_EDGE = "P" + + # aliases + X = EMPTY + N = NAVIGATION + S = SAVE + R = RECHARGE + H = HIDDEN_RECHARGE + D = DATA + I = ITEM + O = OBTAINED_ITEM + B = BOSS + G = GUNSHIP + P = GUNSHIP_EDGE + + +class TileEdges(NamedTuple): + top: Edge = Edge.WALL + left: Edge | ColorDoor = Edge.WALL + right: Edge | ColorDoor = Edge.WALL + bottom: Edge = Edge.WALL + + def __str__(self): + return f"{self.top.value}{self.left.value}{self.right.value}{self.bottom.value}" + + def __repr__(self): + return f"{self.__class__.__name__}({str(self)})" + + def h_flip(self) -> TileEdges: + return TileEdges( + top=self.top, + left=self.right, + right=self.left, + bottom=self.bottom, + ) + + def v_flip(self) -> TileEdges: + return TileEdges( + top=self.bottom, + left=self.left, + right=self.right, + bottom=self.top, + ) + + +class TileCorners(NamedTuple): + top_left: bool = False + top_right: bool = False + bottom_left: bool = False + bottom_right: bool = False + + def __str__(self): + def s(corner: bool) -> str: + return "C" if corner else "x" + + return f"{s(self.top_left)}{s(self.top_right)}{s(self.bottom_left)}{s(self.bottom_right)}" + + def __repr__(self): + return f"{self.__class__.__name__}({str(self)})" + + def h_flip(self) -> TileCorners: + return TileCorners( + top_left=self.top_right, + top_right=self.top_left, + bottom_left=self.bottom_right, + bottom_right=self.bottom_left, + ) + + def v_flip(self) -> TileCorners: + return TileCorners( + top_left=self.bottom_left, + top_right=self.bottom_right, + bottom_left=self.top_left, + bottom_right=self.top_right, + ) -NORMAL_DOOR_TILES = { - "WWxx_xxxx_x": 0x000, - "Wxxx_xxxx_x": 0x001, - "DWxx_xxxx_x": 0x002, - "Dxxx_xxxx_x": 0x003, - "DWxx_xxxC_x": 0x004, - "xWxx_xxxx_x": 0x020, - "xDxx_xxxx_x": 0x021, - "Wxxx_xxCC_x": 0x022, - "xWxx_xCxC_x": 0x023, - "DWWD_xxxx_x": 0x024, - "WWxW_xxxx_x": 0x040, - "WxxW_xxxx_x": 0x041, - "DWxD_xxxx_x": 0x042, - "DWxW_xxxx_x": 0x043, - "DxxD_xxxx_x": 0x044, - "DWWx_xxxx_x": 0x060, - "WDWx_xxxx_x": 0x061, - "WDDx_xxxx_x": 0x062, - "WWWx_xxxx_x": 0x063, - "DxxW_xxxx_x": 0x064, - "DWWW_xxxx_x": 0x080, - "xDWx_xxxx_x": 0x081, - "xDDx_xxxx_x": 0x082, - "xWWx_xxxx_x": 0x083, - "WDDW_xxxx_x": 0x084, - "WWWW_xxxx_x": 0x087, - "DDxW_xxxx_x": 0x0E0, - "DDxD_xxxx_x": 0x0E1, - "DDWx_xxxx_x": 0x0E2, - "DDDx_xxxx_x": 0x0E3, - "WWxx_xxxC_x": 0x0E4, - "xWxx_xxxC_x": 0x0E5, - "Dxxx_xxCC_x": 0x100, - "xDxx_xCxC_x": 0x101, - "DDxx_xxxC_x": 0x102, - # "DWxx_xxxC_x": 0x103, # dupe of 0x004 - "WDxx_xxxC_x": 0x104, - "xDxx_xCxx_x": 0x105, - "DDWW_xxxx_x": 0x120, - "DDDW_xxxx_x": 0x121, - "DDWD_xxxx_x": 0x122, - "DDDD_xxxx_x": 0x123, - "WDWW_xxxx_x": 0x124, - "WDxx_xxxx_x": 0x125, - "xxxx_xxxx_x": 0x126, - "Wxxx_xxxC_x": 0x127, - "WDxW_xxxx_P": 0x129, - "WxWW_xxxx_G": 0x12A, - "WDxW_xxxx_x": 0x12B, - "DDxx_xxxx_x": 0x12C, - "WDWW_xxxx_H": 0x138, - "WWDW_xxxx_H": 0x139, - "WDDW_xxxx_H": 0x13A, - "WDWW_xxxx_R": 0x158, - "WWDW_xxxx_R": 0x159, - "WDDW_xxxx_R": 0x15A, - "WDWW_xxxx_D": 0x15B, - "WWDW_xxxx_D": 0x15C, - "WDDW_xxxx_D": 0x15D, - "WDWW_xxxx_N": 0x178, - "WWDW_xxxx_N": 0x179, - "WDDW_xxxx_N": 0x17A, - "WDWW_xxxx_S": 0x17B, - "WWDW_xxxx_S": 0x17C, - "WDDW_xxxx_S": 0x17D, - "WDWW_xxxx_I": 0x180, - "WDWW_xxxx_O": 0x181, - "WWxW_xxxx_I": 0x182, - "WWxW_xxxx_O": 0x183, - "WDDW_xxxx_I": 0x184, - "WDDW_xxxx_O": 0x185, - "WWxx_xxxx_I": 0x186, - "WWxx_xxxx_O": 0x187, - "WDxW_xxxx_I": 0x188, - "WDxW_xxxx_O": 0x189, - "xWxx_xxxx_I": 0x18A, - "xWxx_xxxx_O": 0x18B, - "xDxx_xxxx_I": 0x18C, - "xDxx_xxxx_O": 0x18D, - "WDxx_xxxx_I": 0x18E, - "WDxx_xxxx_O": 0x18F, - "Wxxx_xxxx_I": 0x190, - "Wxxx_xxxx_O": 0x191, - "WxxW_xxxx_I": 0x192, - "WxxW_xxxx_O": 0x193, - "WWWx_xxxx_I": 0x194, - "WWWx_xxxx_O": 0x195, - "DDDW_xxxx_I": 0x196, - "DDDW_xxxx_O": 0x197, - "xDDW_xxxx_I": 0x19A, - "xDDW_xxxx_O": 0x19B, - "WWDx_xxxx_I": 0x19C, - "WWDx_xxxx_O": 0x19D, - "xWWD_xxxx_I": 0x1A0, - "xWWD_xxxx_O": 0x1A1, - "WWWD_xxxx_I": 0x1A2, - "WWWD_xxxx_O": 0x1A3, - "xxxx_xxxx_I": 0x1A4, - "xxxx_xxxx_O": 0x1A5, - "WWxD_xxxx_I": 0x1A6, - "WWxD_xxxx_O": 0x1A7, - "xWWx_xxxx_I": 0x1A8, - "xWWx_xxxx_O": 0x1A9, - "DDWW_xxxx_I": 0x1AA, - "DDWW_xxxx_O": 0x1AB, + +class MapTile(NamedTuple): + edges: TileEdges = TileEdges() + corners: TileCorners = TileCorners() + content: Content = Content.EMPTY + + def __str__(self) -> str: + return f"{self.edges}_{self.corners}_{self.content.value}" + + def __repr__(self): + return f"{self.__class__.__name__}({str(self)})" + + def h_flip(self) -> MapTile: + return MapTile( + edges=self.edges.h_flip(), + corners=self.corners.h_flip(), + content=self.content, + ) + + def v_flip(self) -> MapTile: + return MapTile( + edges=self.edges.v_flip(), + corners=self.corners.v_flip(), + content=self.content, + ) + + +def _tile( + top: Edge, + left: Edge | ColorDoor, + right: Edge | ColorDoor, + bottom: Edge, + top_left: bool = False, + top_right: bool = False, + bottom_left: bool = False, + bottom_right: bool = False, + content: Content = Content.EMPTY, +) -> MapTile: + return MapTile( + edges=TileEdges(top, left, right, bottom), + corners=TileCorners(top_left, top_right, bottom_left, bottom_right), + content=content, + ) + + +ROW_SIZE = 0x20 +COLOR_PAIRS = [ + (BLUE, GREEN), + (BLUE, RED), + (BLUE, YELLOW), + (GREEN, RED), + (GREEN, YELLOW), + (RED, YELLOW), +] +COLOR_BATCHES = [(BLUE, 0x005), (GREEN, 0x00A), (RED, 0x00F), (YELLOW, 0x014)] + +COLORED_DOOR_TILES: dict[int, MapTile] = {} + + +def basic_color_tiles(start: int, color: ColorDoor): + row = ROW_SIZE + tiles = { + row * 0 + 0: _tile(W, color, X, X), + row * 0 + 1: _tile(D, color, X, X), + row * 0 + 2: _tile(D, color, X, X, bottom_right=True), + row * 0 + 3: _tile(D, color, color, D), + row * 0 + 4: _tile(D, color, color, X), + row * 1 + 0: _tile(X, color, X, X), + row * 1 + 1: _tile(X, color, X, X, top_right=True, bottom_right=True), + row * 1 + 2: _tile(D, color, W, D), + row * 1 + 3: _tile(D, color, D, X), + row * 1 + 4: _tile(D, color, color, W), + row * 2 + 0: _tile(W, color, X, W), + row * 2 + 1: _tile(D, color, X, D), + row * 2 + 2: _tile(D, color, X, W), + row * 2 + 3: _tile(W, color, W, W), + row * 2 + 4: _tile(W, color, color, W), + row * 3 + 0: _tile(D, color, W, X), + row * 3 + 1: _tile(W, color, D, X), + # row * 3 + 2: tile(), + row * 3 + 3: _tile(W, color, W, X), + row * 3 + 4: _tile(W, color, color, X), + row * 4 + 0: _tile(D, color, W, W), + row * 4 + 1: _tile(X, color, D, X), + # row * 4 + 2: tile() + row * 4 + 3: _tile(X, color, W, X), + row * 4 + 4: _tile(X, color, color, X), + } + return {start + offset: tile for offset, tile in tiles.items()} + + +# the 5x5 blocks on the top +for color, offset in COLOR_BATCHES: + COLORED_DOOR_TILES.update(basic_color_tiles(offset, color)) + +# the 6x5 block near the top right +for row, (top, bottom) in enumerate([(D, X), (D, W), (W, W), (W, X), (X, X)]): + for col, (left, right) in enumerate(COLOR_PAIRS): + COLORED_DOOR_TILES[0x019 + row * ROW_SIZE + col] = _tile(top, left, right, bottom) + +# the 1x5 block at the very top right +for i, (left, right) in enumerate(COLOR_PAIRS[:-1]): + COLORED_DOOR_TILES[0x01F + i * ROW_SIZE] = _tile(D, left, right, D) +# this one didn't fit in the nice grid pattern so it's in a weird spot +for left, right in COLOR_PAIRS[-1:]: + COLORED_DOOR_TILES[0x096] = _tile(D, left, right, D) + +# the few colored tiles on row 7 +for color, offset in COLOR_BATCHES: + start = ROW_SIZE * 7 + offset + if color != BLUE: + COLORED_DOOR_TILES[start] = _tile(D, color, X, X, bottom_right=True) + COLORED_DOOR_TILES[start + 1] = _tile(W, D, color, W) + COLORED_DOOR_TILES[start + 2] = _tile(D, D, color, W) + COLORED_DOOR_TILES[start + 3] = _tile(D, D, color, D) +COLORED_DOOR_TILES[0x0F8] = _tile(W, YELLOW, X, X, bottom_right=True) +COLORED_DOOR_TILES[0x06C] = _tile(W, GREEN, X, X, bottom_right=True) + + +def special_room_tiles(offset: int, color: ColorDoor, content: Content): + return { + offset + 0: _tile(W, color, W, W, content=content), # left door + offset + 1: _tile(W, W, color, W, content=content), # right door + offset + 2: _tile(W, color, color, W, content=content), # both doors + } + + +# special rooms +for i, (color, _) in enumerate(COLOR_BATCHES): + COLORED_DOOR_TILES.update(special_room_tiles(0x140 + i * 3, color, Content.RECHARGE)) + COLORED_DOOR_TILES.update(special_room_tiles(0x14C + i * 3, color, Content.NAVIGATION)) + COLORED_DOOR_TILES.update(special_room_tiles(0x160 + i * 3, color, Content.DATA)) + +# random exceptions that don't fit into any other category +COLORED_DOOR_TILES.update( + { + 0x128: _tile(W, RED, D, W, content=Content.RECHARGE), + 0x15E: _tile(W, D, YELLOW, W, content=Content.RECHARGE), + 0x17E: _tile(W, RED, RED, W, content=Content.SAVE), + 0x17F: _tile(W, YELLOW, D, W, content=Content.SAVE), + 0x198: _tile(W, BLUE, W, W, content=Content.ITEM), + 0x199: _tile(W, BLUE, W, W, content=Content.OBTAINED_ITEM), + 0x19E: _tile(W, GREEN, W, W, content=Content.ITEM), + 0x19F: _tile(W, GREEN, W, W, content=Content.OBTAINED_ITEM), + 0x1AC: _tile(W, YELLOW, YELLOW, W, content=Content.ITEM), + 0x1AD: _tile(W, YELLOW, YELLOW, W, content=Content.OBTAINED_ITEM), + } +) + +COLORED_DOOR_TILE_IDS = {tile: idx for idx, tile in COLORED_DOOR_TILES.items()} + + +NORMAL_DOOR_TILE_IDS = { + _tile(W, W, X, X): 0x000, + _tile(W, X, X, X): 0x001, + _tile(D, W, X, X): 0x002, + _tile(D, X, X, X): 0x003, + _tile(D, W, X, X, bottom_right=True): 0x004, + _tile(X, W, X, X): 0x020, + _tile(X, D, X, X): 0x021, + _tile(W, X, X, X, bottom_left=True, bottom_right=True): 0x022, + _tile(X, W, X, X, top_right=True, bottom_right=True): 0x023, + _tile(D, W, W, D): 0x024, + _tile(W, W, X, W): 0x040, + _tile(W, X, X, W): 0x041, + _tile(D, W, X, D): 0x042, + _tile(D, W, X, W): 0x043, + _tile(D, X, X, D): 0x044, + _tile(D, W, W, X): 0x060, + _tile(W, D, W, X): 0x061, + _tile(W, D, D, X): 0x062, + _tile(W, W, W, X): 0x063, + _tile(D, X, X, W): 0x064, + _tile(D, W, W, W): 0x080, + _tile(X, D, W, X): 0x081, + _tile(X, D, D, X): 0x082, + _tile(X, W, W, X): 0x083, + _tile(W, D, D, W): 0x084, + _tile(W, W, W, W): 0x087, + _tile(W, S, D, W, content=Content.ITEM): 0x0B4, + _tile(W, S, D, W): 0x0B6, + _tile(D, D, X, W): 0x0E0, + _tile(D, D, X, D): 0x0E1, + _tile(D, D, W, X): 0x0E2, + _tile(D, D, D, X): 0x0E3, + _tile(W, W, X, X, bottom_right=True): 0x0E4, + _tile(X, W, X, X, bottom_right=True): 0x0E5, + _tile(D, X, X, X, bottom_left=True, bottom_right=True): 0x100, + _tile(X, D, X, X, top_right=True, bottom_right=True): 0x101, + _tile(D, D, X, X, bottom_right=True): 0x102, + # tile(D, W, X, X, br=True): 0x103, # dupe of 0x004 + _tile(W, D, X, X, bottom_right=True): 0x104, + _tile(X, D, X, X, top_right=True): 0x105, + _tile(X, X, D, W, content=Content.BOSS): 0x108, + _tile(W, D, X, W, content=Content.BOSS): 0x109, + _tile(X, X, D, X, content=Content.BOSS): 0x10D, + _tile(X, D, X, W, content=Content.BOSS): 0x10F, + _tile(D, D, W, W): 0x120, + _tile(D, D, D, W): 0x121, + _tile(D, D, W, D): 0x122, + _tile(D, D, D, D): 0x123, + _tile(W, D, W, W): 0x124, + _tile(W, D, X, X): 0x125, + _tile(X, X, X, X): 0x126, + _tile(W, X, X, X, bottom_right=True): 0x127, + _tile(W, D, X, W, content=Content.GUNSHIP_EDGE): 0x129, + _tile(W, X, W, W, content=Content.GUNSHIP): 0x12A, + _tile(W, D, X, W): 0x12B, + _tile(D, D, X, X): 0x12C, + _tile(W, D, W, W, content=Content.HIDDEN_RECHARGE): 0x138, + _tile(W, W, D, W, content=Content.HIDDEN_RECHARGE): 0x139, + _tile(W, D, D, W, content=Content.HIDDEN_RECHARGE): 0x13A, + _tile(W, D, W, W, content=Content.RECHARGE): 0x158, + _tile(W, W, D, W, content=Content.RECHARGE): 0x159, + _tile(W, D, D, W, content=Content.RECHARGE): 0x15A, + _tile(W, D, W, W, content=Content.DATA): 0x15B, + _tile(W, W, D, W, content=Content.DATA): 0x15C, + _tile(W, D, D, W, content=Content.DATA): 0x15D, + _tile(W, D, W, W, content=Content.NAVIGATION): 0x178, + _tile(W, W, D, W, content=Content.NAVIGATION): 0x179, + _tile(W, D, D, W, content=Content.NAVIGATION): 0x17A, + _tile(W, D, W, W, content=Content.SAVE): 0x17B, + _tile(W, W, D, W, content=Content.SAVE): 0x17C, + _tile(W, D, D, W, content=Content.SAVE): 0x17D, + _tile(W, D, W, W, content=Content.ITEM): 0x180, + _tile(W, D, W, W, content=Content.OBTAINED_ITEM): 0x181, + _tile(W, W, X, W, content=Content.ITEM): 0x182, + _tile(W, W, X, W, content=Content.OBTAINED_ITEM): 0x183, + _tile(W, D, D, W, content=Content.ITEM): 0x184, + _tile(W, D, D, W, content=Content.OBTAINED_ITEM): 0x185, + _tile(W, W, X, X, content=Content.ITEM): 0x186, + _tile(W, W, X, X, content=Content.OBTAINED_ITEM): 0x187, + _tile(W, D, X, W, content=Content.ITEM): 0x188, + _tile(W, D, X, W, content=Content.OBTAINED_ITEM): 0x189, + _tile(X, W, X, X, content=Content.ITEM): 0x18A, + _tile(X, W, X, X, content=Content.OBTAINED_ITEM): 0x18B, + _tile(X, D, X, X, content=Content.ITEM): 0x18C, + _tile(X, D, X, X, content=Content.OBTAINED_ITEM): 0x18D, + _tile(W, D, X, X, content=Content.ITEM): 0x18E, + _tile(W, D, X, X, content=Content.OBTAINED_ITEM): 0x18F, + _tile(W, X, X, X, content=Content.ITEM): 0x190, + _tile(W, X, X, X, content=Content.OBTAINED_ITEM): 0x191, + _tile(W, X, X, W, content=Content.ITEM): 0x192, + _tile(W, X, X, W, content=Content.OBTAINED_ITEM): 0x193, + _tile(W, W, W, X, content=Content.ITEM): 0x194, + _tile(W, W, W, X, content=Content.OBTAINED_ITEM): 0x195, + _tile(D, D, D, W, content=Content.ITEM): 0x196, + _tile(D, D, D, W, content=Content.OBTAINED_ITEM): 0x197, + _tile(X, D, D, W, content=Content.ITEM): 0x19A, + _tile(X, D, D, W, content=Content.OBTAINED_ITEM): 0x19B, + _tile(W, W, D, X, content=Content.ITEM): 0x19C, + _tile(W, W, D, X, content=Content.OBTAINED_ITEM): 0x19D, + _tile(X, W, W, D, content=Content.ITEM): 0x1A0, + _tile(X, W, W, D, content=Content.OBTAINED_ITEM): 0x1A1, + _tile(W, W, W, D, content=Content.ITEM): 0x1A2, + _tile(W, W, W, D, content=Content.OBTAINED_ITEM): 0x1A3, + _tile(X, X, X, X, content=Content.ITEM): 0x1A4, + _tile(X, X, X, X, content=Content.OBTAINED_ITEM): 0x1A5, + _tile(W, W, X, D, content=Content.ITEM): 0x1A6, + _tile(W, W, X, D, content=Content.OBTAINED_ITEM): 0x1A7, + _tile(X, W, W, X, content=Content.ITEM): 0x1A8, + _tile(X, W, W, X, content=Content.OBTAINED_ITEM): 0x1A9, + _tile(D, D, W, W, content=Content.ITEM): 0x1AA, + _tile(D, D, W, W, content=Content.OBTAINED_ITEM): 0x1AB, } + +ALL_DOOR_TILE_IDS = COLORED_DOOR_TILE_IDS | NORMAL_DOOR_TILE_IDS +ALL_DOOR_TILES = {idx: tile for tile, idx in ALL_DOOR_TILE_IDS.items()} diff --git a/src/mars_patcher/door_locks.py b/src/mars_patcher/door_locks.py index 5037876..265b67d 100644 --- a/src/mars_patcher/door_locks.py +++ b/src/mars_patcher/door_locks.py @@ -1,14 +1,22 @@ +import logging +from collections import defaultdict from enum import Enum +from typing_extensions import Literal + from mars_patcher.auto_generated_types import MarsschemaDoorlocksItem from mars_patcher.constants.game_data import ( area_doors_ptrs, hatch_lock_event_count, hatch_lock_events, - minimap_count, ) -from mars_patcher.constants.minimap_tiles import COLORED_DOOR_TILES, NORMAL_DOOR_TILES -from mars_patcher.minimap import MINIMAP_DIM, Minimap +from mars_patcher.constants.minimap_tiles import ( + ALL_DOOR_TILE_IDS, + ALL_DOOR_TILES, + ColorDoor, + Edge, +) +from mars_patcher.minimap import Minimap from mars_patcher.rom import Rom from mars_patcher.room_entry import BlockLayer, RoomEntry @@ -58,6 +66,19 @@ class HatchLock(Enum): for val in vals: CLIP_TO_HATCH_LOCK[val] = lock + +MAP_EDGES: dict[HatchLock, Edge | ColorDoor] = { + HatchLock.OPEN: Edge.DOOR, + HatchLock.LEVEL_0: Edge.DOOR, + HatchLock.LEVEL_1: ColorDoor.BLUE, + HatchLock.LEVEL_2: ColorDoor.GREEN, + HatchLock.LEVEL_3: ColorDoor.YELLOW, + HatchLock.LEVEL_4: ColorDoor.RED, + # HatchLock.LOCKED: Edge.DOOR, + HatchLock.LOCKED: Edge.WALL, +} + + EXCLUDED_DOORS = { (0, 0xB4), # Restricted lab escape } @@ -78,6 +99,15 @@ def set_door_locks(rom: Rom, data: list[MarsschemaDoorlocksItem]) -> None: new_room_hatch_slots: dict[tuple[int, int], tuple[int, int]] = {} # (AreaID, RoomID): {OrigSlot: NewSlot} hatch_slot_changes: dict[tuple[int, int], dict[int, int]] = {} + + def factory(): + return defaultdict(dict) + + # AreaID: {(MinimapX, MinimapY, RoomID): {"left" | "right": HatchLock}} + minimap_changes: defaultdict[ + int, defaultdict[tuple[int, int, int], dict[Literal["left", "right"], HatchLock]] + ] = defaultdict(factory) + for area in range(7): area_addr = rom.read_ptr(doors_ptrs + area * 4) for door in range(256): @@ -93,7 +123,8 @@ def set_door_locks(rom: Rom, data: list[MarsschemaDoorlocksItem]) -> None: # Skip excluded doors and doors that aren't lockable hatches lock = door_locks.get((area, door)) if (area, door) in EXCLUDED_DOORS or door_type & 0xF != 4: - assert lock is None, f"Area {area} door {door} cannot have its lock changed" + if lock is not None: + logging.error(f"Area {area} door {door} cannot have its lock changed") continue # Load room's BG1 and clipdata if not already loaded area_room = (area, room) @@ -149,6 +180,25 @@ def set_door_locks(rom: Rom, data: list[MarsschemaDoorlocksItem]) -> None: new_room_hatch_slots[area_room] = (capped_slot, capless_slot) if new_hatch_slot != orig_hatch_slot: hatch_slot_changes[area_room][orig_hatch_slot] = new_hatch_slot + + # Map tiles + if lock is not None: + screen_offset_x = (hatch_x - 2) // 15 + screen_offset_y = (hatch_y - 2) // 10 + + minimap_x = room_entry.map_x + screen_offset_x + minimap_y = room_entry.map_y + screen_offset_y + + minimap_areas = [area] + if area == 0: + minimap_areas = [0, 9] # main deck seemingly has two maps? + for minimap_area in minimap_areas: + map_tile = minimap_changes[minimap_area][minimap_x, minimap_y, room] + if facing_right: + map_tile["left"] = lock + else: + map_tile["right"] = lock + # Overwrite BG1 and clipdata if lock is None: # Even if a hatch's lock hasn't changed, its slot may have changed @@ -163,23 +213,83 @@ def set_door_locks(rom: Rom, data: list[MarsschemaDoorlocksItem]) -> None: bg1.set_block_value(hatch_x, hatch_y + y, bg1_val) clip.set_block_value(hatch_x, hatch_y + y, clip_val) bg1_val += 0x10 + # Write BG1 and clipdata for each room for bg1, clip in loaded_bg1_and_clip.values(): bg1.write() clip.write() + fix_hatch_lock_events(rom, hatch_slot_changes) + ANY_DOOR_EDGE = {Edge.DOOR, ColorDoor.B, ColorDoor.G, ColorDoor.Y, ColorDoor.R} + for area, area_map in minimap_changes.items(): + with Minimap(rom, area) as minimap: + for (x, y, room), tile_changes in area_map.items(): + tile_id, palette, h_flip, v_flip = minimap.get_tile_value(x, y) + + tile_data = ALL_DOOR_TILES[tile_id] + edges = tile_data.edges + if "left" in tile_changes: + if h_flip: + edges = edges._replace(right=MAP_EDGES[tile_changes["left"]]) + else: + edges = edges._replace(left=MAP_EDGES[tile_changes["left"]]) + if "right" in tile_changes: + if h_flip: + edges = edges._replace(left=MAP_EDGES[tile_changes["right"]]) + else: + edges = edges._replace(right=MAP_EDGES[tile_changes["right"]]) + og_new_tile_data = tile_data._replace(edges=edges) + new_tile_data = og_new_tile_data + + if new_tile_data not in ALL_DOOR_TILE_IDS: + # try flipping it + new_tile_data = og_new_tile_data.h_flip() + if new_tile_data in ALL_DOOR_TILE_IDS: + h_flip = not h_flip + + if new_tile_data not in ALL_DOOR_TILE_IDS: + # try flipping it (the other way) + new_tile_data = og_new_tile_data.v_flip() + if new_tile_data in ALL_DOOR_TILE_IDS: + v_flip = not v_flip + + if new_tile_data not in ALL_DOOR_TILE_IDS: + # try flipping it (both ways) + new_tile_data = og_new_tile_data.v_flip() + new_tile_data = new_tile_data.h_flip() + if new_tile_data in ALL_DOOR_TILE_IDS: + v_flip = not v_flip + h_flip = not h_flip + + if new_tile_data not in ALL_DOOR_TILE_IDS: + logging.debug( + f"Could not edit map tile door icons for area {area} tile ({x:X}, {y:X})." + ) + logging.debug(f" Desired tile: {str(og_new_tile_data)}") + logging.debug(" Falling back to unlocked doors.") + + # try replacing with open doors + if "left" in tile_changes and tile_data.edges.left in ANY_DOOR_EDGE: + edges = edges._replace(left=Edge.DOOR) + if "right" in tile_changes and tile_data.edges.right in ANY_DOOR_EDGE: + edges = edges._replace(right=Edge.DOOR) + new_tile_data = og_new_tile_data._replace(edges=edges) + + if new_tile_data not in ALL_DOOR_TILE_IDS: + logging.debug(" Still no luck. Using vanilla tile.") + + logging.debug("") -def remove_door_colors_on_minimap(rom: Rom) -> None: - for id in range(minimap_count(rom)): - with Minimap(rom, id) as minimap: - for y in range(MINIMAP_DIM): - for x in range(MINIMAP_DIM): - tile, pal, h_flip, v_flip = minimap.get_tile_value(x, y) - tile_type = COLORED_DOOR_TILES.get(tile) - if tile_type is not None: - tile = NORMAL_DOOR_TILES[tile_type] - minimap.set_tile_value(x, y, tile, pal, h_flip, v_flip) + if new_tile_data in ALL_DOOR_TILE_IDS: + minimap.set_tile_value( + x, + y, + ALL_DOOR_TILE_IDS[new_tile_data], + palette, + h_flip, + v_flip, + ) def parse_door_lock_data(data: list[MarsschemaDoorlocksItem]) -> dict[tuple[int, int], HatchLock]: diff --git a/src/mars_patcher/misc_patches.py b/src/mars_patcher/misc_patches.py index 9742dce..5e67833 100644 --- a/src/mars_patcher/misc_patches.py +++ b/src/mars_patcher/misc_patches.py @@ -89,3 +89,7 @@ def apply_anti_softlock_edits(rom: Rom) -> None: def apply_reveal_hidden_tiles(rom: Rom) -> None: rom.write_8(ReservedConstants.REVEAL_HIDDEN_TILES_ADDR, 1) + + +def apply_reveal_unexplored_doors(rom: Rom) -> None: + apply_patch_in_asm_path(rom, "unhidden_map_doors.ips") diff --git a/src/mars_patcher/patcher.py b/src/mars_patcher/patcher.py index a18b42f..2b05bda 100644 --- a/src/mars_patcher/patcher.py +++ b/src/mars_patcher/patcher.py @@ -7,7 +7,7 @@ from mars_patcher.connections import Connections from mars_patcher.credits import write_credits from mars_patcher.data import get_data_path -from mars_patcher.door_locks import remove_door_colors_on_minimap, set_door_locks +from mars_patcher.door_locks import set_door_locks from mars_patcher.item_patcher import ItemPatcher, set_required_metroid_count, set_tank_increments from mars_patcher.level_edits import apply_level_edits from mars_patcher.locations import LocationSettings @@ -121,11 +121,6 @@ def patch( conns = Connections(rom) conns.set_shortcut_connections(patch_data["SectorShortcuts"]) - # Door locks - if door_locks := patch_data.get("DoorLocks", []): - status_update("Writing door locks...", -1) - set_door_locks(rom, door_locks) - # Hints if nav_text := patch_data.get("NavigationText", {}): status_update("Writing navigation text...", -1) @@ -172,18 +167,24 @@ def patch( if patch_data.get("UnexploredMap"): apply_unexplored_map(rom) + # TODO: https://github.com/MetroidAdvRandomizerSystem/mars-fusion-asm/pull/217 + # if not "HideDoorsOnMinimap" in patch_data: + # apply_reveal_unexplored_doors(rom) + if patch_data.get("RevealHiddenTiles"): apply_reveal_hidden_tiles(rom) - if patch_data.get("DoorLocks") or "HideDoorsOnMinimap" in patch_data: - remove_door_colors_on_minimap(rom) - if "LevelEdits" in patch_data: apply_level_edits(rom, patch_data["LevelEdits"]) if "MinimapEdits" in patch_data: apply_minimap_edits(rom, patch_data["MinimapEdits"]) + # Door locks + if door_locks := patch_data.get("DoorLocks", []): + status_update("Writing door locks...", -1) + set_door_locks(rom, door_locks) + write_seed_hash(rom, patch_data["SeedHash"]) rom.save(output_path) diff --git a/src/mars_patcher/room_entry.py b/src/mars_patcher/room_entry.py index 4f55b31..e518cf2 100644 --- a/src/mars_patcher/room_entry.py +++ b/src/mars_patcher/room_entry.py @@ -52,6 +52,14 @@ def load_bg2(self) -> BlockLayer: def load_clip(self) -> BlockLayer: return BlockLayer(self.rom, self.clip_ptr()) + @property + def map_x(self) -> int: + return self.rom.read_8(self.addr + 0x35) + + @property + def map_y(self) -> int: + return self.rom.read_8(self.addr + 0x36) + class BlockLayer: def __enter__(self) -> BlockLayer: From 72dbcf21ff7ebfd07934fbe0e8dfb086a38e94d5 Mon Sep 17 00:00:00 2001 From: duncathan Date: Mon, 10 Mar 2025 09:12:45 -0600 Subject: [PATCH 2/5] improve readability --- src/mars_patcher/common_types.py | 12 ++ src/mars_patcher/constants/minimap_tiles.py | 54 +++--- src/mars_patcher/door_locks.py | 181 ++++++++++++-------- src/mars_patcher/minimap.py | 3 +- 4 files changed, 141 insertions(+), 109 deletions(-) create mode 100644 src/mars_patcher/common_types.py diff --git a/src/mars_patcher/common_types.py b/src/mars_patcher/common_types.py new file mode 100644 index 0000000..6be881b --- /dev/null +++ b/src/mars_patcher/common_types.py @@ -0,0 +1,12 @@ +from typing import Annotated + +from typing_extensions import TypeAlias + +from mars_patcher.auto_generated_types import Areaid, Typeu8 + +AreaId: TypeAlias = Areaid +RoomId: TypeAlias = Typeu8 + +AreaRoomPair = tuple[AreaId, RoomId] + +MinimapId: TypeAlias = Annotated[int, "0 <= value < 10"] diff --git a/src/mars_patcher/constants/minimap_tiles.py b/src/mars_patcher/constants/minimap_tiles.py index 10c540a..4935188 100644 --- a/src/mars_patcher/constants/minimap_tiles.py +++ b/src/mars_patcher/constants/minimap_tiles.py @@ -12,12 +12,6 @@ class Edge(Enum): SHORTCUT = "S" DOOR = "D" - # aliases - X = EMPTY - W = WALL - S = SHORTCUT - D = DOOR - class ColorDoor(Enum): BLUE = "B" @@ -25,22 +19,27 @@ class ColorDoor(Enum): YELLOW = "Y" RED = "R" - # aliases - B = BLUE - G = GREEN - Y = YELLOW - R = RED + +AnyEdge = Edge | ColorDoor # aliases for ease of definition -X = Edge.X -W = Edge.W -S = Edge.S -D = Edge.D -BLUE = ColorDoor.B -GREEN = ColorDoor.G -YELLOW = ColorDoor.Y -RED = ColorDoor.R +X = Edge.EMPTY +W = Edge.WALL +S = Edge.SHORTCUT +D = Edge.DOOR +BLUE = ColorDoor.BLUE +GREEN = ColorDoor.GREEN +YELLOW = ColorDoor.YELLOW +RED = ColorDoor.RED + +ANY_DOOR_EDGE: set[AnyEdge] = { + Edge.DOOR, + BLUE, + GREEN, + YELLOW, + RED, +} class Content(Enum): @@ -56,24 +55,11 @@ class Content(Enum): GUNSHIP = "G" GUNSHIP_EDGE = "P" - # aliases - X = EMPTY - N = NAVIGATION - S = SAVE - R = RECHARGE - H = HIDDEN_RECHARGE - D = DATA - I = ITEM - O = OBTAINED_ITEM - B = BOSS - G = GUNSHIP - P = GUNSHIP_EDGE - class TileEdges(NamedTuple): top: Edge = Edge.WALL - left: Edge | ColorDoor = Edge.WALL - right: Edge | ColorDoor = Edge.WALL + left: AnyEdge = Edge.WALL + right: AnyEdge = Edge.WALL bottom: Edge = Edge.WALL def __str__(self): diff --git a/src/mars_patcher/door_locks.py b/src/mars_patcher/door_locks.py index 265b67d..a0dca89 100644 --- a/src/mars_patcher/door_locks.py +++ b/src/mars_patcher/door_locks.py @@ -1,10 +1,10 @@ import logging from collections import defaultdict from enum import Enum - -from typing_extensions import Literal +from typing import Annotated, TypedDict from mars_patcher.auto_generated_types import MarsschemaDoorlocksItem +from mars_patcher.common_types import AreaId, AreaRoomPair, RoomId from mars_patcher.constants.game_data import ( area_doors_ptrs, hatch_lock_event_count, @@ -13,6 +13,8 @@ from mars_patcher.constants.minimap_tiles import ( ALL_DOOR_TILE_IDS, ALL_DOOR_TILES, + ANY_DOOR_EDGE, + AnyEdge, ColorDoor, Edge, ) @@ -67,65 +69,70 @@ class HatchLock(Enum): CLIP_TO_HATCH_LOCK[val] = lock -MAP_EDGES: dict[HatchLock, Edge | ColorDoor] = { - HatchLock.OPEN: Edge.DOOR, - HatchLock.LEVEL_0: Edge.DOOR, - HatchLock.LEVEL_1: ColorDoor.BLUE, - HatchLock.LEVEL_2: ColorDoor.GREEN, - HatchLock.LEVEL_3: ColorDoor.YELLOW, - HatchLock.LEVEL_4: ColorDoor.RED, - # HatchLock.LOCKED: Edge.DOOR, - HatchLock.LOCKED: Edge.WALL, -} - - EXCLUDED_DOORS = { (0, 0xB4), # Restricted lab escape } +HatchSlot = Annotated[int, "0 <= value <= 5"] + +MinimapLocation = tuple[int, int, RoomId] +"""`(X, Y, RoomId)`""" + + +class MinimapLockChanges(TypedDict, total=False): + left: HatchLock + right: HatchLock + # TODO: # - Optimize by only loading rooms that contain doors to modify # - Split into more than one function for readability def set_door_locks(rom: Rom, data: list[MarsschemaDoorlocksItem]) -> None: door_locks = parse_door_lock_data(data) + # Go through all doors in game in order doors_ptrs = area_doors_ptrs(rom) - loaded_rooms: dict[tuple[int, int], RoomEntry] = {} + loaded_rooms: dict[AreaRoomPair, RoomEntry] = {} + # (AreaID, RoomID): (BG1, Clipdata) - loaded_bg1_and_clip: dict[tuple[int, int], tuple[BlockLayer, BlockLayer]] = {} + loaded_bg1_and_clip: dict[AreaRoomPair, tuple[BlockLayer, BlockLayer]] = {} + # (AreaID, RoomID): (CappedSlot, CaplessSlot) - orig_room_hatch_slots: dict[tuple[int, int], tuple[int, int]] = {} - new_room_hatch_slots: dict[tuple[int, int], tuple[int, int]] = {} - # (AreaID, RoomID): {OrigSlot: NewSlot} - hatch_slot_changes: dict[tuple[int, int], dict[int, int]] = {} + orig_room_hatch_slots: dict[AreaRoomPair, tuple[HatchSlot, HatchSlot]] = {} + new_room_hatch_slots: dict[AreaRoomPair, tuple[HatchSlot, HatchSlot]] = {} + + hatch_slot_changes: dict[AreaRoomPair, dict[HatchSlot, HatchSlot]] = {} def factory(): return defaultdict(dict) # AreaID: {(MinimapX, MinimapY, RoomID): {"left" | "right": HatchLock}} - minimap_changes: defaultdict[ - int, defaultdict[tuple[int, int, int], dict[Literal["left", "right"], HatchLock]] - ] = defaultdict(factory) + minimap_changes: defaultdict[AreaId, defaultdict[MinimapLocation, MinimapLockChanges]] = ( + defaultdict(factory) + ) for area in range(7): area_addr = rom.read_ptr(doors_ptrs + area * 4) for door in range(256): door_addr = area_addr + door * 0xC door_type = rom.read_8(door_addr) + # Check if at end of list if door_type == 0: break + # Skip doors that mage marks as deleted room = rom.read_8(door_addr + 1) if room == 0xFF: continue + # Skip excluded doors and doors that aren't lockable hatches lock = door_locks.get((area, door)) if (area, door) in EXCLUDED_DOORS or door_type & 0xF != 4: if lock is not None: logging.error(f"Area {area} door {door} cannot have its lock changed") continue + # Load room's BG1 and clipdata if not already loaded area_room = (area, room) room_entry = loaded_rooms.get(area_room) @@ -147,9 +154,11 @@ def factory(): x_exit = rom.read_8(door_addr + 7) facing_right = x_exit < 0x80 dx = 1 if facing_right else -1 + # Get hatch position hatch_x = rom.read_8(door_addr + 2) + dx hatch_y = rom.read_8(door_addr + 4) + # Get original hatch slot number capped_slot, capless_slot = orig_room_hatch_slots[area_room] clip_val = clip.get_block_value(hatch_x, hatch_y) @@ -163,6 +172,7 @@ def factory(): orig_hatch_slot = capless_slot capless_slot -= 1 orig_room_hatch_slots[area_room] = (capped_slot, capless_slot) + # Get new hatch slot number capped_slot, capless_slot = new_room_hatch_slots[area_room] if lock == HatchLock.LOCKED: @@ -178,6 +188,7 @@ def factory(): new_hatch_slot = capless_slot capless_slot -= 1 new_room_hatch_slots[area_room] = (capped_slot, capless_slot) + if new_hatch_slot != orig_hatch_slot: hatch_slot_changes[area_room][orig_hatch_slot] = new_hatch_slot @@ -205,10 +216,13 @@ def factory(): lock = CLIP_TO_HATCH_LOCK.get(clip_val) if lock is None: continue + bg1_val = BG1_VALUES[lock] if facing_right: bg1_val += 1 + clip_val = CLIP_VALUES[lock][new_hatch_slot] + for y in range(4): bg1.set_block_value(hatch_x, hatch_y + y, bg1_val) clip.set_block_value(hatch_x, hatch_y + y, clip_val) @@ -221,41 +235,93 @@ def factory(): fix_hatch_lock_events(rom, hatch_slot_changes) - ANY_DOOR_EDGE = {Edge.DOOR, ColorDoor.B, ColorDoor.G, ColorDoor.Y, ColorDoor.R} + change_minimap_tiles(rom, minimap_changes) + + +def parse_door_lock_data(data: list[MarsschemaDoorlocksItem]) -> dict[AreaRoomPair, HatchLock]: + """Returns a dictionary of `(AreaID, RoomID): HatchLock` from the input data.""" + door_locks: dict[AreaRoomPair, HatchLock] = {} + for entry in data: + area_door = (entry["Area"], entry["Door"]) + lock = HATCH_LOCK_ENUMS[entry["LockType"]] + door_locks[area_door] = lock + return door_locks + + +def fix_hatch_lock_events( + rom: Rom, hatch_slot_changes: dict[AreaRoomPair, dict[HatchSlot, HatchSlot]] +) -> None: + hatch_locks_addr = hatch_lock_events(rom) + count = hatch_lock_event_count(rom) + for i in range(count): + addr = hatch_locks_addr + i * 5 + area = rom.read_8(addr + 1) + room = rom.read_8(addr + 2) - 1 + changes = hatch_slot_changes.get((area, room)) + # Some rooms no longer have doors in rando + if changes is None: + continue + hatch_flags = rom.read_8(addr + 3) + new_flags = 0 + remain = (1 << 6) - 1 + for prev_slot, new_slot in changes.items(): + if (1 << prev_slot) & hatch_flags != 0: + new_flags |= 1 << new_slot + remain &= ~(1 << new_slot) + new_flags |= hatch_flags & remain + rom.write_8(addr + 3, new_flags) + + +def change_minimap_tiles( + rom: Rom, minimap_changes: dict[AreaId, dict[MinimapLocation, MinimapLockChanges]] +): + MAP_EDGES: dict[HatchLock, AnyEdge] = { + HatchLock.OPEN: Edge.DOOR, + HatchLock.LEVEL_0: Edge.DOOR, + HatchLock.LEVEL_1: ColorDoor.BLUE, + HatchLock.LEVEL_2: ColorDoor.GREEN, + HatchLock.LEVEL_3: ColorDoor.YELLOW, + HatchLock.LEVEL_4: ColorDoor.RED, + HatchLock.LOCKED: Edge.DOOR, + # HatchLock.LOCKED: Edge.WALL, + } + for area, area_map in minimap_changes.items(): with Minimap(rom, area) as minimap: for (x, y, room), tile_changes in area_map.items(): tile_id, palette, h_flip, v_flip = minimap.get_tile_value(x, y) tile_data = ALL_DOOR_TILES[tile_id] + + # account for h_flip before changing edges + left = tile_changes.get("left") + right = tile_changes.get("right") + if h_flip: + left, right = right, left + + # replace edges edges = tile_data.edges - if "left" in tile_changes: - if h_flip: - edges = edges._replace(right=MAP_EDGES[tile_changes["left"]]) - else: - edges = edges._replace(left=MAP_EDGES[tile_changes["left"]]) - if "right" in tile_changes: - if h_flip: - edges = edges._replace(left=MAP_EDGES[tile_changes["right"]]) - else: - edges = edges._replace(right=MAP_EDGES[tile_changes["right"]]) + if left is not None: + edges = edges._replace(left=MAP_EDGES[left]) + if right is not None: + edges = edges._replace(right=MAP_EDGES[right]) og_new_tile_data = tile_data._replace(edges=edges) new_tile_data = og_new_tile_data if new_tile_data not in ALL_DOOR_TILE_IDS: - # try flipping it + # try flipping horizontally new_tile_data = og_new_tile_data.h_flip() if new_tile_data in ALL_DOOR_TILE_IDS: h_flip = not h_flip if new_tile_data not in ALL_DOOR_TILE_IDS: - # try flipping it (the other way) + # try flipping vertically new_tile_data = og_new_tile_data.v_flip() if new_tile_data in ALL_DOOR_TILE_IDS: v_flip = not v_flip if new_tile_data not in ALL_DOOR_TILE_IDS: - # try flipping it (both ways) + # try flipping it both ways new_tile_data = og_new_tile_data.v_flip() new_tile_data = new_tile_data.h_flip() if new_tile_data in ALL_DOOR_TILE_IDS: @@ -264,15 +330,16 @@ def factory(): if new_tile_data not in ALL_DOOR_TILE_IDS: logging.debug( - f"Could not edit map tile door icons for area {area} tile ({x:X}, {y:X})." + "Could not edit map tile door icons for " + f"area {area} room {room:X}. ({x:X}, {y:X})." ) logging.debug(f" Desired tile: {str(og_new_tile_data)}") logging.debug(" Falling back to unlocked doors.") # try replacing with open doors - if "left" in tile_changes and tile_data.edges.left in ANY_DOOR_EDGE: + if (left is not None) and (tile_data.edges.left in ANY_DOOR_EDGE): edges = edges._replace(left=Edge.DOOR) - if "right" in tile_changes and tile_data.edges.right in ANY_DOOR_EDGE: + if (right is not None) and (tile_data.edges.right in ANY_DOOR_EDGE): edges = edges._replace(right=Edge.DOOR) new_tile_data = og_new_tile_data._replace(edges=edges) @@ -290,37 +357,3 @@ def factory(): h_flip, v_flip, ) - - -def parse_door_lock_data(data: list[MarsschemaDoorlocksItem]) -> dict[tuple[int, int], HatchLock]: - """Returns a dictionary of `(AreaID, RoomID): HatchLock` from the input data.""" - door_locks: dict[tuple[int, int], HatchLock] = {} - for entry in data: - area_door = (entry["Area"], entry["Door"]) - lock = HATCH_LOCK_ENUMS[entry["LockType"]] - door_locks[area_door] = lock - return door_locks - - -def fix_hatch_lock_events( - rom: Rom, hatch_slot_changes: dict[tuple[int, int], dict[int, int]] -) -> None: - hatch_locks_addr = hatch_lock_events(rom) - count = hatch_lock_event_count(rom) - for i in range(count): - addr = hatch_locks_addr + i * 5 - area = rom.read_8(addr + 1) - room = rom.read_8(addr + 2) - 1 - changes = hatch_slot_changes.get((area, room)) - # Some rooms no longer have doors in rando - if changes is None: - continue - hatch_flags = rom.read_8(addr + 3) - new_flags = 0 - remain = (1 << 6) - 1 - for prev_slot, new_slot in changes.items(): - if (1 << prev_slot) & hatch_flags != 0: - new_flags |= 1 << new_slot - remain &= ~(1 << new_slot) - new_flags |= hatch_flags & remain - rom.write_8(addr + 3, new_flags) diff --git a/src/mars_patcher/minimap.py b/src/mars_patcher/minimap.py index 403009f..14cdc94 100644 --- a/src/mars_patcher/minimap.py +++ b/src/mars_patcher/minimap.py @@ -8,6 +8,7 @@ if TYPE_CHECKING: from types import TracebackType + from mars_patcher.common_types import MinimapId from mars_patcher.rom import Rom MINIMAP_DIM = 32 @@ -16,7 +17,7 @@ class Minimap: """Class for reading/writing minimap data and setting tiles.""" - def __init__(self, rom: Rom, id: int): + def __init__(self, rom: Rom, id: MinimapId): self.rom = rom self.pointer = minimap_ptrs(rom) + (id * 4) addr = rom.read_ptr(self.pointer) From bdf5d47dba359b18ca9d5a0ddfea60764f79b4e0 Mon Sep 17 00:00:00 2001 From: duncathan Date: Mon, 10 Mar 2025 12:47:09 -0600 Subject: [PATCH 3/5] cleanup and review comments --- src/mars_patcher/constants/minimap_tiles.py | 680 +++++++++++--------- src/mars_patcher/door_locks.py | 34 +- 2 files changed, 409 insertions(+), 305 deletions(-) diff --git a/src/mars_patcher/constants/minimap_tiles.py b/src/mars_patcher/constants/minimap_tiles.py index 4935188..aaf0161 100644 --- a/src/mars_patcher/constants/minimap_tiles.py +++ b/src/mars_patcher/constants/minimap_tiles.py @@ -3,70 +3,67 @@ from enum import Enum from typing import NamedTuple -# ruff: noqa: E741 +from typing_extensions import Self +# Edges class Edge(Enum): EMPTY = "x" WALL = "W" SHORTCUT = "S" DOOR = "D" + @property + def is_door(self) -> bool: + return self == Edge.DOOR -class ColorDoor(Enum): + +class ColoredDoor(Enum): BLUE = "B" GREEN = "G" YELLOW = "Y" RED = "R" + @property + def is_door(self) -> bool: + return True -AnyEdge = Edge | ColorDoor - - -# aliases for ease of definition -X = Edge.EMPTY -W = Edge.WALL -S = Edge.SHORTCUT -D = Edge.DOOR -BLUE = ColorDoor.BLUE -GREEN = ColorDoor.GREEN -YELLOW = ColorDoor.YELLOW -RED = ColorDoor.RED - -ANY_DOOR_EDGE: set[AnyEdge] = { - Edge.DOOR, - BLUE, - GREEN, - YELLOW, - RED, -} - - -class Content(Enum): - EMPTY = "x" - NAVIGATION = "N" - SAVE = "S" - RECHARGE = "R" - HIDDEN_RECHARGE = "H" - DATA = "D" - ITEM = "I" - OBTAINED_ITEM = "O" - BOSS = "B" - GUNSHIP = "G" - GUNSHIP_EDGE = "P" + # Aliases + L1 = BLUE + L2 = GREEN + L3 = YELLOW + L4 = RED class TileEdges(NamedTuple): top: Edge = Edge.WALL - left: AnyEdge = Edge.WALL - right: AnyEdge = Edge.WALL + left: Edge | ColoredDoor = Edge.WALL + right: Edge | ColoredDoor = Edge.WALL bottom: Edge = Edge.WALL - def __str__(self): + @property + def as_str(self) -> str: return f"{self.top.value}{self.left.value}{self.right.value}{self.bottom.value}" - def __repr__(self): - return f"{self.__class__.__name__}({str(self)})" + @classmethod + def from_str(cls, value: str) -> Self: + if len(value) != 4: + raise ValueError(f"'{value}' is not a valid TileEdges string") + top, left, right, bottom = value + + def any_edge_from_value(v: str) -> Edge | ColoredDoor: + try: + edge = Edge(v) + except ValueError: + edge = ColoredDoor(v) + return edge + + return cls( + top=Edge(top), + left=any_edge_from_value(left), + right=any_edge_from_value(right), + bottom=Edge(bottom), + ) def h_flip(self) -> TileEdges: return TileEdges( @@ -85,20 +82,31 @@ def v_flip(self) -> TileEdges: ) +# Corners class TileCorners(NamedTuple): top_left: bool = False top_right: bool = False bottom_left: bool = False bottom_right: bool = False - def __str__(self): + @property + def as_str(self) -> str: def s(corner: bool) -> str: return "C" if corner else "x" return f"{s(self.top_left)}{s(self.top_right)}{s(self.bottom_left)}{s(self.bottom_right)}" - def __repr__(self): - return f"{self.__class__.__name__}({str(self)})" + @classmethod + def from_str(cls, value: str) -> Self: + if len(value) != 4: + raise ValueError(f"'{value}' is not a valid TileCorners string") + tl, tr, bl, br = value + return cls( + top_left=(tl == "C"), + top_right=(tr == "C"), + bottom_left=(bl == "C"), + bottom_right=(br == "C"), + ) def h_flip(self) -> TileCorners: return TileCorners( @@ -117,16 +125,41 @@ def v_flip(self) -> TileCorners: ) +# Contents +class Content(Enum): + EMPTY = "x" + NAVIGATION = "N" + SAVE = "S" + RECHARGE = "R" + HIDDEN_RECHARGE = "H" + DATA = "D" + ITEM = "I" + OBTAINED_ITEM = "O" + BOSS = "B" + GUNSHIP = "G" + GUNSHIP_EDGE = "P" + + +# Tile class MapTile(NamedTuple): edges: TileEdges = TileEdges() corners: TileCorners = TileCorners() content: Content = Content.EMPTY - def __str__(self) -> str: - return f"{self.edges}_{self.corners}_{self.content.value}" - - def __repr__(self): - return f"{self.__class__.__name__}({str(self)})" + @property + def as_str(self) -> str: + return f"{self.edges.as_str}_{self.corners.as_str}_{self.content.value}" + + @classmethod + def from_str(cls, value: str) -> Self: + if len(value) != 11: + raise ValueError(f"'{value}' is not a valid MapTile string") + edges, corners, content = value.split("_") + return cls( + edges=TileEdges.from_str(edges), + corners=TileCorners.from_str(corners), + content=Content(content), + ) def h_flip(self) -> MapTile: return MapTile( @@ -143,244 +176,317 @@ def v_flip(self) -> MapTile: ) -def _tile( - top: Edge, - left: Edge | ColorDoor, - right: Edge | ColorDoor, - bottom: Edge, - top_left: bool = False, - top_right: bool = False, - bottom_left: bool = False, - bottom_right: bool = False, - content: Content = Content.EMPTY, -) -> MapTile: - return MapTile( - edges=TileEdges(top, left, right, bottom), - corners=TileCorners(top_left, top_right, bottom_left, bottom_right), - content=content, - ) - - -ROW_SIZE = 0x20 -COLOR_PAIRS = [ - (BLUE, GREEN), - (BLUE, RED), - (BLUE, YELLOW), - (GREEN, RED), - (GREEN, YELLOW), - (RED, YELLOW), -] -COLOR_BATCHES = [(BLUE, 0x005), (GREEN, 0x00A), (RED, 0x00F), (YELLOW, 0x014)] - -COLORED_DOOR_TILES: dict[int, MapTile] = {} - - -def basic_color_tiles(start: int, color: ColorDoor): - row = ROW_SIZE - tiles = { - row * 0 + 0: _tile(W, color, X, X), - row * 0 + 1: _tile(D, color, X, X), - row * 0 + 2: _tile(D, color, X, X, bottom_right=True), - row * 0 + 3: _tile(D, color, color, D), - row * 0 + 4: _tile(D, color, color, X), - row * 1 + 0: _tile(X, color, X, X), - row * 1 + 1: _tile(X, color, X, X, top_right=True, bottom_right=True), - row * 1 + 2: _tile(D, color, W, D), - row * 1 + 3: _tile(D, color, D, X), - row * 1 + 4: _tile(D, color, color, W), - row * 2 + 0: _tile(W, color, X, W), - row * 2 + 1: _tile(D, color, X, D), - row * 2 + 2: _tile(D, color, X, W), - row * 2 + 3: _tile(W, color, W, W), - row * 2 + 4: _tile(W, color, color, W), - row * 3 + 0: _tile(D, color, W, X), - row * 3 + 1: _tile(W, color, D, X), - # row * 3 + 2: tile(), - row * 3 + 3: _tile(W, color, W, X), - row * 3 + 4: _tile(W, color, color, X), - row * 4 + 0: _tile(D, color, W, W), - row * 4 + 1: _tile(X, color, D, X), - # row * 4 + 2: tile() - row * 4 + 3: _tile(X, color, W, X), - row * 4 + 4: _tile(X, color, color, X), - } - return {start + offset: tile for offset, tile in tiles.items()} - - -# the 5x5 blocks on the top -for color, offset in COLOR_BATCHES: - COLORED_DOOR_TILES.update(basic_color_tiles(offset, color)) - -# the 6x5 block near the top right -for row, (top, bottom) in enumerate([(D, X), (D, W), (W, W), (W, X), (X, X)]): - for col, (left, right) in enumerate(COLOR_PAIRS): - COLORED_DOOR_TILES[0x019 + row * ROW_SIZE + col] = _tile(top, left, right, bottom) - -# the 1x5 block at the very top right -for i, (left, right) in enumerate(COLOR_PAIRS[:-1]): - COLORED_DOOR_TILES[0x01F + i * ROW_SIZE] = _tile(D, left, right, D) -# this one didn't fit in the nice grid pattern so it's in a weird spot -for left, right in COLOR_PAIRS[-1:]: - COLORED_DOOR_TILES[0x096] = _tile(D, left, right, D) - -# the few colored tiles on row 7 -for color, offset in COLOR_BATCHES: - start = ROW_SIZE * 7 + offset - if color != BLUE: - COLORED_DOOR_TILES[start] = _tile(D, color, X, X, bottom_right=True) - COLORED_DOOR_TILES[start + 1] = _tile(W, D, color, W) - COLORED_DOOR_TILES[start + 2] = _tile(D, D, color, W) - COLORED_DOOR_TILES[start + 3] = _tile(D, D, color, D) -COLORED_DOOR_TILES[0x0F8] = _tile(W, YELLOW, X, X, bottom_right=True) -COLORED_DOOR_TILES[0x06C] = _tile(W, GREEN, X, X, bottom_right=True) - - -def special_room_tiles(offset: int, color: ColorDoor, content: Content): - return { - offset + 0: _tile(W, color, W, W, content=content), # left door - offset + 1: _tile(W, W, color, W, content=content), # right door - offset + 2: _tile(W, color, color, W, content=content), # both doors - } - - -# special rooms -for i, (color, _) in enumerate(COLOR_BATCHES): - COLORED_DOOR_TILES.update(special_room_tiles(0x140 + i * 3, color, Content.RECHARGE)) - COLORED_DOOR_TILES.update(special_room_tiles(0x14C + i * 3, color, Content.NAVIGATION)) - COLORED_DOOR_TILES.update(special_room_tiles(0x160 + i * 3, color, Content.DATA)) - -# random exceptions that don't fit into any other category -COLORED_DOOR_TILES.update( - { - 0x128: _tile(W, RED, D, W, content=Content.RECHARGE), - 0x15E: _tile(W, D, YELLOW, W, content=Content.RECHARGE), - 0x17E: _tile(W, RED, RED, W, content=Content.SAVE), - 0x17F: _tile(W, YELLOW, D, W, content=Content.SAVE), - 0x198: _tile(W, BLUE, W, W, content=Content.ITEM), - 0x199: _tile(W, BLUE, W, W, content=Content.OBTAINED_ITEM), - 0x19E: _tile(W, GREEN, W, W, content=Content.ITEM), - 0x19F: _tile(W, GREEN, W, W, content=Content.OBTAINED_ITEM), - 0x1AC: _tile(W, YELLOW, YELLOW, W, content=Content.ITEM), - 0x1AD: _tile(W, YELLOW, YELLOW, W, content=Content.OBTAINED_ITEM), - } -) - -COLORED_DOOR_TILE_IDS = {tile: idx for idx, tile in COLORED_DOOR_TILES.items()} - - -NORMAL_DOOR_TILE_IDS = { - _tile(W, W, X, X): 0x000, - _tile(W, X, X, X): 0x001, - _tile(D, W, X, X): 0x002, - _tile(D, X, X, X): 0x003, - _tile(D, W, X, X, bottom_right=True): 0x004, - _tile(X, W, X, X): 0x020, - _tile(X, D, X, X): 0x021, - _tile(W, X, X, X, bottom_left=True, bottom_right=True): 0x022, - _tile(X, W, X, X, top_right=True, bottom_right=True): 0x023, - _tile(D, W, W, D): 0x024, - _tile(W, W, X, W): 0x040, - _tile(W, X, X, W): 0x041, - _tile(D, W, X, D): 0x042, - _tile(D, W, X, W): 0x043, - _tile(D, X, X, D): 0x044, - _tile(D, W, W, X): 0x060, - _tile(W, D, W, X): 0x061, - _tile(W, D, D, X): 0x062, - _tile(W, W, W, X): 0x063, - _tile(D, X, X, W): 0x064, - _tile(D, W, W, W): 0x080, - _tile(X, D, W, X): 0x081, - _tile(X, D, D, X): 0x082, - _tile(X, W, W, X): 0x083, - _tile(W, D, D, W): 0x084, - _tile(W, W, W, W): 0x087, - _tile(W, S, D, W, content=Content.ITEM): 0x0B4, - _tile(W, S, D, W): 0x0B6, - _tile(D, D, X, W): 0x0E0, - _tile(D, D, X, D): 0x0E1, - _tile(D, D, W, X): 0x0E2, - _tile(D, D, D, X): 0x0E3, - _tile(W, W, X, X, bottom_right=True): 0x0E4, - _tile(X, W, X, X, bottom_right=True): 0x0E5, - _tile(D, X, X, X, bottom_left=True, bottom_right=True): 0x100, - _tile(X, D, X, X, top_right=True, bottom_right=True): 0x101, - _tile(D, D, X, X, bottom_right=True): 0x102, - # tile(D, W, X, X, br=True): 0x103, # dupe of 0x004 - _tile(W, D, X, X, bottom_right=True): 0x104, - _tile(X, D, X, X, top_right=True): 0x105, - _tile(X, X, D, W, content=Content.BOSS): 0x108, - _tile(W, D, X, W, content=Content.BOSS): 0x109, - _tile(X, X, D, X, content=Content.BOSS): 0x10D, - _tile(X, D, X, W, content=Content.BOSS): 0x10F, - _tile(D, D, W, W): 0x120, - _tile(D, D, D, W): 0x121, - _tile(D, D, W, D): 0x122, - _tile(D, D, D, D): 0x123, - _tile(W, D, W, W): 0x124, - _tile(W, D, X, X): 0x125, - _tile(X, X, X, X): 0x126, - _tile(W, X, X, X, bottom_right=True): 0x127, - _tile(W, D, X, W, content=Content.GUNSHIP_EDGE): 0x129, - _tile(W, X, W, W, content=Content.GUNSHIP): 0x12A, - _tile(W, D, X, W): 0x12B, - _tile(D, D, X, X): 0x12C, - _tile(W, D, W, W, content=Content.HIDDEN_RECHARGE): 0x138, - _tile(W, W, D, W, content=Content.HIDDEN_RECHARGE): 0x139, - _tile(W, D, D, W, content=Content.HIDDEN_RECHARGE): 0x13A, - _tile(W, D, W, W, content=Content.RECHARGE): 0x158, - _tile(W, W, D, W, content=Content.RECHARGE): 0x159, - _tile(W, D, D, W, content=Content.RECHARGE): 0x15A, - _tile(W, D, W, W, content=Content.DATA): 0x15B, - _tile(W, W, D, W, content=Content.DATA): 0x15C, - _tile(W, D, D, W, content=Content.DATA): 0x15D, - _tile(W, D, W, W, content=Content.NAVIGATION): 0x178, - _tile(W, W, D, W, content=Content.NAVIGATION): 0x179, - _tile(W, D, D, W, content=Content.NAVIGATION): 0x17A, - _tile(W, D, W, W, content=Content.SAVE): 0x17B, - _tile(W, W, D, W, content=Content.SAVE): 0x17C, - _tile(W, D, D, W, content=Content.SAVE): 0x17D, - _tile(W, D, W, W, content=Content.ITEM): 0x180, - _tile(W, D, W, W, content=Content.OBTAINED_ITEM): 0x181, - _tile(W, W, X, W, content=Content.ITEM): 0x182, - _tile(W, W, X, W, content=Content.OBTAINED_ITEM): 0x183, - _tile(W, D, D, W, content=Content.ITEM): 0x184, - _tile(W, D, D, W, content=Content.OBTAINED_ITEM): 0x185, - _tile(W, W, X, X, content=Content.ITEM): 0x186, - _tile(W, W, X, X, content=Content.OBTAINED_ITEM): 0x187, - _tile(W, D, X, W, content=Content.ITEM): 0x188, - _tile(W, D, X, W, content=Content.OBTAINED_ITEM): 0x189, - _tile(X, W, X, X, content=Content.ITEM): 0x18A, - _tile(X, W, X, X, content=Content.OBTAINED_ITEM): 0x18B, - _tile(X, D, X, X, content=Content.ITEM): 0x18C, - _tile(X, D, X, X, content=Content.OBTAINED_ITEM): 0x18D, - _tile(W, D, X, X, content=Content.ITEM): 0x18E, - _tile(W, D, X, X, content=Content.OBTAINED_ITEM): 0x18F, - _tile(W, X, X, X, content=Content.ITEM): 0x190, - _tile(W, X, X, X, content=Content.OBTAINED_ITEM): 0x191, - _tile(W, X, X, W, content=Content.ITEM): 0x192, - _tile(W, X, X, W, content=Content.OBTAINED_ITEM): 0x193, - _tile(W, W, W, X, content=Content.ITEM): 0x194, - _tile(W, W, W, X, content=Content.OBTAINED_ITEM): 0x195, - _tile(D, D, D, W, content=Content.ITEM): 0x196, - _tile(D, D, D, W, content=Content.OBTAINED_ITEM): 0x197, - _tile(X, D, D, W, content=Content.ITEM): 0x19A, - _tile(X, D, D, W, content=Content.OBTAINED_ITEM): 0x19B, - _tile(W, W, D, X, content=Content.ITEM): 0x19C, - _tile(W, W, D, X, content=Content.OBTAINED_ITEM): 0x19D, - _tile(X, W, W, D, content=Content.ITEM): 0x1A0, - _tile(X, W, W, D, content=Content.OBTAINED_ITEM): 0x1A1, - _tile(W, W, W, D, content=Content.ITEM): 0x1A2, - _tile(W, W, W, D, content=Content.OBTAINED_ITEM): 0x1A3, - _tile(X, X, X, X, content=Content.ITEM): 0x1A4, - _tile(X, X, X, X, content=Content.OBTAINED_ITEM): 0x1A5, - _tile(W, W, X, D, content=Content.ITEM): 0x1A6, - _tile(W, W, X, D, content=Content.OBTAINED_ITEM): 0x1A7, - _tile(X, W, W, X, content=Content.ITEM): 0x1A8, - _tile(X, W, W, X, content=Content.OBTAINED_ITEM): 0x1A9, - _tile(D, D, W, W, content=Content.ITEM): 0x1AA, - _tile(D, D, W, W, content=Content.OBTAINED_ITEM): 0x1AB, +# Constants +COLORED_DOOR_TILES = { + 0x005: MapTile.from_str("WBxx_xxxx_x"), + 0x006: MapTile.from_str("DBxx_xxxx_x"), + 0x007: MapTile.from_str("DBxx_xxxC_x"), + 0x008: MapTile.from_str("DBBD_xxxx_x"), + 0x009: MapTile.from_str("DBBx_xxxx_x"), + 0x025: MapTile.from_str("xBxx_xxxx_x"), + 0x026: MapTile.from_str("xBxx_xCxC_x"), + 0x027: MapTile.from_str("DBWD_xxxx_x"), + 0x028: MapTile.from_str("DBDx_xxxx_x"), + 0x029: MapTile.from_str("DBBW_xxxx_x"), + 0x045: MapTile.from_str("WBxW_xxxx_x"), + 0x046: MapTile.from_str("DBxD_xxxx_x"), + 0x047: MapTile.from_str("DBxW_xxxx_x"), + 0x048: MapTile.from_str("WBWW_xxxx_x"), + 0x049: MapTile.from_str("WBBW_xxxx_x"), + 0x065: MapTile.from_str("DBWx_xxxx_x"), + 0x066: MapTile.from_str("WBDx_xxxx_x"), + 0x068: MapTile.from_str("WBWx_xxxx_x"), + 0x069: MapTile.from_str("WBBx_xxxx_x"), + 0x085: MapTile.from_str("DBWW_xxxx_x"), + 0x086: MapTile.from_str("xBDx_xxxx_x"), + 0x088: MapTile.from_str("xBWx_xxxx_x"), + 0x089: MapTile.from_str("xBBx_xxxx_x"), + 0x00A: MapTile.from_str("WGxx_xxxx_x"), + 0x00B: MapTile.from_str("DGxx_xxxx_x"), + 0x00C: MapTile.from_str("DGxx_xxxC_x"), + 0x00D: MapTile.from_str("DGGD_xxxx_x"), + 0x00E: MapTile.from_str("DGGx_xxxx_x"), + 0x02A: MapTile.from_str("xGxx_xxxx_x"), + 0x02B: MapTile.from_str("xGxx_xCxC_x"), + 0x02C: MapTile.from_str("DGWD_xxxx_x"), + 0x02D: MapTile.from_str("DGDx_xxxx_x"), + 0x02E: MapTile.from_str("DGGW_xxxx_x"), + 0x04A: MapTile.from_str("WGxW_xxxx_x"), + 0x04B: MapTile.from_str("DGxD_xxxx_x"), + 0x04C: MapTile.from_str("DGxW_xxxx_x"), + 0x04D: MapTile.from_str("WGWW_xxxx_x"), + 0x04E: MapTile.from_str("WGGW_xxxx_x"), + 0x06A: MapTile.from_str("DGWx_xxxx_x"), + 0x06B: MapTile.from_str("WGDx_xxxx_x"), + 0x06D: MapTile.from_str("WGWx_xxxx_x"), + 0x06E: MapTile.from_str("WGGx_xxxx_x"), + 0x08A: MapTile.from_str("DGWW_xxxx_x"), + 0x08B: MapTile.from_str("xGDx_xxxx_x"), + 0x08D: MapTile.from_str("xGWx_xxxx_x"), + 0x08E: MapTile.from_str("xGGx_xxxx_x"), + 0x00F: MapTile.from_str("WRxx_xxxx_x"), + 0x010: MapTile.from_str("DRxx_xxxx_x"), + 0x011: MapTile.from_str("DRxx_xxxC_x"), + 0x012: MapTile.from_str("DRRD_xxxx_x"), + 0x013: MapTile.from_str("DRRx_xxxx_x"), + 0x02F: MapTile.from_str("xRxx_xxxx_x"), + 0x030: MapTile.from_str("xRxx_xCxC_x"), + 0x031: MapTile.from_str("DRWD_xxxx_x"), + 0x032: MapTile.from_str("DRDx_xxxx_x"), + 0x033: MapTile.from_str("DRRW_xxxx_x"), + 0x04F: MapTile.from_str("WRxW_xxxx_x"), + 0x050: MapTile.from_str("DRxD_xxxx_x"), + 0x051: MapTile.from_str("DRxW_xxxx_x"), + 0x052: MapTile.from_str("WRWW_xxxx_x"), + 0x053: MapTile.from_str("WRRW_xxxx_x"), + 0x06F: MapTile.from_str("DRWx_xxxx_x"), + 0x070: MapTile.from_str("WRDx_xxxx_x"), + 0x072: MapTile.from_str("WRWx_xxxx_x"), + 0x073: MapTile.from_str("WRRx_xxxx_x"), + 0x08F: MapTile.from_str("DRWW_xxxx_x"), + 0x090: MapTile.from_str("xRDx_xxxx_x"), + 0x092: MapTile.from_str("xRWx_xxxx_x"), + 0x093: MapTile.from_str("xRRx_xxxx_x"), + 0x014: MapTile.from_str("WYxx_xxxx_x"), + 0x015: MapTile.from_str("DYxx_xxxx_x"), + 0x016: MapTile.from_str("DYxx_xxxC_x"), + 0x017: MapTile.from_str("DYYD_xxxx_x"), + 0x018: MapTile.from_str("DYYx_xxxx_x"), + 0x034: MapTile.from_str("xYxx_xxxx_x"), + 0x035: MapTile.from_str("xYxx_xCxC_x"), + 0x036: MapTile.from_str("DYWD_xxxx_x"), + 0x037: MapTile.from_str("DYDx_xxxx_x"), + 0x038: MapTile.from_str("DYYW_xxxx_x"), + 0x054: MapTile.from_str("WYxW_xxxx_x"), + 0x055: MapTile.from_str("DYxD_xxxx_x"), + 0x056: MapTile.from_str("DYxW_xxxx_x"), + 0x057: MapTile.from_str("WYWW_xxxx_x"), + 0x058: MapTile.from_str("WYYW_xxxx_x"), + 0x074: MapTile.from_str("DYWx_xxxx_x"), + 0x075: MapTile.from_str("WYDx_xxxx_x"), + 0x077: MapTile.from_str("WYWx_xxxx_x"), + 0x078: MapTile.from_str("WYYx_xxxx_x"), + 0x094: MapTile.from_str("DYWW_xxxx_x"), + 0x095: MapTile.from_str("xYDx_xxxx_x"), + 0x097: MapTile.from_str("xYWx_xxxx_x"), + 0x098: MapTile.from_str("xYYx_xxxx_x"), + 0x019: MapTile.from_str("DBGx_xxxx_x"), + 0x01A: MapTile.from_str("DBRx_xxxx_x"), + 0x01B: MapTile.from_str("DBYx_xxxx_x"), + 0x01C: MapTile.from_str("DGRx_xxxx_x"), + 0x01D: MapTile.from_str("DGYx_xxxx_x"), + 0x01E: MapTile.from_str("DRYx_xxxx_x"), + 0x039: MapTile.from_str("DBGW_xxxx_x"), + 0x03A: MapTile.from_str("DBRW_xxxx_x"), + 0x03B: MapTile.from_str("DBYW_xxxx_x"), + 0x03C: MapTile.from_str("DGRW_xxxx_x"), + 0x03D: MapTile.from_str("DGYW_xxxx_x"), + 0x03E: MapTile.from_str("DRYW_xxxx_x"), + 0x059: MapTile.from_str("WBGW_xxxx_x"), + 0x05A: MapTile.from_str("WBRW_xxxx_x"), + 0x05B: MapTile.from_str("WBYW_xxxx_x"), + 0x05C: MapTile.from_str("WGRW_xxxx_x"), + 0x05D: MapTile.from_str("WGYW_xxxx_x"), + 0x05E: MapTile.from_str("WRYW_xxxx_x"), + 0x079: MapTile.from_str("WBGx_xxxx_x"), + 0x07A: MapTile.from_str("WBRx_xxxx_x"), + 0x07B: MapTile.from_str("WBYx_xxxx_x"), + 0x07C: MapTile.from_str("WGRx_xxxx_x"), + 0x07D: MapTile.from_str("WGYx_xxxx_x"), + 0x07E: MapTile.from_str("WRYx_xxxx_x"), + 0x099: MapTile.from_str("xBGx_xxxx_x"), + 0x09A: MapTile.from_str("xBRx_xxxx_x"), + 0x09B: MapTile.from_str("xBYx_xxxx_x"), + 0x09C: MapTile.from_str("xGRx_xxxx_x"), + 0x09D: MapTile.from_str("xGYx_xxxx_x"), + 0x09E: MapTile.from_str("xRYx_xxxx_x"), + 0x01F: MapTile.from_str("DBGD_xxxx_x"), + 0x03F: MapTile.from_str("DBRD_xxxx_x"), + 0x05F: MapTile.from_str("DBYD_xxxx_x"), + 0x07F: MapTile.from_str("DGRD_xxxx_x"), + 0x09F: MapTile.from_str("DGYD_xxxx_x"), + 0x096: MapTile.from_str("DRYD_xxxx_x"), + 0x0E6: MapTile.from_str("WDBW_xxxx_x"), + 0x0E7: MapTile.from_str("DDBW_xxxx_x"), + 0x0E8: MapTile.from_str("DDBD_xxxx_x"), + 0x0EA: MapTile.from_str("DGxx_xxxC_x"), + 0x0EB: MapTile.from_str("WDGW_xxxx_x"), + 0x0EC: MapTile.from_str("DDGW_xxxx_x"), + 0x0ED: MapTile.from_str("DDGD_xxxx_x"), + 0x0EF: MapTile.from_str("DRxx_xxxC_x"), + 0x0F0: MapTile.from_str("WDRW_xxxx_x"), + 0x0F1: MapTile.from_str("DDRW_xxxx_x"), + 0x0F2: MapTile.from_str("DDRD_xxxx_x"), + 0x0F4: MapTile.from_str("DYxx_xxxC_x"), + 0x0F5: MapTile.from_str("WDYW_xxxx_x"), + 0x0F6: MapTile.from_str("DDYW_xxxx_x"), + 0x0F7: MapTile.from_str("DDYD_xxxx_x"), + 0x0F8: MapTile.from_str("WYxx_xxxC_x"), + 0x06C: MapTile.from_str("WGxx_xxxC_x"), + 0x140: MapTile.from_str("WBWW_xxxx_R"), + 0x141: MapTile.from_str("WWBW_xxxx_R"), + 0x142: MapTile.from_str("WBBW_xxxx_R"), + 0x14C: MapTile.from_str("WBWW_xxxx_N"), + 0x14D: MapTile.from_str("WWBW_xxxx_N"), + 0x14E: MapTile.from_str("WBBW_xxxx_N"), + 0x160: MapTile.from_str("WBWW_xxxx_D"), + 0x161: MapTile.from_str("WWBW_xxxx_D"), + 0x162: MapTile.from_str("WBBW_xxxx_D"), + 0x143: MapTile.from_str("WGWW_xxxx_R"), + 0x144: MapTile.from_str("WWGW_xxxx_R"), + 0x145: MapTile.from_str("WGGW_xxxx_R"), + 0x14F: MapTile.from_str("WGWW_xxxx_N"), + 0x150: MapTile.from_str("WWGW_xxxx_N"), + 0x151: MapTile.from_str("WGGW_xxxx_N"), + 0x163: MapTile.from_str("WGWW_xxxx_D"), + 0x164: MapTile.from_str("WWGW_xxxx_D"), + 0x165: MapTile.from_str("WGGW_xxxx_D"), + 0x146: MapTile.from_str("WRWW_xxxx_R"), + 0x147: MapTile.from_str("WWRW_xxxx_R"), + 0x148: MapTile.from_str("WRRW_xxxx_R"), + 0x152: MapTile.from_str("WRWW_xxxx_N"), + 0x153: MapTile.from_str("WWRW_xxxx_N"), + 0x154: MapTile.from_str("WRRW_xxxx_N"), + 0x166: MapTile.from_str("WRWW_xxxx_D"), + 0x167: MapTile.from_str("WWRW_xxxx_D"), + 0x168: MapTile.from_str("WRRW_xxxx_D"), + 0x149: MapTile.from_str("WYWW_xxxx_R"), + 0x14A: MapTile.from_str("WWYW_xxxx_R"), + 0x14B: MapTile.from_str("WYYW_xxxx_R"), + 0x155: MapTile.from_str("WYWW_xxxx_N"), + 0x156: MapTile.from_str("WWYW_xxxx_N"), + 0x157: MapTile.from_str("WYYW_xxxx_N"), + 0x169: MapTile.from_str("WYWW_xxxx_D"), + 0x16A: MapTile.from_str("WWYW_xxxx_D"), + 0x16B: MapTile.from_str("WYYW_xxxx_D"), + 0x128: MapTile.from_str("WRDW_xxxx_R"), + 0x15E: MapTile.from_str("WDYW_xxxx_R"), + 0x17E: MapTile.from_str("WRRW_xxxx_S"), + 0x17F: MapTile.from_str("WYDW_xxxx_S"), + 0x198: MapTile.from_str("WBWW_xxxx_I"), + 0x199: MapTile.from_str("WBWW_xxxx_O"), + 0x19E: MapTile.from_str("WGWW_xxxx_I"), + 0x19F: MapTile.from_str("WGWW_xxxx_O"), + 0x1AC: MapTile.from_str("WYYW_xxxx_I"), + 0x1AD: MapTile.from_str("WYYW_xxxx_O"), } + +NORMAL_DOOR_TILES = { + 0x000: MapTile.from_str("WWxx_xxxx_x"), + 0x001: MapTile.from_str("Wxxx_xxxx_x"), + 0x002: MapTile.from_str("DWxx_xxxx_x"), + 0x003: MapTile.from_str("Dxxx_xxxx_x"), + 0x004: MapTile.from_str("DWxx_xxxC_x"), + 0x020: MapTile.from_str("xWxx_xxxx_x"), + 0x021: MapTile.from_str("xDxx_xxxx_x"), + 0x022: MapTile.from_str("Wxxx_xxCC_x"), + 0x023: MapTile.from_str("xWxx_xCxC_x"), + 0x024: MapTile.from_str("DWWD_xxxx_x"), + 0x040: MapTile.from_str("WWxW_xxxx_x"), + 0x041: MapTile.from_str("WxxW_xxxx_x"), + 0x042: MapTile.from_str("DWxD_xxxx_x"), + 0x043: MapTile.from_str("DWxW_xxxx_x"), + 0x044: MapTile.from_str("DxxD_xxxx_x"), + 0x060: MapTile.from_str("DWWx_xxxx_x"), + 0x061: MapTile.from_str("WDWx_xxxx_x"), + 0x062: MapTile.from_str("WDDx_xxxx_x"), + 0x063: MapTile.from_str("WWWx_xxxx_x"), + 0x064: MapTile.from_str("DxxW_xxxx_x"), + 0x080: MapTile.from_str("DWWW_xxxx_x"), + 0x081: MapTile.from_str("xDWx_xxxx_x"), + 0x082: MapTile.from_str("xDDx_xxxx_x"), + 0x083: MapTile.from_str("xWWx_xxxx_x"), + 0x084: MapTile.from_str("WDDW_xxxx_x"), + 0x087: MapTile.from_str("WWWW_xxxx_x"), + 0x0B4: MapTile.from_str("WSDW_xxxx_I"), + 0x0B6: MapTile.from_str("WSDW_xxxx_x"), + 0x0E0: MapTile.from_str("DDxW_xxxx_x"), + 0x0E1: MapTile.from_str("DDxD_xxxx_x"), + 0x0E2: MapTile.from_str("DDWx_xxxx_x"), + 0x0E3: MapTile.from_str("DDDx_xxxx_x"), + 0x0E4: MapTile.from_str("WWxx_xxxC_x"), + 0x0E5: MapTile.from_str("xWxx_xxxC_x"), + 0x100: MapTile.from_str("Dxxx_xxCC_x"), + 0x101: MapTile.from_str("xDxx_xCxC_x"), + 0x102: MapTile.from_str("DDxx_xxxC_x"), + 0x104: MapTile.from_str("WDxx_xxxC_x"), + 0x105: MapTile.from_str("xDxx_xCxx_x"), + 0x108: MapTile.from_str("xxDW_xxxx_B"), + 0x109: MapTile.from_str("WDxW_xxxx_B"), + 0x10D: MapTile.from_str("xxDx_xxxx_B"), + 0x10F: MapTile.from_str("xDxW_xxxx_B"), + 0x120: MapTile.from_str("DDWW_xxxx_x"), + 0x121: MapTile.from_str("DDDW_xxxx_x"), + 0x122: MapTile.from_str("DDWD_xxxx_x"), + 0x123: MapTile.from_str("DDDD_xxxx_x"), + 0x124: MapTile.from_str("WDWW_xxxx_x"), + 0x125: MapTile.from_str("WDxx_xxxx_x"), + 0x126: MapTile.from_str("xxxx_xxxx_x"), + 0x127: MapTile.from_str("Wxxx_xxxC_x"), + 0x129: MapTile.from_str("WDxW_xxxx_P"), + 0x12A: MapTile.from_str("WxWW_xxxx_G"), + 0x12B: MapTile.from_str("WDxW_xxxx_x"), + 0x12C: MapTile.from_str("DDxx_xxxx_x"), + 0x138: MapTile.from_str("WDWW_xxxx_H"), + 0x139: MapTile.from_str("WWDW_xxxx_H"), + 0x13A: MapTile.from_str("WDDW_xxxx_H"), + 0x158: MapTile.from_str("WDWW_xxxx_R"), + 0x159: MapTile.from_str("WWDW_xxxx_R"), + 0x15A: MapTile.from_str("WDDW_xxxx_R"), + 0x15B: MapTile.from_str("WDWW_xxxx_D"), + 0x15C: MapTile.from_str("WWDW_xxxx_D"), + 0x15D: MapTile.from_str("WDDW_xxxx_D"), + 0x178: MapTile.from_str("WDWW_xxxx_N"), + 0x179: MapTile.from_str("WWDW_xxxx_N"), + 0x17A: MapTile.from_str("WDDW_xxxx_N"), + 0x17B: MapTile.from_str("WDWW_xxxx_S"), + 0x17C: MapTile.from_str("WWDW_xxxx_S"), + 0x17D: MapTile.from_str("WDDW_xxxx_S"), + 0x180: MapTile.from_str("WDWW_xxxx_I"), + 0x181: MapTile.from_str("WDWW_xxxx_O"), + 0x182: MapTile.from_str("WWxW_xxxx_I"), + 0x183: MapTile.from_str("WWxW_xxxx_O"), + 0x184: MapTile.from_str("WDDW_xxxx_I"), + 0x185: MapTile.from_str("WDDW_xxxx_O"), + 0x186: MapTile.from_str("WWxx_xxxx_I"), + 0x187: MapTile.from_str("WWxx_xxxx_O"), + 0x188: MapTile.from_str("WDxW_xxxx_I"), + 0x189: MapTile.from_str("WDxW_xxxx_O"), + 0x18A: MapTile.from_str("xWxx_xxxx_I"), + 0x18B: MapTile.from_str("xWxx_xxxx_O"), + 0x18C: MapTile.from_str("xDxx_xxxx_I"), + 0x18D: MapTile.from_str("xDxx_xxxx_O"), + 0x18E: MapTile.from_str("WDxx_xxxx_I"), + 0x18F: MapTile.from_str("WDxx_xxxx_O"), + 0x190: MapTile.from_str("Wxxx_xxxx_I"), + 0x191: MapTile.from_str("Wxxx_xxxx_O"), + 0x192: MapTile.from_str("WxxW_xxxx_I"), + 0x193: MapTile.from_str("WxxW_xxxx_O"), + 0x194: MapTile.from_str("WWWx_xxxx_I"), + 0x195: MapTile.from_str("WWWx_xxxx_O"), + 0x196: MapTile.from_str("DDDW_xxxx_I"), + 0x197: MapTile.from_str("DDDW_xxxx_O"), + 0x19A: MapTile.from_str("xDDW_xxxx_I"), + 0x19B: MapTile.from_str("xDDW_xxxx_O"), + 0x19C: MapTile.from_str("WWDx_xxxx_I"), + 0x19D: MapTile.from_str("WWDx_xxxx_O"), + 0x1A0: MapTile.from_str("xWWD_xxxx_I"), + 0x1A1: MapTile.from_str("xWWD_xxxx_O"), + 0x1A2: MapTile.from_str("WWWD_xxxx_I"), + 0x1A3: MapTile.from_str("WWWD_xxxx_O"), + 0x1A4: MapTile.from_str("xxxx_xxxx_I"), + 0x1A5: MapTile.from_str("xxxx_xxxx_O"), + 0x1A6: MapTile.from_str("WWxD_xxxx_I"), + 0x1A7: MapTile.from_str("WWxD_xxxx_O"), + 0x1A8: MapTile.from_str("xWWx_xxxx_I"), + 0x1A9: MapTile.from_str("xWWx_xxxx_O"), + 0x1AA: MapTile.from_str("DDWW_xxxx_I"), + 0x1AB: MapTile.from_str("DDWW_xxxx_O"), +} + +COLORED_DOOR_TILE_IDS = {tile: id for id, tile in COLORED_DOOR_TILES.items()} +NORMAL_DOOR_TILE_IDS = {tile: id for id, tile in NORMAL_DOOR_TILES.items()} + +ALL_DOOR_TILES = COLORED_DOOR_TILES | NORMAL_DOOR_TILES ALL_DOOR_TILE_IDS = COLORED_DOOR_TILE_IDS | NORMAL_DOOR_TILE_IDS -ALL_DOOR_TILES = {idx: tile for tile, idx in ALL_DOOR_TILE_IDS.items()} diff --git a/src/mars_patcher/door_locks.py b/src/mars_patcher/door_locks.py index a0dca89..39ef4b4 100644 --- a/src/mars_patcher/door_locks.py +++ b/src/mars_patcher/door_locks.py @@ -13,9 +13,7 @@ from mars_patcher.constants.minimap_tiles import ( ALL_DOOR_TILE_IDS, ALL_DOOR_TILES, - ANY_DOOR_EDGE, - AnyEdge, - ColorDoor, + ColoredDoor, Edge, ) from mars_patcher.minimap import Minimap @@ -202,7 +200,7 @@ def factory(): minimap_areas = [area] if area == 0: - minimap_areas = [0, 9] # main deck seemingly has two maps? + minimap_areas = [0, 9] # Main deck seemingly has two maps? for minimap_area in minimap_areas: map_tile = minimap_changes[minimap_area][minimap_x, minimap_y, room] if facing_right: @@ -275,13 +273,13 @@ def fix_hatch_lock_events( def change_minimap_tiles( rom: Rom, minimap_changes: dict[AreaId, dict[MinimapLocation, MinimapLockChanges]] ): - MAP_EDGES: dict[HatchLock, AnyEdge] = { + MAP_EDGES: dict[HatchLock, Edge | ColoredDoor] = { HatchLock.OPEN: Edge.DOOR, HatchLock.LEVEL_0: Edge.DOOR, - HatchLock.LEVEL_1: ColorDoor.BLUE, - HatchLock.LEVEL_2: ColorDoor.GREEN, - HatchLock.LEVEL_3: ColorDoor.YELLOW, - HatchLock.LEVEL_4: ColorDoor.RED, + HatchLock.LEVEL_1: ColoredDoor.BLUE, + HatchLock.LEVEL_2: ColoredDoor.GREEN, + HatchLock.LEVEL_3: ColoredDoor.YELLOW, + HatchLock.LEVEL_4: ColoredDoor.RED, HatchLock.LOCKED: Edge.DOOR, # HatchLock.LOCKED: Edge.WALL, } @@ -293,13 +291,13 @@ def change_minimap_tiles( tile_data = ALL_DOOR_TILES[tile_id] - # account for h_flip before changing edges + # Account for h_flip before changing edges left = tile_changes.get("left") right = tile_changes.get("right") if h_flip: left, right = right, left - # replace edges + # Replace edges edges = tile_data.edges if left is not None: edges = edges._replace(left=MAP_EDGES[left]) @@ -309,19 +307,19 @@ def change_minimap_tiles( new_tile_data = og_new_tile_data if new_tile_data not in ALL_DOOR_TILE_IDS: - # try flipping horizontally + # Try flipping horizontally new_tile_data = og_new_tile_data.h_flip() if new_tile_data in ALL_DOOR_TILE_IDS: h_flip = not h_flip if new_tile_data not in ALL_DOOR_TILE_IDS: - # try flipping vertically + # Try flipping vertically new_tile_data = og_new_tile_data.v_flip() if new_tile_data in ALL_DOOR_TILE_IDS: v_flip = not v_flip if new_tile_data not in ALL_DOOR_TILE_IDS: - # try flipping it both ways + # Try flipping it both ways new_tile_data = og_new_tile_data.v_flip() new_tile_data = new_tile_data.h_flip() if new_tile_data in ALL_DOOR_TILE_IDS: @@ -333,13 +331,13 @@ def change_minimap_tiles( "Could not edit map tile door icons for " f"area {area} room {room:X}. ({x:X}, {y:X})." ) - logging.debug(f" Desired tile: {str(og_new_tile_data)}") + logging.debug(f" Desired tile: {og_new_tile_data.as_str}") logging.debug(" Falling back to unlocked doors.") - # try replacing with open doors - if (left is not None) and (tile_data.edges.left in ANY_DOOR_EDGE): + # Try replacing with open doors + if (left is not None) and tile_data.edges.left.is_door: edges = edges._replace(left=Edge.DOOR) - if (right is not None) and (tile_data.edges.right in ANY_DOOR_EDGE): + if (right is not None) and tile_data.edges.right.is_door: edges = edges._replace(right=Edge.DOOR) new_tile_data = og_new_tile_data._replace(edges=edges) From 987ea4162f4291eb0f03cdf61e1fb812f8ad08b4 Mon Sep 17 00:00:00 2001 From: duncathan Date: Mon, 10 Mar 2025 13:00:59 -0600 Subject: [PATCH 4/5] don't try flipping asymmetric tile contents --- src/mars_patcher/constants/minimap_tiles.py | 45 +++++++++++++++++++++ src/mars_patcher/door_locks.py | 25 +++++++----- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/mars_patcher/constants/minimap_tiles.py b/src/mars_patcher/constants/minimap_tiles.py index aaf0161..035c032 100644 --- a/src/mars_patcher/constants/minimap_tiles.py +++ b/src/mars_patcher/constants/minimap_tiles.py @@ -5,6 +5,19 @@ from typing_extensions import Self +# String Format +# __ + +# Chunk 1 (tile edges) +# - see Edge and ColoredDoor for values + +# Chunk 2 (tile corners) +# - C: corner pixel +# - x: none + +# Chunk 3 (tile content) +# - see Content for values + # Edges class Edge(Enum): @@ -139,6 +152,32 @@ class Content(Enum): GUNSHIP = "G" GUNSHIP_EDGE = "P" + @property + def can_h_flip(self) -> bool: + exclude = { + Content.NAVIGATION, + Content.SAVE, + Content.RECHARGE, + Content.HIDDEN_RECHARGE, + Content.DATA, + Content.GUNSHIP, + Content.GUNSHIP_EDGE, + } + return self not in exclude + + @property + def can_v_flip(self) -> bool: + exclude = { + Content.NAVIGATION, + Content.SAVE, + Content.RECHARGE, + Content.HIDDEN_RECHARGE, + Content.GUNSHIP, + Content.GUNSHIP_EDGE, + Content.BOSS, + } + return self not in exclude + # Tile class MapTile(NamedTuple): @@ -162,6 +201,9 @@ def from_str(cls, value: str) -> Self: ) def h_flip(self) -> MapTile: + if not self.content.can_h_flip: + raise ValueError(f"Cannot h_flip tile with contents {self.content}") + return MapTile( edges=self.edges.h_flip(), corners=self.corners.h_flip(), @@ -169,6 +211,9 @@ def h_flip(self) -> MapTile: ) def v_flip(self) -> MapTile: + if not self.content.can_v_flip: + raise ValueError(f"Cannot v_flip tile with contents {self.content}") + return MapTile( edges=self.edges.v_flip(), corners=self.corners.v_flip(), diff --git a/src/mars_patcher/door_locks.py b/src/mars_patcher/door_locks.py index 39ef4b4..a7cfeb8 100644 --- a/src/mars_patcher/door_locks.py +++ b/src/mars_patcher/door_locks.py @@ -306,27 +306,34 @@ def change_minimap_tiles( og_new_tile_data = tile_data._replace(edges=edges) new_tile_data = og_new_tile_data - if new_tile_data not in ALL_DOOR_TILE_IDS: + def tile_exists() -> bool: + return new_tile_data in ALL_DOOR_TILE_IDS + + if new_tile_data.content.can_h_flip and not tile_exists(): # Try flipping horizontally new_tile_data = og_new_tile_data.h_flip() - if new_tile_data in ALL_DOOR_TILE_IDS: + if tile_exists(): h_flip = not h_flip - if new_tile_data not in ALL_DOOR_TILE_IDS: + if new_tile_data.content.can_v_flip and not tile_exists(): # Try flipping vertically new_tile_data = og_new_tile_data.v_flip() - if new_tile_data in ALL_DOOR_TILE_IDS: + if tile_exists(): v_flip = not v_flip - if new_tile_data not in ALL_DOOR_TILE_IDS: + if ( + new_tile_data.content.can_h_flip + and new_tile_data.content.can_v_flip + and not tile_exists() + ): # Try flipping it both ways new_tile_data = og_new_tile_data.v_flip() new_tile_data = new_tile_data.h_flip() - if new_tile_data in ALL_DOOR_TILE_IDS: + if tile_exists(): v_flip = not v_flip h_flip = not h_flip - if new_tile_data not in ALL_DOOR_TILE_IDS: + if not tile_exists(): logging.debug( "Could not edit map tile door icons for " f"area {area} room {room:X}. ({x:X}, {y:X})." @@ -341,12 +348,12 @@ def change_minimap_tiles( edges = edges._replace(right=Edge.DOOR) new_tile_data = og_new_tile_data._replace(edges=edges) - if new_tile_data not in ALL_DOOR_TILE_IDS: + if tile_exists(): logging.debug(" Still no luck. Using vanilla tile.") logging.debug("") - if new_tile_data in ALL_DOOR_TILE_IDS: + if tile_exists(): minimap.set_tile_value( x, y, From 34d104388cdd996562ab50e1cd3ea49d4c1b261d Mon Sep 17 00:00:00 2001 From: duncathan Date: Fri, 14 Mar 2025 12:26:53 -0600 Subject: [PATCH 5/5] mypy --- src/mars_patcher/constants/minimap_tiles.py | 14 +++++++++----- src/mars_patcher/door_locks.py | 8 +++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/mars_patcher/constants/minimap_tiles.py b/src/mars_patcher/constants/minimap_tiles.py index 035c032..a8302b4 100644 --- a/src/mars_patcher/constants/minimap_tiles.py +++ b/src/mars_patcher/constants/minimap_tiles.py @@ -62,14 +62,18 @@ def as_str(self) -> str: def from_str(cls, value: str) -> Self: if len(value) != 4: raise ValueError(f"'{value}' is not a valid TileEdges string") - top, left, right, bottom = value + top, left, right, bottom = tuple(value) def any_edge_from_value(v: str) -> Edge | ColoredDoor: try: - edge = Edge(v) + return Edge(v) except ValueError: - edge = ColoredDoor(v) - return edge + pass + + try: + return ColoredDoor(v) + except ValueError: + raise ValueError(f"{repr(v)} is not a valid Edge or ColoredDoor") return cls( top=Edge(top), @@ -113,7 +117,7 @@ def s(corner: bool) -> str: def from_str(cls, value: str) -> Self: if len(value) != 4: raise ValueError(f"'{value}' is not a valid TileCorners string") - tl, tr, bl, br = value + tl, tr, bl, br = tuple(value) return cls( top_left=(tl == "C"), top_right=(tr == "C"), diff --git a/src/mars_patcher/door_locks.py b/src/mars_patcher/door_locks.py index a7cfeb8..9c98014 100644 --- a/src/mars_patcher/door_locks.py +++ b/src/mars_patcher/door_locks.py @@ -101,13 +101,11 @@ def set_door_locks(rom: Rom, data: list[MarsschemaDoorlocksItem]) -> None: hatch_slot_changes: dict[AreaRoomPair, dict[HatchSlot, HatchSlot]] = {} - def factory(): + def factory() -> dict: return defaultdict(dict) # AreaID: {(MinimapX, MinimapY, RoomID): {"left" | "right": HatchLock}} - minimap_changes: defaultdict[AreaId, defaultdict[MinimapLocation, MinimapLockChanges]] = ( - defaultdict(factory) - ) + minimap_changes: dict[AreaId, dict[MinimapLocation, MinimapLockChanges]] = defaultdict(factory) for area in range(7): area_addr = rom.read_ptr(doors_ptrs + area * 4) @@ -272,7 +270,7 @@ def fix_hatch_lock_events( def change_minimap_tiles( rom: Rom, minimap_changes: dict[AreaId, dict[MinimapLocation, MinimapLockChanges]] -): +) -> None: MAP_EDGES: dict[HatchLock, Edge | ColoredDoor] = { HatchLock.OPEN: Edge.DOOR, HatchLock.LEVEL_0: Edge.DOOR,