diff --git a/src/mars_patcher/mf/connections.py b/src/mars_patcher/mf/connections.py index e17f8cc..9a3f5fc 100644 --- a/src/mars_patcher/mf/connections.py +++ b/src/mars_patcher/mf/connections.py @@ -21,9 +21,9 @@ MAIN_HUB_TILEMAP_ADDR, ) from mars_patcher.mf.data import get_data_path -from mars_patcher.minimap import Minimap from mars_patcher.rom import Game, Rom from mars_patcher.room_entry import BlockLayer, RoomEntry +from mars_patcher.tilemap import Tilemap # Area ID, Room ID, Is area connection ELEVATOR_TOPS = { @@ -235,7 +235,7 @@ def _write_main_hub_small_nums( bg2.set_block_value(x + 1, y, block + 1) def remove_main_deck_minimap_area_nums(self) -> None: - with Minimap(self.rom, 0) as minimap: + with Tilemap.from_minimap(self.rom, 0) as minimap: minimap.set_tile_value(0x2, 0x11, 0xA0, 0) # 5 minimap.set_tile_value(0x3, 0x10, 0xA0, 0) # 3 minimap.set_tile_value(0x4, 0x0F, 0xA0, 0) # 1 diff --git a/src/mars_patcher/mf/door_locks.py b/src/mars_patcher/mf/door_locks.py index 4a8d99e..8ee2017 100644 --- a/src/mars_patcher/mf/door_locks.py +++ b/src/mars_patcher/mf/door_locks.py @@ -15,10 +15,10 @@ BLANK_TILE_IDS, BLANK_TRANSPARENT_TILE_IDS, ) -from mars_patcher.minimap import Minimap from mars_patcher.minimap_tile_creator import create_tile from mars_patcher.rom import Rom from mars_patcher.room_entry import BlockLayer, RoomEntry +from mars_patcher.tilemap import Tilemap class HatchLock(Enum): @@ -315,7 +315,7 @@ def change_minimap_tiles( remaining_blank_transparent_tile_ids = list(BLANK_TRANSPARENT_TILE_IDS) for area, area_map in minimap_changes.items(): - with Minimap(rom, area) as minimap: + with Tilemap.from_minimap(rom, area) as minimap: for (x, y), tile_changes in area_map.items(): tile_id, palette, h_flip, v_flip = minimap.get_tile_value(x, y) diff --git a/src/mars_patcher/mf/patcher.py b/src/mars_patcher/mf/patcher.py index 05d4781..530fab3 100644 --- a/src/mars_patcher/mf/patcher.py +++ b/src/mars_patcher/mf/patcher.py @@ -33,11 +33,11 @@ from mars_patcher.mf.navigation_text import NavigationText from mars_patcher.mf.room_names import write_room_names from mars_patcher.mf.starting import set_starting_items, set_starting_location -from mars_patcher.minimap import apply_minimap_edits from mars_patcher.random_palettes import PaletteRandomizer, PaletteSettings from mars_patcher.rom import Rom from mars_patcher.sounds import set_sounds from mars_patcher.text import write_seed_hash +from mars_patcher.tilemap import apply_minimap_edits from mars_patcher.titlescreen_text import write_title_text diff --git a/src/mars_patcher/minimap.py b/src/mars_patcher/minimap.py deleted file mode 100644 index c10b97c..0000000 --- a/src/mars_patcher/minimap.py +++ /dev/null @@ -1,82 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING - -from mars_patcher.compress import comp_lz77, decomp_lz77 -from mars_patcher.constants.game_data import minimap_ptrs - -if TYPE_CHECKING: - from types import TracebackType - - from mars_patcher.common_types import MinimapId - from mars_patcher.rom import Rom - -MINIMAP_DIM = 32 - - -class Minimap: - """Class for reading/writing minimap data and setting tiles.""" - - def __init__(self, rom: Rom, id: MinimapId): - self.rom = rom - self.pointer = minimap_ptrs(rom) + (id * 4) - addr = rom.read_ptr(self.pointer) - self.tile_data, self.comp_size = decomp_lz77(rom.data, addr) - - def __enter__(self) -> Minimap: - # We don't need to do anything - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - self.write() - - def get_tile_value(self, x: int, y: int) -> tuple[int, int, bool, bool]: - idx = (y * MINIMAP_DIM + x) * 2 - if idx >= len(self.tile_data): - raise IndexError(f"Tile coordinate ({x}, {y}) is not within minimap") - value = self.tile_data[idx] | self.tile_data[idx + 1] << 8 - tile = value & (0x3FF) - palette = value >> 12 - h_flip = value & 0x400 != 0 - v_flip = value & 0x800 != 0 - return tile, palette, h_flip, v_flip - - def set_tile_value( - self, x: int, y: int, tile: int, palette: int, h_flip: bool = False, v_flip: bool = False - ) -> None: - idx = (y * MINIMAP_DIM + x) * 2 - if idx >= len(self.tile_data): - raise IndexError(f"Tile coordinate ({x}, {y}) is not within minimap") - value = tile | (palette << 12) - if h_flip: - value |= 0x400 - if v_flip: - value |= 0x800 - self.tile_data[idx] = value & 0xFF - self.tile_data[idx + 1] = value >> 8 - - def write(self) -> None: - comp_data = comp_lz77(self.tile_data) - addr = self.rom.read_ptr(self.pointer) - self.rom.write_repointable_data(addr, self.comp_size, comp_data, [self.pointer]) - self.comp_size = len(comp_data) - - -def apply_minimap_edits(rom: Rom, edit_dict: dict) -> None: - # Go through every minimap - for map_id, changes in edit_dict.items(): - with Minimap(rom, int(map_id)) as minimap: - for change in changes: - minimap.set_tile_value( - change["X"], - change["Y"], - change["Tile"], - change["Palette"], - change.get("HFlip", False), - change.get("VFlip", False), - ) diff --git a/src/mars_patcher/tilemap.py b/src/mars_patcher/tilemap.py index 1ed1fe3..3f07937 100644 --- a/src/mars_patcher/tilemap.py +++ b/src/mars_patcher/tilemap.py @@ -1,20 +1,31 @@ +from enum import Enum, auto +from types import TracebackType + +from mars_patcher.common_types import MinimapId from mars_patcher.compress import comp_lz77, decomp_lz77 +from mars_patcher.constants.game_data import minimap_ptrs from mars_patcher.convert_array import u8_to_u16, u16_to_u8 from mars_patcher.rom import Rom +class TilemapType(Enum): + TILESET = auto() + """Uncompressed, prefixed with 02 00""" + BACKGROUND = auto() + """Compressed, prefixed with background size""" + MISC = auto() + """Compressed, used for minimaps and cutscene graphics""" + + class Tilemap: - def __init__(self, rom: Rom, ptr: int, compressed: bool): + def __init__(self, rom: Rom, ptr: int, type: TilemapType): self.rom = rom self.pointer = ptr - self.compressed = compressed + self.type = type # Get data addr = rom.read_ptr(ptr) self.data: list[int] = [] - if compressed: - data, self.data_size = decomp_lz77(rom.data, addr) - self.data = u8_to_u16(data) - else: + if type == TilemapType.TILESET: addr += 2 while True: val = rom.read_16(addr) @@ -27,13 +38,66 @@ def __init__(self, rom: Rom, ptr: int, compressed: bool): self.data.append(val) addr += 2 self.data_size = len(self.data) * 2 + 4 + elif type == TilemapType.BACKGROUND: + raise NotImplementedError() + elif type == TilemapType.MISC: + data, self.data_size = decomp_lz77(rom.data, addr) + self.data = u8_to_u16(data) + + def __enter__(self) -> "Tilemap": + # We don't need to do anything + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.write(copy=False) + + @classmethod + def from_minimap(cls, rom: Rom, id: MinimapId) -> "Tilemap": + ptr = minimap_ptrs(rom) + (id * 4) + return Tilemap(rom, ptr, TilemapType.MISC) + + def get_tile_index(self, x: int, y: int) -> int: + if self.type == TilemapType.TILESET: + idx = (y // 2 * 64) + (y % 2 * 2) + (x // 2 * 4) + (x % 2) + else: + idx = y * 32 + x + if idx >= len(self.data): + raise IndexError(f"Tile coordinate ({x}, {y}) is not within tilemap") + return idx + + def get_tile_value(self, x: int, y: int) -> tuple[int, int, bool, bool]: + idx = self.get_tile_index(x, y) + value = self.data[idx] + tile = value & 0x3FF + palette = value >> 12 + h_flip = value & 0x400 != 0 + v_flip = value & 0x800 != 0 + return tile, palette, h_flip, v_flip + + def set_tile_value( + self, x: int, y: int, tile: int, palette: int, h_flip: bool = False, v_flip: bool = False + ) -> None: + idx = self.get_tile_index(x, y) + value = tile | (palette << 12) + if h_flip: + value |= 0x400 + if v_flip: + value |= 0x800 + self.data[idx] = value def byte_data(self) -> bytes: - if self.compressed: + if self.type == TilemapType.TILESET: + return bytes([2, 0]) + u16_to_u8(self.data) + bytes([0, 0]) + elif self.type == TilemapType.BACKGROUND: + raise NotImplementedError() + elif self.type == TilemapType.MISC: data = comp_lz77(u16_to_u8(self.data)) return bytes(data) - else: - return bytes([2, 0]) + u16_to_u8(self.data) + bytes([0, 0]) def write(self, copy: bool) -> None: data = self.byte_data() @@ -42,3 +106,18 @@ def write(self, copy: bool) -> None: else: addr = self.rom.read_ptr(self.pointer) self.rom.write_repointable_data(addr, self.data_size, data, [self.pointer]) + + +def apply_minimap_edits(rom: Rom, edit_dict: dict) -> None: + # Go through every minimap + for map_id, changes in edit_dict.items(): + with Tilemap.from_minimap(rom, int(map_id)) as minimap: + for change in changes: + minimap.set_tile_value( + change["X"], + change["Y"], + change["Tile"], + change["Palette"], + change.get("HFlip", False), + change.get("VFlip", False), + ) diff --git a/src/mars_patcher/zm/item_patcher.py b/src/mars_patcher/zm/item_patcher.py index d8c803c..6ba1fe3 100644 --- a/src/mars_patcher/zm/item_patcher.py +++ b/src/mars_patcher/zm/item_patcher.py @@ -12,7 +12,7 @@ from mars_patcher.rom import Rom from mars_patcher.room_entry import RoomEntry from mars_patcher.text import Language, MessageType, encode_text -from mars_patcher.tilemap import Tilemap +from mars_patcher.tilemap import Tilemap, TilemapType from mars_patcher.tileset import ANIM_TILESET_SIZE, TILESET_SIZE, Tileset from mars_patcher.zm.auto_generated_types import MarsschemazmTankIncrements from mars_patcher.zm.constants.game_data import ( @@ -34,7 +34,7 @@ def __init__(self, rom: Rom, id: int): self.id = id self.meta = Tileset(rom, id) # Load tilemap - self.tilemap = Tilemap(rom, self.meta.tilemap_ptr(), False) + self.tilemap = Tilemap(rom, self.meta.tilemap_ptr(), TilemapType.TILESET) # Load palette self.palette = Palette(14, rom, self.meta.palette_addr()) # Load animated tileset