Merge branch 'OverworldShuffleDev' into OverworldShuffle

This commit is contained in:
codemann8
2024-12-17 07:52:40 -06:00
24 changed files with 316 additions and 174 deletions
+28 -13
View File
@@ -1658,14 +1658,15 @@ class Entrance(object):
'Missing Smith': ('Frog', True, False, True, True),
'Middle Aged Man': ('Dark Blacksmith Ruins', True, False, True, True),
'Old Man Drop Off': ('Lost Old Man', True, False, False, False),
#'Revealing Light': ('Suspicious Maiden', False, False, False, False) }
'Revealing Light': ('Suspicious Maiden', False, False, False, False)
}
if self.name in multi_step_locations:
if self not in state.path:
world = self.parent_region.world
step_location = world.get_location(multi_step_locations[self.name][0], self.player)
if step_location.can_reach(state) and self.can_reach_thru(state, step_location, multi_step_locations[self.name][1], multi_step_locations[self.name][2], multi_step_locations[self.name][3], multi_step_locations[self.name][4]) and self.access_rule(state):
multi_step_loc = multi_step_locations[self.name]
step_location = world.get_location(multi_step_loc[0], self.player)
if step_location.can_reach(state) and self.can_reach_thru(state, step_location, multi_step_loc[1], multi_step_loc[2], multi_step_loc[3], multi_step_loc[4]) and self.access_rule(state):
if not self in state.path:
path = state.path.get(step_location.parent_region, (step_location.parent_region.name, None))
item_name = step_location.item.name if step_location.item else 'Pick Up Item'
@@ -1729,7 +1730,8 @@ class Entrance(object):
# this is checked first as this often the shortest path
follower_region = start_region
if follower_region.type not in [RegionType.LightWorld, RegionType.DarkWorld]:
follower_region = [i for i in start_region.entrances if i.parent_region.name != 'Menu'][0].parent_region
ent_list = [e for e in start_region.entrances if e.parent_region.type != RegionType.Menu]
follower_region = ent_list[0].parent_region
if (follower_region.world.mode[self.player] != 'inverted') == (follower_region.type == RegionType.LightWorld):
from OverworldShuffle import get_mirror_edges
mirror_map = get_mirror_edges(follower_region.world, follower_region, self.player)
@@ -1747,7 +1749,8 @@ class Entrance(object):
path = (follower_region.name, (mirror_exit, path))
item_name = step_location.item.name if step_location.item else 'Pick Up Item'
if start_region.name != follower_region.name:
path = (start_region.name, (start_region.entrances[0].name, path))
ent_list = [e for e in start_region.entrances if e.parent_region.type != RegionType.Menu]
path = (start_region.name, (ent_list[0].name, path))
path = (f'{step_location.parent_region.name} Exit', ('Leave Item Area', (item_name, path)))
else:
path = (item_name, path)
@@ -1758,25 +1761,36 @@ class Entrance(object):
path = (self.parent_region.name, path)
state.path[self] = (self.name, path)
if not found:
# check normal paths
traverse_paths(start_region.entrances[0].parent_region, self.parent_region)
for ent in start_region.entrances:
if not found:
# check normal paths
if ent.parent_region.type != RegionType.Menu and (ent.parent_region.type == start_region.type or \
ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] or not ignore_underworld):
traverse_paths(ent.parent_region, self.parent_region)
if not found and allow_save_quit:
# check paths involving save and quit
exit = self.parent_region.world.get_entrance('Links House S&Q', self.player)
traverse_paths(exit.connected_region, self.parent_region, [exit])
if not found:
world = self.parent_region.world
exit = world.get_entrance('Sanctuary S&Q', self.player)
# only allow save and quit at Sanc if the lobby is guaranteed vanilla
if exit.connected_region != 'Sanctuary' or world.mode[self.player] == 'standard' \
or world.doorShuffle[self.player] == 'vanilla' or world.intensity[self.player] < 3:
traverse_paths(exit.connected_region, self.parent_region, [exit])
if not found and allow_mirror_reentry and state.has_Mirror(self.player):
# check for paths using mirror portal re-entry at location of final destination
# this is checked last as this is the most complicated/exhaustive check
follower_region = start_region
if follower_region.type not in [RegionType.LightWorld, RegionType.DarkWorld]:
follower_region = start_region.entrances[0].parent_region
ent_list = [e for e in start_region.entrances if e.parent_region.type != RegionType.Menu]
follower_region = ent_list[0].parent_region
if (follower_region.world.mode[self.player] != 'inverted') == (follower_region.type == RegionType.LightWorld):
dest_region = self.parent_region
if dest_region.type not in [RegionType.LightWorld, RegionType.DarkWorld]:
dest_region = start_region.entrances[0].parent_region
dest_region = dest_region.entrances[0].parent_region
if (dest_region.world.mode[self.player] != 'inverted') != (dest_region.type == RegionType.LightWorld):
# loop thru potential places to leave a mirror portal
from OverworldShuffle import get_mirror_edges
@@ -1829,7 +1843,8 @@ class Entrance(object):
self.target = target
self.addresses = addresses
self.vanilla = vanilla
region.entrances.append(self)
if self not in region.entrances:
region.entrances.append(self)
def __str__(self):
return str(self.__unicode__())
@@ -3119,9 +3134,9 @@ class Spoiler(object):
self.bosses[str(player)] = OrderedDict()
self.bosses[str(player)]["Eastern Palace"] = self.world.get_dungeon("Eastern Palace", player).boss.name
self.bosses[str(player)]["Desert Palace"] = self.world.get_dungeon("Desert Palace", player).boss.name
self.bosses[str(player)]["Tower Of Hera"] = self.world.get_dungeon("Tower of Hera", player).boss.name
self.bosses[str(player)]["Tower of Hera"] = self.world.get_dungeon("Tower of Hera", player).boss.name
self.bosses[str(player)]["Hyrule Castle"] = "Agahnim"
self.bosses[str(player)]["Palace Of Darkness"] = self.world.get_dungeon("Palace of Darkness", player).boss.name
self.bosses[str(player)]["Palace of Darkness"] = self.world.get_dungeon("Palace of Darkness", player).boss.name
self.bosses[str(player)]["Swamp Palace"] = self.world.get_dungeon("Swamp Palace", player).boss.name
self.bosses[str(player)]["Skull Woods"] = self.world.get_dungeon("Skull Woods", player).boss.name
self.bosses[str(player)]["Thieves Town"] = self.world.get_dungeon("Thieves Town", player).boss.name
+9
View File
@@ -1,5 +1,14 @@
# Changelog
## 0.5.0.6
- New improved Pseudoboots behavior
- Fixed issue with missing Blue Potion in Dark Lake Shop in Inverted
- Various generation and logic fixes for HMG/NL
- Fixed error with prize plando
- Fixed error with District ER
- Added TT Maiden pathing to spoiler log
- Corrected and improved some pathing logic
## 0.5.0.5
- Fixed issue with vanilla HCBK acting like a small key
+1
View File
@@ -3792,6 +3792,7 @@ logical_connections = [
('Thieves Conveyor Block Path', 'Thieves Conveyor Bridge'),
("Thieves Blind's Cell Door", "Thieves Blind's Cell Interior"),
("Thieves Blind's Cell Exit", "Thieves Blind's Cell"),
('Revealing Light', 'Revealing Light'),
('Thieves Town Boss', 'Thieves Boss Spoils'),
('Ice Cross Bottom Push Block Left', 'Ice Floor Switch'),
+1
View File
@@ -727,6 +727,7 @@ def create_doors(world, player):
create_door(player, 'Thieves Big Chest Room ES', Intr).dir(Ea, 0x44, Bot, High).small_key().pos(1),
create_door(player, 'Thieves Conveyor Block WN', Intr).dir(We, 0x44, Top, High).pos(0),
create_door(player, 'Thieves Trap EN', Intr).dir(Ea, 0x44, Left, Top).pos(0),
create_door(player, 'Revealing Light', Lgcl),
create_door(player, 'Thieves Town Boss', Lgcl),
create_door(player, 'Ice Lobby SE', Nrml).dir(So, 0x0e, Right, High).pos(2).portal(X, 0x00),
+2 -1
View File
@@ -139,7 +139,8 @@ thieves_regions = [
'Thieves Attic', 'Thieves Attic Hint', 'Thieves Attic Switch', 'Thieves Cricket Hall Left',
'Thieves Cricket Hall Right', 'Thieves Attic Window', 'Thieves Basement Block', 'Thieves Blocked Entry',
'Thieves Lonely Zazak', "Thieves Blind's Cell", "Thieves Blind's Cell Interior", 'Thieves Conveyor Bridge',
'Thieves Conveyor Block', 'Thieves Big Chest Room', 'Thieves Trap', 'Thieves Boss Spoils', 'Thieves Town Portal'
'Thieves Conveyor Block', 'Thieves Big Chest Room', 'Thieves Trap', 'Revealing Light',
'Thieves Boss Spoils', 'Thieves Town Portal'
]
ice_regions = [
+8 -8
View File
@@ -520,8 +520,8 @@ def set_up_take_anys(world, player, skip_adjustments=False):
world.regions.append(old_man_take_any)
world.dynamic_regions.append(old_man_take_any)
reg = regions.pop()
entrance = world.get_region(reg, player).entrances[0]
reg = world.get_region(regions.pop(), player)
entrance = next((e for e in reg.entrances if e.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]))
connect_entrance(world, entrance, old_man_take_any, player)
entrance.target = 0x58
old_man_take_any.shop = Shop(old_man_take_any, 0x0112, ShopType.TakeAny, 0xE2, True, not world.shopsanity[player], 32)
@@ -1669,16 +1669,16 @@ def get_item_and_event_flag(item, world, player, dungeon_pool, prize_set, prize_
item_player = player if len(item_parts) < 2 else int(item_parts[1])
item_name = item_parts[0]
event_flag = False
if is_dungeon_item(item_name, world, item_player):
item_to_place = next(x for x in dungeon_pool
if x.name == item_name and x.player == item_player)
dungeon_pool.remove(item_to_place)
event_flag = True
elif item_name in prize_set:
if item_name in prize_set:
item_player = player # prizes must be for that player
item_to_place = ItemFactory(item_name, item_player)
prize_pool.remove(item_name)
event_flag = True
elif is_dungeon_item(item_name, world, item_player):
item_to_place = next(x for x in dungeon_pool
if x.name == item_name and x.player == item_player)
dungeon_pool.remove(item_to_place)
event_flag = True
else:
matcher = lambda x: x.name == item_name and x.player == item_player
if item_name == 'Bottle':
+28 -12
View File
@@ -28,7 +28,7 @@ from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dunge
from Fill import dungeon_tracking
from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations, set_prize_drops
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops, fill_specific_items, create_farm_locations
from UnderworldGlitchRules import create_hybridmajor_connections, create_hybridmajor_connectors
from UnderworldGlitchRules import create_hybridmajor_connections, get_hybridmajor_connection_entrances
from Utils import output_path, parse_player_names
from source.item.District import init_districts
@@ -176,8 +176,6 @@ def main(args, seed=None, fish=None):
world.data_tables[player] = init_data_tables(world, player)
place_bosses(world, player)
randomize_enemies(world, player)
if world.logic[player] in ('nologic', 'hybridglitches'):
create_hybridmajor_connections(world, player)
adjust_locations(world, player)
if any(world.potshuffle.values()):
@@ -204,8 +202,6 @@ def main(args, seed=None, fish=None):
for player in range(1, world.players + 1):
link_entrances_new(world, player)
if world.logic[player] in ('nologic', 'hybridglitches'):
create_hybridmajor_connectors(world, player)
logger.info(world.fish.translate("cli", "cli", "shuffling.prep"))
@@ -227,6 +223,8 @@ def main(args, seed=None, fish=None):
create_farm_locations(world, player)
for player in range(1, world.players + 1):
if world.logic[player] in ('nologic', 'hybridglitches'):
create_hybridmajor_connections(world, player)
generate_itempool(world, player)
verify_item_pool_config(world)
@@ -372,6 +370,10 @@ def main(args, seed=None, fish=None):
if 'debug' in world.spoiler.settings:
world.spoiler.extras(output_path(f'{outfilebase}_Spoiler.txt'))
player_logics = set(world.logic.values())
if len(player_logics) == 1 and 'nologic' in player_logics:
args.skip_playthrough = True
if not args.skip_playthrough:
logger.info(world.fish.translate("cli","cli","calc.playthrough"))
create_playthrough(world)
@@ -624,8 +626,6 @@ def copy_world(world):
create_owg_connections(ret, player)
create_dynamic_exits(ret, player)
create_dungeon_regions(ret, player)
if world.logic[player] in ('nologic', 'hybridglitches'):
create_hybridmajor_connections(ret, player)
create_owedges(ret, player)
create_shops(ret, player)
#create_doors(ret, player)
@@ -656,6 +656,11 @@ def copy_world(world):
# connect copied world
copied_locations = {(loc.name, loc.player): loc for loc in ret.get_locations()} # caches all locations
# We have to skip these for now. They require both the rest of the entrances _and_ the dungeon portals to be copied first
# We will connect them later
hmg_entrances = get_hybridmajor_connection_entrances()
for region in world.regions:
copied_region = ret.get_region(region.name, region.player)
copied_region.is_light_world = region.is_light_world
@@ -665,6 +670,8 @@ def copy_world(world):
for location in copied_region.locations:
location.parent_region = copied_region
for entrance in region.entrances:
if entrance.name in hmg_entrances:
continue
ret.get_entrance(entrance.name, entrance.player).connect(copied_region)
for exit in region.exits:
if exit.connected_region:
@@ -732,7 +739,16 @@ def copy_world(world):
categorize_world_regions(ret, player)
create_farm_locations(ret, player)
if world.logic[player] in ('nologic', 'hybridglitches'):
create_hybridmajor_connectors(ret, player)
create_hybridmajor_connections(ret, player)
for region in world.regions:
copied_region = ret.get_region(region.name, region.player)
for entrance in region.entrances:
if entrance.name not in hmg_entrances:
continue
ret.get_entrance(entrance.name, entrance.player).connect(copied_region)
for player in range(1, world.players + 1):
set_rules(ret, player)
return ret
@@ -823,8 +839,6 @@ def copy_world_premature(world, player):
create_owg_connections(ret, player)
create_dynamic_exits(ret, player)
create_dungeon_regions(ret, player)
if world.logic[player] in ('nologic', 'hybridglitches'):
create_hybridmajor_connections(ret, player)
create_owedges(ret, player)
create_shops(ret, player)
create_doors(ret, player)
@@ -849,7 +863,9 @@ def copy_world_premature(world, player):
for location in copied_region.locations:
location.parent_region = copied_region
for entrance in region.entrances:
copied_region.entrances.append(ret.get_entrance(entrance.name, entrance.player))
ent = ret.get_entrance(entrance.name, entrance.player)
if ent not in copied_region.entrances:
copied_region.entrances.append(ent)
for exit in region.exits:
if exit.connected_region:
dest_region = ret.get_region(exit.connected_region.name, region.player)
@@ -884,7 +900,7 @@ def copy_world_premature(world, player):
connect_portal(portal, ret, player)
if world.logic[player] in ('nologic', 'hybridglitches'):
create_hybridmajor_connectors(ret, player)
create_hybridmajor_connections(ret, player)
set_rules(ret, player)
+11 -3
View File
@@ -2,7 +2,7 @@
Helper functions to deliver entrance/exit/region sets to OWG rules.
"""
from BaseClasses import Entrance
from BaseClasses import Entrance, Region
from OWEdges import OWTileRegions
# Cave regions that superbunny can get through - but only with a sword.
@@ -327,10 +327,18 @@ def add_additional_rule(entrance, rule):
entrance.access_rule = lambda state: old_rule(state) and rule(state)
def create_no_logic_connections(player, world, connections):
def create_no_logic_connections(player, world, connections, connect_external=False):
for entrance, parent_region, target_region, *_ in connections:
parent = world.get_region(parent_region, player)
target = world.get_region(target_region, player)
if isinstance(target_region, Region):
target_region = target_region.name
if connect_external and target_region.endswith(" Portal"):
target = world.get_portal(target_region[:-7], player).find_portal_entrance().parent_region
else:
target = world.get_region(target_region, player)
connection = Entrance(player, entrance, parent)
connection.spot_type = 'OWG'
parent.exits.append(connection)
+1 -1
View File
@@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType
from OverworldGlitchRules import create_owg_connections
from Utils import bidict
version_number = '0.5.0.5'
version_number = '0.5.0.6'
# branch indicator is intentionally different across branches
version_branch = ''
+3 -2
View File
@@ -718,7 +718,8 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Thieves Compass Room', 'Thieves\' Town', ['Thieves\' Town - Compass Chest'], ['Thieves Compass Room NW Edge', 'Thieves Compass Room N Edge', 'Thieves Compass Room WS Edge', 'Thieves Compass Room W']),
create_dungeon_region(player, 'Thieves Big Chest Nook', 'Thieves\' Town', ['Thieves\' Town - Big Key Chest'], ['Thieves Big Chest Nook ES Edge']),
create_dungeon_region(player, 'Thieves Hallway', 'Thieves\' Town', ['Thieves\' Town - Hallway Pot Key'], ['Thieves Hallway SE', 'Thieves Hallway NE', 'Thieves Hallway WN', 'Thieves Hallway WS']),
create_dungeon_region(player, 'Thieves Boss', 'Thieves\' Town', ['Revealing Light'], ['Thieves Boss SE', 'Thieves Town Boss']),
create_dungeon_region(player, 'Thieves Boss', 'Thieves\' Town', None, ['Thieves Boss SE', 'Revealing Light']),
create_dungeon_region(player, 'Revealing Light', 'Thieves\' Town', ['Revealing Light'], ['Thieves Town Boss']),
create_dungeon_region(player, 'Thieves Boss Spoils', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize', 'Thieves\' Town - Boss Kill']),
create_dungeon_region(player, 'Thieves Pot Alcove Mid', 'Thieves\' Town', None, ['Thieves Pot Alcove Mid ES', 'Thieves Pot Alcove Mid WS']),
create_dungeon_region(player, 'Thieves Pot Alcove Bottom', 'Thieves\' Town', None, ['Thieves Pot Alcove Bottom SW']),
@@ -1161,7 +1162,7 @@ def create_shops(world, player):
world.shops[player] = []
for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram) in shop_table.items():
if world.mode[player] == 'inverted':
if (not world.is_tile_swapped(0x35, player) and region_name == 'Dark Lake Hylia Shop') \
if (world.is_tile_swapped(0x35, player) and region_name == 'Dark Lake Hylia Shop') \
or (not world.is_tile_swapped(0x35, player) and region_name == 'Lake Hylia Shop'):
locked = True
inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)]
+1 -1
View File
@@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '2d658871687c966f35dd9597a4195256'
RANDOMIZERBASEHASH = 'a51679f0e31ee29f59247545d705992a'
class JsonRom(object):
+2 -1
View File
@@ -98,7 +98,8 @@ def set_rules(world, player):
# These rules go here because they overwrite/add to some of the above rules
if world.logic[player] == 'hybridglitches':
underworld_glitches_rules(world, player)
if not world.is_copied_world:
underworld_glitches_rules(world, player)
def mirrorless_path_to_location(world, startName, targetName, player):
# If Agahnim is defeated then the courtyard needs to be accessible without using the mirror for the mirror offset glitch.
+130 -117
View File
@@ -1,17 +1,19 @@
from BaseClasses import Entrance
import functools
from BaseClasses import Entrance, DoorType
from DoorShuffle import connect_simple_door
import Rules
from OverworldGlitchRules import create_no_logic_connections
from Doors import create_door
kikiskip_spots = [
("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Portal")
]
mireheraswamp_spots = [
("Mire to Hera Clip", "Mire Torches Top", "Hera Portal"),
("Hera to Swamp Clip", "Mire Torches Top", "Swamp Portal"),
]
mirehera_spots = [("Mire to Hera Clip", "Mire Torches Top", "Hera Portal")]
icepalace_spots = [("Ice Lobby Clip", "Ice Portal", "Ice Bomb Drop")]
heraswamp_spots = [("Hera to Swamp Clip", "Mire Torches Top", "Swamp Portal")]
icepalace_spots = [("Ice Lobby Clip", "Ice Portal", "Ice Bomb Drop - Top")]
thievesdesert_spots = [
("Thieves to Desert West Clip", "Thieves Attic", "Desert West Portal"),
@@ -27,74 +29,46 @@ paradox_spots = [
("Paradox Front Teleport", "Paradox Cave Front", "Paradox Cave Chest Area")
]
# We need to make connectors at a separate time from the connections, because of how dungeons are linked to regions
kikiskip_connectors = [
("Kiki Skip Connector", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Exit")
]
mireheraswamp_connectors = [
("Mire to Hera Connector", "Mire Torches Top", "Tower of Hera Exit"),
("Mire to Swamp Connector", "Mire Torches Top", "Swamp Palace Exit"),
]
thievesdesert_connectors = [
("Thieves to Desert West Connector", "Thieves Attic", "Desert Palace Exit (West)"),
(
"Thieves to Desert South Connector",
"Thieves Attic",
"Desert Palace Exit (South)",
),
("Thieves to Desert East Connector", "Thieves Attic", "Desert Palace Exit (East)"),
]
specrock_connectors = [
(
"Spec Rock Top Connector",
"Spectacle Rock Cave (Peak)",
"Spectacle Rock Cave Exit (Top)",
),
(
"Spec Rock Exit Connector",
"Spectacle Rock Cave (Peak)",
"Spectacle Rock Cave Exit",
),
]
# Create connections between dungeons/locations
def create_hybridmajor_connections(world, player):
fix_fake_worlds = world.fix_fake_world[player]
for spots in [
kikiskip_spots,
mireheraswamp_spots,
mirehera_spots,
heraswamp_spots,
icepalace_spots,
thievesdesert_spots,
specrock_spots,
paradox_spots,
]:
create_no_logic_connections(player, world, spots)
create_no_logic_connections(player, world, spots, connect_external=fix_fake_worlds)
# Add the new Ice path (back of bomb drop to front) to the world and model it properly
clip_door = create_door(player, "Ice Bomb Drop Clip", DoorType.Logical)
world.doors += [clip_door]
world.initialize_doors([clip_door])
# Turn dungeons into connectors
def create_hybridmajor_connectors(world, player):
for connectors in [
kikiskip_connectors,
mireheraswamp_connectors,
thievesdesert_connectors,
specrock_connectors,
]:
new_connectors = [
(
connector[0],
connector[1],
world.get_entrance(connector[2], player).connected_region,
)
for connector in connectors
]
new_connectors = [c for c in new_connectors if c[2] is not None]
create_no_logic_connections(player, world, new_connectors)
ice_bomb_top_reg = world.get_region("Ice Bomb Drop - Top", player)
ice_bomb_top_reg.exits.append(
Entrance(player, "Ice Bomb Drop Clip", ice_bomb_top_reg)
)
connect_simple_door(world, "Ice Bomb Drop Clip", "Ice Bomb Drop", player)
def get_hybridmajor_connection_entrances():
connections = []
for connector in (
kikiskip_spots
+ mirehera_spots
+ heraswamp_spots
+ icepalace_spots
+ thievesdesert_spots
+ specrock_spots
+ paradox_spots
):
connections.append(connector[0])
connections.append('Ice Bomb Drop Clip')
return set(connections)
# For some entrances, we need to fake having pearl, because we're in fake DW/LW.
@@ -110,19 +84,40 @@ def fake_pearl_state(state, player):
# Sets the rules on where we can actually go using this clip.
# Behavior differs based on what type of ER shuffle we're playing.
def dungeon_reentry_rules(
world, player, clip: Entrance, dungeon_region: str, dungeon_exit: str
world,
player,
clip: Entrance,
dungeon_region: str,
):
fix_dungeon_exits = world.fix_palaceofdarkness_exit[player]
fix_fake_worlds = world.fix_fake_world[player]
all_clips = [
x[0]
for x in kikiskip_spots
+ mirehera_spots
+ heraswamp_spots
+ icepalace_spots
+ thievesdesert_spots
+ specrock_spots
+ paradox_spots
]
dungeon_entrance = [
r
for r in world.get_region(dungeon_region, player).entrances
if r.name != clip.name
if r.name not in all_clips
][0]
if (
not fix_dungeon_exits
): # vanilla, simple, restricted, dungeonssimple; should never have fake worlds fix
dungeon_exit = [
r
for r in world.get_region(dungeon_region, player).exits
if r.name not in all_clips
][0]
if not fix_dungeon_exits:
# vanilla, simple, restricted, dungeonssimple; should never have fake worlds fix
# Dungeons are only shuffled among themselves. We need to check SW, MM, and AT because they can't be reentered trivially.
# entrance doesn't exist until you fire rod it from the other side
@@ -173,32 +168,66 @@ def dungeon_reentry_rules(
def underworld_glitches_rules(world, player):
# Ice Palace Entrance Clip, needs bombs or cane of somaria to exit bomb drop room
Rules.add_rule(
world.get_entrance("Ice Bomb Drop SE", player),
lambda state: state.can_dash_clip(world.get_region("Ice Lobby", player), player)
and (state.can_use_bombs(player) or state.has("Cane of Somaria", player)),
combine="or",
)
# Kiki Skip
kks = world.get_entrance("Kiki Skip", player)
Rules.set_rule(kks, lambda state: state.can_bomb_clip(kks.parent_region, player))
dungeon_reentry_rules(
world, player, kks, "Palace of Darkness Portal", "Palace of Darkness Exit"
)
# Mire -> Hera -> Swamp
def mire_clip(state):
return state.can_reach(
"Mire Torches Top", "Region", player
) and state.can_dash_clip(world.get_region("Mire Torches Top", player), player)
def hera_clip(state):
return state.can_reach("Hera 4F", "Region", player) and state.can_dash_clip(
world.get_region("Hera 4F", player), player
torches = world.get_region("Mire Torches Top", player)
return state.can_dash_clip(torches, player) or (
state.can_bomb_clip(torches, player) and state.has_fire_source(player)
)
def hera_clip(state):
hera = world.get_region("Hera 4F", player)
return state.can_bomb_clip(hera, player) or state.can_dash_clip(hera, player)
# We use these plus functool.partial because lambdas don't work in loops properly.
def bomb_clip(state, region, player):
return state.can_bomb_clip(region, player)
def dash_clip(state, region, player):
return state.can_dash_clip(region, player)
# Bomb clips
for clip in (
kikiskip_spots
+ icepalace_spots
+ thievesdesert_spots
+ specrock_spots
):
region = world.get_region(clip[1], player)
Rules.set_rule(
world.get_entrance(clip[0], player),
functools.partial(bomb_clip, region=region, player=player),
)
# Dash clips
for clip in icepalace_spots:
region = world.get_region(clip[1], player)
Rules.add_rule(
world.get_entrance(clip[0], player),
functools.partial(dash_clip, region=region, player=player),
combine="or",
)
for spot in kikiskip_spots + thievesdesert_spots:
dungeon_reentry_rules(
world,
player,
world.get_entrance(spot[0], player),
spot[2],
)
for clip in mirehera_spots:
Rules.set_rule(
world.get_entrance(clip[0], player),
lambda state: mire_clip(state),
)
# Need to be able to escape by hitting the switch from the back
Rules.set_rule(
world.get_entrance("Ice Bomb Drop Clip", player),
lambda state: (
state.can_use_bombs(player) or state.has("Cane of Somaria", player)
),
)
# Allow mire big key to be used in Hera
Rules.add_rule(
world.get_entrance("Hera Startile Corner NW", player),
lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player),
@@ -209,20 +238,10 @@ def underworld_glitches_rules(world, player):
lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player),
combine="or",
)
mire_to_hera = world.get_entrance("Mire to Hera Clip", player)
mire_to_swamp = world.get_entrance("Hera to Swamp Clip", player)
Rules.set_rule(mire_to_hera, mire_clip)
# This uses the mire clip because it's always expected to come from mire
Rules.set_rule(
mire_to_swamp, lambda state: mire_clip(state) and state.has("Flippers", player)
)
# Using the entrances for various ER types. Hera -> Swamp never matters because you can only logically traverse with the mire keys
dungeon_reentry_rules(
world, player, mire_to_hera, "Hera Lobby", "Tower of Hera Exit"
)
dungeon_reentry_rules(
world, player, mire_to_swamp, "Swamp Lobby", "Swamp Palace Exit"
world.get_entrance("Hera to Swamp Clip", player),
lambda state: mire_clip(state) and state.has("Flippers", player),
)
# We need to set _all_ swamp doors to be openable with mire keys, otherwise the small key can't be behind them - 6 keys because of Pots
# Flippers required for all of these doors to prevent locks when flooding
@@ -246,7 +265,6 @@ def underworld_glitches_rules(world, player):
and state.has("Flippers", player),
combine="or",
)
# Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has('Flippers', player), combine="or")
Rules.add_rule(
world.get_location("Trench 1 Switch", player),
@@ -272,16 +290,16 @@ def underworld_glitches_rules(world, player):
)
),
}
inverted = world.mode[player] == "inverted"
inverted_dm = (world.mode[player] == "inverted") != world.is_tile_swapped(0x03, player)
def hera_rule(state):
return (state.has("Moon Pearl", player) or not inverted) and rule_map.get(
return (state.has("Moon Pearl", player) or not inverted_dm) and rule_map.get(
world.get_entrance("Tower of Hera", player).connected_region.name,
lambda state: False,
)(state)
def gt_rule(state):
return (state.has("Moon Pearl", player) or inverted) and rule_map.get(
return (state.has("Moon Pearl", player) or inverted_dm) and rule_map.get(
world.get_entrance(("Ganons Tower"), player).connected_region.name,
lambda state: False,
)(state)
@@ -299,21 +317,16 @@ def underworld_glitches_rules(world, player):
lambda state: mirrorless_moat_rule(state),
combine="or",
)
desert_exits = ["West", "South", "East"]
for desert_exit in ["East", "South", "West"]:
for desert_exit in desert_exits:
Rules.add_rule(
world.get_entrance(f"Thieves to Desert {desert_exit} Connector", player),
world.get_entrance(f"Thieves to Desert {desert_exit} Clip", player),
lambda state: state.can_dash_clip(
world.get_region("Thieves Attic", player), player
),
)
dungeon_reentry_rules(
world,
player,
world.get_entrance(f"Thieves to Desert {desert_exit} Connector", player),
f"Desert {desert_exit} Portal",
f"Desert Palace Exit ({desert_exit})",
)
# Collecting left chests in Paradox Cave using a dash clip -> dash citrus, 1f right, teleport up
paradox_left_chests = [
Binary file not shown.
+5 -5
View File
@@ -21,7 +21,7 @@ UwGeneralDeny:
- [ 0x0016, 1, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper", "GreenMimic", "RedMimic", "Pikit" ] ] #"Swamp Palace - Pool - Zol 2"
- [ 0x0016, 2, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper", "GreenMimic", "RedMimic", "Pikit" ] ] #"Swamp Palace - Pool - Blue Bari"
- [ 0x0016, 3, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Pool - Zol 3"
- [ 0x0017, 5, [ "Beamos", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Bumper Room - Fire Bar (Clockwise)"
- [ 0x0017, 5, [ "Beamos", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower of Hera - Bumper Room - Fire Bar (Clockwise)"
- [ 0x0019, 0, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Palace of Darkness - Dark Maze - Kodongo 1"
- [ 0x0019, 1, [ "RollerVerticalUp" ] ] #"Palace of Darkness - Dark Maze - Kodongo 2"
- [ 0x0019, 2, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Palace of Darkness - Dark Maze - Kodongo 3"
@@ -46,11 +46,11 @@ UwGeneralDeny:
- [ 0x0026, 9, [ "RollerHorizontalRight", "Statue" ] ] #"Swamp Palace - Big Spoon - Kyameron"
- [ 0x0026, 10, [ "Statue" ] ] # multiple push statues in this room can cause issues
- [ 0x0026, 11, [ "Statue" ] ] # multiple push statues in this room can cause issues
- [ 0x0027, 0, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalLeft", "FirebarCW" ] ] #"Tower Of Hera - Petting Zoo - Mini Moldorm 1"
- [ 0x0027, 1, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Petting Zoo - Mini Moldorm 2"
- [ 0x0027, 2, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Petting Zoo - Mini Moldorm 3"
- [ 0x0027, 0, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalLeft", "FirebarCW" ] ] #"Tower of Hera - Petting Zoo - Mini Moldorm 1"
- [ 0x0027, 1, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower of Hera - Petting Zoo - Mini Moldorm 2"
- [ 0x0027, 2, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower of Hera - Petting Zoo - Mini Moldorm 3"
- [ 0x0027, 4, ["Bumper", "BigSpike", "AntiFairyCircle", "RollerVerticalDown", "RollerVerticalUp"]]
- [ 0x0027, 5, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Petting Zoo - Kodongo 1"
- [ 0x0027, 5, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Tower of Hera - Petting Zoo - Kodongo 1"
- [0x0028, 0, ["Raven", "Poe", "GreenZirro", "BlueZirro", "Swamola", "Zora"]]
- [0x0028, 1, ["Raven", "Poe", "GreenZirro", "BlueZirro", "Swamola", "Zora"]]
- [0x0028, 2, ["Raven", "Poe", "GreenZirro", "BlueZirro", "Swamola", "Zora"]]
+1 -1
View File
@@ -111,7 +111,7 @@ def resolve_districts(world):
visited = set()
while len(queue) > 0:
region = queue.pop()
if not region:
if region is None:
RuntimeError(f'No region connected to entrance: {ent.name} Likely a missing entry in OWExitTypes')
visited.add(region)
if region.type == RegionType.Cave:
+5 -4
View File
@@ -907,7 +907,7 @@ def figure_out_connectors(exits, avail, cross_world=True):
cave_list = list(Connector_List)
if avail.assumed_loose_caves or (not avail.skull_handled and (cross_world or not avail.world.is_tile_swapped(0x00, avail.player))):
skull_connector = [x for x in ['Skull Woods Second Section Exit (West)', 'Skull Woods Second Section Exit (East)'] if x in exits]
cave_list.extend(skull_connector)
cave_list.extend([skull_connector])
if avail.assumed_loose_caves or not avail.keep_drops_together:
cave_list.extend([[entrance_map[e]] for e in linked_drop_map.values() if 'Inverted ' not in e and 'Skull Woods ' not in e])
@@ -1631,7 +1631,8 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
cave_entrances = []
for cave_exit in rnd_cave[:-1]:
if avail.swapped and cave_exit not in avail.exits:
entrance = avail.world.get_entrance(cave_exit, avail.player).parent_region.entrances[0].name
entrance = avail.world.get_entrance(cave_exit, avail.player)
entrance = next((e for e in entrance.parent_region.entrances if e.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld])).name
cave_entrances.append(entrance)
else:
entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in must_exit
@@ -1917,8 +1918,8 @@ def connect_exit(exit_name, entrancename, avail):
exit.connected_region.entrances.remove(exit)
dest_region = entrance.parent_region
if dest_region.name == 'Pyramid Crack':
# Needs to logically exit into greater Pyramid Area
if dest_region.name in ['Pyramid Crack', 'GT Stairs']:
# Needs to logically exit into greater OW area
dest_region = entrance.parent_region.entrances[0].parent_region
exit.connect(dest_region, door_addresses[entrance.name][1], exit_ids[exit.name][1])
-2
View File
@@ -19,8 +19,6 @@ advanced_placements:
locations:
Link's House: True
Magic Bat: False
advanced_placements:
1:
- type: Verification
item: Progressive Glove
locations:
@@ -5,7 +5,6 @@ settings:
logic: hybridglitches
mode: inverted
shuffle: crossed
start_inventory:
1:
- Pegasus Boots
@@ -15,6 +14,8 @@ start_inventory:
placements:
1:
Desert Palace - Boss: Moon Pearl
Desert Palace - Prize: Green Pendant
Sahasrahla: Magic Mirror
entrances:
1:
two-way:
@@ -23,4 +24,6 @@ entrances:
Thieves Town: Thieves Town Exit
Hyrule Castle Entrance (East): Desert Palace Exit (South)
Hyrule Castle Entrance (West): Desert Palace Exit (North)
entrances:
Agahnims Tower: Pyramid Fairy
@@ -15,6 +15,9 @@ start_inventory:
- Ether
- Quake
- Bombos
- Big Key (Tower of Hera)
- Big Key (Desert Palace)
- Big Key (Eastern Palace)
advanced_placements:
1:
- type: Verification
+3 -2
View File
@@ -3,6 +3,7 @@ meta:
settings:
1:
logic: hybridglitches
shuffle: vanilla
start_inventory:
1:
- Pegasus Boots
@@ -10,9 +11,8 @@ start_inventory:
- Fire Rod
- Book of Mudora
- Progressive Sword
- Progressive Sword
- Lamp
- Magic Mirror
- Hammer
- Ether
- Quake
- Bombos
@@ -41,6 +41,7 @@ advanced_placements:
C-Shaped House: True
Pyramid Fairy - Left: True
Swamp Palace - Entrance: False
Swamp Palace - Boss: False
Thieves' Town - Map Chest: False
@@ -0,0 +1,49 @@
meta:
players: 1
settings:
1:
logic: hybridglitches
shuffle: vanilla
start_inventory:
1:
- Pegasus Boots
- Flippers
- Fire Rod
- Book of Mudora
- Progressive Sword
- Lamp
- Hammer
- Ether
- Quake
- Bombos
- Bombs (10)
placements:
1:
# Put mirror in PF so we can't DMD
Pyramid Fairy - Right: Magic Mirror
# Lock all swords and cape behind pearl so we can't do aga 1 for pyramid access
Thieves' Town - Big Chest: Progressive Sword
Thieves' Town - Attic: Progressive Sword
Thieves' Town - Blind's Cell: Progressive Sword
Thieves' Town - Map Chest: Cape
advanced_placements:
1:
- type: Verification
item: Moon Pearl
locations:
Palace of Darkness - Shooter Room: True
Palace of Darkness - The Arena - Bridge: True
Palace of Darkness - Stalfos Basement: True
Palace of Darkness - Big Key Chest: True
Palace of Darkness - The Arena - Ledge: True
Palace of Darkness - Map Chest: True
Palace of Darkness - Compass Chest: True
Palace of Darkness - Dark Basement - Left: True
Palace of Darkness - Dark Basement - Right: True
Palace of Darkness - Dark Maze - Top: True
Palace of Darkness - Dark Maze - Bottom: True
Palace of Darkness - Big Chest: True
Palace of Darkness - Harmless Hellway: True
Palace of Darkness - Boss: True
Pyramid Fairy - Left: False
Pyramid: False
+8
View File
@@ -9,6 +9,7 @@ start_inventory:
- Flippers
- Pegasus Boots
- Progressive Sword
- Progressive Sword
- Hammer
- Progressive Glove
- Progressive Glove
@@ -17,9 +18,16 @@ start_inventory:
- Bottle
- Magic Mirror
- Lamp
- Beat Agahnim 1
- Cane of Somaria
- Hookshot
- Quake
- Ether
- Bombos
advanced_placements:
1:
- type: Verification
item: Moon Pearl
locations:
Skull Woods - Bridge Room: True
Skull Woods - Boss: True
+13
View File
@@ -0,0 +1,13 @@
meta:
players: 1
settings:
1:
logic: hybridglitches
mode: open
shuffle: vanilla
start_inventory:
1:
- Pegasus Boots
placements:
1:
Palace of Darkness - Shooter Room: Moon Pearl