Merge branch 'OverworldShuffleDev' into OverworldShuffle

This commit is contained in:
codemann8
2025-01-20 22:46:30 -06:00
29 changed files with 213 additions and 99 deletions
+1
View File
@@ -29,6 +29,7 @@ weights/
/Players/
/QUsb2Snes/
/output/
/enemizer/
base2current.json
+7 -2
View File
@@ -168,6 +168,7 @@ class World(object):
set_player_attr('potshuffle', False)
set_player_attr('pot_contents', None)
set_player_attr('pseudoboots', False)
set_player_attr('mirrorscroll', False)
set_player_attr('collection_rate', False)
set_player_attr('colorizepots', True)
set_player_attr('pot_pool', {})
@@ -177,6 +178,7 @@ class World(object):
set_player_attr('trap_door_mode', 'optional')
set_player_attr('key_logic_algorithm', 'partial')
set_player_attr('aga_randomness', True)
set_player_attr('money_balance', 100)
set_player_attr('shopsanity', False)
set_player_attr('mixed_travel', 'prevent')
@@ -3059,6 +3061,7 @@ class Spoiler(object):
'potshuffle': self.world.potshuffle,
'shopsanity': self.world.shopsanity,
'pseudoboots': self.world.pseudoboots,
'mirrorscroll': self.world.mirrorscroll,
'triforcegoal': self.world.treasure_hunt_count,
'triforcepool': self.world.treasure_hunt_total,
'race': self.world.settings.world_rep['meta']['race'],
@@ -3307,6 +3310,7 @@ class Spoiler(object):
outfile.write('Enemy Logic:'.ljust(line_width) + '%s\n' % self.metadata['any_enemy_logic'][player])
outfile.write('\n')
outfile.write('Pseudoboots:'.ljust(line_width) + '%s\n' % yn(self.metadata['pseudoboots'][player]))
outfile.write('Mirror Scroll:'.ljust(line_width) + '%s\n' % yn(self.metadata['mirrorscroll'][player]))
outfile.write('Hints:'.ljust(line_width) + '%s\n' % yn(self.metadata['hints'][player]))
outfile.write('Race:'.ljust(line_width) + '%s\n' % yn(self.world.settings.world_rep['meta']['race']))
@@ -3681,7 +3685,7 @@ overworld_map_mode = {'default': 0, 'compass': 1, 'map': 2}
trap_door_mode = {'vanilla': 0, 'optional': 1, 'boss': 2, 'oneway': 3}
key_logic_algo = {'dangerous': 0, 'partial': 1, 'strict': 2}
# byte 15: SSLL ??DD (skullwoods, linked_drops, 2 free bytes, door_type)
# byte 15: SSLL M?DD (skullwoods, linked_drops, mirrorscroll, 1 free byte, door_type)
skullwoods_mode = {'original': 0, 'restricted': 1, 'loose': 2, 'followlinked': 3}
linked_drops_mode = {'unset': 0, 'linked': 1, 'independent': 2}
door_type_mode = {'original': 0, 'big': 1, 'all': 2, 'chaos': 3}
@@ -3742,7 +3746,7 @@ class Settings(object):
| trap_door_mode[w.trap_door_mode[p]] << 3 | key_logic_algo[w.key_logic_algorithm[p]]),
(skullwoods_mode[w.skullwoods[p]] << 6 | linked_drops_mode[w.linked_drops[p]] << 4
| door_type_mode[w.door_type_mode[p]]),
| (0x8 if w.mirrorscroll[p] else 0) | door_type_mode[w.door_type_mode[p]]),
])
return base64.b64encode(code, "+-".encode()).decode()
@@ -3835,6 +3839,7 @@ class Settings(object):
if len(settings) > 15:
args.skullwoods[p] = r(skullwoods_mode)[(settings[15] & 0xc0) >> 6]
args.linked_drops[p] = r(linked_drops_mode)[(settings[15] & 0x30) >> 4]
args.mirrorscroll[p] = True if settings[15] & 0x8 else False
args.door_type_mode[p] = r(door_type_mode)[(settings[15] & 0x3)]
+4
View File
@@ -1,5 +1,9 @@
# Changelog
## 0.5.1.4
- Fixed incorrect ganon silvers hint
- \~Merged in DR v1.4.8.1~
## 0.5.1.3
- Fixed some minor issues with mystery multiworld
- Corrected some generation issues with Nearby dungeon item shuffles
+5 -2
View File
@@ -139,13 +139,14 @@ def parse_cli(argv, no_defaults=False):
'triforce_max_difference', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern',
'skullwoods', 'linked_drops',
'pseudoboots', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
'pseudoboots', 'mirrorscroll', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', 'shuffle_sfxinstruments',
'shuffle_songinstruments', 'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode',
'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops', 'any_enemy_logic', 'aga_randomness']:
'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops', 'any_enemy_logic', 'aga_randomness',
'money_balance']:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1:
setattr(ret, name, {1: value})
@@ -208,6 +209,7 @@ def parse_settings():
"overworld_map": "default",
"take_any": "none",
"pseudoboots": False,
"mirrorscroll": False,
"shuffleenemies": "none",
"shufflebosses": "none",
@@ -239,6 +241,7 @@ def parse_settings():
"mixed_travel": "prevent",
"standardize_palettes": "standardize",
'aga_randomness': True,
'money_balance': 100,
"triforce_pool": 0,
"triforce_goal": 0,
+4 -2
View File
@@ -3884,8 +3884,10 @@ logical_connections = [
('TR Crystaroller Chest to Middle Barrier - Blue', 'TR Crystaroller Middle'),
('TR Crystaroller Middle Ranged Crystal Exit', 'TR Crystaroller Middle'),
('TR Crystaroller Bottom Ranged Crystal Exit', 'TR Crystaroller Bottom'),
('TR Dark Ride Path', 'TR Dark Ride Ledges'),
('TR Dark Ride Ledges Path', 'TR Dark Ride'),
('TR Dark Ride Normal Path', 'TR Dark Ride South Platform'),
('TR Dark Ride Backward Path', 'TR Dark Ride North Platform'),
('TR Dark Ride Ledge Path', 'TR Dark Ride Ledges'),
('TR Dark Ride Return Path', 'TR Dark Ride South Platform'),
('TR Crystal Maze Start to Interior Barrier - Blue', 'TR Crystal Maze Interior'),
('TR Crystal Maze Start to Crystal', 'TR Crystal Maze Start - Crystal'),
('TR Crystal Maze Start Crystal Exit', 'TR Crystal Maze Start'),
+4 -2
View File
@@ -1061,8 +1061,10 @@ def create_doors(world, player):
create_door(player, 'TR Crystaroller Down Stairs', Sprl).dir(Dn, 0x04, 0, HTH).ss(A, 0x12, 0x80, True, True).small_key().pos(0),
create_door(player, 'TR Dark Ride Up Stairs', Sprl).dir(Up, 0xb5, 0, HTH).ss(A, 0x1b, 0x6c),
create_door(player, 'TR Dark Ride SW', Nrml).dir(So, 0xb5, Left, High).trap(0x4).pos(0).portal(Z, 0x22),
create_door(player, 'TR Dark Ride Path', Lgcl),
create_door(player, 'TR Dark Ride Ledges Path', Lgcl),
create_door(player, 'TR Dark Ride Normal Path', Lgcl),
create_door(player, 'TR Dark Ride Ledge Path', Lgcl),
create_door(player, 'TR Dark Ride Backward Path', Lgcl),
create_door(player, 'TR Dark Ride Return Path', Lgcl),
create_door(player, 'TR Dash Bridge NW', Nrml).dir(No, 0xc5, Left, High).pos(1),
create_door(player, 'TR Dash Bridge SW', Nrml).dir(So, 0xc5, Left, High).pos(2).portal(Z, 0x02),
create_door(player, 'TR Dash Bridge WS', Nrml).dir(We, 0xc5, Bot, High).small_key().pos(0),
+2 -1
View File
@@ -181,7 +181,8 @@ tr_regions = [
'TR Big View', 'TR Big Chest', 'TR Big Chest Entrance', 'TR Lazy Eyes', 'TR Dash Room', 'TR Tongue Pull',
'TR Rupees', 'TR Crystaroller Bottom', 'TR Crystaroller Middle', 'TR Crystaroller Top',
'TR Crystaroller Top - Crystal', 'TR Crystaroller Chest', 'TR Crystaroller Middle - Ranged Crystal',
'TR Crystaroller Bottom - Ranged Crystal', 'TR Dark Ride', 'TR Dark Ride Ledges', 'TR Dash Bridge', 'TR Eye Bridge',
'TR Crystaroller Bottom - Ranged Crystal', 'TR Dark Ride North Platform', 'TR Dark Ride South Platform',
'TR Dark Ride Ledges', 'TR Dash Bridge', 'TR Eye Bridge',
'TR Crystal Maze Start', 'TR Crystal Maze Start - Crystal', 'TR Crystal Maze Interior', 'TR Crystal Maze End',
'TR Crystal Maze End - Ranged Crystal', 'TR Final Abyss Balcony', 'TR Final Abyss Ledge', 'TR Boss', 'TR Boss Spoils',
'Turtle Rock Main Portal', 'Turtle Rock Lazy Eyes Portal', 'Turtle Rock Chest Portal',
+7 -6
View File
@@ -79,7 +79,6 @@ def fill_dungeons_restrictive(world, shuffled_locations):
break
for wix in reversed(to_remove):
del smalls[wix]
# remove 2 swamp locations from pool
hybrid_locations = []
to_remove = []
@@ -91,7 +90,6 @@ def fill_dungeons_restrictive(world, shuffled_locations):
break
for i in reversed(to_remove):
shuffled_locations.pop(i)
# place 2 HMG keys
hybrid_state_base = all_state_base.copy()
for x in bigs + smalls + prizes + others:
@@ -1122,14 +1120,16 @@ def balance_money_progression(world):
solvent = set()
insolvent = set()
for player in range(1, world.players+1):
if wallet[player] >= sphere_costs[player] >= 0:
modifier = world.money_balance[player]/100
if wallet[player] >= sphere_costs[player] * modifier >= 0:
solvent.add(player)
if sphere_costs[player] > 0 and sphere_costs[player] > wallet[player]:
if sphere_costs[player] > 0 and sphere_costs[player] * modifier > wallet[player]:
insolvent.add(player)
if len([p for p in solvent if len(locked_by_money[p]) > 0]) == 0:
if len(insolvent) > 0:
target_player = min(insolvent, key=lambda p: sphere_costs[p]-wallet[p])
difference = sphere_costs[target_player]-wallet[target_player]
target_modifier = world.money_balance[target_player]/100
difference = sphere_costs[target_player] * target_modifier - wallet[target_player]
logger.debug(f'Money balancing needed: Player {target_player} short {difference}')
else:
difference = 0
@@ -1169,7 +1169,8 @@ def balance_money_progression(world):
solvent.add(target_player)
# apply solvency
for player in solvent:
wallet[player] -= sphere_costs[player]
modifier = world.money_balance[player]/100
wallet[player] -= sphere_costs[player] * modifier
for location in locked_by_money[player]:
if isinstance(location, str) and location == 'Kiki':
kiki_paid[player] = True
+1 -1
View File
@@ -124,7 +124,7 @@ class InitialSram:
if startingstate.has('Beat Agahnim 1', player):
self.pre_open_lumberjack()
if world.mode[player] == 'standard':
self.set_progress_indicator(0x80)
self.set_progress_indicator(0x80) # todo: probably missing some code rom side for this
else:
self.set_progress_indicator(0x03)
+24 -36
View File
@@ -837,7 +837,8 @@ def customize_shops(world, player):
if retro_bow and item.name == 'Single Arrow':
price = 80
# randomize price
shop.add_inventory(idx, item.name, randomize_price(price), max_repeat, player=item.player)
price = final_price(loc, price, world, player)
shop.add_inventory(idx, item.name, price, max_repeat, player=item.player)
if item.name in cap_replacements and shop_name not in retro_shops and item.player == player:
possible_replacements.append((shop, idx, location, item))
# randomize shopkeeper
@@ -854,8 +855,10 @@ def customize_shops(world, player):
if len(choices) > 0:
shop, idx, loc, item = random.choice(choices)
upgrade = ItemFactory('Bomb Upgrade (+5)', player)
shop.add_inventory(idx, upgrade.name, randomize_price(upgrade.price), 6,
item.name, randomize_price(item.price), player=item.player)
up_price = final_price(loc.name, upgrade.price, world, player)
rep_price = final_price(loc.name, item.price, world, player)
shop.add_inventory(idx, upgrade.name, up_price, 6,
item.name, rep_price, player=item.player)
loc.item = upgrade
upgrade.location = loc
if not found_arrow_upgrade and len(possible_replacements) > 0:
@@ -866,8 +869,10 @@ def customize_shops(world, player):
if len(choices) > 0:
shop, idx, loc, item = random.choice(choices)
upgrade = ItemFactory('Arrow Upgrade (+5)', player)
shop.add_inventory(idx, upgrade.name, randomize_price(upgrade.price), 6,
item.name, randomize_price(item.price), player=item.player)
up_price = final_price(loc.name, upgrade.price, world, player)
rep_price = final_price(loc.name, item.price, world, player)
shop.add_inventory(idx, upgrade.name, up_price, 6,
item.name, rep_price, player=item.player)
loc.item = upgrade
upgrade.location = loc
change_shop_items_to_rupees(world, player, shops_to_customize)
@@ -875,6 +880,15 @@ def customize_shops(world, player):
check_hints(world, player)
def final_price(location, price, world, player):
if world.customizer and world.customizer.get_prices(player):
custom_prices = world.customizer.get_prices(player)
if location in custom_prices:
# todo: validate valid price
return custom_prices[location]
return randomize_price(price)
def randomize_price(price):
half_price = price // 2
max_price = price - half_price
@@ -914,12 +928,16 @@ def balance_prices(world, player):
shop_locations = []
for shop, loc_list in shop_to_location_table.items():
for loc in loc_list:
if world.customizer and world.customizer.get_prices(player) and loc in world.customizer.get_prices(player):
needed_money += world.customizer.get_prices(player)[loc]
continue # considered a fixed price and shouldn't be altered
loc = world.get_location(loc, player)
shop_locations.append(loc)
slot = shop_to_location_table[loc.parent_region.name].index(loc.name)
needed_money += loc.parent_region.shop.inventory[slot]['price']
target = available_money - needed_money
modifier = world.money_balance[player]/100
target = available_money - needed_money * modifier
# remove the first set of shops from consideration (or used them for discounting)
state, done = CollectionState(world), False
unchecked_locations = world.get_locations().copy()
@@ -1255,32 +1273,11 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer
assert loc not in placed_items
placed_items[loc] = item
# Correct for insanely oversized item counts and take initial steps to handle undersized pools.
# Bow to Silver Arrows Upgrade, including Generic Keys & Rupoors
for x in [*range(0, 69)]:
key = CONST.CUSTOMITEMS[x]
if customitemarray[key] > total_items_to_place:
customitemarray[key] = total_items_to_place
# Triforce
if customitemarray["triforce"] > total_items_to_place:
customitemarray["triforce"] = total_items_to_place
# Triforce Pieces
if goal in ['triforcehunt', 'trinity', 'ganonhunt']:
g, t = set_default_triforce(goal, customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"])
customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"] = g, t
itemtotal = 0
# Bow to Silver Arrows Upgrade, including Generic Keys & Rupoors
for x in [*range(0, 66 + 1), 68, 69]:
key = CONST.CUSTOMITEMS[x]
itemtotal = itemtotal + customitemarray[key]
# Triforce
itemtotal = itemtotal + customitemarray["triforce"]
# Generic Keys
itemtotal = itemtotal + customitemarray["generickeys"]
customitems = [
"Bow", "Silver Arrows", "Blue Boomerang", "Red Boomerang", "Hookshot", "Mushroom", "Magic Powder",
"Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake",
@@ -1322,7 +1319,6 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer
and (goal in ['triforcehunt', 'trinity', 'ganonhunt']) and (customitemarray["triforce"] == 0)):
extrapieces = treasure_hunt_count - customitemarray["triforcepieces"]
pool.extend(['Triforce Piece'] * extrapieces)
itemtotal = itemtotal + extrapieces
if timer in ['display', 'timed', 'timed-countdown']:
clock_mode = 'countdown' if timer == 'timed-countdown' else 'stopwatch'
@@ -1333,7 +1329,6 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer
if goal in ['pedestal', 'trinity']:
place_item('Master Sword Pedestal', 'Triforce')
itemtotal = itemtotal + 1
if mode == 'standard':
if world.keyshuffle[player] == 'universal':
@@ -1350,13 +1345,6 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer
pool.extend(['Magic Mirror'] * customitemarray["mirror"])
pool.extend(['Moon Pearl'] * customitemarray["pearl"])
if world.keyshuffle[player] == 'universal':
itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in Retro Mode
if itemtotal < total_items_to_place:
nothings = total_items_to_place - itemtotal
# print("Placing " + str(nothings) + " Nothings")
pool.extend(['Nothing'] * nothings)
start_inventory = [x for x in world.precollected_items if x.player == player]
if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic'] and all(x.name != 'Pegasus Boots' for x in start_inventory):
precollected_items.append('Pegasus Boots')
+7 -1
View File
@@ -814,7 +814,13 @@ def key_wasted(new_door, old_door, old_counter, new_counter, key_layout, world,
def find_next_counter(new_door, old_counter, key_layout, prize_flag=None):
proposed_doors = {**old_counter.open_doors, **dict.fromkeys([new_door, new_door.dest])}
prop_doors = next((item_or_tuple for item_or_tuple in key_layout.proposal
if new_door == item_or_tuple or (isinstance(item_or_tuple, tuple) and new_door in item_or_tuple)), None)
if prop_doors:
prop_doors = list(prop_doors) if isinstance(prop_doors, tuple) else [prop_doors]
proposed_doors = {**old_counter.open_doors, **dict.fromkeys(prop_doors)}
else:
proposed_doors = {**old_counter.open_doors}
bk_open = old_counter.big_key_opened or new_door.bigKey
prize_flag = prize_flag if prize_flag else old_counter.prize_doors_opened
return find_counter(proposed_doors, bk_open, key_layout, prize_flag)
+17 -1
View File
@@ -40,7 +40,7 @@ from source.enemizer.DamageTables import DamageTable
from source.enemizer.Enemizer import randomize_enemies
from source.rom.DataTables import init_data_tables
version_number = '1.4.7.2'
version_number = '1.4.8.1'
version_branch = '-u'
__version__ = f'{version_number}{version_branch}'
@@ -501,12 +501,14 @@ def init_world(args, fish):
world.skullwoods = args.skullwoods.copy()
world.linked_drops = args.linked_drops.copy()
world.pseudoboots = args.pseudoboots.copy()
world.mirrorscroll = args.mirrorscroll.copy()
world.overworld_map = args.overworld_map.copy()
world.take_any = args.take_any.copy()
world.restrict_boss_items = args.restrict_boss_items.copy()
world.collection_rate = args.collection_rate.copy()
world.colorizepots = args.colorizepots.copy()
world.aga_randomness = args.aga_randomness.copy()
world.money_balance = args.money_balance.copy()
return world
@@ -589,7 +591,13 @@ def copy_world(world):
ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy()
ret.open_pyramid = world.open_pyramid.copy()
ret.shufflelinks = world.shufflelinks.copy()
ret.shuffletavern = world.shuffletavern.copy()
ret.shuffle_ganon = world.shuffle_ganon.copy()
ret.skullwoods = world.skullwoods.copy()
ret.linked_drops = world.linked_drops.copy()
ret.pseudoboots = world.pseudoboots.copy()
ret.mirrorscroll = world.mirrorscroll.copy()
ret.overworld_map = world.overworld_map.copy()
ret.take_any = world.take_any.copy()
ret.boss_shuffle = world.boss_shuffle.copy()
ret.enemy_shuffle = world.enemy_shuffle.copy()
@@ -604,6 +612,7 @@ def copy_world(world):
ret.trap_door_mode = world.trap_door_mode.copy()
ret.key_logic_algorithm = world.key_logic_algorithm.copy()
ret.aga_randomness = world.aga_randomness.copy()
ret.money_balance = world.money_balance.copy()
ret.experimental = world.experimental.copy()
ret.shopsanity = world.shopsanity.copy()
ret.dropshuffle = world.dropshuffle.copy()
@@ -802,7 +811,13 @@ def copy_world_premature(world, player):
ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy()
ret.open_pyramid = world.open_pyramid.copy()
ret.shufflelinks = world.shufflelinks.copy()
ret.shuffletavern = world.shuffletavern.copy()
ret.shuffle_ganon = world.shuffle_ganon.copy()
ret.skullwoods = world.skullwoods.copy()
ret.linked_drops = world.linked_drops.copy()
ret.pseudoboots = world.pseudoboots.copy()
ret.mirrorscroll = world.mirrorscroll.copy()
ret.overworld_map = world.overworld_map.copy()
ret.take_any = world.take_any.copy()
ret.boss_shuffle = world.boss_shuffle.copy()
ret.enemy_shuffle = world.enemy_shuffle.copy()
@@ -817,6 +832,7 @@ def copy_world_premature(world, player):
ret.trap_door_mode = world.trap_door_mode.copy()
ret.key_logic_algorithm = world.key_logic_algorithm.copy()
ret.aga_randomness = world.aga_randomness.copy()
ret.money_balance = world.money_balance.copy()
ret.experimental = world.experimental.copy()
ret.shopsanity = world.shopsanity.copy()
ret.dropshuffle = world.dropshuffle.copy()
+1 -1
View File
@@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType
from OverworldGlitchRules import create_owg_connections
from Utils import bidict
version_number = '0.5.1.3'
version_number = '0.5.1.4'
# branch indicator is intentionally different across branches
version_branch = ''
+14 -16
View File
@@ -141,20 +141,18 @@ These are now independent of retro mode and have three options: None, Random, an
# Patch Notes
* 1.4.7.2
- Fixed an issue with shuffle_ganon/fix_gtower_exit causing a generation failure
* 1.4.8.1
- Fixed broken doors generation
- Fixed bomb/arrow upgrade ignoring custom pricing
- Extended `money_balance` to apply to price balancing for non-custom shops.
* 1.4.8
- New option: Mirror Scroll - to add the item to the starting inventory in non-doors modes (Thanks Telethar!)
- Customizer: Ability to customize shop prices and control money balancing. `money_balance` is a percentage betwen 0 and 100 that attempts to ensure you have that much percentage of money available for purchases. (100 is default, 0 essentially ignores money considerations)
- Fixed a key logic bug with decoupled doors when a big key door leads to a small key door (the small key door was missing appropriate logic)
- Fixed an ER bug where Bonk Fairy could be used for a mandatory connector in standard mode (boots could allow escape to be skipped)
- Fixed an issue with flute activation in rain mode. (thanks Codemann!)
- Fixed an issue with enemies in TR Dark Ride room not requiring Somaria. (Refactored the room for decoupled logic better)
- More HMG fixes by Muffins
* 1.4.7.1
- Fixed an issue with the repaired "beemizer" setting not being backwards compatible
* 1.4.7
- Fixed generation error with Big Key in starting inventory (thanks Cody!)
- HMG/NL logic fixes by Muffins
- Enemizer: Disabled Walking Zora in the UW due to crash with Swamola (they ignore a lot of collison anyway)
- Enemizer: Fixed an issue with enemizer bush sprites
- Enemizer: Banned new Mimics from being the randomized bush sprite due to crash
- "Beatable" or "accessibility: none" can now use randomized trap doors to seal off entire parts of dungeons (was intended, bug prevented the logic skip)
- Logic error with enemizer and standard should use new enemy logic rules
- Fixed a bug with the inconsistent treatment of the beemizer setting
- Fixed an issue with returning Blacksmith in Simple shuffle (when blacksmith is at Link's House)
- Fixed an issue with dark sanctuary spawn at tavern north door (thanks Codemann!)
- Various enemy bans for the last few months
- Fixed an issue with multi-player HMG
- Fixed an issue limiting number of items specified in the item pool on the GUI
- Minor documentation fixes (thanks Codemann!)
+3 -2
View File
@@ -907,8 +907,9 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'TR Crystaroller Chest', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['TR Crystaroller Chest to Middle Barrier - Blue']),
create_dungeon_region(player, 'TR Crystaroller Middle - Ranged Crystal', 'Turtle Rock', None, ['TR Crystaroller Middle Ranged Crystal Exit']),
create_dungeon_region(player, 'TR Crystaroller Bottom - Ranged Crystal', 'Turtle Rock', None, ['TR Crystaroller Bottom Ranged Crystal Exit']),
create_dungeon_region(player, 'TR Dark Ride', 'Turtle Rock', None, ['TR Dark Ride Up Stairs', 'TR Dark Ride SW', 'TR Dark Ride Path']),
create_dungeon_region(player, 'TR Dark Ride Ledges', 'Turtle Rock', None, ['TR Dark Ride Ledges Path']),
create_dungeon_region(player, 'TR Dark Ride North Platform', 'Turtle Rock', None, ['TR Dark Ride Up Stairs', 'TR Dark Ride Normal Path', 'TR Dark Ride Ledge Path']),
create_dungeon_region(player, 'TR Dark Ride South Platform', 'Turtle Rock', None, ['TR Dark Ride SW', 'TR Dark Ride Backward Path']),
create_dungeon_region(player, 'TR Dark Ride Ledges', 'Turtle Rock', None, ['TR Dark Ride Return Path']),
create_dungeon_region(player, 'TR Dash Bridge', 'Turtle Rock', None, ['TR Dash Bridge NW', 'TR Dash Bridge SW', 'TR Dash Bridge WS']),
create_dungeon_region(player, 'TR Eye Bridge', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right',
'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'],
+6 -2
View File
@@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'a84f59e5e76492f6a5693835823e6292'
RANDOMIZERBASEHASH = 'b439d99c7f41914eedb9f4097ecc8cc7'
class JsonRom(object):
@@ -690,7 +690,11 @@ def patch_rom(world, rom, player, team, is_mystery=False):
rom.write_byte(0x157D0, exit.target)
# setup dr option flags based on experimental, etc.
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal
dr_flags = DROptions.NoOptions
if world.mirrorscroll[player] or world.doorShuffle[player] != 'vanilla':
dr_flags |= DROptions.Town_Portal
if world.doorShuffle[player] == 'vanilla':
dr_flags |= DROptions.Eternal_Mini_Bosses
if world.doorShuffle[player] not in ['vanilla', 'basic']:
dr_flags |= DROptions.Map_Info
if ((world.collection_rate[player] or world.goal[player] == 'completionist')
+11 -9
View File
@@ -601,10 +601,11 @@ def global_rules(world, player):
lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has_Boots(player))
set_rule(world.get_entrance('TR Dark Ride Up Stairs', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Dark Ride SW', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Dark Ride Path', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Dark Ride Ledges Path', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Dark Ride SW', player), lambda state: state.has('Cane of Somaria', player)) # due to needing the switch
set_rule(world.get_entrance('TR Dark Ride Normal Path', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Dark Ride Backward Path', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Dark Ride Return Path', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Dark Ride Ledge Path', player), lambda state: state.has('Cane of Somaria', player))
for location in world.get_region('TR Dark Ride Ledges', player).locations:
set_rule(location, lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Final Abyss Balcony Path', player), lambda state: state.has('Cane of Somaria', player))
@@ -1385,8 +1386,9 @@ def add_conditional_lamps(world, player):
add_lamp_requirement(spot, player)
dark_rooms = {
'TR Dark Ride': {'sewer': False, 'entrances': ['TR Dark Ride Up Stairs', 'TR Dark Ride SW', 'TR Dark Ride Path'], 'locations': []},
'TR Dark Ride Ledges': {'sewer': False, 'entrances': ['TR Dark Ride Ledges Path'], 'locations': []},
'TR Dark Ride North Platform': {'sewer': False, 'entrances': ['TR Dark Ride Up Stairs', 'TR Dark Ride Normal Path', 'TR Dark Ride Ledge Path'], 'locations': []},
'TR Dark Ride South Platform': {'sewer': False, 'entrances': ['TR Dark Ride SW', 'TR Dark Ride Backward Path'], 'locations': []},
'TR Dark Ride Ledges': {'sewer': False, 'entrances': ['TR Dark Ride Return Path'], 'locations': []},
'Mire Dark Shooters': {'sewer': False, 'entrances': ['Mire Dark Shooters Up Stairs', 'Mire Dark Shooters SW', 'Mire Dark Shooters SE'], 'locations': []},
'Mire Key Rupees': {'sewer': False, 'entrances': ['Mire Key Rupees NE'], 'locations': []},
'Mire Block X': {'sewer': False, 'entrances': ['Mire Block X NW', 'Mire Block X WS'], 'locations': []},
@@ -1920,7 +1922,7 @@ bunny_revivable_entrances = {
"Ice Many Pots", "Mire South Fish", "Mire Right Bridge", "Mire Left Bridge",
"TR Boss", "Eastern Hint Tile Blocked Path", "Thieves Spike Switch",
"Thieves Boss", "Mire Spike Barrier", "Mire Cross", "Mire Hidden Shooters",
"Mire Spikes", "TR Final Abyss Balcony", "TR Dark Ride", "TR Pokey 1", "TR Tile Room",
"Mire Spikes", "TR Final Abyss Balcony", "TR Dark Ride South Platform", "TR Pokey 1", "TR Tile Room",
"TR Roller Room", "Eastern Cannonball", "Thieves Hallway", "Ice Switch Room",
"Mire Tile Room", "Mire Conveyor Crystal", "Mire Hub", "TR Dash Bridge",
"TR Hub", "Eastern Boss", "Eastern Lobby", "Thieves Ambush",
@@ -1988,8 +1990,8 @@ bunny_impassible_doors = {
'TR Lobby Ledge Gap', 'TR Hub SW', 'TR Hub SE', 'TR Hub ES', 'TR Hub EN', 'TR Hub NW', 'TR Hub NE', 'TR Hub Path',
'TR Hub Ledges Path', 'TR Torches NW', 'TR Pokey 2 Bottom to Top Barrier - Blue',
'TR Pokey 2 Top to Bottom Barrier - Blue', 'TR Twin Pokeys SW', 'TR Twin Pokeys EN', 'TR Big Chest Gap',
'TR Big Chest Entrance Gap', 'TR Lazy Eyes ES', 'TR Tongue Pull WS', 'TR Tongue Pull NE', 'TR Dark Ride Up Stairs',
'TR Dark Ride SW', 'TR Dark Ride Path', 'TR Dark Ride Ledges Path',
'TR Big Chest Entrance Gap', 'TR Lazy Eyes ES', 'TR Tongue Pull WS', 'TR Tongue Pull NE', 'TR Dark Ride SW', # due to needing the switch
'TR Dark Ride Normal Path', 'TR Dark Ride Ledge Path', 'TR Dark Ride Backward Path', 'TR Dark Ride Return Path',
'TR Crystal Maze Start to Interior Barrier - Blue', 'TR Crystal Maze End to Interior Barrier - Blue',
'TR Final Abyss Balcony Path', 'TR Final Abyss Ledge Path', 'GT Hope Room EN', 'GT Blocked Stairs Block Path',
'GT Bob\'s Room Hole', 'GT Speed Torch SE', 'GT Speed Torch South Path', 'GT Speed Torch North Path',
Binary file not shown.
+22
View File
@@ -46,6 +46,13 @@ Player 1's settings will be determined by rolling the mystery weights and player
Start inventory is not supported here. It has a separate section.
#### Extra Settings
Some settings are intended to only be accessed using the customizer:
* `aga_randomness` setting this to false, turns off blue balls and ganon warp randomness
* `money_balance` this is a percent (0-100). For numbers less than 100, both price balancing and money balancing will only attempt to ensure the player has access to only a percentage of the required funds. When 0, this should effectively disable money balancing. Grinding for rupees may be necessary whenever this is less than 100.
###### Not Yet Implemented
Rom/Adjust flags like sprite, quickswap are not outputing with the print_template_yaml or print_custom_yaml settings
@@ -310,3 +317,18 @@ drops:
```
Prize packs expect a list of eight items each (anything not specified will be whatever randomization would have normally occurred). The special drops expect a single item. Packs 1 through 7 are supported. Prize pack 0 is not customizable.
## prices
This must be defined by player. You may have the prices of items in shops defined using the following
```
prices:
1:
Capacity Upgrade - Left: 100
Capacity Upgrade - Right: 200
Dark Death Mountain Shop - Left: 100
Dark Death Mountain Shop - Middle: 150
Dark Death Mountain Shop - Right: 300
Dark Lake Hylia Shop - Left: 200
```
+4
View File
@@ -166,6 +166,10 @@ bosses:
Palace of Darkness: Arrghus
Thieves Town: Blind
Ganons Tower (top): Vitreous
prices:
1:
Capacity Upgrade - Left: 5
Capacity Upgrade - Right: 216
start_inventory:
1:
- Pegasus Boots
+5
View File
@@ -553,6 +553,10 @@
"action": "store_true",
"type": "bool"
},
"mirrorscroll": {
"action": "store_true",
"type": "bool"
},
"calc_playthrough": {
"action": "store_false",
"type": "bool"
@@ -623,6 +627,7 @@
"action": "store_false",
"type": "bool"
},
"money_balance": {},
"settingsonload": {
"choices": [
"default",
+1
View File
@@ -394,6 +394,7 @@
],
"collection_rate": [ "Display collection rate (default: %(default)s)" ],
"pseudoboots": [ " Start with pseudo boots that allow dashing but no item checks (default: %(default)s)"],
"mirrorscroll": [ " Players starts with mirror scroll that allows mirror in dungeons but not overworld (default: %(default)s"],
"bombbag": ["Start with 0 bomb capacity. Two capacity upgrades (+10) are added to the pool (default: %(default)s)" ],
"any_enemy_logic": [
"How to handle potential traversal between dungeon in Crossed door shuffle",
+1
View File
@@ -287,6 +287,7 @@
"randomizer.item.retro": "Retro mode",
"randomizer.item.pseudoboots": "Pseudoboots",
"randomizer.item.collection_rate": "Display Collection Rate",
"randomizer.item.mirrorscroll": "Mirror Scroll",
"randomizer.item.worldstate": "World State",
"randomizer.item.worldstate.standard": "Standard",
@@ -2,6 +2,7 @@
"checkboxes": {
"hints": { "type": "checkbox" },
"pseudoboots": { "type": "checkbox" },
"mirrorscroll": { "type": "checkbox" },
"collection_rate": {"type": "checkbox"},
"race": { "type": "checkbox" }
},
+44
View File
@@ -21,11 +21,20 @@ class CustomSettings(object):
self.relative_dir = None
self.world_rep = {}
self.player_range = None
self.player_map = {} # player number to name
def load_yaml(self, file):
self.file_source = load_yaml(file)
head, filename = os.path.split(file)
self.relative_dir = head
if 'version' in self.file_source and self.file_source['version'].startswith('2'):
player_number = 1
for key in self.file_source.keys():
if key in ['meta', 'version']:
continue
else:
self.player_map[player_number] = key
player_number += 1
def determine_seed(self, default_seed):
if 'meta' in self.file_source:
@@ -179,6 +188,7 @@ class CustomSettings(object):
args.restrict_boss_items[p] = get_setting(settings['restrict_boss_items'], args.restrict_boss_items[p])
args.overworld_map[p] = get_setting(settings['overworld_map'], args.overworld_map[p])
args.pseudoboots[p] = get_setting(settings['pseudoboots'], args.pseudoboots[p])
args.mirrorscroll[p] = get_setting(settings['mirrorscroll'], args.mirrorscroll[p])
args.triforce_goal[p] = get_setting(settings['triforce_goal'], args.triforce_goal[p])
args.triforce_pool[p] = get_setting(settings['triforce_pool'], args.triforce_pool[p])
args.triforce_goal_min[p] = get_setting(settings['triforce_goal_min'], args.triforce_goal_min[p])
@@ -189,6 +199,7 @@ class CustomSettings(object):
args.triforce_max_difference[p] = get_setting(settings['triforce_max_difference'], args.triforce_max_difference[p])
args.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p])
args.aga_randomness[p] = get_setting(settings['aga_randomness'], args.aga_randomness[p])
args.money_balance[p] = get_setting(settings['money_balance'], args.money_balance[p])
# mystery usage
args.usestartinventory[p] = get_setting(settings['usestartinventory'], args.usestartinventory[p])
@@ -219,6 +230,9 @@ class CustomSettings(object):
return self.file_source['placements']
return None
def get_prices(self, player):
return self.get_attribute_by_player_composite('prices', player)
def get_advanced_placements(self):
if 'advanced_placements' in self.file_source:
return self.file_source['advanced_placements']
@@ -284,6 +298,34 @@ class CustomSettings(object):
return self.file_source['enemies']
return None
def get_attribute_by_player_composite(self, attribute, player):
attempt = self.get_attribute_by_player_new(attribute, player)
if attempt is not None:
return attempt
attempt = self.get_attribute_by_player(attribute, player)
return attempt
def get_attribute_by_player(self, attribute, player):
if attribute in self.file_source:
if player in self.file_source[attribute]:
return self.file_source[attribute][player]
return None
def get_attribute_by_player_new(self, attribute, player):
player_id = self.get_player_id(player)
if player_id is not None:
if attribute in self.file_source[player_id]:
return self.file_source[player_id][attribute]
return None
def get_player_id(self, player):
if player in self.file_source:
return player
if player in self.player_map and self.player_map[player] in self.file_source:
return self.player_map[player]
return None
def create_from_world(self, world, settings):
self.player_range = range(1, world.players + 1)
settings_dict, meta_dict = {}, {}
@@ -355,10 +397,12 @@ class CustomSettings(object):
settings_dict[p]['linked_drops'] = world.linked_drops[p]
settings_dict[p]['overworld_map'] = world.overworld_map[p]
settings_dict[p]['pseudoboots'] = world.pseudoboots[p]
settings_dict[p]['mirrorscroll'] = world.mirrorscroll[p]
settings_dict[p]['triforce_goal'] = world.treasure_hunt_count[p]
settings_dict[p]['triforce_pool'] = world.treasure_hunt_total[p]
settings_dict[p]['beemizer'] = world.beemizer[p]
settings_dict[p]['aga_randomness'] = world.aga_randomness[p]
settings_dict[p]['money_balance'] = world.money_balance[p]
if world.precollected_items:
start_inv[p] = []
for item in world.precollected_items:
+1
View File
@@ -57,6 +57,7 @@ SETTINGSTOPROCESS = {
"item": {
"hints": "hints",
"pseudoboots": "pseudoboots",
"mirrorscroll": "mirrorscroll",
'collection_rate': 'collection_rate',
"race": "race",
+3 -3
View File
@@ -1657,9 +1657,9 @@ def init_vanilla_sprites():
create_sprite(0x00b3, EnemySprite.Beamos, 0x00, 0, 0x06, 0x18, 'Mire Spikes')
create_sprite(0x00b3, EnemySprite.FourWayShooter, 0x00, 0, 0x0a, 0x1a, 'Mire Spikes')
create_sprite(0x00b3, EnemySprite.Stalfos, 0x00, 0, 0x07, 0x1c, 'Mire Spikes')
create_sprite(0x00b5, EnemySprite.FirebarCW, 0x00, 0, 0x16, 0x0a, 'TR Dark Ride')
create_sprite(0x00b5, EnemySprite.FirebarCW, 0x00, 0, 0x09, 0x0f, 'TR Dark Ride')
create_sprite(0x00b5, EnemySprite.FirebarCW, 0x00, 0, 0x16, 0x16, 'TR Dark Ride')
create_sprite(0x00b5, EnemySprite.FirebarCW, 0x00, 0, 0x16, 0x0a, 'TR Dark Ride Ledges')
create_sprite(0x00b5, EnemySprite.FirebarCW, 0x00, 0, 0x09, 0x0f, 'TR Dark Ride Ledges')
create_sprite(0x00b5, EnemySprite.FirebarCW, 0x00, 0, 0x16, 0x16, 'TR Dark Ride Ledges')
create_sprite(0x00b6, EnemySprite.Chainchomp, 0x00, 0, 0x06, 0x07, 'TR Chain Chomps Top')
create_sprite(0x00b6, EnemySprite.Chainchomp, 0x00, 0, 0x0a, 0x07, 'TR Chain Chomps Top')
create_sprite(0x00b6, EnemySprite.CrystalSwitch, 0x00, 0, 0x03, 0x04)
+11 -11
View File
@@ -275,7 +275,7 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
rem_entrances.update(lw_entrances)
rem_entrances.update(dw_entrances)
else:
# cross world mandantory
# cross world mandatory
entrance_list = list(entrances)
if avail.swapped:
forbidden = [e for e in Forbidden_Swap_Entrances if e in entrance_list]
@@ -321,16 +321,13 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
avail.decoupled_exits.remove(bomb_shop)
rem_exits.remove(bomb_shop)
def bonk_fairy_exception(x): # (Bonk Fairy not eligible in standard)
return not avail.is_standard() or x != 'Bonk Fairy (Light)'
# old man S&Q cave
if not cross_world and not avail.assumed_loose_caves:
#TODO: Add Swapped ER support for this
# OM Cave entrance in lw/dw if cross_world off
if 'Old Man Cave Exit (West)' in rem_exits:
world_limiter = DW_Entrances if avail.inverted else LW_Entrances
om_cave_options = sorted([x for x in rem_entrances if x in world_limiter and bonk_fairy_exception(x)])
om_cave_options = sorted([x for x in rem_entrances if x in world_limiter and bonk_fairy_exception(avail, x)])
om_cave_choice = random.choice(om_cave_options)
if not avail.coupled:
connect_exit('Old Man Cave Exit (West)', om_cave_choice, avail)
@@ -344,7 +341,7 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
for ext in om_house:
if ext in rem_exits:
world_limiter = DW_Entrances if avail.inverted else LW_Entrances
om_house_options = [x for x in rem_entrances if x in world_limiter and bonk_fairy_exception(x)]
om_house_options = [x for x in rem_entrances if x in world_limiter and bonk_fairy_exception(avail, x)]
om_house_choice = random.choice(om_house_options)
if not avail.coupled:
connect_exit(ext, om_house_choice, avail)
@@ -361,7 +358,7 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
lw_entrances, dw_entrances = [], []
left = sorted(rem_entrances)
for x in left:
if bonk_fairy_exception(x):
if bonk_fairy_exception(avail, x):
lw_entrances.append(x) if x in LW_Entrances else dw_entrances.append(x)
do_same_world_connectors(lw_entrances, dw_entrances, multi_exit_caves, avail)
if avail.world.doorShuffle[avail.player] != 'vanilla':
@@ -371,7 +368,7 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
unused_entrances.update(lw_entrances)
unused_entrances.update(dw_entrances)
else:
entrance_list = sorted([x for x in rem_entrances if bonk_fairy_exception(x)])
entrance_list = sorted([x for x in rem_entrances if bonk_fairy_exception(avail, x)])
do_cross_world_connectors(entrance_list, multi_exit_caves, avail)
unused_entrances.update(entrance_list)
@@ -1527,6 +1524,8 @@ def do_vanilla_connect(pool_def, avail):
avail.entrances.remove(entrance)
avail.exits.remove(target)
def bonk_fairy_exception(avail, x): # (Bonk Fairy not eligible in standard)
return not avail.is_standard() or x != 'Bonk Fairy (Light)'
def do_mandatory_connections(avail, entrances, cave_options, must_exit):
if len(must_exit) == 0:
@@ -1614,7 +1613,8 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
if len(cave) == 2:
entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit]
and e not in invalid_cave_connections[tuple(cave)] and e not in must_exit
and (not avail.swapped or rnd_cave[0] != avail.combine_map[e]))
and (not avail.swapped or rnd_cave[0] != avail.combine_map[e])
and bonk_fairy_exception(avail, e))
entrances.remove(entrance)
connect_two_way(entrance, rnd_cave[0], avail)
if avail.swapped and avail.combine_map[entrance] != rnd_cave[0]:
@@ -1639,7 +1639,7 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
cave_entrances.append(entrance)
else:
entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in must_exit
and (not avail.swapped or cave_exit != avail.combine_map[e]))
and (not avail.swapped or cave_exit != avail.combine_map[e]) and bonk_fairy_exception(avail, e))
cave_entrances.append(entrance)
entrances.remove(entrance)
connect_two_way(entrance, cave_exit, avail)
@@ -1670,7 +1670,7 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
continue
else:
entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)]
and (not avail.swapped or cave_exit != avail.combine_map[e]))
and (not avail.swapped or cave_exit != avail.combine_map[e]) and bonk_fairy_exception(avail, e))
invalid_cave_connections[tuple(cave)] = set()
entrances.remove(entrance)
connect_two_way(entrance, cave_exit, avail)
+1
View File
@@ -152,6 +152,7 @@ def roll_settings(weights):
ret.dungeon_counters = 'pickup' if ret.door_shuffle != 'vanilla' or ret.compassshuffle != 'none' else 'off'
ret.pseudoboots = get_choice_bool('pseudoboots')
ret.mirrorscroll = get_choice_bool('mirrorscroll')
ret.shopsanity = get_choice_bool('shopsanity')
keydropshuffle = get_choice_bool('keydropshuffle')
ret.dropshuffle = get_choice('dropshuffle') if 'dropshuffle' in weights else 'none'