Merge remote-tracking branch 'origin/OverworldShuffle' into OverworldShuffle
This commit is contained in:
195
ItemList.py
195
ItemList.py
@@ -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])
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user