Merge branch 'DoorDevUnstable' into Ambrosia
This commit is contained in:
@@ -77,6 +77,7 @@ class World(object):
|
|||||||
self._room_cache = {}
|
self._room_cache = {}
|
||||||
self.dungeon_layouts = {}
|
self.dungeon_layouts = {}
|
||||||
self.inaccessible_regions = {}
|
self.inaccessible_regions = {}
|
||||||
|
self.enabled_entrances = {}
|
||||||
self.key_logic = {}
|
self.key_logic = {}
|
||||||
self.pool_adjustment = {}
|
self.pool_adjustment = {}
|
||||||
self.key_layout = defaultdict(dict)
|
self.key_layout = defaultdict(dict)
|
||||||
@@ -142,6 +143,7 @@ class World(object):
|
|||||||
set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False})
|
set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False})
|
||||||
|
|
||||||
set_player_attr('exp_cache', defaultdict(dict))
|
set_player_attr('exp_cache', defaultdict(dict))
|
||||||
|
set_player_attr('enabled_entrances', {})
|
||||||
|
|
||||||
def get_name_string_for_object(self, obj):
|
def get_name_string_for_object(self, obj):
|
||||||
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})'
|
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})'
|
||||||
@@ -1165,6 +1167,8 @@ class CollectionState(object):
|
|||||||
def collect(self, item, event=False, location=None):
|
def collect(self, item, event=False, location=None):
|
||||||
if location:
|
if location:
|
||||||
self.locations_checked.add(location)
|
self.locations_checked.add(location)
|
||||||
|
if not item:
|
||||||
|
return
|
||||||
changed = False
|
changed = False
|
||||||
if item.name.startswith('Progressive '):
|
if item.name.startswith('Progressive '):
|
||||||
if 'Sword' in item.name:
|
if 'Sword' in item.name:
|
||||||
|
|||||||
@@ -181,11 +181,11 @@ def place_bosses(world, player):
|
|||||||
|
|
||||||
logging.getLogger('').debug('Bosses chosen %s', bosses)
|
logging.getLogger('').debug('Bosses chosen %s', bosses)
|
||||||
|
|
||||||
random.shuffle(bosses)
|
|
||||||
for [loc, level] in boss_locations:
|
for [loc, level] in boss_locations:
|
||||||
loc_text = loc + (' ('+level+')' if level else '')
|
loc_text = loc + (' ('+level+')' if level else '')
|
||||||
boss = next((b for b in bosses if can_place_boss(world, player, b, loc, level)), None)
|
try:
|
||||||
if not boss:
|
boss = random.choice([b for b in bosses if can_place_boss(world, player, b, loc, level)])
|
||||||
|
except IndexError:
|
||||||
raise FillError('Could not place boss for location %s' % loc_text)
|
raise FillError('Could not place boss for location %s' % loc_text)
|
||||||
bosses.remove(boss)
|
bosses.remove(boss)
|
||||||
|
|
||||||
|
|||||||
@@ -753,7 +753,7 @@ def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map,
|
|||||||
|
|
||||||
def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player):
|
def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player):
|
||||||
entrances_map, potentials, connections = connections_tuple
|
entrances_map, potentials, connections = connections_tuple
|
||||||
enabled_entrances = {}
|
enabled_entrances = world.enabled_entrances[player] = {}
|
||||||
sector_queue = deque(dungeon_builders.values())
|
sector_queue = deque(dungeon_builders.values())
|
||||||
last_key, loops = None, 0
|
last_key, loops = None, 0
|
||||||
logging.getLogger('').info(world.fish.translate("cli", "cli", "generating.dungeon"))
|
logging.getLogger('').info(world.fish.translate("cli", "cli", "generating.dungeon"))
|
||||||
@@ -1582,7 +1582,7 @@ def find_key_door_candidates(region, checked, world, player):
|
|||||||
if d2.type == DoorType.Normal:
|
if d2.type == DoorType.Normal:
|
||||||
room_b = world.get_room(d2.roomIndex, player)
|
room_b = world.get_room(d2.roomIndex, player)
|
||||||
pos_b, kind_b = room_b.doorList[d2.doorListPos]
|
pos_b, kind_b = room_b.doorList[d2.doorListPos]
|
||||||
valid = kind in okay_normals and kind_b in okay_normals
|
valid = kind in okay_normals and kind_b in okay_normals and valid_key_door_pair(d, d2)
|
||||||
else:
|
else:
|
||||||
valid = kind in okay_normals
|
valid = kind in okay_normals
|
||||||
if valid and 0 <= d2.doorListPos < 4:
|
if valid and 0 <= d2.doorListPos < 4:
|
||||||
@@ -1599,6 +1599,12 @@ def find_key_door_candidates(region, checked, world, player):
|
|||||||
return candidates, checked_doors
|
return candidates, checked_doors
|
||||||
|
|
||||||
|
|
||||||
|
def valid_key_door_pair(door1, door2):
|
||||||
|
if door1.roomIndex != door2.roomIndex:
|
||||||
|
return True
|
||||||
|
return len(door1.entrance.parent_region.exits) <= 1 or len(door2.entrance.parent_region.exits) <= 1
|
||||||
|
|
||||||
|
|
||||||
def reassign_key_doors(builder, world, player):
|
def reassign_key_doors(builder, world, player):
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
logger.debug('Key doors for %s', builder.name)
|
logger.debug('Key doors for %s', builder.name)
|
||||||
@@ -1915,14 +1921,18 @@ def check_required_paths(paths, world, player):
|
|||||||
if dungeon_name in world.dungeon_layouts[player].keys():
|
if dungeon_name in world.dungeon_layouts[player].keys():
|
||||||
builder = world.dungeon_layouts[player][dungeon_name]
|
builder = world.dungeon_layouts[player][dungeon_name]
|
||||||
if len(paths[dungeon_name]) > 0:
|
if len(paths[dungeon_name]) > 0:
|
||||||
states_to_explore = defaultdict(list)
|
states_to_explore = {}
|
||||||
for path in paths[dungeon_name]:
|
for path in paths[dungeon_name]:
|
||||||
if type(path) is tuple:
|
if type(path) is tuple:
|
||||||
states_to_explore[tuple([path[0]])] = path[1]
|
states_to_explore[tuple([path[0]])] = (path[1], 'any')
|
||||||
else:
|
else:
|
||||||
states_to_explore[tuple(builder.path_entrances)].append(path)
|
common_starts = tuple(builder.path_entrances)
|
||||||
|
if common_starts not in states_to_explore:
|
||||||
|
states_to_explore[common_starts] = ([], 'all')
|
||||||
|
states_to_explore[common_starts][0].append(path)
|
||||||
cached_initial_state = None
|
cached_initial_state = None
|
||||||
for start_regs, dest_regs in states_to_explore.items():
|
for start_regs, info in states_to_explore.items():
|
||||||
|
dest_regs, path_type = info
|
||||||
if type(dest_regs) is not list:
|
if type(dest_regs) is not list:
|
||||||
dest_regs = [dest_regs]
|
dest_regs = [dest_regs]
|
||||||
check_paths = convert_regions(dest_regs, world, player)
|
check_paths = convert_regions(dest_regs, world, player)
|
||||||
@@ -1939,11 +1949,17 @@ def check_required_paths(paths, world, player):
|
|||||||
cached_initial_state = state
|
cached_initial_state = state
|
||||||
else:
|
else:
|
||||||
state = cached_initial_state
|
state = cached_initial_state
|
||||||
valid, bad_region = check_if_regions_visited(state, check_paths)
|
if path_type == 'any':
|
||||||
|
valid, bad_region = check_if_any_regions_visited(state, check_paths)
|
||||||
|
else:
|
||||||
|
valid, bad_region = check_if_all_regions_visited(state, check_paths)
|
||||||
if not valid:
|
if not valid:
|
||||||
if check_for_pinball_fix(state, bad_region, world, player):
|
if check_for_pinball_fix(state, bad_region, world, player):
|
||||||
explore_state(state, world, player)
|
explore_state(state, world, player)
|
||||||
valid, bad_region = check_if_regions_visited(state, check_paths)
|
if path_type == 'any':
|
||||||
|
valid, bad_region = check_if_any_regions_visited(state, check_paths)
|
||||||
|
else:
|
||||||
|
valid, bad_region = check_if_all_regions_visited(state, check_paths)
|
||||||
if not valid:
|
if not valid:
|
||||||
raise Exception('%s cannot reach %s' % (dungeon_name, bad_region.name))
|
raise Exception('%s cannot reach %s' % (dungeon_name, bad_region.name))
|
||||||
|
|
||||||
@@ -1983,7 +1999,7 @@ def explore_state_not_inaccessible(state, world, player):
|
|||||||
state.add_all_doors_check_unattached(connect_region, world, player)
|
state.add_all_doors_check_unattached(connect_region, world, player)
|
||||||
|
|
||||||
|
|
||||||
def check_if_regions_visited(state, check_paths):
|
def check_if_any_regions_visited(state, check_paths):
|
||||||
valid = False
|
valid = False
|
||||||
breaking_region = None
|
breaking_region = None
|
||||||
for region_target in check_paths:
|
for region_target in check_paths:
|
||||||
@@ -1995,6 +2011,13 @@ def check_if_regions_visited(state, check_paths):
|
|||||||
return valid, breaking_region
|
return valid, breaking_region
|
||||||
|
|
||||||
|
|
||||||
|
def check_if_all_regions_visited(state, check_paths):
|
||||||
|
for region_target in check_paths:
|
||||||
|
if not state.visited_at_all(region_target):
|
||||||
|
return False, region_target
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
def check_for_pinball_fix(state, bad_region, world, player):
|
def check_for_pinball_fix(state, bad_region, world, player):
|
||||||
pinball_region = world.get_region('Skull Pinball', player)
|
pinball_region = world.get_region('Skull Pinball', player)
|
||||||
# todo: lobby shuffle
|
# todo: lobby shuffle
|
||||||
@@ -2018,7 +2041,7 @@ class DROptions(Flag):
|
|||||||
Town_Portal = 0x02 # If on, Players will start with mirror scroll
|
Town_Portal = 0x02 # If on, Players will start with mirror scroll
|
||||||
Map_Info = 0x04
|
Map_Info = 0x04
|
||||||
Debug = 0x08
|
Debug = 0x08
|
||||||
Rails = 0x10 # If on, draws rails
|
# Rails = 0x10 # Unused bit now
|
||||||
OriginalPalettes = 0x20
|
OriginalPalettes = 0x20
|
||||||
Open_PoD_Wall = 0x40 # If on, pre opens the PoD wall, no bow required
|
Open_PoD_Wall = 0x40 # If on, pre opens the PoD wall, no bow required
|
||||||
Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required
|
Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
|
|||||||
p_region = portal.door.entrance.connected_region
|
p_region = portal.door.entrance.connected_region
|
||||||
access_region = next(x.parent_region for x in p_region.entrances
|
access_region = next(x.parent_region for x in p_region.entrances
|
||||||
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld])
|
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld])
|
||||||
if access_region.name in world.inaccessible_regions[player]:
|
if (access_region.name in world.inaccessible_regions[player] and
|
||||||
|
region.name not in world.enabled_entrances[player]):
|
||||||
excluded[region] = None
|
excluded[region] = None
|
||||||
entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
|
entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
|
||||||
doors_to_connect = {}
|
doors_to_connect = {}
|
||||||
|
|||||||
13
Fill.py
13
Fill.py
@@ -248,7 +248,11 @@ def valid_key_placement(item, location, itempool, world):
|
|||||||
return True
|
return True
|
||||||
key_logic = world.key_logic[item.player][dungeon.name]
|
key_logic = world.key_logic[item.player][dungeon.name]
|
||||||
unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player])
|
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)
|
prize_loc = None
|
||||||
|
if key_logic.prize_location:
|
||||||
|
prize_loc = world.get_location(key_logic.prize_location, location.player)
|
||||||
|
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:
|
else:
|
||||||
return item.is_inside_dungeon_item(world)
|
return item.is_inside_dungeon_item(world)
|
||||||
|
|
||||||
@@ -651,7 +655,7 @@ def balance_money_progression(world):
|
|||||||
base_value = sum(rupee_rooms.values())
|
base_value = sum(rupee_rooms.values())
|
||||||
available_money = {player: base_value for player in range(1, world.players+1)}
|
available_money = {player: base_value for player in range(1, world.players+1)}
|
||||||
for loc in world.get_locations():
|
for loc in world.get_locations():
|
||||||
if loc.item.name in rupee_chart:
|
if loc.item and loc.item.name in rupee_chart:
|
||||||
available_money[loc.item.player] += rupee_chart[loc.item.name]
|
available_money[loc.item.player] += rupee_chart[loc.item.name]
|
||||||
|
|
||||||
total_price = {player: 0 for player in range(1, world.players+1)}
|
total_price = {player: 0 for player in range(1, world.players+1)}
|
||||||
@@ -716,7 +720,7 @@ def balance_money_progression(world):
|
|||||||
slot = shop_to_location_table[location.parent_region.name].index(location.name)
|
slot = shop_to_location_table[location.parent_region.name].index(location.name)
|
||||||
shop = location.parent_region.shop
|
shop = location.parent_region.shop
|
||||||
shop_item = shop.inventory[slot]
|
shop_item = shop.inventory[slot]
|
||||||
if interesting_item(location, location.item, world, location.item.player):
|
if location.item and interesting_item(location, location.item, world, location.item.player):
|
||||||
if location.item.name.startswith('Rupee') and loc_player == location.item.player:
|
if location.item.name.startswith('Rupee') and loc_player == location.item.player:
|
||||||
if shop_item['price'] < rupee_chart[location.item.name]:
|
if shop_item['price'] < rupee_chart[location.item.name]:
|
||||||
wallet[loc_player] -= shop_item['price'] # will get picked up in the location_free block
|
wallet[loc_player] -= shop_item['price'] # will get picked up in the location_free block
|
||||||
@@ -736,6 +740,7 @@ def balance_money_progression(world):
|
|||||||
if location_free:
|
if location_free:
|
||||||
state.collect(location.item, True, location)
|
state.collect(location.item, True, location)
|
||||||
unchecked_locations.remove(location)
|
unchecked_locations.remove(location)
|
||||||
|
if location.item:
|
||||||
if location.item.name.startswith('Rupee'):
|
if location.item.name.startswith('Rupee'):
|
||||||
wallet[location.item.player] += rupee_chart[location.item.name]
|
wallet[location.item.player] += rupee_chart[location.item.name]
|
||||||
if location.item.name != 'Rupees (300)':
|
if location.item.name != 'Rupees (300)':
|
||||||
@@ -808,5 +813,5 @@ def balance_money_progression(world):
|
|||||||
else:
|
else:
|
||||||
state.collect(location.item, True, location)
|
state.collect(location.item, True, location)
|
||||||
unchecked_locations.remove(location)
|
unchecked_locations.remove(location)
|
||||||
if location.item.name.startswith('Rupee'):
|
if location.item and location.item.name.startswith('Rupee'):
|
||||||
wallet[location.item.player] += rupee_chart[location.item.name]
|
wallet[location.item.player] += rupee_chart[location.item.name]
|
||||||
|
|||||||
10
ItemList.py
10
ItemList.py
@@ -604,6 +604,7 @@ def customize_shops(world, player):
|
|||||||
upgrade.location = loc
|
upgrade.location = loc
|
||||||
change_shop_items_to_rupees(world, player, shops_to_customize)
|
change_shop_items_to_rupees(world, player, shops_to_customize)
|
||||||
balance_prices(world, player)
|
balance_prices(world, player)
|
||||||
|
check_hints(world, player)
|
||||||
|
|
||||||
|
|
||||||
def randomize_price(price):
|
def randomize_price(price):
|
||||||
@@ -707,6 +708,15 @@ def balance_prices(world, player):
|
|||||||
# loc.parent_region.shop.inventory[slot]['price'] = new_price
|
# loc.parent_region.shop.inventory[slot]['price'] = new_price
|
||||||
|
|
||||||
|
|
||||||
|
def check_hints(world, player):
|
||||||
|
if world.shuffle[player] in ['simple', 'restricted', 'full', 'crossed', 'insanity']:
|
||||||
|
for shop, location_list in shop_to_location_table.items():
|
||||||
|
if shop in ['Capacity Upgrade', 'Light World Death Mountain Shop', 'Potion Shop']:
|
||||||
|
continue # near the queen, near potions, and near 7 chests are fine
|
||||||
|
for loc_name in location_list: # other shops are indistinguishable in ER
|
||||||
|
world.get_location(loc_name, player).hint_text = f'for sale'
|
||||||
|
|
||||||
|
|
||||||
repeatable_shop_items = ['Single Arrow', 'Arrows (10)', 'Bombs (3)', 'Bombs (10)', 'Red Potion', 'Small Heart',
|
repeatable_shop_items = ['Single Arrow', 'Arrows (10)', 'Bombs (3)', 'Bombs (10)', 'Red Potion', 'Small Heart',
|
||||||
'Blue Shield', 'Red Shield', 'Bee', 'Small Key (Universal)', 'Blue Potion', 'Green Potion']
|
'Blue Shield', 'Red Shield', 'Bee', 'Small Key (Universal)', 'Blue Potion', 'Green Potion']
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class KeyLayout(object):
|
|||||||
self.item_locations = set()
|
self.item_locations = set()
|
||||||
|
|
||||||
self.found_doors = set()
|
self.found_doors = set()
|
||||||
self.prize_relevant = False
|
self.prize_relevant = None
|
||||||
# bk special?
|
# bk special?
|
||||||
# bk required? True if big chests or big doors exists
|
# bk required? True if big chests or big doors exists
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ class KeyLayout(object):
|
|||||||
self.max_chests = calc_max_chests(builder, self, world, player)
|
self.max_chests = calc_max_chests(builder, self, world, player)
|
||||||
self.all_locations = set()
|
self.all_locations = set()
|
||||||
self.item_locations = set()
|
self.item_locations = set()
|
||||||
self.prize_relevant = False
|
self.prize_relevant = None
|
||||||
|
|
||||||
|
|
||||||
class KeyLogic(object):
|
class KeyLogic(object):
|
||||||
@@ -59,10 +59,11 @@ class KeyLogic(object):
|
|||||||
self.outside_keys = 0
|
self.outside_keys = 0
|
||||||
self.dungeon = dungeon_name
|
self.dungeon = dungeon_name
|
||||||
self.sm_doors = {}
|
self.sm_doors = {}
|
||||||
|
self.prize_location = None
|
||||||
|
|
||||||
def check_placement(self, unplaced_keys, big_key_loc=None):
|
def check_placement(self, unplaced_keys, big_key_loc=None, prize_loc=None, cr_count=7):
|
||||||
for rule in self.placement_rules:
|
for rule in self.placement_rules:
|
||||||
if not rule.is_satisfiable(self.outside_keys, unplaced_keys, big_key_loc):
|
if not rule.is_satisfiable(self.outside_keys, unplaced_keys, big_key_loc, prize_loc, cr_count):
|
||||||
return False
|
return False
|
||||||
if big_key_loc:
|
if big_key_loc:
|
||||||
for rule_a, rule_b in itertools.combinations(self.placement_rules, 2):
|
for rule_a, rule_b in itertools.combinations(self.placement_rules, 2):
|
||||||
@@ -121,6 +122,7 @@ class PlacementRule(object):
|
|||||||
self.check_locations_wo_bk = None
|
self.check_locations_wo_bk = None
|
||||||
self.bk_relevant = True
|
self.bk_relevant = True
|
||||||
self.key_reduced = False
|
self.key_reduced = False
|
||||||
|
self.prize_relevance = None
|
||||||
|
|
||||||
def contradicts(self, rule, unplaced_keys, big_key_loc):
|
def contradicts(self, rule, unplaced_keys, big_key_loc):
|
||||||
bk_blocked = big_key_loc in self.bk_conditional_set if self.bk_conditional_set else False
|
bk_blocked = big_key_loc in self.bk_conditional_set if self.bk_conditional_set else False
|
||||||
@@ -155,7 +157,14 @@ class PlacementRule(object):
|
|||||||
left -= rule_needed
|
left -= rule_needed
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_satisfiable(self, outside_keys, unplaced_keys, big_key_loc):
|
def is_satisfiable(self, outside_keys, unplaced_keys, big_key_loc, prize_location, cr_count):
|
||||||
|
if self.prize_relevance and prize_location:
|
||||||
|
if self.prize_relevance == 'BigBomb':
|
||||||
|
if prize_location.item.name not in ['Crystal 5', 'Crystal 6']:
|
||||||
|
return True
|
||||||
|
elif self.prize_relevance == 'GT':
|
||||||
|
if 'Crystal' not in prize_location.item.name or cr_count < 7:
|
||||||
|
return True
|
||||||
bk_blocked = False
|
bk_blocked = False
|
||||||
if self.bk_conditional_set:
|
if self.bk_conditional_set:
|
||||||
for loc in self.bk_conditional_set:
|
for loc in self.bk_conditional_set:
|
||||||
@@ -259,6 +268,7 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
find_bk_locked_sections(key_layout, world, player)
|
find_bk_locked_sections(key_layout, world, player)
|
||||||
key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations))
|
key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations))
|
||||||
key_logic.bk_chests.update(find_big_key_locked_locations(key_layout.all_chest_locations))
|
key_logic.bk_chests.update(find_big_key_locked_locations(key_layout.all_chest_locations))
|
||||||
|
key_logic.prize_location = dungeon_table[key_layout.sector.name].prize
|
||||||
if world.retro[player] and world.mode[player] != 'standard':
|
if world.retro[player] and world.mode[player] != 'standard':
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -283,6 +293,12 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
if not big_avail:
|
if not big_avail:
|
||||||
if chest_keys == non_big_locs and chest_keys > 0 and available <= possible_smalls and not avail_bigs:
|
if chest_keys == non_big_locs and chest_keys > 0 and available <= possible_smalls and not avail_bigs:
|
||||||
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
|
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
|
||||||
|
# note to self: this is due to the enough_small_locations function in validate_key_layout_sub_loop
|
||||||
|
# I don't like this exception here or there
|
||||||
|
elif available < possible_smalls and avail_bigs and non_big_locs > 0:
|
||||||
|
max_ctr = find_max_counter(key_layout)
|
||||||
|
bk_lockdown = [x for x in max_ctr.free_locations if x not in key_counter.free_locations]
|
||||||
|
key_logic.bk_restricted.update(filter_big_chest(bk_lockdown))
|
||||||
# try to relax the rules here? - smallest requirement that doesn't force a softlock
|
# try to relax the rules here? - smallest requirement that doesn't force a softlock
|
||||||
child_queue = deque()
|
child_queue = deque()
|
||||||
for child in key_counter.child_doors.keys():
|
for child in key_counter.child_doors.keys():
|
||||||
@@ -356,6 +372,7 @@ def create_exhaustive_placement_rules(key_layout, world, player):
|
|||||||
rule.bk_conditional_set = blocked_loc
|
rule.bk_conditional_set = blocked_loc
|
||||||
rule.needed_keys_wo_bk = min_keys
|
rule.needed_keys_wo_bk = min_keys
|
||||||
rule.check_locations_wo_bk = set(filter_big_chest(accessible_loc))
|
rule.check_locations_wo_bk = set(filter_big_chest(accessible_loc))
|
||||||
|
rule.prize_relevance = key_layout.prize_relevant if rule_prize_relevant(key_counter) else None
|
||||||
if valid_rule:
|
if valid_rule:
|
||||||
key_logic.placement_rules.append(rule)
|
key_logic.placement_rules.append(rule)
|
||||||
adjust_locations_rules(key_logic, rule, accessible_loc, key_layout, key_counter, max_ctr)
|
adjust_locations_rules(key_logic, rule, accessible_loc, key_layout, key_counter, max_ctr)
|
||||||
@@ -363,6 +380,10 @@ def create_exhaustive_placement_rules(key_layout, world, player):
|
|||||||
refine_location_rules(key_layout)
|
refine_location_rules(key_layout)
|
||||||
|
|
||||||
|
|
||||||
|
def rule_prize_relevant(key_counter):
|
||||||
|
return not key_counter.prize_doors_opened and not key_counter.prize_received
|
||||||
|
|
||||||
|
|
||||||
def skip_key_counter_due_to_prize(key_layout, key_counter):
|
def skip_key_counter_due_to_prize(key_layout, key_counter):
|
||||||
return key_layout.prize_relevant and key_counter.prize_received and not key_counter.prize_doors_opened
|
return key_layout.prize_relevant and key_counter.prize_received and not key_counter.prize_doors_opened
|
||||||
|
|
||||||
@@ -462,7 +483,7 @@ def refine_placement_rules(key_layout, max_ctr):
|
|||||||
if rule.needed_keys_wo_bk == 0:
|
if rule.needed_keys_wo_bk == 0:
|
||||||
rules_to_remove.append(rule)
|
rules_to_remove.append(rule)
|
||||||
if len(rule.check_locations_wo_bk) < rule.needed_keys_wo_bk or rule.needed_keys_wo_bk > key_layout.max_chests:
|
if len(rule.check_locations_wo_bk) < rule.needed_keys_wo_bk or rule.needed_keys_wo_bk > key_layout.max_chests:
|
||||||
if len(rule.bk_conditional_set) > 0:
|
if not rule.prize_relevance and len(rule.bk_conditional_set) > 0:
|
||||||
key_logic.bk_restricted.update(rule.bk_conditional_set)
|
key_logic.bk_restricted.update(rule.bk_conditional_set)
|
||||||
rules_to_remove.append(rule)
|
rules_to_remove.append(rule)
|
||||||
changed = True # impossible for bk to be here, I think
|
changed = True # impossible for bk to be here, I think
|
||||||
@@ -1365,6 +1386,15 @@ def forced_big_key_avail(locations):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def prize_relevance(key_layout, dungeon_entrance):
|
||||||
|
if len(key_layout.start_regions) > 1 and dungeon_entrance and dungeon_table[key_layout.key_logic.dungeon].prize:
|
||||||
|
if dungeon_entrance.name in ['Ganons Tower', 'Inverted Ganons Tower']:
|
||||||
|
return 'GT'
|
||||||
|
elif dungeon_entrance.name == 'Pyramid Fairy':
|
||||||
|
return 'BigBomb'
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Soft lock stuff
|
# Soft lock stuff
|
||||||
def validate_key_layout(key_layout, world, player):
|
def validate_key_layout(key_layout, world, player):
|
||||||
# retro is all good - except for hyrule castle in standard mode
|
# retro is all good - except for hyrule castle in standard mode
|
||||||
@@ -1376,12 +1406,11 @@ def validate_key_layout(key_layout, world, player):
|
|||||||
state.big_key_special = check_bk_special(key_layout.sector.regions, world, player)
|
state.big_key_special = check_bk_special(key_layout.sector.regions, world, player)
|
||||||
for region in key_layout.start_regions:
|
for region in key_layout.start_regions:
|
||||||
dungeon_entrance, portal_door = find_outside_connection(region)
|
dungeon_entrance, portal_door = find_outside_connection(region)
|
||||||
if (len(key_layout.start_regions) > 1 and dungeon_entrance and
|
prize_relevant_flag = prize_relevance(key_layout, dungeon_entrance)
|
||||||
dungeon_entrance.name in ['Ganons Tower', 'Inverted Ganons Tower', 'Pyramid Fairy']
|
if prize_relevant_flag:
|
||||||
and dungeon_table[key_layout.key_logic.dungeon].prize):
|
|
||||||
state.append_door_to_list(portal_door, state.prize_doors)
|
state.append_door_to_list(portal_door, state.prize_doors)
|
||||||
state.prize_door_set[portal_door] = dungeon_entrance
|
state.prize_door_set[portal_door] = dungeon_entrance
|
||||||
key_layout.prize_relevant = True
|
key_layout.prize_relevant = prize_relevant_flag
|
||||||
else:
|
else:
|
||||||
state.visit_region(region, key_checks=True)
|
state.visit_region(region, key_checks=True)
|
||||||
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
||||||
@@ -1409,7 +1438,8 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
|||||||
found_forced_bk = state.found_forced_bk()
|
found_forced_bk = state.found_forced_bk()
|
||||||
smalls_done = not smalls_avail or not enough_small_locations(state, available_small_locations)
|
smalls_done = not smalls_avail or not enough_small_locations(state, available_small_locations)
|
||||||
bk_done = state.big_key_opened or num_bigs == 0 or (available_big_locations == 0 and not found_forced_bk)
|
bk_done = state.big_key_opened or num_bigs == 0 or (available_big_locations == 0 and not found_forced_bk)
|
||||||
if smalls_done and bk_done:
|
prize_done = not key_layout.prize_relevant or state.prize_doors_opened
|
||||||
|
if smalls_done and bk_done and prize_done:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
# todo: pretty sure you should OR these paths together, maybe when there's one location and it can
|
# todo: pretty sure you should OR these paths together, maybe when there's one location and it can
|
||||||
@@ -1445,6 +1475,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
|||||||
if not valid:
|
if not valid:
|
||||||
return False
|
return False
|
||||||
# todo: feel like you only open these if the boss is available???
|
# todo: feel like you only open these if the boss is available???
|
||||||
|
# todo: or if a crystal isn't valid placement on this boss
|
||||||
if not state.prize_doors_opened and key_layout.prize_relevant:
|
if not state.prize_doors_opened and key_layout.prize_relevant:
|
||||||
state_copy = state.copy()
|
state_copy = state.copy()
|
||||||
open_a_door(next(iter(state_copy.prize_door_set)), state_copy, flat_proposal, world, player)
|
open_a_door(next(iter(state_copy.prize_door_set)), state_copy, flat_proposal, world, player)
|
||||||
@@ -1539,12 +1570,11 @@ def create_key_counters(key_layout, world, player):
|
|||||||
state.big_key_special = True
|
state.big_key_special = True
|
||||||
for region in key_layout.start_regions:
|
for region in key_layout.start_regions:
|
||||||
dungeon_entrance, portal_door = find_outside_connection(region)
|
dungeon_entrance, portal_door = find_outside_connection(region)
|
||||||
if (len(key_layout.start_regions) > 1 and dungeon_entrance and
|
prize_relevant_flag = prize_relevance(key_layout, dungeon_entrance)
|
||||||
dungeon_entrance.name in ['Ganons Tower', 'Inverted Ganons Tower', 'Pyramid Fairy']
|
if prize_relevant_flag:
|
||||||
and dungeon_table[key_layout.key_logic.dungeon].prize):
|
|
||||||
state.append_door_to_list(portal_door, state.prize_doors)
|
state.append_door_to_list(portal_door, state.prize_doors)
|
||||||
state.prize_door_set[portal_door] = dungeon_entrance
|
state.prize_door_set[portal_door] = dungeon_entrance
|
||||||
key_layout.prize_relevant = True
|
key_layout.prize_relevant = prize_relevant_flag
|
||||||
else:
|
else:
|
||||||
state.visit_region(region, key_checks=True)
|
state.visit_region(region, key_checks=True)
|
||||||
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
||||||
@@ -1610,8 +1640,13 @@ def can_open_door_by_counter(door, counter: KeyCounter, layout, world, player):
|
|||||||
# ttl_small_key_only = len(counter.key_only_locations)
|
# ttl_small_key_only = len(counter.key_only_locations)
|
||||||
return cnt_avail_small_locations_by_ctr(ttl_locations, counter, layout, world, player) > 0
|
return cnt_avail_small_locations_by_ctr(ttl_locations, counter, layout, world, player) > 0
|
||||||
elif door.bigKey:
|
elif door.bigKey:
|
||||||
|
if counter.big_key_opened:
|
||||||
|
return False
|
||||||
|
if layout.big_key_special:
|
||||||
|
return any(x for x in counter.other_locations.keys() if x.forced_item and x.forced_item.bigkey)
|
||||||
|
else:
|
||||||
available_big_locations = cnt_avail_big_locations_by_ctr(ttl_locations, counter, layout, world, player)
|
available_big_locations = cnt_avail_big_locations_by_ctr(ttl_locations, counter, layout, world, player)
|
||||||
return not counter.big_key_opened and available_big_locations > 0 and not layout.big_key_special
|
return available_big_locations > 0
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1968,8 +2003,10 @@ def validate_key_placement(key_layout, world, player):
|
|||||||
found_prize = any(x for x in counter.important_locations if '- Prize' in x.name)
|
found_prize = any(x for x in counter.important_locations if '- Prize' in x.name)
|
||||||
if not found_prize and dungeon_table[key_layout.sector.name].prize:
|
if not found_prize and dungeon_table[key_layout.sector.name].prize:
|
||||||
prize_loc = world.get_location(dungeon_table[key_layout.sector.name].prize, player)
|
prize_loc = world.get_location(dungeon_table[key_layout.sector.name].prize, player)
|
||||||
# todo: pyramid fairy only care about crystals 5 & 6
|
if key_layout.prize_relevant == 'BigBomb':
|
||||||
found_prize = 'Crystal' not in prize_loc.item.name
|
found_prize = prize_loc.item.name not in ['Crystal 5', 'Crystal 6']
|
||||||
|
elif key_layout.prize_relevant == 'GT':
|
||||||
|
found_prize = 'Crystal' not in prize_loc.item.name or world.crystals_needed_for_gt[player] < 7
|
||||||
else:
|
else:
|
||||||
found_prize = False
|
found_prize = False
|
||||||
can_progress = (not counter.big_key_opened and big_found and any(d.bigKey for d in counter.child_doors)) or \
|
can_progress = (not counter.big_key_opened and big_found and any(d.bigKey for d in counter.child_doors)) or \
|
||||||
|
|||||||
2
Main.py
2
Main.py
@@ -30,7 +30,7 @@ from Utils import output_path, parse_player_names
|
|||||||
|
|
||||||
from source.item.FillUtil import create_item_pool_config
|
from source.item.FillUtil import create_item_pool_config
|
||||||
|
|
||||||
__version__ = '0.5.1.0-u'
|
__version__ = '0.5.1.1-u'
|
||||||
|
|
||||||
from source.classes.BabelFish import BabelFish
|
from source.classes.BabelFish import BabelFish
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,17 @@ CLI: ```--bombbag```
|
|||||||
|
|
||||||
# Bug Fixes and Notes.
|
# Bug Fixes and Notes.
|
||||||
|
|
||||||
|
* 0.5.1.1
|
||||||
|
* Shop hints in ER are now more generic instead of using "near X" because they aren't near that anymore
|
||||||
|
* Added memory location for mutliworld scripts to read what item was just obtain (longer than one frame)
|
||||||
|
* Fix for bias in boss shuffle "full"
|
||||||
|
* Fix for certain lone big chests in keysanity (allowed you to get contents without big key)
|
||||||
|
* Fix for pinball checking
|
||||||
|
* Fix for multi-entrance dungeons
|
||||||
|
* 2 fixes for big key placement logic
|
||||||
|
* ensure big key is placed early if the validator assumes it)
|
||||||
|
* Open big key doors appropriately when generating rules and big key is forced somewhere
|
||||||
|
* Updated cutoff entrances for intensity 3
|
||||||
* 0.5.1.0
|
* 0.5.1.0
|
||||||
* Large logic refactor introducing a new method of key logic
|
* Large logic refactor introducing a new method of key logic
|
||||||
* Some performance optimization
|
* Some performance optimization
|
||||||
|
|||||||
@@ -1374,9 +1374,9 @@ location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
|
|||||||
'Red Shield Shop - Left': (None, None, False, 'for sale as a curiosity'),
|
'Red Shield Shop - Left': (None, None, False, 'for sale as a curiosity'),
|
||||||
'Red Shield Shop - Middle': (None, None, False, 'for sale as a curiosity'),
|
'Red Shield Shop - Middle': (None, None, False, 'for sale as a curiosity'),
|
||||||
'Red Shield Shop - Right': (None, None, False, 'for sale as a curiosity'),
|
'Red Shield Shop - Right': (None, None, False, 'for sale as a curiosity'),
|
||||||
'Potion Shop - Left': (None, None, False, 'for sale near the witch'),
|
'Potion Shop - Left': (None, None, False, 'for sale near potions'),
|
||||||
'Potion Shop - Middle': (None, None, False, 'for sale near the witch'),
|
'Potion Shop - Middle': (None, None, False, 'for sale near potions'),
|
||||||
'Potion Shop - Right': (None, None, False, 'for sale near the witch'),
|
'Potion Shop - Right': (None, None, False, 'for sale near potions'),
|
||||||
}
|
}
|
||||||
|
|
||||||
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
|
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
|
||||||
|
|||||||
16
Rom.py
16
Rom.py
@@ -32,7 +32,7 @@ from source.classes.SFX import randomize_sfx
|
|||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = '988f1546b14d8f2e6ee30b9de44882da'
|
RANDOMIZERBASEHASH = 'ef6e3e1aa59838c01dbd5b1b2387e70c'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
@@ -671,7 +671,19 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
dr_flags |= DROptions.Debug
|
dr_flags |= DROptions.Debug
|
||||||
if world.doorShuffle[player] == 'crossed' and world.logic[player] != 'nologic'\
|
if world.doorShuffle[player] == 'crossed' and world.logic[player] != 'nologic'\
|
||||||
and world.mixed_travel[player] == 'prevent':
|
and world.mixed_travel[player] == 'prevent':
|
||||||
dr_flags |= DROptions.Rails
|
# PoD Falling Bridge or Hammjump
|
||||||
|
# 1FA607: db $2D, $79, $69 ; 0x0069: Vertical Rail ↕ | { 0B, 1E } | Size: 05
|
||||||
|
# 1FA60A: db $14, $99, $5D ; 0x005D: Large Horizontal Rail ↔ | { 05, 26 } | Size: 01
|
||||||
|
rom.write_bytes(0xfa607, [0x2d, 0x79, 0x69, 0x14, 0x99, 0x5d])
|
||||||
|
# PoD Arena
|
||||||
|
# 1FA573: db $D4, $B2, $22 ; 0x0022: Horizontal Rail ↔ | { 35, 2C } | Size: 02
|
||||||
|
# 1FA576: db $D4, $CE, $22 ; 0x0022: Horizontal Rail ↔ | { 35, 33 } | Size: 01
|
||||||
|
# 1FA579: db $D9, $AE, $69 ; 0x0069: Vertical Rail ↕ | { 36, 2B } | Size: 06
|
||||||
|
rom.write_bytes(0xfa573, [0xd4, 0xb2, 0x22, 0xd4, 0xce, 0x22, 0xd9, 0xae, 0x69])
|
||||||
|
# Mire BK Pond
|
||||||
|
# 1FB1FC: db $C8, $9D, $69 ; 0x0069: Vertical Rail ↕ | { 32, 27 } | Size: 01
|
||||||
|
# 1FB1FF: db $B4, $AC, $5D ; 0x005D: Large Horizontal Rail ↔ | { 2D, 2B } | Size: 00
|
||||||
|
rom.write_bytes(0xfb1fc, [0xc8, 0x9d, 0x69, 0xb4, 0xac, 0x5d])
|
||||||
if world.standardize_palettes[player] == 'original':
|
if world.standardize_palettes[player] == 'original':
|
||||||
dr_flags |= DROptions.OriginalPalettes
|
dr_flags |= DROptions.OriginalPalettes
|
||||||
if world.experimental[player]:
|
if world.experimental[player]:
|
||||||
|
|||||||
6
Rules.py
6
Rules.py
@@ -1969,9 +1969,11 @@ def add_key_logic_rules(world, player):
|
|||||||
forbid_item(location, d_logic.small_key_name, player)
|
forbid_item(location, d_logic.small_key_name, player)
|
||||||
for door in d_logic.bk_doors:
|
for door in d_logic.bk_doors:
|
||||||
add_rule(world.get_entrance(door.name, player), create_rule(d_logic.bk_name, player))
|
add_rule(world.get_entrance(door.name, player), create_rule(d_logic.bk_name, player))
|
||||||
if len(d_logic.bk_doors) > 0 or len(d_logic.bk_chests) > 1:
|
|
||||||
for chest in d_logic.bk_chests:
|
for chest in d_logic.bk_chests:
|
||||||
add_rule(world.get_location(chest.name, player), create_rule(d_logic.bk_name, player))
|
big_chest = world.get_location(chest.name, player)
|
||||||
|
add_rule(big_chest, create_rule(d_logic.bk_name, player))
|
||||||
|
if len(d_logic.bk_doors) == 0 and len(d_logic.bk_chests) <= 1:
|
||||||
|
set_always_allow(big_chest, lambda state, item: item.name == d_logic.bk_name and item.player == player)
|
||||||
if world.retro[player]:
|
if world.retro[player]:
|
||||||
for d_name, layout in world.key_layout[player].items():
|
for d_name, layout in world.key_layout[player].items():
|
||||||
for door in layout.flat_prop:
|
for door in layout.flat_prop:
|
||||||
|
|||||||
@@ -582,6 +582,8 @@ dw $00bc, $00a2, $00a3, $00c2, $001a, $0049, $0014, $008c
|
|||||||
dw $009f, $0066, $005d, $00a8, $00a9, $00aa, $00b9, $0052
|
dw $009f, $0066, $005d, $00a8, $00a9, $00aa, $00b9, $0052
|
||||||
; HC West Hall, TR Dash Bridge, TR Hub, Pod Arena, GT Petting Zoo, Ice Spike Cross
|
; HC West Hall, TR Dash Bridge, TR Hub, Pod Arena, GT Petting Zoo, Ice Spike Cross
|
||||||
dw $0050, $00c5, $00c6, $0009, $0003, $002a, $007d, $005e
|
dw $0050, $00c5, $00c6, $0009, $0003, $002a, $007d, $005e
|
||||||
|
; Sewer Drop, Mire Cross, GT Crystal Circles
|
||||||
|
dw $0011, $00b2, $003d
|
||||||
dw $ffff
|
dw $ffff
|
||||||
|
|
||||||
; dungeon tables
|
; dungeon tables
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user