From 7e35c51925c534916192c2a819bcd3c534e6ec0b Mon Sep 17 00:00:00 2001 From: biosp4rk <37962487+biosp4rk@users.noreply.github.com> Date: Thu, 26 Feb 2026 13:16:09 -0800 Subject: [PATCH 1/2] [ZM] Support starting location and items --- src/mars_patcher/zm/auto_generated_types.py | 2 +- src/mars_patcher/zm/constants/game_data.py | 4 - src/mars_patcher/zm/constants/items.py | 6 + .../zm/constants/reserved_space.py | 2 - src/mars_patcher/zm/constants/sprites.py | 2 + src/mars_patcher/zm/data/schema.json | 2 +- src/mars_patcher/zm/patcher.py | 21 +-- src/mars_patcher/zm/starting.py | 127 ++++++++++++++++++ 8 files changed, 148 insertions(+), 18 deletions(-) create mode 100644 src/mars_patcher/zm/starting.py diff --git a/src/mars_patcher/zm/auto_generated_types.py b/src/mars_patcher/zm/auto_generated_types.py index 69b5e51..05ed395 100644 --- a/src/mars_patcher/zm/auto_generated_types.py +++ b/src/mars_patcher/zm/auto_generated_types.py @@ -351,7 +351,7 @@ class MarsschemazmStartingItems(typ.TypedDict, total=False): downloaded_maps: typ.Annotated[list[AreaId], 'Unique items'] = [] """Which area maps will be downloaded from the start.""" - suit_type: MarsschemazmStartingItemsSuitType = 'normal' + suit_type: MarsschemazmStartingItemsSuitType = 'NORMAL' """Which suit type the player should start with.""" ziplines_activated: bool = False diff --git a/src/mars_patcher/zm/constants/game_data.py b/src/mars_patcher/zm/constants/game_data.py index c635bf0..3c38d35 100644 --- a/src/mars_patcher/zm/constants/game_data.py +++ b/src/mars_patcher/zm/constants/game_data.py @@ -2,10 +2,6 @@ from mars_patcher.zm.constants.reserved_space import ReservedPointersZM -def tileset_tilemap_sizes_addr(rom: Rom) -> int: - return rom.read_ptr(ReservedPointersZM.TILESET_TILEMAP_SIZES_PTR.value) - - def chozo_statue_targets_addr(rom: Rom) -> int: return rom.read_ptr(ReservedPointersZM.CHOZO_STATUE_TARGETS_PTR.value) diff --git a/src/mars_patcher/zm/constants/items.py b/src/mars_patcher/zm/constants/items.py index 3da739b..7191c20 100644 --- a/src/mars_patcher/zm/constants/items.py +++ b/src/mars_patcher/zm/constants/items.py @@ -176,3 +176,9 @@ class HintLocation(IntEnum): "MORPH_BALL": 1 << 6, "POWER_GRIP": 1 << 7, } + + +class SuitType(IntEnum): + NORMAL = 0 + FULLY_POWERED = auto() + SUITLESS = auto() diff --git a/src/mars_patcher/zm/constants/reserved_space.py b/src/mars_patcher/zm/constants/reserved_space.py index b19b022..b783821 100644 --- a/src/mars_patcher/zm/constants/reserved_space.py +++ b/src/mars_patcher/zm/constants/reserved_space.py @@ -33,8 +33,6 @@ class ReservedPointersZM(IntEnum): """Pointer to the list of pointers to the room entries for each area.""" TILESET_ENTRIES_PTR = auto() """Pointer to the list of tileset entries.""" - TILESET_TILEMAP_SIZES_PTR = auto() - """Pointer to an array containing the size of each tileset's tilemap.""" ANIM_TILESET_ENTRIES_PTR = auto() """Pointer to the list of animated tileset entries.""" MINIMAPS_PTR = auto() diff --git a/src/mars_patcher/zm/constants/sprites.py b/src/mars_patcher/zm/constants/sprites.py index 52ee908..98f6654 100644 --- a/src/mars_patcher/zm/constants/sprites.py +++ b/src/mars_patcher/zm/constants/sprites.py @@ -47,6 +47,7 @@ class SpriteIdZM(IntEnum): RINKA_ORANGE = 0x66 VIOLA_BLUE = 0x68 VIOLA_ORANGE = 0x69 + SAVE_PLATFORM = 0x6E KRAID = 0x6F IMAGO_COCOON_AFTER_FIGHT = 0x70 RIPPER_II = 0x71 @@ -56,6 +57,7 @@ class SpriteIdZM(IntEnum): IMAGO_LARVA_RIGHT_SIDE = 0x7F IMAGO = 0x86 IMAGO_LARVA_LEFT = 0x8B + SAVE_PLATFORM_CHOZODIA = 0x92 PLASMA_BEAM_CHOZO_STATUE = 0x94 KRAID_ELEVATOR_STATUE = 0x95 RIDLEY_ELEVATOR_STATUE = 0x96 diff --git a/src/mars_patcher/zm/data/schema.json b/src/mars_patcher/zm/data/schema.json index 0bed6c3..e4b008c 100644 --- a/src/mars_patcher/zm/data/schema.json +++ b/src/mars_patcher/zm/data/schema.json @@ -190,7 +190,7 @@ "NORMAL", "FULLY_POWERED" ], - "default": "normal" + "default": "NORMAL" }, "ziplines_activated": { "type": "boolean", diff --git a/src/mars_patcher/zm/patcher.py b/src/mars_patcher/zm/patcher.py index 22f6a85..b5789f3 100644 --- a/src/mars_patcher/zm/patcher.py +++ b/src/mars_patcher/zm/patcher.py @@ -8,6 +8,7 @@ from mars_patcher.zm.constants.reserved_space import ReservedConstantsZM from mars_patcher.zm.item_patcher import ItemPatcher, set_tank_increments from mars_patcher.zm.locations import LocationSettings +from mars_patcher.zm.starting import set_starting_items, set_starting_location def patch_zm( @@ -47,21 +48,21 @@ def patch_zm( item_patcher = ItemPatcher(rom, loc_settings) item_patcher.write_items() - # Starting location - # if "StartingLocation" in patch_data: - # status_update("Writing starting location...", -1) - # set_starting_location(rom, patch_data["StartingLocation"]) - - # Starting items - # if "StartingItems" in patch_data: - # status_update("Writing starting items...", -1) - # set_starting_items(rom, patch_data["StartingItems"]) - # Music if "MusicReplacement" in patch_data: status_update("Writing music...", -1) set_sounds(rom, patch_data["MusicReplacement"]) + # Starting location + if "starting_location" in patch_data: + status_update("Writing starting location...", -1) + set_starting_location(rom, patch_data["starting_location"]) + + # Starting items + if "starting_items" in patch_data: + status_update("Writing starting items...", -1) + set_starting_items(rom, patch_data["starting_items"]) + # Tank increments if "tank_increments" in patch_data: status_update("Writing tank increments...", -1) diff --git a/src/mars_patcher/zm/starting.py b/src/mars_patcher/zm/starting.py new file mode 100644 index 0000000..d17e86f --- /dev/null +++ b/src/mars_patcher/zm/starting.py @@ -0,0 +1,127 @@ +from mars_patcher.constants.game_data import area_doors_ptrs, spriteset_ptrs +from mars_patcher.rom import Rom +from mars_patcher.room_entry import RoomEntry +from mars_patcher.zm.auto_generated_types import ( + MarsschemazmStartingItems, + MarsschemazmStartingLocation, +) +from mars_patcher.zm.constants.items import BEAM_BOMB_FLAGS, SUIT_MISC_FLAGS, SuitType +from mars_patcher.zm.constants.reserved_space import ReservedPointersZM +from mars_patcher.zm.constants.sprites import SpriteIdZM + + +def set_starting_location(rom: Rom, data: MarsschemazmStartingLocation) -> None: + area = data["area"] + room = data["room"] + # Don't do anything for area 0 room 0 + if area == 0 and room == 0: + return + # Find any door in the provided room + door = find_door_in_room(rom, area, room) + # Check if save pad in room + pos = find_save_pad_position(rom, area, room) + if pos is not None: + x_pos, y_pos = pos + else: + x_pos = data["block_x"] + y_pos = data["block_y"] + # Write to rom (see struct StartingInfo in structs/randomizer.h) + starting_info_addr = rom.read_ptr(ReservedPointersZM.STARTING_INFO_PTR.value) + rom.write_8(starting_info_addr, area) + rom.write_8(starting_info_addr + 1, room) + rom.write_8(starting_info_addr + 2, door) + rom.write_8(starting_info_addr + 3, x_pos) + rom.write_8(starting_info_addr + 4, y_pos) + # See struct InGameCutsceneData in structs/in_game_cutscene.h + intro_cutscene_addr = rom.read_ptr(ReservedPointersZM.INTRO_CUTSCENE_DATA_PTR.value) + rom.write_8(intro_cutscene_addr + 1, area) + + +def find_door_in_room(rom: Rom, area: int, room: int) -> int: + door_addr = rom.read_ptr(area_doors_ptrs(rom) + area * 4) + door = None + for d in range(256): + if rom.read_8(door_addr) == 0: + break + if rom.read_8(door_addr + 1) == room: + door = d + break + door_addr += 0xC + if door is None: + raise ValueError(f"No door found for area {area} room {room:X}") + return door + + +def find_save_pad_position(rom: Rom, area: int, room: int) -> tuple[int, int] | None: + # Check if room's spriteset has save pad + save_platform_ids = {SpriteIdZM.SAVE_PLATFORM.value, SpriteIdZM.SAVE_PLATFORM_CHOZODIA.value} + room_entry = RoomEntry(rom, area, room) + spriteset = room_entry.default_spriteset() + ss_addr = rom.read_ptr(spriteset_ptrs(rom) + spriteset * 4) + ss_idx = None + for i in range(15): + sprite_id = rom.read_8(ss_addr) + if sprite_id == 0: + break + if sprite_id in save_platform_ids: + ss_idx = i + break + ss_addr += 2 + if ss_idx is None: + return None + # Find save pad in sprite layout list + layout_addr = room_entry.default_sprite_layout_addr() + for i in range(24): + sp_y = rom.read_8(layout_addr) + sp_x = rom.read_8(layout_addr + 1) + prop = rom.read_8(layout_addr + 2) + if sp_x == 0xFF and sp_y == 0xFF and prop == 0xFF: + break + if (prop & 0xF) - 1 == ss_idx: + return sp_x, sp_y - 1 + layout_addr += 3 + # No save pad found + return None + + +def set_starting_items(rom: Rom, data: MarsschemazmStartingItems) -> None: + def get_ability_flags(ability_flags: dict[str, int]) -> int: + status = 0 + for ability, flag in ability_flags.items(): + if ability in abilities: + status |= flag + return status + + # Get health/ammo amounts + energy = data.get("energy", 99) + missiles = data.get("missiles", 0) + super_missiles = data.get("super_missiles", 0) + power_bombs = data.get("power_bombs", 0) + # Get ability status flags + abilities = data.get("abilities", []) + beam_bomb_status = get_ability_flags(BEAM_BOMB_FLAGS) + suit_misc_status = get_ability_flags(SUIT_MISC_FLAGS) + # Get downloaded map flags + maps = data.get("downloaded_maps", range(7)) + map_status = 0 + for map in maps: + map_status |= 1 << map + # Get suit type + suit_str = data.get("suit_type") + suit_type = SuitType[suit_str] if suit_str else SuitType.NORMAL + # Get ziplines activated + ziplines = data.get("ziplines_activated", False) + + # Write to rom (see struct StartingInfo in structs/randomizer.h) + addr = rom.read_ptr(ReservedPointersZM.STARTING_INFO_PTR.value) + rom.write_16(addr + 6, energy) + rom.write_16(addr + 8, missiles) + rom.write_8(addr + 0xA, super_missiles) + rom.write_8(addr + 0xB, power_bombs) + rom.write_8(addr + 0xC, beam_bomb_status) + rom.write_8(addr + 0xD, suit_misc_status) + rom.write_8(addr + 0xE, map_status) + rom.write_8(addr + 0xF, suit_type.value) + rom.write_8(addr + 0x10, ziplines) + # TODO: Disabled hints + rom.write_8(addr + 0x11, 0) From 477dd1f2675b7bd04e11e3f4456237e39febef5a Mon Sep 17 00:00:00 2001 From: biosp4rk <37962487+biosp4rk@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:37:40 -0800 Subject: [PATCH 2/2] [ZM] Random palettes --- src/mars_patcher/constants/game_data.py | 2 +- src/mars_patcher/palette.py | 21 +++++--- src/mars_patcher/random_palettes.py | 60 ++++++++++++--------- src/mars_patcher/zm/auto_generated_types.py | 26 ++++----- src/mars_patcher/zm/data/schema.json | 28 +++++----- src/mars_patcher/zm/patcher.py | 15 +++--- 6 files changed, 85 insertions(+), 67 deletions(-) diff --git a/src/mars_patcher/constants/game_data.py b/src/mars_patcher/constants/game_data.py index 91d978c..eaf1d39 100644 --- a/src/mars_patcher/constants/game_data.py +++ b/src/mars_patcher/constants/game_data.py @@ -224,7 +224,7 @@ def samus_palettes(rom: Rom) -> list[tuple[int, int]]: elif rom.region == Region.C: return [(0x2900C8, 0x5E), (0x290E48, 0x70), (0x56CC68, 3)] elif rom.game == Game.ZM: - addr = rom.read_ptr(ReservedPointersZM.AREA_DOORS_PTR.value) + addr = rom.read_ptr(ReservedPointersZM.SAMUS_PALETTES_PTR.value) return [(addr, 0xA3)] raise ValueError(rom.game, rom.region) diff --git a/src/mars_patcher/palette.py b/src/mars_patcher/palette.py index 30fd798..21b1655 100644 --- a/src/mars_patcher/palette.py +++ b/src/mars_patcher/palette.py @@ -5,12 +5,17 @@ from mars_patcher.convert_array import u16_to_u8 from mars_patcher.rom import Rom +PAL_ROW_COUNT = 16 +"""The number of colors in a palette row.""" +PAL_ROW_SIZE = PAL_ROW_COUNT * 2 +"""The byte size of a palette row.""" + HUE_VARIATION_RANGE = 180.0 """The maximum range that hue can be additionally rotated.""" class SineWave: - STEP = (2 * math.pi) / 16 + STEP = (2 * math.pi) / PAL_ROW_COUNT def __init__(self, amplitude: float, frequency: float, phase: float): self.amplitude = amplitude @@ -36,7 +41,7 @@ def generate(max_range: float) -> "SineWave": return SineWave(amplitude, frequency, phase) def calculate_variation(self, x: int) -> float: - assert 0 <= x < 16 + assert 0 <= x < PAL_ROW_COUNT return self.amplitude * math.sin(self.frequency * x * self.STEP + self.phase) @@ -69,7 +74,7 @@ class Palette: def __init__(self, rows: int, rom: Rom, addr: int): assert rows >= 1 self.colors: list[RgbColor] = [] - for i in range(rows * 16): + for i in range(rows * PAL_ROW_COUNT): rgb = rom.read_16(addr + i * 2) color = RgbColor.from_rgb(rgb, RgbBitSize.Rgb5) self.colors.append(color) @@ -78,7 +83,7 @@ def __getitem__(self, key: int) -> RgbColor: return self.colors[key] def rows(self) -> int: - return len(self.colors) // 16 + return len(self.colors) // PAL_ROW_COUNT def byte_data(self) -> bytes: values = [c.rgb_15() for c in self.colors] @@ -95,8 +100,8 @@ def change_colors_hsv(self, change: ColorChange, excluded_rows: set[int]) -> Non for row in range(self.rows()): if row in excluded_rows: continue - offset = row * 16 - for i in range(16): + offset = row * PAL_ROW_COUNT + for i in range(PAL_ROW_COUNT): # Skip black and white rgb = self.colors[offset + i] if rgb == black or rgb == white: @@ -117,8 +122,8 @@ def change_colors_oklab(self, change: ColorChange, excluded_rows: set[int]) -> N for row in range(self.rows()): if row in excluded_rows: continue - offset = row * 16 - for i in range(16): + offset = row * PAL_ROW_COUNT + for i in range(PAL_ROW_COUNT): rgb = self.colors[offset + i] lab = change.change_oklab(rgb.oklab(), i) self.colors[offset + i] = lab.rgb() diff --git a/src/mars_patcher/random_palettes.py b/src/mars_patcher/random_palettes.py index da4a4f0..0dbc2bc 100644 --- a/src/mars_patcher/random_palettes.py +++ b/src/mars_patcher/random_palettes.py @@ -1,5 +1,5 @@ import random -from enum import Enum +from enum import Enum, auto from typing import TypeAlias from typing_extensions import Self @@ -19,20 +19,30 @@ 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.palette import PAL_ROW_SIZE, ColorChange, Palette, SineWave +from mars_patcher.rom import Rom +from mars_patcher.tileset import Tileset +from mars_patcher.zm.auto_generated_types import ( + MarsschemazmPalettes, + MarsschemazmPalettesColorspace, + MarsschemazmPalettesRandomize, +) 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 +SchemaPalettes = MarsschemamfPalettes | MarsschemazmPalettes +SchemaPalettesColorspace = MarsschemamfPalettesColorspace | MarsschemazmPalettesColorspace +SchemaPalettesRandomize = MarsschemamfPalettesRandomize | MarsschemazmPalettesRandomize + HueRange: TypeAlias = tuple[int, int] class PaletteType(Enum): - TILESETS = 1 - ENEMIES = 2 - SAMUS = 3 - BEAMS = 4 + TILESETS = auto() + ENEMIES = auto() + SAMUS = auto() + BEAMS = auto() class PaletteSettings: @@ -47,18 +57,18 @@ def __init__( self, seed: int, pal_types: dict[PaletteType, HueRange], - color_space: MarsschemamfPalettesColorspace, + color_space: SchemaPalettesColorspace, symmetric: bool, extra_variation: bool, ): self.seed = seed self.pal_types = pal_types - self.color_space: MarsschemamfPalettesColorspace = color_space + self.color_space: SchemaPalettesColorspace = color_space self.symmetric = symmetric self.extra_variation = extra_variation @classmethod - def from_json(cls, data: MarsschemamfPalettes) -> Self: + def from_json(cls, data: SchemaPalettes) -> Self: seed = data.get("Seed", random.randint(0, 2**31 - 1)) random.seed(seed) pal_types = {} @@ -72,7 +82,7 @@ def from_json(cls, data: MarsschemamfPalettes) -> Self: return cls(seed, pal_types, color_space, symmetric, True) @classmethod - def get_hue_range(cls, data: MarsschemamfPalettesRandomize) -> HueRange: + def get_hue_range(cls, data: SchemaPalettesRandomize) -> HueRange: hue_min = data.get("HueMin") hue_max = data.get("HueMax") if hue_min is None or hue_max is None: @@ -141,8 +151,8 @@ def randomize(self) -> None: if PaletteType.BEAMS in pal_types: self.randomize_beams(pal_types[PaletteType.BEAMS]) # Fix any sprite/tileset palettes that should be the same - # if self.rom.is_zm(): - # self.fix_zm_palettes() + if self.rom.is_zm(): + self.fix_zm_palettes() def change_palettes(self, pals: list[tuple[int, int]], change: ColorChange) -> None: for addr, rows in pals: @@ -157,7 +167,8 @@ 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) + if self.rom.is_mf(): + self.change_palettes(sax_palettes(self.rom), change) def randomize_beams(self, hue_range: HueRange) -> None: change = self.generate_palette_change(hue_range) @@ -179,7 +190,7 @@ def randomize_tilesets(self, hue_range: HueRange) -> None: continue # Get excluded palette rows excluded_rows = set() - if rom.game == Game.MF: + if rom.is_mf(): row = MF_TILESET_ALT_PAL_ROWS.get(pal_addr) if row is not None: excluded_rows = {row} @@ -223,6 +234,7 @@ def randomize_enemies(self, hue_range: HueRange) -> None: raise ValueError(rom.game) excluded = {en_id.value for en_id in _excluded} sp_count = gd.sprite_count(rom) + # The first 0x10 sprites have no graphics to_randomize = set(range(0x10, sp_count)) to_randomize -= excluded @@ -287,9 +299,9 @@ def get_sprite_addr(self, sprite_id: int) -> int: addr = gd.sprite_palette_ptrs(self.rom) + (sprite_id - 0x10) * 4 return self.rom.read_ptr(addr) - def get_tileset_addr(self, sprite_id: int) -> int: - addr = gd.tileset_entries(self.rom) + sprite_id * 0x14 + 4 - return self.rom.read_ptr(addr) + def get_tileset_addr(self, tileset_id: int) -> int: + tileset = Tileset(self.rom, tileset_id) + return tileset.palette_addr() def fix_nettori(self, change: ColorChange) -> None: """Nettori has extra palettes stored separately, so they require the same color change.""" @@ -303,25 +315,25 @@ def fix_zm_palettes(self) -> None: PaletteType.ENEMIES in self.settings.pal_types or PaletteType.TILESETS in self.settings.pal_types ): - # Fix kraid's body + # Fix kraid's body (copy row from sprite to tileset) 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) + self.rom.copy_bytes(sp_addr, ts_addr + (8 * PAL_ROW_SIZE), PAL_ROW_SIZE) 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) + self.rom.copy_bytes(ts_addr + PAL_ROW_SIZE, sp_addr, PAL_ROW_SIZE) # Fix ridley elevator statue ts_addr = self.get_tileset_addr(7) - self.rom.copy_bytes(ts_addr + 0x20, sp_addr + 0x20, 0x20) + self.rom.copy_bytes(ts_addr + PAL_ROW_SIZE, sp_addr + PAL_ROW_SIZE, PAL_ROW_SIZE) # 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) + self.rom.copy_bytes(ts_addr + (3 * PAL_ROW_SIZE), sp_addr, PAL_ROW_SIZE) # Fix cutscene sp_addr = statues_cutscene_palette_addr(self.rom) - self.rom.copy_bytes(ts_addr, sp_addr, 0xC0) + self.rom.copy_bytes(ts_addr, sp_addr, 6 * PAL_ROW_SIZE) diff --git a/src/mars_patcher/zm/auto_generated_types.py b/src/mars_patcher/zm/auto_generated_types.py index 05ed395..8575698 100644 --- a/src/mars_patcher/zm/auto_generated_types.py +++ b/src/mars_patcher/zm/auto_generated_types.py @@ -403,42 +403,42 @@ class MarsschemazmDoorLocksItem(typ.TypedDict): """The type of cover on the hatch.""" MarsschemazmPalettesRandomizeKey = typ.Literal[ - 'tilesets', - 'enemies', - 'samus', - 'beams' + 'Tilesets', + 'Enemies', + 'Samus', + 'Beams' ] @typ.final class MarsschemazmPalettesRandomize(typ.TypedDict, total=False): """The range to use for rotating palette hues.""" - hue_min: HueRotation = None + HueMin: HueRotation = None """The minimum value to use for rotating palette hues. If not specified, the patcher will randomly generate one.""" - hue_max: HueRotation = None + HueMax: HueRotation = None """The maximum value to use for rotating palette hues. If not specified, the patcher will randomly generate one.""" -MarsschemazmPalettesColorSpace = typ.Literal[ +MarsschemazmPalettesColorspace = typ.Literal[ 'HSV', - 'OKLAB' + 'Oklab' ] @typ.final class MarsschemazmPalettes(typ.TypedDict, total=False): """Properties for randomized in-game palettes.""" - seed: Seed = None + Seed: Seed = None """A number used to initialize the random number generator for palettes. If not specified, the patcher will randomly generate one.""" - randomize: typ.Required[dict[MarsschemazmPalettesRandomizeKey, MarsschemazmPalettesRandomize]] + Randomize: typ.Required[dict[MarsschemazmPalettesRandomizeKey, MarsschemazmPalettesRandomize]] """What kind of palettes should be randomized.""" - color_space: MarsschemazmPalettesColorSpace = 'OKLAB' + ColorSpace: MarsschemazmPalettesColorspace = 'Oklab' """The color space to use for rotating palette hues.""" - symmetric: bool = True + Symmetric: bool = True """Randomly rotates hues in the positive or negative direction true.""" @@ -544,7 +544,7 @@ class Marsschemazm(typ.TypedDict, total=False): door_locks: list[MarsschemazmDoorLocksItem] """List of all lockable doors and their lock type.""" - palettes: MarsschemazmPalettes = None + Palettes: MarsschemazmPalettes = None """Properties for randomized in-game palettes.""" MusicReplacement: Musicmapping diff --git a/src/mars_patcher/zm/data/schema.json b/src/mars_patcher/zm/data/schema.json index e4b008c..c143ffd 100644 --- a/src/mars_patcher/zm/data/schema.json +++ b/src/mars_patcher/zm/data/schema.json @@ -307,37 +307,37 @@ ] } }, - "palettes": { + "Palettes": { "type": "object", "description": "Properties for randomized in-game palettes.", "properties": { - "seed": { + "Seed": { "$ref": "#/$defs/seed", "description": "A number used to initialize the random number generator for palettes. If not specified, the patcher will randomly generate one.", "default": null }, - "randomize": { + "Randomize": { "type": "object", "description": "What kind of palettes should be randomized.", "propertyNames": { "type": "string", "enum": [ - "tilesets", - "enemies", - "samus", - "beams" + "Tilesets", + "Enemies", + "Samus", + "Beams" ] }, "additionalProperties": { "type": "object", "description": "The range to use for rotating palette hues.", "properties": { - "hue_min": { + "HueMin": { "$ref": "#/$defs/hue_rotation", "description": "The minimum value to use for rotating palette hues. If not specified, the patcher will randomly generate one.", "default": null }, - "hue_max": { + "HueMax": { "$ref": "#/$defs/hue_rotation", "description": "The maximum value to use for rotating palette hues. If not specified, the patcher will randomly generate one.", "default": null @@ -346,16 +346,16 @@ "additionalProperties": false } }, - "color_space": { + "ColorSpace": { "type": "string", "description": "The color space to use for rotating palette hues.", "enum": [ "HSV", - "OKLAB" + "Oklab" ], - "default": "OKLAB" + "default": "Oklab" }, - "symmetric": { + "Symmetric": { "type": "boolean", "description": "Randomly rotates hues in the positive or negative direction true.", "default": true @@ -363,7 +363,7 @@ }, "additionalProperties": false, "required": [ - "randomize" + "Randomize" ], "default": null }, diff --git a/src/mars_patcher/zm/patcher.py b/src/mars_patcher/zm/patcher.py index b5789f3..e9cd2d5 100644 --- a/src/mars_patcher/zm/patcher.py +++ b/src/mars_patcher/zm/patcher.py @@ -1,6 +1,7 @@ from collections.abc import Callable from os import PathLike +from mars_patcher.random_palettes import PaletteRandomizer, PaletteSettings from mars_patcher.rom import Rom from mars_patcher.sounds import set_sounds from mars_patcher.zm.auto_generated_types import MarsSchemaZM @@ -35,11 +36,11 @@ def patch_zm( # Randomize palettes - palettes are randomized first since the item # patcher needs to copy tilesets - # if "Palettes" in patch_data: - # status_update("Randomizing palettes...", -1) - # pal_settings = PaletteSettings.from_json(patch_data["Palettes"]) - # pal_randomizer = PaletteRandomizer(rom, pal_settings) - # pal_randomizer.randomize() + if "Palettes" in patch_data: + status_update("Randomizing palettes...", -1) + pal_settings = PaletteSettings.from_json(patch_data["Palettes"]) + pal_randomizer = PaletteRandomizer(rom, pal_settings) + pal_randomizer.randomize() # Load locations and set assignments status_update("Writing item assignments...", -1) @@ -56,12 +57,12 @@ def patch_zm( # Starting location if "starting_location" in patch_data: status_update("Writing starting location...", -1) - set_starting_location(rom, patch_data["starting_location"]) + set_starting_location(rom, patch_data["starting_location"]) # Starting items if "starting_items" in patch_data: status_update("Writing starting items...", -1) - set_starting_items(rom, patch_data["starting_items"]) + set_starting_items(rom, patch_data["starting_items"]) # Tank increments if "tank_increments" in patch_data: