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