diff --git a/BaseClasses.py b/BaseClasses.py index 9a241504..b271abd2 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -185,7 +185,7 @@ class World(object): return True return False - + def has_beaten_game(self, state): if state.has('Triforce'): return True if self.goal in ['triforcehunt']: @@ -329,7 +329,7 @@ class CollectionState(object): return item in self.prog_items else: return self.item_count(item) >= count - + def item_count(self, item): return len([pritem for pritem in self.prog_items if pritem == item]) diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 8ca0e098..54d7288c 100644 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -20,9 +20,9 @@ if __name__ == '__main__': parser.add_argument('--logic', default='noglitches', const='noglitches', nargs='?', choices=['noglitches', 'minorglitches'], help='''\ Select Enforcement of Item Requirements. (default: %(default)s) - No Glitches: + No Glitches: Minor Glitches: May require Fake Flippers, Bunny Revival - and Dark Room Navigation. + and Dark Room Navigation. ''') parser.add_argument('--mode', default='open', const='open', nargs='?', choices=['standard', 'open', 'swordless'], help='''\ @@ -37,19 +37,19 @@ 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 can be activated with Hammer (and Book). + 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='''\ Select completion goal. (default: %(default)s) - Ganon: Collect all crystals, beat Agahnim 2 then + Ganon: Collect all crystals, beat Agahnim 2 then defeat Ganon. Crystals: Collect all crystals then defeat Ganon. Pedestal: Places the Triforce at the Master Sword Pedestal. All Dungeons: Collect all crystals, pendants, beat both Agahnim fights and then defeat Ganon. - Triforce Hunt: Places 30 Triforce Pieces in the world, collect - 20 of them to beat the game. + Triforce Hunt: Places 30 Triforce Pieces in the world, collect + 20 of them to beat the game. ''') parser.add_argument('--difficulty', default='normal', const='normal', nargs='?', choices=['easy', 'normal', 'hard', 'expert', 'insane'], help='''\ @@ -58,7 +58,7 @@ if __name__ == '__main__': Normal: Normal difficulty. Hard: A harder setting with less equipment and reduced health. Expert: A harder yet setting with minimum equipment and health. - Insane: A setting with the absolute minimum in equipment and no extra health. + Insane: A setting with the absolute minimum in equipment and no extra health. ''') parser.add_argument('--timer', default='none', const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'], help='''\ @@ -78,7 +78,7 @@ if __name__ == '__main__': and the clock is permenantly at zero. Timed Countdown: Starts with clock at 40 minutes. Same clocks as Timed mode. If time runs out, you lose (but can - still keep playing). + still keep playing). ''') parser.add_argument('--progressive', default='on', const='normal', nargs='?', choices=['on', 'off', 'random'], help='''\ @@ -92,7 +92,7 @@ if __name__ == '__main__': be found at any time. Downgrades are not possible. Random: Swords, Shields, Armor, and Gloves will, per category, be randomly progressive or not. - Link will die in one hit. + Link will die in one hit. ''') parser.add_argument('--algorithm', default='balanced', const='balanced', nargs='?', choices=['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'], help='''\ @@ -115,7 +115,7 @@ if __name__ == '__main__': them the more often they were found unreachable. Flood: Push out items starting from Link\'s House and slightly biased to placing progression items with - less restrictions. + less restrictions. ''') parser.add_argument('--shuffle', default='full', const='full', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'madness', 'insanity', 'dungeonsfull', 'dungeonssimple'], help='''\ @@ -137,7 +137,7 @@ if __name__ == '__main__': discretion. Experimental. The dungeon variants only mix up dungeons and keep the rest of - the overworld vanilla. + the overworld vanilla. ''') parser.add_argument('--rom', default='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', help='Path to an ALttP JAP(1.0) rom to use as a base.') parser.add_argument('--loglevel', default='info', const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.') @@ -147,7 +147,7 @@ if __name__ == '__main__': If --seed is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with --seed given will produce the same 10 (different) roms each - time). + time). ''', type=int) parser.add_argument('--fastmenu', help='Enable instant menu', action='store_true') parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true') @@ -157,33 +157,33 @@ if __name__ == '__main__': ''', action='store_true') parser.add_argument('--nodungeonitems', help='''\ Remove Maps and Compasses from Itempool, replacing them by - empty slots. + empty slots. ''', action='store_true') parser.add_argument('--beatableonly', help='''\ Only check if the game is beatable with placement. Do not ensure all locations are reachable. This only has an effect - on the restrictive algorithm currently. + on the restrictive algorithm currently. ''', action='store_true') parser.add_argument('--shuffleganon', help='''\ - If set, include the Pyramid Hole and Ganon's Tower in the - entrance shuffle pool. + If set, include the Pyramid Hole and Ganon's Tower in the + entrance shuffle pool. ''', action='store_true') parser.add_argument('--heartbeep', default='normal', const='normal', nargs='?', choices=['normal', 'half', 'quarter', 'off'], help='''\ Select the rate at which the heart beep sound is played at - low health. (default: %(default)s) + low health. (default: %(default)s) ''') parser.add_argument('--sprite', help='''\ Path to a sprite sheet to use for Link. Needs to be in - binary format and have a length of 0x7000 (28672) bytes, + binary format and have a length of 0x7000 (28672) bytes, or 0x7078 (28792) bytes including palette data. Alternatively, can be a ALttP Rom patched with a Link - sprite that will be extracted. + sprite that will be extracted. ''') parser.add_argument('--suppress_rom', help='Do not create an output rom file.', action='store_true') parser.add_argument('--gui', help='Launch the GUI', action='store_true') parser.add_argument('--jsonout', action='store_true', help='''\ - Output .json patch to stdout instead of a patched rom. Used + Output .json patch to stdout instead of a patched rom. Used for VT site integration, do not use otherwise. ''') args = parser.parse_args() diff --git a/Fill.py b/Fill.py index 351c7d8d..6ed0e3da 100644 --- a/Fill.py +++ b/Fill.py @@ -167,7 +167,7 @@ def fill_restrictive(world, base_state, locations, itempool): while itempool and locations: item_to_place = itempool.pop() maximum_exploration_state = sweep_from_pool() - + if world.check_beatable_only: can_beat_without = world.has_beaten_game(maximum_exploration_state) diff --git a/Main.py b/Main.py index a1b05e5b..feb615cf 100644 --- a/Main.py +++ b/Main.py @@ -228,4 +228,4 @@ def create_playthrough(world): old_world.required_locations = [location.name for sphere in collection_spheres for location in sphere] # we can finally output our playthrough - old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)]) \ No newline at end of file + old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)]) diff --git a/README.md b/README.md index e29dabbf..ecd16f53 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ randomized. --algorithm [{freshness,flood,vt21,vt22,vt25,vt26,balanced}] ``` -Select item filling algorithm. +Select item filling algorithm. ### Balanced (Default) This is a variation of vt26 that aims to strike a balance between the overworld heavy vt25 and the dungeon heavy vt26 algorithm. @@ -220,7 +220,7 @@ Items and locations are shuffled like in VT25, and dungeon items are now placed shuffled it includes a slight deliberate bias against having too many desireable items in Ganon's Tower to help counterbalance the sheer number of chests in that single location. -### VT25 +### VT25 Items and locations are shuffled and placed from the top of the lists. The only thing preventing an item from being placed into a spot is if is absolutely impossible to be there given the previous made placement choices. Leads to very uniform but guaranteed solvable distributions. @@ -243,7 +243,7 @@ staleness, decreasing the likelihood of receiving a progress item. --shuffle [{default,simple,restricted,full,madness,insanity,dungeonsfull,dungeonssimple}] ``` -Select Entrance Shuffling Algorithm. +Select Entrance Shuffling Algorithm. ### Default @@ -256,7 +256,7 @@ on the overworld. On Death Mountain, entrances are connected more freely. ### Full (Default) -Mixes cave and dungeon entrances freely. +Mixes cave and dungeon entrances freely. ### Restricted @@ -296,7 +296,7 @@ Define seed number to generate. (default: None) Using the same seed with same se --count COUNT ``` -Use to batch generate multiple seeds with same settings. +Use to batch generate multiple seeds with same settings. If --seed is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with --seed given will produce the same 10 (different) roms each time). (default: None) ``` diff --git a/Rom.py b/Rom.py index e1bf18ce..2f47d956 100644 --- a/Rom.py +++ b/Rom.py @@ -21,10 +21,10 @@ class JsonRom(object): def write_bytes(self, startaddress, values): self.patches[str(startaddress)] = list(values) - + def write_int16_to_rom(self, address, value): self.write_bytes(address, int16_as_bytes(value)) - + def write_int32_to_rom(self, address, value): self.write_bytes(address, int32_as_bytes(value)) @@ -47,7 +47,7 @@ class LocalRom(object): def write_int16_to_rom(self, address, value): self.write_bytes(address, int16_as_bytes(value)) - + def write_int32_to_rom(self, address, value): self.write_bytes(address, int32_as_bytes(value)) @@ -86,7 +86,7 @@ class LocalRom(object): def int16_as_bytes(value): value = value & 0xFFFF return [value & 0xFF, (value >> 8) & 0xFF] - + def int32_as_bytes(value): value = value & 0xFFFFFFFF return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF] diff --git a/Rules.py b/Rules.py index d7fe41c8..f4f2011d 100644 --- a/Rules.py +++ b/Rules.py @@ -301,7 +301,7 @@ def global_rules(world): set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (state.world.get_location('Palace of Darkness - Big Key Chest').item is not None and (state.world.get_location('Palace of Darkness - Big Key Chest').item.name in ['Small Key (Palace of Darkness)']))) set_rule(world.get_entrance('Palace of Darkness (North)'), lambda state: state.has('Small Key (Palace of Darkness)', 4)) set_rule(world.get_location('Palace of Darkness - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)')) - + if world.keysanity: set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (state.world.get_location('Palace of Darkness - Harmless Hellway').item is not None and (state.world.get_location('Palace of Darkness - Harmless Hellway').item.name in ['Small Key (Palace of Darkness)']))) set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (state.world.get_location('Palace of Darkness - Big Key Chest').item is not None and (state.world.get_location('Palace of Darkness - Big Key Chest').item.name in ['Small Key (Palace of Darkness)']))) @@ -310,7 +310,7 @@ def global_rules(world): set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (state.world.get_location('Palace of Darkness - Harmless Hellway').item is not None and (state.world.get_location('Palace of Darkness - Harmless Hellway').item.name in ['Small Key (Palace of Darkness)']))) set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (state.world.get_location('Palace of Darkness - Big Key Chest').item is not None and (state.world.get_location('Palace of Darkness - Big Key Chest').item.name in ['Small Key (Palace of Darkness)']))) set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 5)) - + for location in ['Palace of Darkness - Big Chest', 'Palace of Darkness - Helmasaur']: forbid_item(world.get_location(location), 'Big Key (Palace of Darkness)') @@ -320,7 +320,7 @@ def global_rules(world): # these key rules are conservative, you might be able to get away with more lenient rules randomizer_room_chests = ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'] compass_room_chests = ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right'] - + set_rule(world.get_location('Ganons Tower - Bob\'s Torch'), lambda state: state.has_Boots()) set_rule(world.get_entrance('Ganons Tower (Tile Room)'), lambda state: state.has('Cane of Somaria')) set_rule(world.get_entrance('Ganons Tower (Hookshot Room)'), lambda state: state.has('Hammer')) @@ -328,24 +328,24 @@ def global_rules(world): set_rule(world.get_entrance('Ganons Tower (Map Room)'), lambda state: state.has('Small Key (Ganons Tower)', 4) or (state.world.get_location('Ganons Tower - Map Chest').item is not None and state.world.get_location('Ganons Tower - Map Chest').item.name == 'Big Key (Ganons Tower)' and state.has('Small Key (Ganons Tower)', 3)) or (state.world.get_location('Ganons Tower - Map Chest').item is not None and state.world.get_location('Ganons Tower - Map Chest').item.name == 'Small Key (Ganons Tower)')) else: set_rule(world.get_entrance('Ganons Tower (Map Room)'), lambda state: state.has('Small Key (Ganons Tower)', 3) or (state.world.get_location('Ganons Tower - Map Chest').item is not None and state.world.get_location('Ganons Tower - Map Chest').item.name == 'Small Key (Ganons Tower)')) - + # It is possible to need more than 2 keys to get through this entance if you spend keys elsewhere We reflect this in the chest requirements. # However we need to leave these at the lower values derive that with 3 keys it is always possible to reach Bob and Ice Armos. set_rule(world.get_entrance('Ganons Tower (Double Switch Room)'), lambda state: state.has('Small Key (Ganons Tower)', 2)) # It is possible to need more than 3 keys .... set_rule(world.get_entrance('Ganons Tower (Firesnake Room)'), lambda state: state.has('Small Key (Ganons Tower)', 3)) - + #The actual requirements for these rooms to avoid key-lock set_rule(world.get_location('Ganons Tower - Firesnake Room'), lambda state: state.has('Small Key (Ganons Tower)', 3) or (item_in_locations(state, 'Big Key (Ganons Tower)', randomizer_room_chests) and state.has('Small Key (Ganons Tower)', 2))) for location in randomizer_room_chests: set_rule(world.get_location(location), lambda state: state.has('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', randomizer_room_chests) and state.has('Small Key (Ganons Tower)', 3))) - + # Once again it is possible to need more than 3 keys... set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door'), lambda state: state.has('Small Key (Ganons Tower)', 3) and state.has('Fire Rod')) # Actual requirements for location in compass_room_chests: set_rule(world.get_location(location), lambda state: state.has('Fire Rod') and (state.has('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', compass_room_chests) and state.has('Small Key (Ganons Tower)', 3)))) - + set_rule(world.get_location('Ganons Tower - Big Chest'), lambda state: state.has('Big Key (Ganons Tower)')) set_rule(world.get_location('Ganons Tower - Big Key Room - Left'), lambda state: state.has('Bow') or state.has_blunt_weapon()) set_rule(world.get_location('Ganons Tower - Big Key Chest'), lambda state: state.has('Bow') or state.has_blunt_weapon()) @@ -429,7 +429,7 @@ def open_rules(world): # softlock protection as you can reach the sewers small key door with a guard drop key forbid_item(world.get_location('Hyrule Castle - Boomerang Chest'), 'Small Key (Escape)') forbid_item(world.get_location('Hyrule Castle - Zelda\'s Chest'), 'Small Key (Escape)') - + # to prevent key-lock in keysanity we need to prevent these chests from having an item that # blocks the small key if (world.keysanity): @@ -443,7 +443,7 @@ def swordless_rules(world): # should there ever be fixes that apply to open mode but not swordless, this # can be revisited. open_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') and state.has('Small Key (Agahnims Tower)', 2)) set_rule(world.get_location('Ether Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer')) @@ -479,7 +479,7 @@ def set_trock_key_rules(world): # if we have backdoor access we can waste a key on the trinexx door, then have no lamp to reverse traverse the maze room. We simply require an additional key just to be super safe then. The backdoor access to the chest is otherwise free if not can_reach_back: - set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has('Small Key (Turtle Rock)', 1)) + set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has('Small Key (Turtle Rock)', 1)) else: set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has('Small Key (Turtle Rock)', 2)) @@ -496,8 +496,8 @@ def set_trock_key_rules(world): # however in keysanity being able to reach all other chests while only having three keys does not imply this contains # a key, so we again need all four keys unless it contains the big key if can_reach_back: - set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 4) or (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Small Key (Turtle Rock)']))) - elif world.keysanity: + set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 4) or (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Small Key (Turtle Rock)']))) + elif world.keysanity: set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 2) if (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Big Key (Turtle Rock)'])) else state.has('Small Key (Turtle Rock)', 4) or (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Small Key (Turtle Rock)']))) else: set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 2) if (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Big Key (Turtle Rock)'])) else state.has('Small Key (Turtle Rock)', 3) or (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Small Key (Turtle Rock)']))) diff --git a/Text.py b/Text.py index 148eccba..f1f308a8 100644 --- a/Text.py +++ b/Text.py @@ -141,11 +141,11 @@ class Credits(object): class CreditLine(object): """Base class of credit lines""" - + def __init__(self, text, align='center'): self.text = text self.align = align - + @property def x(self): x = 0 @@ -157,7 +157,7 @@ class CreditLine(object): x = (32 - len(self.text)) // 2 return x - + class SceneCreditLine(CreditLine): """Base class for credit lines for the scene portion of the credits""" def __init__(self, y, text, align='center'): @@ -214,7 +214,7 @@ class SceneLargeCreditLine(SceneCreditLine): buf += LargeCreditBottomMapper.convert(self.text) return buf - + def string_to_alttp_text(s, maxbytes=256): lines = s.upper().split('\n') outbuf = bytearray() @@ -489,7 +489,7 @@ class GreenCreditMapper(TextMapper): char_map = {' ': 0x9F, '.': 0x52} alpha_offset = -0x29 - + class RedCreditMapper(TextMapper): char_map = {' ': 0x9F} #fixme alpha_offset= -0x61