Merge branch 'Ambrosia' into Bias
This commit is contained in:
@@ -78,6 +78,7 @@ class World(object):
|
||||
self._room_cache = {}
|
||||
self.dungeon_layouts = {}
|
||||
self.inaccessible_regions = {}
|
||||
self.enabled_entrances = {}
|
||||
self.key_logic = {}
|
||||
self.pool_adjustment = {}
|
||||
self.key_layout = defaultdict(dict)
|
||||
@@ -143,6 +144,7 @@ class World(object):
|
||||
set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False})
|
||||
|
||||
set_player_attr('exp_cache', defaultdict(dict))
|
||||
set_player_attr('enabled_entrances', {})
|
||||
|
||||
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)})'
|
||||
@@ -1171,6 +1173,8 @@ class CollectionState(object):
|
||||
def collect(self, item, event=False, location=None):
|
||||
if location:
|
||||
self.locations_checked.add(location)
|
||||
if not item:
|
||||
return
|
||||
changed = False
|
||||
if item.name.startswith('Progressive '):
|
||||
if 'Sword' in item.name:
|
||||
|
||||
@@ -181,11 +181,11 @@ def place_bosses(world, player):
|
||||
|
||||
logging.getLogger('').debug('Bosses chosen %s', bosses)
|
||||
|
||||
random.shuffle(bosses)
|
||||
for [loc, level] in boss_locations:
|
||||
loc_text = loc + (' ('+level+')' if level else '')
|
||||
boss = next((b for b in bosses if can_place_boss(world, player, b, loc, level)), None)
|
||||
if not boss:
|
||||
try:
|
||||
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)
|
||||
bosses.remove(boss)
|
||||
|
||||
|
||||
@@ -778,7 +778,7 @@ def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map,
|
||||
|
||||
def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player):
|
||||
entrances_map, potentials, connections = connections_tuple
|
||||
enabled_entrances = {}
|
||||
enabled_entrances = world.enabled_entrances[player] = {}
|
||||
sector_queue = deque(dungeon_builders.values())
|
||||
last_key, loops = None, 0
|
||||
logging.getLogger('').info(world.fish.translate("cli", "cli", "generating.dungeon"))
|
||||
@@ -1607,7 +1607,7 @@ def find_key_door_candidates(region, checked, world, player):
|
||||
if d2.type == DoorType.Normal:
|
||||
room_b = world.get_room(d2.roomIndex, player)
|
||||
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:
|
||||
valid = kind in okay_normals
|
||||
if valid and 0 <= d2.doorListPos < 4:
|
||||
@@ -1624,6 +1624,12 @@ def find_key_door_candidates(region, checked, world, player):
|
||||
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):
|
||||
logger = logging.getLogger('')
|
||||
logger.debug('Key doors for %s', builder.name)
|
||||
@@ -1940,14 +1946,18 @@ def check_required_paths(paths, world, player):
|
||||
if dungeon_name in world.dungeon_layouts[player].keys():
|
||||
builder = world.dungeon_layouts[player][dungeon_name]
|
||||
if len(paths[dungeon_name]) > 0:
|
||||
states_to_explore = defaultdict(list)
|
||||
states_to_explore = {}
|
||||
for path in paths[dungeon_name]:
|
||||
if type(path) is tuple:
|
||||
states_to_explore[tuple([path[0]])] = path[1]
|
||||
states_to_explore[tuple([path[0]])] = (path[1], 'any')
|
||||
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
|
||||
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:
|
||||
dest_regs = [dest_regs]
|
||||
check_paths = convert_regions(dest_regs, world, player)
|
||||
@@ -1964,11 +1974,17 @@ def check_required_paths(paths, world, player):
|
||||
cached_initial_state = state
|
||||
else:
|
||||
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 check_for_pinball_fix(state, bad_region, 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:
|
||||
raise Exception('%s cannot reach %s' % (dungeon_name, bad_region.name))
|
||||
|
||||
@@ -2008,7 +2024,7 @@ def explore_state_not_inaccessible(state, 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
|
||||
breaking_region = None
|
||||
for region_target in check_paths:
|
||||
@@ -2020,6 +2036,13 @@ def check_if_regions_visited(state, check_paths):
|
||||
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):
|
||||
pinball_region = world.get_region('Skull Pinball', player)
|
||||
# todo: lobby shuffle
|
||||
@@ -2043,7 +2066,7 @@ class DROptions(Flag):
|
||||
Town_Portal = 0x02 # If on, Players will start with mirror scroll
|
||||
Map_Info = 0x04
|
||||
Debug = 0x08
|
||||
Rails = 0x10 # If on, draws rails
|
||||
# Rails = 0x10 # Unused bit now
|
||||
OriginalPalettes = 0x20
|
||||
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
|
||||
|
||||
@@ -109,7 +109,8 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
|
||||
p_region = portal.door.entrance.connected_region
|
||||
access_region = next(x.parent_region for x in p_region.entrances
|
||||
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
|
||||
entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
|
||||
doors_to_connect = {}
|
||||
|
||||
13
Fill.py
13
Fill.py
@@ -161,7 +161,11 @@ def valid_key_placement(item, location, itempool, world):
|
||||
return True
|
||||
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])
|
||||
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:
|
||||
return item.is_inside_dungeon_item(world)
|
||||
|
||||
@@ -602,7 +606,7 @@ def balance_money_progression(world):
|
||||
base_value = sum(rupee_rooms.values())
|
||||
available_money = {player: base_value for player in range(1, world.players+1)}
|
||||
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]
|
||||
|
||||
total_price = {player: 0 for player in range(1, world.players+1)}
|
||||
@@ -667,7 +671,7 @@ def balance_money_progression(world):
|
||||
slot = shop_to_location_table[location.parent_region.name].index(location.name)
|
||||
shop = location.parent_region.shop
|
||||
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 shop_item['price'] < rupee_chart[location.item.name]:
|
||||
wallet[loc_player] -= shop_item['price'] # will get picked up in the location_free block
|
||||
@@ -687,6 +691,7 @@ def balance_money_progression(world):
|
||||
if location_free:
|
||||
state.collect(location.item, True, location)
|
||||
unchecked_locations.remove(location)
|
||||
if location.item:
|
||||
if location.item.name.startswith('Rupee'):
|
||||
wallet[location.item.player] += rupee_chart[location.item.name]
|
||||
if location.item.name != 'Rupees (300)':
|
||||
@@ -759,5 +764,5 @@ def balance_money_progression(world):
|
||||
else:
|
||||
state.collect(location.item, True, 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]
|
||||
|
||||
10
ItemList.py
10
ItemList.py
@@ -609,6 +609,7 @@ def customize_shops(world, player):
|
||||
upgrade.location = loc
|
||||
change_shop_items_to_rupees(world, player, shops_to_customize)
|
||||
balance_prices(world, player)
|
||||
check_hints(world, player)
|
||||
|
||||
|
||||
def randomize_price(price):
|
||||
@@ -712,6 +713,15 @@ def balance_prices(world, player):
|
||||
# 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',
|
||||
'Blue Shield', 'Red Shield', 'Bee', 'Small Key (Universal)', 'Blue Potion', 'Green Potion']
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class KeyLayout(object):
|
||||
self.item_locations = set()
|
||||
|
||||
self.found_doors = set()
|
||||
self.prize_relevant = False
|
||||
self.prize_relevant = None
|
||||
# bk special?
|
||||
# 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.all_locations = set()
|
||||
self.item_locations = set()
|
||||
self.prize_relevant = False
|
||||
self.prize_relevant = None
|
||||
|
||||
|
||||
class KeyLogic(object):
|
||||
@@ -59,10 +59,11 @@ class KeyLogic(object):
|
||||
self.outside_keys = 0
|
||||
self.dungeon = dungeon_name
|
||||
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:
|
||||
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
|
||||
if big_key_loc:
|
||||
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.bk_relevant = True
|
||||
self.key_reduced = False
|
||||
self.prize_relevance = None
|
||||
|
||||
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
|
||||
@@ -155,7 +157,14 @@ class PlacementRule(object):
|
||||
left -= rule_needed
|
||||
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
|
||||
if self.bk_conditional_set:
|
||||
for loc in self.bk_conditional_set:
|
||||
@@ -261,6 +270,7 @@ def analyze_dungeon(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_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':
|
||||
return
|
||||
|
||||
@@ -285,6 +295,12 @@ def analyze_dungeon(key_layout, world, player):
|
||||
if not big_avail:
|
||||
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))
|
||||
# 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
|
||||
child_queue = deque()
|
||||
for child in key_counter.child_doors.keys():
|
||||
@@ -358,6 +374,7 @@ def create_exhaustive_placement_rules(key_layout, world, player):
|
||||
rule.bk_conditional_set = blocked_loc
|
||||
rule.needed_keys_wo_bk = min_keys
|
||||
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:
|
||||
key_logic.placement_rules.append(rule)
|
||||
adjust_locations_rules(key_logic, rule, accessible_loc, key_layout, key_counter, max_ctr)
|
||||
@@ -365,6 +382,10 @@ def create_exhaustive_placement_rules(key_layout, world, player):
|
||||
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):
|
||||
return key_layout.prize_relevant and key_counter.prize_received and not key_counter.prize_doors_opened
|
||||
|
||||
@@ -464,7 +485,7 @@ def refine_placement_rules(key_layout, max_ctr):
|
||||
if rule.needed_keys_wo_bk == 0:
|
||||
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.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)
|
||||
rules_to_remove.append(rule)
|
||||
changed = True # impossible for bk to be here, I think
|
||||
@@ -1367,6 +1388,15 @@ def forced_big_key_avail(locations):
|
||||
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
|
||||
def validate_key_layout(key_layout, world, player):
|
||||
# retro is all good - except for hyrule castle in standard mode
|
||||
@@ -1378,12 +1408,11 @@ def validate_key_layout(key_layout, world, player):
|
||||
state.big_key_special = check_bk_special(key_layout.sector.regions, world, player)
|
||||
for region in key_layout.start_regions:
|
||||
dungeon_entrance, portal_door = find_outside_connection(region)
|
||||
if (len(key_layout.start_regions) > 1 and dungeon_entrance and
|
||||
dungeon_entrance.name in ['Ganons Tower', 'Inverted Ganons Tower', 'Pyramid Fairy']
|
||||
and dungeon_table[key_layout.key_logic.dungeon].prize):
|
||||
prize_relevant_flag = prize_relevance(key_layout, dungeon_entrance)
|
||||
if prize_relevant_flag:
|
||||
state.append_door_to_list(portal_door, state.prize_doors)
|
||||
state.prize_door_set[portal_door] = dungeon_entrance
|
||||
key_layout.prize_relevant = True
|
||||
key_layout.prize_relevant = prize_relevant_flag
|
||||
else:
|
||||
state.visit_region(region, key_checks=True)
|
||||
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
||||
@@ -1411,7 +1440,8 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
||||
found_forced_bk = state.found_forced_bk()
|
||||
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)
|
||||
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
|
||||
else:
|
||||
# todo: pretty sure you should OR these paths together, maybe when there's one location and it can
|
||||
@@ -1447,6 +1477,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
||||
if not valid:
|
||||
return False
|
||||
# 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:
|
||||
state_copy = state.copy()
|
||||
open_a_door(next(iter(state_copy.prize_door_set)), state_copy, flat_proposal, world, player)
|
||||
@@ -1541,12 +1572,11 @@ def create_key_counters(key_layout, world, player):
|
||||
state.big_key_special = True
|
||||
for region in key_layout.start_regions:
|
||||
dungeon_entrance, portal_door = find_outside_connection(region)
|
||||
if (len(key_layout.start_regions) > 1 and dungeon_entrance and
|
||||
dungeon_entrance.name in ['Ganons Tower', 'Inverted Ganons Tower', 'Pyramid Fairy']
|
||||
and dungeon_table[key_layout.key_logic.dungeon].prize):
|
||||
prize_relevant_flag = prize_relevance(key_layout, dungeon_entrance)
|
||||
if prize_relevant_flag:
|
||||
state.append_door_to_list(portal_door, state.prize_doors)
|
||||
state.prize_door_set[portal_door] = dungeon_entrance
|
||||
key_layout.prize_relevant = True
|
||||
key_layout.prize_relevant = prize_relevant_flag
|
||||
else:
|
||||
state.visit_region(region, key_checks=True)
|
||||
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
||||
@@ -1612,8 +1642,13 @@ def can_open_door_by_counter(door, counter: KeyCounter, layout, world, player):
|
||||
# ttl_small_key_only = len(counter.key_only_locations)
|
||||
return cnt_avail_small_locations_by_ctr(ttl_locations, counter, layout, world, player) > 0
|
||||
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)
|
||||
return not counter.big_key_opened and available_big_locations > 0 and not layout.big_key_special
|
||||
return available_big_locations > 0
|
||||
else:
|
||||
return True
|
||||
|
||||
@@ -1970,8 +2005,10 @@ def validate_key_placement(key_layout, world, player):
|
||||
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:
|
||||
prize_loc = world.get_location(dungeon_table[key_layout.sector.name].prize, player)
|
||||
# todo: pyramid fairy only care about crystals 5 & 6
|
||||
found_prize = 'Crystal' not in prize_loc.item.name
|
||||
if key_layout.prize_relevant == 'BigBomb':
|
||||
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:
|
||||
found_prize = False
|
||||
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
@@ -31,7 +31,7 @@ from Utils import output_path, parse_player_names
|
||||
from source.item.BiasedFill import create_item_pool_config, massage_item_pool
|
||||
|
||||
|
||||
__version__ = '0.5.1.0-u'
|
||||
__version__ = '0.5.1.1-u'
|
||||
|
||||
from source.classes.BabelFish import BabelFish
|
||||
|
||||
|
||||
@@ -15,6 +15,17 @@ CLI: ```--bombbag```
|
||||
|
||||
# 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
|
||||
* Large logic refactor introducing a new method of key logic
|
||||
* Some performance optimization
|
||||
|
||||
@@ -1382,9 +1382,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 - Middle': (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 - Middle': (None, None, False, 'for sale near the witch'),
|
||||
'Potion Shop - Right': (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 potions'),
|
||||
'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}
|
||||
|
||||
16
Rom.py
16
Rom.py
@@ -32,7 +32,7 @@ from source.classes.SFX import randomize_sfx
|
||||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = '988f1546b14d8f2e6ee30b9de44882da'
|
||||
RANDOMIZERBASEHASH = 'ef6e3e1aa59838c01dbd5b1b2387e70c'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
@@ -671,7 +671,19 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
dr_flags |= DROptions.Debug
|
||||
if world.doorShuffle[player] == 'crossed' and world.logic[player] != 'nologic'\
|
||||
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':
|
||||
dr_flags |= DROptions.OriginalPalettes
|
||||
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)
|
||||
for door in d_logic.bk_doors:
|
||||
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:
|
||||
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]:
|
||||
for d_name, layout in world.key_layout[player].items():
|
||||
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
|
||||
; 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
|
||||
; Sewer Drop, Mire Cross, GT Crystal Circles
|
||||
dw $0011, $00b2, $003d
|
||||
dw $ffff
|
||||
|
||||
; dungeon tables
|
||||
|
||||
Binary file not shown.
25
source/item/FillUtil.py
Normal file
25
source/item/FillUtil.py
Normal file
@@ -0,0 +1,25 @@
|
||||
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:
|
||||
for item in dungeon.all_items:
|
||||
if item.map or item.compass:
|
||||
item.advancement = True
|
||||
Reference in New Issue
Block a user