Skip to content

Commit

Permalink
Merge branch 'OverworldShuffleDev' into OverworldShuffle
Browse files Browse the repository at this point in the history
  • Loading branch information
codemann8 committed Dec 17, 2024
2 parents f52d4d5 + 3d002c2 commit ffd31f0
Show file tree
Hide file tree
Showing 24 changed files with 314 additions and 172 deletions.
41 changes: 28 additions & 13 deletions BaseClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1658,14 +1658,15 @@ def can_reach(self, state):
'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'
Expand Down Expand Up @@ -1729,7 +1730,8 @@ def traverse_paths(region, destination, start_path=[]):
# 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)
Expand All @@ -1747,7 +1749,8 @@ def traverse_paths(region, destination, start_path=[]):
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)
Expand All @@ -1758,25 +1761,36 @@ def traverse_paths(region, destination, start_path=[]):
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
Expand Down Expand Up @@ -1829,7 +1843,8 @@ def connect(self, region, addresses=None, target=None, vanilla=None):
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__())
Expand Down Expand Up @@ -3119,9 +3134,9 @@ def include_item(item):
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
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
1 change: 1 addition & 0 deletions DoorShuffle.py
Original file line number Diff line number Diff line change
Expand Up @@ -3792,6 +3792,7 @@ class DROptions(Flag):
('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'),
Expand Down
1 change: 1 addition & 0 deletions Doors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
3 changes: 2 additions & 1 deletion Dungeons.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ def make_dungeon(name, id, default_boss, dungeon_regions, big_key, small_keys, d
'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 = [
Expand Down
16 changes: 8 additions & 8 deletions ItemList.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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':
Expand Down
40 changes: 28 additions & 12 deletions Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
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
Expand Down Expand Up @@ -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()):
Expand All @@ -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"))

Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)

Expand Down
14 changes: 11 additions & 3 deletions OverworldGlitchRules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion OverworldShuffle.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
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 = ''

Expand Down
Loading

0 comments on commit ffd31f0

Please sign in to comment.