Merge branch 'OverworldShuffleDev' into OverworldShuffle

This commit is contained in:
codemann8
2025-01-05 07:48:33 -06:00
14 changed files with 134 additions and 79 deletions

View File

@@ -1100,7 +1100,7 @@ class CollectionState(object):
new_locations = True new_locations = True
while new_locations: while new_locations:
reachable_events = [location for location in locations if location.event and reachable_events = [location for location in locations if location.event and
(not key_only or (self.world.keyshuffle[location.item.player] in ['none', 'district'] and location.item.smallkey) or (self.world.bigkeyshuffle[location.item.player] in ['none', 'district'] and location.item.bigkey)) (not key_only or (self.world.keyshuffle[location.item.player] in ['none', 'nearby'] and location.item.smallkey) or (self.world.bigkeyshuffle[location.item.player] in ['none', 'nearby'] and location.item.bigkey))
and location.can_reach(self)] and location.can_reach(self)]
reachable_events = self._do_not_flood_the_keys(reachable_events) reachable_events = self._do_not_flood_the_keys(reachable_events)
new_locations = False new_locations = False
@@ -2806,11 +2806,11 @@ class Item(object):
or (self.map and world.mapshuffle[self.player] == 'none')) or (self.map and world.mapshuffle[self.player] == 'none'))
def is_near_dungeon_item(self, world): def is_near_dungeon_item(self, world):
return ((self.prize and world.prizeshuffle[self.player] == 'district') return ((self.prize and world.prizeshuffle[self.player] == 'nearby')
or (self.smallkey and world.keyshuffle[self.player] == 'district') or (self.smallkey and world.keyshuffle[self.player] == 'nearby')
or (self.bigkey and world.bigkeyshuffle[self.player] == 'district') or (self.bigkey and world.bigkeyshuffle[self.player] == 'nearby')
or (self.compass and world.compassshuffle[self.player] == 'district') or (self.compass and world.compassshuffle[self.player] == 'nearby')
or (self.map and world.mapshuffle[self.player] == 'district')) or (self.map and world.mapshuffle[self.player] == 'nearby'))
def get_map_location(self): def get_map_location(self):
if self.location: if self.location:
@@ -2820,7 +2820,7 @@ class Item(object):
def explore_region(region): def explore_region(region):
explored_regions.append(region.name) explored_regions.append(region.name)
for ent in region.entrances: for ent in region.entrances:
if ent.parent_region is not None and ent.spot_type != 'OWG': if ent.parent_region is not None and ent.spot_type not in ['OWG', 'HMG']:
if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]: if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]:
return ent return ent
elif ent.parent_region.name not in explored_regions: elif ent.parent_region.name not in explored_regions:
@@ -3644,10 +3644,10 @@ counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3}
access_mode = {"items": 0, "locations": 1, "none": 2} access_mode = {"items": 0, "locations": 1, "none": 2}
# byte 7: MMCC SSBB (maps, compass, small, big) # byte 7: MMCC SSBB (maps, compass, small, big)
mapshuffle_mode = {'none': 0, 'off': 0, 'district': 2, 'wild': 3, 'on': 3} mapshuffle_mode = {'none': 0, 'off': 0, 'nearby': 2, 'wild': 3, 'on': 3}
compassshuffle_mode = {'none': 0, 'off': 0, 'district': 2, 'wild': 3, 'on': 3} compassshuffle_mode = {'none': 0, 'off': 0, 'nearby': 2, 'wild': 3, 'on': 3}
keyshuffle_mode = {'none': 0, 'off': 0, 'universal': 1, 'district': 2, 'wild': 3, 'on': 3} keyshuffle_mode = {'none': 0, 'off': 0, 'universal': 1, 'nearby': 2, 'wild': 3, 'on': 3}
bigkeyshuffle_mode = {'none': 0, 'off': 0, 'district': 2, 'wild': 3, 'on': 3} bigkeyshuffle_mode = {'none': 0, 'off': 0, 'nearby': 2, 'wild': 3, 'on': 3}
# byte 8: HHHD DPEE (enemy_health, enemy_dmg, potshuffle, enemies) # byte 8: HHHD DPEE (enemy_health, enemy_dmg, potshuffle, enemies)
e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4} e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4}
@@ -3673,7 +3673,7 @@ flutespot_mode = {"vanilla": 0, "balanced": 1, "random": 2}
flute_mode = {'normal': 0, 'active': 1} flute_mode = {'normal': 0, 'active': 1}
bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3} # reserved 8 modes? bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3} # reserved 8 modes?
take_any_mode = {'none': 0, 'random': 1, 'fixed': 2} take_any_mode = {'none': 0, 'random': 1, 'fixed': 2}
prizeshuffle_mode = {'none': 0, 'dungeon': 1, 'district': 2, 'wild': 3} prizeshuffle_mode = {'none': 0, 'dungeon': 1, 'nearby': 2, 'wild': 3}
# additions # additions
# byte 14: POOT TKKK (pseudoboots, overworld_map, trap_door_mode, key_logic_algo) # byte 14: POOT TKKK (pseudoboots, overworld_map, trap_door_mode, key_logic_algo)

View File

@@ -1,5 +1,11 @@
# Changelog # Changelog
## 0.5.1.2
- Many fixes to HMG logic, generation, key collection issues
- Fixed issue with In-Dungeon Prizes getting placed in the same dungeon
- Fixed issue with Old Man getting placed outside of DM in glitched modes
- Fixed issue with Free Terrain water transitions not checking for Pearl requirement
## 0.5.1.0 (+ Hotfix 0.5.1.1) ## 0.5.1.0 (+ Hotfix 0.5.1.1)
- New "Nearby" Dungeon Item Shuffle option - New "Nearby" Dungeon Item Shuffle option
- Fixed issue with smith follower getting deleted incorrectly on s+q - Fixed issue with smith follower getting deleted incorrectly on s+q

44
Fill.py
View File

@@ -36,18 +36,21 @@ def dungeon_tracking(world):
for dungeon in world.dungeons: for dungeon in world.dungeons:
layout = world.dungeon_layouts[dungeon.player][dungeon.name] layout = world.dungeon_layouts[dungeon.player][dungeon.name]
layout.dungeon_items = len([i for i in dungeon.all_items if i.is_inside_dungeon_item(world)]) layout.dungeon_items = len([i for i in dungeon.all_items if i.is_inside_dungeon_item(world)])
if world.prizeshuffle[dungeon.player] in ['dungeon', 'nearby'] and not dungeon.prize:
from Dungeons import dungeon_table
if dungeon_table[dungeon.name].prize:
layout.dungeon_items += 1
layout.free_items = layout.location_cnt - layout.dungeon_items layout.free_items = layout.location_cnt - layout.dungeon_items
def fill_dungeons_restrictive(world, shuffled_locations): def fill_dungeons_restrictive(world, shuffled_locations):
# with shuffled dungeon items they are distributed as part of the normal item pool # with shuffled dungeon items they are distributed as part of the normal item pool
for item in world.get_items(): for item in world.get_items():
if ((item.prize and world.prizeshuffle[item.player] != 'none') if ((item.prize and world.prizeshuffle[item.player] != 'none')
or (item.smallkey and world.keyshuffle[item.player] != 'none') or (item.smallkey and world.keyshuffle[item.player] != 'none')
or (item.bigkey and world.bigkeyshuffle[item.player] != 'none')): or (item.bigkey and world.bigkeyshuffle[item.player] != 'none')):
item.advancement = True item.advancement = True
elif (item.map and world.mapshuffle[item.player] not in ['none', 'district']) or (item.compass and world.compassshuffle[item.player] not in ['none', 'district']): elif (item.map and world.mapshuffle[item.player] not in ['none', 'nearby']) or (item.compass and world.compassshuffle[item.player] not in ['none', 'nearby']):
item.priority = True item.priority = True
dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world) or item.is_near_dungeon_item(world)] dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world) or item.is_near_dungeon_item(world)]
@@ -56,13 +59,46 @@ def fill_dungeons_restrictive(world, shuffled_locations):
(bigs if i.bigkey else smalls if i.smallkey else prizes if i.prize else others).append(i) (bigs if i.bigkey else smalls if i.smallkey else prizes if i.prize else others).append(i)
unplaced_smalls = list(smalls) unplaced_smalls = list(smalls)
for i in world.itempool: for i in world.itempool:
if i.smallkey and world.keyshuffle[i.player] != 'none': if i.smallkey and world.keyshuffle[i.player] not in ['none', 'nearby']:
unplaced_smalls.append(i) unplaced_smalls.append(i)
def fill(base_state, items, locations, key_pool=None): def fill(base_state, items, locations, key_pool=None):
fill_restrictive(world, base_state, locations, items, key_pool, True) fill_restrictive(world, base_state, locations, items, key_pool, True)
all_state_base = world.get_all_state() all_state_base = world.get_all_state()
for player in range(1, world.players + 1):
if world.logic[player] == 'hybridglitches' and world.keyshuffle[i.player] in ['none', 'nearby'] \
and world.pottery[player] not in ['none', 'cave']:
# remove 2 keys from main pool
count_to_remove = 2
to_remove = []
for wix, wi in enumerate(smalls):
if wi.name == 'Small Key (Swamp Palace)' and wi.player == player:
to_remove.append(wix)
if count_to_remove == len(to_remove):
break
for wix in reversed(to_remove):
del smalls[wix]
# remove 2 swamp locations from pool
hybrid_locations = []
to_remove = []
for i, loc in enumerate(shuffled_locations):
if loc.name in ['Swamp Palace - Trench 1 Pot Key', 'Swamp Palace - Pot Row Pot Key'] and loc.player == player:
to_remove.append(i)
hybrid_locations.append(loc)
if count_to_remove == len(to_remove):
break
for i in reversed(to_remove):
shuffled_locations.pop(i)
# place 2 HMG keys
hybrid_state_base = all_state_base.copy()
for x in bigs + smalls + prizes + others:
hybrid_state_base.collect(x, True)
hybrid_smalls = [ItemFactory('Small Key (Swamp Palace)', player)] * 2
fill(hybrid_state_base, hybrid_smalls, hybrid_locations, unplaced_smalls)
big_state_base = all_state_base.copy() big_state_base = all_state_base.copy()
for x in smalls + prizes + others: for x in smalls + prizes + others:
big_state_base.collect(x, True) big_state_base.collect(x, True)
@@ -77,7 +113,7 @@ def fill_dungeons_restrictive(world, shuffled_locations):
for attempt in range(15): for attempt in range(15):
try: try:
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
if world.prizeshuffle[player] == 'district': if world.prizeshuffle[player] == 'nearby':
dungeon_pool = [] dungeon_pool = []
for dungeon in world.dungeons: for dungeon in world.dungeons:
from Dungeons import dungeon_table from Dungeons import dungeon_table

View File

@@ -114,6 +114,10 @@ class InitialSram:
equip[0x37B] = 1 equip[0x37B] = 1
starting_magic = 0x80 starting_magic = 0x80
if world.mode[player] == 'standard' and world.logic[player] not in ['noglitches', 'minorglitches']:
if startingstate.has('Ocarina (Activated)', player):
self.pre_set_overworld_flag(0x18, 0x20)
if startingstate.has('Return Old Man', player): if startingstate.has('Return Old Man', player):
self._initial_sram_bytes[0x410] |= 0x01 self._initial_sram_bytes[0x410] |= 0x01

View File

@@ -272,7 +272,8 @@ def generate_itempool(world, player):
for _ in range(0, amt): for _ in range(0, amt):
pool.append('Rupees (20)') pool.append('Rupees (20)')
if world.logic[player] == 'hybridglitches' and world.pottery[player] not in ['none', 'cave']: if world.logic[player] == 'hybridglitches' and world.pottery[player] not in ['none', 'cave'] \
and world.keyshuffle[player] not in ['none', 'nearby']:
# In HMG force swamp smalls in pots to allow getting out of swamp palace # In HMG force swamp smalls in pots to allow getting out of swamp palace
placed_items['Swamp Palace - Trench 1 Pot Key'] = 'Small Key (Swamp Palace)' placed_items['Swamp Palace - Trench 1 Pot Key'] = 'Small Key (Swamp Palace)'
placed_items['Swamp Palace - Pot Row Pot Key'] = 'Small Key (Swamp Palace)' placed_items['Swamp Palace - Pot Row Pot Key'] = 'Small Key (Swamp Palace)'
@@ -348,11 +349,11 @@ def generate_itempool(world, player):
world.treasure_hunt_icon[player] = 'Triforce Piece' world.treasure_hunt_icon[player] = 'Triforce Piece'
world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player
and ((item.prize and world.prizeshuffle[player] not in ['none', 'dungeon', 'district']) and ((item.prize and world.prizeshuffle[player] not in ['none', 'dungeon', 'nearby'])
or (item.smallkey and world.keyshuffle[player] not in ['none', 'district']) or (item.smallkey and world.keyshuffle[player] not in ['none', 'nearby'])
or (item.bigkey and world.bigkeyshuffle[player] not in ['none', 'district']) or (item.bigkey and world.bigkeyshuffle[player] not in ['none', 'nearby'])
or (item.map and world.mapshuffle[player] not in ['none', 'district']) or (item.map and world.mapshuffle[player] not in ['none', 'nearby'])
or (item.compass and world.compassshuffle[player] not in ['none', 'district']))]) or (item.compass and world.compassshuffle[player] not in ['none', 'nearby']))])
if world.logic[player] == 'hybridglitches' and world.pottery[player] not in ['none', 'cave']: if world.logic[player] == 'hybridglitches' and world.pottery[player] not in ['none', 'cave']:
keys_to_remove = 2 keys_to_remove = 2
@@ -365,19 +366,6 @@ def generate_itempool(world, player):
for wix in reversed(to_remove): for wix in reversed(to_remove):
del world.itempool[wix] del world.itempool[wix]
if world.logic[player] == 'hybridglitches' and world.pottery[player] not in ['none', 'cave']:
# In HMG force swamp smalls in pots to allow getting out of swamp palace
loc = world.get_location('Swamp Palace - Trench 1 Pot Key', player)
world.push_item(loc, ItemFactory('Small Key (Swamp Palace)', player), False)
loc.event = True
loc.locked = True
loc = world.get_location('Swamp Palace - Pot Row Pot Key', player)
world.push_item(loc, ItemFactory('Small Key (Swamp Palace)', player), False)
loc.event = True
loc.locked = True
world.itempool.remove(ItemFactory('Small Key (Swamp Palace)', player))
world.itempool.remove(ItemFactory('Small Key (Swamp Palace)', player))
# logic has some branches where having 4 hearts is one possible requirement (of several alternatives) # logic has some branches where having 4 hearts is one possible requirement (of several alternatives)
# rather than making all hearts/heart pieces progression items (which slows down generation considerably) # rather than making all hearts/heart pieces progression items (which slows down generation considerably)
# We mark one random heart container as an advancement item (or 4 heart pieces in expert mode) # We mark one random heart container as an advancement item (or 4 heart pieces in expert mode)

View File

@@ -619,6 +619,7 @@ def copy_world(world):
ret.inaccessible_regions = world.inaccessible_regions.copy() ret.inaccessible_regions = world.inaccessible_regions.copy()
ret.damage_table = world.damage_table ret.damage_table = world.damage_table
ret.data_tables = world.data_tables # can be changed... ret.data_tables = world.data_tables # can be changed...
ret.settings = world.settings
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
create_regions(ret, player) create_regions(ret, player)
@@ -832,6 +833,7 @@ def copy_world_premature(world, player):
ret.damage_table = world.damage_table ret.damage_table = world.damage_table
ret.data_tables = world.data_tables # can be changed... ret.data_tables = world.data_tables # can be changed...
ret.key_logic = world.key_logic.copy() ret.key_logic = world.key_logic.copy()
ret.settings = world.settings
ret.is_copied_world = True ret.is_copied_world = True

View File

@@ -1384,11 +1384,10 @@ def can_reach_smith(world, player):
region = world.get_region(region_name, player) region = world.get_region(region_name, player)
for exit in region.exits: for exit in region.exits:
if not found and exit.connected_region is not None: if not found and exit.connected_region is not None:
if exit.spot_type == 'Flute': if starting_flute and exit.spot_type == 'Flute':
if any(map(lambda i: i.name == 'Ocarina (Activated)' and i.player == player, world.precollected_items)): for flutespot in exit.connected_region.exits:
for flutespot in exit.connected_region.exits: if flutespot.connected_region and flutespot.connected_region.name not in explored_regions:
if flutespot.connected_region and flutespot.connected_region.name not in explored_regions: explore_region(flutespot.connected_region.name, flutespot.connected_region)
explore_region(flutespot.connected_region.name, flutespot.connected_region)
elif exit.connected_region.name == 'Blacksmiths Hut' and exit.access_rule(blank_state): elif exit.connected_region.name == 'Blacksmiths Hut' and exit.access_rule(blank_state):
found = True found = True
return return
@@ -1410,6 +1409,7 @@ def can_reach_smith(world, player):
found = False found = False
explored_regions = list() explored_regions = list()
starting_flute = any(map(lambda i: i.name == 'Ocarina (Activated)' and i.player == player, world.precollected_items))
if not world.is_bombshop_start(player): if not world.is_bombshop_start(player):
start_region = 'Links House' start_region = 'Links House'
else: else:
@@ -1479,7 +1479,7 @@ def build_sectors(world, player):
return sectors return sectors
def build_accessible_region_list(world, start_region, player, build_copy_world=False, cross_world=False, region_rules=True, ignore_ledges = False): def build_accessible_region_list(world, start_region, player, build_copy_world=False, cross_world=False, region_rules=True, ignore_ledges=False, restrictive_follower=False):
from BaseClasses import CollectionState from BaseClasses import CollectionState
from Main import copy_world_premature from Main import copy_world_premature
from Items import ItemFactory from Items import ItemFactory
@@ -1494,15 +1494,17 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F
region = base_world.get_region(region_name, player) region = base_world.get_region(region_name, player)
for exit in region.exits: for exit in region.exits:
if exit.connected_region is not None: if exit.connected_region is not None:
if any(map(lambda i: i.name == 'Ocarina (Activated)' and i.player == player, base_world.precollected_items)) and exit.spot_type == 'Flute': if starting_flute and exit.spot_type == 'Flute':
fluteregion = exit.connected_region fluteregion = exit.connected_region
for flutespot in fluteregion.exits: for flutespot in fluteregion.exits:
if flutespot.connected_region and flutespot.connected_region.name not in explored_regions: if flutespot.connected_region and flutespot.connected_region.name not in explored_regions:
explore_region(flutespot.connected_region.name, flutespot.connected_region) explore_region(flutespot.connected_region.name, flutespot.connected_region)
elif exit.connected_region.name not in explored_regions \ elif exit.connected_region.name not in explored_regions \
and (exit.connected_region.type == region.type and (exit.connected_region.type == region.type or exit.name in OWExitTypes['OWEdge']
or exit.name in OWExitTypes['OWEdge'] or (cross_world and exit.name in (OWExitTypes['Portal'] + OWExitTypes['Mirror']))) \ or (cross_world and exit.name in (OWExitTypes['Portal'] + OWExitTypes['Mirror']))) \
and (not region_rules or exit.access_rule(blank_state)) and (not ignore_ledges or exit.name not in (OWExitTypes['Ledge'] + OWExitTypes['OWG'])): and (not region_rules or exit.access_rule(blank_state)) \
and (not restrictive_follower or exit.spot_type != 'OWG') \
and (not ignore_ledges or not (exit.spot_type == 'OWG' or exit.name in OWExitTypes['Ledge'])):
explore_region(exit.connected_region.name, exit.connected_region) explore_region(exit.connected_region.name, exit.connected_region)
if build_copy_world: if build_copy_world:
@@ -1518,6 +1520,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F
if base_world.mode[player] == 'standard': if base_world.mode[player] == 'standard':
blank_state.collect(ItemFactory('Zelda Delivered', player), True) blank_state.collect(ItemFactory('Zelda Delivered', player), True)
explored_regions = list() explored_regions = list()
starting_flute = any(map(lambda i: i.name == 'Ocarina (Activated)' and i.player == player, base_world.precollected_items))
explore_region(start_region) explore_region(start_region)
return explored_regions return explored_regions

24
Rom.py
View File

@@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '1fde4fa24bc9d3efe450c3bc30e4cf2c' RANDOMIZERBASEHASH = 'a84f59e5e76492f6a5693835823e6292'
class JsonRom(object): class JsonRom(object):
@@ -439,6 +439,8 @@ def patch_rom(world, rom, player, team, is_mystery=False):
location.pot.item = valid_pot_items[location.item.name] location.pot.item = valid_pot_items[location.item.name]
else: else:
code = itemid code = itemid
if world.pottery[player] == 'none' or location.locked:
code = handle_native_dungeon(location, itemid)
standing_item_flag = 0x80 standing_item_flag = 0x80
if location.item.player != player: if location.item.player != player:
standing_item_flag |= 0x40 standing_item_flag |= 0x40
@@ -1276,28 +1278,28 @@ def patch_rom(world, rom, player, team, is_mystery=False):
# m - enabled for inside maps # m - enabled for inside maps
# c - enabled for inside compasses # c - enabled for inside compasses
# s - enabled for inside small keys # s - enabled for inside small keys
free_item_text = 0x40 if 'district' in [world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]] else 0x00 free_item_text = 0x40 if 'nearby' in [world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]] else 0x00
rom.write_byte(0x18016A, free_item_text | 0x10 | ((0x20 if world.prizeshuffle[player] not in ['none', 'dungeon'] else 0x00) rom.write_byte(0x18016A, free_item_text | 0x10 | ((0x20 if world.prizeshuffle[player] not in ['none', 'dungeon'] else 0x00)
| (0x01 if world.keyshuffle[player] not in ['none', 'universal'] else 0x00) | (0x01 if world.keyshuffle[player] not in ['none', 'universal'] else 0x00)
| (0x02 if world.compassshuffle[player] != 'none' else 0x00) | (0x02 if world.compassshuffle[player] != 'none' else 0x00)
| (0x04 if world.mapshuffle[player] != 'none' else 0x00) | (0x04 if world.mapshuffle[player] != 'none' else 0x00)
| (0x08 if world.bigkeyshuffle[player] != 'none' else 0x00))) # free roaming item text boxes | (0x08 if world.bigkeyshuffle[player] != 'none' else 0x00))) # free roaming item text boxes
rom.write_byte(0x18003B, 0x01 if world.mapshuffle[player] not in ['none', 'district'] else 0x00) # maps showing crystals on overworld rom.write_byte(0x18003B, 0x01 if world.mapshuffle[player] not in ['none', 'nearby'] else 0x00) # maps showing crystals on overworld
# compasses showing dungeon count # compasses showing dungeon count
compass_mode = 0x80 if world.compassshuffle[player] not in ['none', 'district'] else 0x00 compass_mode = 0x80 if world.compassshuffle[player] not in ['none', 'nearby'] else 0x00
if world.clock_mode != 'none' or world.dungeon_counters[player] == 'off': if world.clock_mode != 'none' or world.dungeon_counters[player] == 'off':
pass pass
elif world.dungeon_counters[player] == 'on': elif world.dungeon_counters[player] == 'on':
compass_mode |= 0x02 # always on compass_mode |= 0x02 # always on
elif (world.compassshuffle[player] not in ['none', 'district'] or world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player] != 'none' elif (world.compassshuffle[player] not in ['none', 'nearby'] or world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player] != 'none'
or world.dungeon_counters[player] == 'pickup' or world.pottery[player] not in ['none', 'cave']): or world.dungeon_counters[player] == 'pickup' or world.pottery[player] not in ['none', 'cave']):
compass_mode |= 0x01 # show on pickup compass_mode |= 0x01 # show on pickup
if world.overworld_map[player] == 'map': if world.overworld_map[player] == 'map':
compass_mode |= 0x10 # show icon if map is collected compass_mode |= 0x10 # show icon if map is collected
elif world.overworld_map[player] == 'compass': elif world.overworld_map[player] == 'compass':
compass_mode |= 0x20 # show icon if compass is collected compass_mode |= 0x20 # show icon if compass is collected
if world.prizeshuffle[player] not in ['none', 'dungeon', 'district']: if world.prizeshuffle[player] not in ['none', 'dungeon', 'nearby']:
compass_mode |= 0x40 # show icon if boss is defeated, hide if collected compass_mode |= 0x40 # show icon if boss is defeated, hide if collected
rom.write_byte(0x18003C, compass_mode) rom.write_byte(0x18003C, compass_mode)
@@ -1333,7 +1335,7 @@ def patch_rom(world, rom, player, team, is_mystery=False):
map_index = max(0, dungeon_index - 2) map_index = max(0, dungeon_index - 2)
# write out dislocated coords # write out dislocated coords
if map_index >= 0x02 and map_index < 0x18 and (world.overworld_map[player] != 'default' or world.prizeshuffle[player] not in ['none', 'dungeon', 'district']): if map_index >= 0x02 and map_index < 0x18 and (world.overworld_map[player] != 'default' or world.prizeshuffle[player] not in ['none', 'dungeon', 'nearby']):
owid_map = [0x1E, 0x30, 0xFF, 0x7B, 0x5E, 0x70, 0x40, 0x75, 0x03, 0x58, 0x47] owid_map = [0x1E, 0x30, 0xFF, 0x7B, 0x5E, 0x70, 0x40, 0x75, 0x03, 0x58, 0x47]
x_map_position_generic = [0x03c0, 0x0740, 0xff00, 0x03c0, 0x01c0, 0x0bc0, 0x05c0, 0x09c0, 0x0ac0, 0x07c0, 0x0dc0] x_map_position_generic = [0x03c0, 0x0740, 0xff00, 0x03c0, 0x01c0, 0x0bc0, 0x05c0, 0x09c0, 0x0ac0, 0x07c0, 0x0dc0]
y_map_position_generic = [0xff00, 0xff00, 0xff00, 0x0fc0, 0x0fc0, 0x0fc0, 0x0fc0, 0x0fc0, 0xff00, 0x0fc0, 0x0fc0] y_map_position_generic = [0xff00, 0xff00, 0xff00, 0x0fc0, 0x0fc0, 0x0fc0, 0x0fc0, 0x0fc0, 0xff00, 0x0fc0, 0x0fc0]
@@ -1347,7 +1349,7 @@ def patch_rom(world, rom, player, team, is_mystery=False):
write_int16(rom, snes_to_pc(0x0ABE2E)+(map_index*6)+6, y_map_position_generic[idx]) write_int16(rom, snes_to_pc(0x0ABE2E)+(map_index*6)+6, y_map_position_generic[idx])
# write out icon coord data # write out icon coord data
if world.prizeshuffle[player] not in ['none', 'dungeon', 'district'] and dungeon_table[dungeon].prize: if world.prizeshuffle[player] not in ['none', 'dungeon', 'nearby'] and dungeon_table[dungeon].prize:
dungeon_obj = world.get_dungeon(dungeon, player) dungeon_obj = world.get_dungeon(dungeon, player)
entrance = dungeon_obj.prize.get_map_location() entrance = dungeon_obj.prize.get_map_location()
coords = get_entrance_coords(entrance) coords = get_entrance_coords(entrance)
@@ -1387,7 +1389,7 @@ def patch_rom(world, rom, player, team, is_mystery=False):
# figure out compass entrances and what world (light/dark) # figure out compass entrances and what world (light/dark)
write_int16s(rom, snes_to_pc(0x0ABE2E)+(map_index*6), coords) write_int16s(rom, snes_to_pc(0x0ABE2E)+(map_index*6), coords)
if world.prizeshuffle[player] in ['none', 'dungeon', 'district'] and dungeon_table[dungeon].prize: if world.prizeshuffle[player] in ['none', 'dungeon', 'nearby'] and dungeon_table[dungeon].prize:
# prize location # prize location
write_int16s(rom, snes_to_pc(0x0ABE2E)+(map_index*6)+8, coords) write_int16s(rom, snes_to_pc(0x0ABE2E)+(map_index*6)+8, coords)
@@ -1426,7 +1428,7 @@ def patch_rom(world, rom, player, team, is_mystery=False):
# b - Big Key # b - Big Key
# a - Small Key # a - Small Key
# #
enable_menu_map_check = (world.overworld_map[player] != 'default' and world.shuffle[player] != 'vanilla') or world.prizeshuffle[player] not in ['none', 'dungeon', 'district'] enable_menu_map_check = (world.overworld_map[player] != 'default' and world.shuffle[player] != 'vanilla') or world.prizeshuffle[player] not in ['none', 'dungeon', 'nearby']
rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] not in ['none', 'universal'] else 0x00) rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] not in ['none', 'universal'] else 0x00)
| (0x02 if world.bigkeyshuffle[player] != 'none' else 0x00) | (0x02 if world.bigkeyshuffle[player] != 'none' else 0x00)
| (0x04 if world.mapshuffle[player] != 'none' or enable_menu_map_check else 0x00) | (0x04 if world.mapshuffle[player] != 'none' or enable_menu_map_check else 0x00)
@@ -2329,7 +2331,7 @@ def write_strings(rom, world, player, team):
(crystal5, crystal6, greenpendant) = tuple([x.parent_region.dungeon.name for x in [crystal5, crystal6, greenpendant]]) (crystal5, crystal6, greenpendant) = tuple([x.parent_region.dungeon.name for x in [crystal5, crystal6, greenpendant]])
tt['bomb_shop'] = 'Big Bomb?\nMy supply is blocked until you clear %s and %s.' % (crystal5, crystal6) tt['bomb_shop'] = 'Big Bomb?\nMy supply is blocked until you clear %s and %s.' % (crystal5, crystal6)
tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant
elif world.prizeshuffle[player] == 'district': elif world.prizeshuffle[player] == 'nearby':
(crystal5, crystal6, greenpendant) = tuple([x.item.dungeon_object.name for x in [crystal5, crystal6, greenpendant]]) (crystal5, crystal6, greenpendant) = tuple([x.item.dungeon_object.name for x in [crystal5, crystal6, greenpendant]])
tt['bomb_shop'] = 'Big Bomb?\nThe crystals can be found near %s and %s.' % (crystal5, crystal6) tt['bomb_shop'] = 'Big Bomb?\nThe crystals can be found near %s and %s.' % (crystal5, crystal6)
tt['sahasrahla_bring_courage'] = 'I lost my family heirloom near %s' % greenpendant tt['sahasrahla_bring_courage'] = 'I lost my family heirloom near %s' % greenpendant

View File

@@ -1306,7 +1306,7 @@ def ow_terrain_rules(world, player):
ent = world.get_entrance(edge.name, player) ent = world.get_entrance(edge.name, player)
if edge.terrain == Terrain.Land: if edge.terrain == Terrain.Land:
set_rule(ent, lambda state: state.has('Flippers', player)) set_rule(ent, lambda state: state.has('Flippers', player))
if ent.parent_region.is_light_world == (world.mode[player] != 'inverted') and ent.connected_region.is_dark_world == (world.mode[player] != 'inverted'): if ent.connected_region.is_dark_world == (world.mode[player] != 'inverted'):
add_rule(ent, lambda state: state.has_Pearl(player)) add_rule(ent, lambda state: state.has_Pearl(player))
for whirlpool_name in OWExitTypes['Whirlpool']: for whirlpool_name in OWExitTypes['Whirlpool']:

View File

@@ -173,20 +173,21 @@ def dungeon_reentry_rules(
def underworld_glitches_rules(world, player): def underworld_glitches_rules(world, player):
def mire_clip(state): def mire_clip(state):
torches = world.get_region("Mire Torches Top", player) torches = world.get_region("Mire Torches Top", player)
return state.can_dash_clip(torches, player) or ( return (state.can_dash_clip(torches, player)
state.can_bomb_clip(torches, player) and state.has_fire_source(player) or (state.can_bomb_clip(torches, player) and state.has_fire_source(player))
) ) and state.can_reach(torches, player)
def hera_clip(state): def hera_clip(state):
hera = world.get_region("Hera 4F", player) hera = world.get_region("Hera 4F", player)
return state.can_bomb_clip(hera, player) or state.can_dash_clip(hera, player) return (state.can_bomb_clip(hera, player) or state.can_dash_clip(hera, player)) \
and state.has("Flippers", player) and state.can_reach(hera) and mire_clip(state)
# We use these plus functool.partial because lambdas don't work in loops properly. # We use these plus functool.partial because lambdas don't work in loops properly.
def bomb_clip(state, region, player): def bomb_clip(state, region, player):
return state.can_bomb_clip(region, player) return state.can_bomb_clip(region, player) and state.can_reach(region, player)
def dash_clip(state, region, player): def dash_clip(state, region, player):
return state.can_dash_clip(region, player) return state.can_dash_clip(region, player) and state.can_reach(region, player)
# Bomb clips # Bomb clips
for clip in ( for clip in (
kikiskip_spots kikiskip_spots
@@ -233,18 +234,24 @@ def underworld_glitches_rules(world, player):
# Allow mire big key to be used in Hera # Allow mire big key to be used in Hera
Rules.add_rule( Rules.add_rule(
world.get_entrance("Hera Startile Corner NW", player), world.get_entrance("Hera Startile Corner NW", player),
lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), lambda state: state.has("Big Key (Misery Mire)", player) and mire_clip(state),
combine="or", combine="or",
) )
Rules.add_rule( Rules.add_rule(
world.get_location("Tower of Hera - Big Chest", player), world.get_location("Tower of Hera - Big Chest", player),
lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), lambda state: state.has("Big Key (Misery Mire)", player) and mire_clip(state),
combine="or", combine="or",
) )
# This uses the mire clip because it's always expected to come from mire # This uses the mire clip because it's always expected to come from mire
Rules.set_rule( Rules.set_rule(
world.get_entrance("Hera to Swamp Clip", player), world.get_entrance("Hera to Swamp Clip", player),
lambda state: mire_clip(state) and state.has("Flippers", player), lambda state: state.has("Flippers", player) and mire_clip(state),
)
Rules.add_rule(
world.get_location("Swamp Palace - Big Chest", player),
lambda state: (state.has("Big Key (Misery Mire)", player) or state.has("Big Key (Tower of Hera)", player)) \
and state.has("Flippers", player) and mire_clip(state),
combine="or",
) )
# 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 # 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 # Flippers required for all of these doors to prevent locks when flooding
@@ -263,9 +270,9 @@ def underworld_glitches_rules(world, player):
]: ]:
Rules.add_rule( Rules.add_rule(
world.get_entrance(door, player), world.get_entrance(door, player),
lambda state: mire_clip(state) lambda state: state.has("Flippers", player)
and state.has("Small Key (Misery Mire)", player, count=6) and state.has("Small Key (Misery Mire)", player, count=6)
and state.has("Flippers", player), and mire_clip(state),
combine="or", combine="or",
) )
@@ -311,8 +318,8 @@ def underworld_glitches_rules(world, player):
return ( return (
state.can_reach("Old Man S&Q", "Entrance", player) state.can_reach("Old Man S&Q", "Entrance", player)
and state.has("Flippers", player) and state.has("Flippers", player)
and mire_clip(state)
and (hera_rule(state) or gt_rule(state)) and (hera_rule(state) or gt_rule(state))
and mire_clip(state)
) )
Rules.add_rule( Rules.add_rule(

Binary file not shown.

View File

@@ -151,7 +151,7 @@
prize_shuffle: prize_shuffle:
none: 1 none: 1
dungeon: 1 dungeon: 1
district: 1 nearby: 1
wild: 1 wild: 1
dungeon_items: dungeon_items:
standard: 10 standard: 10
@@ -162,20 +162,20 @@
# for use when you aren't using the dungeon_items above # for use when you aren't using the dungeon_items above
# map_shuffle: # map_shuffle:
# none: 1 # none: 1
# district: 1 # nearby: 1
# wild: 1 # wild: 1
# compass_shuffle: # compass_shuffle:
# none: 1 # none: 1
# district: 1 # nearby: 1
# wild: 1 # wild: 1
# smallkey_shuffle: # smallkey_shuffle:
# none: 5 # none: 5
# district: 1 # nearby: 1
# wild: 1 # wild: 1
# universal: 1 # universal: 1
# bigkey_shuffle: # bigkey_shuffle:
# none: 1 # none: 1
# district: 1 # nearby: 1
# wild: 1 # wild: 1
dungeon_counters: dungeon_counters:
on: 5 on: 5

View File

@@ -558,6 +558,10 @@ class Sprite(object):
if self.location is not None: if self.location is not None:
item_id = self.location.item.code if self.location.item is not None else 0x5A item_id = self.location.item.code if self.location.item is not None else 0x5A
code = 0xF9 if self.location.item.player != self.location.player else 0xF8 code = 0xF9 if self.location.item.player != self.location.player else 0xF8
if code == 0xF8:
world = self.location.parent_region.world
if world.dropshuffle[self.location.player] == 'none' or self.location.locked:
item_id = handle_native_dungeon(self.location, item_id)
data.append(item_id) data.append(item_id)
data.append(0 if code == 0xF8 else self.location.item.player) data.append(0 if code == 0xF8 else self.location.item.player)
data.append(code) data.append(code)

View File

@@ -405,7 +405,7 @@ def do_old_man_cave_exit(entrances, exits, avail, cross_world):
region_name = 'West Death Mountain (Top)' region_name = 'West Death Mountain (Top)'
else: else:
region_name = 'West Dark Death Mountain (Top)' region_name = 'West Dark Death Mountain (Top)'
om_cave_options = list(get_accessible_entrances(region_name, avail, [], cross_world, True, True, True)) om_cave_options = list(get_accessible_entrances(region_name, avail, [], cross_world, True, True, True, True))
om_cave_options = [e for e in om_cave_options if e in entrances and e != 'Old Man House (Bottom)'] om_cave_options = [e for e in om_cave_options if e in entrances and e != 'Old Man House (Bottom)']
if avail.swapped: if avail.swapped:
om_cave_options = [e for e in om_cave_options if e not in Forbidden_Swap_Entrances] om_cave_options = [e for e in om_cave_options if e not in Forbidden_Swap_Entrances]
@@ -863,7 +863,7 @@ def get_nearby_entrances(avail, start_region):
return candidates return candidates
def get_accessible_entrances(start_region, avail, assumed_inventory=[], cross_world=False, region_rules=True, exit_rules=True, include_one_ways=False): def get_accessible_entrances(start_region, avail, assumed_inventory=[], cross_world=False, region_rules=True, exit_rules=True, include_one_ways=False, restrictive_follower=False):
from Main import copy_world_premature from Main import copy_world_premature
from BaseClasses import CollectionState from BaseClasses import CollectionState
from Items import ItemFactory from Items import ItemFactory
@@ -878,10 +878,12 @@ def get_accessible_entrances(start_region, avail, assumed_inventory=[], cross_wo
blank_state = CollectionState(base_world) blank_state = CollectionState(base_world)
if base_world.mode[avail.player] == 'standard': if base_world.mode[avail.player] == 'standard':
blank_state.collect(ItemFactory('Zelda Delivered', avail.player), True) blank_state.collect(ItemFactory('Zelda Delivered', avail.player), True)
if base_world.logic[avail.player] in ['owglitches', 'hybridglitches', 'nologic']:
blank_state.collect(ItemFactory('Pegasus Boots', avail.player), True)
for item in assumed_inventory: for item in assumed_inventory:
blank_state.collect(ItemFactory(item, avail.player), True) blank_state.collect(ItemFactory(item, avail.player), True)
explored_regions = list(build_accessible_region_list(base_world, start_region, avail.player, False, cross_world, region_rules, False)) explored_regions = list(build_accessible_region_list(base_world, start_region, avail.player, False, cross_world, region_rules, False, restrictive_follower))
if include_one_ways: if include_one_ways:
new_regions = list() new_regions = list()
@@ -1284,8 +1286,9 @@ def handle_skull_woods_drops(avail, pool, mode_cfg):
keep_together = mode_cfg['keep_drops_together'] == 'on' if 'keep_drops_together' in mode_cfg else True keep_together = mode_cfg['keep_drops_together'] == 'on' if 'keep_drops_together' in mode_cfg else True
if keep_together: if keep_together:
for drop in ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)']: for drop in ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)']:
target = drop_map[drop] if drop in avail.entrances:
connect_entrance(drop, target, avail) target = drop_map[drop]
connect_entrance(drop, target, avail)
def handle_skull_woods_entrances(avail, pool): def handle_skull_woods_entrances(avail, pool):