Merge remote-tracking branch 'remotes/multi/multiworld_31' into Dev

This commit is contained in:
compiling
2020-01-04 20:07:29 +11:00
52 changed files with 3306 additions and 1394 deletions

View File

@@ -35,7 +35,7 @@ Difficulty = namedtuple('Difficulty',
'progressivesword', 'basicsword', 'basicbow', 'timedohko', 'timedother',
'triforcehunt', 'triforce_pieces_required', 'retro',
'extras', 'progressive_sword_limit', 'progressive_shield_limit',
'progressive_armor_limit', 'progressive_bottle_limit',
'progressive_armor_limit', 'progressive_bottle_limit',
'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit'])
total_items_to_place = 153
@@ -125,26 +125,23 @@ difficulties = {
}
def generate_itempool(world, player):
if (world.difficulty not in ['normal', 'hard', 'expert'] or world.goal not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals']
or world.mode not in ['open', 'standard', 'inverted'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']):
if (world.difficulty[player] not in ['normal', 'hard', 'expert'] or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals']
or world.mode[player] not in ['open', 'standard', 'inverted'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']):
raise NotImplementedError('Not supported yet')
if world.timer in ['ohko', 'timed-ohko']:
world.can_take_damage = False
if world.goal in ['pedestal', 'triforcehunt']:
if world.goal[player] in ['pedestal', 'triforcehunt']:
world.push_item(world.get_location('Ganon', player), ItemFactory('Nothing', player), False)
else:
world.push_item(world.get_location('Ganon', player), ItemFactory('Triforce', player), False)
if world.goal in ['triforcehunt']:
if world.mode == 'inverted':
region = world.get_region('Light World',player)
else:
region = world.get_region('Hyrule Castle Courtyard', player)
if world.goal[player] in ['triforcehunt']:
region = world.get_region('Light World',player)
loc = Location(player, "Murahdahla", parent=region)
loc.access_rule = lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) > state.world.treasure_hunt_count
loc.access_rule = lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) > state.world.treasure_hunt_count[player]
region.locations.append(loc)
world.dynamic_locations.append(loc)
@@ -153,7 +150,7 @@ def generate_itempool(world, player):
world.push_item(loc, ItemFactory('Triforce', player), False)
loc.event = True
loc.locked = True
world.get_location('Ganon', player).event = True
world.get_location('Ganon', player).locked = True
world.push_item(world.get_location('Agahnim 1', player), ItemFactory('Beat Agahnim 1', player), False)
@@ -177,38 +174,82 @@ def generate_itempool(world, player):
# set up item pool
if world.custom:
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.swords, world.retro, world.customitemarray)
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.customitemarray)
world.rupoor_cost = min(world.customitemarray[69], 9999)
else:
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.swords, world.retro)
world.itempool += ItemFactory(pool, player)
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player])
for item in precollected_items:
world.push_precollected(ItemFactory(item, player))
for (location, item) in placed_items:
if world.mode[player] == 'standard' and not world.state.has_blunt_weapon(player):
if "Link's Uncle" not in placed_items:
found_sword = False
found_bow = False
possible_weapons = []
for item in pool:
if item in ['Progressive Sword', 'Fighter Sword', 'Master Sword', 'Tempered Sword', 'Golden Sword']:
if not found_sword and world.swords[player] != 'swordless':
found_sword = True
possible_weapons.append(item)
if item in ['Progressive Bow', 'Bow'] and not found_bow:
found_bow = True
possible_weapons.append(item)
if item in ['Hammer', 'Bombs (10)', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']:
if item not in possible_weapons:
possible_weapons.append(item)
starting_weapon = random.choice(possible_weapons)
placed_items["Link's Uncle"] = starting_weapon
pool.remove(starting_weapon)
if placed_items["Link's Uncle"] in ['Bow', 'Progressive Bow', 'Bombs (10)', 'Cane of Somaria', 'Cane of Byrna'] and world.enemy_health[player] not in ['default', 'easy']:
world.escape_assist[player].append('bombs')
for (location, item) in placed_items.items():
world.push_item(world.get_location(location, player), ItemFactory(item, player), False)
world.get_location(location, player).event = True
world.get_location(location, player).locked = True
items = ItemFactory(pool, player)
world.lamps_needed_for_dark_rooms = lamps_needed_for_dark_rooms
if clock_mode is not None:
world.clock_mode = clock_mode
if treasure_hunt_count is not None:
world.treasure_hunt_count = treasure_hunt_count
if treasure_hunt_icon is not None:
world.treasure_hunt_icon = treasure_hunt_icon
if world.keysanity:
world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player])
if treasure_hunt_count is not None:
world.treasure_hunt_count[player] = treasure_hunt_count
if treasure_hunt_icon is not None:
world.treasure_hunt_icon[player] = treasure_hunt_icon
world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player
and ((item.smallkey and world.keyshuffle[player])
or (item.bigkey and world.bigkeyshuffle[player])
or (item.map and world.mapshuffle[player])
or (item.compass and world.compassshuffle[player]))])
# logic has some branches where having 4 hearts is one possible requirement (of several alternatives)
# rather than making all hearts/heart pieces progression items (which slows down generation considerably)
# We mark one random heart container as an advancement item (or 4 heart pieces in expert mode)
if world.difficulty in ['normal', 'hard'] and not (world.custom and world.customitemarray[30] == 0):
[item for item in world.itempool if item.name == 'Boss Heart Container' and item.player == player][0].advancement = True
elif world.difficulty in ['expert'] and not (world.custom and world.customitemarray[29] < 4):
adv_heart_pieces = [item for item in world.itempool if item.name == 'Piece of Heart' and item.player == player][0:4]
if world.difficulty[player] in ['normal', 'hard'] and not (world.custom and world.customitemarray[30] == 0):
[item for item in items if item.name == 'Boss Heart Container'][0].advancement = True
elif world.difficulty[player] in ['expert'] and not (world.custom and world.customitemarray[29] < 4):
adv_heart_pieces = [item for item in items if item.name == 'Piece of Heart'][0:4]
for hp in adv_heart_pieces:
hp.advancement = True
beeweights = {0: {None: 100},
1: {None: 75, 'trap': 25},
2: {None: 40, 'trap': 40, 'bee': 20},
3: {'trap': 50, 'bee': 50},
4: {'trap': 100}}
def beemizer(item):
if world.beemizer[item.player] and not item.advancement and not item.priority and not item.type:
choice = random.choices(list(beeweights[world.beemizer[item.player]].keys()), weights=list(beeweights[world.beemizer[item.player]].values()))[0]
return item if not choice else ItemFactory("Bee Trap", player) if choice == 'trap' else ItemFactory("Bee", player)
return item
world.itempool += [beemizer(item) for item in items]
# shuffle medallions
mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
@@ -217,7 +258,7 @@ def generate_itempool(world, player):
place_bosses(world, player)
set_up_shops(world, player)
if world.retro:
if world.retro[player]:
set_up_take_anys(world, player)
create_dynamic_shop_locations(world, player)
@@ -233,9 +274,9 @@ take_any_locations = [
'Dark Lake Hylia Ledge Spike Cave', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Desert Hint']
def set_up_take_anys(world, player):
if world.mode == 'inverted' and 'Dark Sanctuary Hint' in take_any_locations:
if world.mode[player] == 'inverted' and 'Dark Sanctuary Hint' in take_any_locations:
take_any_locations.remove('Dark Sanctuary Hint')
regions = random.sample(take_any_locations, 5)
old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave, 'the sword cave', player)
@@ -275,7 +316,7 @@ def set_up_take_anys(world, player):
take_any.shop.add_inventory(0, 'Blue Potion', 0, 0)
take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0)
world.intialize_regions()
world.initialize_regions()
def create_dynamic_shop_locations(world, player):
for shop in world.shops:
@@ -312,9 +353,9 @@ def fill_prizes(world, attempts=15):
prize_locs = list(empty_crystal_locations)
random.shuffle(prizepool)
random.shuffle(prize_locs)
fill_restrictive(world, all_state, prize_locs, prizepool)
fill_restrictive(world, all_state, prize_locs, prizepool, True)
except FillError as e:
logging.getLogger('').info("Failed to place dungeon prizes (%s). Will retry %s more times", e, attempts)
logging.getLogger('').info("Failed to place dungeon prizes (%s). Will retry %s more times", e, attempts - attempt - 1)
for location in empty_crystal_locations:
location.item = None
continue
@@ -330,13 +371,13 @@ def set_up_shops(world, player):
for shop in world.shops:
shop.active = True
if world.retro:
if world.retro[player]:
rss = world.get_region('Red Shield Shop', player).shop
rss.active = True
rss.add_inventory(2, 'Single Arrow', 80)
# Randomized changes to Shops
if world.retro:
if world.retro[player]:
for shop in random.sample([s for s in world.shops if s.replaceable and s.type == ShopType.Shop and s.region.player == player], 5):
shop.active = True
shop.add_inventory(0, 'Single Arrow', 80)
@@ -347,7 +388,7 @@ def set_up_shops(world, player):
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro):
pool = []
placed_items = []
placed_items = {}
precollected_items = []
clock_mode = None
treasure_hunt_count = None
@@ -355,6 +396,10 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
pool.extend(alwaysitems)
def place_item(loc, item):
assert loc not in placed_items
placed_items[loc] = item
def want_progressives():
return random.choice([True, False]) if progressive == 'random' else progressive == 'on'
@@ -367,8 +412,8 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
# insanity shuffle doesn't have fake LW/DW logic so for now guaranteed Mirror and Moon Pearl at the start
if shuffle == 'insanity_legacy':
placed_items.append(('Link\'s House', 'Magic Mirror'))
placed_items.append(('Sanctuary', 'Moon Pearl'))
place_item('Link\'s House', 'Magic Mirror')
place_item('Sanctuary', 'Moon Pearl')
else:
pool.extend(['Magic Mirror', 'Moon Pearl'])
@@ -429,13 +474,13 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
swords_to_use.extend(['Fighter Sword'])
random.shuffle(swords_to_use)
placed_items.append(('Link\'s Uncle', swords_to_use.pop()))
placed_items.append(('Blacksmith', swords_to_use.pop()))
placed_items.append(('Pyramid Fairy - Left', swords_to_use.pop()))
place_item('Link\'s Uncle', swords_to_use.pop())
place_item('Blacksmith', swords_to_use.pop())
place_item('Pyramid Fairy - Left', swords_to_use.pop())
if goal != 'pedestal':
placed_items.append(('Master Sword Pedestal', swords_to_use.pop()))
place_item('Master Sword Pedestal', swords_to_use.pop())
else:
placed_items.append(('Master Sword Pedestal', 'Triforce'))
place_item('Master Sword Pedestal', 'Triforce')
else:
if want_progressives():
pool.extend(diff.progressivesword)
@@ -466,7 +511,7 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
extraitems -= len(extra)
if goal == 'pedestal' and swords != 'vanilla':
placed_items.append(('Master Sword Pedestal', 'Triforce'))
place_item('Master Sword Pedestal', 'Triforce')
if retro:
pool = [item.replace('Single Arrow','Rupees (5)') for item in pool]
pool = [item.replace('Arrows (10)','Rupees (5)') for item in pool]
@@ -475,19 +520,23 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
pool.extend(diff.retro)
if mode == 'standard':
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
placed_items.append((key_location, 'Small Key (Universal)'))
place_item(key_location, 'Small Key (Universal)')
else:
pool.extend(['Small Key (Universal)'])
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, customitemarray):
pool = []
placed_items = []
placed_items = {}
precollected_items = []
clock_mode = None
treasure_hunt_count = None
treasure_hunt_icon = None
def place_item(loc, item):
assert loc not in placed_items
placed_items[loc] = item
# Correct for insanely oversized item counts and take initial steps to handle undersized pools.
for x in range(0, 66):
if customitemarray[x] > total_items_to_place:
@@ -594,13 +643,13 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
clock_mode = 'ohko'
if goal == 'pedestal':
placed_items.append(('Master Sword Pedestal', 'Triforce'))
place_item('Master Sword Pedestal', 'Triforce')
itemtotal = itemtotal + 1
if mode == 'standard':
if retro:
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
placed_items.append((key_location, 'Small Key (Universal)'))
place_item(key_location, 'Small Key (Universal)')
pool.extend(['Small Key (Universal)'] * max((customitemarray[70] - 1), 0))
else:
pool.extend(['Small Key (Universal)'] * customitemarray[70])
@@ -611,8 +660,8 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
pool.extend(['Progressive Sword'] * customitemarray[36])
if shuffle == 'insanity_legacy':
placed_items.append(('Link\'s House', 'Magic Mirror'))
placed_items.append(('Sanctuary', 'Moon Pearl'))
place_item('Link\'s House', 'Magic Mirror')
place_item('Sanctuary', 'Moon Pearl')
pool.extend(['Magic Mirror'] * max((customitemarray[22] -1 ), 0))
pool.extend(['Moon Pearl'] * max((customitemarray[28] - 1), 0))
else: