From 9c6d649996cc4c80ff6859fdb324d5ed72640735 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sat, 14 Oct 2017 14:45:59 -0400 Subject: [PATCH] Updating logic and features to better match VT26 Updates include: * Allow acticating tablets with hammer * Remove 1/4 magic from the normal mode pool * Incorporate OHKO compatible cape/cane-of-byrna requirements * Upgrade Mirror Shield, Progressive Shield, and Bug Catching Net to qualify as advancement items * Allow Mirror shield as alternative to cape/byrna for Laser Bridge * Prevent Waterfall Fairy from upgrading Boomerang or Shield * Change PoD key logic to match VT26. * Add restrictions on small key placement (to match VT26). I think these restrictions may be redundant, but I'm adding them just in case any of them are not. --- BaseClasses.py | 12 ++++++++++++ EntranceRandomizer.py | 3 +-- Items.py | 6 +++--- Main.py | 9 ++------- Rom.py | 4 ++++ Rules.py | 39 +++++++++++++++++++++++++++++---------- 6 files changed, 51 insertions(+), 22 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 0212c683..4419cd0c 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -372,6 +372,18 @@ class CollectionState(object): else: self.prog_items.append('Power Glove') changed = True + elif 'Shield' in item.name: + if self.has('Mirror Shield'): + pass + elif self.has('Red Shield'): + self.prog_items.append('Mirror Shield') + changed = True + elif self.has('Blue Shield'): + self.prog_items.append('Red Shield') + changed = True + else: + self.prog_items.append('Blue Shield') + changed = True elif event or item.advancement: self.prog_items.append(item.name) diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 91f36192..82edd5bc 100644 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -36,8 +36,7 @@ if __name__ == '__main__': Agahnim\'s Tower barrier can be destroyed with hammer. Misery Mire and Turtle Rock can be opened without a sword. Hammer damages Ganon. Ether and - Bombos Tablet are unreachable but contain trash items - always. + Bombos Tablet can be activated with Hammer (and Book). ''') parser.add_argument('--goal', default='ganon', const='ganon', nargs='?', choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'], help='''\ diff --git a/Items.py b/Items.py index 1f5c8d65..a4bbd595 100644 --- a/Items.py +++ b/Items.py @@ -85,9 +85,9 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla 'Red Boomerang': (False, True, None, 0x2A, 'No matter what\nyou do, red\nreturns to you', 'and the badmarang', None, None, None, None), 'Blue Shield': (False, True, None, 0x04, 'Now you can\ndefend against\npebbles!', 'and the stone blocker', None, None, None, None), 'Red Shield': (False, True, None, 0x05, 'Now you can\ndefend against\nfireballs!', 'and the shot blocker', None, None, None, None), - 'Mirror Shield': (False, True, None, 0x06, 'Now you can\ndefend against\nlasers!', 'and the laser blocker', None, None, None, None), - 'Progressive Shield': (False, True, None, 0x5F, 'have a better\nblocker in\nfront of you', 'and the new shield', None, None, None, None), - 'Bug Catching Net': (False, True, None, 0x21, 'Let\'s catch\nsome bees and\nfaeries!', 'and the bee catcher', None, None, None, None), + 'Mirror Shield': (True, False, None, 0x06, 'Now you can\ndefend against\nlasers!', 'and the laser blocker', None, None, None, None), + 'Progressive Shield': (True, False, None, 0x5F, 'have a better\nblocker in\nfront of you', 'and the new shield', None, None, None, None), + 'Bug Catching Net': (True, False, None, 0x21, 'Let\'s catch\nsome bees and\nfaeries!', 'and the bee catcher', None, None, None, None), 'Cane of Byrna': (True, False, None, 0x18, 'Use this to\nbecome\ninvincible!', 'and the bad cane', None, None, None, None), 'Boss Heart Container': (False, False, None, 0x3E, 'Maximum health\nincreased!\nYeah!', 'and the love', None, None, None, None), 'Sanctuary Heart Container': (False, False, None, 0x3F, 'Maximum health\nincreased!\nYeah!', 'and the love', None, None, None, None), diff --git a/Main.py b/Main.py index f93e5fa0..513ce2c0 100644 --- a/Main.py +++ b/Main.py @@ -158,9 +158,7 @@ def generate_itempool(world): ['Arrows (10)'] * 5 + ['Bombs (3)'] * 10) if world.mode == 'swordless': - world.push_item('Ether Tablet', ItemFactory('Rupees (20)'), False) - world.push_item('Bombos Tablet', ItemFactory('Rupees (20)'), False) - world.itempool.extend(ItemFactory(['Rupees (20)', 'Rupees (20)'])) + world.itempool.extend(ItemFactory(['Rupees (20)'] * 4)) elif world.mode == 'standard': world.push_item('Uncle', ItemFactory('Progressive Sword'), False) world.get_location('Uncle').event = True @@ -184,10 +182,7 @@ def generate_itempool(world): world.treasure_hunt_icon = 'Triforce Piece' world.itempool.extend(ItemFactory(['Triforce Piece'] * 30)) - if random.randint(0, 3) == 0: - world.itempool.append(ItemFactory('Magic Upgrade (1/4)')) - else: - world.itempool.append(ItemFactory('Magic Upgrade (1/2)')) + world.itempool.append(ItemFactory('Magic Upgrade (1/2)')) # shuffle medallions mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] diff --git a/Rom.py b/Rom.py index 274a8ade..ea952a80 100644 --- a/Rom.py +++ b/Rom.py @@ -252,12 +252,16 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None): 202, 33, 98, 255, 38, 131, 187, 35, 250, 195, 35, 250, 187, 43, 250, 195, 43, 250, 187, 83, 250, 195, 83, 250, 176, 160, 61, 152, 19, 192, 152, 82, 192, 136, 0, 96, 144, 0, 96, 232, 0, 96, 240, 0, 96, 152, 202, 192, 216, 202, 192, 216, 19, 192, 216, 82, 192, 252, 189, 133, 253, 29, 135, 255, 255, 255, 255, 240, 255, 128, 46, 97, 14, 129, 14, 255, 255]) + # set Waterfall fairy prizes to be disappointing + rom.write_byte(0x348DB, 0x3A) # Red Boomerang becomes Red Boomerang + rom.write_byte(0x348EB, 0x05) # Blue Shield becomes Blue Shield # set swordless mode settings rom.write_byte(0x18003F, 0x01 if world.mode == 'swordless' else 0x00) # hammer can harm ganon rom.write_byte(0x180040, 0x01 if world.mode == 'swordless' else 0x00) # open curtains rom.write_byte(0x180041, 0x01 if world.mode == 'swordless' else 0x00) # swordless medallions rom.write_byte(0x180043, 0xFF if world.mode == 'swordless' else 0x00) # starting sword for link + rom.write_byte(0x180044, 0x01 if world.mode == 'swordless' else 0x00) # hammer activates tablets # set up clocks for timed modes if world.clock_mode == 'off': diff --git a/Rules.py b/Rules.py index 67f9b147..276b4c67 100644 --- a/Rules.py +++ b/Rules.py @@ -53,6 +53,13 @@ def add_lamp_requirement(spot): def forbid_item(location, item): old_rule = location.item_rule location.item_rule = lambda i: i.name != item and old_rule(i) + +def item_in_locations(state, item, locations): + for location in locations: + loc=state.world.get_location(location) + if loc.item is not None and loc.item.name == item: + return True + return False def global_rules(world): @@ -168,7 +175,7 @@ def global_rules(world): set_rule(world.get_entrance('Dark Death Mountain Ascend (Bottom)'), lambda state: state.has_Pearl()) set_rule(world.get_entrance('Cave Shop (Dark Death Mountain)'), lambda state: state.has_Pearl()) # just for save bunny algo for now set_rule(world.get_entrance('Dark Death Mountain Ascend Exit (Bottom)'), lambda state: False) # Cannot get to bottom exit from top. Just exists for shuffling - set_rule(world.get_location('[cave-055] Spike Cave'), lambda state: state.has('Hammer') and state.can_lift_rocks() and (state.has('Cane of Byrna') or state.has('Cape'))) + set_rule(world.get_location('[cave-055] Spike Cave'), lambda state: state.has('Hammer') and state.can_lift_rocks() and (state.has('Cane of Byrna') or state.has('Cape')) and (state.has('Bottle') or state.has('Half Magic') or state.has('Quarter Magic'))) set_rule(world.get_location('[cave-056] Hookshot Cave [top right chest]'), lambda state: state.has('Hookshot')) set_rule(world.get_location('[cave-056] Hookshot Cave [top left chest]'), lambda state: state.has('Hookshot')) set_rule(world.get_location('[cave-056] Hookshot Cave [bottom right chest]'), lambda state: state.has('Hookshot') or state.has('Pegasus Boots')) @@ -195,6 +202,8 @@ def global_rules(world): (state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Bow'))) for location in ['Lanmolas - Heart Container', '[dungeon-L2-B1] Desert Palace - Big Chest']: forbid_item(world.get_location(location), 'Big Key (Desert Palace)') + for location in ['Lanmolas - Heart Container', '[dungeon-L2-B1] Desert Palace - Big Key Room', '[dungeon-L2-B1] Desert Palace - Compass Room']: + forbid_item(world.get_location(location), 'Small Key (Desert Palace)') set_rule(world.get_entrance('Tower of Hera Small Key Door'), lambda state: state.has('Small Key (Tower of Hera)')) set_rule(world.get_entrance('Tower of Hera Big Key Door'), lambda state: state.has('Big Key (Tower of Hera)')) @@ -204,6 +213,8 @@ def global_rules(world): set_rule(world.get_location('Moldorm - Pendant'), lambda state: state.has_blunt_weapon()) for location in ['Moldorm - Heart Container', '[dungeon-L3-1F] Tower of Hera - Big Chest', '[dungeon-L3-1F] Tower of Hera - 4F [small chest]']: forbid_item(world.get_location(location), 'Big Key (Tower of Hera)') + for location in ['[dungeon-L3-1F] Tower of Hera - Basement']: + forbid_item(world.get_location(location), 'Small Key (Tower of Hera)') set_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has('Flippers') and state.can_reach('Dam')) set_rule(world.get_entrance('Swamp Palace Small Key Door'), lambda state: state.has('Small Key (Swamp Palace)')) @@ -221,18 +232,20 @@ def global_rules(world): set_rule(world.get_location('[dungeon-D4-1F] Thieves Town - Room above Boss'), lambda state: state.has('Small Key (Thieves Town)')) for location in ['[dungeon-D4-1F] Thieves Town - Room above Boss', '[dungeon-D4-B2] Thieves Town - Big Chest', '[dungeon-D4-B2] Thieves Town - Chest next to Blind', 'Blind - Heart Container']: forbid_item(world.get_location(location), 'Big Key (Thieves Town)') + for location in ['[dungeon-D4-1F] Thieves Town - Room above Boss', '[dungeon-D4-B2] Thieves Town - Big Chest', 'Blind - Heart Container']: + forbid_item(world.get_location(location), 'Small Key (Thieves Town)') set_rule(world.get_location('[dungeon-D3-B1] Skull Woods - Big Chest'), lambda state: state.has('Big Key (Skull Woods)')) set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has('Small Key (Skull Woods)', 3) and state.has('Fire Rod') and state.has_sword()) # sword required for curtain for location in ['[dungeon-D3-B1] Skull Woods - Big Chest']: forbid_item(world.get_location(location), 'Big Key (Skull Woods)') + for location in ['Mothula - Heart Container']: + forbid_item(world.get_location(location), 'Small Key (Skull Woods)') set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or (state.has('Bombos') and state.has_sword())) set_rule(world.get_location('[dungeon-D5-B5] Ice Palace - Big Chest'), lambda state: state.has('Big Key (Ice Palace)')) set_rule(world.get_entrance('Ice Palace (Kholdstare)'), lambda state: state.can_lift_rocks() and state.has('Hammer') and state.has('Big Key (Ice Palace)') and (state.has('Small Key (Ice Palace)', 2) or (state.has('Cane of Somaria') and state.has('Small Key (Ice Palace)', 1)))) - set_rule(world.get_entrance('Ice Palace (East)'), lambda state: state.has('Hookshot') or (state.has('Small Key (Ice Palace)', 1) and ((state.world.get_location('[dungeon-D5-B3] Ice Palace - Spike Room').item is not None and state.world.get_location('[dungeon-D5-B3] Ice Palace - Spike Room').item.name in ['Big Key (Ice Palace)']) or - (state.world.get_location('[dungeon-D5-B1] Ice Palace - Big Key Room').item is not None and state.world.get_location('[dungeon-D5-B1] Ice Palace - Big Key Room').item.name in ['Big Key (Ice Palace)']) or - (state.world.get_location('[dungeon-D5-B2] Ice Palace - Map Room').item is not None and state.world.get_location('[dungeon-D5-B2] Ice Palace - Map Room').item.name in ['Big Key (Ice Palace)'])))) # if you do ipbj and waste SKs in the basement, you have to BJ over the hookshot room to fix your mess potentially. This seems fair + set_rule(world.get_entrance('Ice Palace (East)'), lambda state: (state.has('Hookshot') or (item_in_locations(state,'Big Key (Ice Palace)',['[dungeon-D5-B3] Ice Palace - Spike Room','[dungeon-D5-B1] Ice Palace - Big Key Room','[dungeon-D5-B2] Ice Palace - Map Room']) and state.has('Small Key (Ice Palace)')) or state.has('Small Key (Ice Palace)',2)) and (state.has('Hookshot') or state.has('Cape') or state.has('Cane of Byrna'))) set_rule(world.get_entrance('Ice Palace (East Top)'), lambda state: state.can_lift_rocks() and state.has('Hammer')) for location in ['[dungeon-D5-B5] Ice Palace - Big Chest', 'Kholdstare - Heart Container']: forbid_item(world.get_location(location), 'Big Key (Ice Palace)') @@ -266,10 +279,10 @@ def global_rules(world): set_rule(world.get_entrance('Turtle Rock Dark Room Staircase'), lambda state: state.has('Small Key (Turtle Rock)', 3)) set_rule(world.get_entrance('Turtle Rock (Dark Room) (North)'), lambda state: state.has('Cane of Somaria')) set_rule(world.get_entrance('Turtle Rock (Dark Room) (South)'), lambda state: state.has('Cane of Somaria')) - set_rule(world.get_location('[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [bottom left chest]'), lambda state: state.has('Cane of Byrna') or state.has('Cape')) - set_rule(world.get_location('[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [bottom right chest]'), lambda state: state.has('Cane of Byrna') or state.has('Cape')) - set_rule(world.get_location('[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [top left chest]'), lambda state: state.has('Cane of Byrna') or state.has('Cape')) - set_rule(world.get_location('[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [top right chest]'), lambda state: state.has('Cane of Byrna') or state.has('Cape')) + set_rule(world.get_location('[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [bottom left chest]'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) + set_rule(world.get_location('[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [bottom right chest]'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) + set_rule(world.get_location('[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [top left chest]'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) + set_rule(world.get_location('[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [top right chest]'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) set_rule(world.get_entrance('Turtle Rock (Trinexx)'), lambda state: state.has('Small Key (Turtle Rock)', 4) and state.has('Big Key (Turtle Rock)') and state.has('Cane of Somaria') and state.has('Fire Rod') and state.has('Ice Rod') and (state.has('Hammer') or state.has_beam_sword() or state.has('Bottle') or state.has('Half Magic') or state.has('Quarter Magic'))) set_trock_key_rules(world) @@ -285,6 +298,8 @@ def global_rules(world): set_rule(world.get_location('[dungeon-D1-1F] Dark Palace - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)')) for location in ['[dungeon-D1-1F] Dark Palace - Big Chest', 'Helmasaur - Heart Container']: forbid_item(world.get_location(location), 'Big Key (Palace of Darkness)') + for location in ['[dungeon-D1-1F] Dark Palace - Big Chest', '[dungeon-D1-1F] Dark Palace - Maze Room [top chest]', '[dungeon-D1-1F] Dark Palace - Maze Room [bottom chest]']: + forbid_item(world.get_location(location), 'Small Key (Palace of Darkness)') # these key rules are conservative, you might be able to get away with more lenient rules set_rule(world.get_location('[dungeon-A2-1F] Ganons Tower - Torch'), lambda state: state.has_Boots()) @@ -382,8 +397,8 @@ def open_rules(world): def swordless_rules(world): set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has('Hammer') or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle set_rule(world.get_entrance('Agahnim 1'), lambda state: state.has('Hammer') or state.has('Bug Catching Net')) - set_rule(world.get_location('Ether Tablet'), lambda state: True) # will have fixed rupee drop, unobtainable - set_rule(world.get_location('Bombos Tablet'), lambda state: True) # will have fixed rupee drop, unobtainable + set_rule(world.get_location('Ether Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer')) + set_rule(world.get_location('Bombos Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer') and state.has_Mirror()) set_rule(world.get_entrance('Misery Mire'), lambda state: state.has_Pearl() and state.has_misery_mire_medallion()) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_entrance('Turtle Rock'), lambda state: state.has_Pearl() and state.has_turtle_rock_medallion() and state.can_reach('Turtle Rock (Top)', 'Region')) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has('Small Key (Skull Woods)', 3) and state.has('Fire Rod')) # no curtain @@ -438,6 +453,10 @@ def set_trock_key_rules(world): for location in non_big_key_locations: forbid_item(world.get_location(location), 'Big Key (Turtle Rock)') + + # small key restriction + for location in ['Trinexx - Heart Container']: + forbid_item(world.get_location(location), 'Small Key (Turtle Rock)') def set_big_bomb_rules(world):