From c09fab64f81c3016d3f7d01f4c79ee77343dde95 Mon Sep 17 00:00:00 2001 From: qadan Date: Fri, 3 Jan 2020 04:02:15 -0400 Subject: [PATCH] first pass at owg logic support --- EntranceRandomizer.py | 7 ++-- EntranceShuffle.py | 3 +- Gui.py | 2 +- InvertedRegions.py | 2 +- ItemList.py | 34 ++++++++++-------- Mystery.py | 6 ++-- Rom.py | 8 ++++- Rules.py | 84 ++++++++++++++++++++++++++++++++++++++++++- 8 files changed, 122 insertions(+), 24 deletions(-) diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 91fa5bfa..53476548 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -28,14 +28,17 @@ def parse_arguments(argv, no_defaults=False): parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true') - parser.add_argument('--logic', default=defval('noglitches'), const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'nologic'], + parser.add_argument('--logic', default=defval('noglitches'), const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'owglitches', 'nologic'], help='''\ Select Enforcement of Item Requirements. (default: %(default)s) No Glitches: Minor Glitches: May require Fake Flippers, Bunny Revival and Dark Room Navigation. + Overworld Glitches: May require overworld glitches. Starts with + boots. No Logic: Distribute items without regard for - item requirements. + item requirements. Starts with + boots ''') parser.add_argument('--mode', default=defval('open'), const='open', nargs='?', choices=['standard', 'open', 'inverted'], help='''\ diff --git a/EntranceShuffle.py b/EntranceShuffle.py index a96811b8..49339ab2 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -2987,7 +2987,8 @@ mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central ('Pyramid Drop', 'East Dark World') ] -inverted_mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'), +inverted_mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'), + ('Lake Hylia Island', 'Lake Hylia Island'), ('Zoras River', 'Zoras River'), ('Kings Grave Outer Rocks', 'Kings Grave Area'), ('Kings Grave Inner Rocks', 'Light World'), diff --git a/Gui.py b/Gui.py index b897866b..538afdbc 100755 --- a/Gui.py +++ b/Gui.py @@ -193,7 +193,7 @@ def guiMain(args=None): logicFrame = Frame(drowDownFrame) logicVar = StringVar() logicVar.set('noglitches') - logicOptionMenu = OptionMenu(logicFrame, logicVar, 'noglitches', 'minorglitches', 'nologic') + logicOptionMenu = OptionMenu(logicFrame, logicVar, 'noglitches', 'minorglitches', 'owglitches', 'nologic') logicOptionMenu.pack(side=RIGHT) logicLabel = Label(logicFrame, text='Game logic') logicLabel.pack(side=LEFT) diff --git a/InvertedRegions.py b/InvertedRegions.py index d0f0e8bc..e1915d39 100644 --- a/InvertedRegions.py +++ b/InvertedRegions.py @@ -9,7 +9,7 @@ def create_inverted_regions(world, player): ["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Kings Grave Outer Rocks', 'Dam', 'Inverted Big Bomb Shop', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave', 'Blacksmiths Hut', 'Bat Cave Drop Ledge', 'Bat Cave Cave', 'Sick Kids House', 'Hobo Bridge', 'Lost Woods Hideout Drop', 'Lost Woods Hideout Stump', - 'Lumberjack Tree Tree', 'Lumberjack Tree Cave', 'Mini Moldorm Cave', 'Ice Rod Cave', 'Lake Hylia Central Island Pier', + 'Lumberjack Tree Tree', 'Lumberjack Tree Cave', 'Mini Moldorm Cave', 'Ice Rod Cave', 'Lake Hylia Central Island Pier', 'Lake Hylia Island', 'Bonk Rock Cave', 'Library', 'Two Brothers House (East)', 'Desert Palace Stairs', 'Eastern Palace', 'Master Sword Meadow', 'Sanctuary', 'Sanctuary Grave', 'Death Mountain Entrance Rock', 'Light World River Drop', 'Elder House (East)', 'Elder House (West)', 'North Fairy Cave', 'North Fairy Cave Drop', 'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Tavern (Front)', diff --git a/ItemList.py b/ItemList.py index 19eb83e3..3db3f130 100644 --- a/ItemList.py +++ b/ItemList.py @@ -177,7 +177,7 @@ def generate_itempool(world, player): (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.customitemarray) world.rupoor_cost = min(world.customitemarray[69], 9999) else: - (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player]) + (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.logic[player]) for item in precollected_items: world.push_precollected(ItemFactory(item, player)) @@ -386,7 +386,7 @@ def set_up_shops(world, player): #special shop types -def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro): +def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, logic): pool = [] placed_items = {} precollected_items = [] @@ -403,6 +403,11 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r def want_progressives(): return random.choice([True, False]) if progressive == 'random' else progressive == 'on' + # provide boots to major glitch dependent seeds + if logic in ['owglitches', 'nologic']: + precollected_items.append('Pegasus Boots') + pool.remove('Pegasus Boots') + if want_progressives(): pool.extend(progressivegloves) else: @@ -685,19 +690,20 @@ def test(): for progressive in ['on', 'off']: for shuffle in ['full', 'insanity_legacy']: for retro in [True, False]: - out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro) - count = len(out[0]) + len(out[1]) + for logic in ['noglitches', 'owglitches']: + out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, logic) + count = len(out[0]) + len(out[1]) - correct_count = total_items_to_place - if goal == 'pedestal' and swords != 'vanilla': - # pedestal goals generate one extra item - correct_count += 1 - if retro: - correct_count += 28 - try: - assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro)) - except AssertionError as e: - print(e) + correct_count = total_items_to_place + if goal == 'pedestal' and swords != 'vanilla': + # pedestal goals generate one extra item + correct_count += 1 + if retro: + correct_count += 28 + try: + assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro)) + except AssertionError as e: + print(e) if __name__ == '__main__': test() diff --git a/Mystery.py b/Mystery.py index 06f57aca..2a79c990 100644 --- a/Mystery.py +++ b/Mystery.py @@ -114,10 +114,10 @@ def roll_settings(weights): ret = argparse.Namespace() glitches_required = get_choice('glitches_required') - if glitches_required not in ['none', 'no_logic']: - print("Only NMG and No Logic supported") + if glitches_required not in ['none', 'no_logic', 'overworld_glitches']: + print("Only NMG, OWG and No Logic supported") glitches_required = 'none' - ret.logic = {'none': 'noglitches', 'no_logic': 'nologic'}[glitches_required] + ret.logic = {'none': 'noglitches', 'no_logic': 'nologic', 'overworld_glitches': 'owglitches'}[glitches_required] item_placement = get_choice('item_placement') # not supported in ER diff --git a/Rom.py b/Rom.py index aa4c150a..79e85a1b 100644 --- a/Rom.py +++ b/Rom.py @@ -901,7 +901,7 @@ def patch_rom(world, player, rom, enemized): rom.write_byte(x, 0) # Zero the initial equipment array rom.write_byte(0x18302C, 0x18) # starting max health rom.write_byte(0x18302D, 0x18) # starting current health - rom.write_byte(0x183039, 0x68) # starting abilities, bit array + ability_flags = 0x68 # starting abilities, bit array; may be modified by precollected items for item in world.precollected_items: if item.player != player: @@ -911,9 +911,15 @@ def patch_rom(world, player, rom, enemized): rom.write_byte(0x183000+0x19, 0x01) rom.write_byte(0x0271A6+0x19, 0x01) rom.write_byte(0x180043, 0x01) # special starting sword byte + elif item.name == 'Pegasus Boots': + rom.write_byte(0x183015, 0x01) + ability_flags |= 0b00000100 else: raise RuntimeError("Unsupported pre-collected item: {}".format(item)) + # write abilities after ability flags have been determined + rom.write_byte(0x183039, ability_flags) + rom.write_byte(0x18004A, 0x00 if world.mode[player] != 'inverted' else 0x01) # Inverted mode rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier rom.write_byte(0x2AF79, 0xD0 if world.mode[player] != 'inverted' else 0xF0) # vortexes: Normal (D0=light to dark, F0=dark to light, 42 = both) diff --git a/Rules.py b/Rules.py index 5ce634d4..41015113 100644 --- a/Rules.py +++ b/Rules.py @@ -38,6 +38,8 @@ def set_rules(world, player): if world.logic[player] == 'noglitches': no_glitches_rules(world, player) + elif world.logic[player] == 'owglitches': + overworld_glitches_rules(world, player) elif world.logic[player] == 'minorglitches': logging.getLogger('').info('Minor Glitches may be buggy still. No guarantee for proper logic checks.') else: @@ -357,7 +359,6 @@ def default_rules(world, player): set_rule(world.get_entrance('Kings Grave Mirror Spot', player), lambda state: state.has_Pearl(player) and state.has_Mirror(player)) # Caution: If king's grave is releaxed at all to account for reaching it via a two way cave's exit in insanity mode, then the bomb shop logic will need to be updated (that would involve create a small ledge-like Region for it) set_rule(world.get_entrance('Bonk Fairy (Light)', player), lambda state: state.has_Boots(player)) - set_rule(world.get_entrance('Bat Cave Drop Ledge', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('Lumberjack Tree Tree', player), lambda state: state.has_Boots(player) and state.has('Beat Agahnim 1', player)) set_rule(world.get_entrance('Bonk Rock Cave', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('Desert Palace Stairs', player), lambda state: state.has('Book of Mudora', player)) @@ -625,8 +626,10 @@ def no_glitches_rules(world, player): set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player) and (state.has('Hammer', player) or state.can_lift_rocks(player))) set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) else: + set_rule(world.get_entrance('Bat Cave Drop Ledge', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('Zoras River', player), lambda state: state.has_Pearl(player) and (state.has('Flippers', player) or state.can_lift_rocks(player))) set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # can be fake flippered to + set_rule(world.get_entrance('Lake Hylia Island', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has('Flippers', player) and (state.has('Hammer', player) or state.can_lift_rocks(player))) @@ -641,7 +644,10 @@ def no_glitches_rules(world, player): set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: False) # no glitches does not require block override set_rule(world.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False) set_rule(world.get_entrance('Skull Woods First Section Bomb Jump', player), lambda state: False) + add_conditional_lamps(world, player) + +def add_conditional_lamps(world, player): # Light cones in standard depend on which world we actually are in, not which one the location would normally be # We add Lamp requirements only to those locations which lie in the dark world (or everything if open DW_Entrances = ['Bumper Cave (Bottom)', 'Superbunny Cave (Top)', 'Superbunny Cave (Bottom)', 'Hookshot Cave', 'Bumper Cave (Top)', 'Hookshot Cave Back Entrance', 'Dark Death Mountain Ledge (East)', @@ -690,6 +696,82 @@ def no_glitches_rules(world, player): add_lamp_requirement(world.get_entrance('Throne Room', player), player) +def overworld_glitches_rules(world, player): + # spots that are immediately accessible + set_rule(world.get_entrance('Hobo Bridge', player), lambda state: True) + set_rule(world.get_region('Lake Hylia Central Island', player), lambda state: True) + set_rule(world.get_entrance('Zoras River', player), lambda state: True) + # lw boots-accessible locations + lw_boots_accessible_regions = [ + 'Bat Cave Drop Ledge', + 'Lake Hylia Island', + 'Desert Ledge', + 'Desert Ledge (Northeast)', + 'Desert Palace Lone Stairs', + 'Desert Palace Entrance (North) Spot', + 'Death Mountain', + 'Death Mountain Return Ledge', + 'East Death Mountain (Bottom)', + 'East Death Mountain (Top)', + 'Death Mountain (Top)', + 'Spectacle Rock', + 'Death Mountain Floating Island (Light World)', + ] + # dw boots-accessible regions + dw_boots_accessible_regions = [ + 'East Dark World', + 'Northeast Dark World', + 'West Dark World', + 'Hammer Peg Area', + 'Bumper Cave Ledge', + 'Dark Desert', + 'Dark Death Mountain (Top)', + 'Dark Death Mountain (East Bottom)', + 'Dark Death Mountain Ledge', + 'Death Mountain Floating Island (Dark World)', + 'Turtle Rock (Top)', + ] + # set up boots-accessible regions + if world.mode[player] != 'inverted': + lw_boots_accessible_regions.append('Cave 45 Ledge') + lw_boots_accessible_regions.append('Graveyard Ledge') + # couple other random spots + set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player) and (state.has_Mirror(player) or state.has_Boots(player))) + set_rule(world.get_entrance('Dark Desert Teleporter', player), lambda state: (state.has('Ocarina', player) or state.has_Boots(player)) and state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('South Hyrule Teleporter', player), lambda state: (state.has('Hammer', player) or state.has_Boots(player)) and state.can_lift_rocks(player)) + set_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has_Boots(player)) + add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.has_Boots(player) and state.has_Pearl(player), 'or') + needs_boots = lambda state: state.has_Boots(player) + needs_boots_and_pearl = lambda state: state.has_Boots(player) and state.has_Pearl(player) + for spot in lw_boots_accessible_regions: + for location in world.get_region(spot, player).locations: + add_rule(world.get_location(location, player), needs_boots_and_pearl if world.mode[player] == 'inverted' else needs_boots, 'or') + for spot in dw_boots_accessible_regions: + for location in world.get_region(spot, player).locations: + add_rule(world.get_location(location, player), needs_boots if world.mode[player] == 'inverted' else needs_boots_and_pearl, 'or') + # bunny DMD rules + if world.mode[player] != 'inverted': + # set up some mirror-accessible dw entrances. + boots_and_mirror = lambda state: state.has_Boots(player) and state.has_Mirror(player) + add_rule(world.get_entrance('Dark Sanctuary Hint', player), boots_and_mirror, 'or') # should suffice to give us west dark world access + for spot in world.get_region('Dark Death Mountain (East Bottom)', player).locations: + add_rule(world.get_location(spot, player), boots_and_mirror, 'or') + # dw entrances accessible with mirror and hookshot + mirror_hookshot_accessible_dw_locations = [ + 'Pyramid Fairy', + 'Pyramid Entrance', + 'Pyramid Drop', + ] + mirror_hookshot_accessible_dw_locations.extend(world.get_region('Dark Death Mountain Ledge', player).locations) + for spot in mirror_hookshot_accessible_dw_locations: + add_rule(world.get_entrance(spot, player), lambda state: state.has_Boots(player) and state.has_Mirror(player) and state.has('Hookshot', player), 'or') + # dw entrances accessible with mirror and titans + boots_mirror_titans = lambda state: state.has_Boots(player) and state.has_Mirror(player) and state.can_lift_heavy_rocks(player) + add_rule(world.get_entrance('Mire Shed', player), boots_mirror_titans, 'or') + add_rule(world.get_location('Frog', player), boots_mirror_titans, 'or') + add_conditional_lamps(world, player) + + def open_rules(world, player): # softlock protection as you can reach the sewers small key door with a guard drop key set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.has_key('Small Key (Escape)', player))