Main structure for various biased fills
Lots of help around correctly reserving locations
This commit is contained in:
@@ -17,6 +17,7 @@ from Utils import int16_as_bytes
|
|||||||
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
|
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
|
||||||
from RoomData import Room
|
from RoomData import Room
|
||||||
|
|
||||||
|
|
||||||
class World(object):
|
class World(object):
|
||||||
|
|
||||||
def __init__(self, players, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments,
|
def __init__(self, players, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments,
|
||||||
@@ -213,6 +214,11 @@ class World(object):
|
|||||||
return r_location
|
return r_location
|
||||||
raise RuntimeError('No such location %s for player %d' % (location, player))
|
raise RuntimeError('No such location %s for player %d' % (location, player))
|
||||||
|
|
||||||
|
def get_location_unsafe(self, location, player):
|
||||||
|
if (location, player) in self._location_cache:
|
||||||
|
return self._location_cache[(location, player)]
|
||||||
|
return None
|
||||||
|
|
||||||
def get_dungeon(self, dungeonname, player):
|
def get_dungeon(self, dungeonname, player):
|
||||||
if isinstance(dungeonname, Dungeon):
|
if isinstance(dungeonname, Dungeon):
|
||||||
return dungeonname
|
return dungeonname
|
||||||
@@ -1452,6 +1458,10 @@ class Dungeon(object):
|
|||||||
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
|
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
|
||||||
|
|
||||||
|
|
||||||
|
class FillError(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class DoorType(Enum):
|
class DoorType(Enum):
|
||||||
Normal = 1
|
Normal = 1
|
||||||
@@ -1842,6 +1852,8 @@ class Sector(object):
|
|||||||
self.destination_entrance = False
|
self.destination_entrance = False
|
||||||
self.equations = None
|
self.equations = None
|
||||||
self.item_logic = set()
|
self.item_logic = set()
|
||||||
|
self.chest_location_set = set()
|
||||||
|
|
||||||
|
|
||||||
def region_set(self):
|
def region_set(self):
|
||||||
if self.r_name_set is None:
|
if self.r_name_set is None:
|
||||||
@@ -2084,6 +2096,7 @@ class Location(object):
|
|||||||
self.recursion_count = 0
|
self.recursion_count = 0
|
||||||
self.staleness_count = 0
|
self.staleness_count = 0
|
||||||
self.locked = False
|
self.locked = False
|
||||||
|
self.real = not crystal
|
||||||
self.always_allow = lambda item, state: False
|
self.always_allow = lambda item, state: False
|
||||||
self.access_rule = lambda state: True
|
self.access_rule = lambda state: True
|
||||||
self.item_rule = lambda item: True
|
self.item_rule = lambda item: True
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
import RaceRandom as random
|
import RaceRandom as random
|
||||||
|
|
||||||
from BaseClasses import Boss
|
from BaseClasses import Boss, FillError
|
||||||
from Fill import FillError
|
|
||||||
|
|
||||||
def BossFactory(boss, player):
|
def BossFactory(boss, player):
|
||||||
if boss is None:
|
if boss is None:
|
||||||
|
|||||||
@@ -46,8 +46,7 @@ def link_doors(world, player):
|
|||||||
world.swamp_patch_required[player] = orig_swamp_patch
|
world.swamp_patch_required[player] = orig_swamp_patch
|
||||||
|
|
||||||
|
|
||||||
def link_doors_main(world, player):
|
def link_doors_prep(world, player):
|
||||||
|
|
||||||
# Drop-down connections & push blocks
|
# Drop-down connections & push blocks
|
||||||
for exitName, regionName in logical_connections:
|
for exitName, regionName in logical_connections:
|
||||||
connect_simple_door(world, exitName, regionName, player)
|
connect_simple_door(world, exitName, regionName, player)
|
||||||
@@ -99,6 +98,7 @@ def link_doors_main(world, player):
|
|||||||
analyze_portals(world, player)
|
analyze_portals(world, player)
|
||||||
for portal in world.dungeon_portals[player]:
|
for portal in world.dungeon_portals[player]:
|
||||||
connect_portal(portal, world, player)
|
connect_portal(portal, world, player)
|
||||||
|
|
||||||
if not world.doorShuffle[player] == 'vanilla':
|
if not world.doorShuffle[player] == 'vanilla':
|
||||||
fix_big_key_doors_with_ugly_smalls(world, player)
|
fix_big_key_doors_with_ugly_smalls(world, player)
|
||||||
else:
|
else:
|
||||||
@@ -119,11 +119,14 @@ def link_doors_main(world, player):
|
|||||||
for ent, ext in default_one_way_connections:
|
for ent, ext in default_one_way_connections:
|
||||||
connect_one_way(world, ent, ext, player)
|
connect_one_way(world, ent, ext, player)
|
||||||
vanilla_key_logic(world, player)
|
vanilla_key_logic(world, player)
|
||||||
elif world.doorShuffle[player] == 'basic':
|
|
||||||
|
|
||||||
|
def link_doors_main(world, player):
|
||||||
|
if world.doorShuffle[player] == 'basic':
|
||||||
within_dungeon(world, player)
|
within_dungeon(world, player)
|
||||||
elif world.doorShuffle[player] == 'crossed':
|
elif world.doorShuffle[player] == 'crossed':
|
||||||
cross_dungeon(world, player)
|
cross_dungeon(world, player)
|
||||||
else:
|
elif world.doorShuffle[player] != 'vanilla':
|
||||||
logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
||||||
raise Exception('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
raise Exception('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
||||||
|
|
||||||
@@ -214,11 +217,33 @@ def vanilla_key_logic(world, player):
|
|||||||
world.key_logic[player] = {}
|
world.key_logic[player] = {}
|
||||||
analyze_dungeon(key_layout, world, player)
|
analyze_dungeon(key_layout, world, player)
|
||||||
world.key_logic[player][builder.name] = key_layout.key_logic
|
world.key_logic[player][builder.name] = key_layout.key_logic
|
||||||
|
world.key_layout[player][builder.name] = key_layout
|
||||||
log_key_logic(builder.name, key_layout.key_logic)
|
log_key_logic(builder.name, key_layout.key_logic)
|
||||||
# if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]:
|
# if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]:
|
||||||
# validate_vanilla_key_logic(world, player)
|
# validate_vanilla_key_logic(world, player)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_vanilla_reservation(dungeon, world, player):
|
||||||
|
return validate_key_layout(world.key_layout[player][dungeon.name], world, player)
|
||||||
|
# if not hasattr(world, 'builder_cache'):
|
||||||
|
# world.builder_cache = {}
|
||||||
|
# if (dungeon.name, player) not in world.builder_cache:
|
||||||
|
# sector = Sector()
|
||||||
|
# sector.name = dungeon.name
|
||||||
|
# sector.regions.extend(convert_regions(dungeon.regions, world, player))
|
||||||
|
# builder = simple_dungeon_builder(sector.name, [sector])
|
||||||
|
# builder.master_sector = sector
|
||||||
|
#
|
||||||
|
# origin_list = find_accessible_entrances(world, player, builder)
|
||||||
|
# start_regions = convert_regions(origin_list, world, player)
|
||||||
|
# doors = convert_key_doors(default_small_key_doors[builder.name], world, player)
|
||||||
|
# key_layout = build_key_layout(builder, start_regions, doors, world, player)
|
||||||
|
# world.builder_cache[(dungeon.name, player)] = key_layout
|
||||||
|
# else:
|
||||||
|
# key_layout = world.builder_cache[(dungeon.name, player)]
|
||||||
|
# return validate_key_layout(key_layout, world, player)
|
||||||
|
|
||||||
|
|
||||||
# some useful functions
|
# some useful functions
|
||||||
oppositemap = {
|
oppositemap = {
|
||||||
Direction.South: Direction.North,
|
Direction.South: Direction.North,
|
||||||
|
|||||||
@@ -1198,6 +1198,8 @@ class DungeonBuilder(object):
|
|||||||
self.sectors = []
|
self.sectors = []
|
||||||
self.location_cnt = 0
|
self.location_cnt = 0
|
||||||
self.key_drop_cnt = 0
|
self.key_drop_cnt = 0
|
||||||
|
self.dungeon_items = None # during fill how many dungeon items are left
|
||||||
|
self.free_items = None # during fill how many dungeon items are left
|
||||||
self.bk_required = False
|
self.bk_required = False
|
||||||
self.bk_provided = False
|
self.bk_provided = False
|
||||||
self.c_switch_required = False
|
self.c_switch_required = False
|
||||||
@@ -1359,7 +1361,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
|
|||||||
polarized_sectors[sector] = None
|
polarized_sectors[sector] = None
|
||||||
if bow_sectors:
|
if bow_sectors:
|
||||||
assign_bow_sectors(dungeon_map, bow_sectors, global_pole)
|
assign_bow_sectors(dungeon_map, bow_sectors, global_pole)
|
||||||
assign_location_sectors(dungeon_map, free_location_sectors, global_pole)
|
assign_location_sectors(dungeon_map, free_location_sectors, global_pole, world, player)
|
||||||
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole)
|
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole)
|
||||||
ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole)
|
ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole)
|
||||||
for sector in leftover:
|
for sector in leftover:
|
||||||
@@ -1510,6 +1512,7 @@ def define_sector_features(sectors):
|
|||||||
sector.bk_provided = True
|
sector.bk_provided = True
|
||||||
elif loc.name not in dungeon_events and not loc.forced_item:
|
elif loc.name not in dungeon_events and not loc.forced_item:
|
||||||
sector.chest_locations += 1
|
sector.chest_locations += 1
|
||||||
|
sector.chest_location_set.add(loc.name)
|
||||||
if '- Big Chest' in loc.name or loc.name in ["Hyrule Castle - Zelda's Chest",
|
if '- Big Chest' in loc.name or loc.name in ["Hyrule Castle - Zelda's Chest",
|
||||||
"Thieves' Town - Blind's Cell"]:
|
"Thieves' Town - Blind's Cell"]:
|
||||||
sector.bk_required = True
|
sector.bk_required = True
|
||||||
@@ -1590,19 +1593,26 @@ def assign_bow_sectors(dungeon_map, bow_sectors, global_pole):
|
|||||||
assign_sector(sector_list[i], builder, bow_sectors, global_pole)
|
assign_sector(sector_list[i], builder, bow_sectors, global_pole)
|
||||||
|
|
||||||
|
|
||||||
def assign_location_sectors(dungeon_map, free_location_sectors, global_pole):
|
def assign_location_sectors(dungeon_map, free_location_sectors, global_pole, world, player):
|
||||||
valid = False
|
valid = False
|
||||||
choices = None
|
choices = None
|
||||||
sector_list = list(free_location_sectors)
|
sector_list = list(free_location_sectors)
|
||||||
random.shuffle(sector_list)
|
random.shuffle(sector_list)
|
||||||
|
orig_location_set = build_orig_location_set(dungeon_map)
|
||||||
|
num_dungeon_items = requested_dungeon_items(world, player)
|
||||||
while not valid:
|
while not valid:
|
||||||
choices, d_idx, totals = weighted_random_locations(dungeon_map, sector_list)
|
choices, d_idx, totals = weighted_random_locations(dungeon_map, sector_list)
|
||||||
|
location_set = {x: set(y) for x, y in orig_location_set.items()}
|
||||||
for i, sector in enumerate(sector_list):
|
for i, sector in enumerate(sector_list):
|
||||||
choice = d_idx[choices[i].name]
|
d_name = choices[i].name
|
||||||
|
choice = d_idx[d_name]
|
||||||
totals[choice] += sector.chest_locations
|
totals[choice] += sector.chest_locations
|
||||||
|
location_set[d_name].update(sector.chest_location_set)
|
||||||
valid = True
|
valid = True
|
||||||
for d_name, idx in d_idx.items():
|
for d_name, idx in d_idx.items():
|
||||||
if totals[idx] < 5: # min locations for dungeons is 5 (bk exception)
|
free_items = count_reserved_locations(world, player, location_set[d_name])
|
||||||
|
target = max(free_items, 2) + num_dungeon_items
|
||||||
|
if totals[idx] < target:
|
||||||
valid = False
|
valid = False
|
||||||
break
|
break
|
||||||
for i, choice in enumerate(choices):
|
for i, choice in enumerate(choices):
|
||||||
@@ -1633,6 +1643,30 @@ def weighted_random_locations(dungeon_map, free_location_sectors):
|
|||||||
return choices, d_idx, totals
|
return choices, d_idx, totals
|
||||||
|
|
||||||
|
|
||||||
|
def build_orig_location_set(dungeon_map):
|
||||||
|
orig_locations = {}
|
||||||
|
for name, builder in dungeon_map.items():
|
||||||
|
orig_locations[name] = set().union(*(s.chest_location_set for s in builder.sectors))
|
||||||
|
return orig_locations
|
||||||
|
|
||||||
|
|
||||||
|
def requested_dungeon_items(world, player):
|
||||||
|
num = 0
|
||||||
|
if not world.bigkeyshuffle[player]:
|
||||||
|
num += 1
|
||||||
|
if not world.compassshuffle[player]:
|
||||||
|
num += 1
|
||||||
|
if not world.mapshuffle[player]:
|
||||||
|
num += 1
|
||||||
|
return num
|
||||||
|
|
||||||
|
|
||||||
|
def count_reserved_locations(world, player, proposed_set):
|
||||||
|
if world.item_pool_config:
|
||||||
|
return len([x for x in proposed_set if x in world.item_pool_config.reserved_locations[player]])
|
||||||
|
return 2
|
||||||
|
|
||||||
|
|
||||||
def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole, assign_one=False):
|
def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole, assign_one=False):
|
||||||
population = []
|
population = []
|
||||||
some_c_switches_present = False
|
some_c_switches_present = False
|
||||||
|
|||||||
114
Dungeons.py
114
Dungeons.py
@@ -1,8 +1,5 @@
|
|||||||
import RaceRandom as random
|
|
||||||
|
|
||||||
from BaseClasses import Dungeon
|
from BaseClasses import Dungeon
|
||||||
from Bosses import BossFactory
|
from Bosses import BossFactory
|
||||||
from Fill import fill_restrictive
|
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
|
|
||||||
|
|
||||||
@@ -36,117 +33,6 @@ def create_dungeons(world, player):
|
|||||||
|
|
||||||
world.dungeons += [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT]
|
world.dungeons += [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT]
|
||||||
|
|
||||||
def fill_dungeons(world):
|
|
||||||
freebes = ['Ganons Tower - Map Chest', 'Palace of Darkness - Harmless Hellway', 'Palace of Darkness - Big Key Chest', 'Turtle Rock - Big Key Chest']
|
|
||||||
|
|
||||||
all_state_base = world.get_all_state()
|
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
|
||||||
pinball_room = world.get_location('Skull Woods - Pinball Room', player)
|
|
||||||
if world.retro[player]:
|
|
||||||
world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
|
|
||||||
else:
|
|
||||||
world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
|
|
||||||
pinball_room.event = True
|
|
||||||
pinball_room.locked = True
|
|
||||||
|
|
||||||
dungeons = [(list(dungeon.regions), dungeon.big_key, list(dungeon.small_keys), list(dungeon.dungeon_items)) for dungeon in world.dungeons]
|
|
||||||
|
|
||||||
loopcnt = 0
|
|
||||||
while dungeons:
|
|
||||||
loopcnt += 1
|
|
||||||
dungeon_regions, big_key, small_keys, dungeon_items = dungeons.pop(0)
|
|
||||||
# this is what we need to fill
|
|
||||||
dungeon_locations = [location for location in world.get_unfilled_locations() if location.parent_region.name in dungeon_regions]
|
|
||||||
random.shuffle(dungeon_locations)
|
|
||||||
|
|
||||||
all_state = all_state_base.copy()
|
|
||||||
|
|
||||||
# first place big key
|
|
||||||
if big_key is not None:
|
|
||||||
bk_location = None
|
|
||||||
for location in dungeon_locations:
|
|
||||||
if location.item_rule(big_key):
|
|
||||||
bk_location = location
|
|
||||||
break
|
|
||||||
|
|
||||||
if bk_location is None:
|
|
||||||
raise RuntimeError('No suitable location for %s' % big_key)
|
|
||||||
|
|
||||||
world.push_item(bk_location, big_key, False)
|
|
||||||
bk_location.event = True
|
|
||||||
bk_location.locked = True
|
|
||||||
dungeon_locations.remove(bk_location)
|
|
||||||
big_key = None
|
|
||||||
|
|
||||||
# next place small keys
|
|
||||||
while small_keys:
|
|
||||||
small_key = small_keys.pop()
|
|
||||||
all_state.sweep_for_events()
|
|
||||||
sk_location = None
|
|
||||||
for location in dungeon_locations:
|
|
||||||
if location.name in freebes or (location.can_reach(all_state) and location.item_rule(small_key)):
|
|
||||||
sk_location = location
|
|
||||||
break
|
|
||||||
|
|
||||||
if sk_location is None:
|
|
||||||
# need to retry this later
|
|
||||||
small_keys.append(small_key)
|
|
||||||
dungeons.append((dungeon_regions, big_key, small_keys, dungeon_items))
|
|
||||||
# infinite regression protection
|
|
||||||
if loopcnt < (30 * world.players):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise RuntimeError('No suitable location for %s' % small_key)
|
|
||||||
|
|
||||||
world.push_item(sk_location, small_key, False)
|
|
||||||
sk_location.event = True
|
|
||||||
sk_location.locked = True
|
|
||||||
dungeon_locations.remove(sk_location)
|
|
||||||
|
|
||||||
if small_keys:
|
|
||||||
# key placement not finished, loop again
|
|
||||||
continue
|
|
||||||
|
|
||||||
# next place dungeon items
|
|
||||||
for dungeon_item in dungeon_items:
|
|
||||||
di_location = dungeon_locations.pop()
|
|
||||||
world.push_item(di_location, dungeon_item, False)
|
|
||||||
|
|
||||||
|
|
||||||
def get_dungeon_item_pool(world):
|
|
||||||
return [item for dungeon in world.dungeons for item in dungeon.all_items]
|
|
||||||
|
|
||||||
|
|
||||||
def fill_dungeons_restrictive(world, shuffled_locations):
|
|
||||||
all_state_base = world.get_all_state()
|
|
||||||
|
|
||||||
# for player in range(1, world.players + 1):
|
|
||||||
# pinball_room = world.get_location('Skull Woods - Pinball Room', player)
|
|
||||||
# if world.retro[player]:
|
|
||||||
# world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
|
|
||||||
# else:
|
|
||||||
# world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
|
|
||||||
# pinball_room.event = True
|
|
||||||
# pinball_room.locked = True
|
|
||||||
# shuffled_locations.remove(pinball_room)
|
|
||||||
|
|
||||||
# with shuffled dungeon items they are distributed as part of the normal item pool
|
|
||||||
for item in world.get_items():
|
|
||||||
if (item.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]):
|
|
||||||
item.advancement = True
|
|
||||||
elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]):
|
|
||||||
item.priority = True
|
|
||||||
|
|
||||||
dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world)]
|
|
||||||
|
|
||||||
# sort in the order Big Key, Small Key, Other before placing dungeon items
|
|
||||||
sort_order = {"BigKey": 3, "SmallKey": 2}
|
|
||||||
dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
|
|
||||||
|
|
||||||
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items,
|
|
||||||
keys_in_itempool={player: not world.keyshuffle[player] for player in range(1, world.players+1)}, single_player_placement=True)
|
|
||||||
|
|
||||||
|
|
||||||
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
|
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
|
||||||
'Desert Palace - Prize': [0x1559B, 0x1559C, 0x1559D, 0x1559E],
|
'Desert Palace - Prize': [0x1559B, 0x1559C, 0x1559D, 0x1559E],
|
||||||
|
|||||||
403
Fill.py
403
Fill.py
@@ -3,176 +3,74 @@ import collections
|
|||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from BaseClasses import CollectionState
|
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
|
||||||
|
|
||||||
|
|
||||||
class FillError(RuntimeError):
|
def get_dungeon_item_pool(world):
|
||||||
pass
|
return [item for dungeon in world.dungeons for item in dungeon.all_items]
|
||||||
|
|
||||||
def distribute_items_cutoff(world, cutoffrate=0.33):
|
|
||||||
# get list of locations to fill in
|
|
||||||
fill_locations = world.get_unfilled_locations()
|
|
||||||
random.shuffle(fill_locations)
|
|
||||||
|
|
||||||
# get items to distribute
|
|
||||||
random.shuffle(world.itempool)
|
|
||||||
itempool = world.itempool
|
|
||||||
|
|
||||||
total_advancement_items = len([item for item in itempool if item.advancement])
|
|
||||||
placed_advancement_items = 0
|
|
||||||
|
|
||||||
progress_done = False
|
|
||||||
advancement_placed = False
|
|
||||||
|
|
||||||
# sweep once to pick up preplaced items
|
|
||||||
world.state.sweep_for_events()
|
|
||||||
|
|
||||||
while itempool and fill_locations:
|
|
||||||
candidate_item_to_place = None
|
|
||||||
item_to_place = None
|
|
||||||
for item in itempool:
|
|
||||||
if advancement_placed or (progress_done and (item.advancement or item.priority)):
|
|
||||||
item_to_place = item
|
|
||||||
break
|
|
||||||
if item.advancement:
|
|
||||||
candidate_item_to_place = item
|
|
||||||
if world.unlocks_new_location(item):
|
|
||||||
item_to_place = item
|
|
||||||
placed_advancement_items += 1
|
|
||||||
break
|
|
||||||
|
|
||||||
if item_to_place is None:
|
|
||||||
# check if we can reach all locations and that is why we find no new locations to place
|
|
||||||
if not progress_done and len(world.get_reachable_locations()) == len(world.get_locations()):
|
|
||||||
progress_done = True
|
|
||||||
continue
|
|
||||||
# check if we have now placed all advancement items
|
|
||||||
if progress_done:
|
|
||||||
advancement_placed = True
|
|
||||||
continue
|
|
||||||
# we might be in a situation where all new locations require multiple items to reach. If that is the case, just place any advancement item we've found and continue trying
|
|
||||||
if candidate_item_to_place is not None:
|
|
||||||
item_to_place = candidate_item_to_place
|
|
||||||
placed_advancement_items += 1
|
|
||||||
else:
|
|
||||||
# we placed all available progress items. Maybe the game can be beaten anyway?
|
|
||||||
if world.can_beat_game():
|
|
||||||
logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.')
|
|
||||||
progress_done = True
|
|
||||||
continue
|
|
||||||
raise FillError('No more progress items left to place.')
|
|
||||||
|
|
||||||
spot_to_fill = None
|
|
||||||
for location in fill_locations if placed_advancement_items / total_advancement_items < cutoffrate else reversed(fill_locations):
|
|
||||||
if location.can_fill(world.state, item_to_place):
|
|
||||||
spot_to_fill = location
|
|
||||||
break
|
|
||||||
|
|
||||||
if spot_to_fill is None:
|
|
||||||
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
|
||||||
if world.can_beat_game():
|
|
||||||
logging.getLogger('').warning('Not all items placed. Game beatable anyway.')
|
|
||||||
break
|
|
||||||
raise FillError('No more spots to place %s' % item_to_place)
|
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, True)
|
|
||||||
itempool.remove(item_to_place)
|
|
||||||
fill_locations.remove(spot_to_fill)
|
|
||||||
unplaced = [item.name for item in itempool]
|
|
||||||
unfilled = [location.name for location in fill_locations]
|
|
||||||
if unplaced or unfilled:
|
|
||||||
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
|
||||||
|
|
||||||
|
|
||||||
def distribute_items_staleness(world):
|
def promote_dungeon_items(world):
|
||||||
# get list of locations to fill in
|
world.itempool += get_dungeon_item_pool(world)
|
||||||
fill_locations = world.get_unfilled_locations()
|
|
||||||
random.shuffle(fill_locations)
|
|
||||||
|
|
||||||
# get items to distribute
|
for item in world.get_items():
|
||||||
random.shuffle(world.itempool)
|
if item.smallkey or item.bigkey:
|
||||||
itempool = world.itempool
|
item.advancement = True
|
||||||
|
elif item.map or item.compass:
|
||||||
|
item.priority = True
|
||||||
|
dungeon_tracking(world)
|
||||||
|
|
||||||
progress_done = False
|
|
||||||
advancement_placed = False
|
|
||||||
|
|
||||||
# sweep once to pick up preplaced items
|
def dungeon_tracking(world):
|
||||||
world.state.sweep_for_events()
|
for dungeon in world.dungeons:
|
||||||
|
layout = world.dungeon_layouts[dungeon.player][dungeon.name]
|
||||||
|
layout.dungeon_items = len(dungeon.all_items)
|
||||||
|
layout.free_items = layout.location_cnt - layout.dungeon_items
|
||||||
|
|
||||||
while itempool and fill_locations:
|
|
||||||
candidate_item_to_place = None
|
|
||||||
item_to_place = None
|
|
||||||
for item in itempool:
|
|
||||||
if advancement_placed or (progress_done and (item.advancement or item.priority)):
|
|
||||||
item_to_place = item
|
|
||||||
break
|
|
||||||
if item.advancement:
|
|
||||||
candidate_item_to_place = item
|
|
||||||
if world.unlocks_new_location(item):
|
|
||||||
item_to_place = item
|
|
||||||
break
|
|
||||||
|
|
||||||
if item_to_place is None:
|
def fill_dungeons_restrictive(world, shuffled_locations):
|
||||||
# check if we can reach all locations and that is why we find no new locations to place
|
dungeon_tracking(world)
|
||||||
if not progress_done and len(world.get_reachable_locations()) == len(world.get_locations()):
|
all_state_base = world.get_all_state()
|
||||||
progress_done = True
|
|
||||||
continue
|
|
||||||
# check if we have now placed all advancement items
|
|
||||||
if progress_done:
|
|
||||||
advancement_placed = True
|
|
||||||
continue
|
|
||||||
# we might be in a situation where all new locations require multiple items to reach. If that is the case, just place any advancement item we've found and continue trying
|
|
||||||
if candidate_item_to_place is not None:
|
|
||||||
item_to_place = candidate_item_to_place
|
|
||||||
else:
|
|
||||||
# we placed all available progress items. Maybe the game can be beaten anyway?
|
|
||||||
if world.can_beat_game():
|
|
||||||
logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.')
|
|
||||||
progress_done = True
|
|
||||||
continue
|
|
||||||
raise FillError('No more progress items left to place.')
|
|
||||||
|
|
||||||
spot_to_fill = None
|
# for player in range(1, world.players + 1):
|
||||||
for location in fill_locations:
|
# pinball_room = world.get_location('Skull Woods - Pinball Room', player)
|
||||||
# increase likelyhood of skipping a location if it has been found stale
|
# if world.retro[player]:
|
||||||
if not progress_done and random.randint(0, location.staleness_count) > 2:
|
# world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
|
||||||
continue
|
# else:
|
||||||
|
# world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
|
||||||
|
# pinball_room.event = True
|
||||||
|
# pinball_room.locked = True
|
||||||
|
# shuffled_locations.remove(pinball_room)
|
||||||
|
|
||||||
if location.can_fill(world.state, item_to_place):
|
# with shuffled dungeon items they are distributed as part of the normal item pool
|
||||||
spot_to_fill = location
|
for item in world.get_items():
|
||||||
break
|
if (item.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]):
|
||||||
else:
|
item.advancement = True
|
||||||
location.staleness_count += 1
|
elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]):
|
||||||
|
item.priority = True
|
||||||
|
|
||||||
# might have skipped too many locations due to potential staleness. Do not check for staleness now to find a candidate
|
dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world)]
|
||||||
if spot_to_fill is None:
|
|
||||||
for location in fill_locations:
|
|
||||||
if location.can_fill(world.state, item_to_place):
|
|
||||||
spot_to_fill = location
|
|
||||||
break
|
|
||||||
|
|
||||||
if spot_to_fill is None:
|
# sort in the order Big Key, Small Key, Other before placing dungeon items
|
||||||
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
sort_order = {"BigKey": 3, "SmallKey": 2}
|
||||||
if world.can_beat_game():
|
dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
|
||||||
logging.getLogger('').warning('Not all items placed. Game beatable anyway.')
|
|
||||||
break
|
|
||||||
raise FillError('No more spots to place %s' % item_to_place)
|
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, True)
|
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items,
|
||||||
itempool.remove(item_to_place)
|
keys_in_itempool={player: not world.keyshuffle[player] for player in range(1, world.players+1)},
|
||||||
fill_locations.remove(spot_to_fill)
|
single_player_placement=True)
|
||||||
|
|
||||||
unplaced = [item.name for item in itempool]
|
|
||||||
unfilled = [location.name for location in fill_locations]
|
|
||||||
if unplaced or unfilled:
|
|
||||||
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
|
||||||
|
|
||||||
def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool = None, single_player_placement = False):
|
def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=None, single_player_placement=False,
|
||||||
|
reserved_items=None):
|
||||||
|
if not reserved_items:
|
||||||
|
reserved_items = []
|
||||||
|
|
||||||
def sweep_from_pool():
|
def sweep_from_pool():
|
||||||
new_state = base_state.copy()
|
new_state = base_state.copy()
|
||||||
for item in itempool:
|
for item in itempool + reserved_items:
|
||||||
new_state.collect(item, True)
|
new_state.collect(item, True)
|
||||||
new_state.sweep_for_events()
|
new_state.sweep_for_events()
|
||||||
return new_state
|
return new_state
|
||||||
@@ -201,41 +99,56 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool =
|
|||||||
|
|
||||||
spot_to_fill = None
|
spot_to_fill = None
|
||||||
|
|
||||||
for location in locations:
|
item_locations = filter_locations(item_to_place, locations, world)
|
||||||
if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there
|
for location in item_locations:
|
||||||
location.item = item_to_place
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state,
|
||||||
test_state = maximum_exploration_state.copy()
|
single_player_placement, perform_access_check, itempool,
|
||||||
test_state.stale[item_to_place.player] = True
|
keys_in_itempool, world)
|
||||||
else:
|
if spot_to_fill:
|
||||||
test_state = maximum_exploration_state
|
|
||||||
if (not single_player_placement or location.player == item_to_place.player)\
|
|
||||||
and location.can_fill(test_state, item_to_place, perform_access_check)\
|
|
||||||
and valid_key_placement(item_to_place, location, itempool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool, world):
|
|
||||||
spot_to_fill = location
|
|
||||||
break
|
break
|
||||||
if item_to_place.smallkey or item_to_place.bigkey:
|
|
||||||
location.item = None
|
|
||||||
|
|
||||||
if spot_to_fill is None:
|
if spot_to_fill is None:
|
||||||
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
||||||
unplaced_items.insert(0, item_to_place)
|
unplaced_items.insert(0, item_to_place)
|
||||||
if world.can_beat_game():
|
if world.can_beat_game():
|
||||||
if world.accessibility[item_to_place.player] != 'none':
|
if world.accessibility[item_to_place.player] != 'none':
|
||||||
logging.getLogger('').warning('Not all items placed. Game beatable anyway. (Could not place %s)' % item_to_place)
|
logging.getLogger('').warning('Not all items placed. Game beatable anyway.'
|
||||||
|
f' (Could not place {item_to_place})')
|
||||||
continue
|
continue
|
||||||
spot_to_fill = last_ditch_placement(item_to_place, locations, world, maximum_exploration_state,
|
if world.algorithm in ['balanced', 'equitable']:
|
||||||
base_state, itempool, keys_in_itempool, single_player_placement)
|
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:
|
||||||
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)
|
||||||
track_outside_keys(item_to_place, spot_to_fill, world)
|
track_outside_keys(item_to_place, spot_to_fill, world)
|
||||||
|
track_dungeon_items(item_to_place, spot_to_fill, world)
|
||||||
locations.remove(spot_to_fill)
|
locations.remove(spot_to_fill)
|
||||||
spot_to_fill.event = True
|
spot_to_fill.event = True
|
||||||
|
|
||||||
itempool.extend(unplaced_items)
|
itempool.extend(unplaced_items)
|
||||||
|
|
||||||
|
|
||||||
|
def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_placement, perform_access_check,
|
||||||
|
itempool, keys_in_itempool, world):
|
||||||
|
if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there
|
||||||
|
location.item = item_to_place
|
||||||
|
test_state = max_exp_state.copy()
|
||||||
|
test_state.stale[item_to_place.player] = True
|
||||||
|
else:
|
||||||
|
test_state = max_exp_state
|
||||||
|
if not single_player_placement or location.player == item_to_place.player:
|
||||||
|
if location.can_fill(test_state, item_to_place, perform_access_check):
|
||||||
|
test_pool = itempool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool
|
||||||
|
if valid_key_placement(item_to_place, location, test_pool, world):
|
||||||
|
if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world):
|
||||||
|
return location
|
||||||
|
if item_to_place.smallkey or item_to_place.bigkey:
|
||||||
|
location.item = None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def valid_key_placement(item, location, itempool, world):
|
def valid_key_placement(item, location, itempool, world):
|
||||||
if not valid_reserved_placement(item, location, world):
|
if not valid_reserved_placement(item, location, world):
|
||||||
return False
|
return False
|
||||||
@@ -259,6 +172,17 @@ def valid_reserved_placement(item, location, world):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def valid_dungeon_placement(item, location, world):
|
||||||
|
if location.parent_region.dungeon:
|
||||||
|
layout = world.dungeon_layouts[location.player][location.parent_region.dungeon.name]
|
||||||
|
if not is_dungeon_item(item, world) or item.player != location.player:
|
||||||
|
return layout.free_items > 0
|
||||||
|
else:
|
||||||
|
# the second half probably doesn't matter much - should always return true
|
||||||
|
return item.dungeon == location.parent_region.dungeon.name and layout.dungeon_items > 0
|
||||||
|
return not is_dungeon_item(item, world)
|
||||||
|
|
||||||
|
|
||||||
def track_outside_keys(item, location, world):
|
def track_outside_keys(item, location, world):
|
||||||
if not item.smallkey:
|
if not item.smallkey:
|
||||||
return
|
return
|
||||||
@@ -270,6 +194,22 @@ def track_outside_keys(item, location, world):
|
|||||||
world.key_logic[item.player][item_dungeon].outside_keys += 1
|
world.key_logic[item.player][item_dungeon].outside_keys += 1
|
||||||
|
|
||||||
|
|
||||||
|
def track_dungeon_items(item, location, world):
|
||||||
|
if location.parent_region.dungeon and not item.crystal:
|
||||||
|
layout = world.dungeon_layouts[location.player][location.parent_region.dungeon.name]
|
||||||
|
if is_dungeon_item(item, world) and item.player == location.player:
|
||||||
|
layout.dungeon_items -= 1
|
||||||
|
else:
|
||||||
|
layout.free_items -= 1
|
||||||
|
|
||||||
|
|
||||||
|
def is_dungeon_item(item, world):
|
||||||
|
return ((item.smallkey and not world.keyshuffle[item.player])
|
||||||
|
or (item.bigkey and not world.bigkeyshuffle[item.player])
|
||||||
|
or (item.compass and not world.compassshuffle[item.player])
|
||||||
|
or (item.map and not world.mapshuffle[item.player]))
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
@@ -351,6 +291,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
random.shuffle(fill_locations)
|
random.shuffle(fill_locations)
|
||||||
|
|
||||||
# get items to distribute
|
# get items to distribute
|
||||||
|
classify_major_items(world)
|
||||||
random.shuffle(world.itempool)
|
random.shuffle(world.itempool)
|
||||||
progitempool = [item for item in world.itempool if item.advancement]
|
progitempool = [item for item in world.itempool if item.advancement]
|
||||||
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]
|
||||||
@@ -379,21 +320,53 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
|
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
|
||||||
# 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)}
|
||||||
|
if world.algorithm in ['balanced', 'equitable', 'vanilla_bias', 'dungeon_bias', 'entangled']:
|
||||||
|
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 ['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_items = [item for item in world.itempool if item.name == 'Rupee (1)']
|
||||||
|
for i in placeholder_items:
|
||||||
|
restitempool.remove(i)
|
||||||
|
for l in placeholder_locations:
|
||||||
|
fill_locations.remove(l)
|
||||||
|
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
|
||||||
|
|
||||||
fill_restrictive(world, world.state, fill_locations, progitempool,
|
if world.algorithm == 'vanilla_bias':
|
||||||
keys_in_itempool={player: world.keyshuffle[player] for player in range(1, world.players + 1)})
|
fast_vanilla_fill(world, restitempool, fill_locations)
|
||||||
|
else:
|
||||||
random.shuffle(fill_locations)
|
fast_fill(world, restitempool, fill_locations)
|
||||||
|
|
||||||
fast_fill(world, prioitempool, fill_locations)
|
|
||||||
|
|
||||||
fast_fill(world, restitempool, fill_locations)
|
|
||||||
|
|
||||||
unplaced = [item.name for item in prioitempool + restitempool]
|
unplaced = [item.name for item in prioitempool + restitempool]
|
||||||
unfilled = [location.name for location in fill_locations]
|
unfilled = [location.name for location in fill_locations]
|
||||||
if unplaced or unfilled:
|
if unplaced or unfilled:
|
||||||
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
||||||
|
|
||||||
|
|
||||||
def fast_fill(world, item_pool, fill_locations):
|
def fast_fill(world, item_pool, fill_locations):
|
||||||
while item_pool and fill_locations:
|
while item_pool and fill_locations:
|
||||||
spot_to_fill = fill_locations.pop()
|
spot_to_fill = fill_locations.pop()
|
||||||
@@ -401,70 +374,48 @@ def fast_fill(world, item_pool, fill_locations):
|
|||||||
world.push_item(spot_to_fill, item_to_place, False)
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
|
||||||
|
|
||||||
def flood_items(world):
|
def filtered_fill(world, item_pool, fill_locations):
|
||||||
# get items to distribute
|
while item_pool and fill_locations:
|
||||||
random.shuffle(world.itempool)
|
item_to_place = item_pool.pop()
|
||||||
itempool = world.itempool
|
item_locations = filter_locations(item_to_place, fill_locations, world)
|
||||||
progress_done = False
|
spot_to_fill = next(iter(item_locations))
|
||||||
|
fill_locations.remove(spot_to_fill)
|
||||||
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
|
||||||
# sweep once to pick up preplaced items
|
# sweep once to pick up preplaced items
|
||||||
world.state.sweep_for_events()
|
world.state.sweep_for_events()
|
||||||
|
|
||||||
# fill world from top of itempool while we can
|
def fast_vanilla_fill(world, item_pool, fill_locations):
|
||||||
while not progress_done:
|
while item_pool and fill_locations:
|
||||||
location_list = world.get_unfilled_locations()
|
item_to_place = item_pool.pop()
|
||||||
random.shuffle(location_list)
|
spot_to_fill = next(iter(filter_locations(item_to_place, fill_locations, world)))
|
||||||
spot_to_fill = None
|
fill_locations.remove(spot_to_fill)
|
||||||
for location in location_list:
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
if location.can_fill(world.state, itempool[0]):
|
|
||||||
spot_to_fill = location
|
|
||||||
break
|
|
||||||
|
|
||||||
if spot_to_fill:
|
|
||||||
item = itempool.pop(0)
|
|
||||||
world.push_item(spot_to_fill, item, True)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# ran out of spots, check if we need to step in and correct things
|
def filtered_equitable_fill(world, item_pool, fill_locations):
|
||||||
if len(world.get_reachable_locations()) == len(world.get_locations()):
|
while item_pool and fill_locations:
|
||||||
progress_done = True
|
item_to_place = item_pool.pop()
|
||||||
continue
|
item_locations = filter_locations(item_to_place, fill_locations, world)
|
||||||
|
spot_to_fill = next(l for l in item_locations if valid_dungeon_placement(item_to_place, l, world))
|
||||||
|
fill_locations.remove(spot_to_fill)
|
||||||
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
track_dungeon_items(item_to_place, spot_to_fill, world)
|
||||||
|
|
||||||
# need to place a progress item instead of an already placed item, find candidate
|
|
||||||
item_to_place = None
|
|
||||||
candidate_item_to_place = None
|
|
||||||
for item in itempool:
|
|
||||||
if item.advancement:
|
|
||||||
candidate_item_to_place = item
|
|
||||||
if world.unlocks_new_location(item):
|
|
||||||
item_to_place = item
|
|
||||||
break
|
|
||||||
|
|
||||||
# we might be in a situation where all new locations require multiple items to reach. If that is the case, just place any advancement item we've found and continue trying
|
def fast_equitable_fill(world, item_pool, fill_locations):
|
||||||
if item_to_place is None:
|
while item_pool and fill_locations:
|
||||||
if candidate_item_to_place is not None:
|
item_to_place = item_pool.pop()
|
||||||
item_to_place = candidate_item_to_place
|
spot_to_fill = next(l for l in fill_locations if valid_dungeon_placement(item_to_place, l, world))
|
||||||
else:
|
fill_locations.remove(spot_to_fill)
|
||||||
raise FillError('No more progress items left to place.')
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
track_dungeon_items(item_to_place, spot_to_fill, world)
|
||||||
# find item to replace with progress item
|
|
||||||
location_list = world.get_reachable_locations()
|
|
||||||
random.shuffle(location_list)
|
|
||||||
for location in location_list:
|
|
||||||
if location.item is not None and not location.item.advancement and not location.item.priority and not location.item.smallkey and not location.item.bigkey:
|
|
||||||
# safe to replace
|
|
||||||
replace_item = location.item
|
|
||||||
replace_item.location = None
|
|
||||||
itempool.append(replace_item)
|
|
||||||
world.push_item(location, item_to_place, True)
|
|
||||||
itempool.remove(item_to_place)
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def lock_shop_locations(world, player):
|
def lock_shop_locations(world, player):
|
||||||
for shop, loc_names in shop_to_location_table.items():
|
for shop, loc_names in shop_to_location_table.items():
|
||||||
for loc in loc_names:
|
for loc in loc_names:
|
||||||
world.get_location(loc, player).event = True
|
# world.get_location(loc, player).event = True
|
||||||
world.get_location(loc, player).locked = True
|
world.get_location(loc, player).locked = True
|
||||||
# I don't believe these locations exist in non-shopsanity
|
# I don't believe these locations exist in non-shopsanity
|
||||||
# if world.retro[player]:
|
# if world.retro[player]:
|
||||||
|
|||||||
13
ItemList.py
13
ItemList.py
@@ -5,12 +5,13 @@ import RaceRandom as random
|
|||||||
|
|
||||||
from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState
|
from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState
|
||||||
from Bosses import place_bosses
|
from Bosses import place_bosses
|
||||||
from Dungeons import get_dungeon_item_pool
|
|
||||||
from EntranceShuffle import connect_entrance
|
from EntranceShuffle import connect_entrance
|
||||||
from Regions import shop_to_location_table, retro_shops, shop_table_by_location
|
from Regions import shop_to_location_table, retro_shops, shop_table_by_location
|
||||||
from Fill import FillError, fill_restrictive, fast_fill
|
from Fill import FillError, fill_restrictive, fast_fill, get_dungeon_item_pool
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
|
|
||||||
|
from source.item.BiasedFill import trash_items
|
||||||
|
|
||||||
import source.classes.constants as CONST
|
import source.classes.constants as CONST
|
||||||
|
|
||||||
|
|
||||||
@@ -262,8 +263,12 @@ def generate_itempool(world, player):
|
|||||||
if player in world.pool_adjustment.keys():
|
if player in world.pool_adjustment.keys():
|
||||||
amt = world.pool_adjustment[player]
|
amt = world.pool_adjustment[player]
|
||||||
if amt < 0:
|
if amt < 0:
|
||||||
for _ in range(amt, 0):
|
trash_options = [x for x in pool if x in trash_items]
|
||||||
pool.remove(next(iter([x for x in pool if x in ['Rupees (20)', 'Rupees (5)', 'Rupee (1)']])))
|
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:
|
elif amt > 0:
|
||||||
for _ in range(0, amt):
|
for _ in range(0, amt):
|
||||||
pool.append('Rupees (20)')
|
pool.append('Rupees (20)')
|
||||||
|
|||||||
@@ -177,6 +177,8 @@ class PlacementRule(object):
|
|||||||
return True
|
return True
|
||||||
available_keys = outside_keys
|
available_keys = outside_keys
|
||||||
empty_chests = 0
|
empty_chests = 0
|
||||||
|
# todo: sometimes we need an extra empty chest to accomodate the big key too
|
||||||
|
# dungeon bias seed 563518200 for example
|
||||||
threshold = self.needed_keys_wo_bk if bk_blocked else self.needed_keys_w_bk
|
threshold = self.needed_keys_wo_bk if bk_blocked else self.needed_keys_w_bk
|
||||||
for loc in check_locations:
|
for loc in check_locations:
|
||||||
if not loc.item:
|
if not loc.item:
|
||||||
|
|||||||
64
Main.py
64
Main.py
@@ -19,16 +19,17 @@ from InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
|||||||
from EntranceShuffle import link_entrances, link_inverted_entrances
|
from EntranceShuffle import link_entrances, link_inverted_entrances
|
||||||
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, JsonRom, get_hash_string
|
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, JsonRom, get_hash_string
|
||||||
from Doors import create_doors
|
from Doors import create_doors
|
||||||
from DoorShuffle import link_doors, connect_portal
|
from DoorShuffle import link_doors, connect_portal, link_doors_prep
|
||||||
from RoomData import create_rooms
|
from RoomData import create_rooms
|
||||||
from Rules import set_rules
|
from Rules import set_rules
|
||||||
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
from Dungeons import create_dungeons
|
||||||
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items
|
from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dungeons_restrictive
|
||||||
from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations
|
from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations
|
||||||
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
|
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
|
||||||
from Utils import output_path, parse_player_names
|
from Utils import output_path, parse_player_names
|
||||||
|
|
||||||
from source.item.FillUtil import create_item_pool_config
|
from source.item.BiasedFill import create_item_pool_config, massage_item_pool
|
||||||
|
|
||||||
|
|
||||||
__version__ = '0.5.1.0-u'
|
__version__ = '0.5.1.0-u'
|
||||||
|
|
||||||
@@ -149,7 +150,6 @@ def main(args, seed=None, fish=None):
|
|||||||
create_rooms(world, player)
|
create_rooms(world, player)
|
||||||
create_dungeons(world, player)
|
create_dungeons(world, player)
|
||||||
adjust_locations(world, player)
|
adjust_locations(world, player)
|
||||||
create_item_pool_config(world)
|
|
||||||
|
|
||||||
if any(world.potshuffle.values()):
|
if any(world.potshuffle.values()):
|
||||||
logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))
|
logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))
|
||||||
@@ -165,7 +165,13 @@ def main(args, seed=None, fish=None):
|
|||||||
else:
|
else:
|
||||||
link_inverted_entrances(world, player)
|
link_inverted_entrances(world, player)
|
||||||
|
|
||||||
logger.info(world.fish.translate("cli","cli","shuffling.dungeons"))
|
logger.info(world.fish.translate("cli", "cli", "shuffling.prep"))
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
link_doors_prep(world, player)
|
||||||
|
|
||||||
|
create_item_pool_config(world)
|
||||||
|
|
||||||
|
logger.info(world.fish.translate("cli", "cli", "shuffling.dungeons"))
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
link_doors(world, player)
|
link_doors(world, player)
|
||||||
@@ -173,8 +179,7 @@ def main(args, seed=None, fish=None):
|
|||||||
mark_light_world_regions(world, player)
|
mark_light_world_regions(world, player)
|
||||||
else:
|
else:
|
||||||
mark_dark_world_regions(world, player)
|
mark_dark_world_regions(world, player)
|
||||||
logger.info(world.fish.translate("cli","cli","generating.itempool"))
|
logger.info(world.fish.translate("cli", "cli", "generating.itempool"))
|
||||||
logger.info(world.fish.translate("cli","cli","generating.itempool"))
|
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
generate_itempool(world, player)
|
generate_itempool(world, player)
|
||||||
@@ -192,8 +197,8 @@ def main(args, seed=None, fish=None):
|
|||||||
else:
|
else:
|
||||||
lock_shop_locations(world, player)
|
lock_shop_locations(world, player)
|
||||||
|
|
||||||
|
massage_item_pool(world)
|
||||||
logger.info(world.fish.translate("cli","cli","placing.dungeon.prizes"))
|
logger.info(world.fish.translate("cli", "cli", "placing.dungeon.prizes"))
|
||||||
|
|
||||||
fill_prizes(world)
|
fill_prizes(world)
|
||||||
|
|
||||||
@@ -202,14 +207,14 @@ 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"))
|
||||||
|
|
||||||
shuffled_locations = None
|
if args.algorithm in ['balanced', 'dungeon_bias', 'entangled']:
|
||||||
if args.algorithm in ['balanced', 'vt26'] or any(list(args.mapshuffle.values()) + list(args.compassshuffle.values()) +
|
|
||||||
list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())):
|
|
||||||
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:
|
||||||
fill_dungeons(world)
|
promote_dungeon_items(world)
|
||||||
|
|
||||||
for player in range(1, world.players+1):
|
for player in range(1, world.players+1):
|
||||||
if world.logic[player] != 'nologic':
|
if world.logic[player] != 'nologic':
|
||||||
@@ -227,34 +232,22 @@ def main(args, seed=None, fish=None):
|
|||||||
|
|
||||||
logger.info(world.fish.translate("cli","cli","fill.world"))
|
logger.info(world.fish.translate("cli","cli","fill.world"))
|
||||||
|
|
||||||
if args.algorithm == 'flood':
|
distribute_items_restrictive(world, True)
|
||||||
flood_items(world) # different algo, biased towards early game progress items
|
|
||||||
elif args.algorithm == 'vt21':
|
|
||||||
distribute_items_cutoff(world, 1)
|
|
||||||
elif args.algorithm == 'vt22':
|
|
||||||
distribute_items_cutoff(world, 0.66)
|
|
||||||
elif args.algorithm == 'freshness':
|
|
||||||
distribute_items_staleness(world)
|
|
||||||
elif args.algorithm == 'vt25':
|
|
||||||
distribute_items_restrictive(world, False)
|
|
||||||
elif args.algorithm == 'vt26':
|
|
||||||
|
|
||||||
distribute_items_restrictive(world, True, shuffled_locations)
|
|
||||||
elif args.algorithm == 'balanced':
|
|
||||||
distribute_items_restrictive(world, True)
|
|
||||||
|
|
||||||
if world.players > 1:
|
if world.players > 1:
|
||||||
logger.info(world.fish.translate("cli","cli","balance.multiworld"))
|
logger.info(world.fish.translate("cli", "cli", "balance.multiworld"))
|
||||||
balance_multiworld_progression(world)
|
if args.algorithm in ['balanced', 'equitable']:
|
||||||
|
balance_multiworld_progression(world)
|
||||||
|
|
||||||
# if we only check for beatable, we can do this sanity check first before creating the rom
|
# if we only check for beatable, we can do this sanity check first before creating the rom
|
||||||
if not world.can_beat_game(log_error=True):
|
if not world.can_beat_game(log_error=True):
|
||||||
raise RuntimeError(world.fish.translate("cli","cli","cannot.beat.game"))
|
raise RuntimeError(world.fish.translate("cli", "cli", "cannot.beat.game"))
|
||||||
|
|
||||||
for player in range(1, world.players+1):
|
for player in range(1, world.players+1):
|
||||||
if world.shopsanity[player]:
|
if world.shopsanity[player]:
|
||||||
customize_shops(world, player)
|
customize_shops(world, player)
|
||||||
balance_money_progression(world)
|
if args.algorithm in ['balanced', 'equitable']:
|
||||||
|
balance_money_progression(world)
|
||||||
|
|
||||||
outfilebase = f'DR_{args.outputname if args.outputname else world.seed}'
|
outfilebase = f'DR_{args.outputname if args.outputname else world.seed}'
|
||||||
|
|
||||||
@@ -405,6 +398,7 @@ def copy_world(world):
|
|||||||
ret.keydropshuffle = world.keydropshuffle.copy()
|
ret.keydropshuffle = world.keydropshuffle.copy()
|
||||||
ret.mixed_travel = world.mixed_travel.copy()
|
ret.mixed_travel = world.mixed_travel.copy()
|
||||||
ret.standardize_palettes = world.standardize_palettes.copy()
|
ret.standardize_palettes = world.standardize_palettes.copy()
|
||||||
|
ret.restrict_boss_items = world.restrict_boss_items.copy()
|
||||||
|
|
||||||
ret.exp_cache = world.exp_cache.copy()
|
ret.exp_cache = world.exp_cache.copy()
|
||||||
|
|
||||||
@@ -579,11 +573,11 @@ def create_playthrough(world):
|
|||||||
# todo: this is not very efficient, but I'm not sure how else to do it for this backwards logic
|
# todo: this is not very efficient, but I'm not sure how else to do it for this backwards logic
|
||||||
# world.clear_exp_cache()
|
# world.clear_exp_cache()
|
||||||
if world.can_beat_game(state_cache[num]):
|
if world.can_beat_game(state_cache[num]):
|
||||||
# logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is not required')
|
logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is not required')
|
||||||
to_delete.add(location)
|
to_delete.add(location)
|
||||||
else:
|
else:
|
||||||
# still required, got to keep it around
|
# still required, got to keep it around
|
||||||
# logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is required')
|
logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is required')
|
||||||
location.item = old_item
|
location.item = old_item
|
||||||
|
|
||||||
# cull entries in spheres for spoiler walkthrough at end
|
# cull entries in spheres for spoiler walkthrough at end
|
||||||
|
|||||||
11
Mystery.py
11
Mystery.py
@@ -71,6 +71,8 @@ def main():
|
|||||||
if args.enemizercli:
|
if args.enemizercli:
|
||||||
erargs.enemizercli = args.enemizercli
|
erargs.enemizercli = args.enemizercli
|
||||||
|
|
||||||
|
mw_settings = {'algorithm': False}
|
||||||
|
|
||||||
settings_cache = {k: (roll_settings(v) if args.samesettings else None) for k, v in weights_cache.items()}
|
settings_cache = {k: (roll_settings(v) if args.samesettings else None) for k, v in weights_cache.items()}
|
||||||
|
|
||||||
for player in range(1, args.multi + 1):
|
for player in range(1, args.multi + 1):
|
||||||
@@ -79,7 +81,12 @@ def main():
|
|||||||
settings = settings_cache[path] if settings_cache[path] else roll_settings(weights_cache[path])
|
settings = settings_cache[path] if settings_cache[path] else roll_settings(weights_cache[path])
|
||||||
for k, v in vars(settings).items():
|
for k, v in vars(settings).items():
|
||||||
if v is not None:
|
if v is not None:
|
||||||
getattr(erargs, k)[player] = v
|
if k == 'algorithm': # multiworld wide parameters
|
||||||
|
if not mw_settings[k]: # only use the first roll
|
||||||
|
setattr(erargs, k, v)
|
||||||
|
mw_settings[k] = True
|
||||||
|
else:
|
||||||
|
getattr(erargs, k)[player] = v
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(f'No weights specified for player {player}')
|
raise RuntimeError(f'No weights specified for player {player}')
|
||||||
|
|
||||||
@@ -116,6 +123,8 @@ def roll_settings(weights):
|
|||||||
|
|
||||||
ret = argparse.Namespace()
|
ret = argparse.Namespace()
|
||||||
|
|
||||||
|
ret.algorithm = get_choice('algorithm')
|
||||||
|
|
||||||
glitches_required = get_choice('glitches_required')
|
glitches_required = get_choice('glitches_required')
|
||||||
if glitches_required not in ['none', 'no_logic']:
|
if glitches_required not in ['none', 'no_logic']:
|
||||||
print("Only NMG and No Logic supported")
|
print("Only NMG and No Logic supported")
|
||||||
|
|||||||
@@ -999,6 +999,14 @@ def adjust_locations(world, player):
|
|||||||
world.get_location(location, player).address = 0x400000 + index
|
world.get_location(location, player).address = 0x400000 + index
|
||||||
# player address? it is in the shop table
|
# player address? it is in the shop table
|
||||||
index += 1
|
index += 1
|
||||||
|
# unreal events:
|
||||||
|
for l in ['Ganon', 'Agahnim 1', 'Agahnim 2', 'Dark Blacksmith Ruins', 'Frog', 'Missing Smith', 'Floodgate',
|
||||||
|
'Trench 1 Switch', 'Trench 2 Switch', 'Swamp Drain', 'Attic Cracked Floor', 'Suspicious Maiden',
|
||||||
|
'Revealing Light', 'Ice Block Drop', 'Zelda Pickup', 'Zelda Drop Off']:
|
||||||
|
location = world.get_location_unsafe(l, player)
|
||||||
|
if location:
|
||||||
|
location.real = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# (type, room_id, shopkeeper, custom, locked, [items])
|
# (type, room_id, shopkeeper, custom, locked, [items])
|
||||||
|
|||||||
@@ -101,12 +101,12 @@
|
|||||||
"algorithm": {
|
"algorithm": {
|
||||||
"choices": [
|
"choices": [
|
||||||
"balanced",
|
"balanced",
|
||||||
"freshness",
|
"equitable",
|
||||||
"flood",
|
"vanilla_bias",
|
||||||
"vt21",
|
"major_bias",
|
||||||
"vt22",
|
"dungeon_bias",
|
||||||
"vt25",
|
"cluster_bias",
|
||||||
"vt26"
|
"entangled"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"shuffle": {
|
"shuffle": {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"seed": "Seed",
|
"seed": "Seed",
|
||||||
"player": "Player",
|
"player": "Player",
|
||||||
"shuffling.world": "Shuffling the World about",
|
"shuffling.world": "Shuffling the World about",
|
||||||
|
"shuffling.prep": "Dungeon and Item prep",
|
||||||
"shuffling.dungeons": "Shuffling dungeons",
|
"shuffling.dungeons": "Shuffling dungeons",
|
||||||
"shuffling.pots": "Shuffling pots",
|
"shuffling.pots": "Shuffling pots",
|
||||||
"basic.traversal": "--Basic Traversal",
|
"basic.traversal": "--Basic Traversal",
|
||||||
@@ -153,22 +154,29 @@
|
|||||||
"balanced: vt26 derivative that aims to strike a balance between",
|
"balanced: vt26 derivative that aims to strike a balance between",
|
||||||
" the overworld heavy vt25 and the dungeon heavy vt26",
|
" the overworld heavy vt25 and the dungeon heavy vt26",
|
||||||
" algorithm.",
|
" algorithm.",
|
||||||
"vt26: Shuffle items and place them in a random location",
|
"equitable: does not place dungeon items first allowing new potential",
|
||||||
" that it is not impossible to be in. This includes",
|
" but mixed with the normal advancement pool",
|
||||||
" dungeon keys and items.",
|
"biased placements: these consider all major items to be special and attempts",
|
||||||
"vt25: Shuffle items and place them in a random location",
|
"to place items from fixed to semi-random locations. For purposes of these shuffles, all",
|
||||||
" that it is not impossible to be in.",
|
"Y items, A items, swords (unless vanilla swords), mails, shields, heart containers and",
|
||||||
"vt21: Unbiased in its selection, but has tendency to put",
|
"1/2 magic are considered to be part of a major items pool. Big Keys are added to the pool",
|
||||||
" Ice Rod in Turtle Rock.",
|
"if shuffled. Same for small keys, compasses, maps, keydrops (if small keys are also shuffled),",
|
||||||
"vt22: Drops off stale locations after 1/3 of progress",
|
"1 of each capacity upgrade for shopsanity, the quiver item for retro+shopsanity, and",
|
||||||
" items were placed to try to circumvent vt21\\'s",
|
"triforce pieces for Triforce Hunt. Future modes will add to these as appropriate.",
|
||||||
" shortcomings.",
|
"vanilla_bias Same as above, but attempts to place items in their vanilla",
|
||||||
"Freshness: Keep track of stale locations (ones that cannot be",
|
" location first. Major items that cannot be placed that way",
|
||||||
" reached yet) and decrease likeliness of selecting",
|
" will attempt to be placed in other failed locations first.",
|
||||||
" them the more often they were found unreachable.",
|
" Also attempts to place junk items in vanilla locations",
|
||||||
"Flood: Push out items starting from Link\\'s House and",
|
"major_bias same as above, but uses the major items' location preferentially",
|
||||||
" slightly biased to placing progression items with",
|
" major item location are defined as the group of location where",
|
||||||
" less restrictions."
|
" the items are found in the vanilla game. Backup locations for items",
|
||||||
|
" not in the vanilla game will be in the documentation",
|
||||||
|
"dungeon_bias same as above, but major items are preferentially placed",
|
||||||
|
" in dungeons locations first",
|
||||||
|
"cluster_bias same as above, but groups of locations are chosen randomly",
|
||||||
|
" from a pool of fixed locations designed to be interesting",
|
||||||
|
" and give major clues about the location of other",
|
||||||
|
" advancement items. These fixed groups will be documented"
|
||||||
],
|
],
|
||||||
"shuffle": [
|
"shuffle": [
|
||||||
"Select Entrance Shuffling Algorithm. (default: %(default)s)",
|
"Select Entrance Shuffling Algorithm. (default: %(default)s)",
|
||||||
|
|||||||
@@ -279,13 +279,12 @@
|
|||||||
"randomizer.item.accessibility.none": "Beatable",
|
"randomizer.item.accessibility.none": "Beatable",
|
||||||
|
|
||||||
"randomizer.item.sortingalgo": "Item Sorting",
|
"randomizer.item.sortingalgo": "Item Sorting",
|
||||||
"randomizer.item.sortingalgo.freshness": "Freshness",
|
|
||||||
"randomizer.item.sortingalgo.flood": "Flood",
|
|
||||||
"randomizer.item.sortingalgo.vt21": "VT8.21",
|
|
||||||
"randomizer.item.sortingalgo.vt22": "VT8.22",
|
|
||||||
"randomizer.item.sortingalgo.vt25": "VT8.25",
|
|
||||||
"randomizer.item.sortingalgo.vt26": "VT8.26",
|
|
||||||
"randomizer.item.sortingalgo.balanced": "Balanced",
|
"randomizer.item.sortingalgo.balanced": "Balanced",
|
||||||
|
"randomizer.item.sortingalgo.equitable": "Equitable",
|
||||||
|
"randomizer.item.sortingalgo.vanilla_bias": "Biased: Vanilla",
|
||||||
|
"randomizer.item.sortingalgo.major_bias": "Biased: Major Items",
|
||||||
|
"randomizer.item.sortingalgo.dungeon_bias": "Biased: Dungeons",
|
||||||
|
"randomizer.item.sortingalgo.cluster_bias": "Biased: Clustered",
|
||||||
|
|
||||||
"randomizer.item.restrict_boss_items": "Forbidden Boss Items",
|
"randomizer.item.restrict_boss_items": "Forbidden Boss Items",
|
||||||
"randomizer.item.restrict_boss_items.none": "None",
|
"randomizer.item.restrict_boss_items.none": "None",
|
||||||
|
|||||||
@@ -116,13 +116,12 @@
|
|||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
"default": "balanced",
|
"default": "balanced",
|
||||||
"options": [
|
"options": [
|
||||||
"freshness",
|
"balanced",
|
||||||
"flood",
|
"equitable",
|
||||||
"vt21",
|
"vanilla_bias",
|
||||||
"vt22",
|
"major_bias",
|
||||||
"vt25",
|
"dungeon_bias",
|
||||||
"vt26",
|
"cluster_bias"
|
||||||
"balanced"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"restrict_boss_items": {
|
"restrict_boss_items": {
|
||||||
|
|||||||
881
source/item/BiasedFill.py
Normal file
881
source/item/BiasedFill.py
Normal file
@@ -0,0 +1,881 @@
|
|||||||
|
import RaceRandom as random
|
||||||
|
import logging
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from DoorShuffle import validate_vanilla_reservation
|
||||||
|
from Dungeons import dungeon_table
|
||||||
|
from Items import item_table, ItemFactory
|
||||||
|
|
||||||
|
|
||||||
|
class ItemPoolConfig(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.location_groups = None
|
||||||
|
self.static_placement = None
|
||||||
|
self.item_pool = None
|
||||||
|
self.placeholders = None
|
||||||
|
self.reserved_locations = defaultdict(set)
|
||||||
|
|
||||||
|
|
||||||
|
class LocationGroup(object):
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self.locations = []
|
||||||
|
|
||||||
|
# flags
|
||||||
|
self.keyshuffle = False
|
||||||
|
self.keydropshuffle = False
|
||||||
|
self.shopsanity = False
|
||||||
|
self.retro = False
|
||||||
|
|
||||||
|
def locs(self, locs):
|
||||||
|
self.locations = locs
|
||||||
|
return self
|
||||||
|
|
||||||
|
def flags(self, k, d=False, s=False, r=False):
|
||||||
|
self.keyshuffle = k
|
||||||
|
self.keydropshuffle = d
|
||||||
|
self.shopsanity = s
|
||||||
|
self.retro = r
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
def create_item_pool_config(world):
|
||||||
|
world.item_pool_config = config = ItemPoolConfig()
|
||||||
|
player_set = set()
|
||||||
|
for player in range(1, world.players+1):
|
||||||
|
if world.restrict_boss_items[player] != 'none':
|
||||||
|
player_set.add(player)
|
||||||
|
if world.restrict_boss_items[player] == 'dungeon':
|
||||||
|
for dungeon, info in dungeon_table.items():
|
||||||
|
if info.prize:
|
||||||
|
d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon
|
||||||
|
config.reserved_locations[player].add(f'{d_name} - Boss')
|
||||||
|
for dungeon in world.dungeons:
|
||||||
|
for item in dungeon.all_items:
|
||||||
|
if item.map or item.compass:
|
||||||
|
item.advancement = True
|
||||||
|
if world.algorithm == 'vanilla_bias':
|
||||||
|
config.static_placement = {}
|
||||||
|
config.location_groups = {}
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
config.static_placement[player] = vanilla_mapping.copy()
|
||||||
|
if world.keydropshuffle[player]:
|
||||||
|
for item, locs in keydrop_vanilla_mapping.items():
|
||||||
|
if item in config.static_placement[player]:
|
||||||
|
config.static_placement[player][item].extend(locs)
|
||||||
|
else:
|
||||||
|
config.static_placement[player][item] = list(locs)
|
||||||
|
# todo: shopsanity...
|
||||||
|
# todo: retro (universal keys...)
|
||||||
|
# retro + shops
|
||||||
|
config.location_groups[player] = [
|
||||||
|
LocationGroup('bkhp').locs(mode_grouping['Heart Pieces']),
|
||||||
|
LocationGroup('bktrash').locs(mode_grouping['Overworld Trash'] + mode_grouping['Dungeon Trash']),
|
||||||
|
LocationGroup('bkgt').locs(mode_grouping['GT Trash'])]
|
||||||
|
elif world.algorithm == 'major_bias':
|
||||||
|
config.location_groups = [
|
||||||
|
LocationGroup('MajorItems'),
|
||||||
|
LocationGroup('Backup')
|
||||||
|
]
|
||||||
|
config.item_pool = {}
|
||||||
|
init_set = mode_grouping['Overworld Major'] + mode_grouping['Big Chests'] + mode_grouping['Heart Containers']
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
groups = LocationGroup('Major').locs(init_set)
|
||||||
|
if world.bigkeyshuffle[player]:
|
||||||
|
groups.locations.extend(mode_grouping['Big Keys'])
|
||||||
|
if world.keydropshuffle[player]:
|
||||||
|
groups.locations.append(mode_grouping['Big Key Drops'])
|
||||||
|
if world.keyshuffle[player]:
|
||||||
|
groups.locations.extend(mode_grouping['Small Keys'])
|
||||||
|
if world.keydropshuffle[player]:
|
||||||
|
groups.locations.extend(mode_grouping['Key Drops'])
|
||||||
|
if world.compassshuffle[player]:
|
||||||
|
groups.locations.extend(mode_grouping['Compasses'])
|
||||||
|
if world.mapshuffle[player]:
|
||||||
|
groups.locations.extend(mode_grouping['Maps'])
|
||||||
|
if world.shopsanity[player]:
|
||||||
|
groups.locations.append('Capacity Upgrade - Left')
|
||||||
|
groups.locations.append('Capacity Upgrade - Right')
|
||||||
|
if world.retro[player]:
|
||||||
|
if world.shopsanity[player]:
|
||||||
|
pass # todo: 5 locations for single arrow representation?
|
||||||
|
config.item_pool[player] = determine_major_items(world, player)
|
||||||
|
config.location_groups[0].locations = set(groups.locations)
|
||||||
|
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Dungeon Trash'] + mode_grouping['Shops']
|
||||||
|
+ mode_grouping['Overworld Trash'] + mode_grouping['GT Trash'] + mode_grouping['RetroShops'])
|
||||||
|
config.location_groups[1].locations = set(backup)
|
||||||
|
elif world.algorithm == 'dungeon_bias':
|
||||||
|
config.location_groups = [
|
||||||
|
LocationGroup('Dungeons'),
|
||||||
|
LocationGroup('Backup')
|
||||||
|
]
|
||||||
|
config.item_pool = {}
|
||||||
|
dungeon_set = (mode_grouping['Big Chests'] + mode_grouping['Dungeon Trash'] + mode_grouping['Big Keys'] +
|
||||||
|
mode_grouping['Heart Containers'] + mode_grouping['GT Trash'] + mode_grouping['Small Keys'] +
|
||||||
|
mode_grouping['Compasses'] + mode_grouping['Maps'] + mode_grouping['Key Drops'] +
|
||||||
|
mode_grouping['Big Key Drops'])
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
config.item_pool[player] = determine_major_items(world, player)
|
||||||
|
config.location_groups[0].locations = set(dungeon_set)
|
||||||
|
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Overworld Major']
|
||||||
|
+ mode_grouping['Overworld Trash'] + mode_grouping['Shops'] + mode_grouping['RetroShops'])
|
||||||
|
config.location_groups[1].locations = set(backup)
|
||||||
|
elif world.algorithm == 'entangled' and world.players > 1:
|
||||||
|
config.location_groups = [
|
||||||
|
LocationGroup('Entangled'),
|
||||||
|
]
|
||||||
|
item_cnt = 0
|
||||||
|
config.item_pool = {}
|
||||||
|
limits = {}
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
config.item_pool[player] = determine_major_items(world, player)
|
||||||
|
item_cnt += count_major_items(world, player)
|
||||||
|
limits[player] = calc_dungeon_limits(world, player)
|
||||||
|
c_set = {}
|
||||||
|
for location in world.get_locations():
|
||||||
|
if location.real and not location.forced_item:
|
||||||
|
c_set[location.name] = None
|
||||||
|
# todo: retroshop locations are created later, so count them here?
|
||||||
|
ttl_locations, candidates = 0, list(c_set.keys())
|
||||||
|
chosen_locations = defaultdict(set)
|
||||||
|
random.shuffle(candidates)
|
||||||
|
while ttl_locations < item_cnt:
|
||||||
|
choice = candidates.pop()
|
||||||
|
dungeon = world.get_location(choice, 1).parent_region.dungeon
|
||||||
|
if dungeon:
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
location = world.get_location(choice, player)
|
||||||
|
if location.real and not location.forced_item:
|
||||||
|
if isinstance(limits[player], int):
|
||||||
|
if limits[player] > 0:
|
||||||
|
config.reserved_locations[player].add(choice)
|
||||||
|
limits[player] -= 1
|
||||||
|
chosen_locations[choice].add(player)
|
||||||
|
else:
|
||||||
|
previous = previously_reserved(location, world, player)
|
||||||
|
if limits[player][dungeon.name] > 0 or previous:
|
||||||
|
if validate_reservation(location, dungeon, world, player):
|
||||||
|
if not previous:
|
||||||
|
limits[player][dungeon.name] -= 1
|
||||||
|
chosen_locations[choice].add(player)
|
||||||
|
else: # not dungeon restricted
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
location = world.get_location(choice, player)
|
||||||
|
if location.real and not location.forced_item:
|
||||||
|
chosen_locations[choice].add(player)
|
||||||
|
ttl_locations += len(chosen_locations[choice])
|
||||||
|
config.placeholders = ttl_locations - item_cnt
|
||||||
|
config.location_groups[0].locations = chosen_locations
|
||||||
|
|
||||||
|
|
||||||
|
def previously_reserved(location, world, player):
|
||||||
|
if '- Boss' in location.name:
|
||||||
|
if world.restrict_boss_items[player] == 'mapcompass' and (not world.compassshuffle[player]
|
||||||
|
or not world.mapshuffle[player]):
|
||||||
|
return True
|
||||||
|
if world.restrict_boss_items[player] == 'dungeon' and (not world.compassshuffle[player]
|
||||||
|
or not world.mapshuffle[player]
|
||||||
|
or not world.bigkeyshuffle[player]
|
||||||
|
or not (world.keyshuffle[player] or world.retro[player])):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def massage_item_pool(world):
|
||||||
|
player_pool = defaultdict(list)
|
||||||
|
for item in world.itempool:
|
||||||
|
player_pool[item.player].append(item)
|
||||||
|
for dungeon in world.dungeons:
|
||||||
|
for item in dungeon.all_items:
|
||||||
|
if item not in player_pool[item.player]: # filters out maps, compasses, etc
|
||||||
|
player_pool[item.player].append(item)
|
||||||
|
player_locations = defaultdict(list)
|
||||||
|
for player in player_pool:
|
||||||
|
player_locations[player] = [x for x in world.get_unfilled_locations(player) if '- Prize' not in x.name]
|
||||||
|
discrepancy = len(player_pool[player]) - len(player_locations[player])
|
||||||
|
if discrepancy:
|
||||||
|
trash_options = [x for x in player_pool[player] if x.name in trash_items]
|
||||||
|
random.shuffle(trash_options)
|
||||||
|
trash_options = sorted(trash_options, key=lambda x: trash_items[x.name], reverse=True)
|
||||||
|
while discrepancy > 0 and len(trash_options) > 0:
|
||||||
|
deleted = trash_options.pop()
|
||||||
|
world.itempool.remove(deleted)
|
||||||
|
discrepancy -= 1
|
||||||
|
if discrepancy > 0:
|
||||||
|
logging.getLogger('').warning(f'Too many good items in pool, something will be removed at random')
|
||||||
|
if world.item_pool_config.placeholders is not None:
|
||||||
|
removed = 0
|
||||||
|
single_rupees = [item for item in world.itempool if item.name == 'Rupee (1)']
|
||||||
|
removed += len(single_rupees)
|
||||||
|
for x in single_rupees:
|
||||||
|
world.itempool.remove(x)
|
||||||
|
if removed < world.item_pool_config.placeholders:
|
||||||
|
trash_options = [x for x in world.itempool if x.name in trash_items]
|
||||||
|
random.shuffle(trash_options)
|
||||||
|
trash_options = sorted(trash_options, key=lambda x: trash_items[x.name], reverse=True)
|
||||||
|
while removed < world.item_pool_config.placeholders:
|
||||||
|
if len(trash_options) == 0:
|
||||||
|
logging.getLogger('').warning(f'Too many good items in pool, not enough room for placeholders')
|
||||||
|
deleted = trash_options.pop()
|
||||||
|
world.itempool.remove(deleted)
|
||||||
|
removed += 1
|
||||||
|
placeholders = random.sample(single_rupees, world.item_pool_config.placeholders)
|
||||||
|
world.itempool += placeholders
|
||||||
|
removed -= len(placeholders)
|
||||||
|
for _ in range(removed):
|
||||||
|
world.itempool.append(ItemFactory('Rupees (5)', random.randint(1, world.players)))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_reservation(location, dungeon, world, player):
|
||||||
|
world.item_pool_config.reserved_locations[player].add(location.name)
|
||||||
|
if world.doorShuffle[player] != 'vanilla':
|
||||||
|
return True # we can generate the dungeon somehow most likely
|
||||||
|
if validate_vanilla_reservation(dungeon, world, player):
|
||||||
|
return True
|
||||||
|
world.item_pool_config.reserved_locations[player].remove(location.name)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def count_major_items(world, player):
|
||||||
|
major_item_set = 52
|
||||||
|
if world.bigkeyshuffle[player]:
|
||||||
|
major_item_set += 11
|
||||||
|
if world.keydropshuffle[player]:
|
||||||
|
major_item_set += 1
|
||||||
|
if world.doorShuffle[player] == 'crossed':
|
||||||
|
major_item_set += 1
|
||||||
|
if world.keyshuffle[player]:
|
||||||
|
major_item_set += 29
|
||||||
|
if world.keydropshuffle[player]:
|
||||||
|
major_item_set += 32
|
||||||
|
if world.compassshuffle[player]:
|
||||||
|
major_item_set += 11
|
||||||
|
if world.doorShuffle[player] == 'crossed':
|
||||||
|
major_item_set += 2
|
||||||
|
if world.mapshuffle[player]:
|
||||||
|
major_item_set += 12
|
||||||
|
if world.doorShuffle[player] == 'crossed':
|
||||||
|
major_item_set += 1
|
||||||
|
if world.shopsanity[player]:
|
||||||
|
major_item_set += 2
|
||||||
|
if world.retro[player]:
|
||||||
|
major_item_set += 5 # the single arrow quiver
|
||||||
|
if world.goal == 'triforcehunt':
|
||||||
|
major_item_set += world.triforce_pool[player]
|
||||||
|
if world.bombbag[player]:
|
||||||
|
major_item_set += world.triforce_pool[player]
|
||||||
|
# todo: vanilla, assured, swordless?
|
||||||
|
# if world.swords[player] != "random":
|
||||||
|
# if world.swords[player] == 'assured':
|
||||||
|
# major_item_set -= 1
|
||||||
|
# if world.swords[player] in ['vanilla', 'swordless']:
|
||||||
|
# major_item_set -= 4
|
||||||
|
# todo: starting equipment?
|
||||||
|
return major_item_set
|
||||||
|
|
||||||
|
|
||||||
|
def calc_dungeon_limits(world, player):
|
||||||
|
b, s, c, m, k, r, bi = (world.bigkeyshuffle[player], world.keyshuffle[player], world.compassshuffle[player],
|
||||||
|
world.mapshuffle[player], world.keydropshuffle[player], world.retro[player],
|
||||||
|
world.restrict_boss_items[player])
|
||||||
|
if world.doorShuffle[player] in ['vanilla', 'basic']:
|
||||||
|
limits = {}
|
||||||
|
for dungeon, info in dungeon_table.items():
|
||||||
|
val = info.free_items
|
||||||
|
if bi != 'none' and info.prize:
|
||||||
|
if bi == 'mapcompass' and (not c or not m):
|
||||||
|
val -= 1
|
||||||
|
if bi == 'dungeon' and (not c or not m or not (s or r) or not b):
|
||||||
|
val -= 1
|
||||||
|
if b:
|
||||||
|
val += 1 if info.bk_present else 0
|
||||||
|
if k:
|
||||||
|
val += 1 if info.bk_drops else 0
|
||||||
|
if s or r:
|
||||||
|
val += info.key_num
|
||||||
|
if k:
|
||||||
|
val += info.key_drops
|
||||||
|
if c:
|
||||||
|
val += 1 if info.compass_present else 0
|
||||||
|
if m:
|
||||||
|
val += 1 if info.map_present else 0
|
||||||
|
limits[dungeon] = val
|
||||||
|
else:
|
||||||
|
limits = 60
|
||||||
|
if world.bigkeyshuffle[player]:
|
||||||
|
limits += 11
|
||||||
|
if world.keydropshuffle[player]:
|
||||||
|
limits += 1
|
||||||
|
if world.keyshuffle[player] or world.retro[player]:
|
||||||
|
limits += 29
|
||||||
|
if world.keydropshuffle[player]:
|
||||||
|
limits += 32
|
||||||
|
if world.compassshuffle[player]:
|
||||||
|
limits += 11
|
||||||
|
if world.mapshuffle[player]:
|
||||||
|
limits += 12
|
||||||
|
return limits
|
||||||
|
|
||||||
|
|
||||||
|
def determine_major_items(world, player):
|
||||||
|
major_item_set = set(major_items)
|
||||||
|
if world.bigkeyshuffle[player]:
|
||||||
|
major_item_set.update({x for x, y in item_table.items() if y[2] == 'BigKey'})
|
||||||
|
if world.keyshuffle[player]:
|
||||||
|
major_item_set.update({x for x, y in item_table.items() if y[2] == 'SmallKey'})
|
||||||
|
if world.compassshuffle[player]:
|
||||||
|
major_item_set.update({x for x, y in item_table.items() if y[2] == 'Compass'})
|
||||||
|
if world.mapshuffle[player]:
|
||||||
|
major_item_set.update({x for x, y in item_table.items() if y[2] == 'Map'})
|
||||||
|
if world.shopsanity[player]:
|
||||||
|
major_item_set.add('Bomb Upgrade (+5)')
|
||||||
|
major_item_set.add('Arrow Upgrade (+5)')
|
||||||
|
if world.retro[player]:
|
||||||
|
major_item_set.add('Single Arrow')
|
||||||
|
major_item_set.add('Small Key (Universal)')
|
||||||
|
if world.goal == 'triforcehunt':
|
||||||
|
major_item_set.add('Triforce Piece')
|
||||||
|
if world.bombbag[player]:
|
||||||
|
major_item_set.add('Bomb Upgrade (+10)')
|
||||||
|
return major_item_set
|
||||||
|
|
||||||
|
|
||||||
|
def classify_major_items(world):
|
||||||
|
if world.algorithm in ['major_bias', 'dungeon_bias', 'cluster_bias'] or (world.algorithm == 'entangled'
|
||||||
|
and world.players > 1):
|
||||||
|
config = world.item_pool_config
|
||||||
|
for item in world.itempool:
|
||||||
|
if item.name in config.item_pool[item.player]:
|
||||||
|
if not item.advancement or not item.priority:
|
||||||
|
if item.smallkey or item.bigkey:
|
||||||
|
item.advancement = True
|
||||||
|
else:
|
||||||
|
item.priority = True
|
||||||
|
|
||||||
|
|
||||||
|
def split_pool(pool, world):
|
||||||
|
# bias or entangled
|
||||||
|
config = world.item_pool_config
|
||||||
|
priority, secondary = [], []
|
||||||
|
for item in pool:
|
||||||
|
if item.name in config.item_pool[item.player]:
|
||||||
|
priority.append(item)
|
||||||
|
else:
|
||||||
|
secondary.append(item)
|
||||||
|
return priority, secondary
|
||||||
|
|
||||||
|
|
||||||
|
def filter_locations(item_to_place, locations, world):
|
||||||
|
if world.algorithm == 'vanilla_bias':
|
||||||
|
config, filtered = world.item_pool_config, []
|
||||||
|
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
|
||||||
|
if item_name in config.static_placement[item_to_place.player]:
|
||||||
|
restricted = config.static_placement[item_to_place.player][item_name]
|
||||||
|
filtered = [l for l in locations if l.player == item_to_place.player and l.name in restricted]
|
||||||
|
i = 0
|
||||||
|
while len(filtered) <= 0:
|
||||||
|
if i >= len(config.location_groups[item_to_place.player]):
|
||||||
|
return locations
|
||||||
|
restricted = config.location_groups[item_to_place.player][i].locations
|
||||||
|
filtered = [l for l in locations if l.player == item_to_place.player and l.name in restricted]
|
||||||
|
i += 1
|
||||||
|
return filtered
|
||||||
|
if world.algorithm in ['major_bias', 'dungeon_bias']:
|
||||||
|
config = world.item_pool_config
|
||||||
|
if item_to_place.name in config.item_pool[item_to_place.player]:
|
||||||
|
restricted = config.location_groups[0].locations
|
||||||
|
filtered = [l for l in locations if l.name in restricted]
|
||||||
|
if len(filtered) == 0:
|
||||||
|
restricted = config.location_groups[1].locations
|
||||||
|
filtered = [l for l in locations if l.name in restricted]
|
||||||
|
# bias toward certain location in overflow? (thinking about this for major_bias)
|
||||||
|
return filtered if len(filtered) > 0 else locations
|
||||||
|
if world.algorithm == 'entangled' and world.players > 1:
|
||||||
|
config = world.item_pool_config
|
||||||
|
if item_to_place == 'Placeholder' or item_to_place.name in config.item_pool[item_to_place.player]:
|
||||||
|
restricted = config.location_groups[0].locations
|
||||||
|
filtered = [l for l in locations if l.name in restricted and l.player in restricted[l.name]]
|
||||||
|
return filtered if len(filtered) > 0 else locations
|
||||||
|
return locations
|
||||||
|
|
||||||
|
|
||||||
|
vanilla_mapping = {
|
||||||
|
'Green Pendant': ['Eastern Palace - Prize'],
|
||||||
|
'Red Pendant': ['Desert Palace - Prize', 'Tower of Hera - Prize'],
|
||||||
|
'Blue Pendant': ['Desert Palace - Prize', 'Tower of Hera - Prize'],
|
||||||
|
'Crystal 1': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||||
|
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||||
|
'Crystal 2': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||||
|
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||||
|
'Crystal 3': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||||
|
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||||
|
'Crystal 4': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||||
|
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||||
|
'Crystal 7': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||||
|
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||||
|
'Crystal 5': ['Ice Palace - Prize', 'Misery Mire - Prize'],
|
||||||
|
'Crystal 6': ['Ice Palace - Prize', 'Misery Mire - Prize'],
|
||||||
|
'Bow': ['Eastern Palace - Big Chest'],
|
||||||
|
'Progressive Bow': ['Eastern Palace - Big Chest', 'Pyramid Fairy - Left'],
|
||||||
|
'Book of Mudora': ['Library'],
|
||||||
|
'Hammer': ['Palace of Darkness - Big Chest'],
|
||||||
|
'Hookshot': ['Swamp Palace - Big Chest'],
|
||||||
|
'Magic Mirror': ['Old Man'],
|
||||||
|
'Ocarina': ['Flute Spot'],
|
||||||
|
'Pegasus Boots': ['Sahasrahla'],
|
||||||
|
'Power Glove': ['Desert Palace - Big Chest'],
|
||||||
|
'Cape': ["King's Tomb"],
|
||||||
|
'Mushroom': ['Mushroom'],
|
||||||
|
'Shovel': ['Stumpy'],
|
||||||
|
'Lamp': ["Link's House"],
|
||||||
|
'Magic Powder': ['Potion Shop'],
|
||||||
|
'Moon Pearl': ['Tower of Hera - Big Chest'],
|
||||||
|
'Cane of Somaria': ['Misery Mire - Big Chest'],
|
||||||
|
'Fire Rod': ['Skull Woods - Big Chest'],
|
||||||
|
'Flippers': ['King Zora'],
|
||||||
|
'Ice Rod': ['Ice Rod Cave'],
|
||||||
|
'Titans Mitts': ["Thieves' Town - Big Chest"],
|
||||||
|
'Bombos': ['Bombos Tablet'],
|
||||||
|
'Ether': ['Ether Tablet'],
|
||||||
|
'Quake': ['Catfish'],
|
||||||
|
'Bottle': ['Bottle Merchant', 'Kakariko Tavern', 'Purple Chest', 'Hobo'],
|
||||||
|
'Master Sword': ['Master Sword Pedestal'],
|
||||||
|
'Tempered Sword': ['Blacksmith'],
|
||||||
|
'Fighter Sword': ["Link's Uncle"],
|
||||||
|
'Golden Sword': ['Pyramid Fairy - Right'],
|
||||||
|
'Progressive Sword': ["Link's Uncle", 'Blacksmith', 'Master Sword Pedestal', 'Pyramid Fairy - Right'],
|
||||||
|
'Progressive Glove': ['Desert Palace - Big Chest', "Thieves' Town - Big Chest"],
|
||||||
|
'Silver Arrows': ['Pyramid Fairy - Left'],
|
||||||
|
'Single Arrow': ['Palace of Darkness - Dark Basement - Left'],
|
||||||
|
'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',
|
||||||
|
'Ganons Tower - Compass Room - Bottom Right', 'Ganons Tower - DMs 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'],
|
||||||
|
'Bombs (3)': ['Floodgate Chest', "Sahasrahla's Hut - Middle", 'Kakariko Well - Bottom', 'Superbunny Cave - Top',
|
||||||
|
'Mini Moldorm Cave - Far Left', 'Sewers - Secret Room - Left', 'Paradox Cave Upper - Left',
|
||||||
|
"Thieves' Town - Attic", 'Ice Palace - Freezor Chest', 'Palace of Darkness - Dark Maze - Top',
|
||||||
|
'Ganons Tower - Hope Room - Right', 'Ganons Tower - DMs Room - Top Left',
|
||||||
|
'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right',
|
||||||
|
'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Mini Helmasaur Room - Left',
|
||||||
|
'Ganons Tower - Mini Helmasaur Room - Right'],
|
||||||
|
'Blue Mail': ['Ice Palace - Big Chest'],
|
||||||
|
'Red Mail': ['Ganons Tower - Big Chest'],
|
||||||
|
'Progressive Armor': ['Ice Palace - Big Chest', 'Ganons Tower - Big Chest'],
|
||||||
|
'Blue Boomerang': ['Hyrule Castle - Boomerang Chest'],
|
||||||
|
'Red Boomerang': ['Waterfall Fairy - Left'],
|
||||||
|
'Blue Shield': ['Secret Passage'],
|
||||||
|
'Red Shield': ['Waterfall Fairy - Right'],
|
||||||
|
'Mirror Shield': ['Turtle Rock - Big Chest'],
|
||||||
|
'Progressive Shield': ['Secret Passage', 'Waterfall Fairy - Right', 'Turtle Rock - Big Chest'],
|
||||||
|
'Bug Catching Net': ['Sick Kid'],
|
||||||
|
'Cane of Byrna': ['Spike Cave'],
|
||||||
|
'Boss Heart Container': ['Desert Palace - Boss', 'Eastern Palace - Boss', 'Tower of Hera - Boss',
|
||||||
|
'Swamp Palace - Boss', "Thieves' Town - Boss", 'Skull Woods - Boss', 'Ice Palace - Boss',
|
||||||
|
'Misery Mire - Boss', 'Turtle Rock - Boss', 'Palace of Darkness - Boss'],
|
||||||
|
'Sanctuary Heart Container': ['Sanctuary'],
|
||||||
|
'Piece of Heart': ['Sunken Treasure', "Blind's Hideout - Top", "Zora's Ledge", "Aginah's Cave", 'Maze Race',
|
||||||
|
'Kakariko Well - Top', 'Lost Woods Hideout', 'Lumberjack Tree', 'Cave 45', 'Graveyard Cave',
|
||||||
|
'Checkerboard Cave', 'Bonk Rock Cave', 'Lake Hylia Island', 'Desert Ledge', 'Spectacle Rock',
|
||||||
|
'Spectacle Rock Cave', 'Pyramid', 'Digging Game', 'Peg Cave', 'Chest Game', 'Bumper Cave Ledge',
|
||||||
|
'Mire Shed - Left', 'Floating Island', 'Mimic Cave'],
|
||||||
|
'Rupee (1)': ['Turtle Rock - Eye Bridge - Top Right', 'Ganons Tower - Compass Room - Top Right'],
|
||||||
|
'Rupees (5)': ["Hyrule Castle - Zelda's Chest", 'Turtle Rock - Eye Bridge - Top Left',
|
||||||
|
# 'Palace of Darkness - Harmless Hellway',
|
||||||
|
'Palace of Darkness - Dark Maze - Bottom',
|
||||||
|
'Ganons Tower - Validation Chest'],
|
||||||
|
'Rupees (20)': ["Blind's Hideout - Left", "Blind's Hideout - Right", "Blind's Hideout - Far Left",
|
||||||
|
"Blind's Hideout - Far Right", 'Kakariko Well - Left', 'Kakariko Well - Middle',
|
||||||
|
'Kakariko Well - Right', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right',
|
||||||
|
'Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right',
|
||||||
|
'Paradox Cave Lower - Far Right', 'Paradox Cave Lower - Middle', 'Hype Cave - Top',
|
||||||
|
'Hype Cave - Middle Right', 'Hype Cave - Middle Left', 'Hype Cave - Bottom',
|
||||||
|
'Swamp Palace - West Chest', 'Swamp Palace - Flooded Room - Left', 'Swamp Palace - Waterfall Room',
|
||||||
|
'Swamp Palace - Flooded Room - Right', "Thieves' Town - Ambush Chest",
|
||||||
|
'Turtle Rock - Eye Bridge - Bottom Right', 'Ganons Tower - Compass Room - Bottom Left',
|
||||||
|
'Swamp Palace - Flooded Room - Right', "Thieves' Town - Ambush Chest",
|
||||||
|
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right'],
|
||||||
|
'Rupees (50)': ["Sahasrahla's Hut - Left", "Sahasrahla's Hut - Right", 'Spiral Cave', 'Superbunny Cave - Bottom',
|
||||||
|
'Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right',
|
||||||
|
'Hookshot Cave - Bottom Left'],
|
||||||
|
'Rupees (100)': ['Eastern Palace - Cannonball Chest'],
|
||||||
|
'Rupees (300)': ['Mini Moldorm Cave - Generous Guy', 'Sewers - Secret Room - Middle', 'Hype Cave - Generous Guy',
|
||||||
|
'Brewery', 'C-Shaped House'],
|
||||||
|
'Magic Upgrade (1/2)': ['Magic Bat'],
|
||||||
|
'Big Key (Eastern Palace)': ['Eastern Palace - Big Key Chest'],
|
||||||
|
'Compass (Eastern Palace)': ['Eastern Palace - Compass Chest'],
|
||||||
|
'Map (Eastern Palace)': ['Eastern Palace - Map Chest'],
|
||||||
|
'Small Key (Desert Palace)': ['Desert Palace - Torch'],
|
||||||
|
'Big Key (Desert Palace)': ['Desert Palace - Big Key Chest'],
|
||||||
|
'Compass (Desert Palace)': ['Desert Palace - Compass Chest'],
|
||||||
|
'Map (Desert Palace)': ['Desert Palace - Map Chest'],
|
||||||
|
'Small Key (Tower of Hera)': ['Tower of Hera - Basement Cage'],
|
||||||
|
'Big Key (Tower of Hera)': ['Tower of Hera - Big Key Chest'],
|
||||||
|
'Compass (Tower of Hera)': ['Tower of Hera - Compass Chest'],
|
||||||
|
'Map (Tower of Hera)': ['Tower of Hera - Map Chest'],
|
||||||
|
'Small Key (Escape)': ['Sewers - Dark Cross'],
|
||||||
|
'Map (Escape)': ['Hyrule Castle - Map Chest'],
|
||||||
|
'Small Key (Agahnims Tower)': ['Castle Tower - Room 03', 'Castle Tower - Dark Maze'],
|
||||||
|
'Small Key (Palace of Darkness)': ['Palace of Darkness - Shooter Room', 'Palace of Darkness - The Arena - Bridge',
|
||||||
|
'Palace of Darkness - Stalfos Basement',
|
||||||
|
'Palace of Darkness - The Arena - Ledge',
|
||||||
|
'Palace of Darkness - Dark Basement - Right',
|
||||||
|
'Palace of Darkness - Harmless Hellway'],
|
||||||
|
# 'Palace of Darkness - Dark Maze - Bottom'],
|
||||||
|
'Big Key (Palace of Darkness)': ['Palace of Darkness - Big Key Chest'],
|
||||||
|
'Compass (Palace of Darkness)': ['Palace of Darkness - Compass Chest'],
|
||||||
|
'Map (Palace of Darkness)': ['Palace of Darkness - Map Chest'],
|
||||||
|
'Small Key (Thieves Town)': ["Thieves' Town - Blind's Cell"],
|
||||||
|
'Big Key (Thieves Town)': ["Thieves' Town - Big Key Chest"],
|
||||||
|
'Compass (Thieves Town)': ["Thieves' Town - Compass Chest"],
|
||||||
|
'Map (Thieves Town)': ["Thieves' Town - Map Chest"],
|
||||||
|
'Small Key (Skull Woods)': ['Skull Woods - Pot Prison', 'Skull Woods - Pinball Room', 'Skull Woods - Bridge Room'],
|
||||||
|
'Big Key (Skull Woods)': ['Skull Woods - Big Key Chest'],
|
||||||
|
'Compass (Skull Woods)': ['Skull Woods - Compass Chest'],
|
||||||
|
'Map (Skull Woods)': ['Skull Woods - Map Chest'],
|
||||||
|
'Small Key (Swamp Palace)': ['Swamp Palace - Entrance'],
|
||||||
|
'Big Key (Swamp Palace)': ['Swamp Palace - Big Key Chest'],
|
||||||
|
'Compass (Swamp Palace)': ['Swamp Palace - Compass Chest'],
|
||||||
|
'Map (Swamp Palace)': ['Swamp Palace - Map Chest'],
|
||||||
|
'Small Key (Ice Palace)': ['Ice Palace - Iced T Room', 'Ice Palace - Spike Room'],
|
||||||
|
'Big Key (Ice Palace)': ['Ice Palace - Big Key Chest'],
|
||||||
|
'Compass (Ice Palace)': ['Ice Palace - Compass Chest'],
|
||||||
|
'Map (Ice Palace)': ['Ice Palace - Map Chest'],
|
||||||
|
'Small Key (Misery Mire)': ['Misery Mire - Main Lobby', 'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest'],
|
||||||
|
'Big Key (Misery Mire)': ['Misery Mire - Big Key Chest'],
|
||||||
|
'Compass (Misery Mire)': ['Misery Mire - Compass Chest'],
|
||||||
|
'Map (Misery Mire)': ['Misery Mire - Map Chest'],
|
||||||
|
'Small Key (Turtle Rock)': ['Turtle Rock - Roller Room - Right', 'Turtle Rock - Chain Chomps',
|
||||||
|
'Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left'],
|
||||||
|
'Big Key (Turtle Rock)': ['Turtle Rock - Big Key Chest'],
|
||||||
|
'Compass (Turtle Rock)': ['Turtle Rock - Compass Chest'],
|
||||||
|
'Map (Turtle Rock)': ['Turtle Rock - Roller Room - Left'],
|
||||||
|
'Small Key (Ganons Tower)': ["Ganons Tower - Bob's Torch", 'Ganons Tower - Tile Room',
|
||||||
|
'Ganons Tower - Firesnake Room', 'Ganons Tower - Pre-Moldorm Chest'],
|
||||||
|
'Big Key (Ganons Tower)': ['Ganons Tower - Big Key Chest'],
|
||||||
|
'Compass (Ganons Tower)': ['Ganons Tower - Compass Room - Top Left'],
|
||||||
|
'Map (Ganons Tower)': ['Ganons Tower - Map Chest']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
keydrop_vanilla_mapping = {
|
||||||
|
'Small Key (Desert Palace)': ['Desert Palace - Desert Tiles 1 Pot Key',
|
||||||
|
'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key'],
|
||||||
|
'Small Key (Eastern Palace)': ['Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop'],
|
||||||
|
'Small Key (Escape)': ['Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
||||||
|
'Hyrule Castle - Key Rat Key Drop'],
|
||||||
|
'Big Key (Escape)': ['Hyrule Castle - Big Key Drop'],
|
||||||
|
'Small Key (Agahnims Tower)': ['Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop'],
|
||||||
|
'Small Key (Thieves Town)': ["Thieves' Town - Hallway Pot Key", "Thieves' Town - Spike Switch Pot Key"],
|
||||||
|
'Small Key (Skull Woods)': ['Skull Woods - West Lobby Pot Key', 'Skull Woods - Spike Corner Key Drop'],
|
||||||
|
'Small Key (Swamp Palace)': ['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'],
|
||||||
|
'Small Key (Ice Palace)': ['Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop',
|
||||||
|
'Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key'],
|
||||||
|
'Small Key (Misery Mire)': ['Misery Mire - Spikes Pot Key',
|
||||||
|
'Misery Mire - Fishbone Pot Key', 'Misery Mire - Conveyor Crystal Key Drop'],
|
||||||
|
'Small Key (Turtle Rock)': ['Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop'],
|
||||||
|
'Small Key (Ganons Tower)': ['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'],
|
||||||
|
}
|
||||||
|
|
||||||
|
mode_grouping = {
|
||||||
|
'Overworld Major': [
|
||||||
|
"Link's Uncle", 'King Zora', "Link's House", 'Sahasrahla', 'Ice Rod Cave', 'Library',
|
||||||
|
'Master Sword Pedestal', 'Old Man', 'Ether Tablet', 'Catfish', 'Stumpy', 'Bombos Tablet', 'Mushroom',
|
||||||
|
'Bottle Merchant', 'Kakariko Tavern', 'Secret Passage', 'Flute Spot', 'Purple Chest',
|
||||||
|
'Waterfall Fairy - Left', 'Waterfall Fairy - Right', 'Blacksmith', 'Magic Bat', 'Sick Kid', 'Hobo',
|
||||||
|
'Potion Shop', 'Spike Cave', 'Pyramid Fairy - Left', 'Pyramid Fairy - Right', "King's Tomb",
|
||||||
|
],
|
||||||
|
'Big Chests': ['Eastern Palace - Big Chest','Desert Palace - Big Chest', 'Tower of Hera - Big Chest',
|
||||||
|
'Palace of Darkness - Big Chest', 'Swamp Palace - Big Chest', 'Skull Woods - Big Chest',
|
||||||
|
"Thieves' Town - Big Chest", 'Misery Mire - Big Chest', 'Hyrule Castle - Boomerang Chest',
|
||||||
|
'Ice Palace - Big Chest', 'Turtle Rock - Big Chest', 'Ganons Tower - Big Chest'],
|
||||||
|
'Heart Containers': ['Sanctuary', 'Eastern Palace - Boss','Desert Palace - Boss', 'Tower of Hera - Boss',
|
||||||
|
'Palace of Darkness - Boss', 'Swamp Palace - Boss', 'Skull Woods - Boss',
|
||||||
|
"Thieves' Town - Boss", 'Ice Palace - Boss', 'Misery Mire - Boss', 'Turtle Rock - Boss'],
|
||||||
|
'Heart Pieces': [
|
||||||
|
'Bumper Cave Ledge', 'Desert Ledge', 'Lake Hylia Island', 'Floating Island',
|
||||||
|
'Maze Race', 'Spectacle Rock', 'Pyramid', "Zora's Ledge", 'Lumberjack Tree',
|
||||||
|
'Sunken Treasure', 'Spectacle Rock Cave', 'Lost Woods Hideout', 'Checkerboard Cave', 'Peg Cave', 'Cave 45',
|
||||||
|
'Graveyard Cave', 'Kakariko Well - Top', "Blind's Hideout - Top", 'Bonk Rock Cave', "Aginah's Cave",
|
||||||
|
'Chest Game', 'Digging Game', 'Mire Shed - Right', 'Mimic Cave'
|
||||||
|
],
|
||||||
|
'Big Keys': [
|
||||||
|
'Eastern Palace - Big Key Chest', 'Ganons Tower - Big Key Chest',
|
||||||
|
'Desert Palace - Big Key Chest', 'Tower of Hera - Big Key Chest', 'Palace of Darkness - Big Key Chest',
|
||||||
|
'Swamp Palace - Big Key Chest', "Thieves' Town - Big Key Chest", 'Skull Woods - Big Key Chest',
|
||||||
|
'Ice Palace - Big Key Chest', 'Misery Mire - Big Key Chest', 'Turtle Rock - Big Key Chest',
|
||||||
|
],
|
||||||
|
'Compasses': [
|
||||||
|
'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'
|
||||||
|
],
|
||||||
|
'Maps': [
|
||||||
|
'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'
|
||||||
|
],
|
||||||
|
'Small Keys': [
|
||||||
|
'Sewers - Dark Cross', 'Desert Palace - Torch', 'Tower of Hera - Basement Cage',
|
||||||
|
'Castle Tower - Room 03', 'Castle Tower - Dark Maze',
|
||||||
|
'Palace of Darkness - Stalfos Basement', 'Palace of Darkness - Dark Basement - Right',
|
||||||
|
'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Shooter Room',
|
||||||
|
'Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - The Arena - Ledge',
|
||||||
|
"Thieves' Town - Blind's Cell", 'Skull Woods - Bridge Room', 'Ice Palace - Spike Room',
|
||||||
|
'Skull Woods - Pot Prison', 'Skull Woods - Pinball Room', 'Misery Mire - Spike Chest',
|
||||||
|
'Ice Palace - Iced T Room', 'Misery Mire - Main Lobby', 'Misery Mire - Bridge Chest', 'Swamp Palace - Entrance',
|
||||||
|
'Turtle Rock - Chain Chomps', 'Turtle Rock - Crystaroller Room', 'Turtle Rock - Roller Room - Right',
|
||||||
|
'Turtle Rock - Eye Bridge - Bottom Left', "Ganons Tower - Bob's Torch", 'Ganons Tower - Tile Room',
|
||||||
|
'Ganons Tower - Firesnake Room', 'Ganons Tower - Pre-Moldorm Chest'
|
||||||
|
],
|
||||||
|
'Dungeon Trash': [
|
||||||
|
'Sewers - Secret Room - Right', 'Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
|
||||||
|
"Hyrule Castle - Zelda's Chest", 'Eastern Palace - Cannonball Chest', "Thieves' Town - Ambush Chest",
|
||||||
|
"Thieves' Town - Attic", 'Ice Palace - Freezor Chest', 'Palace of Darkness - Dark Basement - Left',
|
||||||
|
'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Dark Maze - Top',
|
||||||
|
'Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right', 'Swamp Palace - Waterfall Room',
|
||||||
|
'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left',
|
||||||
|
'Turtle Rock - Eye Bridge - Top Right', 'Swamp Palace - West Chest',
|
||||||
|
],
|
||||||
|
'Overworld Trash': [
|
||||||
|
"Blind's Hideout - Left", "Blind's Hideout - Right", "Blind's Hideout - Far Left",
|
||||||
|
"Blind's Hideout - Far Right", 'Kakariko Well - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right',
|
||||||
|
'Kakariko Well - Bottom', 'Chicken House', 'Floodgate Chest', 'Mini Moldorm Cave - Left',
|
||||||
|
'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Generous Guy', 'Mini Moldorm Cave - Far Left',
|
||||||
|
'Mini Moldorm Cave - Far Right', "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',
|
||||||
|
'Paradox Cave Upper - Left', 'Paradox Cave Upper - Right', 'Spiral Cave', 'Brewery', 'C-Shaped House',
|
||||||
|
'Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left', 'Hype Cave - Bottom',
|
||||||
|
'Hype Cave - Generous Guy', 'Superbunny Cave - Bottom', 'Superbunny Cave - Top', 'Hookshot Cave - Top Right',
|
||||||
|
'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left', 'Mire Shed - Left'
|
||||||
|
],
|
||||||
|
'GT Trash': [
|
||||||
|
'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Top Left',
|
||||||
|
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right',
|
||||||
|
'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Right',
|
||||||
|
'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Hope Room - Left',
|
||||||
|
'Ganons Tower - Hope Room - Right', 'Ganons Tower - Randomizer Room - Top Left',
|
||||||
|
'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Right',
|
||||||
|
'Ganons Tower - Randomizer Room - Bottom Left', "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',
|
||||||
|
'Ganons Tower - Validation Chest',
|
||||||
|
],
|
||||||
|
'Key Drops': [
|
||||||
|
'Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
||||||
|
'Hyrule Castle - Key Rat Key Drop', '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',
|
||||||
|
'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop',
|
||||||
|
'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', "Thieves' Town - Hallway Pot Key",
|
||||||
|
"Thieves' Town - Spike Switch Pot Key", 'Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop',
|
||||||
|
'Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key', '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', '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',
|
||||||
|
],
|
||||||
|
'Big Key Drops': ['Hyrule Castle - Big Key Drop'],
|
||||||
|
'Shops': [
|
||||||
|
'Dark Death Mountain Shop - Left', 'Dark Death Mountain Shop - Middle', 'Dark Death Mountain Shop - Right',
|
||||||
|
'Red Shield Shop - Left', 'Red Shield Shop - Middle', 'Red Shield Shop - Right', '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', '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', 'Paradox Shop - Left', 'Paradox Shop - Middle',
|
||||||
|
'Paradox Shop - Right', 'Kakariko Shop - Left', 'Kakariko Shop - Middle', 'Kakariko Shop - Right',
|
||||||
|
'Lake Hylia Shop - Left', 'Lake Hylia Shop - Middle', 'Lake Hylia Shop - Right', 'Capacity Upgrade - Left',
|
||||||
|
'Capacity Upgrade - Right'
|
||||||
|
],
|
||||||
|
'RetroShops': [
|
||||||
|
'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'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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',
|
||||||
|
'Bug Catching Net', 'Cane of Byrna', 'Blue Boomerang', 'Red Boomerang', 'Progressive Glove',
|
||||||
|
'Power Glove', 'Titans Mitts', 'Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Magic Mirror',
|
||||||
|
'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)', 'Magic Upgrade (1/2)',
|
||||||
|
'Sanctuary Heart Container', 'Boss Heart Container', 'Progressive Shield', 'Blue Shield', 'Red Shield',
|
||||||
|
'Mirror Shield', 'Progressive Armor', 'Blue Mail', 'Red Mail', 'Progressive Sword', 'Fighter Sword',
|
||||||
|
'Master Sword', 'Tempered Sword', 'Golden Sword', 'Bow', 'Silver Arrows', 'Triforce Piece', 'Moon Pearl',
|
||||||
|
'Progressive Bow', 'Progressive Bow (Alt)'}
|
||||||
|
|
||||||
|
|
||||||
|
# todo: re-enter these
|
||||||
|
clustered_groups = [
|
||||||
|
LocationGroup("MajorRoute1").locs([
|
||||||
|
'Library', 'Master Sword Pedestal', 'Old Man', 'Flute Spot',
|
||||||
|
'Ether Tablet', 'Stumpy', 'Bombos Tablet', 'Mushroom', 'Bottle Merchant', 'Kakariko Tavern',
|
||||||
|
'Sick Kid', 'Pyramid Fairy - Left', 'Pyramid Fairy - Right'
|
||||||
|
]),
|
||||||
|
LocationGroup("MajorRoute2").locs([
|
||||||
|
'King Zora', 'Sahasrahla', 'Ice Rod Cave', 'Catfish',
|
||||||
|
'Purple Chest', 'Waterfall Fairy - Left', 'Waterfall Fairy - Right', 'Blacksmith',
|
||||||
|
'Magic Bat', 'Hobo', 'Potion Shop', 'Spike Cave', "King's Tomb"
|
||||||
|
]),
|
||||||
|
LocationGroup("BigChest").locs([
|
||||||
|
'Sanctuary', 'Eastern Palace - Big Chest',
|
||||||
|
'Desert Palace - Big Chest', 'Tower of Hera - Big Chest', 'Palace of Darkness - Big Chest',
|
||||||
|
'Swamp Palace - Big Chest', 'Skull Woods - Big Chest', "Thieves' Town - Big Chest",
|
||||||
|
'Misery Mire - Big Chest', 'Hyrule Castle - Boomerang Chest', 'Ice Palace - Big Chest',
|
||||||
|
'Turtle Rock - Big Chest', 'Ganons Tower - Big Chest'
|
||||||
|
]),
|
||||||
|
LocationGroup("BossUncle").locs([
|
||||||
|
"Link's Uncle", "Link's House", 'Secret Passage', 'Eastern Palace - Boss',
|
||||||
|
'Desert Palace - Boss', 'Tower of Hera - Boss', 'Palace of Darkness - Boss', 'Swamp Palace - Boss',
|
||||||
|
'Skull Woods - Boss', "Thieves' Town - Boss", 'Ice Palace - Boss', 'Misery Mire - Boss',
|
||||||
|
'Turtle Rock - Boss']),
|
||||||
|
LocationGroup("HeartPieces LW").locs([
|
||||||
|
'Lost Woods Hideout', 'Kakariko Well - Top', "Blind's Hideout - Top", 'Maze Race', 'Sunken Treasure',
|
||||||
|
'Bonk Rock Cave', 'Desert Ledge', "Aginah's Cave", 'Spectacle Rock Cave', 'Spectacle Rock', 'Pyramid',
|
||||||
|
'Lumberjack Tree', "Zora's Ledge"]),
|
||||||
|
LocationGroup("HeartPieces DW").locs([
|
||||||
|
'Lake Hylia Island', 'Chest Game', 'Digging Game', 'Graveyard Cave', 'Mimic Cave',
|
||||||
|
'Cave 45', 'Peg Cave', 'Bumper Cave Ledge', 'Checkerboard Cave', 'Mire Shed - Right', 'Floating Island',
|
||||||
|
'Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right']),
|
||||||
|
LocationGroup("Minor Trash").locs([
|
||||||
|
'Ice Palace - Freezor Chest', 'Skull Woods - Pot Prison', 'Misery Mire - Bridge Chest',
|
||||||
|
'Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Maze - Top',
|
||||||
|
'Palace of Darkness - Shooter Room', 'Palace of Darkness - The Arena - Bridge',
|
||||||
|
'Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right',
|
||||||
|
'Swamp Palace - Waterfall Room', 'Turtle Rock - Eye Bridge - Bottom Right',
|
||||||
|
'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 - Far Right", 'Chicken House', 'Paradox Cave Lower - Far Left',
|
||||||
|
'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right',
|
||||||
|
'Paradox Cave Lower - Middle', 'Paradox Cave Upper - Left', 'Paradox Cave Upper - Right', 'Spiral Cave',
|
||||||
|
]),
|
||||||
|
LocationGroup("BK-Bunny").locs([
|
||||||
|
'Eastern Palace - Big Key Chest', 'Ganons Tower - Big Key Chest',
|
||||||
|
'Desert Palace - Big Key Chest', 'Tower of Hera - Big Key Chest', 'Palace of Darkness - Big Key Chest',
|
||||||
|
'Swamp Palace - Big Key Chest', "Thieves' Town - Big Key Chest", 'Skull Woods - Big Key Chest',
|
||||||
|
'Ice Palace - Big Key Chest', 'Misery Mire - Big Key Chest', 'Turtle Rock - Big Key Chest',
|
||||||
|
'Superbunny Cave - Top', 'Superbunny Cave - Bottom',
|
||||||
|
]),
|
||||||
|
LocationGroup("Early Drops").flags(True, True).locs([
|
||||||
|
'Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
||||||
|
'Hyrule Castle - Key Rat Key Drop', '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',
|
||||||
|
'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',
|
||||||
|
]),
|
||||||
|
LocationGroup("Late Drops").flags(True, True).locs([
|
||||||
|
'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',
|
||||||
|
'Paradox Shop - Left', 'Paradox Shop - Middle', 'Paradox Shop - Right',
|
||||||
|
'Kakariko Shop - Left', 'Kakariko Shop - Middle', 'Kakariko Shop - Right',
|
||||||
|
'Capacity Upgrade - Left']),
|
||||||
|
LocationGroup("Hylia Shop").flags(False, False, True).locs([
|
||||||
|
'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',
|
||||||
|
'Dark Potion Shop - Left', 'Dark Potion Shop - Middle', 'Dark Potion Shop - Right',
|
||||||
|
'Lake Hylia Shop - Left', 'Lake Hylia Shop - Middle', 'Lake Hylia Shop - Right',
|
||||||
|
'Capacity Upgrade - Right']),
|
||||||
|
LocationGroup("Map Validation").locs([
|
||||||
|
'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',
|
||||||
|
])
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
trash_items = {
|
||||||
|
'Nothing': -1,
|
||||||
|
'Bee Trap': 0,
|
||||||
|
'Rupee (1)': 1,
|
||||||
|
'Rupees (5)': 1,
|
||||||
|
'Rupees (20)': 1,
|
||||||
|
|
||||||
|
'Small Heart': 2,
|
||||||
|
'Bee': 2,
|
||||||
|
|
||||||
|
'Bombs (3)': 3,
|
||||||
|
'Arrows (10)': 3,
|
||||||
|
'Bombs (10)': 3,
|
||||||
|
|
||||||
|
'Red Potion': 4,
|
||||||
|
'Blue Shield': 4,
|
||||||
|
'Rupees (50)': 4,
|
||||||
|
'Rupees (100)': 4,
|
||||||
|
'Rupees (300)': 5,
|
||||||
|
|
||||||
|
'Piece of Heart': 17
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from Dungeons import dungeon_prize
|
|
||||||
|
|
||||||
class ItemPoolConfig(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.reserved_locations = defaultdict(set)
|
|
||||||
|
|
||||||
|
|
||||||
def create_item_pool_config(world):
|
|
||||||
config = ItemPoolConfig()
|
|
||||||
if world.algorithm in ['balanced']:
|
|
||||||
for player in range(1, world.players+1):
|
|
||||||
if world.restrict_boss_items[player]:
|
|
||||||
for dungeon in dungeon_prize:
|
|
||||||
if dungeon.startswith('Thieves'):
|
|
||||||
dungeon = "Thieves' Town"
|
|
||||||
config.reserved_locations[player].add(f'{dungeon} - Boss')
|
|
||||||
world.item_pool_config = config
|
|
||||||
Reference in New Issue
Block a user