Merge branch 'OverworldShuffleDev' into OverworldShuffle
This commit is contained in:
+23
-13
@@ -96,6 +96,7 @@ class World(object):
|
|||||||
self.rooms = []
|
self.rooms = []
|
||||||
self._room_cache = {}
|
self._room_cache = {}
|
||||||
self.dungeon_layouts = {}
|
self.dungeon_layouts = {}
|
||||||
|
self.dungeon_pool = {}
|
||||||
self.inaccessible_regions = {}
|
self.inaccessible_regions = {}
|
||||||
self.enabled_entrances = {}
|
self.enabled_entrances = {}
|
||||||
self.key_logic = {}
|
self.key_logic = {}
|
||||||
@@ -164,6 +165,7 @@ class World(object):
|
|||||||
set_player_attr('colorizepots', True)
|
set_player_attr('colorizepots', True)
|
||||||
set_player_attr('pot_pool', {})
|
set_player_attr('pot_pool', {})
|
||||||
set_player_attr('decoupledoors', False)
|
set_player_attr('decoupledoors', False)
|
||||||
|
set_player_attr('door_self_loops', False)
|
||||||
set_player_attr('door_type_mode', 'original')
|
set_player_attr('door_type_mode', 'original')
|
||||||
set_player_attr('trap_door_mode', 'optional')
|
set_player_attr('trap_door_mode', 'optional')
|
||||||
set_player_attr('key_logic_algorithm', 'default')
|
set_player_attr('key_logic_algorithm', 'default')
|
||||||
@@ -347,7 +349,7 @@ class World(object):
|
|||||||
else:
|
else:
|
||||||
if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
|
if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
|
||||||
return False
|
return False
|
||||||
elif self.goal[player] in ['crystals', 'trinity']:
|
elif self.goal[player] in ['crystals', 'trinity', 'ganonhunt']:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
@@ -2023,6 +2025,7 @@ class Door(object):
|
|||||||
self.dest = None
|
self.dest = None
|
||||||
self.blocked = False # Indicates if the door is normally blocked off as an exit. (Sanc door or always closed)
|
self.blocked = False # Indicates if the door is normally blocked off as an exit. (Sanc door or always closed)
|
||||||
self.blocked_orig = False
|
self.blocked_orig = False
|
||||||
|
self.trapped = False
|
||||||
self.stonewall = False # Indicate that the door cannot be enter until exited (Desert Torches, PoD Eye Statue)
|
self.stonewall = False # Indicate that the door cannot be enter until exited (Desert Torches, PoD Eye Statue)
|
||||||
self.smallKey = False # There's a small key door on this side
|
self.smallKey = False # There's a small key door on this side
|
||||||
self.bigKey = False # There's a big key door on this side
|
self.bigKey = False # There's a big key door on this side
|
||||||
@@ -2133,7 +2136,7 @@ class Door(object):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def no_exit(self):
|
def no_exit(self):
|
||||||
self.blocked = self.blocked_orig = True
|
self.blocked = self.blocked_orig = self.trapped = True
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def no_entrance(self):
|
def no_entrance(self):
|
||||||
@@ -2869,6 +2872,7 @@ class Spoiler(object):
|
|||||||
'trap_door_mode': self.world.trap_door_mode,
|
'trap_door_mode': self.world.trap_door_mode,
|
||||||
'key_logic': self.world.key_logic_algorithm,
|
'key_logic': self.world.key_logic_algorithm,
|
||||||
'decoupledoors': self.world.decoupledoors,
|
'decoupledoors': self.world.decoupledoors,
|
||||||
|
'door_self_loops': self.world.door_self_loops,
|
||||||
'dungeon_counters': self.world.dungeon_counters,
|
'dungeon_counters': self.world.dungeon_counters,
|
||||||
'item_pool': self.world.difficulty,
|
'item_pool': self.world.difficulty,
|
||||||
'item_functionality': self.world.difficulty_adjustments,
|
'item_functionality': self.world.difficulty_adjustments,
|
||||||
@@ -2898,6 +2902,7 @@ class Spoiler(object):
|
|||||||
'triforcegoal': self.world.treasure_hunt_count,
|
'triforcegoal': self.world.treasure_hunt_count,
|
||||||
'triforcepool': self.world.treasure_hunt_total,
|
'triforcepool': self.world.treasure_hunt_total,
|
||||||
'race': self.world.settings.world_rep['meta']['race'],
|
'race': self.world.settings.world_rep['meta']['race'],
|
||||||
|
'user_notes': self.world.settings.world_rep['meta']['user_notes'],
|
||||||
'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)},
|
'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)},
|
||||||
'seed': self.world.seed
|
'seed': self.world.seed
|
||||||
}
|
}
|
||||||
@@ -3047,6 +3052,8 @@ class Spoiler(object):
|
|||||||
outfile.write('ALttP Overworld Randomizer - Seed: %s\n\n' % (self.world.seed))
|
outfile.write('ALttP Overworld Randomizer - Seed: %s\n\n' % (self.world.seed))
|
||||||
for k,v in self.metadata["versions"].items():
|
for k,v in self.metadata["versions"].items():
|
||||||
outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v)
|
outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v)
|
||||||
|
if self.metadata['user_notes']:
|
||||||
|
outfile.write('User Notes:'.ljust(line_width) + '%s\n' % self.metadata['user_notes'])
|
||||||
outfile.write('Filling Algorithm:'.ljust(line_width) + '%s\n' % self.world.algorithm)
|
outfile.write('Filling Algorithm:'.ljust(line_width) + '%s\n' % self.world.algorithm)
|
||||||
outfile.write('Players:'.ljust(line_width) + '%d\n' % self.world.players)
|
outfile.write('Players:'.ljust(line_width) + '%d\n' % self.world.players)
|
||||||
outfile.write('Teams:'.ljust(line_width) + '%d\n' % self.world.teams)
|
outfile.write('Teams:'.ljust(line_width) + '%d\n' % self.world.teams)
|
||||||
@@ -3112,6 +3119,7 @@ class Spoiler(object):
|
|||||||
outfile.write('Door Type Mode:'.ljust(line_width) + '%s\n' % self.metadata['door_type_mode'][player])
|
outfile.write('Door Type Mode:'.ljust(line_width) + '%s\n' % self.metadata['door_type_mode'][player])
|
||||||
outfile.write('Trap Door Mode:'.ljust(line_width) + '%s\n' % self.metadata['trap_door_mode'][player])
|
outfile.write('Trap Door Mode:'.ljust(line_width) + '%s\n' % self.metadata['trap_door_mode'][player])
|
||||||
outfile.write('Decouple Doors:'.ljust(line_width) + '%s\n' % yn(self.metadata['decoupledoors'][player]))
|
outfile.write('Decouple Doors:'.ljust(line_width) + '%s\n' % yn(self.metadata['decoupledoors'][player]))
|
||||||
|
outfile.write('Spiral Stairs Self-Loop:'.ljust(line_width) + '%s\n' % yn(self.metadata['door_self_loops'][player]))
|
||||||
outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player]))
|
outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player]))
|
||||||
outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player])
|
outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player])
|
||||||
outfile.write('\n')
|
outfile.write('\n')
|
||||||
@@ -3393,7 +3401,7 @@ class Pot(object):
|
|||||||
|
|
||||||
|
|
||||||
# byte 0: DDDE EEEE (DR, ER)
|
# byte 0: DDDE EEEE (DR, ER)
|
||||||
dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0, "partitioned": 3}
|
dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0, "partitioned": 3, 'paired': 4}
|
||||||
er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, 'lite': 8,
|
er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, 'lite': 8,
|
||||||
'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10}
|
'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10}
|
||||||
|
|
||||||
@@ -3418,10 +3426,10 @@ mixed_travel_mode = {"prevent": 0, "allow": 1, "force": 2}
|
|||||||
pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7,
|
pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7,
|
||||||
'clustered': 8, 'nonempty': 9}
|
'clustered': 8, 'nonempty': 9}
|
||||||
|
|
||||||
# byte 5: CCCC CTTX (crystals gt, ctr2, experimental)
|
# byte 5: SCCC CTTX (self-loop doors, crystals gt, ctr2, experimental)
|
||||||
counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3}
|
counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3}
|
||||||
|
|
||||||
# byte 6: CCCC CPAA (crystals ganon, pyramid, access
|
# byte 6: ?CCC CPAA (crystals ganon, pyramid, access
|
||||||
access_mode = {"items": 0, "locations": 1, "none": 2}
|
access_mode = {"items": 0, "locations": 1, "none": 2}
|
||||||
|
|
||||||
# byte 7: B?MC DDEE (big, ?, maps, compass, door_type, enemies)
|
# byte 7: B?MC DDEE (big, ?, maps, compass, door_type, enemies)
|
||||||
@@ -3450,7 +3458,7 @@ flutespot_mode = {"vanilla": 0, "balanced": 1, "random": 2}
|
|||||||
|
|
||||||
# byte 13: FBBB TTSS (flute_mode, bow_mode, take_any, small_key_mode)
|
# byte 13: FBBB TTSS (flute_mode, bow_mode, take_any, small_key_mode)
|
||||||
flute_mode = {'normal': 0, 'active': 1}
|
flute_mode = {'normal': 0, 'active': 1}
|
||||||
keyshuffle_mode = {'none': 0, 'wild': 1, 'universal': 2} # reserved 8 modes?
|
keyshuffle_mode = {'none': 0, 'off': 0, 'wild': 1, 'on': 1, 'universal': 2} # reserved 8 modes?
|
||||||
take_any_mode = {'none': 0, 'random': 1, 'fixed': 2}
|
take_any_mode = {'none': 0, 'random': 1, 'fixed': 2}
|
||||||
bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3}
|
bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3}
|
||||||
|
|
||||||
@@ -3486,7 +3494,8 @@ class Settings(object):
|
|||||||
|
|
||||||
(0x80 if w.shuffletavern[p] else 0) | (0x10 if w.dropshuffle[p] else 0) | (pottery_mode[w.pottery[p]]),
|
(0x80 if w.shuffletavern[p] else 0) | (0x10 if w.dropshuffle[p] else 0) | (pottery_mode[w.pottery[p]]),
|
||||||
|
|
||||||
((8 if w.crystals_gt_orig[p] == "random" else int(w.crystals_gt_orig[p])) << 3)
|
(0x80 if w.door_self_loops[p] else 0)
|
||||||
|
| ((8 if w.crystals_gt_orig[p] == "random" else int(w.crystals_gt_orig[p])) << 3)
|
||||||
| (counter_mode[w.dungeon_counters[p]] << 1) | (1 if w.experimental[p] else 0),
|
| (counter_mode[w.dungeon_counters[p]] << 1) | (1 if w.experimental[p] else 0),
|
||||||
|
|
||||||
((8 if w.crystals_ganon_orig[p] == "random" else int(w.crystals_ganon_orig[p])) << 3)
|
((8 if w.crystals_ganon_orig[p] == "random" else int(w.crystals_ganon_orig[p])) << 3)
|
||||||
@@ -3512,8 +3521,8 @@ class Settings(object):
|
|||||||
(flute_mode[w.flute_mode[p]] << 7 | bow_mode[w.bow_mode[p]] << 4
|
(flute_mode[w.flute_mode[p]] << 7 | bow_mode[w.bow_mode[p]] << 4
|
||||||
| take_any_mode[w.take_any[p]] << 2 | keyshuffle_mode[w.keyshuffle[p]]),
|
| take_any_mode[w.take_any[p]] << 2 | keyshuffle_mode[w.keyshuffle[p]]),
|
||||||
|
|
||||||
((0x80 if w.pseudoboots[p] else 0) | overworld_map_mode[w.overworld_map[p]] << 6
|
((0x80 if w.pseudoboots[p] else 0) | overworld_map_mode[w.overworld_map[p]] << 5
|
||||||
| trap_door_mode[w.trap_door_mode[p]] << 4 | key_logic_algo[w.key_logic_algorithm[p]]),
|
| trap_door_mode[w.trap_door_mode[p]] << 3 | key_logic_algo[w.key_logic_algorithm[p]]),
|
||||||
])
|
])
|
||||||
return base64.b64encode(code, "+-".encode()).decode()
|
return base64.b64encode(code, "+-".encode()).decode()
|
||||||
|
|
||||||
@@ -3551,12 +3560,13 @@ class Settings(object):
|
|||||||
args.dropshuffle[p] = True if settings[4] & 0x10 else False
|
args.dropshuffle[p] = True if settings[4] & 0x10 else False
|
||||||
args.pottery[p] = r(pottery_mode)[settings[4] & 0x0F]
|
args.pottery[p] = r(pottery_mode)[settings[4] & 0x0F]
|
||||||
|
|
||||||
|
args.door_self_loops[p] = True if settings[5] & 0x80 else False
|
||||||
args.dungeon_counters[p] = r(counter_mode)[(settings[5] & 0x6) >> 1]
|
args.dungeon_counters[p] = r(counter_mode)[(settings[5] & 0x6) >> 1]
|
||||||
cgt = (settings[5] & 0xf8) >> 3
|
cgt = (settings[5] & 0x78) >> 3
|
||||||
args.crystals_gt[p] = "random" if cgt == 8 else cgt
|
args.crystals_gt[p] = "random" if cgt == 8 else cgt
|
||||||
args.experimental[p] = True if settings[5] & 0x1 else False
|
args.experimental[p] = True if settings[5] & 0x1 else False
|
||||||
|
|
||||||
cgan = (settings[6] & 0xf8) >> 3
|
cgan = (settings[6] & 0x78) >> 3
|
||||||
args.crystals_ganon[p] = "random" if cgan == 8 else cgan
|
args.crystals_ganon[p] = "random" if cgan == 8 else cgan
|
||||||
args.openpyramid[p] = True if settings[6] & 0x4 else False
|
args.openpyramid[p] = True if settings[6] & 0x4 else False
|
||||||
|
|
||||||
@@ -3596,8 +3606,8 @@ class Settings(object):
|
|||||||
|
|
||||||
if len(settings) > 14:
|
if len(settings) > 14:
|
||||||
args.pseudoboots[p] = True if settings[14] & 0x80 else False
|
args.pseudoboots[p] = True if settings[14] & 0x80 else False
|
||||||
args.overworld_map[p] = r(overworld_map_mode)[(settings[14] & 0x60) >> 6]
|
args.overworld_map[p] = r(overworld_map_mode)[(settings[14] & 0x60) >> 5]
|
||||||
args.trap_door_mode[p] = r(trap_door_mode)[(settings[14] & 0x14) >> 4]
|
args.trap_door_mode[p] = r(trap_door_mode)[(settings[14] & 0x18) >> 3]
|
||||||
args.key_logic_algorithm[p] = r(key_logic_algo)[settings[14] & 0x07]
|
args.key_logic_algorithm[p] = r(key_logic_algo)[settings[14] & 0x07]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.3.2.1
|
||||||
|
- \~Merged in DR v1.2.0.20~
|
||||||
|
- Some minor Swapped ER improvements
|
||||||
|
- Fixed generation error with Flute Activation
|
||||||
|
|
||||||
## 0.3.2.0
|
## 0.3.2.0
|
||||||
- New Swapped ER mode option
|
- New Swapped ER mode option
|
||||||
- Fixed issue with flipper rules not properly getting pearl requirement added
|
- Fixed issue with flipper rules not properly getting pearl requirement added
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ def parse_cli(argv, no_defaults=False):
|
|||||||
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
||||||
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
||||||
'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items',
|
'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items',
|
||||||
'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
|
'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', 'triforce_max_difference',
|
||||||
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern',
|
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern',
|
||||||
'pseudoboots', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
|
'pseudoboots', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
|
||||||
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||||
@@ -142,7 +142,7 @@ def parse_cli(argv, no_defaults=False):
|
|||||||
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
|
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
|
||||||
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx',
|
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx',
|
||||||
'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode',
|
'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode',
|
||||||
'bonk_drops', 'trap_door_mode', 'key_logic_algorithm']:
|
'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops']:
|
||||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||||
if player == 1:
|
if player == 1:
|
||||||
setattr(ret, name, {1: value})
|
setattr(ret, name, {1: value})
|
||||||
@@ -227,6 +227,7 @@ def parse_settings():
|
|||||||
"trap_door_mode": "optional",
|
"trap_door_mode": "optional",
|
||||||
"key_logic_algorithm": "default",
|
"key_logic_algorithm": "default",
|
||||||
"decoupledoors": False,
|
"decoupledoors": False,
|
||||||
|
"door_self_loops": False,
|
||||||
"experimental": False,
|
"experimental": False,
|
||||||
"dungeon_counters": "default",
|
"dungeon_counters": "default",
|
||||||
"mixed_travel": "prevent",
|
"mixed_travel": "prevent",
|
||||||
@@ -239,6 +240,7 @@ def parse_settings():
|
|||||||
"triforce_goal_min": 0,
|
"triforce_goal_min": 0,
|
||||||
"triforce_goal_max": 0,
|
"triforce_goal_max": 0,
|
||||||
"triforce_min_difference": 0,
|
"triforce_min_difference": 0,
|
||||||
|
"triforce_max_difference": 10000,
|
||||||
|
|
||||||
"code": "",
|
"code": "",
|
||||||
"multi": 1,
|
"multi": 1,
|
||||||
@@ -354,7 +356,8 @@ def parse_settings():
|
|||||||
"outputpath": os.path.join("."),
|
"outputpath": os.path.join("."),
|
||||||
"saveonexit": "ask",
|
"saveonexit": "ask",
|
||||||
"outputname": "",
|
"outputname": "",
|
||||||
"startinventoryarray": {}
|
"startinventoryarray": {},
|
||||||
|
"notes": ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if sys.platform.lower().find("windows"):
|
if sys.platform.lower().find("windows"):
|
||||||
|
|||||||
+74
-57
@@ -88,7 +88,8 @@ def link_doors_prep(world, player):
|
|||||||
|
|
||||||
find_inaccessible_regions(world, player)
|
find_inaccessible_regions(world, player)
|
||||||
|
|
||||||
if world.intensity[player] >= 3 and world.doorShuffle[player] != 'vanilla':
|
create_dungeon_pool(world, player)
|
||||||
|
if world.intensity[player] >= 3 and world.doorShuffle[player] != 'vanilla':
|
||||||
choose_portals(world, player)
|
choose_portals(world, player)
|
||||||
else:
|
else:
|
||||||
if world.shuffle[player] == 'vanilla':
|
if world.shuffle[player] == 'vanilla':
|
||||||
@@ -133,6 +134,20 @@ def create_dungeon_pool(world, player):
|
|||||||
pool = None
|
pool = None
|
||||||
if world.doorShuffle[player] == 'basic':
|
if world.doorShuffle[player] == 'basic':
|
||||||
pool = [([name], regions) for name, regions in dungeon_regions.items()]
|
pool = [([name], regions) for name, regions in dungeon_regions.items()]
|
||||||
|
elif world.doorShuffle[player] == 'paired':
|
||||||
|
dungeon_pool = list(dungeon_regions.keys())
|
||||||
|
groups = []
|
||||||
|
while dungeon_pool:
|
||||||
|
if len(dungeon_pool) == 3:
|
||||||
|
groups.append(list(dungeon_pool))
|
||||||
|
dungeon_pool.clear()
|
||||||
|
else:
|
||||||
|
choice_a = random.choice(dungeon_pool)
|
||||||
|
dungeon_pool.remove(choice_a)
|
||||||
|
choice_b = random.choice(dungeon_pool)
|
||||||
|
dungeon_pool.remove(choice_b)
|
||||||
|
groups.append([choice_a, choice_b])
|
||||||
|
pool = [(group, list(chain.from_iterable([dungeon_regions[d] for d in group]))) for group in groups]
|
||||||
elif world.doorShuffle[player] == 'partitioned':
|
elif world.doorShuffle[player] == 'partitioned':
|
||||||
groups = [['Hyrule Castle', 'Eastern Palace', 'Desert Palace', 'Tower of Hera', 'Agahnims Tower'],
|
groups = [['Hyrule Castle', 'Eastern Palace', 'Desert Palace', 'Tower of Hera', 'Agahnims Tower'],
|
||||||
['Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town'],
|
['Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town'],
|
||||||
@@ -143,38 +158,17 @@ def create_dungeon_pool(world, player):
|
|||||||
elif world.doorShuffle[player] != 'vanilla':
|
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])
|
||||||
return pool
|
world.dungeon_pool[player] = pool
|
||||||
|
|
||||||
|
|
||||||
def link_doors_main(world, player):
|
def link_doors_main(world, player):
|
||||||
pool = create_dungeon_pool(world, player)
|
pool = world.dungeon_pool[player]
|
||||||
if pool:
|
if pool:
|
||||||
main_dungeon_pool(pool, world, player)
|
main_dungeon_pool(pool, world, player)
|
||||||
if world.doorShuffle[player] != 'vanilla':
|
if world.doorShuffle[player] != 'vanilla':
|
||||||
create_door_spoiler(world, player)
|
create_door_spoiler(world, player)
|
||||||
|
|
||||||
|
|
||||||
# todo: I think this function is not necessary
|
|
||||||
def mark_regions(world, player):
|
|
||||||
# traverse dungeons and make sure dungeon property is assigned
|
|
||||||
player_dungeons = [dungeon for dungeon in world.dungeons if dungeon.player == player]
|
|
||||||
for dungeon in player_dungeons:
|
|
||||||
queue = deque(dungeon.regions)
|
|
||||||
while len(queue) > 0:
|
|
||||||
region = world.get_region(queue.popleft(), player)
|
|
||||||
if region.name not in dungeon.regions:
|
|
||||||
dungeon.regions.append(region.name)
|
|
||||||
region.dungeon = dungeon
|
|
||||||
for ext in region.exits:
|
|
||||||
d = world.check_for_door(ext.name, player)
|
|
||||||
connected = ext.connected_region
|
|
||||||
if d is not None and connected is not None:
|
|
||||||
if d.dest is not None and connected.name not in dungeon.regions and connected.type == RegionType.Dungeon and connected.name not in queue:
|
|
||||||
queue.append(connected) # needs to be added
|
|
||||||
elif connected is not None and connected.name not in dungeon.regions and connected.type == RegionType.Dungeon and connected.name not in queue:
|
|
||||||
queue.append(connected) # needs to be added
|
|
||||||
|
|
||||||
|
|
||||||
def create_door_spoiler(world, player):
|
def create_door_spoiler(world, player):
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
shuffled_door_types = [DoorType.Normal, DoorType.SpiralStairs]
|
shuffled_door_types = [DoorType.Normal, DoorType.SpiralStairs]
|
||||||
@@ -438,17 +432,7 @@ def pair_existing_key_doors(world, player, door_a, door_b):
|
|||||||
def choose_portals(world, player):
|
def choose_portals(world, player):
|
||||||
if world.doorShuffle[player] != ['vanilla']:
|
if world.doorShuffle[player] != ['vanilla']:
|
||||||
shuffle_flag = world.doorShuffle[player] != 'basic'
|
shuffle_flag = world.doorShuffle[player] != 'basic'
|
||||||
allowed = {}
|
allowed = {name: set(group[0]) for group in world.dungeon_pool[player] for name in group[0]}
|
||||||
if world.doorShuffle[player] == 'basic':
|
|
||||||
allowed = {name: {name} for name in dungeon_regions}
|
|
||||||
elif world.doorShuffle[player] == 'partitioned':
|
|
||||||
groups = [['Hyrule Castle', 'Eastern Palace', 'Desert Palace', 'Tower of Hera', 'Agahnims Tower'],
|
|
||||||
['Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town'],
|
|
||||||
['Ice Palace', 'Misery Mire', 'Turtle Rock', 'Ganons Tower']]
|
|
||||||
allowed = {name: set(group) for group in groups for name in group}
|
|
||||||
elif world.doorShuffle[player] == 'crossed':
|
|
||||||
all_dungeons = set(dungeon_regions.keys())
|
|
||||||
allowed = {name: all_dungeons for name in dungeon_regions}
|
|
||||||
|
|
||||||
# key drops allow the big key in the right place in Desert Tiles 2
|
# key drops allow the big key in the right place in Desert Tiles 2
|
||||||
bk_shuffle = world.bigkeyshuffle[player] or world.pottery[player] not in ['none', 'cave']
|
bk_shuffle = world.bigkeyshuffle[player] or world.pottery[player] not in ['none', 'cave']
|
||||||
@@ -593,7 +577,7 @@ def customizer_portals(master_door_list, world, player):
|
|||||||
assigned_doors.add(door)
|
assigned_doors.add(door)
|
||||||
# restricts connected doors to the customized portals
|
# restricts connected doors to the customized portals
|
||||||
if assigned_doors:
|
if assigned_doors:
|
||||||
pool = create_dungeon_pool(world, player)
|
pool = world.dungeon_pool[player]
|
||||||
if pool:
|
if pool:
|
||||||
pool_map = {}
|
pool_map = {}
|
||||||
for pool, region_list in pool:
|
for pool, region_list in pool:
|
||||||
@@ -1860,12 +1844,12 @@ def shuffle_trap_doors(door_type_pools, paths, start_regions_map, all_custom, wo
|
|||||||
builder.candidates.trap = filter_key_door_pool(builder.candidates.trap, all_custom[dungeon])
|
builder.candidates.trap = filter_key_door_pool(builder.candidates.trap, all_custom[dungeon])
|
||||||
remaining -= len(custom_trap_doors[dungeon])
|
remaining -= len(custom_trap_doors[dungeon])
|
||||||
ttl += len(builder.candidates.trap)
|
ttl += len(builder.candidates.trap)
|
||||||
if ttl == 0:
|
if ttl == 0 and all(len(custom_trap_doors[dungeon]) == 0 for dungeon in pool):
|
||||||
continue
|
continue
|
||||||
for dungeon in pool:
|
for dungeon in pool:
|
||||||
builder = world.dungeon_layouts[player][dungeon]
|
builder = world.dungeon_layouts[player][dungeon]
|
||||||
proportion = len(builder.candidates.trap)
|
proportion = len(builder.candidates.trap)
|
||||||
calc = int(round(proportion * door_type_pool.traps/ttl))
|
calc = 0 if ttl == 0 else int(round(proportion * door_type_pool.traps/ttl))
|
||||||
suggested = min(proportion, calc)
|
suggested = min(proportion, calc)
|
||||||
remaining -= suggested
|
remaining -= suggested
|
||||||
suggestion_map[dungeon] = suggested
|
suggestion_map[dungeon] = suggested
|
||||||
@@ -1963,7 +1947,7 @@ def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, all_cu
|
|||||||
if flex_map[dungeon] > 0:
|
if flex_map[dungeon] > 0:
|
||||||
queue.append(dungeon)
|
queue.append(dungeon)
|
||||||
# time to re-assign
|
# 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():
|
for name, big_list in bk_map.items():
|
||||||
used_doors.update(flatten_pair_list(big_list))
|
used_doors.update(flatten_pair_list(big_list))
|
||||||
return used_doors
|
return used_doors
|
||||||
@@ -1997,7 +1981,10 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_
|
|||||||
remaining = max(0, remaining)
|
remaining = max(0, remaining)
|
||||||
for dungeon in pool:
|
for dungeon in pool:
|
||||||
builder = world.dungeon_layouts[player][dungeon]
|
builder = world.dungeon_layouts[player][dungeon]
|
||||||
calculated = int(round(builder.key_doors_num*total_keys/ttl))
|
if ttl == 0:
|
||||||
|
calculated = 0
|
||||||
|
else:
|
||||||
|
calculated = int(round(builder.key_doors_num*total_keys/ttl))
|
||||||
max_keys = max(0, builder.location_cnt - calc_used_dungeon_items(builder, world, player))
|
max_keys = max(0, builder.location_cnt - calc_used_dungeon_items(builder, world, player))
|
||||||
cand_len = max(0, len(builder.candidates.small) - builder.key_drop_cnt)
|
cand_len = max(0, len(builder.candidates.small) - builder.key_drop_cnt)
|
||||||
limit = min(max_keys, cand_len, max_computation)
|
limit = min(max_keys, cand_len, max_computation)
|
||||||
@@ -2048,7 +2035,7 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_
|
|||||||
else:
|
else:
|
||||||
builder.key_doors_num -= 1
|
builder.key_doors_num -= 1
|
||||||
# time to re-assign
|
# 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:
|
for dungeon_name in pool:
|
||||||
if world.keyshuffle[player] != 'universal':
|
if world.keyshuffle[player] != 'universal':
|
||||||
builder = world.dungeon_layouts[player][dungeon_name]
|
builder = world.dungeon_layouts[player][dungeon_name]
|
||||||
@@ -2130,7 +2117,7 @@ def shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, all_
|
|||||||
suggestion_map[dungeon] = pair
|
suggestion_map[dungeon] = pair
|
||||||
queue.append(dungeon)
|
queue.append(dungeon)
|
||||||
# time to re-assign
|
# 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():
|
for name, pair in bd_map.items():
|
||||||
used_doors.update(flatten_pair_list(pair[0]))
|
used_doors.update(flatten_pair_list(pair[0]))
|
||||||
used_doors.update(flatten_pair_list(pair[1]))
|
used_doors.update(flatten_pair_list(pair[1]))
|
||||||
@@ -2227,9 +2214,10 @@ def find_valid_trap_combination(builder, suggested, start_regions, paths, world,
|
|||||||
sample_list = build_sample_list(combinations, 1000)
|
sample_list = build_sample_list(combinations, 1000)
|
||||||
proposal = kth_combination(sample_list[itr], trap_door_pool, trap_doors_needed)
|
proposal = kth_combination(sample_list[itr], trap_door_pool, trap_doors_needed)
|
||||||
proposal.extend(custom_trap_doors)
|
proposal.extend(custom_trap_doors)
|
||||||
|
filtered_proposal = [x for x in proposal if x.name not in trap_door_exceptions]
|
||||||
|
|
||||||
start_regions, event_starts = filter_start_regions(builder, start_regions, world, player)
|
start_regions, event_starts = filter_start_regions(builder, start_regions, world, player)
|
||||||
while not validate_trap_layout(proposal, builder, start_regions, paths, world, player):
|
while not validate_trap_layout(filtered_proposal, builder, start_regions, paths, world, player):
|
||||||
itr += 1
|
itr += 1
|
||||||
if itr >= len(sample_list):
|
if itr >= len(sample_list):
|
||||||
if not drop:
|
if not drop:
|
||||||
@@ -2264,6 +2252,12 @@ def filter_start_regions(builder, start_regions, world, player):
|
|||||||
portal_entrance_region = portal.door.entrance.parent_region.name
|
portal_entrance_region = portal.door.entrance.parent_region.name
|
||||||
if portal_entrance_region not in builder.path_entrances:
|
if portal_entrance_region not in builder.path_entrances:
|
||||||
excluded[region] = None
|
excluded[region] = None
|
||||||
|
if not portal:
|
||||||
|
drop_region = next((x.parent_region for x in region.entrances
|
||||||
|
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]
|
||||||
|
or x.parent_region.name == 'Sewer Drop'), None)
|
||||||
|
if drop_region and drop_region.name in world.inaccessible_regions[player]:
|
||||||
|
excluded[region] = None
|
||||||
if std_flag and (not portal or portal.find_portal_entrance().parent_region.name != 'Hyrule Castle Courtyard'):
|
if std_flag and (not portal or portal.find_portal_entrance().parent_region.name != 'Hyrule Castle Courtyard'):
|
||||||
excluded[region] = None
|
excluded[region] = None
|
||||||
if portal is None:
|
if portal is None:
|
||||||
@@ -2359,10 +2353,12 @@ def reassign_trap_doors(trap_map, world, player):
|
|||||||
elif kind in [DoorKind.Trap2, DoorKind.TrapTriggerable]:
|
elif kind in [DoorKind.Trap2, DoorKind.TrapTriggerable]:
|
||||||
room.change(d.doorListPos, DoorKind.Normal)
|
room.change(d.doorListPos, DoorKind.Normal)
|
||||||
d.blocked = False
|
d.blocked = False
|
||||||
|
d.trapped = False
|
||||||
# connect_one_way(world, d.name, d.dest.name, player)
|
# connect_one_way(world, d.name, d.dest.name, player)
|
||||||
elif d.type is DoorType.Normal and d not in traps:
|
elif d.type is DoorType.Normal and d not in traps:
|
||||||
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
||||||
d.blocked = False
|
d.blocked = False
|
||||||
|
d.trapped = False
|
||||||
for d in traps:
|
for d in traps:
|
||||||
change_door_to_trap(d, world, player)
|
change_door_to_trap(d, world, player)
|
||||||
world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Trap Door', player)
|
world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Trap Door', player)
|
||||||
@@ -2400,24 +2396,45 @@ def change_door_to_trap(d, world, player):
|
|||||||
elif d.direction in [Direction.North, Direction.West]:
|
elif d.direction in [Direction.North, Direction.West]:
|
||||||
new_kind = DoorKind.TrapTriggerable
|
new_kind = DoorKind.TrapTriggerable
|
||||||
if new_kind:
|
if new_kind:
|
||||||
d.blocked = True
|
d.blocked = is_trap_door_blocked(d)
|
||||||
|
d.trapped = True
|
||||||
pos = 3 if d.type == DoorType.Normal else 4
|
pos = 3 if d.type == DoorType.Normal else 4
|
||||||
verify_door_list_pos(d, room, world, player, pos)
|
verify_door_list_pos(d, room, world, player, pos)
|
||||||
d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1, 3: 0x8}[d.doorListPos]
|
d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1, 3: 0x8}[d.doorListPos]
|
||||||
room.change(d.doorListPos, new_kind)
|
room.change(d.doorListPos, new_kind)
|
||||||
if d.entrance.connected_region is not None:
|
if d.entrance.connected_region is not None and d.blocked:
|
||||||
d.entrance.connected_region.entrances.remove(d.entrance)
|
d.entrance.connected_region.entrances.remove(d.entrance)
|
||||||
d.entrance.connected_region = None
|
d.entrance.connected_region = None
|
||||||
elif d.type is DoorType.Normal:
|
elif d.type is DoorType.Normal:
|
||||||
d.blocked = True
|
d.blocked = is_trap_door_blocked(d)
|
||||||
|
d.trapped = True
|
||||||
verify_door_list_pos(d, room, world, player, pos=3)
|
verify_door_list_pos(d, room, world, player, pos=3)
|
||||||
d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1}[d.doorListPos]
|
d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1}[d.doorListPos]
|
||||||
room.change(d.doorListPos, DoorKind.Trap)
|
room.change(d.doorListPos, DoorKind.Trap)
|
||||||
if d.entrance.connected_region is not None:
|
if d.entrance.connected_region is not None and d.blocked:
|
||||||
d.entrance.connected_region.entrances.remove(d.entrance)
|
d.entrance.connected_region.entrances.remove(d.entrance)
|
||||||
d.entrance.connected_region = None
|
d.entrance.connected_region = None
|
||||||
|
|
||||||
|
|
||||||
|
trap_door_exceptions = {
|
||||||
|
'PoD Mimics 2 SW', 'TR Twin Pokeys NW', 'Thieves Blocked Entry SW', 'Hyrule Dungeon Armory Interior Key Door N',
|
||||||
|
'Desert Compass Key Door WN', 'TR Tile Room SE', 'Mire Cross SW', 'Tower Circle of Pots ES',
|
||||||
|
'Eastern Single Eyegore ES', 'Eastern Duo Eyegores SE', 'Swamp Push Statue S',
|
||||||
|
'Skull 2 East Lobby WS', 'GT Hope Room WN', 'Eastern Courtyard Ledge S', 'Ice Lobby SE', 'GT Speed Torch WN',
|
||||||
|
'Ice Switch Room ES', 'Ice Switch Room NE', 'Skull Torch Room WS', 'GT Speed Torch NE', 'GT Speed Torch WS',
|
||||||
|
'GT Torch Cross WN', 'Mire Tile Room SW', 'Mire Tile Room ES', 'TR Torches WN', 'PoD Lobby N', 'PoD Middle Cage S',
|
||||||
|
'Ice Bomb Jump NW', 'GT Hidden Spikes SE', 'Ice Tall Hint EN', 'GT Conveyor Cross EN', 'Eastern Pot Switch WN',
|
||||||
|
'Thieves Conveyor Maze WN', 'Thieves Conveyor Maze SW', 'Eastern Dark Square Key Door WN', 'Eastern Lobby NW',
|
||||||
|
'Eastern Lobby NE', 'Ice Cross Bottom SE', 'Desert Back Lobby S', 'Desert West S',
|
||||||
|
'Desert West Lobby ES', 'Mire Hidden Shooters SE', 'Mire Hidden Shooters ES', 'Mire Hidden Shooters WS',
|
||||||
|
'Tower Dark Pits EN', 'Tower Dark Maze ES', 'TR Tongue Pull WS',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def is_trap_door_blocked(door):
|
||||||
|
return door.name not in trap_door_exceptions
|
||||||
|
|
||||||
|
|
||||||
def find_big_key_candidates(builder, start_regions, used, world, player):
|
def find_big_key_candidates(builder, start_regions, used, world, player):
|
||||||
if world.door_type_mode[player] != 'original': # big, all, chaos
|
if world.door_type_mode[player] != 'original': # big, all, chaos
|
||||||
# traverse dungeon and find candidates
|
# traverse dungeon and find candidates
|
||||||
@@ -2540,7 +2557,7 @@ def find_current_bk_doors(builder):
|
|||||||
return current_doors
|
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('')
|
logger = logging.getLogger('')
|
||||||
for name, big_doors in bk_map.items():
|
for name, big_doors in bk_map.items():
|
||||||
flat_proposal = flatten_pair_list(big_doors)
|
flat_proposal = flatten_pair_list(big_doors)
|
||||||
@@ -2549,11 +2566,11 @@ def reassign_big_key_doors(bk_map, world, player):
|
|||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
d = queue.pop()
|
d = queue.pop()
|
||||||
if d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal:
|
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)
|
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
||||||
d.bigKey = False
|
d.bigKey = False
|
||||||
elif d.type is DoorType.Normal and d not in flat_proposal:
|
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)
|
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
||||||
d.bigKey = False
|
d.bigKey = False
|
||||||
for obj in big_doors:
|
for obj in big_doors:
|
||||||
@@ -2796,7 +2813,7 @@ def find_valid_bd_combination(builder, suggested, world, player):
|
|||||||
return bomb_proposal, dash_proposal, ttl_needed
|
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():
|
for name, pair in bd_map.items():
|
||||||
flat_bomb_proposal = flatten_pair_list(pair[0])
|
flat_bomb_proposal = flatten_pair_list(pair[0])
|
||||||
flat_dash_proposal = flatten_pair_list(pair[1])
|
flat_dash_proposal = flatten_pair_list(pair[1])
|
||||||
@@ -2809,10 +2826,10 @@ def reassign_bd_doors(bd_map, world, player):
|
|||||||
queue = deque(find_current_bd_doors(builder, world))
|
queue = deque(find_current_bd_doors(builder, world))
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
d = queue.pop()
|
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:
|
if not d.entranceFlag:
|
||||||
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
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:
|
if not d.entranceFlag:
|
||||||
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
||||||
do_bombable_dashable(pair[0], DoorKind.Bombable, world, player)
|
do_bombable_dashable(pair[0], DoorKind.Bombable, world, player)
|
||||||
@@ -3004,7 +3021,7 @@ def valid_key_door_pair(door1, door2):
|
|||||||
return len(door1.entrance.parent_region.exits) <= 1 or len(door2.entrance.parent_region.exits) <= 1
|
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('')
|
logger = logging.getLogger('')
|
||||||
for name, small_doors in small_map.items():
|
for name, small_doors in small_map.items():
|
||||||
logger.debug(f'Key doors for {name}')
|
logger.debug(f'Key doors for {name}')
|
||||||
@@ -3025,13 +3042,13 @@ def reassign_key_doors(small_map, world, player):
|
|||||||
room.delete(d.doorListPos)
|
room.delete(d.doorListPos)
|
||||||
d.smallKey = False
|
d.smallKey = False
|
||||||
elif d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal:
|
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)
|
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
||||||
d.smallKey = False
|
d.smallKey = False
|
||||||
d.dest.smallKey = False
|
d.dest.smallKey = False
|
||||||
queue.remove(d.dest)
|
queue.remove(d.dest)
|
||||||
elif d.type is DoorType.Normal and d not in flat_proposal:
|
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)
|
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
||||||
d.smallKey = False
|
d.smallKey = False
|
||||||
for dp in world.paired_doors[player]:
|
for dp in world.paired_doors[player]:
|
||||||
|
|||||||
+22
-10
@@ -1677,10 +1677,24 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p
|
|||||||
random.shuffle(sector_list)
|
random.shuffle(sector_list)
|
||||||
orig_location_set = build_orig_location_set(dungeon_map)
|
orig_location_set = build_orig_location_set(dungeon_map)
|
||||||
num_dungeon_items = requested_dungeon_items(world, player)
|
num_dungeon_items = requested_dungeon_items(world, player)
|
||||||
|
locations_to_distribute = sum(sector.chest_locations for sector in free_location_sectors.keys())
|
||||||
|
reserved_per_dungeon = {d_name: count_reserved_locations(world, player, orig_location_set[d_name])
|
||||||
|
for d_name in dungeon_map.keys()}
|
||||||
|
base_free, found_enough = 2, False
|
||||||
|
while not found_enough:
|
||||||
|
needed = sum(max(0, max(base_free, reserved_per_dungeon[d]) + num_dungeon_items - len(orig_location_set[d]))
|
||||||
|
for d in dungeon_map.keys())
|
||||||
|
if needed > locations_to_distribute:
|
||||||
|
if base_free == 0:
|
||||||
|
raise Exception('Unable to meet minimum requirements, check for customizer problems')
|
||||||
|
base_free -= 1
|
||||||
|
else:
|
||||||
|
found_enough = True
|
||||||
d_idx = {builder.name: i for i, builder in enumerate(dungeon_map.values())}
|
d_idx = {builder.name: i for i, builder in enumerate(dungeon_map.values())}
|
||||||
next_sector = sector_list.pop()
|
next_sector = sector_list.pop()
|
||||||
while not valid:
|
while not valid:
|
||||||
choice, totals, location_set = weighted_random_location(dungeon_map, choices, orig_location_set, world, player)
|
choice, totals, location_set = weighted_random_location(dungeon_map, choices, orig_location_set,
|
||||||
|
base_free, world, player)
|
||||||
if not choice:
|
if not choice:
|
||||||
break
|
break
|
||||||
choices[choice].append(next_sector)
|
choices[choice].append(next_sector)
|
||||||
@@ -1691,7 +1705,7 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p
|
|||||||
valid = True
|
valid = True
|
||||||
for d_name, idx in d_idx.items():
|
for d_name, idx in d_idx.items():
|
||||||
free_items = count_reserved_locations(world, player, location_set[d_name])
|
free_items = count_reserved_locations(world, player, location_set[d_name])
|
||||||
target = max(free_items, 2) + num_dungeon_items
|
target = max(free_items, base_free) + num_dungeon_items
|
||||||
if totals[idx] < target:
|
if totals[idx] < target:
|
||||||
valid = False
|
valid = False
|
||||||
break
|
break
|
||||||
@@ -1699,8 +1713,7 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p
|
|||||||
if len(sector_list) == 0:
|
if len(sector_list) == 0:
|
||||||
choices = defaultdict(list)
|
choices = defaultdict(list)
|
||||||
sector_list = list(free_location_sectors)
|
sector_list = list(free_location_sectors)
|
||||||
else:
|
next_sector = sector_list.pop()
|
||||||
next_sector = sector_list.pop()
|
|
||||||
else:
|
else:
|
||||||
choices[choice].remove(next_sector)
|
choices[choice].remove(next_sector)
|
||||||
for builder, choice_list in choices.items():
|
for builder, choice_list in choices.items():
|
||||||
@@ -1709,7 +1722,7 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p
|
|||||||
return free_location_sectors
|
return free_location_sectors
|
||||||
|
|
||||||
|
|
||||||
def weighted_random_location(dungeon_map, choices, orig_location_set, world, player):
|
def weighted_random_location(dungeon_map, choices, orig_location_set, base_free, world, player):
|
||||||
population = []
|
population = []
|
||||||
totals = []
|
totals = []
|
||||||
location_set = {x: set(y) for x, y in orig_location_set.items()}
|
location_set = {x: set(y) for x, y in orig_location_set.items()}
|
||||||
@@ -1720,7 +1733,7 @@ def weighted_random_location(dungeon_map, choices, orig_location_set, world, pla
|
|||||||
builder_set = location_set[dungeon_builder.name]
|
builder_set = location_set[dungeon_builder.name]
|
||||||
builder_set.update(set().union(*(s.chest_location_set for s in choices[dungeon_builder])))
|
builder_set.update(set().union(*(s.chest_location_set for s in choices[dungeon_builder])))
|
||||||
free_items = count_reserved_locations(world, player, builder_set)
|
free_items = count_reserved_locations(world, player, builder_set)
|
||||||
target = max(free_items, 2) + num_dungeon_items
|
target = max(free_items, base_free) + num_dungeon_items
|
||||||
if ttl < target:
|
if ttl < target:
|
||||||
population.append(dungeon_builder)
|
population.append(dungeon_builder)
|
||||||
choice = random.choice(population) if len(population) > 0 else None
|
choice = random.choice(population) if len(population) > 0 else None
|
||||||
@@ -1775,7 +1788,7 @@ def count_reserved_locations(world, player, proposed_set):
|
|||||||
return 2
|
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):
|
||||||
population = []
|
population = []
|
||||||
some_c_switches_present = False
|
some_c_switches_present = False
|
||||||
for name, builder in dungeon_map.items():
|
for name, builder in dungeon_map.items():
|
||||||
@@ -1784,7 +1797,7 @@ def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barrier
|
|||||||
if builder.c_switch_present and not builder.c_locked:
|
if builder.c_switch_present and not builder.c_locked:
|
||||||
some_c_switches_present = True
|
some_c_switches_present = True
|
||||||
if len(population) == 0: # nothing needs a switch
|
if len(population) == 0: # nothing needs a switch
|
||||||
if assign_one and not some_c_switches_present: # something should have one
|
if len(crystal_barriers) > 0 and not some_c_switches_present: # something should have one
|
||||||
if len(crystal_switches) == 0:
|
if len(crystal_switches) == 0:
|
||||||
raise GenerationException('No crystal switches to assign. Ref %s' % next(iter(dungeon_map.keys())))
|
raise GenerationException('No crystal switches to assign. Ref %s' % next(iter(dungeon_map.keys())))
|
||||||
valid, builder_choice, switch_choice = False, None, None
|
valid, builder_choice, switch_choice = False, None, None
|
||||||
@@ -3139,8 +3152,7 @@ def balance_split(candidate_sectors, dungeon_map, global_pole, builder_info):
|
|||||||
check_for_forced_assignments(dungeon_map, candidate_sectors, global_pole)
|
check_for_forced_assignments(dungeon_map, candidate_sectors, global_pole)
|
||||||
check_for_forced_crystal(dungeon_map, candidate_sectors, global_pole)
|
check_for_forced_crystal(dungeon_map, candidate_sectors, global_pole)
|
||||||
crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors)
|
crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors)
|
||||||
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers,
|
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole)
|
||||||
global_pole, len(crystal_barriers) > 0)
|
|
||||||
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:
|
||||||
if sector.polarity().is_neutral():
|
if sector.polarity().is_neutral():
|
||||||
|
|||||||
+26
-30
@@ -212,7 +212,8 @@ def generate_itempool(world, player):
|
|||||||
loc.locked = True
|
loc.locked = True
|
||||||
loc.forced_item = loc.item
|
loc.forced_item = loc.item
|
||||||
|
|
||||||
if not world.is_tile_swapped(0x18, player):
|
if (world.flute_mode[player] != 'active' and not world.is_tile_swapped(0x18, player)
|
||||||
|
and 'Ocarina (Activated)' not in list(map(str, [i for i in world.precollected_items if i.player == player]))):
|
||||||
region = world.get_region('Kakariko Village',player)
|
region = world.get_region('Kakariko Village',player)
|
||||||
|
|
||||||
loc = Location(player, "Flute Activation", parent=region)
|
loc = Location(player, "Flute Activation", parent=region)
|
||||||
@@ -1361,19 +1362,6 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer
|
|||||||
|
|
||||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_total, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_total, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
||||||
|
|
||||||
def set_default_triforce(goal, custom_goal, custom_total):
|
|
||||||
triforce_goal, triforce_total = 0, 0
|
|
||||||
if goal == 'triforcehunt':
|
|
||||||
triforce_goal, triforce_total = 20, 30
|
|
||||||
elif goal == 'trinity':
|
|
||||||
triforce_goal, triforce_total = 8, 10
|
|
||||||
if custom_goal > 0:
|
|
||||||
triforce_goal = max(min(custom_goal, 128), 1)
|
|
||||||
if custom_total > 0 or custom_goal > 0:
|
|
||||||
triforce_total = max(min(custom_total, 128), triforce_goal) #128 max to ensure other progression can fit.
|
|
||||||
return (triforce_goal, triforce_total)
|
|
||||||
|
|
||||||
|
|
||||||
def make_customizer_pool(world, player):
|
def make_customizer_pool(world, player):
|
||||||
pool = []
|
pool = []
|
||||||
placed_items = {}
|
placed_items = {}
|
||||||
@@ -1486,7 +1474,8 @@ def make_customizer_pool(world, player):
|
|||||||
bow_found = next((i for i in pool if i in {'Bow', 'Progressive Bow'}), None)
|
bow_found = next((i for i in pool if i in {'Bow', 'Progressive Bow'}), None)
|
||||||
if not bow_found:
|
if not bow_found:
|
||||||
missing_items.append('Progressive Bow')
|
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],
|
g, t = set_default_triforce(world.goal[player], world.treasure_hunt_count[player],
|
||||||
world.treasure_hunt_total[player])
|
world.treasure_hunt_total[player])
|
||||||
@@ -1495,20 +1484,23 @@ def make_customizer_pool(world, player):
|
|||||||
if pieces < t:
|
if pieces < t:
|
||||||
pool.extend(['Triforce Piece'] * (t - pieces))
|
pool.extend(['Triforce Piece'] * (t - pieces))
|
||||||
|
|
||||||
if not world.customizer.get_start_inventory():
|
sphere_0 = world.customizer.get_start_inventory()
|
||||||
if world.logic[player] in ['owglitches', 'nologic']:
|
no_start_inventory = not sphere_0 or not sphere_0[player]
|
||||||
precollected_items.append('Pegasus Boots')
|
init_equip = [] if no_start_inventory else sphere_0[player]
|
||||||
if 'Pegasus Boots' in pool:
|
if (world.logic[player] in ['owglitches', 'nologic']
|
||||||
pool.remove('Pegasus Boots')
|
and (no_start_inventory or all(x != 'Pegasus Boots' for x in init_equip))):
|
||||||
pool.append('Rupees (20)')
|
precollected_items.append('Pegasus Boots')
|
||||||
if world.swords[player] == 'assured':
|
if 'Pegasus Boots' in pool:
|
||||||
precollected_items.append('Progressive Sword')
|
pool.remove('Pegasus Boots')
|
||||||
if 'Progressive Sword' in pool:
|
pool.append('Rupees (20)')
|
||||||
pool.remove('Progressive Sword')
|
if world.swords[player] == 'assured' and (no_start_inventory or all(' Sword' not in x for x in init_equip)):
|
||||||
pool.append('Rupees (50)')
|
precollected_items.append('Progressive Sword')
|
||||||
elif 'Fighter Sword' in pool:
|
if 'Progressive Sword' in pool:
|
||||||
pool.remove('Fighter Sword')
|
pool.remove('Progressive Sword')
|
||||||
pool.append('Rupees (50)')
|
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
|
return pool, placed_items, precollected_items, clock_mode, 1
|
||||||
|
|
||||||
@@ -1577,10 +1569,13 @@ def fill_specific_items(world):
|
|||||||
dungeon_pool, prize_set, prize_pool)
|
dungeon_pool, prize_set, prize_pool)
|
||||||
if item_to_place:
|
if item_to_place:
|
||||||
world.push_item(loc, item_to_place, False)
|
world.push_item(loc, item_to_place, False)
|
||||||
|
loc.locked = True
|
||||||
track_outside_keys(item_to_place, loc, world)
|
track_outside_keys(item_to_place, loc, world)
|
||||||
track_dungeon_items(item_to_place, loc, world)
|
track_dungeon_items(item_to_place, loc, world)
|
||||||
loc.event = (event_flag or item_to_place.advancement
|
loc.event = (event_flag or item_to_place.advancement
|
||||||
or item_to_place.bigkey or item_to_place.smallkey)
|
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()
|
advanced_placements = world.customizer.get_advanced_placements()
|
||||||
if advanced_placements:
|
if advanced_placements:
|
||||||
for player, placement_list in advanced_placements.items():
|
for player, placement_list in advanced_placements.items():
|
||||||
@@ -1590,7 +1585,7 @@ def fill_specific_items(world):
|
|||||||
item_to_place, event_flag = get_item_and_event_flag(item, world, player,
|
item_to_place, event_flag = get_item_and_event_flag(item, world, player,
|
||||||
dungeon_pool, prize_set, prize_pool)
|
dungeon_pool, prize_set, prize_pool)
|
||||||
if not item_to_place:
|
if not item_to_place:
|
||||||
continue
|
raise Exception(f'Did not find "{item}" in item pool to place for a LocationGroup"')
|
||||||
locations = placement['locations']
|
locations = placement['locations']
|
||||||
handled = False
|
handled = False
|
||||||
while not handled:
|
while not handled:
|
||||||
@@ -1610,6 +1605,7 @@ def fill_specific_items(world):
|
|||||||
if loc.item:
|
if loc.item:
|
||||||
continue
|
continue
|
||||||
world.push_item(loc, item_to_place, False)
|
world.push_item(loc, item_to_place, False)
|
||||||
|
loc.locked = True
|
||||||
track_outside_keys(item_to_place, loc, world)
|
track_outside_keys(item_to_place, loc, world)
|
||||||
track_dungeon_items(item_to_place, loc, world)
|
track_dungeon_items(item_to_place, loc, world)
|
||||||
loc.event = (event_flag or item_to_place.advancement
|
loc.event = (event_flag or item_to_place.advancement
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new
|
|||||||
from source.tools.BPS import create_bps_from_data
|
from source.tools.BPS import create_bps_from_data
|
||||||
from source.classes.CustomSettings import CustomSettings
|
from source.classes.CustomSettings import CustomSettings
|
||||||
|
|
||||||
version_number = '1.2.0.17'
|
version_number = '1.2.0.20'
|
||||||
version_branch = '-u'
|
version_branch = '-u'
|
||||||
__version__ = f'{version_number}{version_branch}'
|
__version__ = f'{version_number}{version_branch}'
|
||||||
|
|
||||||
@@ -122,6 +122,7 @@ def main(args, seed=None, fish=None):
|
|||||||
world.trap_door_mode = args.trap_door_mode.copy()
|
world.trap_door_mode = args.trap_door_mode.copy()
|
||||||
world.key_logic_algorithm = args.key_logic_algorithm.copy()
|
world.key_logic_algorithm = args.key_logic_algorithm.copy()
|
||||||
world.decoupledoors = args.decoupledoors.copy()
|
world.decoupledoors = args.decoupledoors.copy()
|
||||||
|
world.door_self_loops = args.door_self_loops.copy()
|
||||||
world.experimental = args.experimental.copy()
|
world.experimental = args.experimental.copy()
|
||||||
world.dungeon_counters = args.dungeon_counters.copy()
|
world.dungeon_counters = args.dungeon_counters.copy()
|
||||||
world.fish = fish
|
world.fish = fish
|
||||||
@@ -131,8 +132,6 @@ def main(args, seed=None, fish=None):
|
|||||||
world.potshuffle = args.shufflepots.copy()
|
world.potshuffle = args.shufflepots.copy()
|
||||||
world.mixed_travel = args.mixed_travel.copy()
|
world.mixed_travel = args.mixed_travel.copy()
|
||||||
world.standardize_palettes = args.standardize_palettes.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.shufflelinks = args.shufflelinks.copy()
|
||||||
world.shuffletavern = args.shuffletavern.copy()
|
world.shuffletavern = args.shuffletavern.copy()
|
||||||
world.pseudoboots = args.pseudoboots.copy()
|
world.pseudoboots = args.pseudoboots.copy()
|
||||||
@@ -142,6 +141,26 @@ def main(args, seed=None, fish=None):
|
|||||||
world.collection_rate = args.collection_rate.copy()
|
world.collection_rate = args.collection_rate.copy()
|
||||||
world.colorizepots = args.colorizepots.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.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
||||||
world.finish_init()
|
world.finish_init()
|
||||||
|
|
||||||
@@ -165,7 +184,7 @@ def main(args, seed=None, fish=None):
|
|||||||
world.player_names[player].append(name)
|
world.player_names[player].append(name)
|
||||||
logger.info('')
|
logger.info('')
|
||||||
world.settings = CustomSettings()
|
world.settings = CustomSettings()
|
||||||
world.settings.create_from_world(world, args.race)
|
world.settings.create_from_world(world, args)
|
||||||
|
|
||||||
outfilebase = f'OR_{args.outputname if args.outputname else world.seed}'
|
outfilebase = f'OR_{args.outputname if args.outputname else world.seed}'
|
||||||
|
|
||||||
@@ -501,6 +520,7 @@ def copy_world(world):
|
|||||||
ret.beemizer = world.beemizer.copy()
|
ret.beemizer = world.beemizer.copy()
|
||||||
ret.intensity = world.intensity.copy()
|
ret.intensity = world.intensity.copy()
|
||||||
ret.decoupledoors = world.decoupledoors.copy()
|
ret.decoupledoors = world.decoupledoors.copy()
|
||||||
|
ret.door_self_loops = world.door_self_loops.copy()
|
||||||
ret.experimental = world.experimental.copy()
|
ret.experimental = world.experimental.copy()
|
||||||
ret.shopsanity = world.shopsanity.copy()
|
ret.shopsanity = world.shopsanity.copy()
|
||||||
ret.dropshuffle = world.dropshuffle.copy()
|
ret.dropshuffle = world.dropshuffle.copy()
|
||||||
@@ -814,7 +834,9 @@ def create_playthrough(world):
|
|||||||
|
|
||||||
logging.getLogger('').debug(world.fish.translate("cli", "cli", "building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations))
|
logging.getLogger('').debug(world.fish.translate("cli", "cli", "building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations))
|
||||||
if not sphere:
|
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]):
|
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"))
|
raise RuntimeError(world.fish.translate("cli", "cli", "cannot.reach.progression"))
|
||||||
else:
|
else:
|
||||||
@@ -877,6 +899,7 @@ def create_playthrough(world):
|
|||||||
if world.has_beaten_game(state):
|
if world.has_beaten_game(state):
|
||||||
required_locations.clear()
|
required_locations.clear()
|
||||||
else:
|
else:
|
||||||
|
logging.getLogger('').error(world.fish.translate("cli", "cli", "cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (loc.item.name, loc.item.player, loc.name, loc.player) for loc in required_locations])
|
||||||
raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.required"))
|
raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.required"))
|
||||||
|
|
||||||
# store the required locations for statistical analysis
|
# store the required locations for statistical analysis
|
||||||
|
|||||||
+12
-3
@@ -66,6 +66,8 @@ class Context:
|
|||||||
self.lookup_name_to_id = {}
|
self.lookup_name_to_id = {}
|
||||||
self.lookup_id_to_name = {}
|
self.lookup_id_to_name = {}
|
||||||
|
|
||||||
|
self.pottery_locations_enabled = None
|
||||||
|
|
||||||
def color_code(*args):
|
def color_code(*args):
|
||||||
codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
|
codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
|
||||||
'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
|
'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
|
||||||
@@ -96,6 +98,8 @@ SHOP_SRAM_START = WRAM_START + 0x0164B8 # 2 bytes?
|
|||||||
ITEM_SRAM_SIZE = 0x250
|
ITEM_SRAM_SIZE = 0x250
|
||||||
SHOP_SRAM_LEN = 0x29 # 41 tracked items
|
SHOP_SRAM_LEN = 0x29 # 41 tracked items
|
||||||
|
|
||||||
|
POT_LOCATION_TABLE = 0x142A60
|
||||||
|
|
||||||
RECV_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes
|
RECV_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes
|
||||||
RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte
|
RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte
|
||||||
RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte
|
RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte
|
||||||
@@ -827,12 +831,14 @@ def get_location_name_from_address(ctx, address):
|
|||||||
|
|
||||||
|
|
||||||
def filter_location(ctx, location):
|
def filter_location(ctx, location):
|
||||||
|
if location in location_table_pot_items:
|
||||||
|
tile_idx, mask = location_table_pot_items[location]
|
||||||
|
tracking_data = ctx.pottery_locations_enabled
|
||||||
|
tile_pots = tracking_data[tile_idx] | (tracking_data[tile_idx+1] << 8)
|
||||||
|
return (mask & tile_pots) == 0
|
||||||
if (not ctx.key_drop_mode and location in PotShuffle.key_drop_data
|
if (not ctx.key_drop_mode and location in PotShuffle.key_drop_data
|
||||||
and PotShuffle.key_drop_data[location][0] == 'Drop'):
|
and PotShuffle.key_drop_data[location][0] == 'Drop'):
|
||||||
return True
|
return True
|
||||||
if (not ctx.pottery_mode and location in PotShuffle.key_drop_data
|
|
||||||
and PotShuffle.key_drop_data[location][0] == 'Pot'):
|
|
||||||
return True
|
|
||||||
if not ctx.shop_mode and location in Regions.flat_normal_shops:
|
if not ctx.shop_mode and location in Regions.flat_normal_shops:
|
||||||
return True
|
return True
|
||||||
if not ctx.retro_mode and location in Regions.flat_retro_shops:
|
if not ctx.retro_mode and location in Regions.flat_retro_shops:
|
||||||
@@ -1006,6 +1012,9 @@ async def game_watcher(ctx : Context):
|
|||||||
logging.warning("ROM change detected, please reconnect to the multiworld server")
|
logging.warning("ROM change detected, please reconnect to the multiworld server")
|
||||||
await disconnect(ctx)
|
await disconnect(ctx)
|
||||||
|
|
||||||
|
if ctx.pottery_locations_enabled is None:
|
||||||
|
ctx.pottery_locations_enabled = await snes_read(ctx, POT_LOCATION_TABLE, 0x250)
|
||||||
|
|
||||||
gamemode = await snes_read(ctx, WRAM_START + 0x10, 1)
|
gamemode = await snes_read(ctx, WRAM_START + 0x10, 1)
|
||||||
if gamemode is None or gamemode[0] not in INGAME_MODES:
|
if gamemode is None or gamemode[0] not in INGAME_MODES:
|
||||||
continue
|
continue
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType
|
|||||||
from OverworldGlitchRules import create_owg_connections
|
from OverworldGlitchRules import create_owg_connections
|
||||||
from Utils import bidict
|
from Utils import bidict
|
||||||
|
|
||||||
version_number = '0.3.2.0'
|
version_number = '0.3.2.1'
|
||||||
# branch indicator is intentionally different across branches
|
# branch indicator is intentionally different across branches
|
||||||
version_branch = ''
|
version_branch = ''
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1022,7 +1022,7 @@ key_drop_data = {
|
|||||||
'Ice Palace - Jelly Key Drop': ['Drop', (0x09DA21, 0xE, 3), 'dropped in Ice Palace', 'Small Key (Ice Palace)'],
|
'Ice Palace - Jelly Key Drop': ['Drop', (0x09DA21, 0xE, 3), 'dropped in Ice Palace', 'Small Key (Ice Palace)'],
|
||||||
'Ice Palace - Conveyor Key Drop': ['Drop', (0x09DE08, 0x3E, 8), 'dropped in Ice Palace', 'Small Key (Ice Palace)'],
|
'Ice Palace - Conveyor Key Drop': ['Drop', (0x09DE08, 0x3E, 8), 'dropped in Ice Palace', 'Small Key (Ice Palace)'],
|
||||||
'Ice Palace - Hammer Block Key Drop': ['Pot', 0x3F, 'under a block in Ice Palace', 'Small Key (Ice Palace)'],
|
'Ice Palace - Hammer Block Key Drop': ['Pot', 0x3F, 'under a block in Ice Palace', 'Small Key (Ice Palace)'],
|
||||||
'Ice Palace - Many Pots Pot Key': ['Pot', 0x9F, 'int a pot in Ice Palace', 'Small Key (Ice Palace)'],
|
'Ice Palace - Many Pots Pot Key': ['Pot', 0x9F, 'in a pot in Ice Palace', 'Small Key (Ice Palace)'],
|
||||||
'Misery Mire - Spikes Pot Key': ['Pot', 0xB3, 'in a pot in Misery Mire', 'Small Key (Misery Mire)'],
|
'Misery Mire - Spikes Pot Key': ['Pot', 0xB3, 'in a pot in Misery Mire', 'Small Key (Misery Mire)'],
|
||||||
'Misery Mire - Fishbone Pot Key': ['Pot', 0xA1, 'in a pot in forgotten Mire', 'Small Key (Misery Mire)'],
|
'Misery Mire - Fishbone Pot Key': ['Pot', 0xA1, 'in a pot in forgotten Mire', 'Small Key (Misery Mire)'],
|
||||||
'Misery Mire - Conveyor Crystal Key Drop': ['Drop', (0x09E7FB, 0xC1, 9), 'dropped in Misery Mire', 'Small Key (Misery Mire)'],
|
'Misery Mire - Conveyor Crystal Key Drop': ['Drop', (0x09E7FB, 0xC1, 9), 'dropped in Misery Mire', 'Small Key (Misery Mire)'],
|
||||||
|
|||||||
+26
-1
@@ -57,7 +57,7 @@ Please see [Customizer documentation](docs/Customizer.md) on how to create custo
|
|||||||
|
|
||||||
## New Goals
|
## New Goals
|
||||||
|
|
||||||
### Triforce Hunt + Ganon
|
### Ganonhunt
|
||||||
Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI
|
Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI
|
||||||
|
|
||||||
### Completionist
|
### Completionist
|
||||||
@@ -109,6 +109,31 @@ These are now independent of retro mode and have three options: None, Random, an
|
|||||||
|
|
||||||
# Bug Fixes and Notes
|
# Bug Fixes and Notes
|
||||||
|
|
||||||
|
* 1.2.0.20u
|
||||||
|
* New generation feature that allows Spiral Stair to link to themselves (thank Catobat)
|
||||||
|
* Added logic for trap doors that could be opened using existing room triggers
|
||||||
|
* Fixed a problem with inverted generation and the experimental flag
|
||||||
|
* Added a notes field for user added notes either via CLI or Customizer (thanks Hiimcody and Codemann)
|
||||||
|
* Fixed a typo for a specific pot hint
|
||||||
|
* Fix for Hera Boss music (thanks Codemann)
|
||||||
|
* 1.1.6 (from Stable)
|
||||||
|
* Minor issue with dungeon counter hud interfering with timer
|
||||||
|
* 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
|
* 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)
|
* 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
|
* 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
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127
|
|||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = '32f6a9f479f6ccb7e66e9906ff0d0e4c'
|
RANDOMIZERBASEHASH = 'da111397d4118054e5ab4b9375cfb9e4'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ def set_rules(world, player):
|
|||||||
elif world.goal[player] == 'completionist':
|
elif world.goal[player] == 'completionist':
|
||||||
add_rule(world.get_location('Ganon', player), lambda state: state.everything(player))
|
add_rule(world.get_location('Ganon', player), lambda state: state.everything(player))
|
||||||
|
|
||||||
if not world.is_tile_swapped(0x18, player):
|
if (world.flute_mode[player] != 'active' and not world.is_tile_swapped(0x18, player)
|
||||||
|
and 'Ocarina (Activated)' not in list(map(str, [i for i in world.precollected_items if i.player == player]))):
|
||||||
if not world.is_copied_world:
|
if not world.is_copied_world:
|
||||||
# Commented out below, this would be needed for rando implementations where Inverted requires flute activation in bunny territory
|
# Commented out below, this would be needed for rando implementations where Inverted requires flute activation in bunny territory
|
||||||
# kak_region = self.world.get_region('Kakariko Village', player)
|
# kak_region = self.world.get_region('Kakariko Village', player)
|
||||||
@@ -382,11 +383,18 @@ def global_rules(world, player):
|
|||||||
# Start of door rando rules
|
# Start of door rando rules
|
||||||
# TODO: Do these need to flag off when door rando is off? - some of them, yes
|
# TODO: Do these need to flag off when door rando is off? - some of them, yes
|
||||||
|
|
||||||
|
def is_trapped(entrance):
|
||||||
|
return world.get_entrance(entrance, player).door.trapped
|
||||||
|
|
||||||
# Eastern Palace
|
# Eastern Palace
|
||||||
# Eyegore room needs a bow
|
# Eyegore room needs a bow
|
||||||
set_rule(world.get_entrance('Eastern Duo Eyegores NE', player), lambda state: state.can_shoot_arrows(player))
|
set_rule(world.get_entrance('Eastern Duo Eyegores NE', player), lambda state: state.can_shoot_arrows(player))
|
||||||
set_rule(world.get_entrance('Eastern Single Eyegore NE', player), lambda state: state.can_shoot_arrows(player))
|
set_rule(world.get_entrance('Eastern Single Eyegore NE', player), lambda state: state.can_shoot_arrows(player))
|
||||||
set_rule(world.get_entrance('Eastern Map Balcony Hook Path', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_entrance('Eastern Map Balcony Hook Path', player), lambda state: state.has('Hookshot', player))
|
||||||
|
if is_trapped('Eastern Single Eyegore ES'):
|
||||||
|
set_rule(world.get_entrance('Eastern Single Eyegore ES', player), lambda state: state.can_shoot_arrows(player))
|
||||||
|
if is_trapped('Eastern Duo Eyegores SE'):
|
||||||
|
set_rule(world.get_entrance('Eastern Duo Eyegores SE', player), lambda state: state.can_shoot_arrows(player))
|
||||||
|
|
||||||
# Boss rules. Same as below but no BK or arrow requirement.
|
# Boss rules. Same as below but no BK or arrow requirement.
|
||||||
set_defeat_dungeon_boss_rule(world.get_location('Eastern Palace - Prize', player))
|
set_defeat_dungeon_boss_rule(world.get_location('Eastern Palace - Prize', player))
|
||||||
@@ -411,13 +419,18 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_entrance('Tower Red Spears WN', player), lambda state: state.can_kill_most_things(player))
|
set_rule(world.get_entrance('Tower Red Spears WN', player), lambda state: state.can_kill_most_things(player))
|
||||||
set_rule(world.get_entrance('Tower Red Guards EN', player), lambda state: state.can_kill_most_things(player))
|
set_rule(world.get_entrance('Tower Red Guards EN', player), lambda state: state.can_kill_most_things(player))
|
||||||
set_rule(world.get_entrance('Tower Red Guards SW', player), lambda state: state.can_kill_most_things(player))
|
set_rule(world.get_entrance('Tower Red Guards SW', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
set_rule(world.get_entrance('Tower Circle of Pots NW', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
if is_trapped('Tower Circle of Pots ES'):
|
||||||
|
set_rule(world.get_entrance('Tower Circle of Pots ES', player),
|
||||||
|
lambda state: state.can_kill_most_things(player))
|
||||||
set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player))
|
set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player))
|
||||||
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player))
|
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player))
|
||||||
|
|
||||||
|
|
||||||
set_rule(world.get_entrance('PoD Arena Landing Bonk Path', player), lambda state: state.has_Boots(player))
|
set_rule(world.get_entrance('PoD Arena Landing Bonk Path', player), lambda state: state.has_Boots(player))
|
||||||
set_rule(world.get_entrance('PoD Mimics 1 NW', player), lambda state: state.can_shoot_arrows(player))
|
set_rule(world.get_entrance('PoD Mimics 1 NW', player), lambda state: state.can_shoot_arrows(player))
|
||||||
set_rule(world.get_entrance('PoD Mimics 2 NW', player), lambda state: state.can_shoot_arrows(player))
|
set_rule(world.get_entrance('PoD Mimics 2 NW', player), lambda state: state.can_shoot_arrows(player))
|
||||||
|
if is_trapped('PoD Mimics 2 SW'):
|
||||||
|
set_rule(world.get_entrance('PoD Mimics 2 SW', player), lambda state: state.can_shoot_arrows(player))
|
||||||
set_rule(world.get_entrance('PoD Bow Statue Down Ladder', player), lambda state: state.can_shoot_arrows(player))
|
set_rule(world.get_entrance('PoD Bow Statue Down Ladder', player), lambda state: state.can_shoot_arrows(player))
|
||||||
set_rule(world.get_entrance('PoD Map Balcony Drop Down', player), lambda state: state.has('Hammer', player))
|
set_rule(world.get_entrance('PoD Map Balcony Drop Down', player), lambda state: state.has('Hammer', player))
|
||||||
set_rule(world.get_entrance('PoD Dark Pegs Landing to Right', player), lambda state: state.has('Hammer', player))
|
set_rule(world.get_entrance('PoD Dark Pegs Landing to Right', player), lambda state: state.has('Hammer', player))
|
||||||
@@ -466,6 +479,8 @@ def global_rules(world, player):
|
|||||||
|
|
||||||
set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player))
|
||||||
set_rule(world.get_entrance('Skull Torch Room WN', player), lambda state: state.has('Fire Rod', player))
|
set_rule(world.get_entrance('Skull Torch Room WN', player), lambda state: state.has('Fire Rod', player))
|
||||||
|
if is_trapped('Skull Torch Room WS'):
|
||||||
|
set_rule(world.get_entrance('Skull Torch Room WS', player), lambda state: state.has('Fire Rod', player))
|
||||||
set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player))
|
set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player))
|
||||||
|
|
||||||
hidden_pits_door = world.get_door('Skull Small Hall WS', player)
|
hidden_pits_door = world.get_door('Skull Small Hall WS', player)
|
||||||
@@ -503,6 +518,8 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_location('Thieves\' Town - Prize', player), lambda state: state.has('Maiden Unmasked', player) and world.get_location('Thieves\' Town - Prize', player).parent_region.dungeon.boss.can_defeat(state))
|
set_rule(world.get_location('Thieves\' Town - Prize', player), lambda state: state.has('Maiden Unmasked', player) and world.get_location('Thieves\' Town - Prize', player).parent_region.dungeon.boss.can_defeat(state))
|
||||||
|
|
||||||
set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.can_melt_things(player))
|
set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.can_melt_things(player))
|
||||||
|
if is_trapped('Ice Lobby SE'):
|
||||||
|
set_rule(world.get_entrance('Ice Lobby SE', player), lambda state: state.can_melt_things(player))
|
||||||
set_rule(world.get_entrance('Ice Hammer Block ES', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
|
set_rule(world.get_entrance('Ice Hammer Block ES', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
|
||||||
set_rule(world.get_location('Ice Palace - Hammer Block Key Drop', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
|
set_rule(world.get_location('Ice Palace - Hammer Block Key Drop', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
|
||||||
set_rule(world.get_location('Ice Palace - Map Chest', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
|
set_rule(world.get_location('Ice Palace - Map Chest', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
|
||||||
@@ -517,6 +534,12 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_entrance('Ice Hookshot Balcony Path', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_entrance('Ice Hookshot Balcony Path', player), lambda state: state.has('Hookshot', player))
|
||||||
if not world.get_door('Ice Switch Room SE', player).entranceFlag:
|
if not world.get_door('Ice Switch Room SE', player).entranceFlag:
|
||||||
set_rule(world.get_entrance('Ice Switch Room SE', player), lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player))
|
set_rule(world.get_entrance('Ice Switch Room SE', player), lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player))
|
||||||
|
if is_trapped('Ice Switch Room ES'):
|
||||||
|
set_rule(world.get_entrance('Ice Switch Room ES', player),
|
||||||
|
lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player))
|
||||||
|
if is_trapped('Ice Switch Room NE'):
|
||||||
|
set_rule(world.get_entrance('Ice Switch Room NE', player),
|
||||||
|
lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player))
|
||||||
set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Boss', player))
|
set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Boss', player))
|
||||||
set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Prize', player))
|
set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Prize', player))
|
||||||
|
|
||||||
@@ -537,8 +560,15 @@ def global_rules(world, player):
|
|||||||
or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
||||||
set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player))
|
||||||
set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player))
|
set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player))
|
||||||
|
if is_trapped('Mire Tile Room SW'):
|
||||||
|
set_rule(world.get_entrance('Mire Tile Room SW', player), lambda state: state.has_fire_source(player))
|
||||||
|
if is_trapped('Mire Tile Room ES'):
|
||||||
|
set_rule(world.get_entrance('Mire Tile Room ES', player), lambda state: state.has_fire_source(player))
|
||||||
set_rule(world.get_entrance('Mire Attic Hint Hole', player), lambda state: state.has_fire_source(player))
|
set_rule(world.get_entrance('Mire Attic Hint Hole', player), lambda state: state.has_fire_source(player))
|
||||||
set_rule(world.get_entrance('Mire Dark Shooters SW', player), lambda state: state.has('Cane of Somaria', player))
|
set_rule(world.get_entrance('Mire Dark Shooters SW', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
|
if is_trapped('Mire Dark Shooters SE'):
|
||||||
|
set_rule(world.get_entrance('Mire Dark Shooters SE', player),
|
||||||
|
lambda state: state.has('Cane of Somaria', player))
|
||||||
|
|
||||||
set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Boss', player))
|
set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Boss', player))
|
||||||
set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Prize', player))
|
set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Prize', player))
|
||||||
@@ -554,6 +584,9 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_entrance('TR Hub Path', player), lambda state: state.has('Cane of Somaria', player))
|
set_rule(world.get_entrance('TR Hub Path', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
set_rule(world.get_entrance('TR Hub Ledges Path', player), lambda state: state.has('Cane of Somaria', player))
|
set_rule(world.get_entrance('TR Hub Ledges Path', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
set_rule(world.get_entrance('TR Torches NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
|
set_rule(world.get_entrance('TR Torches NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
|
||||||
|
if is_trapped('TR Torches WN'):
|
||||||
|
set_rule(world.get_entrance('TR Torches WN', player),
|
||||||
|
lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
|
||||||
set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
|
set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
|
||||||
set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has_Boots(player))
|
set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has_Boots(player))
|
||||||
set_rule(world.get_entrance('TR Dark Ride Up Stairs', player), lambda state: state.has('Cane of Somaria', player))
|
set_rule(world.get_entrance('TR Dark Ride Up Stairs', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
@@ -573,10 +606,20 @@ 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_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 Hope Room EN', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
|
if is_trapped('GT Hope Room WN'):
|
||||||
|
set_rule(world.get_entrance('GT Hope Room WN', player), lambda state: state.has('Cane of Somaria', 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 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))
|
set_rule(world.get_entrance('GT Conveyor Cross Hookshot Path', player), lambda state: state.has('Hookshot', player))
|
||||||
|
if is_trapped('GT Conveyor Cross EN'):
|
||||||
|
set_rule(world.get_entrance('GT Conveyor Cross EN', player), lambda state: state.has('Hammer', player))
|
||||||
if not world.get_door('GT Speed Torch SE', player).entranceFlag:
|
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 Speed Torch SE', player), lambda state: state.has('Fire Rod', player))
|
||||||
|
if is_trapped('GT Speed Torch NE'):
|
||||||
|
set_rule(world.get_entrance('GT Speed Torch NE', player), lambda state: state.has('Fire Rod', player))
|
||||||
|
if is_trapped('GT Speed Torch WS'):
|
||||||
|
set_rule(world.get_entrance('GT Speed Torch WS', player), lambda state: state.has('Fire Rod', player))
|
||||||
|
if is_trapped('GT Speed Torch WN'):
|
||||||
|
set_rule(world.get_entrance('GT Speed Torch WN', 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))
|
set_rule(world.get_entrance('GT Hookshot South-Mid Path', player), lambda state: state.has('Hookshot', player))
|
||||||
set_rule(world.get_entrance('GT Hookshot Mid-North Path', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_entrance('GT Hookshot Mid-North Path', player), lambda state: state.has('Hookshot', player))
|
||||||
set_rule(world.get_entrance('GT Hookshot East-Mid Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
|
set_rule(world.get_entrance('GT Hookshot East-Mid Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
|
||||||
@@ -611,6 +654,8 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_entrance('GT Lanmolas 2 ES', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
|
set_rule(world.get_entrance('GT Lanmolas 2 ES', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
|
||||||
set_rule(world.get_entrance('GT Lanmolas 2 NW', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
|
set_rule(world.get_entrance('GT Lanmolas 2 NW', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
|
||||||
set_rule(world.get_entrance('GT Torch Cross ES', player), lambda state: state.has_fire_source(player))
|
set_rule(world.get_entrance('GT Torch Cross ES', player), lambda state: state.has_fire_source(player))
|
||||||
|
if is_trapped('GT Torch Cross WN'):
|
||||||
|
set_rule(world.get_entrance('GT Torch Cross WN', player), lambda state: state.has_fire_source(player))
|
||||||
set_rule(world.get_entrance('GT Falling Torches NE', player), lambda state: state.has_fire_source(player))
|
set_rule(world.get_entrance('GT Falling Torches NE', player), lambda state: state.has_fire_source(player))
|
||||||
# todo: the following only applies to crystal state propagation from this supertile
|
# todo: the following only applies to crystal state propagation from this supertile
|
||||||
# you can also reset the supertile, but I'm not sure how to model that
|
# you can also reset the supertile, but I'm not sure how to model that
|
||||||
@@ -870,13 +915,29 @@ def bomb_rules(world, player):
|
|||||||
('GT Petting Zoo SE', False), # Dont make anyone do this room with bombs and/or pots.
|
('GT Petting Zoo SE', False), # Dont make anyone do this room with bombs and/or pots.
|
||||||
('GT DMs Room SW', False) # Four red stalfos
|
('GT DMs Room SW', False) # Four red stalfos
|
||||||
]
|
]
|
||||||
|
conditional_kill_traps = [
|
||||||
|
('Hyrule Dungeon Armory Interior Key Door N', True),
|
||||||
|
('Desert Compass Key Door WN', True),
|
||||||
|
('Thieves Blocked Entry SW', True),
|
||||||
|
('TR Tongue Pull WS', True),
|
||||||
|
('TR Twin Pokeys NW', False),
|
||||||
|
]
|
||||||
for killdoor,bombable in easy_kill_rooms:
|
for killdoor,bombable in easy_kill_rooms:
|
||||||
if bombable:
|
if bombable:
|
||||||
add_rule(world.get_entrance(killdoor, player), lambda state: (state.can_use_bombs(player) or state.can_kill_most_things(player)))
|
add_rule(world.get_entrance(killdoor, player), lambda state: (state.can_use_bombs(player) or state.can_kill_most_things(player)))
|
||||||
else:
|
else:
|
||||||
add_rule(world.get_entrance(killdoor, player), lambda state: state.can_kill_most_things(player))
|
add_rule(world.get_entrance(killdoor, player), lambda state: state.can_kill_most_things(player))
|
||||||
|
for kill_door, bombable in conditional_kill_traps:
|
||||||
|
if world.get_entrance(kill_door, player).door.trapped:
|
||||||
|
if bombable:
|
||||||
|
add_rule(world.get_entrance(kill_door, player),
|
||||||
|
lambda state: (state.can_use_bombs(player) or state.can_kill_most_things(player)))
|
||||||
|
else:
|
||||||
|
add_rule(world.get_entrance(kill_door, player), lambda state: state.can_kill_most_things(player))
|
||||||
add_rule(world.get_entrance('Ice Stalfos Hint SE', player), lambda state: state.can_use_bombs(player)) # Need bombs for big stalfos knights
|
add_rule(world.get_entrance('Ice Stalfos Hint SE', player), lambda state: state.can_use_bombs(player)) # Need bombs for big stalfos knights
|
||||||
add_rule(world.get_entrance('Mire Cross ES', player), lambda state: state.can_kill_most_things(player)) # 4 Sluggulas. Bombs don't work // or (state.can_use_bombs(player) and state.has('Magic Powder'), player)
|
add_rule(world.get_entrance('Mire Cross ES', player), lambda state: state.can_kill_most_things(player)) # 4 Sluggulas. Bombs don't work // or (state.can_use_bombs(player) and state.has('Magic Powder'), player)
|
||||||
|
if world.get_entrance('Mire Cross SW', player).door.trapped:
|
||||||
|
add_rule(world.get_entrance('Mire Cross SW', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
|
||||||
enemy_kill_drops = [ # Location, bool-bombable
|
enemy_kill_drops = [ # Location, bool-bombable
|
||||||
('Hyrule Castle - Map Guard Key Drop', True),
|
('Hyrule Castle - Map Guard Key Drop', True),
|
||||||
@@ -1399,6 +1460,9 @@ def swordless_rules(world, player):
|
|||||||
set_rule(world.get_entrance('Tower Altar NW', player), lambda state: True)
|
set_rule(world.get_entrance('Tower Altar NW', player), lambda state: True)
|
||||||
set_rule(world.get_entrance('Skull Vines NW', player), lambda state: True)
|
set_rule(world.get_entrance('Skull Vines NW', player), lambda state: True)
|
||||||
set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))
|
set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))
|
||||||
|
if world.get_entrance('Ice Lobby SE', player).door.trapped:
|
||||||
|
set_rule(world.get_entrance('Ice Lobby SE', player),
|
||||||
|
lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))
|
||||||
set_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))
|
set_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))
|
||||||
|
|
||||||
set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player))
|
set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player))
|
||||||
@@ -1412,7 +1476,7 @@ def swordless_rules(world, player):
|
|||||||
if not world.is_atgt_swapped(player):
|
if not world.is_atgt_swapped(player):
|
||||||
set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player)) # barrier gets removed after killing agahnim, rule for that added later
|
set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player)) # barrier gets removed after killing agahnim, rule for that added later
|
||||||
|
|
||||||
|
# todo: new traps
|
||||||
std_kill_rooms = {
|
std_kill_rooms = {
|
||||||
'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES'], # One green guard
|
'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES'], # One green guard
|
||||||
'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'], # One blue guard
|
'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'], # One blue guard
|
||||||
@@ -1443,6 +1507,18 @@ std_kill_rooms = {
|
|||||||
'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE'] # Wizzrobes. Bombs don't work
|
'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE'] # Wizzrobes. Bombs don't work
|
||||||
} # all trap rooms?
|
} # all trap rooms?
|
||||||
|
|
||||||
|
std_kill_doors_if_trapped = {
|
||||||
|
'Hyrule Dungeon Armory Main': 'Hyrule Dungeon Armory Interior Key Door N',
|
||||||
|
# 'Eastern Single Eyegore ES', # arrow rule is sufficient
|
||||||
|
# 'Eastern Duo Eyegores S', # arrow rule is sufficient
|
||||||
|
'TR Twin Pokeys': 'TR Twin Pokeys NW',
|
||||||
|
'Thieves Basement Block': 'Thieves Blocked Entry SW',
|
||||||
|
'Desert Compass Room': 'Desert Compass Key Door WN',
|
||||||
|
'Mire Cross': 'Mire Cross SW',
|
||||||
|
'Tower Circle of Pots': 'Tower Circle of Pots ES',
|
||||||
|
# 'Ice Lobby S' # can melt rule is sufficient
|
||||||
|
}
|
||||||
|
|
||||||
def add_connection(parent_name, target_name, entrance_name, world, player):
|
def add_connection(parent_name, target_name, entrance_name, world, player):
|
||||||
parent = world.get_region(parent_name, player)
|
parent = world.get_region(parent_name, player)
|
||||||
target = world.get_region(target_name, player)
|
target = world.get_region(target_name, player)
|
||||||
@@ -1495,6 +1571,10 @@ def standard_rules(world, player):
|
|||||||
if region.name in std_kill_rooms:
|
if region.name in std_kill_rooms:
|
||||||
for ent in std_kill_rooms[region.name]:
|
for ent in std_kill_rooms[region.name]:
|
||||||
add_rule(world.get_entrance(ent, player), lambda state: standard_escape_rule(state))
|
add_rule(world.get_entrance(ent, player), lambda state: standard_escape_rule(state))
|
||||||
|
if region.name in std_kill_doors_if_trapped:
|
||||||
|
ent = world.get_entrance(std_kill_doors_if_trapped[region.name], player)
|
||||||
|
if ent.door.trapped:
|
||||||
|
add_rule(ent, lambda state: standard_escape_rule(state))
|
||||||
|
|
||||||
set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player))
|
set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player))
|
||||||
set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), lambda state: state.has('Zelda Herself', player))
|
set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), lambda state: state.has('Zelda Herself', player))
|
||||||
@@ -1685,6 +1765,11 @@ def set_bunny_rules(world, player, inverted):
|
|||||||
if bunny_exit.connected_region and is_bunny(bunny_exit.parent_region):
|
if bunny_exit.connected_region and is_bunny(bunny_exit.parent_region):
|
||||||
add_rule(bunny_exit, get_rule_to_add(bunny_exit.parent_region))
|
add_rule(bunny_exit, get_rule_to_add(bunny_exit.parent_region))
|
||||||
|
|
||||||
|
for ent_name in bunny_impassible_if_trapped:
|
||||||
|
bunny_exit = world.get_entrance(ent_name, player)
|
||||||
|
if bunny_exit.door.trapped and is_bunny(bunny_exit.parent_region):
|
||||||
|
add_rule(bunny_exit, get_rule_to_add(bunny_exit.parent_region))
|
||||||
|
|
||||||
doors_to_check = [x for x in world.doors if x.player == player and x not in bunny_impassible_doors]
|
doors_to_check = [x for x in world.doors if x.player == player and x not in bunny_impassible_doors]
|
||||||
doors_to_check = [x for x in doors_to_check if x.type in [DoorType.Normal, DoorType.Interior] and not x.blocked]
|
doors_to_check = [x for x in doors_to_check if x.type in [DoorType.Normal, DoorType.Interior] and not x.blocked]
|
||||||
for door in doors_to_check:
|
for door in doors_to_check:
|
||||||
@@ -1816,6 +1901,17 @@ bunny_impassible_doors = {
|
|||||||
'GT Validation Block Path'
|
'GT Validation Block Path'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bunny_impassible_if_trapped = {
|
||||||
|
'Hyrule Dungeon Armory Interior Key Door N', 'Eastern Pot Switch WN', 'Eastern Lobby NW',
|
||||||
|
'Eastern Lobby NE', 'Desert Compass Key Door WN', 'Tower Circle of Pots ES', 'PoD Mimics 2 SW',
|
||||||
|
'PoD Middle Cage S', 'Swamp Push Statue S', 'Skull 2 East Lobby WS', 'Skull Torch Room WS',
|
||||||
|
'Thieves Conveyor Maze WN', 'Thieves Conveyor Maze SW', 'Thieves Blocked Entry SW', 'Ice Bomb Jump NW',
|
||||||
|
'Ice Tall Hint EN', 'Ice Switch Room ES', 'Ice Switch Room NE', 'Mire Cross SW',
|
||||||
|
'Mire Tile Room SW', 'Mire Tile Room ES', 'TR Twin Pokeys NW', 'TR Torches WN', 'GT Hope Room WN',
|
||||||
|
'GT Speed Torch NE', 'GT Speed Torch WS', 'GT Torch Cross WN', 'GT Hidden Spikes SE', 'GT Conveyor Cross EN',
|
||||||
|
'GT Speed Torch WN', 'Ice Lobby SE'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def add_key_logic_rules(world, player):
|
def add_key_logic_rules(world, player):
|
||||||
key_logic = world.key_logic[player]
|
key_logic = world.key_logic[player]
|
||||||
|
|||||||
@@ -737,6 +737,13 @@ class bidict(dict):
|
|||||||
super(bidict, self).__delitem__(key)
|
super(bidict, self).__delitem__(key)
|
||||||
|
|
||||||
|
|
||||||
|
class HexInt(int): pass
|
||||||
|
|
||||||
|
def hex_representer(dumper, data):
|
||||||
|
import yaml
|
||||||
|
return yaml.ScalarNode('tag:yaml.org,2002:int', f"{data:#0{4}x}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# make_new_base2current()
|
# make_new_base2current()
|
||||||
# read_entrance_data(old_rom=sys.argv[1])
|
# read_entrance_data(old_rom=sys.argv[1])
|
||||||
|
|||||||
Binary file not shown.
@@ -1,8 +1,9 @@
|
|||||||
meta:
|
meta:
|
||||||
algorithm: balanced
|
algorithm: balanced
|
||||||
players: 1
|
players: 1
|
||||||
seed: 42
|
seed: 41 # note to self: seed 42 had an interesting Swamp Palace problem
|
||||||
names: Lonk
|
names: Lonk
|
||||||
|
notes: "Some notes specified by the user"
|
||||||
settings:
|
settings:
|
||||||
1:
|
1:
|
||||||
door_shuffle: basic
|
door_shuffle: basic
|
||||||
|
|||||||
@@ -52,6 +52,9 @@
|
|||||||
partial: 0
|
partial: 0
|
||||||
strict: 0
|
strict: 0
|
||||||
decoupledoors: off
|
decoupledoors: off
|
||||||
|
door_self_loops:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
dropshuffle:
|
dropshuffle:
|
||||||
on: 1
|
on: 1
|
||||||
off: 1
|
off: 1
|
||||||
@@ -134,6 +137,7 @@
|
|||||||
triforce_pool_min: 20
|
triforce_pool_min: 20
|
||||||
triforce_pool_max: 40
|
triforce_pool_max: 40
|
||||||
triforce_min_difference: 10
|
triforce_min_difference: 10
|
||||||
|
triforce_max_difference: 15
|
||||||
algorithm:
|
algorithm:
|
||||||
balanced: 12
|
balanced: 12
|
||||||
vanilla_fill: 1
|
vanilla_fill: 1
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ key_logic_algorithm:
|
|||||||
decoupledoors:
|
decoupledoors:
|
||||||
off: 9 # more strict
|
off: 9 # more strict
|
||||||
on: 1
|
on: 1
|
||||||
|
door_self_loops:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
dropshuffle:
|
dropshuffle:
|
||||||
on: 1
|
on: 1
|
||||||
off: 1
|
off: 1
|
||||||
@@ -86,6 +89,7 @@ triforce_goal_max: 30
|
|||||||
triforce_pool_min: 30
|
triforce_pool_min: 30
|
||||||
triforce_pool_max: 40
|
triforce_pool_max: 40
|
||||||
triforce_min_difference: 10
|
triforce_min_difference: 10
|
||||||
|
triforce_max_difference: 12
|
||||||
map_shuffle:
|
map_shuffle:
|
||||||
on: 1
|
on: 1
|
||||||
off: 1
|
off: 1
|
||||||
|
|||||||
@@ -259,6 +259,10 @@
|
|||||||
"action": "store_true",
|
"action": "store_true",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
},
|
},
|
||||||
|
"door_self_loops": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
"experimental": {
|
"experimental": {
|
||||||
"action": "store_true",
|
"action": "store_true",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
@@ -402,6 +406,7 @@
|
|||||||
"triforce_goal_min": {},
|
"triforce_goal_min": {},
|
||||||
"triforce_goal_max": {},
|
"triforce_goal_max": {},
|
||||||
"triforce_min_difference": {},
|
"triforce_min_difference": {},
|
||||||
|
"triforce_max_difference": {},
|
||||||
"custom": {
|
"custom": {
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"help": "suppress"
|
"help": "suppress"
|
||||||
@@ -575,5 +580,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"outputname": {},
|
"outputname": {},
|
||||||
|
"notes": {},
|
||||||
"code": {}
|
"code": {}
|
||||||
}
|
}
|
||||||
@@ -266,6 +266,7 @@
|
|||||||
"door_shuffle": [
|
"door_shuffle": [
|
||||||
"Select Door Shuffling Algorithm. (default: %(default)s)",
|
"Select Door Shuffling Algorithm. (default: %(default)s)",
|
||||||
"Basic: Doors are mixed within a single dungeon.",
|
"Basic: Doors are mixed within a single dungeon.",
|
||||||
|
"Paired Dungeon are paired (with one trio) and only mixed in those groups",
|
||||||
"Partitioned Doors are mixed in 3 partitions: L1-3+HC+AT, D1-4, D5-8",
|
"Partitioned Doors are mixed in 3 partitions: L1-3+HC+AT, D1-4, D5-8",
|
||||||
"Crossed: Doors are mixed between all dungeons.",
|
"Crossed: Doors are mixed between all dungeons.",
|
||||||
"Vanilla: All doors are connected the same way they were in the",
|
"Vanilla: All doors are connected the same way they were in the",
|
||||||
@@ -299,6 +300,7 @@
|
|||||||
"strict: Ensure small keys are available"
|
"strict: Ensure small keys are available"
|
||||||
],
|
],
|
||||||
"decoupledoors" : [ "Door entrances and exits are decoupled" ],
|
"decoupledoors" : [ "Door entrances and exits are decoupled" ],
|
||||||
|
"door_self_loops" : [ "Spiral stairs are allowed to self-loop" ],
|
||||||
"experimental": [ "Enable experimental features. (default: %(default)s)" ],
|
"experimental": [ "Enable experimental features. (default: %(default)s)" ],
|
||||||
"dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ],
|
"dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ],
|
||||||
"crystals_ganon": [
|
"crystals_ganon": [
|
||||||
|
|||||||
@@ -58,10 +58,12 @@
|
|||||||
"randomizer.dungeon.smallkeyshuffle.universal": "Universal",
|
"randomizer.dungeon.smallkeyshuffle.universal": "Universal",
|
||||||
"randomizer.dungeon.bigkeyshuffle": "Big Keys",
|
"randomizer.dungeon.bigkeyshuffle": "Big Keys",
|
||||||
"randomizer.dungeon.decoupledoors": "Decouple Doors",
|
"randomizer.dungeon.decoupledoors": "Decouple Doors",
|
||||||
|
"randomizer.dungeon.door_self_loops": "Allow Self-Looping Spiral Stairs",
|
||||||
|
|
||||||
"randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle",
|
"randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle",
|
||||||
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",
|
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",
|
||||||
"randomizer.dungeon.dungeondoorshuffle.basic": "Basic",
|
"randomizer.dungeon.dungeondoorshuffle.basic": "Basic",
|
||||||
|
"randomizer.dungeon.dungeondoorshuffle.paired": "Paired",
|
||||||
"randomizer.dungeon.dungeondoorshuffle.partitioned": "Partitioned",
|
"randomizer.dungeon.dungeondoorshuffle.partitioned": "Partitioned",
|
||||||
"randomizer.dungeon.dungeondoorshuffle.crossed": "Crossed",
|
"randomizer.dungeon.dungeondoorshuffle.crossed": "Crossed",
|
||||||
|
|
||||||
@@ -269,7 +271,7 @@
|
|||||||
"randomizer.item.goal.triforcehunt": "Triforce Hunt",
|
"randomizer.item.goal.triforcehunt": "Triforce Hunt",
|
||||||
"randomizer.item.goal.trinity": "Trinity",
|
"randomizer.item.goal.trinity": "Trinity",
|
||||||
"randomizer.item.goal.crystals": "Crystals",
|
"randomizer.item.goal.crystals": "Crystals",
|
||||||
"randomizer.item.goal.ganonhunt": "Triforce Hunt + Ganon",
|
"randomizer.item.goal.ganonhunt": "Ganonhunt",
|
||||||
"randomizer.item.goal.completionist": "Completionist",
|
"randomizer.item.goal.completionist": "Completionist",
|
||||||
|
|
||||||
"randomizer.item.crystals_gt": "Crystals to open GT",
|
"randomizer.item.crystals_gt": "Crystals to open GT",
|
||||||
|
|||||||
@@ -66,6 +66,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"decoupledoors": {
|
"decoupledoors": {
|
||||||
|
"type": "checkbox",
|
||||||
|
"config": {
|
||||||
|
"padx": [20,0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"door_self_loops": {
|
||||||
"type": "checkbox",
|
"type": "checkbox",
|
||||||
"config": {
|
"config": {
|
||||||
"padx": [20,0],
|
"padx": [20,0],
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import urllib.parse
|
|||||||
import yaml
|
import yaml
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from yaml.representer import Representer
|
from yaml.representer import Representer
|
||||||
|
from Utils import HexInt, hex_representer
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ class CustomSettings(object):
|
|||||||
args.suppress_rom = get_setting(meta['suppress_rom'], args.suppress_rom)
|
args.suppress_rom = get_setting(meta['suppress_rom'], args.suppress_rom)
|
||||||
args.names = get_setting(meta['names'], args.names)
|
args.names = get_setting(meta['names'], args.names)
|
||||||
args.race = get_setting(meta['race'], args.race)
|
args.race = get_setting(meta['race'], args.race)
|
||||||
|
args.notes = get_setting(meta['user_notes'], args.notes)
|
||||||
self.player_range = range(1, args.multi + 1)
|
self.player_range = range(1, args.multi + 1)
|
||||||
if 'settings' in self.file_source:
|
if 'settings' in self.file_source:
|
||||||
for p in self.player_range:
|
for p in self.player_range:
|
||||||
@@ -121,6 +123,7 @@ class CustomSettings(object):
|
|||||||
args.trap_door_mode[p] = get_setting(settings['trap_door_mode'], args.trap_door_mode[p])
|
args.trap_door_mode[p] = get_setting(settings['trap_door_mode'], args.trap_door_mode[p])
|
||||||
args.key_logic_algorithm[p] = get_setting(settings['key_logic_algorithm'], args.key_logic_algorithm[p])
|
args.key_logic_algorithm[p] = get_setting(settings['key_logic_algorithm'], args.key_logic_algorithm[p])
|
||||||
args.decoupledoors[p] = get_setting(settings['decoupledoors'], args.decoupledoors[p])
|
args.decoupledoors[p] = get_setting(settings['decoupledoors'], args.decoupledoors[p])
|
||||||
|
args.door_self_loops[p] = get_setting(settings['door_self_loops'], args.door_self_loops[p])
|
||||||
args.dungeon_counters[p] = get_setting(settings['dungeon_counters'], args.dungeon_counters[p])
|
args.dungeon_counters[p] = get_setting(settings['dungeon_counters'], args.dungeon_counters[p])
|
||||||
args.crystals_gt[p] = get_setting(settings['crystals_gt'], args.crystals_gt[p])
|
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.crystals_ganon[p] = get_setting(settings['crystals_ganon'], args.crystals_ganon[p])
|
||||||
@@ -152,6 +155,12 @@ class CustomSettings(object):
|
|||||||
args.pseudoboots[p] = get_setting(settings['pseudoboots'], args.pseudoboots[p])
|
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_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_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])
|
args.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p])
|
||||||
|
|
||||||
# mystery usage
|
# mystery usage
|
||||||
@@ -221,14 +230,15 @@ class CustomSettings(object):
|
|||||||
return self.file_source['drops']
|
return self.file_source['drops']
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def create_from_world(self, world, race):
|
def create_from_world(self, world, settings):
|
||||||
self.player_range = range(1, world.players + 1)
|
self.player_range = range(1, world.players + 1)
|
||||||
settings_dict, meta_dict = {}, {}
|
settings_dict, meta_dict = {}, {}
|
||||||
self.world_rep['meta'] = meta_dict
|
self.world_rep['meta'] = meta_dict
|
||||||
meta_dict['players'] = world.players
|
meta_dict['players'] = world.players
|
||||||
meta_dict['algorithm'] = world.algorithm
|
meta_dict['algorithm'] = world.algorithm
|
||||||
meta_dict['seed'] = world.seed
|
meta_dict['seed'] = world.seed
|
||||||
meta_dict['race'] = race
|
meta_dict['race'] = settings.race
|
||||||
|
meta_dict['user_notes'] = settings.notes
|
||||||
self.world_rep['settings'] = settings_dict
|
self.world_rep['settings'] = settings_dict
|
||||||
for p in self.player_range:
|
for p in self.player_range:
|
||||||
settings_dict[p] = {}
|
settings_dict[p] = {}
|
||||||
@@ -247,6 +257,7 @@ class CustomSettings(object):
|
|||||||
settings_dict[p]['trap_door_mode'] = world.trap_door_mode[p]
|
settings_dict[p]['trap_door_mode'] = world.trap_door_mode[p]
|
||||||
settings_dict[p]['key_logic_algorithm'] = world.key_logic_algorithm[p]
|
settings_dict[p]['key_logic_algorithm'] = world.key_logic_algorithm[p]
|
||||||
settings_dict[p]['decoupledoors'] = world.decoupledoors[p]
|
settings_dict[p]['decoupledoors'] = world.decoupledoors[p]
|
||||||
|
settings_dict[p]['door_self_loops'] = world.door_self_loops[p]
|
||||||
settings_dict[p]['logic'] = world.logic[p]
|
settings_dict[p]['logic'] = world.logic[p]
|
||||||
settings_dict[p]['mode'] = world.mode[p]
|
settings_dict[p]['mode'] = world.mode[p]
|
||||||
settings_dict[p]['swords'] = world.swords[p]
|
settings_dict[p]['swords'] = world.swords[p]
|
||||||
@@ -342,7 +353,7 @@ class CustomSettings(object):
|
|||||||
for p in self.player_range:
|
for p in self.player_range:
|
||||||
if p in world.owswaps and len(world.owswaps[p][0]) > 0:
|
if p in world.owswaps and len(world.owswaps[p][0]) > 0:
|
||||||
flips[p] = {}
|
flips[p] = {}
|
||||||
flips[p]['force_flip'] = list(f for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80)
|
flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80)
|
||||||
flips[p]['force_flip'].sort()
|
flips[p]['force_flip'].sort()
|
||||||
flips[p]['undefined_chance'] = 0
|
flips[p]['undefined_chance'] = 0
|
||||||
|
|
||||||
@@ -408,6 +419,7 @@ class CustomSettings(object):
|
|||||||
|
|
||||||
def write_to_file(self, destination):
|
def write_to_file(self, destination):
|
||||||
yaml.add_representer(defaultdict, Representer.represent_dict)
|
yaml.add_representer(defaultdict, Representer.represent_dict)
|
||||||
|
yaml.add_representer(HexInt, hex_representer)
|
||||||
with open(destination, 'w') as file:
|
with open(destination, 'w') as file:
|
||||||
yaml.dump(self.world_rep, file)
|
yaml.dump(self.world_rep, file)
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ SETTINGSTOPROCESS = {
|
|||||||
"door_type_mode": "door_type_mode",
|
"door_type_mode": "door_type_mode",
|
||||||
"trap_door_mode": "trap_door_mode",
|
"trap_door_mode": "trap_door_mode",
|
||||||
"decoupledoors": "decoupledoors",
|
"decoupledoors": "decoupledoors",
|
||||||
|
"door_self_loops": "door_self_loops",
|
||||||
"experimental": "experimental",
|
"experimental": "experimental",
|
||||||
"dungeon_counters": "dungeon_counters",
|
"dungeon_counters": "dungeon_counters",
|
||||||
"mixed_travel": "mixed_travel",
|
"mixed_travel": "mixed_travel",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def generate_dungeon(builder, entrance_region_names, split_dungeon, world, playe
|
|||||||
queue = collections.deque(proposed_map.items())
|
queue = collections.deque(proposed_map.items())
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
a, b = queue.popleft()
|
a, b = queue.popleft()
|
||||||
if world.decoupledoors[player]:
|
if a == b or world.decoupledoors[player]:
|
||||||
connect_doors_one_way(a, b)
|
connect_doors_one_way(a, b)
|
||||||
else:
|
else:
|
||||||
connect_doors(a, b)
|
connect_doors(a, b)
|
||||||
@@ -128,14 +128,14 @@ def create_random_proposal(doors_to_connect, world, player):
|
|||||||
next_hook = random.choice(hooks_left)
|
next_hook = random.choice(hooks_left)
|
||||||
primary_door = random.choice(primary_bucket[next_hook])
|
primary_door = random.choice(primary_bucket[next_hook])
|
||||||
opp_hook, secondary_door = type_map[next_hook], None
|
opp_hook, secondary_door = type_map[next_hook], None
|
||||||
while (secondary_door is None or secondary_door == primary_door
|
while (secondary_door is None or (secondary_door == primary_door and not world.door_self_loops[player])
|
||||||
or decouple_check(primary_bucket[next_hook], secondary_bucket[opp_hook],
|
or decouple_check(primary_bucket[next_hook], secondary_bucket[opp_hook],
|
||||||
primary_door, secondary_door, world, player)):
|
primary_door, secondary_door, world, player)):
|
||||||
secondary_door = random.choice(secondary_bucket[opp_hook])
|
secondary_door = random.choice(secondary_bucket[opp_hook])
|
||||||
proposal[primary_door] = secondary_door
|
proposal[primary_door] = secondary_door
|
||||||
primary_bucket[next_hook].remove(primary_door)
|
primary_bucket[next_hook].remove(primary_door)
|
||||||
secondary_bucket[opp_hook].remove(secondary_door)
|
secondary_bucket[opp_hook].remove(secondary_door)
|
||||||
if not world.decoupledoors[player]:
|
if primary_door != secondary_door and not world.decoupledoors[player]:
|
||||||
proposal[secondary_door] = primary_door
|
proposal[secondary_door] = primary_door
|
||||||
primary_bucket[opp_hook].remove(secondary_door)
|
primary_bucket[opp_hook].remove(secondary_door)
|
||||||
secondary_bucket[next_hook].remove(primary_door)
|
secondary_bucket[next_hook].remove(primary_door)
|
||||||
@@ -200,11 +200,19 @@ def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_se
|
|||||||
unvisted_bucket[opp_hook].sort(key=lambda d: d.name)
|
unvisted_bucket[opp_hook].sort(key=lambda d: d.name)
|
||||||
new_door = random.choice(unvisted_bucket[opp_hook])
|
new_door = random.choice(unvisted_bucket[opp_hook])
|
||||||
old_target = proposed_map[attempt]
|
old_target = proposed_map[attempt]
|
||||||
proposed_map[attempt] = new_door
|
|
||||||
if not world.decoupledoors[player]:
|
if not world.decoupledoors[player]:
|
||||||
old_attempt = proposed_map[new_door]
|
old_attempt = proposed_map[new_door]
|
||||||
else:
|
else:
|
||||||
old_attempt = next(x for x in proposed_map if proposed_map[x] == new_door)
|
old_attempt = next(x for x in proposed_map if proposed_map[x] == new_door)
|
||||||
|
# ensure nothing gets messed up when something loops with itself
|
||||||
|
if attempt == old_target and old_attempt == new_door:
|
||||||
|
old_attempt = new_door
|
||||||
|
old_target = attempt
|
||||||
|
elif attempt == old_target:
|
||||||
|
old_target = old_attempt
|
||||||
|
elif old_attempt == new_door:
|
||||||
|
old_attempt = old_target
|
||||||
|
proposed_map[attempt] = new_door
|
||||||
proposed_map[old_attempt] = old_target
|
proposed_map[old_attempt] = old_target
|
||||||
if not world.decoupledoors[player]:
|
if not world.decoupledoors[player]:
|
||||||
proposed_map[old_target] = old_attempt
|
proposed_map[old_target] = old_attempt
|
||||||
|
|||||||
@@ -289,6 +289,7 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
|
|||||||
bomb_shop_options = [x for x in bomb_shop_options if x not in ['Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)']]
|
bomb_shop_options = [x for x in bomb_shop_options if x not in ['Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)']]
|
||||||
if avail.swapped and len(bomb_shop_options) > 1:
|
if avail.swapped and len(bomb_shop_options) > 1:
|
||||||
bomb_shop_options = [x for x in bomb_shop_options if x != 'Big Bomb Shop']
|
bomb_shop_options = [x for x in bomb_shop_options if x != 'Big Bomb Shop']
|
||||||
|
|
||||||
bomb_shop_choice = random.choice(bomb_shop_options)
|
bomb_shop_choice = random.choice(bomb_shop_options)
|
||||||
connect_entrance(bomb_shop_choice, bomb_shop, avail)
|
connect_entrance(bomb_shop_choice, bomb_shop, avail)
|
||||||
rem_entrances.remove(bomb_shop_choice)
|
rem_entrances.remove(bomb_shop_choice)
|
||||||
@@ -433,6 +434,8 @@ def do_blacksmith(entrances, exits, avail):
|
|||||||
if avail.swapped:
|
if avail.swapped:
|
||||||
blacksmith_options = [e for e in blacksmith_options if e not in Forbidden_Swap_Entrances]
|
blacksmith_options = [e for e in blacksmith_options if e not in Forbidden_Swap_Entrances]
|
||||||
blacksmith_options = [x for x in blacksmith_options if x in entrances]
|
blacksmith_options = [x for x in blacksmith_options if x in entrances]
|
||||||
|
|
||||||
|
assert len(blacksmith_options), 'No available entrances left to place Blacksmith'
|
||||||
blacksmith_choice = random.choice(blacksmith_options)
|
blacksmith_choice = random.choice(blacksmith_options)
|
||||||
connect_entrance(blacksmith_choice, 'Blacksmiths Hut', avail)
|
connect_entrance(blacksmith_choice, 'Blacksmiths Hut', avail)
|
||||||
entrances.remove(blacksmith_choice)
|
entrances.remove(blacksmith_choice)
|
||||||
@@ -563,6 +566,7 @@ def do_dark_sanc(entrances, exits, avail):
|
|||||||
choices = [e for e in avail.world.districts[avail.player]['Northwest Dark World'].entrances if e not in forbidden and e in entrances]
|
choices = [e for e in avail.world.districts[avail.player]['Northwest Dark World'].entrances if e not in forbidden and e in entrances]
|
||||||
else:
|
else:
|
||||||
choices = [e for e in get_starting_entrances(avail) if e not in forbidden and e in entrances]
|
choices = [e for e in get_starting_entrances(avail) if e not in forbidden and e in entrances]
|
||||||
|
|
||||||
choice = random.choice(choices)
|
choice = random.choice(choices)
|
||||||
entrances.remove(choice)
|
entrances.remove(choice)
|
||||||
exits.remove('Dark Sanctuary Hint')
|
exits.remove('Dark Sanctuary Hint')
|
||||||
@@ -596,6 +600,10 @@ def do_links_house(entrances, exits, avail, cross_world):
|
|||||||
if avail.inverted:
|
if avail.inverted:
|
||||||
dark_sanc_region = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player).connected_region.name
|
dark_sanc_region = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player).connected_region.name
|
||||||
forbidden.extend(get_nearby_entrances(avail, dark_sanc_region))
|
forbidden.extend(get_nearby_entrances(avail, dark_sanc_region))
|
||||||
|
else:
|
||||||
|
if (avail.world.doorShuffle[avail.player] != 'vanilla' and avail.world.intensity[avail.player] > 2
|
||||||
|
and not avail.world.is_tile_swapped(0x1b, avail.player)):
|
||||||
|
forbidden.append('Hyrule Castle Entrance (South)')
|
||||||
if avail.swapped:
|
if avail.swapped:
|
||||||
forbidden.append(links_house_vanilla)
|
forbidden.append(links_house_vanilla)
|
||||||
forbidden.extend(Forbidden_Swap_Entrances)
|
forbidden.extend(Forbidden_Swap_Entrances)
|
||||||
@@ -1381,13 +1389,17 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
|
|||||||
elif cave[-1] == 'Spectacle Rock Cave Exit': # Spectacle rock only has one exit
|
elif cave[-1] == 'Spectacle Rock Cave Exit': # Spectacle rock only has one exit
|
||||||
cave_entrances = []
|
cave_entrances = []
|
||||||
for cave_exit in rnd_cave[:-1]:
|
for cave_exit in rnd_cave[:-1]:
|
||||||
entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in must_exit)
|
if avail.swapped and cave_exit not in avail.exits:
|
||||||
cave_entrances.append(entrance)
|
entrance = avail.world.get_entrance(cave_exit, avail.player).parent_region.entrances[0].name
|
||||||
entrances.remove(entrance)
|
cave_entrances.append(entrance)
|
||||||
connect_two_way(entrance, cave_exit, avail)
|
else:
|
||||||
if avail.swapped and combine_map[entrance] != cave_exit:
|
entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in must_exit)
|
||||||
swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave)
|
cave_entrances.append(entrance)
|
||||||
entrances.remove(swap_ent)
|
entrances.remove(entrance)
|
||||||
|
connect_two_way(entrance, cave_exit, avail)
|
||||||
|
if avail.swapped and combine_map[entrance] != cave_exit:
|
||||||
|
swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave)
|
||||||
|
entrances.remove(swap_ent)
|
||||||
if entrance not in invalid_connections:
|
if entrance not in invalid_connections:
|
||||||
invalid_connections[exit] = set()
|
invalid_connections[exit] = set()
|
||||||
if all(entrance in invalid_connections for entrance in cave_entrances):
|
if all(entrance in invalid_connections for entrance in cave_entrances):
|
||||||
@@ -1408,13 +1420,16 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
|
|||||||
for cave in used_caves:
|
for cave in used_caves:
|
||||||
if cave in cave_options: # check if we placed multiple entrances from this 3 or 4 exit
|
if cave in cave_options: # check if we placed multiple entrances from this 3 or 4 exit
|
||||||
for cave_exit in cave:
|
for cave_exit in cave:
|
||||||
entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)])
|
if avail.swapped and cave_exit not in avail.exits:
|
||||||
invalid_cave_connections[tuple(cave)] = set()
|
continue
|
||||||
entrances.remove(entrance)
|
else:
|
||||||
connect_two_way(entrance, cave_exit, avail)
|
entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)])
|
||||||
if avail.swapped and combine_map[entrance] != cave_exit:
|
invalid_cave_connections[tuple(cave)] = set()
|
||||||
swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave)
|
entrances.remove(entrance)
|
||||||
entrances.remove(swap_ent)
|
connect_two_way(entrance, cave_exit, avail)
|
||||||
|
if avail.swapped and combine_map[entrance] != cave_exit:
|
||||||
|
swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave)
|
||||||
|
entrances.remove(swap_ent)
|
||||||
cave_options.remove(cave)
|
cave_options.remove(cave)
|
||||||
if avail.swapped:
|
if avail.swapped:
|
||||||
entrances.extend(swap_forbidden)
|
entrances.extend(swap_forbidden)
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ def roll_settings(weights):
|
|||||||
ret.trap_door_mode = get_choice('trap_door_mode')
|
ret.trap_door_mode = get_choice('trap_door_mode')
|
||||||
ret.key_logic_algorithm = get_choice('key_logic_algorithm')
|
ret.key_logic_algorithm = get_choice('key_logic_algorithm')
|
||||||
ret.decoupledoors = get_choice('decoupledoors') == 'on'
|
ret.decoupledoors = get_choice('decoupledoors') == 'on'
|
||||||
|
ret.door_self_loops = get_choice('door_self_loops') == 'on'
|
||||||
ret.experimental = get_choice('experimental') == 'on'
|
ret.experimental = get_choice('experimental') == 'on'
|
||||||
ret.collection_rate = get_choice('collection_rate') == 'on'
|
ret.collection_rate = get_choice('collection_rate') == 'on'
|
||||||
|
|
||||||
@@ -138,15 +139,14 @@ def roll_settings(weights):
|
|||||||
ret.crystals_gt = get_choice('tower_open')
|
ret.crystals_gt = get_choice('tower_open')
|
||||||
ret.crystals_ganon = get_choice('ganon_open')
|
ret.crystals_ganon = get_choice('ganon_open')
|
||||||
|
|
||||||
from ItemList import set_default_triforce
|
ret.triforce_pool = get_choice_default('triforce_pool', default=0)
|
||||||
default_tf_goal, default_tf_pool = set_default_triforce(ret.goal, 0, 0)
|
ret.triforce_goal = get_choice_default('triforce_goal', default=0)
|
||||||
goal_min = get_choice_default('triforce_goal_min', default=default_tf_goal)
|
ret.triforce_pool_min = get_choice_default('triforce_pool_min', default=0)
|
||||||
goal_max = get_choice_default('triforce_goal_max', default=default_tf_goal)
|
ret.triforce_pool_max = get_choice_default('triforce_pool_max', default=0)
|
||||||
pool_min = get_choice_default('triforce_pool_min', default=default_tf_pool)
|
ret.triforce_goal_min = get_choice_default('triforce_goal_min', default=0)
|
||||||
pool_max = get_choice_default('triforce_pool_max', default=default_tf_pool)
|
ret.triforce_goal_max = get_choice_default('triforce_goal_max', default=0)
|
||||||
ret.triforce_goal = random.randint(int(goal_min), int(goal_max))
|
ret.triforce_min_difference = get_choice_default('triforce_min_difference', default=0)
|
||||||
min_diff = get_choice_default('triforce_min_difference', default=(default_tf_pool-default_tf_goal))
|
ret.triforce_max_difference = get_choice_default('triforce_max_difference', default=10000)
|
||||||
ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max))
|
|
||||||
|
|
||||||
ret.mode = get_choice('world_state')
|
ret.mode = get_choice('world_state')
|
||||||
if ret.mode == 'retro':
|
if ret.mode == 'retro':
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
meta:
|
||||||
|
players: 1
|
||||||
|
settings:
|
||||||
|
1:
|
||||||
|
door_shuffle: basic
|
||||||
|
intensity: 3
|
||||||
|
door_type_mode: all
|
||||||
|
doors:
|
||||||
|
1:
|
||||||
|
doors:
|
||||||
|
PoD Mimics 2 SW:
|
||||||
|
type: Trap Door
|
||||||
|
# TR Twin Pokeys NW: # not possible due to trap flags
|
||||||
|
# type: Trap Door
|
||||||
|
Thieves Blocked Entry SW:
|
||||||
|
type: Trap Door
|
||||||
|
Hyrule Dungeon Armory Interior Key Door N:
|
||||||
|
type: Trap Door
|
||||||
|
Desert Compass Key Door WN:
|
||||||
|
type: Trap Door
|
||||||
|
TR Tile Room SE:
|
||||||
|
type: Trap Door
|
||||||
|
# Mire Cross SW: # not possible due to trap flags
|
||||||
|
# type: Trap Door
|
||||||
|
Tower Circle of Pots ES:
|
||||||
|
type: Trap Door
|
||||||
|
Eastern Single Eyegore ES:
|
||||||
|
type: Trap Door
|
||||||
|
Eastern Duo Eyegores SE:
|
||||||
|
type: Trap Door
|
||||||
|
Swamp Push Statue S:
|
||||||
|
type: Trap Door
|
||||||
|
# Skull 2 East Lobby WS: # currently not possible due to trap flags
|
||||||
|
# type: Trap Door
|
||||||
|
GT Hope Room WN :
|
||||||
|
type: Trap Door
|
||||||
|
|
||||||
|
# Eastern Courtyard Ledge S: # currently not possible due to trap flags
|
||||||
|
# type: Trap Door
|
||||||
|
Ice Switch Room ES :
|
||||||
|
type: Trap Door
|
||||||
|
Ice Switch Room NE :
|
||||||
|
type: Trap Door
|
||||||
|
Skull Torch Room WS :
|
||||||
|
type: Trap Door
|
||||||
|
GT Speed Torch NE :
|
||||||
|
type: Trap Door
|
||||||
|
GT Speed Torch WS :
|
||||||
|
type: Trap Door
|
||||||
|
GT Torch Cross WN :
|
||||||
|
type: Trap Door
|
||||||
|
Mire Tile Room SW :
|
||||||
|
type: Trap Door
|
||||||
|
Mire Tile Room ES :
|
||||||
|
type: Trap Door
|
||||||
|
TR Torches WN :
|
||||||
|
type: Trap Door
|
||||||
|
PoD Lobby N:
|
||||||
|
type: Trap Door
|
||||||
|
PoD Middle Cage S:
|
||||||
|
type: Trap Door
|
||||||
|
Ice Bomb Jump NW:
|
||||||
|
type: Trap Door
|
||||||
|
GT Hidden Spikes SE:
|
||||||
|
type: Trap Door
|
||||||
|
Ice Tall Hint EN:
|
||||||
|
type: Trap Door
|
||||||
|
GT Conveyor Cross EN:
|
||||||
|
type: Trap Door
|
||||||
|
Eastern Pot Switch WN:
|
||||||
|
type: Trap Door
|
||||||
|
Thieves Conveyor Maze WN:
|
||||||
|
type: Trap Door
|
||||||
|
# Thieves Conveyor Maze SW: #not possible due to 4 door limit
|
||||||
|
# type: Trap Door
|
||||||
|
Eastern Dark Square Key Door WN:
|
||||||
|
type: Trap Door
|
||||||
|
Eastern Lobby NW:
|
||||||
|
type: Trap Door
|
||||||
|
Eastern Lobby NE:
|
||||||
|
type: Trap Door
|
||||||
|
# Ice Cross Bottom SE: # not possible due to trap flags
|
||||||
|
# type: Trap Door
|
||||||
|
Desert Back Lobby S:
|
||||||
|
type: Trap Door
|
||||||
|
# Desert West S: need enough lobbies for basic, should otherwise work
|
||||||
|
# type: Trap Door
|
||||||
|
Desert West Lobby ES:
|
||||||
|
type: Trap Door
|
||||||
|
# Mire Hidden Shooters SE: # not possible due to trap flags
|
||||||
|
# type: Trap Door
|
||||||
|
# Mire Hidden Shooters ES: # not possible due to trap flags
|
||||||
|
# type: Trap Door
|
||||||
|
Mire Hidden Shooters WS:
|
||||||
|
type: Trap Door
|
||||||
|
Tower Dark Pits EN:
|
||||||
|
type: Trap Door
|
||||||
|
Tower Dark Maze ES:
|
||||||
|
type: Trap Door
|
||||||
|
TR Tongue Pull WS:
|
||||||
|
type: Trap Door
|
||||||
|
|
||||||
|
# Lower layer: not valid
|
||||||
|
# Sewers Pull Switch N:
|
||||||
|
# type: Trap Door
|
||||||
|
# PoD Sexy Statue W: # not possible due to trap flags and low layer too, so likely not an exception
|
||||||
|
# type: Trap Door
|
||||||
|
|
||||||
|
# Not valid due to disappearing somaria block
|
||||||
|
# Mire Dark Shooters SE:
|
||||||
|
# type: Trap Door
|
||||||
|
|
||||||
|
# These triggers don't open doors
|
||||||
|
# Ice Compass Room NE:
|
||||||
|
# type: Trap Door
|
||||||
|
# Hera Torches NE:
|
||||||
|
# type: Trap Door
|
||||||
|
# Mire Spikes WS:
|
||||||
|
# type: Trap Door
|
||||||
|
# Mire Spikes SW:
|
||||||
|
# type: Trap Door
|
||||||
|
# Mire Spikes NW:
|
||||||
|
# type: Trap Door
|
||||||
|
# Tower Room 03 WN:
|
||||||
|
# type: Trap Door
|
||||||
Reference in New Issue
Block a user