Merge branch 'DoorDevUnstable' into DoorDevVolatile
# Conflicts: # Main.py # MultiClient.py # Rom.py # data/base2current.bps # resources/app/gui/lang/en.json # resources/app/gui/randomize/item/widgets.json # source/gui/bottom.py # source/gui/widgets.py
This commit is contained in:
@@ -289,7 +289,7 @@ class World(object):
|
||||
else:
|
||||
if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
|
||||
return False
|
||||
elif self.goal[player] in ['crystals', 'trinity']:
|
||||
elif self.goal[player] in ['crystals', 'trinity', 'ganonhunt']:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -2513,6 +2513,7 @@ class Spoiler(object):
|
||||
'pseudoboots': self.world.pseudoboots,
|
||||
'triforcegoal': self.world.treasure_hunt_count,
|
||||
'triforcepool': self.world.treasure_hunt_total,
|
||||
'race': self.world.settings.world_rep['meta']['race'],
|
||||
'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)}
|
||||
}
|
||||
|
||||
@@ -2615,6 +2616,7 @@ class Spoiler(object):
|
||||
self.set_lobby(portal.name, portal.door.name, player)
|
||||
|
||||
def to_json(self):
|
||||
self.parse_meta()
|
||||
self.parse_data()
|
||||
out = OrderedDict()
|
||||
out['Entrances'] = list(self.entrances.values())
|
||||
|
||||
5
CLI.py
5
CLI.py
@@ -132,7 +132,7 @@ def parse_cli(argv, no_defaults=False):
|
||||
'flute_mode', 'bow_mode', 'take_any', 'boots_hint',
|
||||
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
||||
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
||||
'usestartinventory', 'bombbag', 'overworld_map', 'restrict_boss_items',
|
||||
'usestartinventory', 'bombbag', 'overworld_map', 'restrict_boss_items', 'triforce_max_difference',
|
||||
'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
|
||||
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern',
|
||||
'pseudoboots', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
|
||||
@@ -229,7 +229,8 @@ def parse_settings():
|
||||
"triforce_pool_max": 0,
|
||||
"triforce_goal_min": 0,
|
||||
"triforce_goal_max": 0,
|
||||
"triforce_min_difference": 10,
|
||||
"triforce_min_difference": 0,
|
||||
"triforce_max_difference": 10000,
|
||||
|
||||
"code": "",
|
||||
"multi": 1,
|
||||
|
||||
@@ -1962,7 +1962,7 @@ def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, all_cu
|
||||
if flex_map[dungeon] > 0:
|
||||
queue.append(dungeon)
|
||||
# time to re-assign
|
||||
reassign_big_key_doors(bk_map, world, player)
|
||||
reassign_big_key_doors(bk_map, used_doors, world, player)
|
||||
for name, big_list in bk_map.items():
|
||||
used_doors.update(flatten_pair_list(big_list))
|
||||
return used_doors
|
||||
@@ -2047,7 +2047,7 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_
|
||||
else:
|
||||
builder.key_doors_num -= 1
|
||||
# time to re-assign
|
||||
reassign_key_doors(small_map, world, player)
|
||||
reassign_key_doors(small_map, used_doors, world, player)
|
||||
for dungeon_name in pool:
|
||||
if world.keyshuffle[player] != 'universal':
|
||||
builder = world.dungeon_layouts[player][dungeon_name]
|
||||
@@ -2129,7 +2129,7 @@ def shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, all_
|
||||
suggestion_map[dungeon] = pair
|
||||
queue.append(dungeon)
|
||||
# time to re-assign
|
||||
reassign_bd_doors(bd_map, world, player)
|
||||
reassign_bd_doors(bd_map, used_doors, world, player)
|
||||
for name, pair in bd_map.items():
|
||||
used_doors.update(flatten_pair_list(pair[0]))
|
||||
used_doors.update(flatten_pair_list(pair[1]))
|
||||
@@ -2539,7 +2539,7 @@ def find_current_bk_doors(builder):
|
||||
return current_doors
|
||||
|
||||
|
||||
def reassign_big_key_doors(bk_map, world, player):
|
||||
def reassign_big_key_doors(bk_map, used_doors, world, player):
|
||||
logger = logging.getLogger('')
|
||||
for name, big_doors in bk_map.items():
|
||||
flat_proposal = flatten_pair_list(big_doors)
|
||||
@@ -2548,11 +2548,11 @@ def reassign_big_key_doors(bk_map, world, player):
|
||||
while len(queue) > 0:
|
||||
d = queue.pop()
|
||||
if d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal:
|
||||
if not d.entranceFlag:
|
||||
if not d.entranceFlag and d not in used_doors and d.dest not in used_doors:
|
||||
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
||||
d.bigKey = False
|
||||
elif d.type is DoorType.Normal and d not in flat_proposal:
|
||||
if not d.entranceFlag:
|
||||
elif d.type is DoorType.Normal and d not in flat_proposal :
|
||||
if not d.entranceFlag and d not in used_doors:
|
||||
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
||||
d.bigKey = False
|
||||
for obj in big_doors:
|
||||
@@ -2795,7 +2795,7 @@ def find_valid_bd_combination(builder, suggested, world, player):
|
||||
return bomb_proposal, dash_proposal, ttl_needed
|
||||
|
||||
|
||||
def reassign_bd_doors(bd_map, world, player):
|
||||
def reassign_bd_doors(bd_map, used_doors, world, player):
|
||||
for name, pair in bd_map.items():
|
||||
flat_bomb_proposal = flatten_pair_list(pair[0])
|
||||
flat_dash_proposal = flatten_pair_list(pair[1])
|
||||
@@ -2808,10 +2808,10 @@ def reassign_bd_doors(bd_map, world, player):
|
||||
queue = deque(find_current_bd_doors(builder, world))
|
||||
while len(queue) > 0:
|
||||
d = queue.pop()
|
||||
if d.type is DoorType.Interior and not_in_proposal(d):
|
||||
if d.type is DoorType.Interior and not_in_proposal(d) and d not in used_doors and d.dest not in used_doors:
|
||||
if not d.entranceFlag:
|
||||
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
||||
elif d.type is DoorType.Normal and not_in_proposal(d):
|
||||
elif d.type is DoorType.Normal and not_in_proposal(d) and d not in used_doors:
|
||||
if not d.entranceFlag:
|
||||
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
||||
do_bombable_dashable(pair[0], DoorKind.Bombable, world, player)
|
||||
@@ -3003,7 +3003,7 @@ def valid_key_door_pair(door1, door2):
|
||||
return len(door1.entrance.parent_region.exits) <= 1 or len(door2.entrance.parent_region.exits) <= 1
|
||||
|
||||
|
||||
def reassign_key_doors(small_map, world, player):
|
||||
def reassign_key_doors(small_map, used_doors, world, player):
|
||||
logger = logging.getLogger('')
|
||||
for name, small_doors in small_map.items():
|
||||
logger.debug(f'Key doors for {name}')
|
||||
@@ -3024,13 +3024,13 @@ def reassign_key_doors(small_map, world, player):
|
||||
room.delete(d.doorListPos)
|
||||
d.smallKey = False
|
||||
elif d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal:
|
||||
if not d.entranceFlag:
|
||||
if not d.entranceFlag and d not in used_doors and d.dest not in used_doors:
|
||||
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
||||
d.smallKey = False
|
||||
d.dest.smallKey = False
|
||||
queue.remove(d.dest)
|
||||
elif d.type is DoorType.Normal and d not in flat_proposal:
|
||||
if not d.entranceFlag:
|
||||
if not d.entranceFlag and d not in used_doors:
|
||||
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
||||
d.smallKey = False
|
||||
for dp in world.paired_doors[player]:
|
||||
@@ -3295,7 +3295,7 @@ def find_inaccessible_regions(world, player):
|
||||
while len(queue) > 0:
|
||||
next_region = queue.popleft()
|
||||
visited_regions.add(next_region)
|
||||
if next_region.name == 'Dark Sanctuary Hint': # special spawn point in cave
|
||||
if world.mode[player] == 'inverted' and next_region.name == 'Dark Sanctuary Hint': # special spawn point in cave
|
||||
for ent in next_region.entrances:
|
||||
parent = ent.parent_region
|
||||
if parent and parent.type is not RegionType.Dungeon and parent not in queue and parent not in visited_regions:
|
||||
@@ -3811,6 +3811,8 @@ logical_connections = [
|
||||
('GT Blocked Stairs Block Path', 'GT Big Chest'),
|
||||
('GT Speed Torch South Path', 'GT Speed Torch'),
|
||||
('GT Speed Torch North Path', 'GT Speed Torch Upper'),
|
||||
('GT Conveyor Cross Hammer Path', 'GT Conveyor Cross Across Pits'),
|
||||
('GT Conveyor Cross Hookshot Path', 'GT Conveyor Cross'),
|
||||
('GT Hookshot East-Mid Path', 'GT Hookshot Mid Platform'),
|
||||
('GT Hookshot Mid-East Path', 'GT Hookshot East Platform'),
|
||||
('GT Hookshot North-Mid Path', 'GT Hookshot Mid Platform'),
|
||||
@@ -4159,7 +4161,7 @@ interior_doors = [
|
||||
('Mire Neglected Room SE', 'Mire Chest View NE'),
|
||||
('Mire BK Chest Ledge WS', 'Mire Warping Pool ES'), # technically one-way
|
||||
('Mire Torches Top SW', 'Mire Torches Bottom NW'),
|
||||
('Mire Torches Bottom WS', 'Mire Attic Hint ES'),
|
||||
('Mire Torches Bottom ES', 'Mire Attic Hint WS'),
|
||||
('Mire Dark Shooters SE', 'Mire Key Rupees NE'),
|
||||
('Mire Dark Shooters SW', 'Mire Block X NW'),
|
||||
('Mire Tall Dark and Roomy WS', 'Mire Crystal Right ES'),
|
||||
|
||||
6
Doors.py
6
Doors.py
@@ -929,9 +929,9 @@ def create_doors(world, player):
|
||||
create_door(player, 'Mire Torches Top SW', Intr).dir(So, 0x97, Left, High).pos(1),
|
||||
create_door(player, 'Mire Torches Bottom Holes', Hole),
|
||||
create_door(player, 'Mire Torches Bottom NW', Intr).dir(No, 0x97, Left, High).pos(1),
|
||||
create_door(player, 'Mire Torches Bottom WS', Intr).dir(We, 0x97, Bot, High).pos(0),
|
||||
create_door(player, 'Mire Torches Bottom ES', Intr).dir(Ea, 0x97, Bot, High).pos(0),
|
||||
create_door(player, 'Mire Torches Top Holes', Hole),
|
||||
create_door(player, 'Mire Attic Hint ES', Intr).dir(Ea, 0x97, Bot, High).pos(0),
|
||||
create_door(player, 'Mire Attic Hint WS', Intr).dir(We, 0x97, Bot, High).pos(0),
|
||||
create_door(player, 'Mire Attic Hint Hole', Hole),
|
||||
create_door(player, 'Mire Dark Shooters Up Stairs', Sprl).dir(Up, 0x93, 0, LTH).ss(A, 0x32, 0xec),
|
||||
create_door(player, 'Mire Dark Shooters SW', Intr).dir(So, 0x93, Left, High).pos(0),
|
||||
@@ -1120,6 +1120,8 @@ def create_doors(world, player):
|
||||
create_door(player, 'GT Invisible Catwalk NE', Nrml).dir(No, 0x9c, Right, High).pos(2),
|
||||
create_door(player, 'GT Conveyor Cross EN', Nrml).dir(Ea, 0x8b, Top, High).pos(2),
|
||||
create_door(player, 'GT Conveyor Cross WN', Intr).dir(We, 0x8b, Top, High).pos(0),
|
||||
create_door(player, 'GT Conveyor Cross Hammer Path', Lgcl),
|
||||
create_door(player, 'GT Conveyor Cross Hookshot Path', Lgcl),
|
||||
create_door(player, 'GT Hookshot EN', Intr).dir(Ea, 0x8b, Top, High).pos(0),
|
||||
create_door(player, 'GT Hookshot East-Mid Path', Lgcl),
|
||||
create_door(player, 'GT Hookshot Mid-East Path', Lgcl),
|
||||
|
||||
14
Dungeons.py
14
Dungeons.py
@@ -191,10 +191,11 @@ gt_regions = [
|
||||
'GT Tile Room', 'GT Speed Torch', 'GT Speed Torch Upper', 'GT Pots n Blocks', 'GT Crystal Conveyor',
|
||||
'GT Crystal Conveyor Corner', 'GT Crystal Conveyor Left', 'GT Crystal Conveyor - Ranged Crystal',
|
||||
'GT Crystal Conveyor Corner - Ranged Crystal', 'GT Compass Room', 'GT Invisible Bridges', 'GT Invisible Catwalk',
|
||||
'GT Conveyor Cross', 'GT Hookshot East Platform', 'GT Hookshot Mid Platform', 'GT Hookshot North Platform',
|
||||
'GT Hookshot South Platform', 'GT Hookshot South Entry', 'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room',
|
||||
'GT Double Switch Entry', 'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners',
|
||||
'GT Double Switch Left', 'GT Double Switch Left - Crystal', 'GT Double Switch Entry - Ranged Switches',
|
||||
'GT Conveyor Cross', 'GT Conveyor Cross Across Pits', 'GT Hookshot East Platform', 'GT Hookshot Mid Platform',
|
||||
'GT Hookshot North Platform', 'GT Hookshot South Platform', 'GT Hookshot South Entry',
|
||||
'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room', 'GT Double Switch Entry',
|
||||
'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners', 'GT Double Switch Left',
|
||||
'GT Double Switch Left - Crystal', 'GT Double Switch Entry - Ranged Switches',
|
||||
'GT Double Switch Exit', 'GT Spike Crystal Left',
|
||||
'GT Spike Crystal Right', 'GT Warp Maze - Left Section', 'GT Warp Maze - Mid Section',
|
||||
'GT Warp Maze - Right Section', 'GT Warp Maze - Pit Section', 'GT Warp Maze - Pit Exit Warp Spot',
|
||||
@@ -205,8 +206,9 @@ gt_regions = [
|
||||
'GT Dash Hall', 'GT Hidden Spikes', 'GT Cannonball Bridge', 'GT Refill', 'GT Gauntlet 1', 'GT Gauntlet 2',
|
||||
'GT Gauntlet 3', 'GT Gauntlet 4', 'GT Gauntlet 5', 'GT Beam Dash', 'GT Lanmolas 2', 'GT Quad Pot', 'GT Wizzrobes 1',
|
||||
'GT Dashing Bridge', 'GT Wizzrobes 2', 'GT Conveyor Bridge', 'GT Torch Cross', 'GT Staredown', 'GT Falling Torches',
|
||||
'GT Mini Helmasaur Room', 'GT Bomb Conveyor', 'GT Crystal Circles', 'GT Crystal Inner Circle', 'GT Crystal Circles - Ranged Crystal',
|
||||
'GT Left Moldorm Ledge', 'GT Right Moldorm Ledge', 'GT Moldorm', 'GT Moldorm Pit', 'GT Validation', 'GT Validation Door',
|
||||
'GT Mini Helmasaur Room', 'GT Bomb Conveyor', 'GT Crystal Circles', 'GT Crystal Inner Circle',
|
||||
'GT Crystal Circles - Ranged Crystal', 'GT Left Moldorm Ledge', 'GT Right Moldorm Ledge', 'GT Moldorm',
|
||||
'GT Moldorm Pit', 'GT Validation', 'GT Validation Door',
|
||||
'GT Frozen Over', 'GT Brightly Lit Hall', 'GT Agahnim 2', 'Ganons Tower Portal'
|
||||
]
|
||||
|
||||
|
||||
@@ -2171,6 +2171,8 @@ mandatory_connections = [('Links House S&Q', 'Links House'),
|
||||
('West Dark World Gap', 'West Dark World'),
|
||||
('Broken Bridge Pass (Top)', 'East Dark World'),
|
||||
('Broken Bridge Pass (Bottom)', 'Northeast Dark World'),
|
||||
('Dark Graveyard Bush (South)', 'Dark Graveyard North'),
|
||||
('Dark Graveyard Bush (North)', 'West Dark World'),
|
||||
('Peg Area Rocks (Left)', 'Hammer Peg Area'),
|
||||
('Peg Area Rocks (Right)', 'West Dark World'),
|
||||
('Village of Outcasts Heavy Rock', 'West Dark World'),
|
||||
|
||||
6
Fill.py
6
Fill.py
@@ -279,6 +279,10 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp
|
||||
if spot_to_fill:
|
||||
return spot_to_fill
|
||||
return None
|
||||
# explicitly fail these cases
|
||||
elif world.algorithm in ['dungeon_only', 'major_only']:
|
||||
raise FillError(f'Rare placement for {world.algorithm} detected. {item_to_place} unable to be placed.'
|
||||
f' Try a different seed')
|
||||
else:
|
||||
other_locations = [x for x in locations if x not in attempted]
|
||||
for location in other_locations:
|
||||
@@ -402,7 +406,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
||||
else:
|
||||
max_trash = gt_count
|
||||
scaled_trash = math.floor(max_trash * scale_factor)
|
||||
if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||
if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt'] or world.algorithm == 'dungeon_only':
|
||||
gftower_trash_count = random.randint(scaled_trash, max_trash)
|
||||
else:
|
||||
gftower_trash_count = random.randint(0, scaled_trash)
|
||||
|
||||
40
ItemList.py
40
ItemList.py
@@ -1294,7 +1294,8 @@ def make_customizer_pool(world, player):
|
||||
bow_found = next((i for i in pool if i in {'Bow', 'Progressive Bow'}), None)
|
||||
if not bow_found:
|
||||
missing_items.append('Progressive Bow')
|
||||
logging.getLogger('').warning(f'The following items are not in the custom item pool {", ".join(missing_items)}')
|
||||
if missing_items:
|
||||
logging.getLogger('').warning(f'The following items are not in the custom item pool {", ".join(missing_items)}')
|
||||
|
||||
g, t = set_default_triforce(world.goal[player], world.treasure_hunt_count[player],
|
||||
world.treasure_hunt_total[player])
|
||||
@@ -1303,20 +1304,23 @@ def make_customizer_pool(world, player):
|
||||
if pieces < t:
|
||||
pool.extend(['Triforce Piece'] * (t - pieces))
|
||||
|
||||
if not world.customizer.get_start_inventory():
|
||||
if world.logic[player] in ['owglitches', 'nologic']:
|
||||
precollected_items.append('Pegasus Boots')
|
||||
if 'Pegasus Boots' in pool:
|
||||
pool.remove('Pegasus Boots')
|
||||
pool.append('Rupees (20)')
|
||||
if world.swords[player] == 'assured':
|
||||
precollected_items.append('Progressive Sword')
|
||||
if 'Progressive Sword' in pool:
|
||||
pool.remove('Progressive Sword')
|
||||
pool.append('Rupees (50)')
|
||||
elif 'Fighter Sword' in pool:
|
||||
pool.remove('Fighter Sword')
|
||||
pool.append('Rupees (50)')
|
||||
sphere_0 = world.customizer.get_start_inventory()
|
||||
no_start_inventory = not sphere_0 or not sphere_0[player]
|
||||
init_equip = [] if no_start_inventory else sphere_0[player]
|
||||
if (world.logic[player] in ['owglitches', 'nologic']
|
||||
and (no_start_inventory or all(x != 'Pegasus Boots' for x in init_equip))):
|
||||
precollected_items.append('Pegasus Boots')
|
||||
if 'Pegasus Boots' in pool:
|
||||
pool.remove('Pegasus Boots')
|
||||
pool.append('Rupees (20)')
|
||||
if world.swords[player] == 'assured' and (no_start_inventory or all(' Sword' not in x for x in init_equip)):
|
||||
precollected_items.append('Progressive Sword')
|
||||
if 'Progressive Sword' in pool:
|
||||
pool.remove('Progressive Sword')
|
||||
pool.append('Rupees (50)')
|
||||
elif 'Fighter Sword' in pool:
|
||||
pool.remove('Fighter Sword')
|
||||
pool.append('Rupees (50)')
|
||||
|
||||
return pool, placed_items, precollected_items, clock_mode, 1
|
||||
|
||||
@@ -1399,10 +1403,13 @@ def fill_specific_items(world):
|
||||
dungeon_pool, prize_set, prize_pool)
|
||||
if item_to_place:
|
||||
world.push_item(loc, item_to_place, False)
|
||||
loc.locked = True
|
||||
track_outside_keys(item_to_place, loc, world)
|
||||
track_dungeon_items(item_to_place, loc, world)
|
||||
loc.event = (event_flag or item_to_place.advancement
|
||||
or item_to_place.bigkey or item_to_place.smallkey)
|
||||
else:
|
||||
raise Exception(f'Did not find "{item}" in item pool to place at "{location}"')
|
||||
advanced_placements = world.customizer.get_advanced_placements()
|
||||
if advanced_placements:
|
||||
for player, placement_list in advanced_placements.items():
|
||||
@@ -1412,7 +1419,7 @@ def fill_specific_items(world):
|
||||
item_to_place, event_flag = get_item_and_event_flag(item, world, player,
|
||||
dungeon_pool, prize_set, prize_pool)
|
||||
if not item_to_place:
|
||||
continue
|
||||
raise Exception(f'Did not find "{item}" in item pool to place for a LocationGroup"')
|
||||
locations = placement['locations']
|
||||
handled = False
|
||||
while not handled:
|
||||
@@ -1432,6 +1439,7 @@ def fill_specific_items(world):
|
||||
if loc.item:
|
||||
continue
|
||||
world.push_item(loc, item_to_place, False)
|
||||
loc.locked = True
|
||||
track_outside_keys(item_to_place, loc, world)
|
||||
track_dungeon_items(item_to_place, loc, world)
|
||||
loc.event = (event_flag or item_to_place.advancement
|
||||
|
||||
26
Main.py
26
Main.py
@@ -128,8 +128,6 @@ def main(args, seed=None, fish=None):
|
||||
world.potshuffle = args.shufflepots.copy()
|
||||
world.mixed_travel = args.mixed_travel.copy()
|
||||
world.standardize_palettes = args.standardize_palettes.copy()
|
||||
world.treasure_hunt_count = {k: int(v) for k, v in args.triforce_goal.items()}
|
||||
world.treasure_hunt_total = {k: int(v) for k, v in args.triforce_pool.items()}
|
||||
world.shufflelinks = args.shufflelinks.copy()
|
||||
world.shuffletavern = args.shuffletavern.copy()
|
||||
world.pseudoboots = args.pseudoboots.copy()
|
||||
@@ -139,6 +137,26 @@ def main(args, seed=None, fish=None):
|
||||
world.collection_rate = args.collection_rate.copy()
|
||||
world.colorizepots = args.colorizepots.copy()
|
||||
|
||||
world.treasure_hunt_count = {}
|
||||
world.treasure_hunt_total = {}
|
||||
for p in args.triforce_goal:
|
||||
if int(args.triforce_goal[p]) != 0 or int(args.triforce_pool[p]) != 0 or int(args.triforce_goal_min[p]) != 0 or int(args.triforce_goal_max[p]) != 0 or int(args.triforce_pool_min[p]) != 0 or int(args.triforce_pool_max[p]) != 0:
|
||||
if int(args.triforce_goal[p]) != 0:
|
||||
world.treasure_hunt_count[p] = int(args.triforce_goal[p])
|
||||
elif int(args.triforce_goal_min[p]) != 0 and int(args.triforce_goal_max[p]) != 0:
|
||||
world.treasure_hunt_count[p] = random.randint(int(args.triforce_goal_min[p]), int(args.triforce_goal_max[p]))
|
||||
else:
|
||||
world.treasure_hunt_count[p] = 8 if world.goal[p] == 'trinity' else 20
|
||||
if int(args.triforce_pool[p]) != 0:
|
||||
world.treasure_hunt_total[p] = int(args.triforce_pool[p])
|
||||
elif int(args.triforce_pool_min[p]) != 0 and int(args.triforce_pool_max[p]) != 0:
|
||||
world.treasure_hunt_total[p] = random.randint(max(int(args.triforce_pool_min[p]), world.treasure_hunt_count[p] + int(args.triforce_min_difference[p])), min(int(args.triforce_pool_max[p]), world.treasure_hunt_count[p] + int(args.triforce_max_difference[p])))
|
||||
else:
|
||||
world.treasure_hunt_total[p] = 10 if world.goal[p] == 'trinity' else 30
|
||||
else:
|
||||
# this will be handled in ItemList.py and custom item pool is used to determine the numbers
|
||||
world.treasure_hunt_count[p], world.treasure_hunt_total[p] = 0, 0
|
||||
|
||||
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
||||
world.finish_init()
|
||||
|
||||
@@ -620,7 +638,9 @@ def create_playthrough(world):
|
||||
|
||||
logging.getLogger('').debug(world.fish.translate("cli", "cli", "building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations))
|
||||
if not sphere:
|
||||
logging.getLogger('').error(world.fish.translate("cli", "cli", "cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
|
||||
if world.accessibility[location.item.player] != 'none':
|
||||
logging.getLogger('').error(world.fish.translate("cli", "cli", "cannot.reach.items"),
|
||||
[world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
|
||||
if any([location.name not in optional_locations and world.accessibility[location.item.player] != 'none' for location in sphere_candidates]):
|
||||
raise RuntimeError(world.fish.translate("cli", "cli", "cannot.reach.progression"))
|
||||
else:
|
||||
|
||||
@@ -48,7 +48,7 @@ mirror_connections = {
|
||||
'East Dark Death Mountain (Bushes)': ['Fairy Ascension Plateau'],
|
||||
'East Dark Death Mountain (Bottom)': ['East Death Mountain (Bottom)'],
|
||||
|
||||
'West Dark World': ['Graveyard Ledge', 'Kings Grave Area'],
|
||||
'Dark Graveyard North': ['Graveyard Ledge', 'Kings Grave Area'],
|
||||
|
||||
'Bumper Cave Ledge': ['Death Mountain Return Ledge'],
|
||||
'Bumper Cave Entrance': ['Death Mountain Entrance'],
|
||||
|
||||
@@ -404,7 +404,7 @@ CLI: `--logic owglitches`
|
||||
New supported goals:
|
||||
|
||||
* Trinity: Find one of 3 triforces to win. One is at pedestal. One is with Ganon. One is with Murahdahla who wants you to find 8 of 10 triforce pieces to complete.
|
||||
* Triforce Hunt + Ganon: Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI
|
||||
* Ganonhunt: Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI
|
||||
* Completionist: All dungeons not enough for you? You have to obtain every item in the game too. This option turns on the collection rate counter and forces accessibility to be 100% locations. Finish by defeating Ganon.
|
||||
|
||||
|
||||
@@ -573,13 +573,14 @@ Create bps patch(es) instead of generating rom(s) for distribution. `--bps`
|
||||
|
||||
### Triforce Hunt Settings
|
||||
|
||||
A collection of settings to control the triforce piece pool for the CLI/Mystery
|
||||
A collection of settings to control the triforce piece pool if not specified through --triforce_goal and --triforce_pool
|
||||
|
||||
* --triforce_goal_min: Minimum number of pieces to collect to win
|
||||
* --triforce_goal_max: Maximum number of pieces to collect to win
|
||||
* --triforce_pool_min: Minimum number of pieces in item pool
|
||||
* --triforce_pool_max: Maximum number of pieces in item pool
|
||||
* --triforce_min_difference: Minimum difference between pool and goal to win
|
||||
* --triforce_max_difference: Maximum difference between pool and goal to win
|
||||
|
||||
### Seed
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ Please see [Customizer documentation](docs/Customizer.md) on how to create custo
|
||||
|
||||
## New Goals
|
||||
|
||||
### Triforce Hunt + Ganon
|
||||
### Ganonhunt
|
||||
Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI
|
||||
|
||||
### Completionist
|
||||
@@ -109,8 +109,35 @@ These are now independent of retro mode and have three options: None, Random, an
|
||||
|
||||
# Bug Fixes and Notes
|
||||
|
||||
* 1.2.0.19u
|
||||
* Added min/max for triforce pool, goal, and difference for CLI and Customizer. (Thanks Catobat)
|
||||
* Fixed a bug with dungeon generation
|
||||
* Multiworld: Fixed /missing command to not list all the pots
|
||||
* Changed the "Ganonhunt" goal to use open pyramid on the Auto setting
|
||||
* Customizer: Fixed the example yaml for shopsanity
|
||||
* 1.2.0.18u
|
||||
* Fixed an issue with pyramid hole being in logic when it is not opened.
|
||||
* Crystal cutscene at GT use new symmetrical layouts (thanks Codemann)
|
||||
* Fix for Hera Boss music (thanks Codemann)
|
||||
* Fixed an issue where certain vanilla door types would not allow other types to be placed.
|
||||
* Customizer: fixed an issue where last ditch placements would move customized items. Those are now locked and the generation will fail instead if no alternatives are found.
|
||||
* Customizer: fixed an issue with assured sword and start_inventory
|
||||
* Customizer: warns when trying to specifically place an item that's not in the item pool
|
||||
* Fixed "accessibility: none" displaying a spoiling message
|
||||
* Fixed warning message about custom item pool when it is fine
|
||||
* 1.2.0.17u
|
||||
* Fixed logic bug that allowed Pearl to be behind Graveyard Cave or King's Tomb entrances with only Mirror and West Dark World access (cross world shuffles only)
|
||||
* Removed backup locations for Dungeon Only and Major Only algorithms. If item cannot be placed in the appropriate location, the seed will fail to generate instead
|
||||
* Fix for Non-ER Inverted Experimental (Aga and GT weren't logically swapped)
|
||||
* Fix for customizer setting crystals to 0 for either GT/Ganon
|
||||
* 1.2.0.16u
|
||||
* Fix for partial key logic on vanilla Mire
|
||||
* Fix for Kholdstare Shell collision when at Lanmo 2
|
||||
* Fix for Mire Attic Hint door (direction was swapped)
|
||||
* Dungeon at Chest Game displays correctly on OW map option
|
||||
* 1.2.0.15u
|
||||
* GUI reorganization
|
||||
* Logic fix for pots in GT conveyor cross
|
||||
* Auto option for pyramid open (trinity or ER + crystals goal)
|
||||
* World model refactor (combining inverted and normal world models)
|
||||
* Partitioned fix for lamp logic and links house
|
||||
@@ -118,6 +145,7 @@ These are now independent of retro mode and have three options: None, Random, an
|
||||
* Reduced universal keys in pool slightly for non-vanilla dungeons
|
||||
* Fake world fix finally
|
||||
* Some extra restrictions on links house placement for lite/lean
|
||||
* Collection_rate works in customizer files
|
||||
* 1.2.0.14u
|
||||
* Small fix for key logic validation (got rid of a false negative)
|
||||
* Customized doors in ice cross work properly now
|
||||
|
||||
10
Regions.py
10
Regions.py
@@ -87,7 +87,8 @@ def create_regions(world, player):
|
||||
create_dw_region(player, 'West Dark World', ['Frog'], ['Dark Lumberjack Shop', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Chest Game', 'Thieves Town',
|
||||
'C-Shaped House', 'Brewery', 'Red Shield Shop', 'Skull Woods Forest', 'Bumper Cave Entrance Rock',
|
||||
'West Dark World Water Drop', 'Grassy Lawn Pegs (Bottom)', 'Peg Area Rocks (Left)', 'Village of Outcasts Drop',
|
||||
'West Dark World Teleporter', 'WDW Flute']),
|
||||
'West Dark World Teleporter', 'WDW Flute', 'Dark Graveyard Bush (South)']),
|
||||
create_dw_region(player, 'Dark Graveyard North', None, ['Dark Graveyard Bush (North)']),
|
||||
create_dw_region(player, 'Skull Woods Forest', None, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)',
|
||||
'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']),
|
||||
create_dw_region(player, 'Skull Woods Forest (West)', None, ['Skull Woods Second Section Hole', 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section'], 'a deep, dark forest'),
|
||||
@@ -697,8 +698,8 @@ def create_dungeon_regions(world, player):
|
||||
create_dungeon_region(player, 'Mire BK Chest Ledge', 'Misery Mire', ['Misery Mire - Big Key Chest'], ['Mire BK Chest Ledge WS']),
|
||||
create_dungeon_region(player, 'Mire Warping Pool', 'Misery Mire', None, ['Mire Warping Pool ES', 'Mire Warping Pool Warp']),
|
||||
create_dungeon_region(player, 'Mire Torches Top', 'Misery Mire', None, ['Mire Torches Top Down Stairs', 'Mire Torches Top SW', 'Mire Torches Top Holes']),
|
||||
create_dungeon_region(player, 'Mire Torches Bottom', 'Misery Mire', None, ['Mire Torches Bottom NW', 'Mire Torches Bottom WS', 'Mire Torches Bottom Holes']),
|
||||
create_dungeon_region(player, 'Mire Attic Hint', 'Misery Mire', None, ['Mire Attic Hint ES', 'Mire Attic Hint Hole']),
|
||||
create_dungeon_region(player, 'Mire Torches Bottom', 'Misery Mire', None, ['Mire Torches Bottom NW', 'Mire Torches Bottom ES', 'Mire Torches Bottom Holes']),
|
||||
create_dungeon_region(player, 'Mire Attic Hint', 'Misery Mire', None, ['Mire Attic Hint WS', 'Mire Attic Hint Hole']),
|
||||
create_dungeon_region(player, 'Mire Dark Shooters', 'Misery Mire', None, ['Mire Dark Shooters Up Stairs', 'Mire Dark Shooters SW', 'Mire Dark Shooters SE']),
|
||||
create_dungeon_region(player, 'Mire Key Rupees', 'Misery Mire', None, ['Mire Key Rupees NE']),
|
||||
create_dungeon_region(player, 'Mire Block X', 'Misery Mire', None, ['Mire Block X NW', 'Mire Block X WS']),
|
||||
@@ -792,7 +793,8 @@ def create_dungeon_regions(world, player):
|
||||
['GT Compass Room EN', 'GT Compass Room Warp']),
|
||||
create_dungeon_region(player, 'GT Invisible Bridges', 'Ganon\'s Tower', None, ['GT Invisible Bridges WS']),
|
||||
create_dungeon_region(player, 'GT Invisible Catwalk', 'Ganon\'s Tower', None, ['GT Invisible Catwalk ES', 'GT Invisible Catwalk WS', 'GT Invisible Catwalk NW', 'GT Invisible Catwalk NE']),
|
||||
create_dungeon_region(player, 'GT Conveyor Cross', 'Ganon\'s Tower', ['Ganons Tower - Conveyor Cross Pot Key'], ['GT Conveyor Cross EN', 'GT Conveyor Cross WN']),
|
||||
create_dungeon_region(player, 'GT Conveyor Cross', 'Ganon\'s Tower', ['Ganons Tower - Conveyor Cross Pot Key'], ['GT Conveyor Cross EN', 'GT Conveyor Cross Hammer Path']),
|
||||
create_dungeon_region(player, 'GT Conveyor Cross Across Pits', 'Ganon\'s Tower', None, ['GT Conveyor Cross Hookshot Path', 'GT Conveyor Cross WN']),
|
||||
create_dungeon_region(player, 'GT Hookshot East Platform', 'Ganon\'s Tower', None, ['GT Hookshot EN', 'GT Hookshot East-Mid Path']),
|
||||
create_dungeon_region(player, 'GT Hookshot Mid Platform', 'Ganon\'s Tower', None, ['GT Hookshot Mid-East Path', 'GT Hookshot Mid-South Path', 'GT Hookshot Mid-North Path']),
|
||||
create_dungeon_region(player, 'GT Hookshot North Platform', 'Ganon\'s Tower', None, ['GT Hookshot NW', 'GT Hookshot North-Mid Path']),
|
||||
|
||||
2
Rom.py
2
Rom.py
@@ -40,7 +40,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings
|
||||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = '563fe28515c5dd9f64270ca475d1c2d3'
|
||||
RANDOMIZERBASEHASH = 'e5f3f7dd5be54dce0f490529803dafc3'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
|
||||
47
Rules.py
47
Rules.py
@@ -209,7 +209,6 @@ def global_rules(world, player):
|
||||
set_rule(world.get_location('Purple Chest', player), lambda state: state.has('Pick Up Purple Chest', player)) # Can S&Q with chest
|
||||
|
||||
# underworld rules
|
||||
set_rule(world.get_entrance('Old Man Cave Exit (West)', player), lambda state: False) # drop cannot be climbed up
|
||||
set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player))
|
||||
set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: state.has_Mirror(player)) # can erase block - overridden in noglitches
|
||||
set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player) and state.can_reach('Potion Shop Area', 'Region', player))
|
||||
@@ -479,8 +478,8 @@ def global_rules(world, player):
|
||||
|
||||
set_rule(world.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has_Boots(player))
|
||||
set_rule(world.get_entrance('GT Hope Room EN', player), lambda state: state.has('Cane of Somaria', player))
|
||||
set_rule(world.get_entrance('GT Conveyor Cross WN', player), lambda state: state.has('Hammer', player))
|
||||
set_rule(world.get_entrance('GT Conveyor Cross EN', player), lambda state: state.has('Hookshot', player))
|
||||
set_rule(world.get_entrance('GT Conveyor Cross Hammer Path', player), lambda state: state.has('Hammer', player))
|
||||
set_rule(world.get_entrance('GT Conveyor Cross Hookshot Path', player), lambda state: state.has('Hookshot', player))
|
||||
if not world.get_door('GT Speed Torch SE', player).entranceFlag:
|
||||
set_rule(world.get_entrance('GT Speed Torch SE', player), lambda state: state.has('Fire Rod', player))
|
||||
set_rule(world.get_entrance('GT Hookshot South-Mid Path', player), lambda state: state.has('Hookshot', player))
|
||||
@@ -887,7 +886,7 @@ def ow_inverted_rules(world, player):
|
||||
set_rule(world.get_entrance('Hyrule Castle Main Gate', player), lambda state: state.has_Mirror(player))
|
||||
set_rule(world.get_entrance('Hyrule Castle Main Gate (North)', player), lambda state: state.has_Mirror(player))
|
||||
set_rule(world.get_location('Frog', player), lambda state: state.can_lift_heavy_rocks(player) and state.has_Pearl(player))
|
||||
set_rule(world.get_entrance('Pyramid Hole', player), lambda state: world.open_pyramid[player] or world.goal[player] == 'trinity' or state.has('Beat Agahnim 2', player))
|
||||
set_rule(world.get_entrance('Pyramid Hole', player), lambda state: world.is_pyramid_open(player) or state.has('Beat Agahnim 2', player))
|
||||
else:
|
||||
set_rule(world.get_entrance('East Dark Death Mountain Teleporter (Top)', player), lambda state: state.can_lift_heavy_rocks(player) and state.has('Hammer', player) and state.has_Pearl(player)) # bunny cannot use hammer
|
||||
set_rule(world.get_entrance('East Dark Death Mountain Teleporter (Bottom)', player), lambda state: state.can_lift_heavy_rocks(player))
|
||||
@@ -965,6 +964,8 @@ def ow_bunny_rules(world, player):
|
||||
|
||||
add_bunny_rule(world.get_entrance('East Dark Death Mountain Bushes', player), player)
|
||||
add_bunny_rule(world.get_entrance('Bumper Cave Entrance Rock', player), player)
|
||||
add_bunny_rule(world.get_entrance('Dark Graveyard Bush (South)', player), player)
|
||||
add_bunny_rule(world.get_entrance('Dark Graveyard Bush (North)', player), player)
|
||||
add_bunny_rule(world.get_entrance('Dark Witch Rock (North)', player), player)
|
||||
add_bunny_rule(world.get_entrance('Dark Witch Rock (South)', player), player)
|
||||
add_bunny_rule(world.get_entrance('Grassy Lawn Pegs (Bottom)', player), player)
|
||||
@@ -2056,7 +2057,9 @@ def add_key_logic_rules(world, player):
|
||||
key_logic = world.key_logic[player]
|
||||
eval_func = eval_small_key_door
|
||||
if world.key_logic_algorithm[player] == 'strict' and world.keyshuffle[player] == 'wild':
|
||||
eval_func = eval_small_key_door_strict
|
||||
eval_func = eval_small_key_door_strict
|
||||
elif world.key_logic_algorithm[player] != 'default':
|
||||
eval_func = eval_small_key_door_partial
|
||||
for d_name, d_logic in key_logic.items():
|
||||
for door_name, rule in d_logic.door_rules.items():
|
||||
door_entrance = world.get_entrance(door_name, player)
|
||||
@@ -2112,6 +2115,36 @@ def eval_small_key_door_main(state, door_name, dungeon, player):
|
||||
return door_openable
|
||||
|
||||
|
||||
def eval_small_key_door_partial_main(state, door_name, dungeon, player):
|
||||
if state.is_door_open(door_name, player):
|
||||
return True
|
||||
key_logic = state.world.key_logic[player][dungeon]
|
||||
if door_name not in key_logic.door_rules:
|
||||
return False
|
||||
door_rule = key_logic.door_rules[door_name]
|
||||
door_openable = False
|
||||
for ruleType, number in door_rule.new_rules.items():
|
||||
if door_openable:
|
||||
return True
|
||||
if ruleType == KeyRuleType.WorstCase:
|
||||
number = min(number, door_rule.small_key_num)
|
||||
door_openable |= state.has_sm_key(key_logic.small_key_name, player, number)
|
||||
elif ruleType == KeyRuleType.AllowSmall:
|
||||
small_loc_item = door_rule.small_location.item
|
||||
if small_loc_item and small_loc_item.name == key_logic.small_key_name and small_loc_item.player == player:
|
||||
door_openable |= state.has_sm_key(key_logic.small_key_name, player, number)
|
||||
elif isinstance(ruleType, tuple):
|
||||
lock, lock_item = ruleType
|
||||
# this doesn't track logical locks yet, i.e. hammer locks the item and hammer is there, but the item isn't
|
||||
for loc in door_rule.alternate_big_key_loc:
|
||||
spot = state.world.get_location(loc, player)
|
||||
if spot.item and spot.item.name == lock_item:
|
||||
number = min(number, door_rule.alternate_small_key)
|
||||
door_openable |= state.has_sm_key(key_logic.small_key_name, player, number)
|
||||
break
|
||||
return door_openable
|
||||
|
||||
|
||||
def eval_small_key_door_strict_main(state, door_name, dungeon, player):
|
||||
if state.is_door_open(door_name, player):
|
||||
return True
|
||||
@@ -2126,6 +2159,10 @@ def eval_small_key_door(door_name, dungeon, player):
|
||||
return lambda state: eval_small_key_door_main(state, door_name, dungeon, player)
|
||||
|
||||
|
||||
def eval_small_key_door_partial(door_name, dungeon, player):
|
||||
return lambda state: eval_small_key_door_partial_main(state, door_name, dungeon, player)
|
||||
|
||||
|
||||
def eval_small_key_door_strict(door_name, dungeon, player):
|
||||
return lambda state: eval_small_key_door_strict_main(state, door_name, dungeon, player)
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
meta:
|
||||
algorithm: balanced
|
||||
players: 1
|
||||
seed: 42
|
||||
seed: 41 # note to self: seed 42 had an interesting Swamp Palace problem
|
||||
names: Lonk
|
||||
settings:
|
||||
1:
|
||||
@@ -56,6 +56,9 @@ item_pool:
|
||||
Sanctuary Heart Container: 3
|
||||
Shovel: 3
|
||||
Single Arrow: 1
|
||||
Green Potion: 1
|
||||
Blue Potion: 1
|
||||
Red Potion: 1
|
||||
placements:
|
||||
1:
|
||||
Palace of Darkness - Big Chest: Hammer
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
triforce_pool_min: 20
|
||||
triforce_pool_max: 40
|
||||
triforce_min_difference: 10
|
||||
triforce_max_difference: 15
|
||||
dungeon_items:
|
||||
standard: 10
|
||||
mc: 3
|
||||
|
||||
@@ -84,6 +84,7 @@ triforce_goal_max: 30
|
||||
triforce_pool_min: 30
|
||||
triforce_pool_max: 40
|
||||
triforce_min_difference: 10
|
||||
triforce_max_difference: 12
|
||||
map_shuffle:
|
||||
on: 1
|
||||
off: 1
|
||||
|
||||
@@ -342,6 +342,7 @@
|
||||
"triforce_goal_min": {},
|
||||
"triforce_goal_max": {},
|
||||
"triforce_min_difference": {},
|
||||
"triforce_max_difference": {},
|
||||
"custom": {
|
||||
"type": "bool",
|
||||
"help": "suppress"
|
||||
|
||||
@@ -228,6 +228,7 @@
|
||||
"randomizer.item.worldstate.open": "Open",
|
||||
"randomizer.item.worldstate.inverted": "Inverted",
|
||||
"randomizer.item.worldstate.retro": "Retro",
|
||||
"randomizer.item.retro": "Enable Retro",
|
||||
|
||||
"randomizer.item.logiclevel": "Logic Level",
|
||||
"randomizer.item.logiclevel.noglitches": "No Glitches",
|
||||
@@ -242,7 +243,7 @@
|
||||
"randomizer.item.goal.triforcehunt": "Triforce Hunt",
|
||||
"randomizer.item.goal.trinity": "Trinity",
|
||||
"randomizer.item.goal.crystals": "Crystals",
|
||||
"randomizer.item.goal.ganonhunt": "Triforce Hunt + Ganon",
|
||||
"randomizer.item.goal.ganonhunt": "Ganonhunt",
|
||||
"randomizer.item.goal.completionist": "Completionist",
|
||||
|
||||
"randomizer.item.crystals_gt": "Crystals to open GT",
|
||||
@@ -325,7 +326,7 @@
|
||||
"randomizer.item.dropshuffle.none": "None",
|
||||
"randomizer.item.dropshuffle.keys": "Small Key Enemies",
|
||||
"randomizer.item.dropshuffle.underworld": "Underworld Enemies",
|
||||
"randomizer.item.keydropshuffle": "Key Drop Shuffle (Legacy)",
|
||||
"randomizer.item.keydropshuffle": "Enable Key Drop Shuffle (Legacy)",
|
||||
|
||||
"randomizer.item.take_any": "Take Any Caves",
|
||||
"randomizer.item.take_any.none": "None",
|
||||
|
||||
@@ -11,12 +11,8 @@
|
||||
"options": [
|
||||
"standard",
|
||||
"open",
|
||||
"inverted",
|
||||
"retro"
|
||||
],
|
||||
"config": {
|
||||
"command": "worldstate"
|
||||
}
|
||||
"inverted"
|
||||
]
|
||||
},
|
||||
"logiclevel": {
|
||||
"type": "selectbox",
|
||||
@@ -65,6 +61,12 @@
|
||||
}
|
||||
},
|
||||
"rightItemFrame": {
|
||||
"retro": {
|
||||
"type": "button",
|
||||
"config": {
|
||||
"command": "retro"
|
||||
}
|
||||
},
|
||||
"sortingalgo": {
|
||||
"type": "selectbox",
|
||||
"default": "balanced",
|
||||
@@ -140,13 +142,19 @@
|
||||
"colorizepots": {
|
||||
"type": "checkbox",
|
||||
"config": {
|
||||
"padx": [50,0]
|
||||
"padx": [
|
||||
50,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"potshuffle": {
|
||||
"type": "checkbox",
|
||||
"config": {
|
||||
"padx": [50,0]
|
||||
"padx": [
|
||||
50,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"dropshuffle": {
|
||||
@@ -156,7 +164,9 @@
|
||||
"none",
|
||||
"keys",
|
||||
"underworld"
|
||||
]},
|
||||
]}
|
||||
},
|
||||
"leftPoolFrame2": {
|
||||
"keydropshuffle": {
|
||||
"type": "checkbox",
|
||||
"config": {
|
||||
|
||||
@@ -2,6 +2,7 @@ import os
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import yaml
|
||||
from typing import Any
|
||||
from yaml.representer import Representer
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
@@ -46,8 +47,8 @@ class CustomSettings(object):
|
||||
return meta['players']
|
||||
|
||||
def adjust_args(self, args):
|
||||
def get_setting(value, default):
|
||||
if value:
|
||||
def get_setting(value: Any, default):
|
||||
if value or value == 0:
|
||||
if isinstance(value, dict):
|
||||
return random.choices(list(value.keys()), list(value.values()), k=1)[0]
|
||||
else:
|
||||
@@ -117,6 +118,7 @@ class CustomSettings(object):
|
||||
args.crystals_gt[p] = get_setting(settings['crystals_gt'], args.crystals_gt[p])
|
||||
args.crystals_ganon[p] = get_setting(settings['crystals_ganon'], args.crystals_ganon[p])
|
||||
args.experimental[p] = get_setting(settings['experimental'], args.experimental[p])
|
||||
args.collection_rate[p] = get_setting(settings['collection_rate'], args.collection_rate[p])
|
||||
args.openpyramid[p] = get_setting(settings['openpyramid'], args.openpyramid[p])
|
||||
args.bigkeyshuffle[p] = get_setting(settings['bigkeyshuffle'], args.bigkeyshuffle[p])
|
||||
args.keyshuffle[p] = get_setting(settings['keyshuffle'], args.keyshuffle[p])
|
||||
@@ -144,6 +146,12 @@ class CustomSettings(object):
|
||||
args.pseudoboots[p] = get_setting(settings['pseudoboots'], args.pseudoboots[p])
|
||||
args.triforce_goal[p] = get_setting(settings['triforce_goal'], args.triforce_goal[p])
|
||||
args.triforce_pool[p] = get_setting(settings['triforce_pool'], args.triforce_pool[p])
|
||||
args.triforce_goal_min[p] = get_setting(settings['triforce_goal_min'], args.triforce_goal_min[p])
|
||||
args.triforce_goal_max[p] = get_setting(settings['triforce_goal_max'], args.triforce_goal_max[p])
|
||||
args.triforce_pool_min[p] = get_setting(settings['triforce_pool_min'], args.triforce_pool_min[p])
|
||||
args.triforce_pool_max[p] = get_setting(settings['triforce_pool_max'], args.triforce_pool_max[p])
|
||||
args.triforce_min_difference[p] = get_setting(settings['triforce_min_difference'], args.triforce_min_difference[p])
|
||||
args.triforce_max_difference[p] = get_setting(settings['triforce_max_difference'], args.triforce_max_difference[p])
|
||||
args.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p])
|
||||
|
||||
# mystery usage
|
||||
@@ -251,6 +259,7 @@ class CustomSettings(object):
|
||||
settings_dict[p]['crystals_gt'] = world.crystals_gt_orig[p]
|
||||
settings_dict[p]['crystals_ganon'] = world.crystals_ganon_orig[p]
|
||||
settings_dict[p]['experimental'] = world.experimental[p]
|
||||
settings_dict[p]['collection_rate'] = world.collection_rate[p]
|
||||
settings_dict[p]['openpyramid'] = world.open_pyramid[p]
|
||||
settings_dict[p]['bigkeyshuffle'] = world.bigkeyshuffle[p]
|
||||
settings_dict[p]['keyshuffle'] = world.keyshuffle[p]
|
||||
|
||||
@@ -66,6 +66,7 @@ SETTINGSTOPROCESS = {
|
||||
"crystals_ganon": "crystals_ganon",
|
||||
"weapons": "swords",
|
||||
|
||||
"retro": "retro",
|
||||
"sortingalgo": "algorithm",
|
||||
"accessibility": "accessibility",
|
||||
"restrict_boss_items": "restrict_boss_items",
|
||||
|
||||
@@ -214,7 +214,8 @@ def create_guiargs(parent):
|
||||
arg = options[mainpage][subpage][widget] if subpage != "" else options[mainpage][widget]
|
||||
page = parent.pages[mainpage].pages[subpage] if subpage != "" else parent.pages[mainpage]
|
||||
pagewidgets = page.content.customWidgets if mainpage == "custom" else page.content.startingWidgets if mainpage == "startinventory" else page.widgets
|
||||
setattr(guiargs, arg, pagewidgets[widget].storageVar.get())
|
||||
if hasattr(pagewidgets[widget], 'storageVar'):
|
||||
setattr(guiargs, arg, pagewidgets[widget].storageVar.get())
|
||||
|
||||
# Get Multiworld Worlds count
|
||||
guiargs.multi = int(parent.pages["bottom"].pages["content"].widgets["worlds"].storageVar.get())
|
||||
@@ -282,17 +283,4 @@ def create_guiargs(parent):
|
||||
|
||||
guiargs = update_deprecated_args(guiargs)
|
||||
|
||||
# Key drop shuffle stuff
|
||||
if guiargs.keydropshuffle:
|
||||
guiargs.dropshuffle = 'keys' if guiargs.dropshuffle == 'none' else guiargs.dropshuffle
|
||||
guiargs.pottery = 'keys' if guiargs.pottery == 'none' else guiargs.pottery
|
||||
|
||||
if (hasattr(guiargs, 'retro') and guiargs.retro) or guiargs.mode == 'retro':
|
||||
if guiargs.bow_mode == 'progressive':
|
||||
guiargs.bow_mode = 'retro'
|
||||
elif guiargs.bow_mode == 'silvers':
|
||||
guiargs.bow_mode = 'retro_silvers'
|
||||
guiargs.take_any = 'random' if guiargs.take_any == 'none' else guiargs.take_any
|
||||
guiargs.keyshuffle = 'universal'
|
||||
|
||||
return guiargs
|
||||
|
||||
@@ -57,7 +57,10 @@ def loadcliargs(gui, args, settings=None):
|
||||
pagewidgets[widget].selectbox.options = theseOptions
|
||||
elif thisType == "spinbox":
|
||||
pagewidgets[widget].label.configure(text=label)
|
||||
pagewidgets[widget].storageVar.set(args[arg])
|
||||
elif thisType == 'button':
|
||||
pagewidgets[widget].button.configure(text=label)
|
||||
if hasattr(pagewidgets[widget], 'storageVar'):
|
||||
pagewidgets[widget].storageVar.set(args[arg])
|
||||
# If we're on the Game Options page and it's not about Hints
|
||||
if subpage == "gameoptions" and widget not in ["hints", "collection_rate"]:
|
||||
# Check if we've got settings
|
||||
|
||||
@@ -43,7 +43,10 @@ def item_page(parent):
|
||||
self.frames["leftPoolHeader"].pack(side=TOP, anchor=W)
|
||||
|
||||
self.frames["leftPoolFrame"] = Frame(self.frames["leftPoolContainer"])
|
||||
self.frames["leftPoolFrame"].pack(side=LEFT, fill=Y)
|
||||
self.frames["leftPoolFrame"].pack(side=TOP, fill=Y)
|
||||
|
||||
self.frames["leftPoolFrame2"] = Frame(self.frames["leftPoolContainer"])
|
||||
self.frames["leftPoolFrame2"].pack(side=LEFT, fill=Y)
|
||||
|
||||
self.frames["rightPoolFrame"] = Frame(self.frames["poolFrame"])
|
||||
self.frames["rightPoolFrame"].pack(side=RIGHT)
|
||||
@@ -62,14 +65,16 @@ def item_page(parent):
|
||||
for key in dictWidgets:
|
||||
self.widgets[key] = dictWidgets[key]
|
||||
packAttrs = {"anchor":E}
|
||||
if self.widgets[key].type == "checkbox" or framename == "leftPoolFrame":
|
||||
if key == "retro":
|
||||
packAttrs["side"] = RIGHT
|
||||
if self.widgets[key].type == "checkbox" or framename.startswith("leftPoolFrame"):
|
||||
packAttrs["anchor"] = W
|
||||
if framename == "checkboxes":
|
||||
packAttrs["side"] = LEFT
|
||||
packAttrs["padx"] = (10,0)
|
||||
packAttrs["padx"] = (10, 0)
|
||||
elif framename == "leftPoolHeader":
|
||||
packAttrs["side"] = LEFT
|
||||
packAttrs["padx"] = (0,20)
|
||||
packAttrs["padx"] = (0, 20)
|
||||
packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key])
|
||||
self.widgets[key].pack(packAttrs)
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
|
||||
from tkinter import messagebox, Checkbutton, Entry, Frame, IntVar, Label, OptionMenu, Spinbox, StringVar, LEFT, RIGHT, X
|
||||
from tkinter import Button
|
||||
from source.classes.Empty import Empty
|
||||
|
||||
# Override Spinbox to include mousewheel support for changing value
|
||||
@@ -172,6 +174,22 @@ def make_textbox(self, parent, label, storageVar, manager, managerAttrs):
|
||||
widget.textbox.pack(managerAttrs["textbox"] if managerAttrs is not None and "textbox" in managerAttrs else None)
|
||||
return widget
|
||||
|
||||
|
||||
def make_button(self, parent, label, manager, managerAttrs, config):
|
||||
self = Frame(parent)
|
||||
if config and "command" in config:
|
||||
self.command = config["command"]
|
||||
else:
|
||||
self.command = lambda: None
|
||||
|
||||
self.button = Button(parent, text=label, command=lambda: widget_command(self, self.command))
|
||||
if managerAttrs is not None:
|
||||
self.button.pack(managerAttrs)
|
||||
else:
|
||||
self.button.pack(anchor='w')
|
||||
return self
|
||||
|
||||
|
||||
# Make a generic widget
|
||||
def make_widget(self, type, parent, label, storageVar=None, manager=None, managerAttrs=dict(),
|
||||
options=None, config=None):
|
||||
@@ -201,6 +219,8 @@ def make_widget(self, type, parent, label, storageVar=None, manager=None, manage
|
||||
if thisStorageVar is None:
|
||||
thisStorageVar = StringVar()
|
||||
widget = make_textbox(self, parent, label, thisStorageVar, manager, managerAttrs)
|
||||
elif type == 'button':
|
||||
widget = make_button(self, parent, label, manager, managerAttrs, config)
|
||||
widget.type = type
|
||||
return widget
|
||||
|
||||
@@ -243,47 +263,36 @@ def add_padding_from_config(packAttrs, defn):
|
||||
def widget_command(widget, command=""):
|
||||
root = widget.winfo_toplevel()
|
||||
text_output = ""
|
||||
if command == "worldstate":
|
||||
if widget.storageVar.get() == 'retro':
|
||||
temp_widget = root.pages["randomizer"].pages["dungeon"].widgets["smallkeyshuffle"]
|
||||
text_output += f'\n {temp_widget.label.cget("text")}'
|
||||
temp_widget.storageVar.set('universal')
|
||||
if command == "retro":
|
||||
temp_widget = root.pages["randomizer"].pages["dungeon"].widgets["smallkeyshuffle"]
|
||||
text_output += f'\n {temp_widget.label.cget("text")}'
|
||||
temp_widget.storageVar.set('universal')
|
||||
|
||||
temp_widget = root.pages["randomizer"].pages["item"].widgets["bow_mode"]
|
||||
text_output += f'\n {temp_widget.label.cget("text")}'
|
||||
if temp_widget.storageVar.get() == 'progressive':
|
||||
temp_widget.storageVar.set('retro')
|
||||
elif temp_widget.storageVar.get() == 'silvers':
|
||||
temp_widget.storageVar.set('retro_silvers')
|
||||
temp_widget = root.pages["randomizer"].pages["item"].widgets["bow_mode"]
|
||||
text_output += f'\n {temp_widget.label.cget("text")}'
|
||||
if temp_widget.storageVar.get() == 'progressive':
|
||||
temp_widget.storageVar.set('retro')
|
||||
elif temp_widget.storageVar.get() == 'silvers':
|
||||
temp_widget.storageVar.set('retro_silvers')
|
||||
|
||||
temp_widget = root.pages["randomizer"].pages["item"].widgets["take_any"]
|
||||
text_output += f'\n {temp_widget.label.cget("text")}'
|
||||
if temp_widget.storageVar.get() == 'none':
|
||||
temp_widget.storageVar.set('random')
|
||||
temp_widget = root.pages["randomizer"].pages["item"].widgets["take_any"]
|
||||
text_output += f'\n {temp_widget.label.cget("text")}'
|
||||
if temp_widget.storageVar.get() == 'none':
|
||||
temp_widget.storageVar.set('random')
|
||||
|
||||
widget.storageVar.set('open')
|
||||
messagebox.showinfo('', f'The following settings were changed:{text_output}')
|
||||
messagebox.showinfo('', f'The following settings were changed:{text_output}')
|
||||
elif command == "keydropshuffle":
|
||||
if widget.storageVar.get() > 0:
|
||||
temp_widget = root.pages["randomizer"].pages["item"].widgets["pottery"]
|
||||
if temp_widget.storageVar.get() == 'none':
|
||||
text_output += f'\n {temp_widget.label.cget("text")}'
|
||||
temp_widget.storageVar.set('keys')
|
||||
temp_widget = root.pages["randomizer"].pages["item"].widgets["pottery"]
|
||||
text_output += f'\n {temp_widget.label.cget("text")}'
|
||||
if temp_widget.storageVar.get() == 'none':
|
||||
|
||||
temp_widget.storageVar.set('keys')
|
||||
|
||||
temp_widget = root.pages["randomizer"].pages["item"].widgets["dropshuffle"]
|
||||
text_output += f'\n {temp_widget.label.cget("text")}'
|
||||
if temp_widget.storageVar.get() == 'none':
|
||||
temp_widget.storageVar.set('keys')
|
||||
|
||||
if text_output:
|
||||
messagebox.showinfo('', f'The following settings were changed:{text_output}')
|
||||
|
||||
temp_widget = root.pages["randomizer"].pages["item"].widgets["dropshuffle"]
|
||||
if temp_widget.storageVar.get() == 'none':
|
||||
temp_widget.storageVar.set('keys')
|
||||
text_output += f'\n {temp_widget.label.cget("text")}'
|
||||
if text_output:
|
||||
messagebox.showinfo('', f'The following settings were changed:{text_output}')
|
||||
else:
|
||||
temp_widget = root.pages["randomizer"].pages["item"].widgets["pottery"]
|
||||
if temp_widget.storageVar.get() == 'keys':
|
||||
text_output += f'\n {temp_widget.label.cget("text")}'
|
||||
temp_widget.storageVar.set('none')
|
||||
temp_widget = root.pages["randomizer"].pages["item"].widgets["dropshuffle"]
|
||||
if temp_widget.storageVar.get() == 'keys':
|
||||
temp_widget.storageVar.set('none')
|
||||
text_output += f'\n {temp_widget.label.cget("text")}'
|
||||
if text_output:
|
||||
messagebox.showinfo('', f'The following settings were changed:{text_output}')
|
||||
|
||||
@@ -151,9 +151,6 @@ def create_item_pool_config(world):
|
||||
config.item_pool[player] = determine_major_items(world, player)
|
||||
config.location_groups[0].locations = set(groups.locations)
|
||||
config.reserved_locations[player].update(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_only':
|
||||
config.location_groups = [
|
||||
LocationGroup('Dungeons'),
|
||||
@@ -171,9 +168,6 @@ def create_item_pool_config(world):
|
||||
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)
|
||||
|
||||
|
||||
def district_item_pool_config(world):
|
||||
@@ -419,11 +413,7 @@ def filter_locations(item_to_place, locations, world, vanilla_skip=False, potion
|
||||
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
|
||||
return filtered
|
||||
if world.algorithm == 'district':
|
||||
config = world.item_pool_config
|
||||
if ((isinstance(item_to_place,str) and item_to_place == 'Placeholder')
|
||||
|
||||
@@ -56,6 +56,8 @@ def link_entrances_new(world, player):
|
||||
one_way_map.update(drop_map)
|
||||
one_way_map.update(single_entrance_map)
|
||||
if avail_pool.inverted:
|
||||
default_map['Ganons Tower'] = 'Agahnims Tower Exit'
|
||||
default_map['Agahnims Tower'] = 'Ganons Tower Exit'
|
||||
default_map['Old Man Cave (West)'] = 'Bumper Cave Exit (Bottom)'
|
||||
default_map['Death Mountain Return Cave (West)'] = 'Bumper Cave Exit (Top)'
|
||||
default_map['Bumper Cave (Bottom)'] = 'Old Man Cave Exit (West)'
|
||||
@@ -64,8 +66,6 @@ def link_entrances_new(world, player):
|
||||
default_map['Old Man Cave (East)'] = 'Death Mountain Return Cave Exit (West)'
|
||||
one_way_map['Bumper Cave (Top)'] = 'Dark Death Mountain Healer Fairy'
|
||||
del default_map['Bumper Cave (Top)']
|
||||
del one_way_map['Big Bomb Shop']
|
||||
one_way_map['Inverted Big Bomb Shop'] = 'Inverted Big Bomb Shop'
|
||||
avail_pool.default_map = default_map
|
||||
avail_pool.one_way_map = one_way_map
|
||||
|
||||
@@ -1964,6 +1964,8 @@ mandatory_connections = [('Links House S&Q', 'Links House'),
|
||||
('Grassy Lawn Pegs (Top)', 'West Dark World'),
|
||||
('Grassy Lawn Pegs (Bottom)', 'Dark Grassy Lawn'),
|
||||
('West Dark World Gap', 'West Dark World'),
|
||||
('Dark Graveyard Bush (South)', 'Dark Graveyard North'),
|
||||
('Dark Graveyard Bush (North)', 'West Dark World'),
|
||||
('Broken Bridge Pass (Top)', 'East Dark World'),
|
||||
('Broken Bridge Pass (Bottom)', 'Northeast Dark World'),
|
||||
('Peg Area Rocks (Left)', 'Hammer Peg Area'),
|
||||
@@ -2746,7 +2748,7 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d),
|
||||
'Dark Lake Hylia Ledge Hint': (0xec0, 0xc00),
|
||||
'Hype Cave': (0x940, 0xc80),
|
||||
'Bonk Fairy (Dark)': (0x740, 0xa80),
|
||||
'Brewery': (0x170, 0x980), 'C-Shaped House': (0x310, 0x7a0), 'Chest Game': (0x800, 0x7a0),
|
||||
'Brewery': (0x170, 0x980), 'C-Shaped House': (0x310, 0x7a0), 'Chest Game': (0x080, 0x7a0),
|
||||
'Hammer Peg Cave': (0x4c0, 0x940),
|
||||
'Red Shield Shop': (0x500, 0x680),
|
||||
'Dark Sanctuary Hint': (0x720, 0x4a0),
|
||||
|
||||
@@ -126,15 +126,14 @@ def roll_settings(weights):
|
||||
|
||||
ret.crystals_ganon = get_choice('ganon_open')
|
||||
|
||||
from ItemList import set_default_triforce
|
||||
default_tf_goal, default_tf_pool = set_default_triforce(ret.goal, 0, 0)
|
||||
goal_min = get_choice_default('triforce_goal_min', default=default_tf_goal)
|
||||
goal_max = get_choice_default('triforce_goal_max', default=default_tf_goal)
|
||||
pool_min = get_choice_default('triforce_pool_min', default=default_tf_pool)
|
||||
pool_max = get_choice_default('triforce_pool_max', default=default_tf_pool)
|
||||
ret.triforce_goal = random.randint(int(goal_min), int(goal_max))
|
||||
min_diff = get_choice_default('triforce_min_difference', default=default_tf_pool-default_tf_goal)
|
||||
ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max))
|
||||
ret.triforce_pool = get_choice_default('triforce_pool', default=0)
|
||||
ret.triforce_goal = get_choice_default('triforce_goal', default=0)
|
||||
ret.triforce_pool_min = get_choice_default('triforce_pool_min', default=0)
|
||||
ret.triforce_pool_max = get_choice_default('triforce_pool_max', default=0)
|
||||
ret.triforce_goal_min = get_choice_default('triforce_goal_min', default=0)
|
||||
ret.triforce_goal_max = get_choice_default('triforce_goal_max', default=0)
|
||||
ret.triforce_min_difference = get_choice_default('triforce_min_difference', default=0)
|
||||
ret.triforce_max_difference = get_choice_default('triforce_max_difference', default=10000)
|
||||
|
||||
ret.mode = get_choice('world_state')
|
||||
if ret.mode == 'retro':
|
||||
|
||||
Reference in New Issue
Block a user