From dc7d4940d99a6f1334a1f832020a07d6e9984c0a Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 21 Oct 2021 16:29:09 -0600 Subject: [PATCH] Overworld map code --- BaseClasses.py | 6 + CLI.py | 3 +- DoorShuffle.py | 1 + Dungeons.py | 32 +++-- EntranceShuffle.py | 136 +++++++++++++++++- Items.py | 2 +- Main.py | 1 + Mystery.py | 2 + Regions.py | 20 +-- Rom.py | 54 +++++-- data/base2current.bps | Bin 136122 -> 136369 bytes mystery_example.yml | 4 + resources/app/cli/args.json | 7 + resources/app/cli/lang/en.json | 3 + resources/app/gui/lang/en.json | 4 + .../app/gui/randomize/entrando/widgets.json | 13 +- source/classes/constants.py | 3 +- 17 files changed, 250 insertions(+), 41 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 58530558..c3c9b59e 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1957,6 +1957,12 @@ class Portal(object): self.dependent = None self.deadEnd = False self.light_world = False + self.chosen = False + + def find_portal_entrance(self): + p_region = self.door.entrance.connected_region + return next((x for x in p_region.entrances + if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]), None) def change_boss_exit(self, exit_idx): self.default = False diff --git a/CLI.py b/CLI.py index bb9ab0a2..168008ae 100644 --- a/CLI.py +++ b/CLI.py @@ -96,7 +96,7 @@ def parse_cli(argv, no_defaults=False): for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', - 'bombbag', + 'bombbag', 'overworld_map', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'pseudoboots', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', @@ -146,6 +146,7 @@ def parse_settings(): "shuffleganon": True, "shuffle": "vanilla", "shufflelinks": False, + "overworld_map": "default", "pseudoboots": False, "shufflepots": False, diff --git a/DoorShuffle.py b/DoorShuffle.py index 3d3a6eda..3e4bfac9 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1278,6 +1278,7 @@ def refine_boss_exits(world, player): if 0 < len(filtered) < len(reachable_portals): reachable_portals = filtered chosen_one = random.choice(reachable_portals) if len(reachable_portals) > 1 else reachable_portals[0] + chosen_one.chosen = True if chosen_one != current_boss: chosen_one.change_boss_exit(current_boss.boss_exit_idx) current_boss.change_boss_exit(-1) diff --git a/Dungeons.py b/Dungeons.py index 6fe38cfb..fb081155 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -378,8 +378,8 @@ flexible_starts = { class DungeonInfo: - def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize=None): - # todo reduce static maps ideas: prize, bk_name, sm_name, cmp_name, map_name): + def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize, midx): + # todo reduce static maps ideas: prize, bk_name, sm_name, cmp_name, map_name): self.free_items = free self.key_num = keys self.bk_present = bk @@ -389,21 +389,23 @@ class DungeonInfo: self.key_drops = drops self.prize = prize + self.map_index = midx + dungeon_table = { - 'Hyrule Castle': DungeonInfo(6, 1, False, True, False, True, 3, None), - 'Eastern Palace': DungeonInfo(3, 0, True, True, True, False, 2, 'Eastern Palace - Prize'), - 'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, 'Desert Palace - Prize'), - 'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, 'Tower of Hera - Prize'), - 'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None), - 'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, 'Palace of Darkness - Prize'), - 'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, 'Swamp Palace - Prize'), - 'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, 'Skull Woods - Prize'), - 'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, "Thieves' Town - Prize"), - 'Ice Palace': DungeonInfo(3, 2, True, True, True, False, 4, 'Ice Palace - Prize'), - 'Misery Mire': DungeonInfo(2, 3, True, True, True, False, 3, 'Misery Mire - Prize'), - 'Turtle Rock': DungeonInfo(5, 4, True, True, True, False, 2, 'Turtle Rock - Prize'), - 'Ganons Tower': DungeonInfo(20, 4, True, True, True, False, 4, None), + 'Hyrule Castle': DungeonInfo(6, 1, False, True, False, True, 3, None, 0xc), + 'Eastern Palace': DungeonInfo(3, 0, True, True, True, False, 2, 'Eastern Palace - Prize', 0x0), + 'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, 'Desert Palace - Prize', 0x2), + 'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, 'Tower of Hera - Prize', 0x1), + 'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None, 0xb), + 'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, 'Palace of Darkness - Prize', 0x3), + 'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, 'Swamp Palace - Prize', 0x9), + 'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, 'Skull Woods - Prize', 0x4), + 'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, "Thieves' Town - Prize", 0x6), + 'Ice Palace': DungeonInfo(3, 2, True, True, True, False, 4, 'Ice Palace - Prize', 0x8), + 'Misery Mire': DungeonInfo(2, 3, True, True, True, False, 3, 'Misery Mire - Prize', 0x7), + 'Turtle Rock': DungeonInfo(5, 4, True, True, True, False, 2, 'Turtle Rock - Prize', 0x5), + 'Ganons Tower': DungeonInfo(20, 4, True, True, True, False, 4, None, 0xa), } diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 7e5dcdd3..7a7d3116 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -3733,8 +3733,6 @@ indirect_connections = { # | ([addr], None) # holes # exitdata = (room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2) -# ToDo somehow merge this with creation of the locations - # ToDo somehow merge this with creation of the locations door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)), 'Inverted Big Bomb Shop': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)), @@ -4034,3 +4032,137 @@ exit_ids = {'Links House Exit': (0x01, 0x00), 'Skull Pinball': 0x78, 'Skull Pot Circle': 0x76, 'Pyramid': 0x7B} + +ow_prize_table = {'Links House': (0x8b1, 0xb2d), 'Inverted Big Bomb Shop': (0x8b1, 0xb2d), + 'Desert Palace Entrance (South)': (0x108, 0xd70), 'Desert Palace Entrance (West)': (0x031, 0xca0), + 'Desert Palace Entrance (North)': (0x0e1, 0xba0), 'Desert Palace Entrance (East)': (0x191, 0xca0), + 'Eastern Palace': (0xf31, 0x620), 'Tower of Hera': (0x8D0, 0x080), + 'Hyrule Castle Entrance (South)': (0x7b0, 0x730), 'Hyrule Castle Entrance (West)': (0x700, 0x640), + 'Hyrule Castle Entrance (East)': (0x8a0, 0x640), 'Inverted Pyramid Entrance': (0x720, 0x700), + 'Agahnims Tower': (0x7e0, 0x640), 'Inverted Ganons Tower': (0x7e0, 0x640), + 'Thieves Town': (0x1d0, 0x780), 'Skull Woods First Section Door': (0x240, 0x280), + 'Skull Woods Second Section Door (East)': (0x1a0, 0x240), + 'Skull Woods Second Section Door (West)': (0x0c0, 0x1c0), 'Skull Woods Final Section': (0x082, 0x0b0), + 'Ice Palace': (0xca0, 0xda0), + 'Misery Mire': (0x100, 0xca0), + 'Palace of Darkness': (0xf40, 0x620), 'Swamp Palace': (0x759, 0xED0), + 'Turtle Rock': (0xf11, 0x103), + 'Dark Death Mountain Ledge (West)': (0xb80, 0x180), + 'Dark Death Mountain Ledge (East)': (0xc80, 0x180), + 'Turtle Rock Isolated Ledge Entrance': (0xc00, 0x240), + 'Hyrule Castle Secret Entrance Stairs': (0x850, 0x700), + 'Kakariko Well Cave': (0x060, 0x680), + 'Bat Cave Cave': (0x540, 0x8f0), + 'Elder House (East)': (0x2b0, 0x6a0), + 'Elder House (West)': (0x230, 0x6a0), + 'North Fairy Cave': (0xa80, 0x440), + 'Lost Woods Hideout Stump': (0x240, 0x280), + 'Lumberjack Tree Cave': (0x4e0, 0x004), + 'Two Brothers House (East)': (0x200, 0x0b60), + 'Two Brothers House (West)': (0x180, 0x0b60), + 'Sanctuary': (0x720, 0x4a0), + 'Old Man Cave (West)': (0x580, 0x2c0), + 'Old Man Cave (East)': (0x620, 0x2c0), + 'Old Man House (Bottom)': (0x720, 0x320), + 'Old Man House (Top)': (0x820, 0x220), + 'Death Mountain Return Cave (East)': (0x600, 0x220), + 'Death Mountain Return Cave (West)': (0x500, 0x1c0), + 'Spectacle Rock Cave Peak': (0x720, 0x0a0), + 'Spectacle Rock Cave': (0x790, 0x1a0), + 'Spectacle Rock Cave (Bottom)': (0x710, 0x0a0), + 'Paradox Cave (Bottom)': (0xd80, 0x180), + 'Paradox Cave (Middle)': (0xd80, 0x380), + 'Paradox Cave (Top)': (0xd80, 0x020), + 'Fairy Ascension Cave (Bottom)': (0xcc8, 0x2a0), + 'Fairy Ascension Cave (Top)': (0xc00, 0x240), + 'Spiral Cave': (0xb80, 0x180), + 'Spiral Cave (Bottom)': (0xb80, 0x2c0), + 'Bumper Cave (Bottom)': (0x580, 0x2c0), + 'Bumper Cave (Top)': (0x500, 0x1c0), + 'Superbunny Cave (Top)': (0xd80, 0x020), + 'Superbunny Cave (Bottom)': (0xd00, 0x180), + 'Hookshot Cave': (0xc80, 0x0c0), + 'Hookshot Cave Back Entrance': (0xcf0, 0x004), + 'Ganons Tower': (0x8D0, 0x080), + 'Inverted Agahnims Tower': (0x8D0, 0x080), + 'Pyramid Entrance': (0x640, 0x7c0), + 'Skull Woods First Section Hole (West)': None, + 'Skull Woods First Section Hole (East)': None, + 'Skull Woods First Section Hole (North)': None, + 'Skull Woods Second Section Hole': None, + 'Pyramid Hole': None, + 'Inverted Pyramid Hole': None, + 'Waterfall of Wishing': (0xe80, 0x280), + 'Dam': (0x759, 0xED0), + 'Blinds Hideout': (0x190, 0x6c0), + 'Hyrule Castle Secret Entrance Drop': None, + 'Bonk Fairy (Light)': (0x740, 0xa80), + 'Lake Hylia Fairy': (0xd40, 0x9f0), + 'Light Hype Fairy': (0x940, 0xc80), + 'Desert Fairy': (0x420, 0xe00), + 'Kings Grave': (0x920, 0x520), + 'Tavern North': None, # can't mark this one technically + 'Chicken House': (0x120, 0x880), + 'Aginahs Cave': (0x2e0, 0xd00), + 'Sahasrahlas Hut': (0xcf0, 0x6c0), + 'Cave Shop (Lake Hylia)': (0xbc0, 0xc00), + 'Capacity Upgrade': (0xca0, 0xda0), + 'Kakariko Well Drop': None, + 'Blacksmiths Hut': (0x4a0, 0x880), + 'Bat Cave Drop': None, + 'Sick Kids House': (0x220, 0x880), + 'North Fairy Cave Drop': None, + 'Lost Woods Gamble': (0x240, 0x080), + 'Fortune Teller (Light)': (0x2c0, 0x4c0), + 'Snitch Lady (East)': (0x310, 0x7a0), + 'Snitch Lady (West)': (0x800, 0x7a0), + 'Bush Covered House': (0x2e0, 0x880), + 'Tavern (Front)': (0x270, 0x980), + 'Light World Bomb Hut': (0x070, 0x980), + 'Kakariko Shop': (0x170, 0x980), + 'Lost Woods Hideout Drop': None, + 'Lumberjack Tree Tree': None, + 'Cave 45': (0x440, 0xca0), 'Graveyard Cave': (0x8f0, 0x430), + 'Checkerboard Cave': (0x260, 0xc00), + 'Mini Moldorm Cave': (0xa40, 0xe80), + 'Long Fairy Cave': (0xf60, 0xb00), + 'Good Bee Cave': (0xec0, 0xc00), + '20 Rupee Cave': (0xe80, 0xca0), + '50 Rupee Cave': (0x4d0, 0xed0), + 'Ice Rod Cave': (0xe00, 0xc00), + 'Bonk Rock Cave': (0x5f0, 0x460), + 'Library': (0x270, 0xaa0), + 'Potion Shop': (0xc80, 0x4c0), + 'Sanctuary Grave': None, + 'Hookshot Fairy': (0xd00, 0x180), + 'Pyramid Fairy': (0x740, 0x740), + 'East Dark World Hint': (0xf60, 0xb00), + 'Palace of Darkness Hint': (0xd60, 0x7c0), + 'Dark Lake Hylia Fairy': (0xd40, 0x9f0), + 'Dark Lake Hylia Ledge Fairy': (0xe00, 0xc00), + 'Dark Lake Hylia Ledge Spike Cave': (0xe80, 0xca0), + 'Dark Lake Hylia Ledge Hint': (0xec0, 0xc00), + 'Hype Cave': (0x940, 0xc80), + 'Bonk Fairy (Dark)': (0x740, 0xa80), + 'Brewery': (0x170, 0x980), 'C-Shaped House': (0x310, 0x7a0), 'Chest Game': (0x800, 0x7a0), + 'Dark World Hammer Peg Cave': (0x4c0, 0x940), + 'Red Shield Shop': (0x500, 0x680), + 'Dark Sanctuary Hint': (0x720, 0x4a0), + 'Inverted Dark Sanctuary': (0x720, 0x4a0), + 'Fortune Teller (Dark)': (0x2c0, 0x4c0), + 'Dark World Shop': (0x2e0, 0x880), + 'Dark World Lumberjack Shop': (0x4e0, 0x0d0), + 'Dark World Potion Shop': (0xc80, 0x4c0), + 'Archery Game': (0x2f0, 0xaf0), + 'Mire Shed': (0x060, 0xc90), + 'Dark Desert Hint': (0x2e0, 0xd00), + 'Dark Desert Fairy': (0x1c0, 0xc90), + 'Spike Cave': (0x860, 0x180), + 'Cave Shop (Dark Death Mountain)': (0xd80, 0x180), + 'Dark Death Mountain Fairy': (0x620, 0x2c0), + 'Mimic Cave': (0xc80, 0x180), + 'Big Bomb Shop': (0x8b1, 0xb2d), 'Inverted Links House': (0x8b1, 0xb2d), + 'Dark Lake Hylia Shop': (0xa40, 0xc40), + 'Lumberjack House': (0x4e0, 0x0d0), + 'Lake Hylia Fortune Teller': (0xa40, 0xc40), + 'Kakariko Gamble Game': (0x2f0, 0xaf0)} diff --git a/Items.py b/Items.py index 808a0740..ba85e51f 100644 --- a/Items.py +++ b/Items.py @@ -22,7 +22,7 @@ def ItemFactory(items, player): return ret -# Format: Name: (Advancement, Priority, Type, ItemCode, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text) +# Format: Name: (Advancement, Priority, Type, ItemCode, BasePrice, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text) item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the Bow'), 'Progressive Bow': (True, False, None, 0x64, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'), 'Progressive Bow (Alt)': (True, False, None, 0x65, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'), diff --git a/Main.py b/Main.py index cfd79062..1f11f3cc 100644 --- a/Main.py +++ b/Main.py @@ -104,6 +104,7 @@ def main(args, seed=None, fish=None): world.treasure_hunt_total = args.triforce_pool.copy() world.shufflelinks = args.shufflelinks.copy() world.pseudoboots = args.pseudoboots.copy() + world.overworld_map = args.overworld_map.copy() world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} diff --git a/Mystery.py b/Mystery.py index 73644500..3ab32406 100644 --- a/Mystery.py +++ b/Mystery.py @@ -135,6 +135,8 @@ def roll_settings(weights): entrance_shuffle = get_choice('entrance_shuffle') ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla' + overworld_map = get_choice('overworld_map') + ret.overworld_map = overworld_map if overworld_map != 'default' else 'default' door_shuffle = get_choice('door_shuffle') ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla' ret.intensity = get_choice('intensity') diff --git a/Regions.py b/Regions.py index 546cd49b..c21953f6 100644 --- a/Regions.py +++ b/Regions.py @@ -1335,16 +1335,16 @@ location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'), 'Ice Block Drop': (None, None, False, None), 'Zelda Pickup': (None, None, False, None), 'Zelda Drop Off': (None, None, False, None), - 'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], None, True, 'Eastern Palace'), - 'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], None, True, 'Desert Palace'), - 'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], None, True, 'Tower of Hera'), - 'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], None, True, 'Palace of Darkness'), - 'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], None, True, 'Swamp Palace'), - 'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], None, True, 'Thieves\' Town'), - 'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], None, True, 'Skull Woods'), - 'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], None, True, 'Ice Palace'), - 'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], None, True, 'Misery Mire'), - 'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock'), + 'Eastern Palace - Prize': ([0x1209D, 0x53E76, 0x53E77, 0x180052, 0x180070, 0xC6FE], None, True, 'Eastern Palace'), + 'Desert Palace - Prize': ([0x1209E, 0x53E7A, 0x53E7B, 0x180053, 0x180072, 0xC6FF], None, True, 'Desert Palace'), + 'Tower of Hera - Prize': ([0x120A5, 0x53E78, 0x53E79, 0x18005A, 0x180071, 0xC706], None, True, 'Tower of Hera'), + 'Palace of Darkness - Prize': ([0x120A1, 0x53E7C, 0x53E7D, 0x180056, 0x180073, 0xC702], None, True, 'Palace of Darkness'), + 'Swamp Palace - Prize': ([0x120A0, 0x53E88, 0x53E89, 0x180055, 0x180079, 0xC701], None, True, 'Swamp Palace'), + 'Thieves\' Town - Prize': ([0x120A6, 0x53E82, 0x53E83, 0x18005B, 0x180076, 0xC707], None, True, 'Thieves\' Town'), + 'Skull Woods - Prize': ([0x120A3, 0x53E7E, 0x53E7F, 0x180058, 0x180074, 0xC704], None, True, 'Skull Woods'), + 'Ice Palace - Prize': ([0x120A4, 0x53E86, 0x53E87, 0x180059, 0x180078, 0xC705], None, True, 'Ice Palace'), + 'Misery Mire - Prize': ([0x120A2, 0x53E84, 0x53E85, 0x180057, 0x180077, 0xC703], None, True, 'Misery Mire'), + 'Turtle Rock - Prize': ([0x120A7, 0x53E80, 0x53E81, 0x18005C, 0x180075, 0xC708], None, True, 'Turtle Rock'), 'Kakariko Shop - Left': (None, None, False, 'for sale in Kakariko'), 'Kakariko Shop - Middle': (None, None, False, 'for sale in Kakariko'), 'Kakariko Shop - Right': (None, None, False, 'for sale in Kakariko'), diff --git a/Rom.py b/Rom.py index d8514556..ccad9d13 100644 --- a/Rom.py +++ b/Rom.py @@ -16,8 +16,8 @@ except ImportError: raise Exception('Could not load BPS module') from BaseClasses import CollectionState, ShopType, Region, Location, Door, DoorType, RegionType, PotItem -from DoorShuffle import compass_data, DROptions, boss_indicator -from Dungeons import dungeon_music_addresses +from DoorShuffle import compass_data, DROptions, boss_indicator, dungeon_portals +from Dungeons import dungeon_music_addresses, dungeon_table from Regions import location_table, shop_to_location_table, retro_shops from RoomData import DoorKind from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable @@ -26,13 +26,13 @@ from Text import Triforce_texts, Blind_texts, BombShop2_texts, junk_texts from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc from Items import ItemFactory -from EntranceShuffle import door_addresses, exit_ids +from EntranceShuffle import door_addresses, exit_ids, ow_prize_table from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '1c59cec98ba4555db8eed1d2dea76497' +RANDOMIZERBASEHASH = '7ec52e136e8c73a9e093a4baa43fc2d2' class JsonRom(object): @@ -1401,14 +1401,48 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x18003B, 0x01 if world.mapshuffle[player] else 0x00) # maps showing crystals on overworld # compasses showing dungeon count + compass_mode = 0x00 if world.clock_mode != 'none' or world.dungeon_counters[player] == 'off': - rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location - elif world.dungeon_counters[player] == 'on': - rom.write_byte(0x18003C, 0x02) # always on - elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dungeon_counters[player] == 'pickup': - rom.write_byte(0x18003C, 0x01) # show on pickup - else: + compass_mode = 0x00 # Currently must be off if timer is on, because they use same HUD location rom.write_byte(0x18003C, 0x00) + elif world.dungeon_counters[player] == 'on': + compass_mode = 0x02 # always on + elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dungeon_counters[player] == 'pickup': + compass_mode = 0x01 # show on pickup + if world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default': + compass_mode |= 0x80 # turn on locating dungeons + x_map_position_generic = [0x3c0, 0xbc0, 0x7c0, 0x1c0, 0x5c0, 0xdc0, 0x7c0, 0xbc0, 0x9c0, 0x3c0] + for idx, x_map in enumerate(x_map_position_generic): + rom.write_bytes(0x53df6+idx*2, int16_as_bytes(x_map)) + rom.write_bytes(0x53e16+idx*2, int16_as_bytes(0xFC0)) + if world.compassshuffle[player] and world.overworld_map[player] == 'compass': + compass_mode |= 0x40 # compasses are wild + for dungeon, portal_list in dungeon_portals.items(): + ow_map_index = dungeon_table[dungeon].map_index + if len(portal_list) == 1: + portal_idx = 0 + else: + if world.doorShuffle[player] == 'crossed': + # the random choice excludes sanctuary + portal_idx = next((i for i, elem in enumerate(portal_list) + if world.get_portal(elem, player).chosen), random.choice([1, 2, 3])) + else: + portal_idx = {'Hyrule Castle': 0, 'Desert Palace': 0, 'Skull Woods': 3, 'Turtle Rock': 3}[dungeon] + portal = world.get_portal(portal_list[portal_idx], player) + entrance = portal.find_portal_entrance() + world_indicator = 0x01 if entrance.parent_region.type == RegionType.DarkWorld else 0x00 + coords = ow_prize_table[entrance.name] + # figure out compass entrances and what world (light/dark) + rom.write_bytes(0x53E36+ow_map_index*2, int16_as_bytes(coords[0])) + rom.write_bytes(0x53E56+ow_map_index*2, int16_as_bytes(coords[1])) + rom.write_byte(0x53EA6+ow_map_index, world_indicator) + # in crossed doors - flip the compass exists flags + if world.doorShuffle[player] == 'crossed': + exists_flag = any(x for x in world.get_dungeon(dungeon, player).dungeon_items if x.type == 'Compass') + rom.write_byte(0x53E96+ow_map_index, 0x1 if exists_flag else 0x0) + + + rom.write_byte(0x18003C, compass_mode) # Bitfield - enable free items to show up in menu # diff --git a/data/base2current.bps b/data/base2current.bps index fca7a6ecdc7d2b97a0a85bb86dd89a16dfb4749c..0e2a33a60a727bdf791e67f15a7a564fb7eb477b 100644 GIT binary patch delta 9261 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%0DhXFh(4rE7Z_WsuHYfs5#FVDs<`!|ans*s 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