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):
|
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.players = players
|
||||||
self.shuffle = shuffle
|
self.shuffle = shuffle
|
||||||
self.logic = logic
|
self.logic = logic
|
||||||
|
@ -35,7 +35,6 @@ class World(object):
|
||||||
self._entrance_cache = {}
|
self._entrance_cache = {}
|
||||||
self._location_cache = {}
|
self._location_cache = {}
|
||||||
self.required_locations = []
|
self.required_locations = []
|
||||||
self.place_dungeon_items = place_dungeon_items # configurable in future
|
|
||||||
self.shuffle_bonk_prizes = False
|
self.shuffle_bonk_prizes = False
|
||||||
self.swamp_patch_required = {player: False for player in range(1, players + 1)}
|
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)}
|
self.powder_patch_required = {player: False for player in range(1, players + 1)}
|
||||||
|
@ -65,7 +64,10 @@ class World(object):
|
||||||
self.quickswap = quickswap
|
self.quickswap = quickswap
|
||||||
self.fastmenu = fastmenu
|
self.fastmenu = fastmenu
|
||||||
self.disable_music = disable_music
|
self.disable_music = disable_music
|
||||||
self.keysanity = keysanity
|
self.mapshuffle = False
|
||||||
|
self.compassshuffle = False
|
||||||
|
self.keyshuffle = False
|
||||||
|
self.bigkeyshuffle = False
|
||||||
self.retro = retro
|
self.retro = retro
|
||||||
self.custom = custom
|
self.custom = custom
|
||||||
self.customitemarray = customitemarray
|
self.customitemarray = customitemarray
|
||||||
|
@ -175,7 +177,7 @@ class World(object):
|
||||||
elif item.name.startswith('Bottle'):
|
elif item.name.startswith('Bottle'):
|
||||||
if ret.bottle_count(item.player) < self.difficulty_requirements.progressive_bottle_limit:
|
if ret.bottle_count(item.player) < self.difficulty_requirements.progressive_bottle_limit:
|
||||||
ret.prog_items.add((item.name, item.player))
|
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))
|
ret.prog_items.add((item.name, item.player))
|
||||||
|
|
||||||
for item in self.itempool:
|
for item in self.itempool:
|
||||||
|
@ -352,12 +354,14 @@ class CollectionState(object):
|
||||||
|
|
||||||
def sweep_for_events(self, key_only=False, locations=None):
|
def sweep_for_events(self, key_only=False, locations=None):
|
||||||
# this may need improvement
|
# this may need improvement
|
||||||
|
if locations is None:
|
||||||
|
locations = self.world.get_filled_locations()
|
||||||
new_locations = True
|
new_locations = True
|
||||||
checked_locations = 0
|
checked_locations = 0
|
||||||
while new_locations:
|
while new_locations:
|
||||||
if locations is None:
|
reachable_events = [location for location in locations if location.event and
|
||||||
locations = self.world.get_filled_locations()
|
(not key_only or (not self.world.keyshuffle and location.item.smallkey) or (not self.world.bigkeyshuffle and location.item.bigkey))
|
||||||
reachable_events = [location for location in locations if location.event and (not key_only or location.item.key) and location.can_reach(self)]
|
and location.can_reach(self)]
|
||||||
for event in reachable_events:
|
for event in reachable_events:
|
||||||
if (event.name, event.player) not in self.events:
|
if (event.name, event.player) not in self.events:
|
||||||
self.events.append((event.name, event.player))
|
self.events.append((event.name, event.player))
|
||||||
|
@ -677,9 +681,12 @@ class Region(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def can_fill(self, item):
|
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)'
|
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 self.dungeon and self.dungeon.is_dungeon_item(item) and item.player == self.player
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -838,14 +845,18 @@ class Item(object):
|
||||||
self.location = None
|
self.location = None
|
||||||
self.player = player
|
self.player = player
|
||||||
|
|
||||||
@property
|
|
||||||
def key(self):
|
|
||||||
return self.type == 'SmallKey' or self.type == 'BigKey'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def crystal(self):
|
def crystal(self):
|
||||||
return self.type == 'Crystal'
|
return self.type == 'Crystal'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def smallkey(self):
|
||||||
|
return self.type == 'SmallKey'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bigkey(self):
|
||||||
|
return self.type == 'BigKey'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def map(self):
|
def map(self):
|
||||||
return self.type == 'Map'
|
return self.type == 'Map'
|
||||||
|
@ -1036,7 +1047,10 @@ class Spoiler(object):
|
||||||
'item_functionality': self.world.difficulty_adjustments,
|
'item_functionality': self.world.difficulty_adjustments,
|
||||||
'accessibility': self.world.accessibility,
|
'accessibility': self.world.accessibility,
|
||||||
'hints': self.world.hints,
|
'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
|
'players': self.world.players
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1068,10 +1082,12 @@ class Spoiler(object):
|
||||||
outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'])
|
outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'])
|
||||||
outfile.write('Filling Algorithm: %s\n' % self.world.algorithm)
|
outfile.write('Filling Algorithm: %s\n' % self.world.algorithm)
|
||||||
outfile.write('Accessibility: %s\n' % self.metadata['accessibility'])
|
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('L\\R Quickswap enabled: %s\n' % ('Yes' if self.world.quickswap else 'No'))
|
||||||
outfile.write('Menu speed: %s\n' % self.world.fastmenu)
|
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)
|
outfile.write('Players: %d' % self.world.players)
|
||||||
if self.entrances:
|
if self.entrances:
|
||||||
outfile.write('\n\nEntrances:\n\n')
|
outfile.write('\n\nEntrances:\n\n')
|
||||||
|
|
29
Dungeons.py
29
Dungeons.py
|
@ -113,14 +113,13 @@ def fill_dungeons(world):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# next place dungeon items
|
# next place dungeon items
|
||||||
if world.place_dungeon_items:
|
for dungeon_item in dungeon_items:
|
||||||
for dungeon_item in dungeon_items:
|
di_location = dungeon_locations.pop()
|
||||||
di_location = dungeon_locations.pop()
|
world.push_item(di_location, dungeon_item, False)
|
||||||
world.push_item(di_location, dungeon_item, False)
|
|
||||||
|
|
||||||
|
|
||||||
def get_dungeon_item_pool(world):
|
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):
|
def fill_dungeons_restrictive(world, shuffled_locations):
|
||||||
all_state_base = world.get_all_state()
|
all_state_base = world.get_all_state()
|
||||||
|
@ -135,16 +134,18 @@ def fill_dungeons_restrictive(world, shuffled_locations):
|
||||||
pinball_room.locked = True
|
pinball_room.locked = True
|
||||||
shuffled_locations.remove(pinball_room)
|
shuffled_locations.remove(pinball_room)
|
||||||
|
|
||||||
if world.keysanity:
|
# with shuffled dungeon items they are distributed as part of the normal item pool
|
||||||
#in keysanity dungeon items are distributed as part of the normal item pool
|
for item in world.get_items():
|
||||||
for item in world.get_items():
|
if (item.smallkey and world.keyshuffle) or (item.bigkey and world.bigkeyshuffle):
|
||||||
if item.key:
|
all_state_base.collect(item, True)
|
||||||
item.advancement = True
|
item.advancement = True
|
||||||
elif item.map or item.compass:
|
elif (item.map and world.mapshuffle) or (item.compass and world.compassshuffle):
|
||||||
item.priority = True
|
item.priority = True
|
||||||
return
|
|
||||||
|
|
||||||
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 in the order Big Key, Small Key, Other before placing dungeon items
|
||||||
sort_order = {"BigKey": 3, "SmallKey": 2}
|
sort_order = {"BigKey": 3, "SmallKey": 2}
|
||||||
|
|
|
@ -85,7 +85,7 @@ In the vanilla, dungeonssimple, and dungeonsfull shuffles, the following two loc
|
||||||
Graveyard Cave
|
Graveyard Cave
|
||||||
Mimic 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:
|
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('--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('--disablemusic', help='Disables game music.', action='store_true')
|
||||||
parser.add_argument('--keysanity', help='''\
|
parser.add_argument('--mapshuffle', help='Maps are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||||
Keys (and other dungeon items) are no longer restricted to
|
parser.add_argument('--compassshuffle', help='Compasses are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||||
their dungeons, but can be anywhere
|
parser.add_argument('--keyshuffle', help='Small Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||||
''', 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='''\
|
parser.add_argument('--retro', help='''\
|
||||||
Keys are universal, shooting arrows costs rupees,
|
Keys are universal, shooting arrows costs rupees,
|
||||||
and a few other little things make this more like Zelda-1.
|
and a few other little things make this more like Zelda-1.
|
||||||
''', action='store_true')
|
''', action='store_true')
|
||||||
parser.add_argument('--custom', default=False, help='Not supported.')
|
parser.add_argument('--custom', default=False, help='Not supported.')
|
||||||
parser.add_argument('--customitemarray', 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='''\
|
parser.add_argument('--accessibility', default='items', const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\
|
||||||
Select Item/Location Accessibility. (default: %(default)s)
|
Select Item/Location Accessibility. (default: %(default)s)
|
||||||
Items: You can reach all unique inventory items. No guarantees about
|
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)
|
random.shuffle(fill_locations)
|
||||||
fill_locations.reverse()
|
fill_locations.reverse()
|
||||||
|
|
||||||
# Make sure the escape small key is placed first in standard keysanity to prevent running out of spots
|
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
|
||||||
if world.keysanity and world.mode == 'standard':
|
if world.keyshuffle and world.mode == 'standard':
|
||||||
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' else 0)
|
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' else 0)
|
||||||
|
|
||||||
fill_restrictive(world, world.state, fill_locations, progitempool)
|
fill_restrictive(world, world.state, fill_locations, progitempool)
|
||||||
|
@ -312,7 +312,7 @@ def flood_items(world):
|
||||||
location_list = world.get_reachable_locations()
|
location_list = world.get_reachable_locations()
|
||||||
random.shuffle(location_list)
|
random.shuffle(location_list)
|
||||||
for location in 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
|
# safe to replace
|
||||||
replace_item = location.item
|
replace_item = location.item
|
||||||
replace_item.location = None
|
replace_item.location = None
|
||||||
|
@ -332,8 +332,7 @@ def balance_multiworld_progression(world):
|
||||||
reachable_locations_count[player] = 0
|
reachable_locations_count[player] = 0
|
||||||
|
|
||||||
def get_sphere_locations(sphere_state, locations):
|
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)]
|
return [loc for loc in locations if sphere_state.can_reach(loc)]
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
@ -354,7 +353,7 @@ def balance_multiworld_progression(world):
|
||||||
candidate_items = []
|
candidate_items = []
|
||||||
while True:
|
while True:
|
||||||
for location in balancing_sphere:
|
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)
|
balancing_state.collect(location.item, True, location)
|
||||||
if location.item.player in balancing_players and not location.locked:
|
if location.item.player in balancing_players and not location.locked:
|
||||||
candidate_items.append(location)
|
candidate_items.append(location)
|
||||||
|
@ -364,11 +363,14 @@ def balance_multiworld_progression(world):
|
||||||
balancing_reachables[location.player] += 1
|
balancing_reachables[location.player] += 1
|
||||||
if world.has_beaten_game(balancing_state) or all([reachables >= threshold for reachables in balancing_reachables.values()]):
|
if world.has_beaten_game(balancing_state) or all([reachables >= threshold for reachables in balancing_reachables.values()]):
|
||||||
break
|
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]
|
unlocked_locations = [l for l in unchecked_locations if l not in balancing_unchecked_locations]
|
||||||
items_to_replace = []
|
items_to_replace = []
|
||||||
for player in balancing_players:
|
for player in balancing_players:
|
||||||
locations_to_test = [l for l in unlocked_locations if l.player == player]
|
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]
|
items_to_test = [l for l in candidate_items if l.item.player == player and l.player != player]
|
||||||
while items_to_test:
|
while items_to_test:
|
||||||
testing = items_to_test.pop()
|
testing = items_to_test.pop()
|
||||||
|
@ -392,7 +394,7 @@ def balance_multiworld_progression(world):
|
||||||
new_location = replacement_locations.pop()
|
new_location = replacement_locations.pop()
|
||||||
old_location = items_to_replace.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)
|
replacement_locations.insert(0, new_location)
|
||||||
new_location = replacement_locations.pop()
|
new_location = replacement_locations.pop()
|
||||||
|
|
||||||
|
@ -407,9 +409,11 @@ def balance_multiworld_progression(world):
|
||||||
sphere_locations.append(location)
|
sphere_locations.append(location)
|
||||||
|
|
||||||
for location in sphere_locations:
|
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)
|
state.collect(location.item, True, location)
|
||||||
checked_locations.extend(sphere_locations)
|
checked_locations.extend(sphere_locations)
|
||||||
|
|
||||||
if world.has_beaten_game(state):
|
if world.has_beaten_game(state):
|
||||||
break
|
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)
|
quickSwapCheckbutton = Checkbutton(checkBoxFrame, text="Enabled L/R Item quickswapping", variable=quickSwapVar)
|
||||||
openpyramidVar = IntVar()
|
openpyramidVar = IntVar()
|
||||||
openpyramidCheckbutton = Checkbutton(checkBoxFrame, text="Pre-open Pyramid Hole", variable=openpyramidVar)
|
openpyramidCheckbutton = Checkbutton(checkBoxFrame, text="Pre-open Pyramid Hole", variable=openpyramidVar)
|
||||||
keysanityVar = IntVar()
|
mcsbshuffleFrame = Frame(checkBoxFrame)
|
||||||
keysanityCheckbutton = Checkbutton(checkBoxFrame, text="Keysanity (keys anywhere)", variable=keysanityVar)
|
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()
|
retroVar = IntVar()
|
||||||
retroCheckbutton = Checkbutton(checkBoxFrame, text="Retro mode (universal keys)", variable=retroVar)
|
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()
|
disableMusicVar = IntVar()
|
||||||
disableMusicCheckbutton = Checkbutton(checkBoxFrame, text="Disable game music", variable=disableMusicVar)
|
disableMusicCheckbutton = Checkbutton(checkBoxFrame, text="Disable game music", variable=disableMusicVar)
|
||||||
shuffleGanonVar = IntVar()
|
shuffleGanonVar = IntVar()
|
||||||
|
@ -84,9 +90,13 @@ def guiMain(args=None):
|
||||||
suppressRomCheckbutton.pack(expand=True, anchor=W)
|
suppressRomCheckbutton.pack(expand=True, anchor=W)
|
||||||
quickSwapCheckbutton.pack(expand=True, anchor=W)
|
quickSwapCheckbutton.pack(expand=True, anchor=W)
|
||||||
openpyramidCheckbutton.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)
|
retroCheckbutton.pack(expand=True, anchor=W)
|
||||||
dungeonItemsCheckbutton.pack(expand=True, anchor=W)
|
|
||||||
disableMusicCheckbutton.pack(expand=True, anchor=W)
|
disableMusicCheckbutton.pack(expand=True, anchor=W)
|
||||||
shuffleGanonCheckbutton.pack(expand=True, anchor=W)
|
shuffleGanonCheckbutton.pack(expand=True, anchor=W)
|
||||||
hintsCheckbutton.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.create_spoiler = bool(createSpoilerVar.get())
|
||||||
guiargs.suppress_rom = bool(suppressRomVar.get())
|
guiargs.suppress_rom = bool(suppressRomVar.get())
|
||||||
guiargs.openpyramid = bool(openpyramidVar.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.retro = bool(retroVar.get())
|
||||||
guiargs.nodungeonitems = bool(dungeonItemsVar.get())
|
|
||||||
guiargs.quickswap = bool(quickSwapVar.get())
|
guiargs.quickswap = bool(quickSwapVar.get())
|
||||||
guiargs.disablemusic = bool(disableMusicVar.get())
|
guiargs.disablemusic = bool(disableMusicVar.get())
|
||||||
guiargs.shuffleganon = bool(shuffleGanonVar.get())
|
guiargs.shuffleganon = bool(shuffleGanonVar.get())
|
||||||
|
@ -1160,10 +1172,11 @@ def guiMain(args=None):
|
||||||
# load values from commandline args
|
# load values from commandline args
|
||||||
createSpoilerVar.set(int(args.create_spoiler))
|
createSpoilerVar.set(int(args.create_spoiler))
|
||||||
suppressRomVar.set(int(args.suppress_rom))
|
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)
|
retroVar.set(args.retro)
|
||||||
if args.nodungeonitems:
|
|
||||||
dungeonItemsVar.set(int(not args.nodungeonitems))
|
|
||||||
quickSwapVar.set(int(args.quickswap))
|
quickSwapVar.set(int(args.quickswap))
|
||||||
disableMusicVar.set(int(args.disablemusic))
|
disableMusicVar.set(int(args.disablemusic))
|
||||||
if args.count:
|
if args.count:
|
||||||
|
|
|
@ -221,8 +221,11 @@ def generate_itempool(world, player):
|
||||||
if treasure_hunt_icon is not None:
|
if treasure_hunt_icon is not None:
|
||||||
world.treasure_hunt_icon = treasure_hunt_icon
|
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)
|
# 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)
|
# 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()
|
start = time.process_time()
|
||||||
|
|
||||||
# initialize the world
|
# 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('')
|
logger = logging.getLogger('')
|
||||||
if seed is None:
|
if seed is None:
|
||||||
random.seed(None)
|
random.seed(None)
|
||||||
|
@ -34,6 +34,19 @@ def main(args, seed=None):
|
||||||
world.seed = int(seed)
|
world.seed = int(seed)
|
||||||
random.seed(world.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_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.crystals_needed_for_gt = random.randint(0, 7) if args.crystals_gt == 'random' else int(args.crystals_gt)
|
||||||
world.open_pyramid = args.openpyramid
|
world.open_pyramid = args.openpyramid
|
||||||
|
@ -83,7 +96,7 @@ def main(args, seed=None):
|
||||||
logger.info('Placing Dungeon Items.')
|
logger.info('Placing Dungeon Items.')
|
||||||
|
|
||||||
shuffled_locations = None
|
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()
|
shuffled_locations = world.get_unfilled_locations()
|
||||||
random.shuffle(shuffled_locations)
|
random.shuffle(shuffled_locations)
|
||||||
fill_dungeons_restrictive(world, shuffled_locations)
|
fill_dungeons_restrictive(world, shuffled_locations)
|
||||||
|
@ -124,7 +137,7 @@ def main(args, seed=None):
|
||||||
|
|
||||||
player_names = parse_names_string(args.names)
|
player_names = parse_names_string(args.names)
|
||||||
outfileprefix = 'ER_%s_' % world.seed
|
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
|
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)
|
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):
|
def copy_world(world):
|
||||||
# ToDo: Not good yet
|
# 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.required_medallions = world.required_medallions.copy()
|
||||||
ret.swamp_patch_required = world.swamp_patch_required.copy()
|
ret.swamp_patch_required = world.swamp_patch_required.copy()
|
||||||
ret.ganon_at_pyramid = world.ganon_at_pyramid.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.difficulty_requirements = world.difficulty_requirements
|
||||||
ret.fix_fake_world = world.fix_fake_world
|
ret.fix_fake_world = world.fix_fake_world
|
||||||
ret.lamps_needed_for_dark_rooms = world.lamps_needed_for_dark_rooms
|
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_ganon = world.crystals_needed_for_ganon
|
||||||
ret.crystals_needed_for_gt = world.crystals_needed_for_gt
|
ret.crystals_needed_for_gt = world.crystals_needed_for_gt
|
||||||
|
|
||||||
|
@ -318,8 +335,7 @@ def create_playthrough(world):
|
||||||
sphere_candidates = list(prog_locations)
|
sphere_candidates = list(prog_locations)
|
||||||
logging.getLogger('').debug('Building up collection spheres.')
|
logging.getLogger('').debug('Building up collection spheres.')
|
||||||
while sphere_candidates:
|
while sphere_candidates:
|
||||||
if not world.keysanity:
|
state.sweep_for_events(key_only=True)
|
||||||
state.sweep_for_events(key_only=True)
|
|
||||||
|
|
||||||
sphere = []
|
sphere = []
|
||||||
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
# 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)
|
state = CollectionState(world)
|
||||||
collection_spheres = []
|
collection_spheres = []
|
||||||
while required_locations:
|
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))
|
sphere = list(filter(state.can_reach, required_locations))
|
||||||
|
|
||||||
|
|
|
@ -174,7 +174,7 @@ def fill_world(world, plando, text_patches):
|
||||||
item = ItemFactory(itemstr.strip(), 1)
|
item = ItemFactory(itemstr.strip(), 1)
|
||||||
if item is not None:
|
if item is not None:
|
||||||
world.push_item(location, item)
|
world.push_item(location, item)
|
||||||
if item.key:
|
if item.smallkey or item.bigkey:
|
||||||
location.event = True
|
location.event = True
|
||||||
elif '<=>' in line:
|
elif '<=>' in line:
|
||||||
entrance, exit = line.split('<=>', 1)
|
entrance, exit = line.split('<=>', 1)
|
||||||
|
|
23
README.md
23
README.md
|
@ -121,7 +121,7 @@ Does not invoke a timer.
|
||||||
### Display
|
### Display
|
||||||
|
|
||||||
Displays a timer on-screen but does not alter the item pool.
|
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
|
### 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.
|
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
|
These settings allow dungeon specific items to be distributed anywhere in the world and not just in their native dungeon.
|
||||||
in their native dungeon. Small Keys dropped by enemies or found in pots are not affected. The chest in southeast Skull Woods that
|
Small Keys dropped by enemies or found in pots are not affected. The chest in southeast Skull Woods that is traditionally
|
||||||
is traditionally a guaranteed Small Key still is. These items will be distributed according to the v26/balanced algorithm, but
|
a guaranteed Small Key still is. These items will be distributed according to the v26/balanced algorithm, but the rest
|
||||||
the rest of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell
|
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.
|
for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize.
|
||||||
|
|
||||||
## Retro
|
## 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)
|
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
|
--retro
|
||||||
|
@ -433,13 +433,6 @@ Enable Keysanity (default: False)
|
||||||
|
|
||||||
Enable Retro mode (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}]
|
--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
|
# Keys in their native dungeon should use the orignal item code for keys
|
||||||
if location.parent_region.dungeon:
|
if location.parent_region.dungeon:
|
||||||
dungeon = 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 is not None and dungeon.is_dungeon_item(location.item):
|
||||||
if location.item.type == "BigKey":
|
if location.item.bigkey:
|
||||||
itemid = 0x32
|
itemid = 0x32
|
||||||
if location.item.type == "SmallKey":
|
if location.item.smallkey:
|
||||||
itemid = 0x24
|
itemid = 0x24
|
||||||
if location.item and location.item.player != player:
|
if location.item and location.item.player != player:
|
||||||
if location.player_address is not None:
|
if location.player_address is not None:
|
||||||
|
@ -462,15 +462,15 @@ def patch_rom(world, player, rom):
|
||||||
|
|
||||||
# patch music
|
# patch music
|
||||||
music_addresses = dungeon_music_addresses[location.name]
|
music_addresses = dungeon_music_addresses[location.name]
|
||||||
if world.keysanity:
|
if world.mapshuffle:
|
||||||
music = random.choice([0x11, 0x16])
|
music = random.choice([0x11, 0x16])
|
||||||
else:
|
else:
|
||||||
music = 0x11 if 'Pendant' in location.item.name else 0x16
|
music = 0x11 if 'Pendant' in location.item.name else 0x16
|
||||||
for music_address in music_addresses:
|
for music_address in music_addresses:
|
||||||
rom.write_byte(music_address, music)
|
rom.write_byte(music_address, music)
|
||||||
|
|
||||||
if world.keysanity:
|
if world.mapshuffle:
|
||||||
rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too in keysanity mode
|
rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
|
||||||
|
|
||||||
# patch entrance/exits/holes
|
# patch entrance/exits/holes
|
||||||
for region in world.regions:
|
for region in world.regions:
|
||||||
|
@ -811,7 +811,7 @@ def patch_rom(world, player, rom):
|
||||||
ERtimeincrease = 10
|
ERtimeincrease = 10
|
||||||
else:
|
else:
|
||||||
ERtimeincrease = 20
|
ERtimeincrease = 20
|
||||||
if world.keysanity:
|
if world.keyshuffle or world.bigkeyshuffle or world.mapshuffle:
|
||||||
ERtimeincrease = ERtimeincrease + 15
|
ERtimeincrease = ERtimeincrease + 15
|
||||||
if world.clock_mode == 'off':
|
if world.clock_mode == 'off':
|
||||||
rom.write_bytes(0x180190, [0x00, 0x00, 0x00]) # turn off clock mode
|
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(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(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(0x18016A, 0x10 | ((0x01 if world.keyshuffle else 0x00)
|
||||||
rom.write_byte(0x18003B, 0x01 if world.keysanity else 0x00) # maps showing crystals on overworld
|
| (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
|
# compasses showing dungeon count
|
||||||
if world.clock_mode != 'off':
|
if world.clock_mode != 'off':
|
||||||
rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location
|
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
|
rom.write_byte(0x18003C, 0x01) # show on pickup
|
||||||
else:
|
else:
|
||||||
rom.write_byte(0x18003C, 0x00)
|
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
|
# Map reveals
|
||||||
reveal_bytes = {
|
reveal_bytes = {
|
||||||
|
@ -958,8 +964,8 @@ def patch_rom(world, player, rom):
|
||||||
return reveal_bytes.get(location.parent_region.dungeon.name, 0x0000)
|
return reveal_bytes.get(location.parent_region.dungeon.name, 0x0000)
|
||||||
return 0x0000
|
return 0x0000
|
||||||
|
|
||||||
write_int16(rom, 0x18017A, get_reveal_bytes('Green Pendant') if world.keysanity else 0x0000) # Sahasrahla 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.keysanity else 0x0000) # Bomb Shop 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(0x180172, 0x01 if world.retro else 0x00) # universal keys
|
||||||
rom.write_byte(0x180175, 0x01 if world.retro else 0x00) # rupee bow
|
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.
|
# 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()
|
items_to_hint = RelevantItems.copy()
|
||||||
if world.keysanity:
|
if world.keyshuffle:
|
||||||
items_to_hint.extend(KeysanityItems)
|
items_to_hint.extend(SmallKeys)
|
||||||
|
if world.bigkeyshuffle:
|
||||||
|
items_to_hint.extend(BigKeys)
|
||||||
random.shuffle(items_to_hint)
|
random.shuffle(items_to_hint)
|
||||||
hint_count = 5 if world.shuffle not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8
|
hint_count = 5 if world.shuffle not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8
|
||||||
while hint_count > 0:
|
while hint_count > 0:
|
||||||
|
@ -2022,28 +2030,30 @@ RelevantItems = ['Bow',
|
||||||
'Magic Upgrade (1/4)'
|
'Magic Upgrade (1/4)'
|
||||||
]
|
]
|
||||||
|
|
||||||
KeysanityItems = ['Small Key (Eastern Palace)',
|
SmallKeys = ['Small Key (Eastern Palace)',
|
||||||
'Big Key (Eastern Palace)',
|
'Small Key (Escape)',
|
||||||
'Small Key (Escape)',
|
'Small Key (Desert Palace)',
|
||||||
'Small Key (Desert Palace)',
|
'Small Key (Tower of Hera)',
|
||||||
'Big Key (Desert Palace)',
|
'Small Key (Agahnims Tower)',
|
||||||
'Small Key (Tower of Hera)',
|
'Small Key (Palace of Darkness)',
|
||||||
'Big Key (Tower of Hera)',
|
'Small Key (Thieves Town)',
|
||||||
'Small Key (Agahnims Tower)',
|
'Small Key (Swamp Palace)',
|
||||||
'Small Key (Palace of Darkness)',
|
'Small Key (Skull Woods)',
|
||||||
'Big Key (Palace of Darkness)',
|
'Small Key (Ice Palace)',
|
||||||
'Small Key (Thieves Town)',
|
'Small Key (Misery Mire)',
|
||||||
'Big Key (Thieves Town)',
|
'Small Key (Turtle Rock)',
|
||||||
'Small Key (Swamp Palace)',
|
'Small Key (Ganons Tower)',
|
||||||
'Big Key (Swamp Palace)',
|
]
|
||||||
'Small Key (Skull Woods)',
|
|
||||||
'Big Key (Skull Woods)',
|
BigKeys = ['Big Key (Eastern Palace)',
|
||||||
'Small Key (Ice Palace)',
|
'Big Key (Desert Palace)',
|
||||||
'Big Key (Ice Palace)',
|
'Big Key (Tower of Hera)',
|
||||||
'Small Key (Misery Mire)',
|
'Big Key (Palace of Darkness)',
|
||||||
'Big Key (Misery Mire)',
|
'Big Key (Thieves Town)',
|
||||||
'Small Key (Turtle Rock)',
|
'Big Key (Swamp Palace)',
|
||||||
'Big Key (Turtle Rock)',
|
'Big Key (Skull Woods)',
|
||||||
'Small Key (Ganons Tower)',
|
'Big Key (Ice Palace)',
|
||||||
'Big Key (Ganons Tower)'
|
'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',
|
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 - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left',
|
||||||
'Turtle Rock - Eye Bridge - Top Right']
|
'Turtle Rock - Eye Bridge - Top Right']
|
||||||
if not world.keysanity:
|
if not world.keyshuffle:
|
||||||
non_big_key_locations += ['Turtle Rock - Big Key Chest']
|
non_big_key_locations += ['Turtle Rock - Big Key Chest']
|
||||||
else:
|
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))
|
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',
|
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 - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left',
|
||||||
'Turtle Rock - Eye Bridge - Top Right']
|
'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']
|
non_big_key_locations += ['Turtle Rock - Big Key Chest', 'Turtle Rock - Chain Chomps']
|
||||||
|
|
||||||
# set big key restrictions
|
# set big key restrictions
|
||||||
|
|
Loading…
Reference in New Issue