Boss item restriction logic added
Reserved location logic started
This commit is contained in:
@@ -114,6 +114,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')
|
||||
@@ -324,7 +325,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:
|
||||
@@ -339,6 +340,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)
|
||||
@@ -2178,6 +2181,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__())
|
||||
|
||||
@@ -2388,6 +2397,7 @@ class Spoiler(object):
|
||||
'weapons': self.world.swords,
|
||||
'goal': self.world.goal,
|
||||
'shuffle': self.world.shuffle,
|
||||
'linkshuffle': self.world.shufflelinks,
|
||||
'door_shuffle': self.world.doorShuffle,
|
||||
'intensity': self.world.intensity,
|
||||
'item_pool': self.world.difficulty,
|
||||
@@ -2396,6 +2406,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,
|
||||
@@ -2411,6 +2422,7 @@ class Spoiler(object):
|
||||
'experimental': self.world.experimental,
|
||||
'keydropshuffle': self.world.keydropshuffle,
|
||||
'shopsanity': self.world.shopsanity,
|
||||
'psuedoboots': 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)}
|
||||
@@ -2438,6 +2450,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))
|
||||
@@ -2462,6 +2477,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"Links House Shuffled: {self.metadata['linkshuffle'][player]}\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 ''
|
||||
@@ -2470,6 +2486,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'))
|
||||
@@ -2478,12 +2495,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['bombbag'][player])}\n")
|
||||
if self.doors:
|
||||
outfile.write('\n\nDoors:\n\n')
|
||||
outfile.write('\n'.join(
|
||||
@@ -2665,6 +2683,12 @@ 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}
|
||||
|
||||
# additions
|
||||
# shuffle links: 1 bit
|
||||
# restrict_boss_mode: 2 bits
|
||||
# psuedoboots does not effect code
|
||||
# sfx_shuffle and other adjust items does not effect settings code
|
||||
|
||||
class Settings(object):
|
||||
|
||||
@staticmethod
|
||||
@@ -2738,7 +2762,6 @@ class Settings(object):
|
||||
args.shufflepots[p] = True if settings[7] & 0x4 else False
|
||||
|
||||
|
||||
@unique
|
||||
class KeyRuleType(FastEnum):
|
||||
WorstCase = 0
|
||||
AllowSmall = 1
|
||||
|
||||
3
CLI.py
3
CLI.py
@@ -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',
|
||||
'bombbag', '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,
|
||||
|
||||
@@ -226,7 +226,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:
|
||||
@@ -247,7 +248,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)
|
||||
@@ -347,7 +348,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):
|
||||
@@ -639,7 +640,7 @@ def stonewall_valid(stonewall):
|
||||
return True
|
||||
|
||||
|
||||
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 = {}
|
||||
@@ -671,16 +672,14 @@ 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 count_locations_exclude_big_chest(locations, world, player)
|
||||
|
||||
|
||||
type_map = {
|
||||
@@ -1023,12 +1022,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,
|
||||
@@ -1069,6 +1064,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 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):
|
||||
@@ -1092,7 +1113,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:
|
||||
|
||||
@@ -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}
|
||||
@@ -414,7 +412,7 @@ dungeon_prize = {
|
||||
'Palace of Darkness': 'Palace of Darkness - Prize',
|
||||
'Swamp Palace': 'Swamp Palace - Prize',
|
||||
'Skull Woods': 'Skull Woods - Prize',
|
||||
'Thieves Town': 'Thieves Town - Prize',
|
||||
'Thieves Town': "Thieves' Town - Prize",
|
||||
'Ice Palace': 'Ice Palace - Prize',
|
||||
'Misery Mire': 'Misery Mire - Prize',
|
||||
'Turtle Rock': 'Turtle Rock - Prize',
|
||||
|
||||
15
Fill.py
15
Fill.py
@@ -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:
|
||||
@@ -247,9 +250,13 @@ def valid_key_placement(item, location, itempool, world):
|
||||
unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player])
|
||||
return key_logic.check_placement(unplaced_keys, location if item.bigkey else None)
|
||||
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 item.is_inside_dungeon_item(world)
|
||||
|
||||
|
||||
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):
|
||||
|
||||
@@ -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_prize
|
||||
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):
|
||||
@@ -1078,40 +1079,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
|
||||
|
||||
@@ -1407,7 +1398,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)
|
||||
@@ -1596,7 +1587,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)
|
||||
|
||||
4
Main.py
4
Main.py
@@ -28,6 +28,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__ = '0.5.1.0-u'
|
||||
|
||||
from source.classes.BabelFish import BabelFish
|
||||
@@ -103,6 +105,7 @@ def main(args, seed=None, fish=None):
|
||||
world.treasure_hunt_total = args.triforce_pool.copy()
|
||||
world.shufflelinks = args.shufflelinks.copy()
|
||||
world.pseudoboots = args.pseudoboots.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)}
|
||||
|
||||
@@ -146,6 +149,7 @@ def main(args, seed=None, fish=None):
|
||||
create_rooms(world, player)
|
||||
create_dungeons(world, player)
|
||||
adjust_locations(world, player)
|
||||
create_item_pool_config(world)
|
||||
|
||||
if any(world.potshuffle.values()):
|
||||
logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))
|
||||
|
||||
@@ -132,6 +132,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'
|
||||
|
||||
18
Rules.py
18
Rules.py
@@ -4,6 +4,7 @@ from collections import deque
|
||||
|
||||
import OverworldGlitchRules
|
||||
from BaseClasses import CollectionState, RegionType, DoorType, Entrance, CrystalBarrier, KeyRuleType
|
||||
from Dungeons import dungeon_regions, dungeon_prize
|
||||
from RoomData import DoorKind
|
||||
from OverworldGlitchRules import overworld_glitches_rules
|
||||
|
||||
@@ -553,11 +554,28 @@ 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 in dungeon_prize.keys():
|
||||
d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon
|
||||
for loc in [dungeon_prize[dungeon], 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',
|
||||
|
||||
@@ -254,6 +254,13 @@
|
||||
"none"
|
||||
]
|
||||
},
|
||||
"restrict_boss_items": {
|
||||
"choices": [
|
||||
"none",
|
||||
"mapcompass",
|
||||
"dungeon"
|
||||
]
|
||||
},
|
||||
"hints": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -287,6 +287,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",
|
||||
|
||||
@@ -124,6 +124,15 @@
|
||||
"vt26",
|
||||
"balanced"
|
||||
]
|
||||
},
|
||||
"restrict_boss_items": {
|
||||
"type": "selectbox",
|
||||
"default": "none",
|
||||
"options": [
|
||||
"none",
|
||||
"mapcompass",
|
||||
"dungeon"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,8 @@ SETTINGSTOPROCESS = {
|
||||
"progressives": "progressive",
|
||||
"accessibility": "accessibility",
|
||||
"sortingalgo": "algorithm",
|
||||
"beemizer": "beemizer"
|
||||
"beemizer": "beemizer",
|
||||
"restrict_boss_items": "restrict_boss_items"
|
||||
},
|
||||
"entrance": {
|
||||
"openpyramid": "openpyramid",
|
||||
|
||||
20
source/item/FillUtil.py
Normal file
20
source/item/FillUtil.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from Dungeons import dungeon_prize
|
||||
|
||||
class ItemPoolConfig(object):
|
||||
|
||||
def __init__(self):
|
||||
self.reserved_locations = defaultdict(set)
|
||||
|
||||
|
||||
def create_item_pool_config(world):
|
||||
config = ItemPoolConfig()
|
||||
if world.algorithm in ['balanced']:
|
||||
for player in range(1, world.players+1):
|
||||
if world.restrict_boss_items[player]:
|
||||
for dungeon in dungeon_prize:
|
||||
if dungeon.startswith('Thieves'):
|
||||
dungeon = "Thieves' Town"
|
||||
config.reserved_locations[player].add(f'{dungeon} - Boss')
|
||||
world.item_pool_config = config
|
||||
Reference in New Issue
Block a user