diff --git a/src/mars_patcher/constants/enemies.py b/src/mars_patcher/constants/enemies.py new file mode 100644 index 0000000..5f0540a --- /dev/null +++ b/src/mars_patcher/constants/enemies.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class EnemyType(Enum): + CRAWLING = 1 + GROUND = 2 + CEILING = 3 + GROUND_CEILING = 4 + WALL = 5 + FLYING = 6 diff --git a/src/mars_patcher/mf/constants/enemies.py b/src/mars_patcher/mf/constants/enemies.py index a087f85..c55b1da 100644 --- a/src/mars_patcher/mf/constants/enemies.py +++ b/src/mars_patcher/mf/constants/enemies.py @@ -1,58 +1,49 @@ -from enum import Enum +from mars_patcher.constants.enemies import EnemyType +from mars_patcher.mf.constants.sprites import SpriteIdMF - -class EnemyType(Enum): - CRAWLING = 1 - GROUND = 2 - CEILING = 3 - GROUND_CEILING = 4 - WALL = 5 - FLYING = 6 - - -ENEMY_TYPES = { - 0x12: ("Hornoad", EnemyType.GROUND), - 0x13: ("Halzyn", EnemyType.FLYING), - 0x14: ("Zebesian (wall)", EnemyType.WALL), - 0x15: ("Hornoad spawner", EnemyType.GROUND), - 0x18: ("Moto", EnemyType.GROUND), - 0x1B: ("Yameba", EnemyType.FLYING), - 0x31: ("Zeela", EnemyType.CRAWLING), - 0x33: ("Skree (unused)", EnemyType.CEILING), - 0x37: ("Zombie", EnemyType.GROUND), - 0x39: ("Geemer", EnemyType.CRAWLING), - 0x3B: ("Waver", EnemyType.FLYING), - 0x3C: ("Sciser", EnemyType.CRAWLING), - 0x3D: ("Sidehopper", EnemyType.GROUND_CEILING), - 0x3E: ("Dessgeega", EnemyType.GROUND_CEILING), - 0x5A: ("Zoro", EnemyType.CRAWLING), - 0x5B: ("Kihunter (flying)", EnemyType.FLYING), - 0x5C: ("Kihunter (ground)", EnemyType.GROUND), - 0x5E: ("Reo", EnemyType.FLYING), - 0x60: ("Namihe", EnemyType.WALL), - 0x61: ("Fune", EnemyType.WALL), - 0x63: ("Blue zoro", EnemyType.CRAWLING), - 0x64: ("Geruda", EnemyType.FLYING), - 0x6A: ("Skultera (large)", EnemyType.FLYING), - 0x6B: ("Skultera (small, cannot become large)", EnemyType.FLYING), - 0x6C: ("Sova", EnemyType.CRAWLING), - 0x6D: ("Yard", EnemyType.CRAWLING), - 0x6E: ("Evir", EnemyType.FLYING), - 0x6F: ("Bull", EnemyType.FLYING), - 0x70: ("Memu", EnemyType.FLYING), - 0x71: ("Geruboss", EnemyType.CEILING), - 0x72: ("Choot", EnemyType.GROUND), - 0x73: ("Zebesian (ground)", EnemyType.GROUND), - 0x88: ("Ripper", EnemyType.FLYING), - 0xA3: ("Red Zeela", EnemyType.CRAWLING), - 0xA4: ("Owtch", EnemyType.CRAWLING), - 0xA8: ("Genesis", EnemyType.GROUND), - 0xA9: ("Puyo", EnemyType.GROUND), - 0xAD: ("Fake energy tank", EnemyType.GROUND), - 0xAE: ("Fake missile tank", EnemyType.GROUND), - 0xB3: ("Zebesian (aqua)", EnemyType.FLYING), - 0xB4: ("Zebesian (pre-aqua)", EnemyType.GROUND), - 0xB5: ("Skultera (small)", EnemyType.FLYING), - 0xBD: ("Powamp", EnemyType.FLYING), - 0xBE: ("Zozoro", EnemyType.CRAWLING), +ENEMY_TYPES_MF = { + SpriteIdMF.HORNOAD: EnemyType.GROUND, + SpriteIdMF.HALZYN: EnemyType.FLYING, + SpriteIdMF.ZEBESIAN_WALL: EnemyType.WALL, + SpriteIdMF.HORNOAD_SPAWNER: EnemyType.GROUND, + SpriteIdMF.MOTO: EnemyType.GROUND, + SpriteIdMF.YAMEBA: EnemyType.FLYING, + SpriteIdMF.ZEELA: EnemyType.CRAWLING, + SpriteIdMF.SKREE_UNUSED: EnemyType.CEILING, + SpriteIdMF.ZOMBIE: EnemyType.GROUND, + SpriteIdMF.GEEMER: EnemyType.CRAWLING, + SpriteIdMF.WAVER: EnemyType.FLYING, + SpriteIdMF.SCISER: EnemyType.CRAWLING, + SpriteIdMF.SIDEHOPPER: EnemyType.GROUND_CEILING, + SpriteIdMF.DESSGEEGA: EnemyType.GROUND_CEILING, + SpriteIdMF.ZORO: EnemyType.CRAWLING, + SpriteIdMF.KIHUNTER_FLYING: EnemyType.FLYING, + SpriteIdMF.KIHUNTER_GROUND: EnemyType.GROUND, + SpriteIdMF.REO: EnemyType.FLYING, + SpriteIdMF.NAMIHE: EnemyType.WALL, + SpriteIdMF.FUNE: EnemyType.WALL, + SpriteIdMF.BLUE_ZORO: EnemyType.CRAWLING, + SpriteIdMF.GERUDA: EnemyType.FLYING, + SpriteIdMF.SKULTERA_LARGE: EnemyType.FLYING, + SpriteIdMF.SKULTERA_SMALL_ONLY: EnemyType.FLYING, + SpriteIdMF.SOVA: EnemyType.CRAWLING, + SpriteIdMF.YARD: EnemyType.CRAWLING, + SpriteIdMF.EVIR: EnemyType.FLYING, + SpriteIdMF.BULL: EnemyType.FLYING, + SpriteIdMF.MEMU: EnemyType.FLYING, + SpriteIdMF.GERUBOSS: EnemyType.CEILING, + SpriteIdMF.CHOOT: EnemyType.GROUND, + SpriteIdMF.ZEBESIAN_GROUND: EnemyType.GROUND, + SpriteIdMF.RIPPER: EnemyType.FLYING, + SpriteIdMF.RED_ZEELA: EnemyType.CRAWLING, + SpriteIdMF.OWTCH: EnemyType.CRAWLING, + SpriteIdMF.GENESIS: EnemyType.GROUND, + SpriteIdMF.PUYO: EnemyType.GROUND, + SpriteIdMF.FAKE_ENERGY_TANK: EnemyType.GROUND, + SpriteIdMF.FAKE_MISSILE_TANK: EnemyType.GROUND, + SpriteIdMF.ZEBESIAN_AQUA: EnemyType.FLYING, + SpriteIdMF.ZEBESIAN_PRE_AQUA: EnemyType.GROUND, + SpriteIdMF.SKULTERA_SMALL: EnemyType.FLYING, + SpriteIdMF.POWAMP: EnemyType.FLYING, + SpriteIdMF.ZOZORO: EnemyType.CRAWLING, } diff --git a/src/mars_patcher/mf/constants/palettes.py b/src/mars_patcher/mf/constants/palettes.py index 0dd5591..1fdd7d2 100644 --- a/src/mars_patcher/mf/constants/palettes.py +++ b/src/mars_patcher/mf/constants/palettes.py @@ -1,4 +1,4 @@ -from mars_patcher.rom import Game +from mars_patcher.mf.constants.sprites import SpriteIdMF MF_TILESET_ALT_PAL_ROWS = { 0x46F134: 0xD, # 08 @@ -71,75 +71,47 @@ } -EXCLUDED_ENEMIES = { - Game.MF: { - 0x11, # SA-X (elevator) - 0x1D, # Area banner - 0x21, # Message box - 0x8C, # SA-X (TRO 1) - 0x8D, # SA-X (NOC) - 0xA0, # SA-X (ARC) - 0xBC, # SA-X (lab) - 0xC0, # SA-X (boss) - 0xC1, # SA-X monster - 0xC9, # SA-X (Omega cutscene) - 0xCD, # SA-X (TRO 2) - }, - Game.ZM: { - 0x10, # Unused - 0x11, # Item banner - 0x8A, # Crocomire - }, +EXCLUDED_ENEMIES_MF = { + SpriteIdMF.SAX_ELEVATOR, + SpriteIdMF.AREA_BANNER, + SpriteIdMF.MESSAGE_BANNER, + SpriteIdMF.SAX_TRO_1, + SpriteIdMF.SAX_NOC, + SpriteIdMF.SAX_ARC, + SpriteIdMF.SAX_LAB, + SpriteIdMF.SAX_BOSS, + SpriteIdMF.SAX_MONSTER, + SpriteIdMF.SAX_OMEGA, + SpriteIdMF.SAX_TRO_2, } -ENEMY_GROUPS = { - Game.MF: { - "Zebesian": [0x14, 0x73, 0xA2, 0xB3, 0xB4], - "Zeela": [0x31, 0xA3], - "Sciser": [0x3C, 0x8B], - "BeamCoreX": [0x56, 0x57, 0x58, 0x59], - "Zoro": [0x5A, 0x63, 0x89, 0x8A], - "FakeTank": [0xAD, 0xAE], - }, - Game.ZM: { - "Ripper": [0x16, 0x17, 0x71], - "Zeb": [0x18, 0x19], - "Skree": [0x1F, 0x20], - "ChozoStatue": [ - 0x22, - 0x23, - 0x24, - 0x25, - 0x26, - 0x27, - 0x28, - 0x29, - 0x2A, - 0x2B, - 0x2C, - 0x2D, - 0x2E, - 0x2F, - 0x30, - 0x31, - 0x58, - 0x59, - 0x94, - ], - "Viola": [0x34, 0x68, 0x69], - "GerutaTangleVine": [0x36, 0x37, 0xB7, 0xB8, 0xB9, 0xBA], - "Reo": [0x3F, 0x40], - "Imago": [0x4D, 0x4F, 0x70, 0x7F, 0x86, 0x8B], - "SpacePirate": [0x51, 0x52, 0x53, 0x54, 0x55, 0xB6, 0xC6, 0xC7], - "Gamet": [0x56, 0x57, 0x9D, 0x9E], - "Zebbo": [0x5B, 0x5C, 0xA1, 0xA2], - "Rinka": [0x66, 0x7C, 0xA5, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD], - "Geega": [0x7A, 0x7B, 0x9F, 0xA0], - "ElevatorStatue": [0x95, 0x96], - "UnknownItemBlocks": [0xAA, 0xAB, 0xAC], - }, + +ENEMY_GROUPS_MF = { + "Zebesian": [ + SpriteIdMF.ZEBESIAN_WALL, + SpriteIdMF.ZEBESIAN_GROUND, + SpriteIdMF.GOLD_ZEBESIAN, + SpriteIdMF.ZEBESIAN_AQUA, + SpriteIdMF.ZEBESIAN_PRE_AQUA, + ], + "Zeela": [SpriteIdMF.ZEELA, SpriteIdMF.RED_ZEELA], + "Sciser": [SpriteIdMF.SCISER, SpriteIdMF.GOLD_SCISER], + "BeamCoreX": [ + SpriteIdMF.CHARGE_BEAM_CORE_X, + SpriteIdMF.WIDE_BEAM_CORE_X, + SpriteIdMF.PLASMA_BEAM_CORE_X, + SpriteIdMF.WAVE_BEAM_CORE_X, + ], + "Zoro": [ + SpriteIdMF.ZORO, + SpriteIdMF.BLUE_ZORO, + SpriteIdMF.ZORO_COCOON, + SpriteIdMF.ZORO_HUSK, + ], + "FakeTank": [SpriteIdMF.FAKE_ENERGY_TANK, SpriteIdMF.FAKE_MISSILE_TANK], } + NETTORI_EXTRA_PALS = [ (0x36A480, 1), # Medium health palette (0x36A4A0, 1), # Low health palette diff --git a/src/mars_patcher/mf/constants/sprites.py b/src/mars_patcher/mf/constants/sprites.py new file mode 100644 index 0000000..f8c6cdd --- /dev/null +++ b/src/mars_patcher/mf/constants/sprites.py @@ -0,0 +1,71 @@ +from enum import IntEnum + + +class SpriteIdMF(IntEnum): + SAX_ELEVATOR = 0x11 + HORNOAD = 0x12 + HALZYN = 0x13 + ZEBESIAN_WALL = 0x14 + HORNOAD_SPAWNER = 0x15 + MOTO = 0x18 + YAMEBA = 0x1B + AREA_BANNER = 0x1D + MESSAGE_BANNER = 0x21 + SAMUS_EATER_BUD = 0x26 + SAMUS_EATER = 0x27 + ZEELA = 0x31 + SKREE_UNUSED = 0x33 + ZOMBIE = 0x37 + GEEMER = 0x39 + WAVER = 0x3B + SCISER = 0x3C + SIDEHOPPER = 0x3D + DESSGEEGA = 0x3E + ICE_BEAM_ABILITY = 0x4D + NETTORI = 0x54 + CHARGE_BEAM_CORE_X = 0x56 + WIDE_BEAM_CORE_X = 0x57 + PLASMA_BEAM_CORE_X = 0x58 + WAVE_BEAM_CORE_X = 0x59 + ZORO = 0x5A + KIHUNTER_FLYING = 0x5B + KIHUNTER_GROUND = 0x5C + REO = 0x5E + NAMIHE = 0x60 + FUNE = 0x61 + BLUE_ZORO = 0x63 + GERUDA = 0x64 + SKULTERA_LARGE = 0x6A + SKULTERA_SMALL_ONLY = 0x6B + SOVA = 0x6C + YARD = 0x6D + EVIR = 0x6E + BULL = 0x6F + MEMU = 0x70 + GERUBOSS = 0x71 + CHOOT = 0x72 + ZEBESIAN_GROUND = 0x73 + RIPPER = 0x88 + ZORO_COCOON = 0x89 + ZORO_HUSK = 0x8A + GOLD_SCISER = 0x8B + SAX_TRO_1 = 0x8C + SAX_NOC = 0x8D + SAX_ARC = 0xA0 + GOLD_ZEBESIAN = 0xA2 + RED_ZEELA = 0xA3 + OWTCH = 0xA4 + GENESIS = 0xA8 + PUYO = 0xA9 + FAKE_ENERGY_TANK = 0xAD + FAKE_MISSILE_TANK = 0xAE + ZEBESIAN_AQUA = 0xB3 + ZEBESIAN_PRE_AQUA = 0xB4 + SKULTERA_SMALL = 0xB5 + SAX_LAB = 0xBC + POWAMP = 0xBD + ZOZORO = 0xBE + SAX_BOSS = 0xC0 + SAX_MONSTER = 0xC1 + SAX_OMEGA = 0xC9 + SAX_TRO_2 = 0xCD diff --git a/src/mars_patcher/random_enemies.py b/src/mars_patcher/random_enemies.py index d8842d1..30e8b87 100644 --- a/src/mars_patcher/random_enemies.py +++ b/src/mars_patcher/random_enemies.py @@ -1,14 +1,27 @@ import random +from typing import TYPE_CHECKING +from mars_patcher.constants.enemies import EnemyType from mars_patcher.constants.game_data import spriteset_count, spriteset_ptrs -from mars_patcher.mf.constants.enemies import ENEMY_TYPES, EnemyType +from mars_patcher.mf.constants.enemies import ENEMY_TYPES_MF from mars_patcher.mf.constants.game_data import sprite_vram_sizes from mars_patcher.rom import Rom +if TYPE_CHECKING: + from mars_patcher.mf.constants.sprites import SpriteIdMF + from mars_patcher.zm.constants.sprites import SpriteIdZM + def randomize_enemies(rom: Rom) -> None: # Setup enemy types dictionary - enemy_types = {k: v[1] for k, v in ENEMY_TYPES.items()} + _enemy_types: dict[SpriteIdMF, EnemyType] | dict[SpriteIdZM, EnemyType] + if rom.is_mf(): + _enemy_types = ENEMY_TYPES_MF + elif rom.is_zm(): + raise NotImplementedError("Enemey types not yet implemented for ZM") + else: + raise ValueError(rom.game) + enemy_types = {k.value: v for k, v in _enemy_types.items()} # Get graphics info for each enemy size_addr = sprite_vram_sizes(rom) diff --git a/src/mars_patcher/random_palettes.py b/src/mars_patcher/random_palettes.py index df40020..da4a4f0 100644 --- a/src/mars_patcher/random_palettes.py +++ b/src/mars_patcher/random_palettes.py @@ -1,5 +1,8 @@ import random from enum import Enum +from typing import TypeAlias + +from typing_extensions import Self import mars_patcher.constants.game_data as gd from mars_patcher.mf.auto_generated_types import ( @@ -9,14 +12,20 @@ ) from mars_patcher.mf.constants.game_data import sax_palettes, sprite_vram_sizes from mars_patcher.mf.constants.palettes import ( - ENEMY_GROUPS, - EXCLUDED_ENEMIES, + ENEMY_GROUPS_MF, + EXCLUDED_ENEMIES_MF, MF_TILESET_ALT_PAL_ROWS, NETTORI_EXTRA_PALS, TILESET_ANIM_PALS, ) +from mars_patcher.mf.constants.sprites import SpriteIdMF from mars_patcher.palette import ColorChange, Palette, SineWave from mars_patcher.rom import Game, Rom +from mars_patcher.zm.constants.game_data import statues_cutscene_palette_addr +from mars_patcher.zm.constants.palettes import ENEMY_GROUPS_ZM, EXCLUDED_ENEMIES_ZM +from mars_patcher.zm.constants.sprites import SpriteIdZM + +HueRange: TypeAlias = tuple[int, int] class PaletteType(Enum): @@ -37,7 +46,7 @@ class PaletteSettings: def __init__( self, seed: int, - pal_types: dict[PaletteType, tuple[int, int]], # TODO: change this tuple(int, int) + pal_types: dict[PaletteType, HueRange], color_space: MarsschemamfPalettesColorspace, symmetric: bool, extra_variation: bool, @@ -49,7 +58,7 @@ def __init__( self.extra_variation = extra_variation @classmethod - def from_json(cls, data: MarsschemamfPalettes) -> "PaletteSettings": + def from_json(cls, data: MarsschemamfPalettes) -> Self: seed = data.get("Seed", random.randint(0, 2**31 - 1)) random.seed(seed) pal_types = {} @@ -63,7 +72,7 @@ def from_json(cls, data: MarsschemamfPalettes) -> "PaletteSettings": return cls(seed, pal_types, color_space, symmetric, True) @classmethod - def get_hue_range(cls, data: MarsschemamfPalettesRandomize) -> tuple[int, int]: + def get_hue_range(cls, data: MarsschemamfPalettesRandomize) -> HueRange: hue_min = data.get("HueMin") hue_max = data.get("HueMax") if hue_min is None or hue_max is None: @@ -104,15 +113,16 @@ def change_palette_oklab( ) -> None: pal.change_colors_oklab(change, excluded_rows) - def generate_palette_change(self, hue_range: tuple[int, int]) -> ColorChange: + def generate_palette_change(self, hue_range: HueRange) -> ColorChange: """Generates a random color change. hue_range determines how far each color's hue will be initially rotated. Individual colors can be additionally rotated using the values of a random sine wave.""" - hue_shift = random.randint(hue_range[0], hue_range[1]) + hue_min, hue_max = hue_range + hue_shift = random.randint(hue_min, hue_max) if self.settings.symmetric and random.choice([True, False]): hue_shift = 360 - hue_shift if self.settings.extra_variation: - hue_var_range = min(1.0, (hue_range[1] - hue_range[0]) / 180) + hue_var_range = min(1.0, (hue_max - hue_min) / 180) hue_var = SineWave.generate(hue_var_range) else: hue_var = None @@ -143,17 +153,17 @@ def change_palettes(self, pals: list[tuple[int, int]], change: ColorChange) -> N pal.write(self.rom, addr) self.randomized_pals.add(addr) - def randomize_samus(self, hue_range: tuple[int, int]) -> None: + def randomize_samus(self, hue_range: HueRange) -> None: change = self.generate_palette_change(hue_range) self.change_palettes(gd.samus_palettes(self.rom), change) self.change_palettes(gd.helmet_cursor_palettes(self.rom), change) self.change_palettes(sax_palettes(self.rom), change) - def randomize_beams(self, hue_range: tuple[int, int]) -> None: + def randomize_beams(self, hue_range: HueRange) -> None: change = self.generate_palette_change(hue_range) self.change_palettes(gd.beam_palettes(self.rom), change) - def randomize_tilesets(self, hue_range: tuple[int, int]) -> None: + def randomize_tilesets(self, hue_range: HueRange) -> None: rom = self.rom ts_addr = gd.tileset_entries(rom) ts_count = gd.tileset_count(rom) @@ -202,15 +212,28 @@ def randomize_anim_palette(self, anim_pal_id: int, change: ColorChange) -> None: pal.write(rom, pal_addr) self.randomized_pals.add(pal_addr) - def randomize_enemies(self, hue_range: tuple[int, int]) -> None: + def randomize_enemies(self, hue_range: HueRange) -> None: rom = self.rom - excluded = EXCLUDED_ENEMIES[rom.game] + _excluded: set[SpriteIdMF] | set[SpriteIdZM] + if rom.is_mf(): + _excluded = EXCLUDED_ENEMIES_MF + elif rom.is_zm(): + _excluded = EXCLUDED_ENEMIES_ZM + else: + raise ValueError(rom.game) + excluded = {en_id.value for en_id in _excluded} sp_count = gd.sprite_count(rom) to_randomize = set(range(0x10, sp_count)) to_randomize -= excluded # Go through sprites in groups - groups = ENEMY_GROUPS[rom.game] + groups: dict[str, list[SpriteIdMF]] | dict[str, list[SpriteIdZM]] + if rom.is_mf(): + groups = ENEMY_GROUPS_MF + elif rom.is_zm(): + groups = ENEMY_GROUPS_ZM + else: + raise ValueError(rom.game) for _, sprite_ids in groups.items(): change = self.generate_palette_change(hue_range) for sprite_id in sprite_ids: @@ -219,9 +242,9 @@ def randomize_enemies(self, hue_range: tuple[int, int]) -> None: to_randomize.remove(sprite_id) # Go through remaining sprites - for sprite_id in to_randomize: + for sp_id in to_randomize: change = self.generate_palette_change(hue_range) - self.randomize_enemy(sprite_id, change) + self.randomize_enemy(sp_id, change) def randomize_enemy(self, sprite_id: int, change: ColorChange) -> None: # Get palette address and row count @@ -229,10 +252,12 @@ def randomize_enemy(self, sprite_id: int, change: ColorChange) -> None: sprite_gfx_id = sprite_id - 0x10 pal_ptr = gd.sprite_palette_ptrs(rom) pal_addr = rom.read_ptr(pal_ptr + sprite_gfx_id * 4) + + # Skip palettes that have already been randomized if pal_addr in self.randomized_pals: return if rom.is_mf(): - if sprite_id == 0x4D or sprite_id == 0xBE: + if sprite_id == SpriteIdMF.ICE_BEAM_ABILITY or sprite_id == SpriteIdMF.ZOZORO: # Ice beam ability and zozoros only have 1 row, not 2 rows = 1 else: @@ -245,12 +270,17 @@ def randomize_enemy(self, sprite_id: int, change: ColorChange) -> None: rows = (rom.read_32(gfx_addr) >> 8) // 0x800 else: raise ValueError("Unknown game!") + # Load palette, change colors, and write to ROM pal = Palette(rows, rom, pal_addr) self.change_func(pal, change) pal.write(rom, pal_addr) self.randomized_pals.add(pal_addr) - if rom.is_mf() and sprite_id == 0x26: + if rom.is_mf() and sprite_id in { + SpriteIdMF.SAMUS_EATER_BUD, + SpriteIdMF.SAMUS_EATER, + SpriteIdMF.NETTORI, + }: self.fix_nettori(change) def get_sprite_addr(self, sprite_id: int) -> int: @@ -268,31 +298,30 @@ def fix_nettori(self, change: ColorChange) -> None: self.change_func(pal, change) pal.write(self.rom, addr) - # TODO: Uncomment this once ZM data addresses are added - # def fix_zm_palettes(self) -> None: - # if ( - # PaletteType.ENEMIES in self.settings.pal_types - # or PaletteType.TILESETS in self.settings.pal_types - # ): - # # Fix kraid's body - # sp_addr = self.get_sprite_addr(0x6F) - # ts_addr = self.get_tileset_addr(9) - # self.rom.copy_bytes(sp_addr, ts_addr + 0x100, 0x20) - - # if PaletteType.TILESETS in self.settings.pal_types: - # # Fix kraid elevator statue - # sp_addr = self.get_sprite_addr(0x95) - # ts_addr = self.get_tileset_addr(0x35) - # self.rom.copy_bytes(ts_addr + 0x20, sp_addr, 0x20) - - # # Fix ridley elevator statue - # ts_addr = self.get_tileset_addr(7) - # self.rom.copy_bytes(ts_addr + 0x20, sp_addr + 0x20, 0x20) - - # # Fix tourian statues - # sp_addr = self.get_sprite_addr(0xA3) - # ts_addr = self.get_tileset_addr(0x41) - # self.rom.copy_bytes(ts_addr + 0x60, sp_addr, 0x20) - # # Fix cutscene - # sp_addr = gd.tourian_statues_cutscene_palette(self.rom) - # self.rom.copy_bytes(ts_addr, sp_addr, 0xC0) + def fix_zm_palettes(self) -> None: + if ( + PaletteType.ENEMIES in self.settings.pal_types + or PaletteType.TILESETS in self.settings.pal_types + ): + # Fix kraid's body + sp_addr = self.get_sprite_addr(SpriteIdZM.KRAID) + ts_addr = self.get_tileset_addr(9) + self.rom.copy_bytes(sp_addr, ts_addr + 0x100, 0x20) + + if PaletteType.TILESETS in self.settings.pal_types: + # Fix kraid elevator statue + sp_addr = self.get_sprite_addr(SpriteIdZM.KRAID_ELEVATOR_STATUE) + ts_addr = self.get_tileset_addr(0x35) + self.rom.copy_bytes(ts_addr + 0x20, sp_addr, 0x20) + + # Fix ridley elevator statue + ts_addr = self.get_tileset_addr(7) + self.rom.copy_bytes(ts_addr + 0x20, sp_addr + 0x20, 0x20) + + # Fix tourian statues + sp_addr = self.get_sprite_addr(SpriteIdZM.KRAID_STATUE) + ts_addr = self.get_tileset_addr(0x41) + self.rom.copy_bytes(ts_addr + 0x60, sp_addr, 0x20) + # Fix cutscene + sp_addr = statues_cutscene_palette_addr(self.rom) + self.rom.copy_bytes(ts_addr, sp_addr, 0xC0) diff --git a/src/mars_patcher/zm/constants/game_data.py b/src/mars_patcher/zm/constants/game_data.py index a94378f..4308480 100644 --- a/src/mars_patcher/zm/constants/game_data.py +++ b/src/mars_patcher/zm/constants/game_data.py @@ -80,3 +80,7 @@ def power_bomb_tank_increase_amount_addr(rom: Rom) -> int: def title_text_lines_addr(rom: Rom) -> int: return rom.read_ptr(ReservedPointersZM.TITLE_TEXT_LINES_PTR) + + +def statues_cutscene_palette_addr(rom: Rom) -> int: + return rom.read_ptr(ReservedPointersZM.STATUES_CUTSCENE_PALETTE_PTR) diff --git a/src/mars_patcher/zm/constants/palettes.py b/src/mars_patcher/zm/constants/palettes.py new file mode 100644 index 0000000..4054681 --- /dev/null +++ b/src/mars_patcher/zm/constants/palettes.py @@ -0,0 +1,96 @@ +from mars_patcher.zm.constants.sprites import SpriteIdZM + +EXCLUDED_ENEMIES_ZM = { + SpriteIdZM.UNUSED_10, + SpriteIdZM.MESSAGE_BANNER, +} + + +ENEMY_GROUPS_ZM = { + "Ripper": [SpriteIdZM.RIPPER_BROWN, SpriteIdZM.RIPPER_PURPLE, SpriteIdZM.RIPPER_II], + "Zeb": [SpriteIdZM.ZEB, SpriteIdZM.ZEB_BLUE], + "Skree": [SpriteIdZM.SKREE_GREEN, SpriteIdZM.SKREE_BLUE], + "ChozoStatue": [ + SpriteIdZM.CHOZO_STATUE_LONG_BEAM_HINT, + SpriteIdZM.LONG_BEAM_CHOZO_STATUE, + SpriteIdZM.CHOZO_STATUE_ICE_BEAM_HINT, + SpriteIdZM.ICE_BEAM_CHOZO_STATUE, + SpriteIdZM.CHOZO_STATUE_WAVE_BEAM_HINT, + SpriteIdZM.WAVE_BEAM_CHOZO_STATUE, + SpriteIdZM.CHOZO_STATUE_BOMB_HINT, + SpriteIdZM.BOMB_CHOZO_STATUE, + SpriteIdZM.CHOZO_STATUE_SPEED_BOOSTER_HINT, + SpriteIdZM.SPEED_BOOSTER_CHOZO_STATUE, + SpriteIdZM.CHOZO_STATUE_HI_JUMP_HINT, + SpriteIdZM.HI_JUMP_CHOZO_STATUE, + SpriteIdZM.CHOZO_STATUE_SCREW_ATTACK_HINT, + SpriteIdZM.SCREW_ATTACK_CHOZO_STATUE, + SpriteIdZM.CHOZO_STATUE_VARIA_SUIT_HINT, + SpriteIdZM.VARIA_SUIT_CHOZO_STATUE, + SpriteIdZM.GRAVITY_SUIT_CHOZO_STATUE, + SpriteIdZM.SPACE_JUMP_CHOZO_STATUE, + SpriteIdZM.PLASMA_BEAM_CHOZO_STATUE, + ], + "Viola": [SpriteIdZM.MULTIVIOLA, SpriteIdZM.VIOLA_BLUE, SpriteIdZM.VIOLA_ORANGE], + "GerutaTangleVine": [ + SpriteIdZM.GERUTA_RED, + SpriteIdZM.GERUTA_GREEN, + SpriteIdZM.TANGLE_VINE_RED_GERUTA, + SpriteIdZM.TANGLE_VINE_GERUTA, + SpriteIdZM.TANGLE_VINE_LARVA_RIGHT, + SpriteIdZM.TANGLE_VINE_LARVA_LEFT, + ], + "Reo": [SpriteIdZM.REO_GREEN_WINGS, SpriteIdZM.REO_PURPLE_WINGS], + "Imago": [ + SpriteIdZM.IMAGO_LARVA_RIGHT, + SpriteIdZM.IMAGO_COCOON, + SpriteIdZM.IMAGO_COCOON_AFTER_FIGHT, + SpriteIdZM.IMAGO_LARVA_RIGHT_SIDE, + SpriteIdZM.IMAGO, + SpriteIdZM.IMAGO_LARVA_LEFT, + ], + "SpacePirate": [ + SpriteIdZM.SPACE_PIRATE, + SpriteIdZM.SPACE_PIRATE_WAITING_1, + SpriteIdZM.SPACE_PIRATE_WAITING_2, + SpriteIdZM.SPACE_PIRATE_WAITING_3, + SpriteIdZM.SPACE_PIRATE_2, + SpriteIdZM.SPACE_PIRATE_CARRYING_POWER_BOMB, + SpriteIdZM.BLACK_SPACE_PIRATE, + SpriteIdZM.ESCAPE_SHIP_SPACE_PIRATE, + ], + "Gamet": [ + SpriteIdZM.GAMET_BLUE_SINGLE, + SpriteIdZM.GAMET_RED_SINGLE, + SpriteIdZM.GAMET_BLUE_LEADER, + SpriteIdZM.GAMET_BLUE_FOLLOWER, + ], + "Zebbo": [ + SpriteIdZM.ZEBBO_GREEN, + SpriteIdZM.ZEBBO_YELLOW, + SpriteIdZM.ZEBBO_GREEN_LEADER, + SpriteIdZM.ZEBBO_GREEN_FOLLOWER, + ], + "Rinka": [ + SpriteIdZM.RINKA_ORANGE, + SpriteIdZM.RINKA_MOTHER_BRAIN, + SpriteIdZM.RINKA_GREEN, + SpriteIdZM.RINKA_MOTHER_BRAIN_C9, + SpriteIdZM.RINKA_MOTHER_BRAIN_CA, + SpriteIdZM.RINKA_MOTHER_BRAIN_CB, + SpriteIdZM.RINKA_MOTHER_BRAIN_CC, + SpriteIdZM.RINKA_MOTHER_BRAIN_CD, + ], + "Geega": [ + SpriteIdZM.GEEGA, + SpriteIdZM.GEEGA_WHITE, + SpriteIdZM.GEEGA_LEADER, + SpriteIdZM.GEEGA_FOLLOWER, + ], + "ElevatorStatue": [SpriteIdZM.KRAID_ELEVATOR_STATUE, SpriteIdZM.RIDLEY_ELEVATOR_STATUE], + "UnknownItemBlocks": [ + SpriteIdZM.PLASMA_BEAM_BLOCK, + SpriteIdZM.GRAVITY_SUIT_BLOCK, + SpriteIdZM.SPACE_JUMP_BLOCK, + ], +} diff --git a/src/mars_patcher/zm/constants/reserved_space.py b/src/mars_patcher/zm/constants/reserved_space.py index d660d76..9c81f1e 100644 --- a/src/mars_patcher/zm/constants/reserved_space.py +++ b/src/mars_patcher/zm/constants/reserved_space.py @@ -55,6 +55,8 @@ class ReservedPointersZM(IntEnum): """Pointer to the palette used for the helmet cursor in menus.""" BEAM_PALETTES_PTR = auto() """Pointer to the start of the beam palettes.""" + STATUES_CUTSCENE_PALETTE_PTR = auto() + """Pointer to the palette of the boss statues near Tourian used during the cutscene.""" CHARACTER_WIDTHS_PTR = auto() """Pointer to the character widths table.""" SOUND_DATA_PTR = auto() diff --git a/src/mars_patcher/zm/constants/sprites.py b/src/mars_patcher/zm/constants/sprites.py new file mode 100644 index 0000000..52ee908 --- /dev/null +++ b/src/mars_patcher/zm/constants/sprites.py @@ -0,0 +1,84 @@ +from enum import IntEnum + + +class SpriteIdZM(IntEnum): + UNUSED_10 = 0x10 + MESSAGE_BANNER = 0x11 + RIPPER_BROWN = 0x16 + RIPPER_PURPLE = 0x17 + ZEB = 0x18 + ZEB_BLUE = 0x19 + SKREE_GREEN = 0x1F + SKREE_BLUE = 0x20 + CHOZO_STATUE_LONG_BEAM_HINT = 0x22 + LONG_BEAM_CHOZO_STATUE = 0x23 + CHOZO_STATUE_ICE_BEAM_HINT = 0x24 + ICE_BEAM_CHOZO_STATUE = 0x25 + CHOZO_STATUE_WAVE_BEAM_HINT = 0x26 + WAVE_BEAM_CHOZO_STATUE = 0x27 + CHOZO_STATUE_BOMB_HINT = 0x28 + BOMB_CHOZO_STATUE = 0x29 + CHOZO_STATUE_SPEED_BOOSTER_HINT = 0x2A + SPEED_BOOSTER_CHOZO_STATUE = 0x2B + CHOZO_STATUE_HI_JUMP_HINT = 0x2C + HI_JUMP_CHOZO_STATUE = 0x2D + CHOZO_STATUE_SCREW_ATTACK_HINT = 0x2E + SCREW_ATTACK_CHOZO_STATUE = 0x2F + CHOZO_STATUE_VARIA_SUIT_HINT = 0x30 + VARIA_SUIT_CHOZO_STATUE = 0x31 + MULTIVIOLA = 0x34 + GERUTA_RED = 0x36 + GERUTA_GREEN = 0x37 + REO_GREEN_WINGS = 0x3F + REO_PURPLE_WINGS = 0x40 + IMAGO_LARVA_RIGHT = 0x4D + IMAGO_COCOON = 0x4F + SPACE_PIRATE = 0x51 + SPACE_PIRATE_WAITING_1 = 0x52 + SPACE_PIRATE_WAITING_2 = 0x53 + SPACE_PIRATE_WAITING_3 = 0x54 + SPACE_PIRATE_2 = 0x55 + GAMET_BLUE_SINGLE = 0x56 + GAMET_RED_SINGLE = 0x57 + GRAVITY_SUIT_CHOZO_STATUE = 0x58 + SPACE_JUMP_CHOZO_STATUE = 0x59 + ZEBBO_GREEN = 0x5B + ZEBBO_YELLOW = 0x5C + RINKA_ORANGE = 0x66 + VIOLA_BLUE = 0x68 + VIOLA_ORANGE = 0x69 + KRAID = 0x6F + IMAGO_COCOON_AFTER_FIGHT = 0x70 + RIPPER_II = 0x71 + GEEGA = 0x7A + GEEGA_WHITE = 0x7B + RINKA_MOTHER_BRAIN = 0x7C + IMAGO_LARVA_RIGHT_SIDE = 0x7F + IMAGO = 0x86 + IMAGO_LARVA_LEFT = 0x8B + PLASMA_BEAM_CHOZO_STATUE = 0x94 + KRAID_ELEVATOR_STATUE = 0x95 + RIDLEY_ELEVATOR_STATUE = 0x96 + GAMET_BLUE_LEADER = 0x9D + GAMET_BLUE_FOLLOWER = 0x9E + GEEGA_LEADER = 0x9F + GEEGA_FOLLOWER = 0xA0 + ZEBBO_GREEN_LEADER = 0xA1 + ZEBBO_GREEN_FOLLOWER = 0xA2 + KRAID_STATUE = 0xA3 + RINKA_GREEN = 0xA5 + PLASMA_BEAM_BLOCK = 0xAA + GRAVITY_SUIT_BLOCK = 0xAB + SPACE_JUMP_BLOCK = 0xAC + SPACE_PIRATE_CARRYING_POWER_BOMB = 0xB6 + TANGLE_VINE_RED_GERUTA = 0xB7 + TANGLE_VINE_GERUTA = 0xB8 + TANGLE_VINE_LARVA_RIGHT = 0xB9 + TANGLE_VINE_LARVA_LEFT = 0xBA + BLACK_SPACE_PIRATE = 0xC6 + ESCAPE_SHIP_SPACE_PIRATE = 0xC7 + RINKA_MOTHER_BRAIN_C9 = 0xC9 + RINKA_MOTHER_BRAIN_CA = 0xCA + RINKA_MOTHER_BRAIN_CB = 0xCB + RINKA_MOTHER_BRAIN_CC = 0xCC + RINKA_MOTHER_BRAIN_CD = 0xCD