Clustered bias algorithm
Fixes for various other algorithms
This commit is contained in:
@@ -44,6 +44,7 @@ def link_doors(world, player):
|
|||||||
reset_rooms(world, player)
|
reset_rooms(world, player)
|
||||||
world.get_door("Skull Pinball WS", player).no_exit()
|
world.get_door("Skull Pinball WS", player).no_exit()
|
||||||
world.swamp_patch_required[player] = orig_swamp_patch
|
world.swamp_patch_required[player] = orig_swamp_patch
|
||||||
|
link_doors_prep(world, player)
|
||||||
|
|
||||||
|
|
||||||
def link_doors_prep(world, player):
|
def link_doors_prep(world, player):
|
||||||
|
|||||||
131
Fill.py
131
Fill.py
@@ -6,7 +6,7 @@ import logging
|
|||||||
from BaseClasses import CollectionState, FillError
|
from BaseClasses import CollectionState, FillError
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
from Regions import shop_to_location_table, retro_shops
|
from Regions import shop_to_location_table, retro_shops
|
||||||
from source.item.BiasedFill import filter_locations, classify_major_items, split_pool
|
from source.item.BiasedFill import filter_locations, classify_major_items, replace_trash_item, vanilla_fallback
|
||||||
|
|
||||||
|
|
||||||
def get_dungeon_item_pool(world):
|
def get_dungeon_item_pool(world):
|
||||||
@@ -107,18 +107,17 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
|
|||||||
if spot_to_fill:
|
if spot_to_fill:
|
||||||
break
|
break
|
||||||
if spot_to_fill is None:
|
if spot_to_fill is None:
|
||||||
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
spot_to_fill = recovery_placement(item_to_place, locations, world, maximum_exploration_state,
|
||||||
unplaced_items.insert(0, item_to_place)
|
base_state, itempool, perform_access_check, item_locations,
|
||||||
if world.can_beat_game():
|
keys_in_itempool, single_player_placement)
|
||||||
if world.accessibility[item_to_place.player] != 'none':
|
|
||||||
logging.getLogger('').warning('Not all items placed. Game beatable anyway.'
|
|
||||||
f' (Could not place {item_to_place})')
|
|
||||||
continue
|
|
||||||
if world.algorithm in ['balanced', 'equitable']:
|
|
||||||
spot_to_fill = last_ditch_placement(item_to_place, locations, world, maximum_exploration_state,
|
|
||||||
base_state, itempool, keys_in_itempool,
|
|
||||||
single_player_placement)
|
|
||||||
if spot_to_fill is None:
|
if spot_to_fill is None:
|
||||||
|
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
||||||
|
unplaced_items.insert(0, item_to_place)
|
||||||
|
if world.can_beat_game():
|
||||||
|
if world.accessibility[item_to_place.player] != 'none':
|
||||||
|
logging.getLogger('').warning('Not all items placed. Game beatable anyway.'
|
||||||
|
f' (Could not place {item_to_place})')
|
||||||
|
continue
|
||||||
raise FillError('No more spots to place %s' % item_to_place)
|
raise FillError('No more spots to place %s' % item_to_place)
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, False)
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
@@ -214,6 +213,55 @@ def is_dungeon_item(item, world):
|
|||||||
or (item.map and not world.mapshuffle[item.player]))
|
or (item.map and not world.mapshuffle[item.player]))
|
||||||
|
|
||||||
|
|
||||||
|
def recovery_placement(item_to_place, locations, world, state, base_state, itempool, perform_access_check, attempted,
|
||||||
|
keys_in_itempool=None, single_player_placement=False):
|
||||||
|
if world.algorithm in ['balanced', 'equitable']:
|
||||||
|
return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, keys_in_itempool,
|
||||||
|
single_player_placement)
|
||||||
|
elif world.algorithm == 'vanilla_bias':
|
||||||
|
if item_to_place.type == 'Crystal':
|
||||||
|
possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal']
|
||||||
|
return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool,
|
||||||
|
keys_in_itempool, single_player_placement)
|
||||||
|
else:
|
||||||
|
i, config = 0, world.item_pool_config
|
||||||
|
tried = set(attempted)
|
||||||
|
if not item_to_place.is_inside_dungeon_item(world):
|
||||||
|
while i < len(config.location_groups[item_to_place.player]):
|
||||||
|
fallback_locations = config.location_groups[item_to_place.player][i].locations
|
||||||
|
other_locs = [x for x in locations if x.name in fallback_locations]
|
||||||
|
for location in other_locs:
|
||||||
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
|
perform_access_check, itempool, keys_in_itempool, world)
|
||||||
|
if spot_to_fill:
|
||||||
|
return spot_to_fill
|
||||||
|
i += 1
|
||||||
|
tried.update(other_locs)
|
||||||
|
else:
|
||||||
|
other_locations = vanilla_fallback(item_to_place, locations, world)
|
||||||
|
for location in other_locations:
|
||||||
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
|
perform_access_check, itempool, keys_in_itempool, world)
|
||||||
|
if spot_to_fill:
|
||||||
|
return spot_to_fill
|
||||||
|
tried.update(other_locations)
|
||||||
|
other_locations = [x for x in locations if x not in tried]
|
||||||
|
for location in other_locations:
|
||||||
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
|
perform_access_check, itempool, keys_in_itempool, world)
|
||||||
|
if spot_to_fill:
|
||||||
|
return spot_to_fill
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
other_locations = [x for x in locations if x not in attempted]
|
||||||
|
for location in other_locations:
|
||||||
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
|
perform_access_check, itempool, keys_in_itempool, world)
|
||||||
|
if spot_to_fill:
|
||||||
|
return spot_to_fill
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool,
|
def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool,
|
||||||
keys_in_itempool=None, single_player_placement=False):
|
keys_in_itempool=None, single_player_placement=False):
|
||||||
def location_preference(loc):
|
def location_preference(loc):
|
||||||
@@ -232,7 +280,12 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite
|
|||||||
possible_swaps = [x for x in state.locations_checked
|
possible_swaps = [x for x in state.locations_checked
|
||||||
if x.item.type not in ['Event', 'Crystal'] and not x.forced_item]
|
if x.item.type not in ['Event', 'Crystal'] and not x.forced_item]
|
||||||
swap_locations = sorted(possible_swaps, key=location_preference)
|
swap_locations = sorted(possible_swaps, key=location_preference)
|
||||||
|
return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
|
||||||
|
keys_in_itempool, single_player_placement)
|
||||||
|
|
||||||
|
|
||||||
|
def try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
|
||||||
|
keys_in_itempool=None, single_player_placement=False):
|
||||||
for location in swap_locations:
|
for location in swap_locations:
|
||||||
old_item = location.item
|
old_item = location.item
|
||||||
new_pool = list(itempool) + [old_item]
|
new_pool = list(itempool) + [old_item]
|
||||||
@@ -301,6 +354,9 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
prioitempool = [item for item in world.itempool if not item.advancement and item.priority]
|
prioitempool = [item for item in world.itempool if not item.advancement and item.priority]
|
||||||
restitempool = [item for item in world.itempool if not item.advancement and not item.priority]
|
restitempool = [item for item in world.itempool if not item.advancement and not item.priority]
|
||||||
|
|
||||||
|
gftower_trash &= world.algorithm in ['balanced', 'equitable', 'dungeon_bias']
|
||||||
|
# dungeon bias may fill up the dungeon... and push items out into the overworld
|
||||||
|
|
||||||
# fill in gtower locations with trash first
|
# fill in gtower locations with trash first
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
if not gftower_trash or not world.ganonstower_vanilla[player] or world.doorShuffle[player] == 'crossed' or world.logic[player] in ['owglitches', 'nologic']:
|
if not gftower_trash or not world.ganonstower_vanilla[player] or world.doorShuffle[player] == 'crossed' or world.logic[player] in ['owglitches', 'nologic']:
|
||||||
@@ -325,40 +381,36 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
# todo: crossed
|
# todo: crossed
|
||||||
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0)
|
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0)
|
||||||
keys_in_pool = {player: world.keyshuffle[player] or world.algorithm != 'balanced' for player in range(1, world.players + 1)}
|
keys_in_pool = {player: world.keyshuffle[player] or world.algorithm != 'balanced' for player in range(1, world.players + 1)}
|
||||||
if world.algorithm in ['balanced', 'equitable', 'vanilla_bias', 'dungeon_bias', 'entangled']:
|
|
||||||
fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool)
|
# sort maps and compasses to the back -- this may not be viable in equitable & ambrosia
|
||||||
|
progitempool.sort(key=lambda item: 0 if item.map or item.compass else 1)
|
||||||
|
fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool)
|
||||||
|
random.shuffle(fill_locations)
|
||||||
|
if world.algorithm == 'balanced':
|
||||||
|
fast_fill(world, prioitempool, fill_locations)
|
||||||
|
elif world.algorithm == 'vanilla_bias':
|
||||||
|
fast_vanilla_fill(world, prioitempool, fill_locations)
|
||||||
|
elif world.algorithm in ['major_bias', 'dungeon_bias', 'cluster_bias', 'entangled']:
|
||||||
|
filtered_fill(world, prioitempool, fill_locations)
|
||||||
|
else: # just need to ensure dungeon items still get placed in dungeons
|
||||||
|
fast_equitable_fill(world, prioitempool, fill_locations)
|
||||||
|
# placeholder work
|
||||||
|
if (world.algorithm == 'entangled' and world.players > 1) or world.algorithm == 'cluster_bias':
|
||||||
random.shuffle(fill_locations)
|
random.shuffle(fill_locations)
|
||||||
if world.algorithm == 'balanced':
|
placeholder_items = [item for item in world.itempool if item.name == 'Rupee (1)']
|
||||||
fast_fill(world, prioitempool, fill_locations)
|
num_ph_items = len(placeholder_items)
|
||||||
elif world.algorithm == 'vanilla_bias':
|
if num_ph_items > 0:
|
||||||
fast_vanilla_fill(world, prioitempool, fill_locations)
|
|
||||||
elif world.algorithm in ['dungeon_bias', 'entangled']:
|
|
||||||
filtered_fill(world, prioitempool, fill_locations)
|
|
||||||
else: # just need to ensure dungeon items still get placed in dungeons
|
|
||||||
fast_equitable_fill(world, prioitempool, fill_locations)
|
|
||||||
# placeholder work
|
|
||||||
if world.algorithm == 'entangled' and world.players > 1:
|
|
||||||
random.shuffle(fill_locations)
|
|
||||||
placeholder_locations = filter_locations('Placeholder', fill_locations, world)
|
placeholder_locations = filter_locations('Placeholder', fill_locations, world)
|
||||||
placeholder_items = [item for item in world.itempool if item.name == 'Rupee (1)']
|
num_ph_locations = len(placeholder_locations)
|
||||||
|
if num_ph_items < num_ph_locations < len(fill_locations):
|
||||||
|
for _ in range(num_ph_locations - num_ph_items):
|
||||||
|
placeholder_items.append(replace_trash_item(restitempool, 'Rupee (1)'))
|
||||||
|
assert len(placeholder_items) == len(placeholder_locations)
|
||||||
for i in placeholder_items:
|
for i in placeholder_items:
|
||||||
restitempool.remove(i)
|
restitempool.remove(i)
|
||||||
for l in placeholder_locations:
|
for l in placeholder_locations:
|
||||||
fill_locations.remove(l)
|
fill_locations.remove(l)
|
||||||
filtered_fill(world, placeholder_items, placeholder_locations)
|
filtered_fill(world, placeholder_items, placeholder_locations)
|
||||||
else:
|
|
||||||
primary, secondary = split_pool(progitempool, world)
|
|
||||||
fill_restrictive(world, world.state, fill_locations, primary, keys_in_pool, False, secondary)
|
|
||||||
random.shuffle(fill_locations)
|
|
||||||
tertiary, quaternary = split_pool(prioitempool, world)
|
|
||||||
prioitempool = []
|
|
||||||
filtered_equitable_fill(world, tertiary, fill_locations)
|
|
||||||
prioitempool += tertiary
|
|
||||||
random.shuffle(fill_locations)
|
|
||||||
fill_restrictive(world, world.state, fill_locations, secondary, keys_in_pool)
|
|
||||||
random.shuffle(fill_locations)
|
|
||||||
fast_equitable_fill(world, quaternary, fill_locations)
|
|
||||||
prioitempool += quaternary
|
|
||||||
|
|
||||||
if world.algorithm == 'vanilla_bias':
|
if world.algorithm == 'vanilla_bias':
|
||||||
fast_vanilla_fill(world, restitempool, fill_locations)
|
fast_vanilla_fill(world, restitempool, fill_locations)
|
||||||
@@ -389,6 +441,7 @@ def filtered_fill(world, item_pool, fill_locations):
|
|||||||
# sweep once to pick up preplaced items
|
# sweep once to pick up preplaced items
|
||||||
world.state.sweep_for_events()
|
world.state.sweep_for_events()
|
||||||
|
|
||||||
|
|
||||||
def fast_vanilla_fill(world, item_pool, fill_locations):
|
def fast_vanilla_fill(world, item_pool, fill_locations):
|
||||||
while item_pool and fill_locations:
|
while item_pool and fill_locations:
|
||||||
item_to_place = item_pool.pop()
|
item_to_place = item_pool.pop()
|
||||||
|
|||||||
4
Main.py
4
Main.py
@@ -207,12 +207,10 @@ def main(args, seed=None, fish=None):
|
|||||||
|
|
||||||
logger.info(world.fish.translate("cli","cli","placing.dungeon.items"))
|
logger.info(world.fish.translate("cli","cli","placing.dungeon.items"))
|
||||||
|
|
||||||
if args.algorithm in ['balanced', 'dungeon_bias', 'entangled']:
|
if args.algorithm != 'equitable':
|
||||||
shuffled_locations = world.get_unfilled_locations()
|
shuffled_locations = world.get_unfilled_locations()
|
||||||
random.shuffle(shuffled_locations)
|
random.shuffle(shuffled_locations)
|
||||||
fill_dungeons_restrictive(world, shuffled_locations)
|
fill_dungeons_restrictive(world, shuffled_locations)
|
||||||
elif args.algorithm == 'equitable':
|
|
||||||
promote_dungeon_items(world)
|
|
||||||
else:
|
else:
|
||||||
promote_dungeon_items(world)
|
promote_dungeon_items(world)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import RaceRandom as random
|
import RaceRandom as random
|
||||||
import logging
|
import logging
|
||||||
|
from math import ceil
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from DoorShuffle import validate_vanilla_reservation
|
from DoorShuffle import validate_vanilla_reservation
|
||||||
@@ -29,7 +30,7 @@ class LocationGroup(object):
|
|||||||
self.retro = False
|
self.retro = False
|
||||||
|
|
||||||
def locs(self, locs):
|
def locs(self, locs):
|
||||||
self.locations = locs
|
self.locations = list(locs)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def flags(self, k, d=False, s=False, r=False):
|
def flags(self, k, d=False, s=False, r=False):
|
||||||
@@ -52,9 +53,10 @@ def create_item_pool_config(world):
|
|||||||
d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon
|
d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon
|
||||||
config.reserved_locations[player].add(f'{d_name} - Boss')
|
config.reserved_locations[player].add(f'{d_name} - Boss')
|
||||||
for dungeon in world.dungeons:
|
for dungeon in world.dungeons:
|
||||||
for item in dungeon.all_items:
|
if world.restrict_boss_items[dungeon.player] != 'none':
|
||||||
if item.map or item.compass:
|
for item in dungeon.all_items:
|
||||||
item.advancement = True
|
if item.map or item.compass:
|
||||||
|
item.advancement = True
|
||||||
if world.algorithm == 'vanilla_bias':
|
if world.algorithm == 'vanilla_bias':
|
||||||
config.static_placement = {}
|
config.static_placement = {}
|
||||||
config.location_groups = {}
|
config.location_groups = {}
|
||||||
@@ -70,9 +72,12 @@ def create_item_pool_config(world):
|
|||||||
# todo: retro (universal keys...)
|
# todo: retro (universal keys...)
|
||||||
# retro + shops
|
# retro + shops
|
||||||
config.location_groups[player] = [
|
config.location_groups[player] = [
|
||||||
|
LocationGroup('Major').locs(mode_grouping['Overworld Major'] + mode_grouping['Big Chests'] + mode_grouping['Heart Containers']),
|
||||||
LocationGroup('bkhp').locs(mode_grouping['Heart Pieces']),
|
LocationGroup('bkhp').locs(mode_grouping['Heart Pieces']),
|
||||||
LocationGroup('bktrash').locs(mode_grouping['Overworld Trash'] + mode_grouping['Dungeon Trash']),
|
LocationGroup('bktrash').locs(mode_grouping['Overworld Trash'] + mode_grouping['Dungeon Trash']),
|
||||||
LocationGroup('bkgt').locs(mode_grouping['GT Trash'])]
|
LocationGroup('bkgt').locs(mode_grouping['GT Trash'])]
|
||||||
|
for loc_name in mode_grouping['Big Chests'] + mode_grouping['Heart Containers']:
|
||||||
|
config.reserved_locations[player].add(loc_name)
|
||||||
elif world.algorithm == 'major_bias':
|
elif world.algorithm == 'major_bias':
|
||||||
config.location_groups = [
|
config.location_groups = [
|
||||||
LocationGroup('MajorItems'),
|
LocationGroup('MajorItems'),
|
||||||
@@ -102,6 +107,7 @@ def create_item_pool_config(world):
|
|||||||
pass # todo: 5 locations for single arrow representation?
|
pass # todo: 5 locations for single arrow representation?
|
||||||
config.item_pool[player] = determine_major_items(world, player)
|
config.item_pool[player] = determine_major_items(world, player)
|
||||||
config.location_groups[0].locations = set(groups.locations)
|
config.location_groups[0].locations = set(groups.locations)
|
||||||
|
config.reserved_locations[player].add(groups.locations)
|
||||||
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Dungeon Trash'] + mode_grouping['Shops']
|
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Dungeon Trash'] + mode_grouping['Shops']
|
||||||
+ mode_grouping['Overworld Trash'] + mode_grouping['GT Trash'] + mode_grouping['RetroShops'])
|
+ mode_grouping['Overworld Trash'] + mode_grouping['GT Trash'] + mode_grouping['RetroShops'])
|
||||||
config.location_groups[1].locations = set(backup)
|
config.location_groups[1].locations = set(backup)
|
||||||
@@ -121,6 +127,36 @@ def create_item_pool_config(world):
|
|||||||
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Overworld Major']
|
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Overworld Major']
|
||||||
+ mode_grouping['Overworld Trash'] + mode_grouping['Shops'] + mode_grouping['RetroShops'])
|
+ mode_grouping['Overworld Trash'] + mode_grouping['Shops'] + mode_grouping['RetroShops'])
|
||||||
config.location_groups[1].locations = set(backup)
|
config.location_groups[1].locations = set(backup)
|
||||||
|
elif world.algorithm == 'cluster_bias':
|
||||||
|
config.location_groups = [
|
||||||
|
LocationGroup('Clusters'),
|
||||||
|
]
|
||||||
|
item_cnt = defaultdict(int)
|
||||||
|
config.item_pool = {}
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
config.item_pool[player] = determine_major_items(world, player)
|
||||||
|
item_cnt[player] += count_major_items(world, player)
|
||||||
|
# set cluster choices
|
||||||
|
cluster_choices = figure_out_clustered_choices(world)
|
||||||
|
|
||||||
|
chosen_locations = defaultdict(set)
|
||||||
|
placeholder_cnt = 0
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
number_of_clusters = ceil(item_cnt[player] / 13)
|
||||||
|
location_cnt = 0
|
||||||
|
while item_cnt[player] > location_cnt:
|
||||||
|
chosen_clusters = random.sample(cluster_choices[player], number_of_clusters)
|
||||||
|
for loc_group in chosen_clusters:
|
||||||
|
for location in loc_group.locations:
|
||||||
|
if not location_prefilled(location, world, player):
|
||||||
|
world.item_pool_config.reserved_locations[player].add(location)
|
||||||
|
chosen_locations[location].add(player)
|
||||||
|
location_cnt += 1
|
||||||
|
cluster_choices[player] = [x for x in cluster_choices[player] if x not in chosen_clusters]
|
||||||
|
number_of_clusters = 1
|
||||||
|
placeholder_cnt += location_cnt - item_cnt[player]
|
||||||
|
config.placeholders = placeholder_cnt
|
||||||
|
config.location_groups[0].locations = chosen_locations
|
||||||
elif world.algorithm == 'entangled' and world.players > 1:
|
elif world.algorithm == 'entangled' and world.players > 1:
|
||||||
config.location_groups = [
|
config.location_groups = [
|
||||||
LocationGroup('Entangled'),
|
LocationGroup('Entangled'),
|
||||||
@@ -169,6 +205,14 @@ def create_item_pool_config(world):
|
|||||||
config.location_groups[0].locations = chosen_locations
|
config.location_groups[0].locations = chosen_locations
|
||||||
|
|
||||||
|
|
||||||
|
def location_prefilled(location, world, player):
|
||||||
|
if world.swords[player] == 'vanilla':
|
||||||
|
return location in vanilla_swords
|
||||||
|
if world.goal[player] == 'pedestal':
|
||||||
|
return location == 'Master Sword Pedestal'
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def previously_reserved(location, world, player):
|
def previously_reserved(location, world, player):
|
||||||
if '- Boss' in location.name:
|
if '- Boss' in location.name:
|
||||||
if world.restrict_boss_items[player] == 'mapcompass' and (not world.compassshuffle[player]
|
if world.restrict_boss_items[player] == 'mapcompass' and (not world.compassshuffle[player]
|
||||||
@@ -188,7 +232,7 @@ def massage_item_pool(world):
|
|||||||
player_pool[item.player].append(item)
|
player_pool[item.player].append(item)
|
||||||
for dungeon in world.dungeons:
|
for dungeon in world.dungeons:
|
||||||
for item in dungeon.all_items:
|
for item in dungeon.all_items:
|
||||||
if item not in player_pool[item.player]: # filters out maps, compasses, etc
|
if (not item.compass and not item.map) or item not in player_pool[item.player]:
|
||||||
player_pool[item.player].append(item)
|
player_pool[item.player].append(item)
|
||||||
player_locations = defaultdict(list)
|
player_locations = defaultdict(list)
|
||||||
for player in player_pool:
|
for player in player_pool:
|
||||||
@@ -220,6 +264,9 @@ def massage_item_pool(world):
|
|||||||
deleted = trash_options.pop()
|
deleted = trash_options.pop()
|
||||||
world.itempool.remove(deleted)
|
world.itempool.remove(deleted)
|
||||||
removed += 1
|
removed += 1
|
||||||
|
if world.item_pool_config.placeholders > len(single_rupees):
|
||||||
|
for _ in range(world.item_pool_config.placeholders-len(single_rupees)):
|
||||||
|
single_rupees.append(ItemFactory('Rupee (1)', random.randint(1, world.players)))
|
||||||
placeholders = random.sample(single_rupees, world.item_pool_config.placeholders)
|
placeholders = random.sample(single_rupees, world.item_pool_config.placeholders)
|
||||||
world.itempool += placeholders
|
world.itempool += placeholders
|
||||||
removed -= len(placeholders)
|
removed -= len(placeholders)
|
||||||
@@ -227,6 +274,19 @@ def massage_item_pool(world):
|
|||||||
world.itempool.append(ItemFactory('Rupees (5)', random.randint(1, world.players)))
|
world.itempool.append(ItemFactory('Rupees (5)', random.randint(1, world.players)))
|
||||||
|
|
||||||
|
|
||||||
|
def replace_trash_item(item_pool, replacement):
|
||||||
|
trash_options = [x for x in item_pool if x.name in trash_items]
|
||||||
|
random.shuffle(trash_options)
|
||||||
|
trash_options = sorted(trash_options, key=lambda x: trash_items[x.name], reverse=True)
|
||||||
|
if len(trash_options) == 0:
|
||||||
|
logging.getLogger('').warning(f'Too many good items in pool, not enough room for placeholders')
|
||||||
|
deleted = trash_options.pop()
|
||||||
|
item_pool.remove(deleted)
|
||||||
|
replace_item = ItemFactory(replacement, deleted.player)
|
||||||
|
item_pool.append(replace_item)
|
||||||
|
return replace_item
|
||||||
|
|
||||||
|
|
||||||
def validate_reservation(location, dungeon, world, player):
|
def validate_reservation(location, dungeon, world, player):
|
||||||
world.item_pool_config.reserved_locations[player].add(location.name)
|
world.item_pool_config.reserved_locations[player].add(location.name)
|
||||||
if world.doorShuffle[player] != 'vanilla':
|
if world.doorShuffle[player] != 'vanilla':
|
||||||
@@ -265,12 +325,22 @@ def count_major_items(world, player):
|
|||||||
major_item_set += world.triforce_pool[player]
|
major_item_set += world.triforce_pool[player]
|
||||||
if world.bombbag[player]:
|
if world.bombbag[player]:
|
||||||
major_item_set += world.triforce_pool[player]
|
major_item_set += world.triforce_pool[player]
|
||||||
# todo: vanilla, assured, swordless?
|
if world.swords[player] != "random":
|
||||||
# if world.swords[player] != "random":
|
if world.swords[player] == 'assured':
|
||||||
# if world.swords[player] == 'assured':
|
major_item_set -= 1
|
||||||
# major_item_set -= 1
|
if world.swords[player] in ['vanilla', 'swordless']:
|
||||||
# if world.swords[player] in ['vanilla', 'swordless']:
|
major_item_set -= 4
|
||||||
# major_item_set -= 4
|
if world.retro[player]:
|
||||||
|
if world.shopsanity[player]:
|
||||||
|
major_item_set -= 1 # sword in old man cave
|
||||||
|
if world.keyshuffle[player]:
|
||||||
|
major_item_set -= 29
|
||||||
|
# universal keys
|
||||||
|
major_item_set += 19 if world.difficulty[player] == 'normal' else 14
|
||||||
|
if world.mode[player] == 'standard' and world.doorShuffle[player] == 'vanilla':
|
||||||
|
major_item_set -= 1 # a key in escape
|
||||||
|
if world.doorShuffle[player] != 'vanilla':
|
||||||
|
major_item_set += 10 # tries to add up to 10 more universal keys for door rando
|
||||||
# todo: starting equipment?
|
# todo: starting equipment?
|
||||||
return major_item_set
|
return major_item_set
|
||||||
|
|
||||||
@@ -354,16 +424,68 @@ def classify_major_items(world):
|
|||||||
item.priority = True
|
item.priority = True
|
||||||
|
|
||||||
|
|
||||||
def split_pool(pool, world):
|
def figure_out_clustered_choices(world):
|
||||||
# bias or entangled
|
cluster_candidates = {}
|
||||||
config = world.item_pool_config
|
for player in range(1, world.players + 1):
|
||||||
priority, secondary = [], []
|
cluster_candidates[player] = [LocationGroup(x.name).locs(x.locations) for x in clustered_groups]
|
||||||
for item in pool:
|
backups = list(reversed(leftovers))
|
||||||
if item.name in config.item_pool[item.player]:
|
if world.bigkeyshuffle[player]:
|
||||||
priority.append(item)
|
bk_grp = LocationGroup('BigKeys').locs(mode_grouping['Big Keys'])
|
||||||
else:
|
if world.keydropshuffle[player]:
|
||||||
secondary.append(item)
|
bk_grp.locations.append(mode_grouping['Big Key Drops'])
|
||||||
return priority, secondary
|
for i in range(13-len(bk_grp.locations)):
|
||||||
|
bk_grp.locations.append(backups.pop())
|
||||||
|
cluster_candidates[player].append(bk_grp)
|
||||||
|
if world.compassshuffle[player]:
|
||||||
|
cmp_grp = LocationGroup('Compasses').locs(mode_grouping['Compasses'])
|
||||||
|
if len(cmp_grp.locations) + len(backups) >= 13:
|
||||||
|
for i in range(13-len(cmp_grp.locations)):
|
||||||
|
cmp_grp.locations.append(backups.pop())
|
||||||
|
cluster_candidates[player].append(cmp_grp)
|
||||||
|
else:
|
||||||
|
backups.extend(reversed(cmp_grp.locations))
|
||||||
|
if world.mapshuffle[player]:
|
||||||
|
mp_grp = LocationGroup('Maps').locs(mode_grouping['Maps'])
|
||||||
|
if len(mp_grp.locations) + len(backups) >= 13:
|
||||||
|
for i in range(13-len(mp_grp.locations)):
|
||||||
|
mp_grp.locations.append(backups.pop())
|
||||||
|
cluster_candidates[player].append(mp_grp)
|
||||||
|
else:
|
||||||
|
backups.extend(reversed(mp_grp.locations))
|
||||||
|
if world.shopsanity[player]:
|
||||||
|
cluster_candidates[player].append(LocationGroup('Shopsanity1').locs(other_clusters['Shopsanity1']))
|
||||||
|
cluster_candidates[player].append(LocationGroup('Shopsanity2').locs(other_clusters['Shopsanity2']))
|
||||||
|
extras = list(other_clusters['ShopsanityLeft'])
|
||||||
|
if world.retro[player]:
|
||||||
|
extras.extend(mode_grouping['RetroShops'])
|
||||||
|
if len(extras)+len(backups) >= 13:
|
||||||
|
for i in range(13-len(extras)):
|
||||||
|
extras.append(backups.pop())
|
||||||
|
cluster_candidates[player].append(LocationGroup('ShopExtra').locs(extras))
|
||||||
|
else:
|
||||||
|
backups.extend(reversed(extras))
|
||||||
|
if world.keyshuffle[player] or world.retro[player]:
|
||||||
|
cluster_candidates[player].append(LocationGroup('SmallKey1').locs(other_clusters['SmallKey1']))
|
||||||
|
cluster_candidates[player].append(LocationGroup('SmallKey2').locs(other_clusters['SmallKey2']))
|
||||||
|
extras = list(other_clusters['SmallKeyLeft'])
|
||||||
|
if world.keydropshuffle[player]:
|
||||||
|
cluster_candidates[player].append(LocationGroup('KeyDrop1').locs(other_clusters['KeyDrop1']))
|
||||||
|
cluster_candidates[player].append(LocationGroup('KeyDrop2').locs(other_clusters['KeyDrop2']))
|
||||||
|
extras.extend(other_clusters['KeyDropLeft'])
|
||||||
|
if len(extras)+len(backups) >= 13:
|
||||||
|
for i in range(13-len(extras)):
|
||||||
|
extras.append(backups.pop())
|
||||||
|
cluster_candidates[player].append(LocationGroup('SmallKeyExtra').locs(extras))
|
||||||
|
else:
|
||||||
|
backups.extend(reversed(extras))
|
||||||
|
return cluster_candidates
|
||||||
|
|
||||||
|
|
||||||
|
def vanilla_fallback(item_to_place, locations, world):
|
||||||
|
if item_to_place.is_inside_dungeon_item(world):
|
||||||
|
return [x for x in locations if x.name in vanilla_fallback_dungeon_set
|
||||||
|
and x.parent_region.dungeon and x.parent_region.dungeon.name == item_to_place.dungeon]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def filter_locations(item_to_place, locations, world):
|
def filter_locations(item_to_place, locations, world):
|
||||||
@@ -391,7 +513,7 @@ def filter_locations(item_to_place, locations, world):
|
|||||||
filtered = [l for l in locations if l.name in restricted]
|
filtered = [l for l in locations if l.name in restricted]
|
||||||
# bias toward certain location in overflow? (thinking about this for major_bias)
|
# bias toward certain location in overflow? (thinking about this for major_bias)
|
||||||
return filtered if len(filtered) > 0 else locations
|
return filtered if len(filtered) > 0 else locations
|
||||||
if world.algorithm == 'entangled' and world.players > 1:
|
if (world.algorithm == 'entangled' and world.players > 1) or world.algorithm == 'cluster_bias':
|
||||||
config = world.item_pool_config
|
config = world.item_pool_config
|
||||||
if item_to_place == 'Placeholder' or item_to_place.name in config.item_pool[item_to_place.player]:
|
if item_to_place == 'Placeholder' or item_to_place.name in config.item_pool[item_to_place.player]:
|
||||||
restricted = config.location_groups[0].locations
|
restricted = config.location_groups[0].locations
|
||||||
@@ -417,7 +539,7 @@ vanilla_mapping = {
|
|||||||
'Crystal 5': ['Ice Palace - Prize', 'Misery Mire - Prize'],
|
'Crystal 5': ['Ice Palace - Prize', 'Misery Mire - Prize'],
|
||||||
'Crystal 6': ['Ice Palace - Prize', 'Misery Mire - Prize'],
|
'Crystal 6': ['Ice Palace - Prize', 'Misery Mire - Prize'],
|
||||||
'Bow': ['Eastern Palace - Big Chest'],
|
'Bow': ['Eastern Palace - Big Chest'],
|
||||||
'Progressive Bow': ['Eastern Palace - Big Chest', 'Pyramid Fairy - Left'],
|
'Progressive Bow': ['Eastern Palace - Big Chest', 'Pyramid Fairy - Right'],
|
||||||
'Book of Mudora': ['Library'],
|
'Book of Mudora': ['Library'],
|
||||||
'Hammer': ['Palace of Darkness - Big Chest'],
|
'Hammer': ['Palace of Darkness - Big Chest'],
|
||||||
'Hookshot': ['Swamp Palace - Big Chest'],
|
'Hookshot': ['Swamp Palace - Big Chest'],
|
||||||
@@ -443,10 +565,10 @@ vanilla_mapping = {
|
|||||||
'Master Sword': ['Master Sword Pedestal'],
|
'Master Sword': ['Master Sword Pedestal'],
|
||||||
'Tempered Sword': ['Blacksmith'],
|
'Tempered Sword': ['Blacksmith'],
|
||||||
'Fighter Sword': ["Link's Uncle"],
|
'Fighter Sword': ["Link's Uncle"],
|
||||||
'Golden Sword': ['Pyramid Fairy - Right'],
|
'Golden Sword': ['Pyramid Fairy - Left'],
|
||||||
'Progressive Sword': ["Link's Uncle", 'Blacksmith', 'Master Sword Pedestal', 'Pyramid Fairy - Right'],
|
'Progressive Sword': ["Link's Uncle", 'Blacksmith', 'Master Sword Pedestal', 'Pyramid Fairy - Left'],
|
||||||
'Progressive Glove': ['Desert Palace - Big Chest', "Thieves' Town - Big Chest"],
|
'Progressive Glove': ['Desert Palace - Big Chest', "Thieves' Town - Big Chest"],
|
||||||
'Silver Arrows': ['Pyramid Fairy - Left'],
|
'Silver Arrows': ['Pyramid Fairy - Right'],
|
||||||
'Single Arrow': ['Palace of Darkness - Dark Basement - Left'],
|
'Single Arrow': ['Palace of Darkness - Dark Basement - Left'],
|
||||||
'Arrows (10)': ['Chicken House', 'Mini Moldorm Cave - Far Right', 'Sewers - Secret Room - Right',
|
'Arrows (10)': ['Chicken House', 'Mini Moldorm Cave - Far Right', 'Sewers - Secret Room - Right',
|
||||||
'Paradox Cave Upper - Right', 'Mire Shed - Right', 'Ganons Tower - Hope Room - Left',
|
'Paradox Cave Upper - Right', 'Mire Shed - Right', 'Ganons Tower - Hope Room - Left',
|
||||||
@@ -702,6 +824,11 @@ mode_grouping = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vanilla_fallback_dungeon_set = set(mode_grouping['Dungeon Trash'] + mode_grouping['Big Keys'] +
|
||||||
|
mode_grouping['GT Trash'] + mode_grouping['Small Keys'] +
|
||||||
|
mode_grouping['Compasses'] + mode_grouping['Maps'] + mode_grouping['Key Drops'] +
|
||||||
|
mode_grouping['Big Key Drops'])
|
||||||
|
|
||||||
|
|
||||||
major_items = {'Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer',
|
major_items = {'Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer',
|
||||||
'Hookshot', 'Ice Rod', 'Lamp', 'Cape', 'Magic Powder', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel',
|
'Hookshot', 'Ice Rod', 'Lamp', 'Cape', 'Magic Powder', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel',
|
||||||
@@ -714,148 +841,122 @@ major_items = {'Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod
|
|||||||
'Progressive Bow', 'Progressive Bow (Alt)'}
|
'Progressive Bow', 'Progressive Bow (Alt)'}
|
||||||
|
|
||||||
|
|
||||||
# todo: re-enter these
|
|
||||||
clustered_groups = [
|
clustered_groups = [
|
||||||
LocationGroup("MajorRoute1").locs([
|
LocationGroup("MajorRoute1").locs([
|
||||||
'Library', 'Master Sword Pedestal', 'Old Man', 'Flute Spot',
|
'Ice Rod Cave', 'Library', 'Old Man', 'Magic Bat', 'Ether Tablet', 'Hobo', 'Purple Chest', 'Spike Cave',
|
||||||
'Ether Tablet', 'Stumpy', 'Bombos Tablet', 'Mushroom', 'Bottle Merchant', 'Kakariko Tavern',
|
'Sahasrahla', 'Superbunny Cave - Bottom', 'Superbunny Cave - Top',
|
||||||
'Sick Kid', 'Pyramid Fairy - Left', 'Pyramid Fairy - Right'
|
'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'
|
||||||
]),
|
]),
|
||||||
LocationGroup("MajorRoute2").locs([
|
LocationGroup("MajorRoute2").locs([
|
||||||
'King Zora', 'Sahasrahla', 'Ice Rod Cave', 'Catfish',
|
'Mushroom', 'Secret Passage', 'Bottle Merchant', 'Flute Spot', 'Catfish', 'Stumpy', 'Waterfall Fairy - Left',
|
||||||
'Purple Chest', 'Waterfall Fairy - Left', 'Waterfall Fairy - Right', 'Blacksmith',
|
'Waterfall Fairy - Right', 'Master Sword Pedestal', "Thieves' Town - Attic", 'Sewers - Secret Room - Right',
|
||||||
'Magic Bat', 'Hobo', 'Potion Shop', 'Spike Cave', "King's Tomb"
|
'Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle'
|
||||||
]),
|
]),
|
||||||
LocationGroup("BigChest").locs([
|
LocationGroup("MajorRoute3").locs([
|
||||||
'Sanctuary', 'Eastern Palace - Big Chest',
|
'Kakariko Tavern', 'Sick Kid', 'King Zora', 'Potion Shop', 'Bombos Tablet', "King's Tomb", 'Blacksmith',
|
||||||
'Desert Palace - Big Chest', 'Tower of Hera - Big Chest', 'Palace of Darkness - Big Chest',
|
'Pyramid Fairy - Left', 'Pyramid Fairy - Right', 'Hookshot Cave - Top Right', 'Hookshot Cave - Top Left',
|
||||||
'Swamp Palace - Big Chest', 'Skull Woods - Big Chest', "Thieves' Town - Big Chest",
|
'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'
|
||||||
'Misery Mire - Big Chest', 'Hyrule Castle - Boomerang Chest', 'Ice Palace - Big Chest',
|
|
||||||
'Turtle Rock - Big Chest', 'Ganons Tower - Big Chest'
|
|
||||||
]),
|
]),
|
||||||
LocationGroup("BossUncle").locs([
|
LocationGroup("Dungeon Major").locs([
|
||||||
"Link's Uncle", "Link's House", 'Secret Passage', 'Eastern Palace - Boss',
|
'Eastern Palace - Big Chest', 'Desert Palace - Big Chest', 'Tower of Hera - Big Chest',
|
||||||
'Desert Palace - Boss', 'Tower of Hera - Boss', 'Palace of Darkness - Boss', 'Swamp Palace - Boss',
|
'Palace of Darkness - Big Chest', 'Swamp Palace - Big Chest', 'Skull Woods - Big Chest',
|
||||||
'Skull Woods - Boss', "Thieves' Town - Boss", 'Ice Palace - Boss', 'Misery Mire - Boss',
|
"Thieves' Town - Big Chest", 'Misery Mire - Big Chest', 'Hyrule Castle - Boomerang Chest',
|
||||||
'Turtle Rock - Boss']),
|
'Ice Palace - Big Chest', 'Turtle Rock - Big Chest', 'Ganons Tower - Big Chest', "Link's Uncle"]),
|
||||||
LocationGroup("HeartPieces LW").locs([
|
LocationGroup("Dungeon Heart").locs([
|
||||||
'Lost Woods Hideout', 'Kakariko Well - Top', "Blind's Hideout - Top", 'Maze Race', 'Sunken Treasure',
|
'Sanctuary', 'Eastern Palace - Boss', 'Desert Palace - Boss', 'Tower of Hera - Boss',
|
||||||
'Bonk Rock Cave', 'Desert Ledge', "Aginah's Cave", 'Spectacle Rock Cave', 'Spectacle Rock', 'Pyramid',
|
'Palace of Darkness - Boss', 'Swamp Palace - Boss', 'Skull Woods - Boss', "Thieves' Town - Boss",
|
||||||
'Lumberjack Tree', "Zora's Ledge"]),
|
'Ice Palace - Boss', 'Misery Mire - Boss', 'Turtle Rock - Boss', "Link's House",
|
||||||
LocationGroup("HeartPieces DW").locs([
|
'Ganons Tower - Validation Chest']),
|
||||||
'Lake Hylia Island', 'Chest Game', 'Digging Game', 'Graveyard Cave', 'Mimic Cave',
|
LocationGroup("HeartPieces1").locs([
|
||||||
'Cave 45', 'Peg Cave', 'Bumper Cave Ledge', 'Checkerboard Cave', 'Mire Shed - Right', 'Floating Island',
|
'Kakariko Well - Top', 'Lost Woods Hideout', 'Maze Race', 'Lumberjack Tree', 'Bonk Rock Cave', 'Graveyard Cave',
|
||||||
'Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right']),
|
'Checkerboard Cave', "Zora's Ledge", 'Digging Game', 'Desert Ledge', 'Bumper Cave Ledge', 'Floating Island',
|
||||||
LocationGroup("Minor Trash").locs([
|
'Swamp Palace - Waterfall Room']),
|
||||||
'Ice Palace - Freezor Chest', 'Skull Woods - Pot Prison', 'Misery Mire - Bridge Chest',
|
LocationGroup("HeartPieces2").locs([
|
||||||
'Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Maze - Top',
|
"Blind's Hideout - Top", 'Sunken Treasure', "Aginah's Cave", 'Mimic Cave', 'Spectacle Rock Cave', 'Cave 45',
|
||||||
'Palace of Darkness - Shooter Room', 'Palace of Darkness - The Arena - Bridge',
|
'Spectacle Rock', 'Lake Hylia Island', 'Chest Game', 'Mire Shed - Right', 'Pyramid', 'Peg Cave',
|
||||||
'Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right',
|
'Eastern Palace - Cannonball Chest']),
|
||||||
'Swamp Palace - Waterfall Room', 'Turtle Rock - Eye Bridge - Bottom Right',
|
LocationGroup("BlindHope").locs([
|
||||||
'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right']),
|
|
||||||
LocationGroup("CompassTT").locs([
|
|
||||||
"Thieves' Town - Ambush Chest", "Thieves' Town - Attic",
|
|
||||||
'Eastern Palace - Compass Chest', 'Desert Palace - Compass Chest', 'Tower of Hera - Compass Chest',
|
|
||||||
'Palace of Darkness - Compass Chest', 'Swamp Palace - Compass Chest', 'Skull Woods - Compass Chest',
|
|
||||||
"Thieves' Town - Compass Chest", 'Ice Palace - Compass Chest', 'Misery Mire - Compass Chest',
|
|
||||||
'Turtle Rock - Compass Chest', 'Ganons Tower - Compass Room - Top Left']),
|
|
||||||
LocationGroup("Early SKs").locs([
|
|
||||||
'Sewers - Dark Cross', 'Desert Palace - Torch', 'Tower of Hera - Basement Cage',
|
|
||||||
'Palace of Darkness - Stalfos Basement', 'Palace of Darkness - Dark Basement - Right',
|
|
||||||
'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Harmless Hellway',
|
|
||||||
"Thieves' Town - Blind's Cell", 'Eastern Palace - Cannonball Chest',
|
|
||||||
'Sewers - Secret Room - Right', 'Sewers - Secret Room - Left',
|
|
||||||
'Sewers - Secret Room - Middle', 'Floodgate Chest'
|
|
||||||
]),
|
|
||||||
LocationGroup("Late SKs").locs([
|
|
||||||
'Skull Woods - Bridge Room', 'Ice Palace - Spike Room', "Hyrule Castle - Zelda's Chest",
|
|
||||||
'Ice Palace - Iced T Room', 'Misery Mire - Main Lobby', 'Swamp Palace - West Chest',
|
|
||||||
'Turtle Rock - Chain Chomps', 'Turtle Rock - Crystaroller Room',
|
|
||||||
'Turtle Rock - Eye Bridge - Bottom Left', "Ganons Tower - Bob's Torch", 'Ganons Tower - Tile Room',
|
|
||||||
'Ganons Tower - Firesnake Room', 'Ganons Tower - Pre-Moldorm Chest',
|
|
||||||
]),
|
|
||||||
LocationGroup("Kak-LDM").locs([
|
|
||||||
"Blind's Hideout - Left", "Blind's Hideout - Right", "Blind's Hideout - Far Left",
|
"Blind's Hideout - Left", "Blind's Hideout - Right", "Blind's Hideout - Far Left",
|
||||||
"Blind's Hideout - Far Right", 'Chicken House', 'Paradox Cave Lower - Far Left',
|
"Blind's Hideout - Far Right", 'Floodgate Chest', 'Spiral Cave', 'Palace of Darkness - Dark Maze - Bottom',
|
||||||
'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right',
|
'Palace of Darkness - Dark Maze - Top', 'Swamp Palace - Flooded Room - Left',
|
||||||
'Paradox Cave Lower - Middle', 'Paradox Cave Upper - Left', 'Paradox Cave Upper - Right', 'Spiral Cave',
|
'Swamp Palace - Flooded Room - Right', "Thieves' Town - Ambush Chest", 'Ganons Tower - Hope Room - Left',
|
||||||
|
'Ganons Tower - Hope Room - Right']),
|
||||||
|
LocationGroup('WellHype').locs([
|
||||||
|
'Kakariko Well - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right', 'Kakariko Well - Bottom',
|
||||||
|
'Paradox Cave Upper - Left', 'Paradox Cave Upper - Right', 'Hype Cave - Top', 'Hype Cave - Middle Right',
|
||||||
|
'Hype Cave - Middle Left', 'Hype Cave - Bottom', 'Hype Cave - Generous Guy',
|
||||||
|
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right',
|
||||||
]),
|
]),
|
||||||
LocationGroup("BK-Bunny").locs([
|
LocationGroup('MiniMoldormLasers').locs([
|
||||||
'Eastern Palace - Big Key Chest', 'Ganons Tower - Big Key Chest',
|
'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Generous Guy',
|
||||||
'Desert Palace - Big Key Chest', 'Tower of Hera - Big Key Chest', 'Palace of Darkness - Big Key Chest',
|
'Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Far Right', 'Chicken House', 'Brewery',
|
||||||
'Swamp Palace - Big Key Chest', "Thieves' Town - Big Key Chest", 'Skull Woods - Big Key Chest',
|
'Palace of Darkness - Dark Basement - Left', 'Ice Palace - Freezor Chest', 'Swamp Palace - West Chest',
|
||||||
'Ice Palace - Big Key Chest', 'Misery Mire - Big Key Chest', 'Turtle Rock - Big Key Chest',
|
'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left',
|
||||||
'Superbunny Cave - Top', 'Superbunny Cave - Bottom',
|
'Turtle Rock - Eye Bridge - Top Right',
|
||||||
]),
|
]),
|
||||||
LocationGroup("Early Drops").flags(True, True).locs([
|
LocationGroup('ParadoxCloset').locs([
|
||||||
|
"Sahasrahla's Hut - Left", "Sahasrahla's Hut - Right", "Sahasrahla's Hut - Middle",
|
||||||
|
'Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right',
|
||||||
|
'Paradox Cave Lower - Far Right', 'Paradox Cave Lower - Middle', "Hyrule Castle - Zelda's Chest",
|
||||||
|
'C-Shaped House', 'Mire Shed - Left', 'Ganons Tower - Compass Room - Bottom Right',
|
||||||
|
'Ganons Tower - Compass Room - Bottom Left',
|
||||||
|
])
|
||||||
|
]
|
||||||
|
|
||||||
|
other_clusters = {
|
||||||
|
'SmallKey1': [
|
||||||
|
'Sewers - Dark Cross', 'Tower of Hera - Basement Cage', 'Palace of Darkness - Shooter Room',
|
||||||
|
'Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement',
|
||||||
|
'Palace of Darkness - Dark Basement - Right', "Thieves' Town - Blind's Cell", 'Skull Woods - Bridge Room',
|
||||||
|
'Ice Palace - Iced T Room', 'Misery Mire - Main Lobby', 'Misery Mire - Bridge Chest',
|
||||||
|
'Misery Mire - Spike Chest', "Ganons Tower - Bob's Torch"],
|
||||||
|
'SmallKey2': [
|
||||||
|
'Desert Palace - Torch', 'Castle Tower - Room 03', 'Castle Tower - Dark Maze',
|
||||||
|
'Palace of Darkness - The Arena - Ledge', 'Palace of Darkness - Harmless Hellway', 'Swamp Palace - Entrance',
|
||||||
|
'Skull Woods - Pot Prison', 'Skull Woods - Pinball Room', 'Ice Palace - Spike Room',
|
||||||
|
'Turtle Rock - Roller Room - Right', 'Turtle Rock - Chain Chomps', 'Turtle Rock - Crystaroller Room',
|
||||||
|
'Turtle Rock - Eye Bridge - Bottom Left'],
|
||||||
|
'SmallKeyLeft': [
|
||||||
|
'Ganons Tower - Tile Room', 'Ganons Tower - Firesnake Room', 'Ganons Tower - Pre-Moldorm Chest'],
|
||||||
|
'KeyDrop1': [
|
||||||
'Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
'Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
||||||
'Hyrule Castle - Key Rat Key Drop', 'Eastern Palace - Dark Square Pot Key',
|
'Hyrule Castle - Key Rat Key Drop', 'Swamp Palace - Hookshot Pot Key', 'Swamp Palace - Trench 2 Pot Key',
|
||||||
'Eastern Palace - Dark Eyegore Key Drop', 'Desert Palace - Desert Tiles 1 Pot Key',
|
'Swamp Palace - Waterway Pot Key', 'Skull Woods - West Lobby Pot Key', 'Skull Woods - Spike Corner Key Drop',
|
||||||
'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key',
|
'Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop', 'Misery Mire - Spikes Pot Key',
|
||||||
|
'Misery Mire - Fishbone Pot Key', 'Misery Mire - Conveyor Crystal Key Drop'],
|
||||||
|
'KeyDrop2': [
|
||||||
|
'Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop',
|
||||||
|
'Desert Palace - Desert Tiles 1 Pot Key', 'Desert Palace - Beamos Hall Pot Key',
|
||||||
|
'Desert Palace - Desert Tiles 2 Pot Key', 'Swamp Palace - Pot Row Pot Key', 'Swamp Palace - Trench 1 Pot Key',
|
||||||
|
"Thieves' Town - Hallway Pot Key", "Thieves' Town - Spike Switch Pot Key", 'Ice Palace - Hammer Block Key Drop',
|
||||||
|
'Ice Palace - Many Pots Pot Key', 'Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop'],
|
||||||
|
'KeyDropLeft': [
|
||||||
'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop',
|
'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop',
|
||||||
'Thieves\' Town - Hallway Pot Key', 'Thieves\' Town - Spike Switch Pot Key', 'Hyrule Castle - Big Key Drop',
|
'Ganons Tower - Conveyor Cross Pot Key', 'Ganons Tower - Double Switch Pot Key',
|
||||||
]),
|
'Ganons Tower - Conveyor Star Pits Pot Key', 'Ganons Tower - Mini Helmasuar Key Drop'],
|
||||||
LocationGroup("Late Drops").flags(True, True).locs([
|
'Shopsanity1': [
|
||||||
'Swamp Palace - Pot Row Pot Key', 'Swamp Palace - Trench 1 Pot Key', 'Swamp Palace - Hookshot Pot Key',
|
|
||||||
'Swamp Palace - Trench 2 Pot Key', 'Swamp Palace - Waterway Pot Key', 'Skull Woods - West Lobby Pot Key',
|
|
||||||
'Skull Woods - Spike Corner Key Drop', 'Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop',
|
|
||||||
'Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key', 'Ganons Tower - Conveyor Cross Pot Key',
|
|
||||||
'Ganons Tower - Double Switch Pot Key']),
|
|
||||||
LocationGroup("SS-Hype-Voo").locs([
|
|
||||||
'Mini Moldorm Cave - Left',
|
|
||||||
'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Generous Guy', 'Mini Moldorm Cave - Far Left',
|
|
||||||
'Mini Moldorm Cave - Far Right', 'Hype Cave - Top', 'Hype Cave - Middle Right',
|
|
||||||
'Hype Cave - Middle Left', 'Hype Cave - Bottom', 'Hype Cave - Generous Guy', 'Brewery',
|
|
||||||
'C-Shaped House', 'Palace of Darkness - The Arena - Ledge',
|
|
||||||
]),
|
|
||||||
LocationGroup("DDM Hard").flags(True, True).locs([
|
|
||||||
'Hookshot Cave - Top Right', 'Hookshot Cave - Top Left',
|
|
||||||
'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left',
|
|
||||||
'Misery Mire - Spike Chest', 'Misery Mire - Spikes Pot Key', 'Misery Mire - Fishbone Pot Key',
|
|
||||||
'Misery Mire - Conveyor Crystal Key Drop', 'Turtle Rock - Pokey 1 Key Drop',
|
|
||||||
'Turtle Rock - Pokey 2 Key Drop', 'Turtle Rock - Roller Room - Right',
|
|
||||||
'Ganons Tower - Conveyor Star Pits Pot Key', 'Ganons Tower - Mini Helmasaur Key Drop'
|
|
||||||
]),
|
|
||||||
LocationGroup("Kak Shop").flags(False, False, True).locs([
|
|
||||||
'Dark Lake Hylia Shop - Left', 'Dark Lake Hylia Shop - Middle', 'Dark Lake Hylia Shop - Right',
|
|
||||||
'Dark Lumberjack Shop - Left', 'Dark Lumberjack Shop - Middle', 'Dark Lumberjack Shop - Right',
|
'Dark Lumberjack Shop - Left', 'Dark Lumberjack Shop - Middle', 'Dark Lumberjack Shop - Right',
|
||||||
|
'Dark Lake Hylia Shop - Left', 'Dark Lake Hylia Shop - Middle', 'Dark Lake Hylia Shop - Right',
|
||||||
'Paradox Shop - Left', 'Paradox Shop - Middle', 'Paradox Shop - Right',
|
'Paradox Shop - Left', 'Paradox Shop - Middle', 'Paradox Shop - Right',
|
||||||
'Kakariko Shop - Left', 'Kakariko Shop - Middle', 'Kakariko Shop - Right',
|
'Kakariko Shop - Left', 'Kakariko Shop - Middle', 'Kakariko Shop - Right', 'Capacity Upgrade - Left'],
|
||||||
'Capacity Upgrade - Left']),
|
'Shopsanity2': [
|
||||||
LocationGroup("Hylia Shop").flags(False, False, True).locs([
|
|
||||||
'Red Shield Shop - Left', 'Red Shield Shop - Middle', 'Red Shield Shop - Right',
|
'Red Shield Shop - Left', 'Red Shield Shop - Middle', 'Red Shield Shop - Right',
|
||||||
'Village of Outcasts Shop - Left', 'Village of Outcasts Shop - Middle', 'Village of Outcasts Shop - Right',
|
'Village of Outcasts Shop - Left', 'Village of Outcasts Shop - Middle', 'Village of Outcasts Shop - Right',
|
||||||
'Dark Potion Shop - Left', 'Dark Potion Shop - Middle', 'Dark Potion Shop - Right',
|
'Dark Potion Shop - Left', 'Dark Potion Shop - Middle', 'Dark Potion Shop - Right',
|
||||||
'Lake Hylia Shop - Left', 'Lake Hylia Shop - Middle', 'Lake Hylia Shop - Right',
|
'Lake Hylia Shop - Left', 'Lake Hylia Shop - Middle', 'Lake Hylia Shop - Right', 'Capacity Upgrade - Right',
|
||||||
'Capacity Upgrade - Right']),
|
],
|
||||||
LocationGroup("Map Validation").locs([
|
'ShopsanityLeft': ['Potion Shop - Left', 'Potion Shop - Middle', 'Potion Shop - Right']
|
||||||
'Hyrule Castle - Map Chest',
|
}
|
||||||
'Eastern Palace - Map Chest', 'Desert Palace - Map Chest', 'Tower of Hera - Map Chest',
|
|
||||||
'Palace of Darkness - Map Chest', 'Swamp Palace - Map Chest', 'Skull Woods - Map Chest',
|
|
||||||
"Thieves' Town - Map Chest", 'Ice Palace - Map Chest', 'Misery Mire - Map Chest',
|
|
||||||
'Turtle Rock - Roller Room - Left', 'Ganons Tower - Map Chest', 'Ganons Tower - Validation Chest']),
|
|
||||||
LocationGroup("SahasWell+MireHopeDDMShop").flags(False, False, True).locs([
|
|
||||||
'Dark Death Mountain Shop - Left', 'Dark Death Mountain Shop - Middle', 'Dark Death Mountain Shop - Right',
|
|
||||||
'Kakariko Well - Bottom', 'Kakariko Well - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right',
|
|
||||||
"Sahasrahla's Hut - Left", "Sahasrahla's Hut - Right", "Sahasrahla's Hut - Middle",
|
|
||||||
'Mire Shed - Left', 'Ganons Tower - Hope Room - Left', 'Ganons Tower - Hope Room - Right']),
|
|
||||||
LocationGroup("Tower Pain").flags(True).locs([
|
|
||||||
'Castle Tower - Room 03', 'Castle Tower - Dark Maze',
|
|
||||||
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right',
|
|
||||||
'Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right',
|
|
||||||
'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right',
|
|
||||||
'Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right',
|
|
||||||
"Ganons Tower - Bob's Chest", 'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Big Key Room - Left']),
|
|
||||||
LocationGroup("Retro Shops").flags(False, False, True, True).locs([
|
|
||||||
'Old Man Sword Cave Item 1', 'Take-Any #1 Item 1', 'Take-Any #1 Item 2',
|
|
||||||
'Take-Any #2 Item 1', 'Take-Any #2 Item 2', 'Take-Any #3 Item 1', 'Take-Any #3 Item 2',
|
|
||||||
'Take-Any #4 Item 1', 'Take-Any #4 Item 2', 'Swamp Palace - Entrance',
|
|
||||||
'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Top Right',
|
|
||||||
'Ganons Tower - Compass Room - Bottom Right',
|
|
||||||
])
|
|
||||||
|
|
||||||
|
leftovers = [
|
||||||
|
'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Top Left',
|
||||||
|
'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Randomizer Room - Top Left',
|
||||||
|
'Ganons Tower - Randomizer Room - Top Right',"Ganons Tower - Bob's Chest", 'Ganons Tower - Big Key Room - Left',
|
||||||
|
'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Mini Helmasaur Room - Left',
|
||||||
|
'Ganons Tower - Mini Helmasaur Room - Right',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
vanilla_swords = {"Link's Uncle", 'Master Sword Pedestal', 'Blacksmith', 'Pyramid Fairy - Left'}
|
||||||
|
|
||||||
trash_items = {
|
trash_items = {
|
||||||
'Nothing': -1,
|
'Nothing': -1,
|
||||||
|
|||||||
Reference in New Issue
Block a user