Merge branch 'OverworldShuffleDev' into OverworldShuffle

This commit is contained in:
codemann8
2021-11-09 14:57:33 -06:00
8 changed files with 168 additions and 71 deletions

View File

@@ -1618,7 +1618,7 @@ class Entrance(object):
def can_reach_thru(self, state, start_region, ignore_underworld=False, ignore_ledges=False, allow_save_quit=False):
def explore_region(region, path = []):
nonlocal found
if region not in explored_regions or len(explored_regions[region]) > len(path):
if region not in explored_regions:
explored_regions[region] = path
for exit in region.exits:
if exit.connected_region and (not ignore_ledges or exit.spot_type != 'Ledge') \
@@ -1628,20 +1628,28 @@ class Entrance(object):
found = True
explored_regions[self.parent_region] = path + [exit]
elif not ignore_underworld or region.type == exit.connected_region.type or exit.connected_region.type not in [RegionType.Cave, RegionType.Dungeon]:
explore_region(exit.connected_region, path + [exit])
exits_to_traverse.append(tuple((exit, path)))
found = False
explored_regions = {}
explore_region(start_region.entrances[0].parent_region)
if found:
self.temp_path = explored_regions[self.parent_region]
elif allow_save_quit:
world = self.parent_region.world if self.parent_region else None
exit = world.get_entrance('Links House S&Q', self.player)
explore_region(exit.connected_region, [exit])
def traverse_paths(region, start_path=[]):
explore_region(region, start_path)
while not found and len(exits_to_traverse):
exit, path = exits_to_traverse.pop(0)
explore_region(exit.connected_region, path + [exit])
if found:
self.temp_path = explored_regions[self.parent_region]
found = False
explored_regions = {}
exits_to_traverse = list()
traverse_paths(start_region.entrances[0].parent_region)
if not found and allow_save_quit:
explored_regions = {}
exits_to_traverse = list()
world = self.parent_region.world if self.parent_region else None
exit = world.get_entrance('Links House S&Q', self.player)
traverse_paths(exit.connected_region, [exit])
#TODO: Implement residual mirror portal placing for the previous leg, to be used for the final destination
return found
@@ -2910,8 +2918,33 @@ class Spoiler(object):
outfile.write(f'{fairy}: {bottle}\n')
if self.overworlds:
# overworlds: overworld transitions;
outfile.write('\n\nOverworld:\n\n')
# overworld tile swaps
swap_output = False
for player in range(1, self.world.players + 1):
if self.world.owMixed[player]:
from OverworldShuffle import tile_swap_spoiler_table
if not swap_output:
swap_output = True
outfile.write('OW Tile Swaps:\n')
outfile.write(('' if self.world.players == 1 else str('(Player ' + str(player) + ')')).ljust(11)) # player name
s = list(map(lambda x: ' ' if x not in self.world.owswaps[player][0] else 'S', [i for i in range(0x40)]))
outfile.write((tile_swap_spoiler_table + '\n\n') % ( s[0x02], s[0x07], \
s[0x00], s[0x03], s[0x05], \
s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f], \
s[0x0a], s[0x0f],
s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17],
s[0x18], s[0x1a],s[0x1b], s[0x1d],s[0x1e], \
s[0x22], s[0x25], s[0x1a], s[0x1d], \
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x18], s[0x1b], s[0x1e], \
s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25], \
s[0x3a],s[0x3b],s[0x3c], s[0x3f], \
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], \
s[0x32],s[0x33],s[0x34], s[0x37], \
s[0x30], s[0x35],
s[0x3a],s[0x3b],s[0x3c], s[0x3f]))
# overworld transitions
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","overworlds",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","overworlds",entry['exit'])) for entry in self.overworlds.values()]))
if self.entrances:

View File

@@ -1,5 +1,13 @@
# Changelog
### 0.2.2.1
- Allow normal Link speed with Old Man following if not in his cave or WDM
- Fixed issue with Flute exits not getting placed on the correct tiles
- Hints in Lite/Lean ER no longer refer to entrances that are guaranteed vanilla
- Added Links House entrance to hint candidate list in ER when it is shuffled
- Added Tile Swaps ASCII map to Spoiler Log when Tile Swap is enabled
- Fixed issue with Whirlpool Shuffle not abiding by Polar rules
### 0.2.2.0
- Delivering Big Red Bomb is now in logic
- Smith/Purple Chest have proper dynamic pathing to fix logical issues

View File

@@ -428,13 +428,21 @@ def link_entrances(world, player):
caves = Cave_Base + (dw_dungeons if not invFlag else lw_dungeons)
connector_entrances = [e for e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] if e in (dw_entrances if not invFlag else lw_entrances)]
connect_inaccessible_regions(world, [], connector_entrances, caves, player)
if invFlag:
lw_dungeons = list(set(lw_dungeons) & set(caves))
else:
dw_dungeons = list(set(dw_dungeons) & set(caves))
caves = list(set(Cave_Base) & set(caves)) + (lw_dungeons if not invFlag else dw_dungeons)
connector_entrances = [e for e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] if e in (lw_entrances if not invFlag else dw_entrances)]
connect_inaccessible_regions(world, connector_entrances, [], caves, player)
if not invFlag:
lw_dungeons = list(set(lw_dungeons) & set(caves))
else:
dw_dungeons = list(set(dw_dungeons) & set(caves))
lw_dungeons = list(set(lw_dungeons) & set(caves)) + (Old_Man_House if not invFlag else [])
dw_dungeons = list(set(dw_dungeons) & set(caves)) + ([] if not invFlag else Old_Man_House)
lw_dungeons = lw_dungeons + (Old_Man_House if not invFlag else [])
dw_dungeons = dw_dungeons + ([] if not invFlag else Old_Man_House)
caves = list(set(Cave_Base) & set(caves)) + DW_Mid_Dungeon_Exits
# place old man, has limited options

View File

@@ -1478,7 +1478,6 @@ OWExitTypes = {
'Dark Bonk Rocks Cliff Ledge Drop',
'Bomb Shop Cliff Ledge Drop',
'Hammer Bridge South Cliff Ledge Drop',
'Ice Lake Northeast Pier Hop',
'Ice Lake Moat Bomb Jump',
'Ice Lake Area Cliff Ledge Drop',
'Ice Palace Island FAWT Ledge Drop',
@@ -1623,6 +1622,7 @@ OWExitTypes = {
'Hype Cave Landing',
'Ice Lake Northeast Water Drop',
'Ice Lake Northeast Pier',
'Ice Lake Northeast Pier Hop',
'Ice Lake Moat Water Entry',
'Ice Palace Approach',
'Ice Palace Leave',

View File

@@ -3,7 +3,7 @@ from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSl
from Regions import mark_dark_world_regions, mark_light_world_regions
from OWEdges import OWTileRegions, OWTileGroups, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel
__version__ = '0.2.2.0-u'
__version__ = '0.2.2.1-u'
def link_overworld(world, player):
# setup mandatory connections
@@ -197,16 +197,17 @@ def link_overworld(world, player):
else:
whirlpool_candidates = [[],[]]
for (from_owid, from_whirlpool, from_region), (to_owid, to_whirlpool, to_region) in default_whirlpool_connections:
if world.owCrossed[player] != 'none':
whirlpool_candidates[0].append(tuple((from_owid, from_whirlpool, from_region)))
whirlpool_candidates[0].append(tuple((to_owid, to_whirlpool, to_region)))
if world.owCrossed[player] == 'polar' and world.owMixed[player] and from_owid == 0x55:
# connect the 2 DW whirlpools in Polar Mixed
connect_simple(world, from_whirlpool, to_region, player)
connect_simple(world, to_whirlpool, from_region, player)
else:
if world.get_region(from_region, player).type == RegionType.LightWorld:
if world.owCrossed[player] != 'none' or world.get_region(from_region, player).type == RegionType.LightWorld:
whirlpool_candidates[0].append(tuple((from_owid, from_whirlpool, from_region)))
else:
whirlpool_candidates[1].append(tuple((from_owid, from_whirlpool, from_region)))
if world.get_region(to_region, player).type == RegionType.LightWorld:
if world.owCrossed[player] != 'none' or world.get_region(to_region, player).type == RegionType.LightWorld:
whirlpool_candidates[0].append(tuple((to_owid, to_whirlpool, to_region)))
else:
whirlpool_candidates[1].append(tuple((to_owid, to_whirlpool, to_region)))
@@ -723,9 +724,7 @@ def reorganize_groups(world, groups, player):
def create_flute_exits(world, player):
for region in (r for r in world.regions if r.player == player and r.terrain == Terrain.Land and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']):
if (not world.owMixed[player] and region.type == RegionType.LightWorld) \
or (world.owMixed[player] and region.type in [RegionType.LightWorld, RegionType.DarkWorld] \
and (region.name not in world.owswaps[player][1] or region.name in world.owswaps[player][2])):
if region.type == (RegionType.LightWorld if world.mode != 'inverted' else RegionType.DarkWorld):
exitname = 'Flute From ' + region.name
exit = Entrance(region.player, exitname, region)
exit.spot_type = 'Flute'
@@ -1589,3 +1588,23 @@ flute_data = {
0x3c: (['South Pass Area', 'Dark South Pass Area'], 0x3c, 0x0584, 0x0ed0, 0x081e, 0x0f38, 0x0898, 0x0f45, 0x08a3, 0xfffe, 0x0002, 0x0f38, 0x0898),
0x3f: (['Octoballoon Area', 'Bomber Corner Area'], 0x3f, 0x0810, 0x0f05, 0x0e75, 0x0f67, 0x0ef3, 0x0f72, 0x0efa, 0xfffb, 0x000b, 0x0f80, 0x0ef0)
}
tile_swap_spoiler_table = \
""" 0 1 2 3 4 5 6 7
+---+-+---+---+-+
01234567 A(00)| |%s| | |%s|
+--------+ | %s +-+ %s | %s +-+
A(00)|%s %s%s %s %s| B(08)| |%s| | |%s|
B(08)| %s %s| +-+-+-+-+-+-+-+-+
C(10)|%s%s%s%s%s%s%s%s| C(10)|%s|%s|%s|%s|%s|%s|%s|%s|
D(18)|%s %s%s %s%s | +-+-+-+-+-+-+-+-+
E(20)| %s %s | D(18)| |%s| |%s| |
F(28)|%s%s%s%s%s%s%s%s| | %s +-+ %s +-+ %s |
G(30)|%s %s%s%s%s %s| E(20)| |%s| |%s| |
H(38)| %s%s%s %s| +-+-+-+-+-+-+-+-+
+--------+ F(28)|%s|%s|%s|%s|%s|%s|%s|%s|
+-+-+-+-+-+-+-+-+
G(30)| |%s|%s|%s| |%s|
| %s +-+-+-+ %s +-+
H(38)| |%s|%s|%s| |%s|
+---+-+-+-+---+-+"""

103
Rom.py
View File

@@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'e3373be98af9d6de1cb1ab12176ecb0e'
RANDOMIZERBASEHASH = '9df10796c8a8fe07d81fc0012700934a'
class JsonRom(object):
@@ -2161,14 +2161,14 @@ def write_strings(rom, world, player, team):
else:
hint_count = 4
for entrance in all_entrances:
if entrance.name in entrances_to_hint:
if hint_count > 0:
if hint_count > 0:
if entrance.name in entrances_to_hint:
this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text(entrance.connected_region) + '.'
tt[hint_locations.pop(0)] = this_hint
entrances_to_hint.pop(entrance.name)
hint_count -= 1
else:
break
else:
break
#Next we handle hints for randomly selected other entrances, curating the selection intelligently based on shuffle.
if world.shuffle[player] not in ['simple', 'restricted', 'restricted_legacy']:
@@ -2180,9 +2180,15 @@ def write_strings(rom, world, player, team):
entrances_to_hint.update({'Agahnims Tower': 'The sealed castle door'})
elif world.shuffle[player] == 'restricted':
entrances_to_hint.update(ConnectorEntrances)
entrances_to_hint.update(OtherEntrances)
entrances_to_hint.update(ItemEntrances)
if world.shuffle[player] not in ['lite', 'lean']:
entrances_to_hint.update(ShopEntrances)
entrances_to_hint.update(OtherEntrances)
elif world.shopsanity[player]:
entrances_to_hint.update(ShopEntrances)
if world.shufflelinks[player] and world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
entrances_to_hint.update({'Links House': 'The hero\'s old residence'})
entrances_to_hint.update({'Dark Sanctuary Hint': 'The dark sanctuary cave'})
entrances_to_hint.update({'Big Bomb Shop': 'The old bomb shop'})
if world.shuffle[player] in ['insanity', 'madness_legacy', 'insanity_legacy']:
entrances_to_hint.update(InsanityEntrances)
if world.shuffle_ganon:
@@ -2254,17 +2260,15 @@ def write_strings(rom, world, player, team):
else:
this_hint = location + ' contains ' + hint_text(world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
# Adding a guaranteed hint for the Flute in overworld shuffle.
# Lastly we write hints to show where certain interesting items are. It is done the way it is to re-use the silver code and also to give one hint per each type of item regardless of how many exist. This supports many settings well.
items_to_hint = RelevantItems.copy()
if world.owShuffle[player] != 'vanilla' or world.owMixed[player]:
# Adding a guaranteed hint for the Flute in overworld shuffle.
this_location = world.find_items_not_key_only('Ocarina', player)
if this_location:
this_hint = this_location[0].item.hint_text + ' can be found ' + hint_text(this_location[0]) + '.'
tt[hint_locations.pop(0)] = this_hint
# Lastly we write hints to show where certain interesting items are. It is done the way it is to re-use the silver code and also to give one hint per each type of item regardless of how many exist. This supports many settings well.
items_to_hint = RelevantItems.copy()
if world.owShuffle[player] != 'vanilla' or world.owMixed[player]:
items_to_hint.remove('Ocarina')
if world.keyshuffle[player]:
items_to_hint.extend(SmallKeys)
@@ -2273,7 +2277,7 @@ def write_strings(rom, world, player, team):
random.shuffle(items_to_hint)
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8
hint_count += 2 if world.doorShuffle[player] == 'crossed' else 0
hint_count += 1 if world.owShuffle[player] != 'vanilla' or world.owMixed[player] else 0
hint_count += 1 if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] != 'none' or world.owMixed[player] else 0
while hint_count > 0:
this_item = items_to_hint.pop(0)
this_location = world.find_items_not_key_only(this_item, player)
@@ -2734,16 +2738,46 @@ DungeonEntrances = {'Eastern Palace': 'Eastern Palace',
'Desert Palace Entrance (North)': 'The northmost cave in the desert'
}
OtherEntrances = {'Blinds Hideout': 'Blind\'s old house',
'Lake Hylia Fairy': 'A cave NE of Lake Hylia',
ItemEntrances = {'Blinds Hideout': 'Blind\'s old house',
'Chicken House': 'The chicken lady\'s house',
'Aginahs Cave': 'The open desert cave',
'Sahasrahlas Hut': 'The house near armos',
'Blacksmiths Hut': 'The old smithery',
'Sick Kids House': 'The central house in Kakariko',
'Mini Moldorm Cave': 'The cave south of Lake Hylia',
'Ice Rod Cave': 'The sealed cave SE Lake Hylia',
'Library': 'The old library',
'Potion Shop': 'The witch\'s building',
'Dam': 'The old dam',
'Waterfall of Wishing': 'Going behind the waterfall',
'Bonk Rock Cave': 'The rock pile near Sanctuary',
'Graveyard Cave': 'The graveyard ledge',
'Checkerboard Cave': 'The NE desert ledge',
'Cave 45': 'The ledge south of haunted grove',
'Kings Grave': 'The northeastmost grave',
'C-Shaped House': 'The NE house in Village of Outcasts',
'Mire Shed': 'The western hut in the mire',
'Spike Cave': 'The ledge cave on west dark DM',
'Hype Cave': 'The cave south of the old bomb shop',
'Brewery': 'The Village of Outcasts building with no door',
'Chest Game': 'The westmost building in the Village of Outcasts',
'Big Bomb Shop': 'The old bomb shop'
}
ShopEntrances = {'Cave Shop (Lake Hylia)': 'The cave NW Lake Hylia',
'Kakariko Shop': 'The old Kakariko shop',
'Capacity Upgrade': 'The cave on the island',
'Dark Lake Hylia Shop': 'The building NW dark Lake Hylia',
'Dark World Shop': 'The hammer sealed building',
'Red Shield Shop': 'The fenced in building',
'Cave Shop (Dark Death Mountain)': 'The base of east dark DM',
'Dark World Potion Shop': 'The building near the catfish',
'Dark World Lumberjack Shop': 'The northmost Dark World building'
}
OtherEntrances = {'Lake Hylia Fairy': 'A cave NE of Lake Hylia',
'Light Hype Fairy': 'The cave south of your house',
'Desert Fairy': 'The cave near the desert',
'Chicken House': 'The chicken lady\'s house',
'Aginahs Cave': 'The open desert cave',
'Sahasrahlas Hut': 'The house near armos',
'Cave Shop (Lake Hylia)': 'The cave NW Lake Hylia',
'Blacksmiths Hut': 'The old smithery',
'Sick Kids House': 'The central house in Kakariko',
'Lost Woods Gamble': 'A tree trunk door',
'Fortune Teller (Light)': 'A building NE of Kakariko',
'Snitch Lady (East)': 'A house guarded by a snitch',
@@ -2751,49 +2785,24 @@ OtherEntrances = {'Blinds Hideout': 'Blind\'s old house',
'Bush Covered House': 'A house with an uncut lawn',
'Tavern (Front)': 'A building with a backdoor',
'Light World Bomb Hut': 'A Kakariko building with no door',
'Kakariko Shop': 'The old Kakariko shop',
'Mini Moldorm Cave': 'The cave south of Lake Hylia',
'Long Fairy Cave': 'The eastmost portal cave',
'Good Bee Cave': 'The open cave SE Lake Hylia',
'20 Rupee Cave': 'The rock SE Lake Hylia',
'50 Rupee Cave': 'The rock near the desert',
'Ice Rod Cave': 'The sealed cave SE Lake Hylia',
'Library': 'The old library',
'Potion Shop': 'The witch\'s building',
'Dam': 'The old dam',
'Lumberjack House': 'The lumberjack house',
'Lake Hylia Fortune Teller': 'The building NW Lake Hylia',
'Kakariko Gamble Game': 'The old Kakariko gambling den',
'Waterfall of Wishing': 'Going behind the waterfall',
'Capacity Upgrade': 'The cave on the island',
'Bonk Rock Cave': 'The rock pile near Sanctuary',
'Graveyard Cave': 'The graveyard ledge',
'Checkerboard Cave': 'The NE desert ledge',
'Cave 45': 'The ledge south of haunted grove',
'Kings Grave': 'The northeastmost grave',
'Bonk Fairy (Light)': 'The rock pile near your home',
'Hookshot Fairy': 'The left paired cave on east DM',
'Bonk Fairy (Dark)': 'The rock pile near the old bomb shop',
'Dark Lake Hylia Fairy': 'The cave NE dark Lake Hylia',
'C-Shaped House': 'The NE house in Village of Outcasts',
'Dark Death Mountain Fairy': 'The SW cave on dark DM',
'Dark Lake Hylia Shop': 'The building NW dark Lake Hylia',
'Dark World Shop': 'The hammer sealed building',
'Red Shield Shop': 'The fenced in building',
'Mire Shed': 'The western hut in the mire',
'East Dark World Hint': 'The dark cave near the eastmost portal',
'Dark Desert Hint': 'The cave east of the mire',
'Spike Cave': 'The ledge cave on west dark DM',
'Palace of Darkness Hint': 'The building south of Kiki',
'Dark Lake Hylia Ledge Spike Cave': 'The rock SE dark Lake Hylia',
'Cave Shop (Dark Death Mountain)': 'The base of east dark DM',
'Dark World Potion Shop': 'The building near the catfish',
'Archery Game': 'The old archery game',
'Dark World Lumberjack Shop': 'The northmost Dark World building',
'Hype Cave': 'The cave south of the old bomb shop',
'Brewery': 'The Village of Outcasts building with no door',
'Dark Lake Hylia Ledge Hint': 'The open cave SE dark Lake Hylia',
'Chest Game': 'The westmost building in the Village of Outcasts',
'Dark Desert Fairy': 'The eastern hut in the mire',
'Dark Lake Hylia Ledge Fairy': 'The sealed cave SE dark Lake Hylia',
'Fortune Teller (Dark)': 'The building NE the Village of Outcasts'

View File

@@ -37,6 +37,10 @@ db #$b0 ; BCS to replace BEQ
org $06907f ; < 3107f - sprite_prep.asm:2170 (LDA $7EF3CA)
lda $8a : and.b #$40
; override Link speed with Old Man following
org $09a32e ; < bank_09.asm:7457 (LDA.b #$0C : STA.b $5E)
jsl OWOldManSpeed
; Dark Bonk Rocks Rain Sequence Guards (allowing Tile Swap on Dark Bonk Rocks)
;org $09c957 ; <- 4c957
;dw #$cb5f ; matches value on Central Bonk Rocks screen
@@ -162,6 +166,22 @@ OWSmithAccept:
clc : rtl
+ sec : rtl
}
OWOldManSpeed:
{
lda $1b : beq .outdoors
lda $a0 : and #$fe : cmp #$f0 : beq .vanilla ; if in cave where you find Old Man
bra .normalspeed
.outdoors
lda $8a : cmp #$03 : beq .vanilla ; if on WDM screen
.normalspeed
lda $5e : cmp #$0c : rtl
stz $5e : rtl
.vanilla
lda #$0c : sta $5e ; what we wrote over
rtl
}
org $aa9000
OWEdgeTransition:

Binary file not shown.