Merged in DR v0.5.1.1
This commit is contained in:
@@ -87,6 +87,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)
|
||||
@@ -153,6 +154,7 @@ class World(object):
|
||||
set_player_attr('prizes', {'pull': [0, 0, 0], 'crab': [0, 0], 'stun': 0, 'fish': 0})
|
||||
|
||||
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)})'
|
||||
@@ -485,8 +487,6 @@ class World(object):
|
||||
return True
|
||||
state = starting_state.copy()
|
||||
else:
|
||||
if self.has_beaten_game(self.state):
|
||||
return True
|
||||
state = CollectionState(self)
|
||||
|
||||
if self.has_beaten_game(state):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -751,7 +751,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"))
|
||||
@@ -1456,6 +1456,14 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True
|
||||
random.shuffle(sample_list)
|
||||
proposal = kth_combination(sample_list[itr], builder.candidates, builder.key_doors_num)
|
||||
|
||||
# eliminate start region if portal marked as destination
|
||||
excluded = {}
|
||||
for region in start_regions:
|
||||
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region == region), None)
|
||||
if portal and portal.destination:
|
||||
excluded[region] = None
|
||||
start_regions = [x for x in start_regions if x not in excluded.keys()]
|
||||
|
||||
key_layout = build_key_layout(builder, start_regions, proposal, world, player)
|
||||
while not validate_key_layout(key_layout, world, player):
|
||||
itr += 1
|
||||
@@ -1914,14 +1922,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)
|
||||
@@ -1938,11 +1950,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))
|
||||
|
||||
@@ -1982,7 +2000,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:
|
||||
@@ -1994,6 +2012,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
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
44
Dungeons.py
44
Dungeons.py
@@ -375,6 +375,38 @@ flexible_starts = {
|
||||
'Skull Woods': ['Skull Left Drop', 'Skull Pinball']
|
||||
}
|
||||
|
||||
|
||||
class DungeonInfo:
|
||||
|
||||
def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize=None):
|
||||
# todo reduce static maps ideas: prize, bk_name, sm_name, cmp_name, map_name):
|
||||
self.free_items = free
|
||||
self.key_num = keys
|
||||
self.bk_present = bk
|
||||
self.map_present = map
|
||||
self.compass_present = compass
|
||||
self.bk_drops = bk_drop
|
||||
self.key_drops = drops
|
||||
self.prize = prize
|
||||
|
||||
|
||||
dungeon_table = {
|
||||
'Hyrule Castle': DungeonInfo(6, 1, False, True, False, True, 3, None),
|
||||
'Eastern Palace': DungeonInfo(3, 0, True, True, True, False, 2, 'Eastern Palace - Prize'),
|
||||
'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, 'Desert Palace - Prize'),
|
||||
'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, 'Tower of Hera - Prize'),
|
||||
'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None),
|
||||
'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, 'Palace of Darkness - Prize'),
|
||||
'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, 'Swamp Palace - Prize'),
|
||||
'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, 'Skull Woods - Prize'),
|
||||
'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, "Thieves' Town - Prize"),
|
||||
'Ice Palace': DungeonInfo(3, 2, True, True, True, False, 4, 'Ice Palace - Prize'),
|
||||
'Misery Mire': DungeonInfo(2, 3, True, True, True, False, 3, 'Misery Mire - Prize'),
|
||||
'Turtle Rock': DungeonInfo(5, 4, True, True, True, False, 2, 'Turtle Rock - Prize'),
|
||||
'Ganons Tower': DungeonInfo(20, 4, True, True, True, False, 4, None),
|
||||
}
|
||||
|
||||
|
||||
dungeon_keys = {
|
||||
'Hyrule Castle': 'Small Key (Escape)',
|
||||
'Eastern Palace': 'Small Key (Eastern Palace)',
|
||||
@@ -407,18 +439,6 @@ dungeon_bigs = {
|
||||
'Ganons Tower': 'Big Key (Ganons Tower)'
|
||||
}
|
||||
|
||||
dungeon_prize = {
|
||||
'Eastern Palace': 'Eastern Palace - Prize',
|
||||
'Desert Palace': 'Desert Palace - Prize',
|
||||
'Tower of Hera': 'Tower of Hera - Prize',
|
||||
'Palace of Darkness': 'Palace of Darkness - Prize',
|
||||
'Swamp Palace': 'Swamp Palace - Prize',
|
||||
'Skull Woods': 'Skull Woods - Prize',
|
||||
'Thieves Town': 'Thieves Town - Prize',
|
||||
'Ice Palace': 'Ice Palace - Prize',
|
||||
'Misery Mire': 'Misery Mire - Prize',
|
||||
'Turtle Rock': 'Turtle Rock - Prize',
|
||||
}
|
||||
|
||||
dungeon_hints = {
|
||||
'Hyrule Castle': 'in Hyrule Castle',
|
||||
|
||||
10
ItemList.py
10
ItemList.py
@@ -606,6 +606,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):
|
||||
@@ -709,6 +710,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']
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ 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 Dungeons import dungeon_keys, dungeon_bigs, dungeon_table
|
||||
from DungeonGenerator import ExplorationState, special_big_key_doors
|
||||
|
||||
|
||||
@@ -282,6 +282,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():
|
||||
@@ -1387,7 +1393,7 @@ def validate_key_layout(key_layout, world, player):
|
||||
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', 'Pyramid Fairy']
|
||||
and key_layout.key_logic.dungeon in dungeon_prize):
|
||||
and dungeon_table[key_layout.key_logic.dungeon].prize):
|
||||
state.append_door_to_list(portal_door, state.prize_doors)
|
||||
state.prize_door_set[portal_door] = dungeon_entrance
|
||||
key_layout.prize_relevant = True
|
||||
@@ -1550,7 +1556,7 @@ def create_key_counters(key_layout, world, player):
|
||||
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', 'Pyramid Fairy']
|
||||
and key_layout.key_logic.dungeon in dungeon_prize):
|
||||
and dungeon_table[key_layout.key_logic.dungeon].prize):
|
||||
state.append_door_to_list(portal_door, state.prize_doors)
|
||||
state.prize_door_set[portal_door] = dungeon_entrance
|
||||
key_layout.prize_relevant = True
|
||||
@@ -1619,8 +1625,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:
|
||||
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
|
||||
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 available_big_locations > 0
|
||||
else:
|
||||
return True
|
||||
|
||||
@@ -1975,8 +1986,8 @@ def validate_key_placement(key_layout, world, player):
|
||||
len(counter.key_only_locations) + keys_outside
|
||||
if key_layout.prize_relevant:
|
||||
found_prize = any(x for x in counter.important_locations if '- Prize' in x.name)
|
||||
if not found_prize and key_layout.sector.name in dungeon_prize:
|
||||
prize_loc = world.get_location(dungeon_prize[key_layout.sector.name], player)
|
||||
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
|
||||
else:
|
||||
|
||||
2
Main.py
2
Main.py
@@ -29,7 +29,7 @@ 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
|
||||
|
||||
__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
|
||||
|
||||
@@ -1536,9 +1536,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}
|
||||
|
||||
8
Rules.py
8
Rules.py
@@ -2340,9 +2340,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))
|
||||
for chest in d_logic.bk_chests:
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user