Replace keysanity with map/compass/key/bk shuffle
This commit is contained in:
		
							parent
							
								
									6ca08a0fa4
								
							
						
					
					
						commit
						fc9d1b501b
					
				|  | @ -8,7 +8,7 @@ from Utils import int16_as_bytes | |||
| 
 | ||||
| class World(object): | ||||
| 
 | ||||
|     def __init__(self, players, shuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, place_dungeon_items, accessibility, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity, retro, custom, customitemarray, boss_shuffle, hints): | ||||
|     def __init__(self, players, shuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, accessibility, shuffle_ganon, quickswap, fastmenu, disable_music, retro, custom, customitemarray, boss_shuffle, hints): | ||||
|         self.players = players | ||||
|         self.shuffle = shuffle | ||||
|         self.logic = logic | ||||
|  | @ -35,7 +35,6 @@ class World(object): | |||
|         self._entrance_cache = {} | ||||
|         self._location_cache = {} | ||||
|         self.required_locations = [] | ||||
|         self.place_dungeon_items = place_dungeon_items  # configurable in future | ||||
|         self.shuffle_bonk_prizes = False | ||||
|         self.swamp_patch_required = {player: False for player in range(1, players + 1)} | ||||
|         self.powder_patch_required = {player: False for player in range(1, players + 1)} | ||||
|  | @ -65,7 +64,10 @@ class World(object): | |||
|         self.quickswap = quickswap | ||||
|         self.fastmenu = fastmenu | ||||
|         self.disable_music = disable_music | ||||
|         self.keysanity = keysanity | ||||
|         self.mapshuffle = False | ||||
|         self.compassshuffle = False | ||||
|         self.keyshuffle = False | ||||
|         self.bigkeyshuffle = False | ||||
|         self.retro = retro | ||||
|         self.custom = custom | ||||
|         self.customitemarray = customitemarray | ||||
|  | @ -175,7 +177,7 @@ class World(object): | |||
|             elif item.name.startswith('Bottle'): | ||||
|                 if ret.bottle_count(item.player) < self.difficulty_requirements.progressive_bottle_limit: | ||||
|                     ret.prog_items.add((item.name, item.player)) | ||||
|             elif item.advancement or item.key: | ||||
|             elif item.advancement or item.smallkey or item.bigkey: | ||||
|                 ret.prog_items.add((item.name, item.player)) | ||||
| 
 | ||||
|         for item in self.itempool: | ||||
|  | @ -352,12 +354,14 @@ class CollectionState(object): | |||
| 
 | ||||
|     def sweep_for_events(self, key_only=False, locations=None): | ||||
|         # this may need improvement | ||||
|         if locations is None: | ||||
|             locations = self.world.get_filled_locations() | ||||
|         new_locations = True | ||||
|         checked_locations = 0 | ||||
|         while new_locations: | ||||
|             if locations is None: | ||||
|                 locations = self.world.get_filled_locations() | ||||
|             reachable_events = [location for location in locations if location.event and (not key_only or location.item.key) and location.can_reach(self)] | ||||
|             reachable_events = [location for location in locations if location.event and | ||||
|                                 (not key_only or (not self.world.keyshuffle and location.item.smallkey) or (not self.world.bigkeyshuffle and location.item.bigkey)) | ||||
|                                 and location.can_reach(self)] | ||||
|             for event in reachable_events: | ||||
|                 if (event.name, event.player) not in self.events: | ||||
|                     self.events.append((event.name, event.player)) | ||||
|  | @ -677,9 +681,12 @@ class Region(object): | |||
|         return False | ||||
| 
 | ||||
|     def can_fill(self, item): | ||||
|         is_dungeon_item = item.key or item.map or item.compass | ||||
|         inside_dungeon_item = ((item.smallkey and not self.world.keyshuffle) | ||||
|                                or (item.bigkey and not self.world.bigkeyshuffle) | ||||
|                                or (item.map and not self.world.mapshuffle) | ||||
|                                or (item.compass and not self.world.compassshuffle)) | ||||
|         sewer_hack = self.world.mode == 'standard' and item.name == 'Small Key (Escape)' | ||||
|         if sewer_hack or (is_dungeon_item and not self.world.keysanity): | ||||
|         if sewer_hack or inside_dungeon_item: | ||||
|             return self.dungeon and self.dungeon.is_dungeon_item(item) and item.player == self.player | ||||
| 
 | ||||
|         return True | ||||
|  | @ -838,14 +845,18 @@ class Item(object): | |||
|         self.location = None | ||||
|         self.player = player | ||||
| 
 | ||||
|     @property | ||||
|     def key(self): | ||||
|         return self.type == 'SmallKey' or self.type == 'BigKey' | ||||
| 
 | ||||
|     @property | ||||
|     def crystal(self): | ||||
|         return self.type == 'Crystal' | ||||
| 
 | ||||
|     @property | ||||
|     def smallkey(self): | ||||
|         return self.type == 'SmallKey' | ||||
| 
 | ||||
|     @property | ||||
|     def bigkey(self): | ||||
|         return self.type == 'BigKey' | ||||
| 
 | ||||
|     @property | ||||
|     def map(self): | ||||
|         return self.type == 'Map' | ||||
|  | @ -1036,7 +1047,10 @@ class Spoiler(object): | |||
|                          'item_functionality': self.world.difficulty_adjustments, | ||||
|                          'accessibility': self.world.accessibility, | ||||
|                          'hints': self.world.hints, | ||||
|                          'keysanity': self.world.keysanity, | ||||
|                          'mapshuffle': self.world.mapshuffle, | ||||
|                          'compassshuffle': self.world.compassshuffle, | ||||
|                          'keyshuffle': self.world.keyshuffle, | ||||
|                          'bigkeyshuffle': self.world.bigkeyshuffle, | ||||
|                          'players': self.world.players | ||||
|                          } | ||||
| 
 | ||||
|  | @ -1068,10 +1082,12 @@ class Spoiler(object): | |||
|             outfile.write('Entrance Shuffle:                %s\n' % self.metadata['shuffle']) | ||||
|             outfile.write('Filling Algorithm:               %s\n' % self.world.algorithm) | ||||
|             outfile.write('Accessibility:                   %s\n' % self.metadata['accessibility']) | ||||
|             outfile.write('Maps and Compasses in Dungeons:  %s\n' % ('Yes' if self.world.place_dungeon_items else 'No')) | ||||
|             outfile.write('L\\R Quickswap enabled:           %s\n' % ('Yes' if self.world.quickswap else 'No')) | ||||
|             outfile.write('Menu speed:                      %s\n' % self.world.fastmenu) | ||||
|             outfile.write('Keysanity enabled:               %s\n' % ('Yes' if self.metadata['keysanity'] else 'No')) | ||||
|             outfile.write('Map shuffle:                     %s\n' % ('Yes' if self.metadata['mapshuffle'] else 'No')) | ||||
|             outfile.write('Compass shuffle:                 %s\n' % ('Yes' if self.metadata['compassshuffle'] else 'No')) | ||||
|             outfile.write('Small Key shuffle:               %s\n' % ('Yes' if self.metadata['keyshuffle'] else 'No')) | ||||
|             outfile.write('Big Key shuffle:                 %s\n' % ('Yes' if self.metadata['bigkeyshuffle'] else 'No')) | ||||
|             outfile.write('Players:                         %d' % self.world.players) | ||||
|             if self.entrances: | ||||
|                 outfile.write('\n\nEntrances:\n\n') | ||||
|  |  | |||
							
								
								
									
										29
									
								
								Dungeons.py
								
								
								
								
							
							
						
						
									
										29
									
								
								Dungeons.py
								
								
								
								
							|  | @ -113,14 +113,13 @@ def fill_dungeons(world): | |||
|             continue | ||||
| 
 | ||||
|         # next place dungeon items | ||||
|         if world.place_dungeon_items: | ||||
|             for dungeon_item in dungeon_items: | ||||
|                 di_location = dungeon_locations.pop() | ||||
|                 world.push_item(di_location, dungeon_item, False) | ||||
|         for dungeon_item in dungeon_items: | ||||
|             di_location = dungeon_locations.pop() | ||||
|             world.push_item(di_location, dungeon_item, False) | ||||
| 
 | ||||
| 
 | ||||
| def get_dungeon_item_pool(world): | ||||
|     return [item for dungeon in world.dungeons for item in dungeon.all_items if item.key or world.place_dungeon_items] | ||||
|     return [item for dungeon in world.dungeons for item in dungeon.all_items] | ||||
| 
 | ||||
| def fill_dungeons_restrictive(world, shuffled_locations): | ||||
|     all_state_base = world.get_all_state() | ||||
|  | @ -135,16 +134,18 @@ def fill_dungeons_restrictive(world, shuffled_locations): | |||
|         pinball_room.locked = True | ||||
|         shuffled_locations.remove(pinball_room) | ||||
| 
 | ||||
|     if world.keysanity: | ||||
|         #in keysanity dungeon items are distributed as part of the normal item pool | ||||
|         for item in world.get_items(): | ||||
|             if item.key: | ||||
|                 item.advancement = True | ||||
|             elif item.map or item.compass: | ||||
|                 item.priority = True | ||||
|         return | ||||
|     # with shuffled dungeon items they are distributed as part of the normal item pool | ||||
|     for item in world.get_items(): | ||||
|         if (item.smallkey and world.keyshuffle) or (item.bigkey and world.bigkeyshuffle): | ||||
|             all_state_base.collect(item, True) | ||||
|             item.advancement = True | ||||
|         elif (item.map and world.mapshuffle) or (item.compass and world.compassshuffle): | ||||
|             item.priority = True | ||||
| 
 | ||||
|     dungeon_items = get_dungeon_item_pool(world) | ||||
|     dungeon_items = [item for item in get_dungeon_item_pool(world) if ((item.smallkey and not world.keyshuffle) | ||||
|                                                                        or (item.bigkey and not world.bigkeyshuffle) | ||||
|                                                                        or (item.map and not world.mapshuffle) | ||||
|                                                                        or (item.compass and not world.compassshuffle))] | ||||
| 
 | ||||
|     # sort in the order Big Key, Small Key, Other before placing dungeon items | ||||
|     sort_order = {"BigKey": 3, "SmallKey": 2} | ||||
|  |  | |||
|  | @ -85,7 +85,7 @@ In the vanilla, dungeonssimple, and dungeonsfull shuffles, the following two loc | |||
| Graveyard Cave | ||||
| Mimic Cave | ||||
| 
 | ||||
| Valuable Items are simply all items that are shown on the pause subscreen (Y, B, or A sections) minus Silver Arrows and plus Triforce Pieces, Magic Upgrades (1/2 or 1/4), and the Single Arrow. If keysanity is being used, you can additionally get hints for Small Keys or Big Keys but not hints for Maps or Compasses. | ||||
| Valuable Items are simply all items that are shown on the pause subscreen (Y, B, or A sections) minus Silver Arrows and plus Triforce Pieces, Magic Upgrades (1/2 or 1/4), and the Single Arrow. If key shuffle is being used, you can additionally get hints for Small Keys or Big Keys but not hints for Maps or Compasses. | ||||
| 
 | ||||
| While the exact verbage of location names and item names can be found in the source code, here's a copy for reference: | ||||
| 
 | ||||
|  |  | |||
|  | @ -198,20 +198,16 @@ def start(): | |||
|                              ''') | ||||
|     parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true') | ||||
|     parser.add_argument('--disablemusic', help='Disables game music.', action='store_true') | ||||
|     parser.add_argument('--keysanity', help='''\ | ||||
|                              Keys (and other dungeon items) are no longer restricted to | ||||
|                              their dungeons, but can be anywhere | ||||
|                              ''', action='store_true') | ||||
|     parser.add_argument('--mapshuffle', help='Maps are no longer restricted to their dungeons, but can be anywhere', action='store_true') | ||||
|     parser.add_argument('--compassshuffle', help='Compasses are no longer restricted to their dungeons, but can be anywhere', action='store_true') | ||||
|     parser.add_argument('--keyshuffle', help='Small Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true') | ||||
|     parser.add_argument('--bigkeyshuffle', help='Big Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true') | ||||
|     parser.add_argument('--retro', help='''\ | ||||
|                              Keys are universal, shooting arrows costs rupees, | ||||
|                              and a few other little things make this more like Zelda-1. | ||||
|                              ''', action='store_true') | ||||
|     parser.add_argument('--custom', default=False, help='Not supported.') | ||||
|     parser.add_argument('--customitemarray', default=False, help='Not supported.') | ||||
|     parser.add_argument('--nodungeonitems', help='''\ | ||||
|                              Remove Maps and Compasses from Itempool, replacing them by | ||||
|                              empty slots. | ||||
|                              ''', action='store_true') | ||||
|     parser.add_argument('--accessibility', default='items', const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\ | ||||
|                              Select Item/Location Accessibility. (default: %(default)s) | ||||
|                              Items:     You can reach all unique inventory items. No guarantees about | ||||
|  |  | |||
							
								
								
									
										20
									
								
								Fill.py
								
								
								
								
							
							
						
						
									
										20
									
								
								Fill.py
								
								
								
								
							|  | @ -240,8 +240,8 @@ def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=No | |||
|     random.shuffle(fill_locations) | ||||
|     fill_locations.reverse() | ||||
| 
 | ||||
|     # Make sure the escape small key is placed first in standard keysanity to prevent running out of spots | ||||
|     if world.keysanity and world.mode == 'standard': | ||||
|     # Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots | ||||
|     if world.keyshuffle and world.mode == 'standard': | ||||
|         progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' else 0) | ||||
| 
 | ||||
|     fill_restrictive(world, world.state, fill_locations, progitempool) | ||||
|  | @ -312,7 +312,7 @@ def flood_items(world): | |||
|         location_list = world.get_reachable_locations() | ||||
|         random.shuffle(location_list) | ||||
|         for location in location_list: | ||||
|             if location.item is not None and not location.item.advancement and not location.item.priority and not location.item.key: | ||||
|             if location.item is not None and not location.item.advancement and not location.item.priority and not location.item.smallkey and not location.item.bigkey: | ||||
|                 # safe to replace | ||||
|                 replace_item = location.item | ||||
|                 replace_item.location = None | ||||
|  | @ -332,8 +332,7 @@ def balance_multiworld_progression(world): | |||
|         reachable_locations_count[player] = 0 | ||||
| 
 | ||||
|     def get_sphere_locations(sphere_state, locations): | ||||
|         if not world.keysanity: | ||||
|             sphere_state.sweep_for_events(key_only=True, locations=locations) | ||||
|         sphere_state.sweep_for_events(key_only=True, locations=locations) | ||||
|         return [loc for loc in locations if sphere_state.can_reach(loc)] | ||||
| 
 | ||||
|     while True: | ||||
|  | @ -354,7 +353,7 @@ def balance_multiworld_progression(world): | |||
|                 candidate_items = [] | ||||
|                 while True: | ||||
|                     for location in balancing_sphere: | ||||
|                         if location.event: | ||||
|                         if location.event and (world.keyshuffle or not location.item.smallkey) and (world.bigkeyshuffle or not location.item.bigkey): | ||||
|                             balancing_state.collect(location.item, True, location) | ||||
|                             if location.item.player in balancing_players and not location.locked: | ||||
|                                 candidate_items.append(location) | ||||
|  | @ -364,11 +363,14 @@ def balance_multiworld_progression(world): | |||
|                         balancing_reachables[location.player] += 1 | ||||
|                     if world.has_beaten_game(balancing_state) or all([reachables >= threshold for reachables in balancing_reachables.values()]): | ||||
|                         break | ||||
|                     elif not balancing_sphere: | ||||
|                         raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') | ||||
| 
 | ||||
|                 unlocked_locations = [l for l in unchecked_locations if l not in balancing_unchecked_locations] | ||||
|                 items_to_replace = [] | ||||
|                 for player in balancing_players: | ||||
|                     locations_to_test = [l for l in unlocked_locations if l.player == player] | ||||
|                     # only replace items that end up in another player's world | ||||
|                     items_to_test = [l for l in candidate_items if l.item.player == player and l.player != player] | ||||
|                     while items_to_test: | ||||
|                         testing = items_to_test.pop() | ||||
|  | @ -392,7 +394,7 @@ def balance_multiworld_progression(world): | |||
|                     new_location = replacement_locations.pop() | ||||
|                     old_location = items_to_replace.pop() | ||||
| 
 | ||||
|                     while not new_location.can_fill(state, old_location.item): | ||||
|                     while not new_location.can_fill(state, old_location.item, False) or (new_location.item and not old_location.can_fill(state, new_location.item, False)): | ||||
|                         replacement_locations.insert(0, new_location) | ||||
|                         new_location = replacement_locations.pop() | ||||
| 
 | ||||
|  | @ -407,9 +409,11 @@ def balance_multiworld_progression(world): | |||
|                         sphere_locations.append(location) | ||||
| 
 | ||||
|         for location in sphere_locations: | ||||
|             if location.event and (world.keysanity or not location.item.key): | ||||
|             if location.event and (world.keyshuffle or not location.item.smallkey) and (world.bigkeyshuffle or not location.item.bigkey): | ||||
|                 state.collect(location.item, True, location) | ||||
|         checked_locations.extend(sphere_locations) | ||||
| 
 | ||||
|         if world.has_beaten_game(state): | ||||
|             break | ||||
|         elif not sphere_locations: | ||||
|             raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') | ||||
|  |  | |||
							
								
								
									
										35
									
								
								Gui.py
								
								
								
								
							
							
						
						
									
										35
									
								
								Gui.py
								
								
								
								
							|  | @ -63,12 +63,18 @@ def guiMain(args=None): | |||
|     quickSwapCheckbutton = Checkbutton(checkBoxFrame, text="Enabled L/R Item quickswapping", variable=quickSwapVar) | ||||
|     openpyramidVar = IntVar() | ||||
|     openpyramidCheckbutton = Checkbutton(checkBoxFrame, text="Pre-open Pyramid Hole", variable=openpyramidVar) | ||||
|     keysanityVar = IntVar() | ||||
|     keysanityCheckbutton = Checkbutton(checkBoxFrame, text="Keysanity (keys anywhere)", variable=keysanityVar) | ||||
|     mcsbshuffleFrame = Frame(checkBoxFrame) | ||||
|     mcsbLabel = Label(mcsbshuffleFrame, text="Shuffle: ") | ||||
|     mapshuffleVar = IntVar() | ||||
|     mapshuffleCheckbutton = Checkbutton(mcsbshuffleFrame, text="Maps", variable=mapshuffleVar) | ||||
|     compassshuffleVar = IntVar() | ||||
|     compassshuffleCheckbutton = Checkbutton(mcsbshuffleFrame, text="Compasses", variable=compassshuffleVar) | ||||
|     keyshuffleVar = IntVar() | ||||
|     keyshuffleCheckbutton = Checkbutton(mcsbshuffleFrame, text="Keys", variable=keyshuffleVar) | ||||
|     bigkeyshuffleVar = IntVar() | ||||
|     bigkeyshuffleCheckbutton = Checkbutton(mcsbshuffleFrame, text="BigKeys", variable=bigkeyshuffleVar) | ||||
|     retroVar = IntVar() | ||||
|     retroCheckbutton = Checkbutton(checkBoxFrame, text="Retro mode (universal keys)", variable=retroVar) | ||||
|     dungeonItemsVar = IntVar() | ||||
|     dungeonItemsCheckbutton = Checkbutton(checkBoxFrame, text="Place Dungeon Items (Compasses/Maps)", onvalue=0, offvalue=1, variable=dungeonItemsVar) | ||||
|     disableMusicVar = IntVar() | ||||
|     disableMusicCheckbutton = Checkbutton(checkBoxFrame, text="Disable game music", variable=disableMusicVar) | ||||
|     shuffleGanonVar = IntVar() | ||||
|  | @ -84,9 +90,13 @@ def guiMain(args=None): | |||
|     suppressRomCheckbutton.pack(expand=True, anchor=W) | ||||
|     quickSwapCheckbutton.pack(expand=True, anchor=W) | ||||
|     openpyramidCheckbutton.pack(expand=True, anchor=W) | ||||
|     keysanityCheckbutton.pack(expand=True, anchor=W) | ||||
|     mcsbshuffleFrame.pack(expand=True, anchor=W) | ||||
|     mcsbLabel.grid(row=0, column=0) | ||||
|     mapshuffleCheckbutton.grid(row=0, column=1) | ||||
|     compassshuffleCheckbutton.grid(row=0, column=2) | ||||
|     keyshuffleCheckbutton.grid(row=0, column=3) | ||||
|     bigkeyshuffleCheckbutton.grid(row=0, column=4) | ||||
|     retroCheckbutton.pack(expand=True, anchor=W) | ||||
|     dungeonItemsCheckbutton.pack(expand=True, anchor=W) | ||||
|     disableMusicCheckbutton.pack(expand=True, anchor=W) | ||||
|     shuffleGanonCheckbutton.pack(expand=True, anchor=W) | ||||
|     hintsCheckbutton.pack(expand=True, anchor=W) | ||||
|  | @ -385,9 +395,11 @@ def guiMain(args=None): | |||
|         guiargs.create_spoiler = bool(createSpoilerVar.get()) | ||||
|         guiargs.suppress_rom = bool(suppressRomVar.get()) | ||||
|         guiargs.openpyramid = bool(openpyramidVar.get()) | ||||
|         guiargs.keysanity = bool(keysanityVar.get()) | ||||
|         guiargs.mapshuffle = bool(mapshuffleVar.get()) | ||||
|         guiargs.compassshuffle = bool(compassshuffleVar.get()) | ||||
|         guiargs.keyshuffle = bool(keyshuffleVar.get()) | ||||
|         guiargs.bigkeyshuffle = bool(bigkeyshuffleVar.get()) | ||||
|         guiargs.retro = bool(retroVar.get()) | ||||
|         guiargs.nodungeonitems = bool(dungeonItemsVar.get()) | ||||
|         guiargs.quickswap = bool(quickSwapVar.get()) | ||||
|         guiargs.disablemusic = bool(disableMusicVar.get()) | ||||
|         guiargs.shuffleganon = bool(shuffleGanonVar.get()) | ||||
|  | @ -1160,10 +1172,11 @@ def guiMain(args=None): | |||
|         # load values from commandline args | ||||
|         createSpoilerVar.set(int(args.create_spoiler)) | ||||
|         suppressRomVar.set(int(args.suppress_rom)) | ||||
|         keysanityVar.set(args.keysanity) | ||||
|         mapshuffleVar.set(args.mapshuffle) | ||||
|         compassshuffleVar.set(args.compassshuffle) | ||||
|         keyshuffleVar.set(args.keyshuffle) | ||||
|         bigkeyshuffleVar.set(args.bigkeyshuffle) | ||||
|         retroVar.set(args.retro) | ||||
|         if args.nodungeonitems: | ||||
|             dungeonItemsVar.set(int(not args.nodungeonitems)) | ||||
|         quickSwapVar.set(int(args.quickswap)) | ||||
|         disableMusicVar.set(int(args.disablemusic)) | ||||
|         if args.count: | ||||
|  |  | |||
|  | @ -221,8 +221,11 @@ def generate_itempool(world, player): | |||
|     if treasure_hunt_icon is not None: | ||||
|         world.treasure_hunt_icon = treasure_hunt_icon | ||||
| 
 | ||||
|     if world.keysanity: | ||||
|         world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player]) | ||||
|     world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player | ||||
|                            and ((item.smallkey and world.keyshuffle) | ||||
|                                 or (item.bigkey and world.bigkeyshuffle) | ||||
|                                 or (item.map and world.mapshuffle) | ||||
|                                 or (item.compass and world.compassshuffle))]) | ||||
| 
 | ||||
|     # logic has some branches where having 4 hearts is one possible requirement (of several alternatives) | ||||
|     # rather than making all hearts/heart pieces progression items (which slows down generation considerably) | ||||
|  |  | |||
							
								
								
									
										31
									
								
								Main.py
								
								
								
								
							
							
						
						
									
										31
									
								
								Main.py
								
								
								
								
							|  | @ -25,7 +25,7 @@ def main(args, seed=None): | |||
|     start = time.process_time() | ||||
| 
 | ||||
|     # initialize the world | ||||
|     world = World(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.accessibility, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.retro, args.custom, args.customitemarray, args.shufflebosses, args.hints) | ||||
|     world = World(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, args.accessibility, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.retro, args.custom, args.customitemarray, args.shufflebosses, args.hints) | ||||
|     logger = logging.getLogger('') | ||||
|     if seed is None: | ||||
|         random.seed(None) | ||||
|  | @ -34,6 +34,19 @@ def main(args, seed=None): | |||
|         world.seed = int(seed) | ||||
|     random.seed(world.seed) | ||||
| 
 | ||||
|     world.mapshuffle = args.mapshuffle | ||||
|     world.compassshuffle = args.compassshuffle | ||||
|     world.keyshuffle = args.keyshuffle | ||||
|     world.bigkeyshuffle = args.bigkeyshuffle | ||||
| 
 | ||||
|     mcsb_name = '' | ||||
|     if all([world.mapshuffle, world.compassshuffle, world.keyshuffle, world.bigkeyshuffle]): | ||||
|         mcsb_name = '-keysanity' | ||||
|     elif [world.mapshuffle, world.compassshuffle, world.keyshuffle, world.bigkeyshuffle].count(True) == 1: | ||||
|         mcsb_name = '-mapshuffle' if world.mapshuffle else '-compassshuffle' if world.compassshuffle else '-keyshuffle' if world.keyshuffle else '-bigkeyshuffle' | ||||
|     elif any([world.mapshuffle, world.compassshuffle, world.keyshuffle, world.bigkeyshuffle]): | ||||
|         mcsb_name = '-%s%s%s%sshuffle' % ('M' if world.mapshuffle else '', 'C' if world.compassshuffle else '', 'S' if world.keyshuffle else '', 'B' if world.bigkeyshuffle else '') | ||||
| 
 | ||||
|     world.crystals_needed_for_ganon = random.randint(0, 7) if args.crystals_ganon == 'random' else int(args.crystals_ganon) | ||||
|     world.crystals_needed_for_gt = random.randint(0, 7) if args.crystals_gt == 'random' else int(args.crystals_gt) | ||||
|     world.open_pyramid = args.openpyramid | ||||
|  | @ -83,7 +96,7 @@ def main(args, seed=None): | |||
|     logger.info('Placing Dungeon Items.') | ||||
| 
 | ||||
|     shuffled_locations = None | ||||
|     if args.algorithm in ['balanced', 'vt26'] or args.keysanity: | ||||
|     if args.algorithm in ['balanced', 'vt26'] or args.mapshuffle or args.compassshuffle or args.keyshuffle or args.bigkeyshuffle: | ||||
|         shuffled_locations = world.get_unfilled_locations() | ||||
|         random.shuffle(shuffled_locations) | ||||
|         fill_dungeons_restrictive(world, shuffled_locations) | ||||
|  | @ -124,7 +137,7 @@ def main(args, seed=None): | |||
| 
 | ||||
|     player_names = parse_names_string(args.names) | ||||
|     outfileprefix = 'ER_%s_' % world.seed | ||||
|     outfilesuffix = '%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s' % (world.logic, world.difficulty, world.difficulty_adjustments, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, "-keysanity" if world.keysanity else "", "-retro" if world.retro else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", "-nohints" if not world.hints else "") | ||||
|     outfilesuffix = '%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s' % (world.logic, world.difficulty, world.difficulty_adjustments, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, mcsb_name, "-retro" if world.retro else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", "-nohints" if not world.hints else "") | ||||
|     outfilebase = outfileprefix + outfilesuffix | ||||
| 
 | ||||
|     use_enemizer = args.enemizercli and (args.shufflebosses != 'none' or args.shuffleenemies or args.enemy_health != 'default' or args.enemy_health != 'default' or args.enemy_damage or args.shufflepalette or args.shufflepots) | ||||
|  | @ -198,7 +211,7 @@ def gt_filler(world): | |||
| 
 | ||||
| def copy_world(world): | ||||
|     # ToDo: Not good yet | ||||
|     ret = World(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.accessibility, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity, world.retro, world.custom, world.customitemarray, world.boss_shuffle, world.hints) | ||||
|     ret = World(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.retro, world.custom, world.customitemarray, world.boss_shuffle, world.hints) | ||||
|     ret.required_medallions = world.required_medallions.copy() | ||||
|     ret.swamp_patch_required = world.swamp_patch_required.copy() | ||||
|     ret.ganon_at_pyramid = world.ganon_at_pyramid.copy() | ||||
|  | @ -218,6 +231,10 @@ def copy_world(world): | |||
|     ret.difficulty_requirements = world.difficulty_requirements | ||||
|     ret.fix_fake_world = world.fix_fake_world | ||||
|     ret.lamps_needed_for_dark_rooms = world.lamps_needed_for_dark_rooms | ||||
|     ret.mapshuffle = world.mapshuffle | ||||
|     ret.compassshuffle = world.compassshuffle | ||||
|     ret.keyshuffle = world.keyshuffle | ||||
|     ret.bigkeyshuffle = world.bigkeyshuffle | ||||
|     ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon | ||||
|     ret.crystals_needed_for_gt = world.crystals_needed_for_gt | ||||
| 
 | ||||
|  | @ -318,8 +335,7 @@ def create_playthrough(world): | |||
|     sphere_candidates = list(prog_locations) | ||||
|     logging.getLogger('').debug('Building up collection spheres.') | ||||
|     while sphere_candidates: | ||||
|         if not world.keysanity: | ||||
|             state.sweep_for_events(key_only=True) | ||||
|         state.sweep_for_events(key_only=True) | ||||
| 
 | ||||
|         sphere = [] | ||||
|         # build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres | ||||
|  | @ -372,8 +388,7 @@ def create_playthrough(world): | |||
|     state = CollectionState(world) | ||||
|     collection_spheres = [] | ||||
|     while required_locations: | ||||
|         if not world.keysanity: | ||||
|             state.sweep_for_events(key_only=True) | ||||
|         state.sweep_for_events(key_only=True) | ||||
| 
 | ||||
|         sphere = list(filter(state.can_reach, required_locations)) | ||||
| 
 | ||||
|  |  | |||
|  | @ -174,7 +174,7 @@ def fill_world(world, plando, text_patches): | |||
|                     item = ItemFactory(itemstr.strip(), 1) | ||||
|                     if item is not None: | ||||
|                         world.push_item(location, item) | ||||
|                     if item.key: | ||||
|                     if item.smallkey or item.bigkey: | ||||
|                         location.event = True | ||||
|             elif '<=>' in line: | ||||
|                 entrance, exit = line.split('<=>', 1) | ||||
|  |  | |||
							
								
								
									
										23
									
								
								README.md
								
								
								
								
							
							
						
						
									
										23
									
								
								README.md
								
								
								
								
							|  | @ -121,7 +121,7 @@ Does not invoke a timer. | |||
| ### Display | ||||
| 
 | ||||
| Displays a timer on-screen but does not alter the item pool. | ||||
| This will prevent the dungeon item count feature in Easy and Keysanity from working. | ||||
| This will prevent the dungeon item count feature in Easy and Compass shuffle from working. | ||||
| 
 | ||||
| ### Timed | ||||
| 
 | ||||
|  | @ -264,12 +264,12 @@ generate spoilers for statistical analysis. | |||
| 
 | ||||
| Use to enable quick item swap with L/R buttons. Press L and R together to switch the state of items like the Mushroom/Powder pair. | ||||
| 
 | ||||
| ## Keysanity | ||||
| ## Map/Compass/Small Key/Big Key shuffle (aka Keysanity) | ||||
| 
 | ||||
| This setting allows dungeon specific items (Small Key, Big Key, Map, Compass) to be distributed anywhere in the world and not just | ||||
| in their native dungeon. Small Keys dropped by enemies or found in pots are not affected. The chest in southeast Skull Woods that | ||||
| is traditionally a guaranteed Small Key still is. These items will be distributed according to the v26/balanced algorithm, but | ||||
| the rest of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell | ||||
| These settings allow dungeon specific items to be distributed anywhere in the world and not just in their native dungeon. | ||||
| Small Keys dropped by enemies or found in pots are not affected. The chest in southeast Skull Woods that is traditionally | ||||
| a guaranteed Small Key still is. These items will be distributed according to the v26/balanced algorithm, but the rest | ||||
| of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell | ||||
| for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize. | ||||
| 
 | ||||
| ## Retro | ||||
|  | @ -422,10 +422,10 @@ Alters the rate at which the menu opens and closes. (default: normal) | |||
| Disables game music, resulting in the game sound being just the SFX. (default: False) | ||||
| 
 | ||||
| ``` | ||||
| --keysanity | ||||
| --mapshuffle --compassshuffle --keyshuffle --bigkeyshuffle | ||||
| ``` | ||||
| 
 | ||||
| Enable Keysanity (default: False) | ||||
| Respectively enable Map/Compass/SmallKey/BigKey shuffle (default: False) | ||||
| 
 | ||||
| ``` | ||||
| --retro | ||||
|  | @ -433,13 +433,6 @@ Enable Keysanity (default: False) | |||
| 
 | ||||
| Enable Retro mode (default: False) | ||||
| 
 | ||||
| ``` | ||||
| --nodungeonitems | ||||
| ``` | ||||
| 
 | ||||
| If set, Compasses and Maps are removed from the dungeon item pools and replaced by empty chests that may end up anywhere in the world. | ||||
| This may lead to different amount of itempool items being placed in a dungeon than you are used to. (default: False) | ||||
| 
 | ||||
| ``` | ||||
| --heartbeep [{normal,half,quarter,off}] | ||||
| ``` | ||||
|  |  | |||
							
								
								
									
										90
									
								
								Rom.py
								
								
								
								
							
							
						
						
									
										90
									
								
								Rom.py
								
								
								
								
							|  | @ -444,10 +444,10 @@ def patch_rom(world, player, rom): | |||
|             # Keys in their native dungeon should use the orignal item code for keys | ||||
|             if location.parent_region.dungeon: | ||||
|                 dungeon = location.parent_region.dungeon | ||||
|                 if location.item is not None and location.item.key and dungeon.is_dungeon_item(location.item): | ||||
|                     if location.item.type == "BigKey": | ||||
|                 if location.item is not None and dungeon.is_dungeon_item(location.item): | ||||
|                     if location.item.bigkey: | ||||
|                         itemid = 0x32 | ||||
|                     if location.item.type == "SmallKey": | ||||
|                     if location.item.smallkey: | ||||
|                         itemid = 0x24 | ||||
|             if location.item and location.item.player != player: | ||||
|                 if location.player_address is not None: | ||||
|  | @ -462,15 +462,15 @@ def patch_rom(world, player, rom): | |||
| 
 | ||||
|             # patch music | ||||
|             music_addresses = dungeon_music_addresses[location.name] | ||||
|             if world.keysanity: | ||||
|             if world.mapshuffle: | ||||
|                 music = random.choice([0x11, 0x16]) | ||||
|             else: | ||||
|                 music = 0x11 if 'Pendant' in location.item.name else 0x16 | ||||
|             for music_address in music_addresses: | ||||
|                 rom.write_byte(music_address, music) | ||||
| 
 | ||||
|     if world.keysanity: | ||||
|         rom.write_byte(0x155C9, random.choice([0x11, 0x16]))  # Randomize GT music too in keysanity mode | ||||
|     if world.mapshuffle: | ||||
|         rom.write_byte(0x155C9, random.choice([0x11, 0x16]))  # Randomize GT music too with map shuffle | ||||
| 
 | ||||
|     # patch entrance/exits/holes | ||||
|     for region in world.regions: | ||||
|  | @ -811,7 +811,7 @@ def patch_rom(world, player, rom): | |||
|         ERtimeincrease = 10 | ||||
|     else: | ||||
|         ERtimeincrease = 20 | ||||
|     if world.keysanity: | ||||
|     if world.keyshuffle or world.bigkeyshuffle or world.mapshuffle: | ||||
|         ERtimeincrease = ERtimeincrease + 15 | ||||
|     if world.clock_mode == 'off': | ||||
|         rom.write_bytes(0x180190, [0x00, 0x00, 0x00])  # turn off clock mode | ||||
|  | @ -922,18 +922,24 @@ def patch_rom(world, player, rom): | |||
|     rom.write_byte(0x18005F, world.crystals_needed_for_ganon) | ||||
|     rom.write_byte(0x18008A, 0x01 if world.mode == "standard" else 0x00) # block HC upstairs doors in rain state in standard mode | ||||
| 
 | ||||
|     rom.write_byte(0x18016A, 0x01 if world.keysanity else 0x00)  # free roaming item text boxes | ||||
|     rom.write_byte(0x18003B, 0x01 if world.keysanity else 0x00)  # maps showing crystals on overworld | ||||
|     rom.write_byte(0x18016A, 0x10 | ((0x01 if world.keyshuffle else 0x00) | ||||
|                                      | (0x02 if world.compassshuffle else 0x00) | ||||
|                                      | (0x04 if world.mapshuffle else 0x00) | ||||
|                                      | (0x08 if world.bigkeyshuffle else 0x00)))  # free roaming item text boxes | ||||
|     rom.write_byte(0x18003B, 0x01 if world.mapshuffle else 0x00)  # maps showing crystals on overworld | ||||
| 
 | ||||
|     # compasses showing dungeon count | ||||
|     if world.clock_mode != 'off': | ||||
|         rom.write_byte(0x18003C, 0x00)  # Currently must be off if timer is on, because they use same HUD location | ||||
|     elif world.keysanity: | ||||
|     elif world.compassshuffle: | ||||
|         rom.write_byte(0x18003C, 0x01)  # show on pickup | ||||
|     else: | ||||
|         rom.write_byte(0x18003C, 0x00) | ||||
| 
 | ||||
|     rom.write_byte(0x180045, 0xFF if world.keysanity else 0x00)  # free roaming items in menu | ||||
|     rom.write_byte(0x180045, ((0x01 if world.keyshuffle else 0x00) | ||||
|                               | (0x02 if world.bigkeyshuffle else 0x00) | ||||
|                               | (0x04 if world.compassshuffle else 0x00) | ||||
|                               | (0x08 if world.mapshuffle else 0x00)))  # free roaming items in menu | ||||
| 
 | ||||
|     # Map reveals | ||||
|     reveal_bytes = { | ||||
|  | @ -958,8 +964,8 @@ def patch_rom(world, player, rom): | |||
|             return reveal_bytes.get(location.parent_region.dungeon.name, 0x0000) | ||||
|         return 0x0000 | ||||
| 
 | ||||
|     write_int16(rom, 0x18017A, get_reveal_bytes('Green Pendant') if world.keysanity else 0x0000) # Sahasrahla reveal | ||||
|     write_int16(rom, 0x18017C, get_reveal_bytes('Crystal 5')|get_reveal_bytes('Crystal 6') if world.keysanity else 0x0000) # Bomb Shop Reveal | ||||
|     write_int16(rom, 0x18017A, get_reveal_bytes('Green Pendant') if world.mapshuffle else 0x0000) # Sahasrahla reveal | ||||
|     write_int16(rom, 0x18017C, get_reveal_bytes('Crystal 5')|get_reveal_bytes('Crystal 6') if world.mapshuffle else 0x0000) # Bomb Shop Reveal | ||||
| 
 | ||||
|     rom.write_byte(0x180172, 0x01 if world.retro else 0x00)  # universal keys | ||||
|     rom.write_byte(0x180175, 0x01 if world.retro else 0x00)  # rupee bow | ||||
|  | @ -1440,8 +1446,10 @@ def write_strings(rom, world, player): | |||
| 
 | ||||
|         # Lastly we write hints to show where certain interesting items are. It is done the way it is to re-use the silver code and also to give one hint per each type of item regardless of how many exist. This supports many settings well. | ||||
|         items_to_hint = RelevantItems.copy() | ||||
|         if world.keysanity: | ||||
|             items_to_hint.extend(KeysanityItems) | ||||
|         if world.keyshuffle: | ||||
|             items_to_hint.extend(SmallKeys) | ||||
|         if world.bigkeyshuffle: | ||||
|             items_to_hint.extend(BigKeys) | ||||
|         random.shuffle(items_to_hint) | ||||
|         hint_count = 5 if world.shuffle not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8 | ||||
|         while hint_count > 0: | ||||
|  | @ -2022,28 +2030,30 @@ RelevantItems = ['Bow', | |||
|                  'Magic Upgrade (1/4)' | ||||
|                  ] | ||||
| 
 | ||||
| KeysanityItems = ['Small Key (Eastern Palace)', | ||||
|                   'Big Key (Eastern Palace)', | ||||
|                   'Small Key (Escape)', | ||||
| 				  'Small Key (Desert Palace)', | ||||
|                   'Big Key (Desert Palace)', | ||||
| 				  'Small Key (Tower of Hera)', | ||||
|                   'Big Key (Tower of Hera)', | ||||
| 				  'Small Key (Agahnims Tower)', | ||||
| 				  'Small Key (Palace of Darkness)', | ||||
|                   'Big Key (Palace of Darkness)', | ||||
| 				  'Small Key (Thieves Town)', | ||||
|                   'Big Key (Thieves Town)', | ||||
| 				  'Small Key (Swamp Palace)', | ||||
|                   'Big Key (Swamp Palace)', | ||||
| 				  'Small Key (Skull Woods)', | ||||
|                   'Big Key (Skull Woods)', | ||||
| 				  'Small Key (Ice Palace)', | ||||
|                   'Big Key (Ice Palace)', | ||||
|                   'Small Key (Misery Mire)', | ||||
|                   'Big Key (Misery Mire)', | ||||
|                   'Small Key (Turtle Rock)', | ||||
|                   'Big Key (Turtle Rock)', | ||||
|                   'Small Key (Ganons Tower)', | ||||
|                   'Big Key (Ganons Tower)' | ||||
| 				  ] | ||||
| SmallKeys = ['Small Key (Eastern Palace)', | ||||
|              'Small Key (Escape)', | ||||
|              'Small Key (Desert Palace)', | ||||
|              'Small Key (Tower of Hera)', | ||||
|              'Small Key (Agahnims Tower)', | ||||
|              'Small Key (Palace of Darkness)', | ||||
|              'Small Key (Thieves Town)', | ||||
|              'Small Key (Swamp Palace)', | ||||
|              'Small Key (Skull Woods)', | ||||
|              'Small Key (Ice Palace)', | ||||
|              'Small Key (Misery Mire)', | ||||
|              'Small Key (Turtle Rock)', | ||||
|              'Small Key (Ganons Tower)', | ||||
|              ] | ||||
| 
 | ||||
| BigKeys = ['Big Key (Eastern Palace)', | ||||
|            'Big Key (Desert Palace)', | ||||
|            'Big Key (Tower of Hera)', | ||||
|            'Big Key (Palace of Darkness)', | ||||
|            'Big Key (Thieves Town)', | ||||
|            'Big Key (Swamp Palace)', | ||||
|            'Big Key (Skull Woods)', | ||||
|            'Big Key (Ice Palace)', | ||||
|            'Big Key (Misery Mire)', | ||||
|            'Big Key (Turtle Rock)', | ||||
|            'Big Key (Ganons Tower)' | ||||
|            ] | ||||
|  |  | |||
							
								
								
									
										4
									
								
								Rules.py
								
								
								
								
							
							
						
						
									
										4
									
								
								Rules.py
								
								
								
								
							|  | @ -804,7 +804,7 @@ def set_trock_key_rules(world, player): | |||
|         non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left', | ||||
|                                   'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left', | ||||
|                                   'Turtle Rock - Eye Bridge - Top Right'] | ||||
|         if not world.keysanity: | ||||
|         if not world.keyshuffle: | ||||
|             non_big_key_locations += ['Turtle Rock - Big Key Chest'] | ||||
|     else: | ||||
|         set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 2) if item_in_locations(state, 'Big Key (Turtle Rock)', player, [('Turtle Rock - Compass Chest', player), ('Turtle Rock - Roller Room - Left', player), ('Turtle Rock - Roller Room - Right', player)]) else state.has_key('Small Key (Turtle Rock)', player, 4)) | ||||
|  | @ -814,7 +814,7 @@ def set_trock_key_rules(world, player): | |||
|         non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left', | ||||
|                                   'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left', | ||||
|                                   'Turtle Rock - Eye Bridge - Top Right'] | ||||
|         if not world.keysanity: | ||||
|         if not world.keyshuffle: | ||||
|             non_big_key_locations += ['Turtle Rock - Big Key Chest', 'Turtle Rock - Chain Chomps'] | ||||
| 
 | ||||
|     # set big key restrictions | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue