Merge remote-tracking branch 'origin/OverworldShuffle' into OverworldShuffle

This commit is contained in:
2022-09-18 18:57:25 -07:00
78 changed files with 8114 additions and 2984 deletions

View File

@@ -3,13 +3,16 @@ import logging
import math
import RaceRandom as random
from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState
from Dungeons import get_dungeon_item_pool
from BaseClasses import LocationType, Region, RegionType, Shop, ShopType, Location, CollectionState, PotItem
from EntranceShuffle import connect_entrance
from Regions import shop_to_location_table, retro_shops, shop_table_by_location
from Fill import FillError, fill_restrictive, fast_fill
from Regions import shop_to_location_table, retro_shops, shop_table_by_location, valid_pot_location
from Fill import FillError, fill_restrictive, fast_fill, get_dungeon_item_pool
from PotShuffle import vanilla_pots
from Tables import bonk_prize_lookup
from Items import ItemFactory
from source.item.FillUtil import trash_items, pot_items
import source.classes.constants as CONST
@@ -42,6 +45,7 @@ Difficulty = namedtuple('Difficulty',
'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit'])
total_items_to_place = 153
max_goal = 850
difficulties = {
'normal': Difficulty(
@@ -208,6 +212,7 @@ def generate_itempool(world, player):
world.push_item(loc, ItemFactory('Triforce', player), False)
loc.event = True
loc.locked = True
loc.forced_item = loc.item
world.get_location('Ganon', player).event = True
world.get_location('Ganon', player).locked = True
@@ -259,6 +264,9 @@ def generate_itempool(world, player):
world.push_item(world.get_location('Ice Block Drop', player), ItemFactory('Convenient Block', player), False)
world.get_location('Ice Block Drop', player).event = True
world.get_location('Ice Block Drop', player).locked = True
world.push_item(world.get_location('Skull Star Tile', player), ItemFactory('Hidden Pits', player), False)
world.get_location('Skull Star Tile', player).event = True
world.get_location('Skull Star Tile', player).locked = True
if world.mode[player] == 'standard':
world.push_item(world.get_location('Zelda Pickup', player), ItemFactory('Zelda Herself', player), False)
world.get_location('Zelda Pickup', player).event = True
@@ -277,8 +285,12 @@ def generate_itempool(world, player):
if player in world.pool_adjustment.keys():
amt = world.pool_adjustment[player]
if amt < 0:
for _ in range(amt, 0):
pool.remove(next(iter([x for x in pool if x in ['Rupees (20)', 'Rupees (5)', 'Rupee (1)']])))
trash_options = [x for x in pool if x in trash_items]
random.shuffle(trash_options)
trash_options = sorted(trash_options, key=lambda x: trash_items[x], reverse=True)
while amt > 0 and len(trash_options) > 0:
pool.remove(trash_options.pop())
amt -= 1
elif amt > 0:
for _ in range(0, amt):
pool.append('Rupees (20)')
@@ -369,8 +381,10 @@ def generate_itempool(world, player):
if clock_mode is not None:
world.clock_mode = clock_mode
if world.goal[player] in ['triforcehunt', 'trinity']:
world.treasure_hunt_count[player], world.treasure_hunt_total[player] = set_default_triforce(world.goal[player], world.treasure_hunt_count[player], world.treasure_hunt_total[player])
goal = world.goal[player]
if goal in ['triforcehunt', 'trinity']:
g, t = set_default_triforce(goal, world.treasure_hunt_count[player], world.treasure_hunt_total[player])
world.treasure_hunt_count[player], world.treasure_hunt_total[player] = g, t
world.treasure_hunt_icon[player] = 'Triforce Piece'
world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player
@@ -420,11 +434,20 @@ def generate_itempool(world, player):
if world.retro[player]:
set_up_take_anys(world, player)
if world.keydropshuffle[player]:
world.itempool += [ItemFactory('Small Key (Universal)', player)] * 32
if world.dropshuffle[player]:
world.itempool += [ItemFactory('Small Key (Universal)', player)] * 13
if world.pottery[player] not in ['none', 'cave']:
world.itempool += [ItemFactory('Small Key (Universal)', player)] * 19
create_dynamic_shop_locations(world, player)
if world.pottery[player] not in ['none', 'keys']:
add_pot_contents(world, player)
if world.shuffle_bonk_drops[player]:
create_dynamic_bonkdrop_locations(world, player)
add_bonkdrop_contents(world, player)
take_any_locations = [
'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut',
@@ -445,7 +468,9 @@ def set_up_take_anys(world, player):
if 'Archery Game' in take_any_locations:
take_any_locations.remove('Archery Game')
regions = random.sample(take_any_locations, 5)
take_any_candidates = [x for x in take_any_locations if len(world.get_region(x, player).locations) == 0]
regions = random.sample(take_any_candidates, 5)
old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave, 'the sword cave', player)
world.regions.append(old_man_take_any)
@@ -512,6 +537,21 @@ def create_dynamic_shop_locations(world, player):
loc.locked = True
def create_dynamic_bonkdrop_locations(world, player):
from Regions import bonk_prize_table
for bonk_location, (_, _, _, _, region_name, hint_text) in bonk_prize_table.items():
region = world.get_region(region_name, player)
loc = Location(player, bonk_location, 0, region, hint_text)
loc.type = LocationType.Bonk
loc.parent_region = region
loc.address = 0x2abb00 + (bonk_prize_table[loc.name][0] * 6) + 3
region.locations.append(loc)
world.dynamic_locations.append(loc)
world.clear_location_cache()
def fill_prizes(world, attempts=15):
all_state = world.get_all_state(keys=True)
for player in range(1, world.players + 1):
@@ -537,7 +577,7 @@ def fill_prizes(world, attempts=15):
continue
break
else:
raise FillError('Unable to place dungeon prizes')
raise FillError(f'Unable to place dungeon prizes {", ".join(list(map(lambda d: d.hint_text, prize_locs)))}')
def set_up_shops(world, player):
@@ -594,7 +634,7 @@ def set_up_shops(world, player):
removals = [item for item in world.itempool if item.name == 'Bomb Upgrade (+5)' and item.player == player]
for remove in removals:
world.itempool.remove(remove)
world.itempool.append(ItemFactory('Rupees (50)', player)) # replace the bomb upgrade
world.itempool.append(ItemFactory('Rupees (50)', player)) # replace the bomb upgrade
else:
cap_shop = world.get_region('Capacity Upgrade', player).shop
cap_shop.inventory[0] = cap_shop.inventory[1] # remove bomb capacity upgrades in bombbag
@@ -797,15 +837,36 @@ rupee_chart = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)'
'Rupees (100)': 100, 'Rupees (300)': 300}
def add_pot_contents(world, player):
for super_tile, pot_list in vanilla_pots.items():
for pot in pot_list:
if pot.item not in [PotItem.Hole, PotItem.Key, PotItem.Switch]:
if valid_pot_location(pot, world.pot_pool[player], world, player):
item = ('Rupees (5)' if world.retro[player] and pot_items[pot.item] == 'Arrows (5)'
else pot_items[pot.item])
world.itempool.append(ItemFactory(item, player))
def add_bonkdrop_contents(world, player):
from Items import item_table
for item_name, (_, count, alt_item) in bonk_prize_lookup.items():
if item_name not in item_table:
item_name = alt_item
while (count > 0):
item = ItemFactory(item_name, player)
world.itempool.append(item)
count -= 1
def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic, flute_activated):
pool = []
placed_items = {}
precollected_items = []
clock_mode = None
if goal in ['triforcehunt', 'trinity']:
if treasure_hunt_total == 0:
treasure_hunt_total = 30
triforcepool = ['Triforce Piece'] * int(treasure_hunt_total)
if treasure_hunt_total == 0 and goal in ['triforcehunt', 'trinity']:
treasure_hunt_total = 30 if goal == 'triforcehunt' else 10
# triforce pieces max out
triforcepool = ['Triforce Piece'] * min(treasure_hunt_total, max_goal)
pool.extend(alwaysitems)
@@ -834,7 +895,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer,
lamps_needed_for_dark_rooms = 1
# insanity shuffle doesn't have fake LW/DW logic so for now guaranteed Mirror and Moon Pearl at the start
if shuffle == 'insanity_legacy':
if shuffle == 'insanity_legacy':
place_item('Link\'s House', 'Magic Mirror')
place_item('Sanctuary', 'Moon Pearl')
else:
@@ -902,6 +963,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer,
place_item('Master Sword Pedestal', swords_to_use.pop())
else:
place_item('Master Sword Pedestal', 'Triforce')
pool.append(swords_to_use.pop())
else:
pool.extend(diff.progressivesword if want_progressives() else diff.basicsword)
if swords in ['assured', 'assured_pseudo']:
@@ -913,26 +975,19 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer,
pool.remove('Fighter Sword')
pool.extend(['Rupees (50)'])
extraitems = total_items_to_place - len(pool) - len(placed_items)
if timer in ['timed', 'timed-countdown']:
pool.extend(diff.timedother)
extraitems -= len(diff.timedother)
clock_mode = 'stopwatch' if timer == 'timed' else 'countdown'
elif timer == 'timed-ohko':
pool.extend(diff.timedohko)
extraitems -= len(diff.timedohko)
clock_mode = 'countdown-ohko'
if goal in ['triforcehunt', 'trinity']:
pool.extend(triforcepool)
extraitems -= len(triforcepool)
for extra in diff.extras:
if extraitems > 0:
if len(extra) > extraitems:
extra = random.choices(extra, k=extraitems)
pool.extend(extra)
extraitems -= len(extra)
pool.extend(extra)
# note: massage item pool now handles shrinking the pool appropriately
if goal in ['pedestal', 'trinity'] and swords != 'vanilla':
place_item('Master Sword Pedestal', 'Triforce')
@@ -963,6 +1018,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer,
pool = [item.replace('Bomb Upgrade (+10)', 'Small Heart') for item in pool]
return (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms)
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bombbag, customitemarray):
pool = []
placed_items = {}
@@ -989,7 +1045,8 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
# Triforce Pieces
if goal in ['triforcehunt', 'trinity']:
customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"] = set_default_triforce(goal, customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"])
g, t = set_default_triforce(goal, customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"])
customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"] = g, t
itemtotal = 0
# Bow to Silver Arrows Upgrade, including Generic Keys & Rupoors
@@ -1021,7 +1078,15 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
pool.append(thisbottle)
if customitemarray["triforcepieces"] > 0 or customitemarray["triforcepiecesgoal"] > 0:
# Location pool doesn't support larger values
treasure_hunt_count = max(min(customitemarray["triforcepiecesgoal"], max_goal), 1)
treasure_hunt_icon = 'Triforce Piece'
# Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling.
if ((customitemarray["triforcepieces"] < treasure_hunt_count) and (goal in ['triforcehunt', 'trinity'])
and (customitemarray["triforce"] == 0)):
extrapieces = treasure_hunt_count - customitemarray["triforcepieces"]
pool.extend(['Triforce Piece'] * extrapieces)
itemtotal = itemtotal + extrapieces
if timer in ['display', 'timed', 'timed-countdown']:
clock_mode = 'countdown' if timer == 'timed-countdown' else 'stopwatch'
@@ -1077,6 +1142,54 @@ def set_default_triforce(goal, custom_goal, custom_total):
triforce_total = max(min(custom_total, 128), triforce_goal) #128 max to ensure other progression can fit.
return (triforce_goal, triforce_total)
def make_customizer_pool(world, player):
pool = []
placed_items = {}
precollected_items = []
clock_mode = None
def place_item(loc, item):
assert loc not in placed_items
placed_items[loc] = item
diff = difficulties[world.difficulty[player]]
for item_name, amount in world.customizer.get_item_pool()[player].items():
if isinstance(amount, int):
if item_name == 'Bottle (Random)':
for _ in range(amount):
pool.append(random.choice(diff.bottles))
else:
pool.extend([item_name] * amount)
timer = world.timer[player]
if timer in ['display', 'timed', 'timed-countdown']:
clock_mode = 'countdown' if timer == 'timed-countdown' else 'stopwatch'
elif timer == 'timed-ohko':
clock_mode = 'countdown-ohko'
elif timer == 'ohko':
clock_mode = 'ohko'
if world.goal[player] == 'pedestal':
place_item('Master Sword Pedestal', 'Triforce')
return pool, placed_items, precollected_items, clock_mode, 1
# location pool doesn't support larger values at this time
def set_default_triforce(goal, custom_goal, custom_total):
triforce_goal, triforce_total = 0, 0
if goal == 'triforcehunt':
triforce_goal, triforce_total = 20, 30
elif goal == 'trinity':
triforce_goal, triforce_total = 8, 10
if custom_goal > 0:
triforce_goal = max(min(custom_goal, max_goal), 1)
if custom_total > 0:
triforce_total = max(min(custom_total, max_goal), triforce_goal)
return triforce_goal, triforce_total
# A quick test to ensure all combinations generate the correct amount of items.
def test():
for difficulty in ['normal', 'hard', 'expert']:
@@ -1106,27 +1219,3 @@ def test():
if __name__ == '__main__':
test()
def fill_specific_items(world):
keypool = [item for item in world.itempool if item.smallkey]
cage = world.get_location('Tower of Hera - Basement Cage', 1)
c_dungeon = cage.parent_region.dungeon
key_item = next(x for x in keypool if c_dungeon.name in x.name or (c_dungeon.name == 'Hyrule Castle' and 'Escape' in x.name))
world.itempool.remove(key_item)
all_state = world.get_all_state(True)
fill_restrictive(world, all_state, [cage], [key_item])
location = world.get_location('Tower of Hera - Map Chest', 1)
key_item = next(x for x in world.itempool if 'Byrna' in x.name)
world.itempool.remove(key_item)
fast_fill(world, [key_item], [location])
# somaria = next(item for item in world.itempool if item.name == 'Cane of Somaria')
# shooter = world.get_location('Palace of Darkness - Shooter Room', 1)
# world.itempool.remove(somaria)
# all_state = world.get_all_state(True)
# fill_restrictive(world, all_state, [shooter], [somaria])