Merge branch 'Ambrosia' into DoorDevVolatile

# Conflicts:
#	BaseClasses.py
#	CLI.py
#	Main.py
This commit is contained in:
aerinon
2021-10-29 15:26:51 -06:00
15 changed files with 200 additions and 73 deletions

View File

@@ -115,6 +115,7 @@ class World(object):
set_player_attr('compassshuffle', False)
set_player_attr('keyshuffle', False)
set_player_attr('bigkeyshuffle', False)
set_player_attr('restrict_boss_items', 'none')
set_player_attr('bombbag', False)
set_player_attr('difficulty_requirements', None)
set_player_attr('boss_shuffle', 'none')
@@ -326,7 +327,7 @@ class World(object):
elif item.name.startswith('Bottle'):
if ret.bottle_count(item.player) < self.difficulty_requirements[item.player].progressive_bottle_limit:
ret.prog_items[item.name, item.player] += 1
elif item.advancement or item.smallkey or item.bigkey:
elif item.advancement or item.smallkey or item.bigkey or item.compass or item.map:
ret.prog_items[item.name, item.player] += 1
for item in self.itempool:
@@ -341,6 +342,8 @@ class World(object):
key_list += [dungeon.big_key.name]
if len(dungeon.small_keys) > 0:
key_list += [x.name for x in dungeon.small_keys]
# map/compass may be required now
key_list += [x.name for x in dungeon.dungeon_items]
from Items import ItemFactory
for item in ItemFactory(key_list, p):
soft_collect(item)
@@ -851,7 +854,7 @@ class CollectionState(object):
reduced = Counter()
for item, cnt in self.prog_items.items():
item_name, item_player = item
if item_player == player and self.check_if_progressive(item_name):
if item_player == player and self.check_if_progressive(item_name, player):
if item_name.startswith('Bottle'): # I think magic requirements can require multiple bottles
bottle_count += cnt
elif item_name in ['Boss Heart Container', 'Sanctuary Heart Container', 'Piece of Heart']:
@@ -867,8 +870,7 @@ class CollectionState(object):
reduced[('Heart Container', player)] = 1
return frozenset(reduced.items())
@staticmethod
def check_if_progressive(item_name):
def check_if_progressive(self, item_name, player):
return (item_name in
['Bow', 'Progressive Bow', 'Progressive Bow (Alt)', 'Book of Mudora', 'Hammer', 'Hookshot',
'Magic Mirror', 'Ocarina', 'Pegasus Boots', 'Power Glove', 'Cape', 'Mushroom', 'Shovel',
@@ -880,7 +882,8 @@ class CollectionState(object):
'Mirror Shield', 'Progressive Shield', 'Bug Catching Net', 'Cane of Byrna',
'Boss Heart Container', 'Sanctuary Heart Container', 'Piece of Heart', 'Magic Upgrade (1/2)',
'Magic Upgrade (1/4)']
or item_name.startswith(('Bottle', 'Small Key', 'Big Key')))
or item_name.startswith(('Bottle', 'Small Key', 'Big Key'))
or (self.world.restrict_boss_items[player] != 'none' and item_name.startswith(('Map', 'Compass'))))
def can_reach(self, spot, resolution_hint=None, player=None):
try:
@@ -2192,6 +2195,12 @@ class Item(object):
item_dungeon = 'Hyrule Castle'
return item_dungeon
def is_inside_dungeon_item(self, world):
return ((self.smallkey and not world.keyshuffle[self.player])
or (self.bigkey and not world.bigkeyshuffle[self.player])
or (self.compass and not world.compassshuffle[self.player])
or (self.map and not world.mapshuffle[self.player]))
def __str__(self):
return str(self.__unicode__())
@@ -2411,6 +2420,7 @@ class Spoiler(object):
'ganon_crystals': self.world.crystals_needed_for_ganon,
'open_pyramid': self.world.open_pyramid,
'accessibility': self.world.accessibility,
'restricted_boss_items': self.world.restrict_boss_items,
'hints': self.world.hints,
'mapshuffle': self.world.mapshuffle,
'compassshuffle': self.world.compassshuffle,
@@ -2426,6 +2436,7 @@ class Spoiler(object):
'experimental': self.world.experimental,
'keydropshuffle': self.world.keydropshuffle,
'shopsanity': self.world.shopsanity,
'pseudoboots': self.world.pseudoboots,
'triforcegoal': self.world.treasure_hunt_count,
'triforcepool': self.world.treasure_hunt_total,
'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)}
@@ -2453,6 +2464,9 @@ class Spoiler(object):
return json.dumps(out)
def to_file(self, filename):
def yn(flag):
return 'Yes' if flag else 'No'
self.parse_data()
with open(filename, 'w') as outfile:
outfile.write('ALttP Entrance Randomizer Version %s - Seed: %s\n\n' % (self.metadata['version'], self.world.seed))
@@ -2477,7 +2491,7 @@ class Spoiler(object):
outfile.write('Difficulty: %s\n' % self.metadata['item_pool'][player])
outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player])
outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player])
outfile.write(f"Link's House Shuffled: {'Yes' if self.metadata['shufflelinks'][player] else 'No'}\n")
outfile.write(f"Link's House Shuffled: {yn(self.metadata['shufflelinks'])}\n")
outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'][player])
outfile.write('Intensity: %s\n' % self.metadata['intensity'][player])
addition = ' (Random)' if self.world.crystals_gt_orig[player] == 'random' else ''
@@ -2486,6 +2500,7 @@ class Spoiler(object):
outfile.write('Crystals required for Ganon: %s\n' % (str(self.metadata['ganon_crystals'][player]) + addition))
outfile.write('Pyramid hole pre-opened: %s\n' % ('Yes' if self.metadata['open_pyramid'][player] else 'No'))
outfile.write('Accessibility: %s\n' % self.metadata['accessibility'][player])
outfile.write(f"Restricted Boss Items: {self.metadata['restricted_boss_items'][player]}\n")
outfile.write('Map shuffle: %s\n' % ('Yes' if self.metadata['mapshuffle'][player] else 'No'))
outfile.write('Compass shuffle: %s\n' % ('Yes' if self.metadata['compassshuffle'][player] else 'No'))
outfile.write('Small Key shuffle: %s\n' % ('Yes' if self.metadata['keyshuffle'][player] else 'No'))
@@ -2494,12 +2509,13 @@ class Spoiler(object):
outfile.write('Enemy shuffle: %s\n' % self.metadata['enemy_shuffle'][player])
outfile.write('Enemy health: %s\n' % self.metadata['enemy_health'][player])
outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'][player])
outfile.write('Pot shuffle: %s\n' % ('Yes' if self.metadata['potshuffle'][player] else 'No'))
outfile.write('Hints: %s\n' % ('Yes' if self.metadata['hints'][player] else 'No'))
outfile.write('Experimental: %s\n' % ('Yes' if self.metadata['experimental'][player] else 'No'))
outfile.write('Key Drops shuffled: %s\n' % ('Yes' if self.metadata['keydropshuffle'][player] else 'No'))
outfile.write(f"Shopsanity: {'Yes' if self.metadata['shopsanity'][player] else 'No'}\n")
outfile.write('Bombbag: %s\n' % ('Yes' if self.metadata['bombbag'][player] else 'No'))
outfile.write(f"Pot shuffle: {yn(self.metadata['potshuffle'][player])}\n")
outfile.write(f"Hints: {yn(self.metadata['hints'][player])}\n")
outfile.write(f"Experimental: {yn(self.metadata['experimental'][player])}\n")
outfile.write(f"Key Drops shuffled: {yn(self.metadata['keydropshuffle'][player])}\n")
outfile.write(f"Shopsanity: {yn(self.metadata['shopsanity'][player])}\n")
outfile.write(f"Bombbag: {yn(self.metadata['bombbag'][player])}\n")
outfile.write(f"Pseudoboots: {yn(self.metadata['pseudoboots'][player])}\n")
if self.doors:
outfile.write('\n\nDoors:\n\n')
outfile.write('\n'.join(
@@ -2681,6 +2697,16 @@ enemy_mode = {"none": 0, "shuffled": 1, "random": 2, "chaos": 2, "legacy": 3}
e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4}
e_dmg = {"default": 0, "shuffled": 1, "random": 2}
# byte 8: RRAA A??? (restrict boss mode, algorithm, ? = unused)
rb_mode = {"none": 0, "mapcompass": 1, "dungeon": 2}
# algorithm: todo with "biased shuffles"
algo_mode = {"balanced": 0, "equitable": 1, "vanilla_fill": 2, "dungeon_only": 3, "district": 4}
# additions
# psuedoboots does not effect code
# sfx_shuffle and other adjust items does not effect settings code
class Settings(object):
@staticmethod
@@ -2709,7 +2735,9 @@ class Settings(object):
| (boss_mode[w.boss_shuffle[p]] << 2) | (enemy_mode[w.enemy_shuffle[p]]),
(e_health[w.enemy_health[p]] << 5) | (e_dmg[w.enemy_damage[p]] << 3) | (0x4 if w.potshuffle[p] else 0)
| (0x2 if w.bombbag[p] else 0) | (1 if w.shufflelinks[p] else 0)])
| (0x2 if w.bombbag[p] else 0) | (1 if w.shufflelinks[p] else 0),
(rb_mode[w.restrict_boss_items[p]] << 6)])
return base64.b64encode(code, "+-".encode()).decode()
@staticmethod
@@ -2755,6 +2783,8 @@ class Settings(object):
args.shufflepots[p] = True if settings[7] & 0x4 else False
args.bombbag[p] = True if settings[7] & 0x2 else False
args.shufflelinks[p] = True if settings[7] & 0x1 else False
if len(settings) > 8:
args.restrict_boss_items[p] = True if r(rb_mode)[(settings[8] & 0x80) >> 6] else False
class KeyRuleType(FastEnum):

3
CLI.py
View File

@@ -96,7 +96,7 @@ def parse_cli(argv, no_defaults=False):
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
'bombbag', 'overworld_map',
'bombbag', 'overworld_map', 'restrict_boss_items',
'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'pseudoboots',
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
@@ -140,6 +140,7 @@ def parse_settings():
"progressive": "on",
"accessibility": "items",
"algorithm": "balanced",
"restrict_boss_items": "none",
# Shuffle Ganon defaults to TRUE
"openpyramid": False,

View File

@@ -218,7 +218,8 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, all_regions, pro
return name == 'Skull Woods 2' and d.name == 'Skull Pinball WS'
original_state = extend_reachable_state_improved(entrance_regions, start, proposed_map, all_regions,
valid_doors, bk_flag, world, player, exception)
dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map, exception)
dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map, exception,
world, player)
either_crystal = True # if all hooks from the origin are either, explore all bits with either
for hook, crystal in dungeon['Origin'].hooks.items():
if crystal != CrystalBarrier.Either:
@@ -239,7 +240,7 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, all_regions, pro
o_state = extend_reachable_state_improved([parent], init_state, proposed_map, all_regions,
valid_doors, bk_flag, world, player, exception)
o_state_cache[door.name] = o_state
piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map, exception)
piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map, exception, world, player)
dungeon[door.name] = piece
check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, all_regions, valid_doors,
group_flags, door_map, world, player, exception)
@@ -339,7 +340,7 @@ def explore_blue_state(door, dungeon, o_state, proposed_map, all_regions, valid_
blue_start.big_key_special = o_state.big_key_special
b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, all_regions, valid_doors, bk_flag,
world, player, exception)
dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception)
dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception, world, player)
def make_a_choice(dungeon, hangers, avail_hooks, prev_choices, name):
@@ -603,7 +604,7 @@ def winnow_hangers(hangers, hooks):
hangers[hanger].remove(door)
def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception):
def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception, world, player):
# todo: info about dungeon events - not sure about that
graph_piece = GraphPiece()
all_unattached = {}
@@ -635,16 +636,15 @@ def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exceptio
graph_piece.visited_regions.update(o_state.visited_orange)
graph_piece.visited_regions.update(b_state.visited_blue)
graph_piece.visited_regions.update(b_state.visited_orange)
graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(o_state.bk_found))
graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(b_state.bk_found))
graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(o_state.bk_found, world, player))
graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(b_state.bk_found, world, player))
graph_piece.pinball_used = o_state.pinball_used or b_state.pinball_used
return graph_piece
def filter_for_potential_bk_locations(locations):
return [x for x in locations if
'- Big Chest' not in x.name and '- Prize' not in x.name and x.name not in dungeon_events
and not x.forced_item and x.name not in ['Agahnim 1', 'Agahnim 2']]
def filter_for_potential_bk_locations(locations, world, player):
return [x for x in locations if '- Big Chest' not in x.name and not reserved_location(x, world, player) and
not x.forced_item and not prize_or_event(x) and not blind_boss_unavail(x, locations, world, player)]
type_map = {
@@ -987,12 +987,8 @@ class ExplorationState(object):
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
return True
def count_locations_exclude_specials(self):
cnt = 0
for loc in self.found_locations:
if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events and not loc.forced_item:
cnt += 1
return cnt
def count_locations_exclude_specials(self, world, player):
return count_locations_exclude_big_chest(self.found_locations, world, player)
def validate(self, door, region, world, player):
return self.can_traverse(door) and not self.visited(region) and valid_region_to_explore(region, self.dungeon,
@@ -1033,6 +1029,32 @@ class ExplorationState(object):
return 2
def count_locations_exclude_big_chest(locations, world, player):
cnt = 0
for loc in locations:
if ('- Big Chest' not in loc.name and not loc.forced_item and not reserved_location(loc, world, player)
and not prize_or_event(loc) and not blind_boss_unavail(loc, locations, world, player)):
cnt += 1
return cnt
def prize_or_event(loc):
return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
def reserved_location(loc, world, player):
return hasattr(world, 'item_pool_config') and loc.name in world.item_pool_config.reserved_locations[player]
def blind_boss_unavail(loc, locations, world, player):
if loc.name == "Thieves' Town - Boss":
return (loc.parent_region.dungeon.boss.name == 'Blind' and
(not any(x for x in locations if x.name == 'Suspicious Maiden') or
(world.get_region('Thieves Attic Window', player).dungeon.name == 'Thieves Town' and
not any(x for x in locations if x.name == 'Attic Cracked Floor'))))
return False
class ExplorableDoor(object):
def __init__(self, door, crystal, flag):
@@ -1056,7 +1078,8 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, all_reg
explorable_door = local_state.next_avail_door()
if explorable_door.door.bigKey:
if bk_flag:
big_not_found = not special_big_key_found(local_state) if local_state.big_key_special else local_state.count_locations_exclude_specials() == 0
big_not_found = (not special_big_key_found(local_state) if local_state.big_key_special
else local_state.count_locations_exclude_specials(world, player) == 0)
if big_not_found:
continue # we can't open this door
if explorable_door.door in proposed_map:

View File

@@ -117,6 +117,7 @@ def fill_dungeons(world):
def get_dungeon_item_pool(world):
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()
@@ -137,10 +138,7 @@ def fill_dungeons_restrictive(world, shuffled_locations):
elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]):
item.priority = True
dungeon_items = [item for item in get_dungeon_item_pool(world) if ((item.smallkey and not world.keyshuffle[item.player])
or (item.bigkey and not world.bigkeyshuffle[item.player])
or (item.map and not world.mapshuffle[item.player])
or (item.compass and not world.compassshuffle[item.player]))]
dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world)]
# sort in the order Big Key, Small Key, Other before placing dungeon items
sort_order = {"BigKey": 3, "SmallKey": 2}
@@ -441,7 +439,6 @@ dungeon_bigs = {
'Ganons Tower': 'Big Key (Ganons Tower)'
}
dungeon_hints = {
'Hyrule Castle': 'in Hyrule Castle',
'Eastern Palace': 'in Eastern Palace',

15
Fill.py
View File

@@ -237,7 +237,10 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool =
def valid_key_placement(item, location, itempool, world):
if (not item.smallkey and not item.bigkey) or item.player != location.player or world.retro[item.player] or world.logic[item.player] == 'nologic':
if not valid_reserved_placement(item, location, world):
return False
if ((not item.smallkey and not item.bigkey) or item.player != location.player
or world.retro[item.player] or world.logic[item.player] == 'nologic'):
return True
dungeon = location.parent_region.dungeon
if dungeon:
@@ -251,9 +254,13 @@ def valid_key_placement(item, location, itempool, world):
cr_count = world.crystals_needed_for_gt[location.player]
return key_logic.check_placement(unplaced_keys, location if item.bigkey else None, prize_loc, cr_count)
else:
inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player])
or (item.bigkey and not world.bigkeyshuffle[item.player]))
return not inside_dungeon_item
return not item.is_inside_dungeon_item(world) # todo: big deal for ambrosia to fix this
def valid_reserved_placement(item, location, world):
if item.player == location.player and item.is_inside_dungeon_item(world):
return location.name not in world.item_pool_config.reserved_locations[location.player]
return True
def track_outside_keys(item, location, world):

View File

@@ -5,7 +5,8 @@ from collections import defaultdict, deque
from BaseClasses import DoorType, dungeon_keys, KeyRuleType, RegionType
from Regions import dungeon_events
from Dungeons import dungeon_keys, dungeon_bigs, dungeon_table
from DungeonGenerator import ExplorationState, special_big_key_doors
from DungeonGenerator import ExplorationState, special_big_key_doors, count_locations_exclude_big_chest, prize_or_event
from DungeonGenerator import reserved_location, blind_boss_unavail
class KeyLayout(object):
@@ -1100,40 +1101,30 @@ def location_is_bk_locked(loc, key_logic):
return loc in key_logic.bk_chests or loc in key_logic.bk_locked
def prize_or_event(loc):
return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
def boss_unavail(loc, world, player):
# todo: ambrosia
# return world.bossdrops[player] == 'ambrosia' and "- Boss" in loc.name
return False
def blind_boss_unavail(loc, state, world, player):
if loc.name == "Thieves' Town - Boss":
# todo: check attic
return (loc.parent_region.dungeon.boss.name == 'Blind' and
(not any(x for x in state.found_locations if x.name == 'Suspicious Maiden') or
(world.get_region('Thieves Attic Window', player).dungeon.name == 'Thieves Town' and
not any(x for x in state.found_locations if x.name == 'Attic Cracked Floor'))))
return False
# todo: verfiy this code is defunct
# def prize_or_event(loc):
# return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
#
#
# def reserved_location(loc, world, player):
# return loc in world.item_pool.config.reserved_locations[player]
#
#
# def blind_boss_unavail(loc, state, world, player):
# if loc.name == "Thieves' Town - Boss":
# return (loc.parent_region.dungeon.boss.name == 'Blind' and
# (not any(x for x in state.found_locations if x.name == 'Suspicious Maiden') or
# (world.get_region('Thieves Attic Window', player).dungeon.name == 'Thieves Town' and
# not any(x for x in state.found_locations if x.name == 'Attic Cracked Floor'))))
# return False
# counts free locations for keys - hence why reserved locations don't count
def count_free_locations(state, world, player):
cnt = 0
for loc in state.found_locations:
if (not prize_or_event(loc) and not loc.forced_item and not boss_unavail(loc, world, player)
and not blind_boss_unavail(loc, state, world, player)):
cnt += 1
return cnt
def count_locations_exclude_big_chest(state, world, player):
cnt = 0
for loc in state.found_locations:
if ('- Big Chest' not in loc.name and not loc.forced_item and not boss_unavail(loc, world, player)
and not prize_or_event(loc) and not blind_boss_unavail(loc, state, world, player)):
if (not prize_or_event(loc) and not loc.forced_item and not reserved_location(loc, world, player)
and not blind_boss_unavail(loc, state.found_locations, world, player)):
cnt += 1
return cnt
@@ -1437,7 +1428,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
if state.big_key_opened:
ttl_locations = count_free_locations(state, world, player)
else:
ttl_locations = count_locations_exclude_big_chest(state, world, player)
ttl_locations = count_locations_exclude_big_chest(state.found_locations, world, player)
ttl_small_key_only = count_small_key_only_locations(state)
available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_small_key_only, state, world, player)
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player)
@@ -1663,7 +1654,7 @@ def can_open_door(door, state, world, player):
if state.big_key_opened:
ttl_locations = count_free_locations(state, world, player)
else:
ttl_locations = count_locations_exclude_big_chest(state, world, player)
ttl_locations = count_locations_exclude_big_chest(state.found_locations, world, player)
if door.smallKey:
ttl_small_key_only = count_small_key_only_locations(state)
available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_small_key_only, state, world, player)

View File

@@ -29,6 +29,8 @@ from Fill import sell_potions, sell_keys, balance_multiworld_progression, balanc
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
from Utils import output_path, parse_player_names
from source.item.FillUtil import create_item_pool_config
__version__ = '1.0.1.0-v'
from source.classes.BabelFish import BabelFish
@@ -105,6 +107,7 @@ def main(args, seed=None, fish=None):
world.shufflelinks = args.shufflelinks.copy()
world.pseudoboots = args.pseudoboots.copy()
world.overworld_map = args.overworld_map.copy()
world.restrict_boss_items = args.restrict_boss_items.copy()
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
@@ -149,6 +152,7 @@ def main(args, seed=None, fish=None):
create_dungeons(world, player)
adjust_locations(world, player)
place_bosses(world, player)
create_item_pool_config(world)
if any(world.potshuffle.values()):
logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))

View File

@@ -134,6 +134,7 @@ def roll_settings(weights):
ret.bigkeyshuffle = get_choice('bigkey_shuffle') == 'on' if 'bigkey_shuffle' in weights else dungeon_items in ['full']
ret.accessibility = get_choice('accessibility')
ret.restrict_boss_items = get_choice('restrict_boss_items')
entrance_shuffle = get_choice('entrance_shuffle')
ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'

View File

@@ -4,6 +4,7 @@ from collections import deque
import OverworldGlitchRules
from BaseClasses import CollectionState, RegionType, DoorType, Entrance, CrystalBarrier, KeyRuleType
from Dungeons import dungeon_table
from RoomData import DoorKind
from OverworldGlitchRules import overworld_glitches_rules
@@ -557,11 +558,29 @@ def global_rules(world, player):
add_key_logic_rules(world, player)
# End of door rando rules.
if world.restrict_boss_items[player] != 'none':
def add_mc_rule(l):
boss_location = world.get_location(l, player)
d_name = boss_location.parent_region.dungeon.name
compass_name = f'Compass ({d_name})'
map_name = f'Map ({d_name})'
add_rule(boss_location, lambda state: state.has(compass_name, player) and state.has(map_name, player))
for dungeon, info in dungeon_table.items():
if info.prize:
d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon
for loc in [info.prize, f'{d_name} - Boss']:
add_mc_rule(loc)
if world.doorShuffle[player] == 'crossed':
add_mc_rule('Agahnim 1')
add_mc_rule('Agahnim 2')
add_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player))
set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player)
and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (state.has('Silver Arrows', player) and state.can_shoot_arrows(player)) or state.has('Lamp', player) or state.can_extend_magic(player, 12))) # need to light torch a sufficient amount of times
set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has_beam_sword(player)) # need to damage ganon to get tiles to drop
def bomb_rules(world, player):
bonkable_doors = ['Two Brothers House Exit (West)', 'Two Brothers House Exit (East)'] # Technically this is incorrectly defined, but functionally the same as what is intended.
bombable_doors = ['Ice Rod Cave', 'Light World Bomb Hut', 'Light World Death Mountain Shop', 'Mini Moldorm Cave',

View File

@@ -254,6 +254,13 @@
"none"
]
},
"restrict_boss_items": {
"choices": [
"none",
"mapcompass",
"dungeon"
]
},
"hints": {
"action": "store_true",
"type": "bool"

View File

@@ -276,6 +276,13 @@
"Locations: You will be able to reach every location in the game.",
"None: You will be able to reach enough locations to beat the game."
],
"restrict_boss_items": [
"Select which dungeon are not allowed on bosses (default: %(default)s)",
"None: All items allowed",
"Mapcompass: Map and Compass are required before you defeat the boss.",
"Dungeon: Same as above and keys too cannot be on the boss. Small key shuffle",
" and big key shuffle override this behavior"
],
"hints": [ "Make telepathic tiles and storytellers give helpful hints. (default: %(default)s)" ],
"shuffleganon": [
"Include the Ganon's Tower and Pyramid Hole in the",

View File

@@ -291,6 +291,10 @@
"randomizer.item.sortingalgo.vt26": "VT8.26",
"randomizer.item.sortingalgo.balanced": "Balanced",
"randomizer.item.restrict_boss_items": "Forbidden Boss Items",
"randomizer.item.restrict_boss_items.none": "None",
"randomizer.item.restrict_boss_items.mapcompass": "Map & Compass",
"randomizer.item.restrict_boss_items.dungeon": "Map & Compass & Keys",
"bottom.content.worlds": "Worlds",
"bottom.content.names": "Player names",

View File

@@ -124,6 +124,15 @@
"vt26",
"balanced"
]
},
"restrict_boss_items": {
"type": "selectbox",
"default": "none",
"options": [
"none",
"mapcompass",
"dungeon"
]
}
}
}

View File

@@ -72,7 +72,8 @@ SETTINGSTOPROCESS = {
"progressives": "progressive",
"accessibility": "accessibility",
"sortingalgo": "algorithm",
"beemizer": "beemizer"
"beemizer": "beemizer",
"restrict_boss_items": "restrict_boss_items"
},
"entrance": {
"openpyramid": "openpyramid",

26
source/item/FillUtil.py Normal file
View File

@@ -0,0 +1,26 @@
from collections import defaultdict
from Dungeons import dungeon_table
class ItemPoolConfig(object):
def __init__(self):
self.reserved_locations = defaultdict(set)
def create_item_pool_config(world):
world.item_pool_config = config = ItemPoolConfig()
player_set = set()
for player in range(1, world.players+1):
if world.restrict_boss_items[player] != 'none':
player_set.add(player)
if world.restrict_boss_items[player] == 'dungeon':
for dungeon, info in dungeon_table.items():
if info.prize:
d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon
config.reserved_locations[player].add(f'{d_name} - Boss')
for dungeon in world.dungeons:
if world.restrict_boss_items[dungeon.player] != 'none':
for item in dungeon.all_items:
if item.map or item.compass:
item.advancement = True