Added option to keep original palettes in crossed dungeon mode

If sanc if in a DW dungeon because of crossed+ ER, then you start in bunny form
Mirroring from sanc to the portal is now in logic
Another fix for animated tiles (fairy fountains)
GT Big Key stat changed on credits

Some standard logic fixes for lobbies (more outstanding)
This commit is contained in:
aerinon
2020-11-16 10:51:26 -07:00
parent 11154e1544
commit 4dda394a90
24 changed files with 186 additions and 37 deletions

View File

@@ -129,6 +129,7 @@ class World(object):
set_player_attr('keydropshuffle', False) set_player_attr('keydropshuffle', False)
set_player_attr('mixed_travel', 'prevent') set_player_attr('mixed_travel', 'prevent')
set_player_attr('standardize_palettes', 'standardize')
set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False}); set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False});
def get_name_string_for_object(self, obj): def get_name_string_for_object(self, obj):
@@ -148,6 +149,11 @@ class World(object):
for door in doors: for door in doors:
self._door_cache[(door.name, door.player)] = door self._door_cache[(door.name, door.player)] = door
def remove_door(self, door, player):
if (door, player) in self._door_cache.keys():
del self._door_cache[(door, player)]
self.doors.remove(door)
def get_regions(self, player=None): def get_regions(self, player=None):
return self.regions if player is None else self._region_cache[player].values() return self.regions if player is None else self._region_cache[player].values()
@@ -176,6 +182,10 @@ class World(object):
return exit return exit
raise RuntimeError('No such entrance %s for player %d' % (entrance, player)) raise RuntimeError('No such entrance %s for player %d' % (entrance, player))
def remove_entrance(self, entrance, player):
if (entrance, player) in self._entrance_cache.keys():
del self._entrance_cache[(entrance, player)]
def get_location(self, location, player): def get_location(self, location, player):
if isinstance(location, Location): if isinstance(location, Location):
return location return location
@@ -862,6 +872,7 @@ class CollectionState(object):
@unique @unique
class RegionType(Enum): class RegionType(Enum):
Menu = 0
LightWorld = 1 LightWorld = 1
DarkWorld = 2 DarkWorld = 2
Cave = 3 # Also includes Houses Cave = 3 # Also includes Houses

3
CLI.py
View File

@@ -95,7 +95,7 @@ def parse_cli(argv, no_defaults=False):
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
'remote_items', 'keydropshuffle', 'mixed_travel']: 'remote_items', 'keydropshuffle', 'mixed_travel', 'standardize_palettes']:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1: if player == 1:
setattr(ret, name, {1: value}) setattr(ret, name, {1: value})
@@ -146,6 +146,7 @@ def parse_settings():
"experimental": False, "experimental": False,
"dungeon_counters": "default", "dungeon_counters": "default",
"mixed_travel": "prevent", "mixed_travel": "prevent",
"standardize_palettes": "standardize",
"multi": 1, "multi": 1,
"names": "", "names": "",

View File

@@ -41,6 +41,14 @@ def link_doors(world, player):
for entrance, ext in straight_staircases: for entrance, ext in straight_staircases:
connect_two_way(world, entrance, ext, player) connect_two_way(world, entrance, ext, player)
if world.intensity[player] < 3 or world.doorShuffle == 'vanilla':
mirror_route = world.get_entrance('Sanctuary Mirror Route', player)
mr_door = mirror_route.door
sanctuary = mirror_route.parent_region
sanctuary.exits.remove(mirror_route)
world.remove_entrance(mirror_route, player)
world.remove_door(mr_door, player)
connect_custom(world, player) connect_custom(world, player)
find_inaccessible_regions(world, player) find_inaccessible_regions(world, player)
@@ -637,6 +645,11 @@ def within_dungeon(world, player):
logging.getLogger('').info('%s: %s', world.fish.translate("cli", "cli", "keydoor.shuffle.time"), time.process_time()-start) logging.getLogger('').info('%s: %s', world.fish.translate("cli", "cli", "keydoor.shuffle.time"), time.process_time()-start)
smooth_door_pairs(world, player) smooth_door_pairs(world, player)
if world.intensity[player] >= 3:
portal = world.get_portal('Sanctuary', player)
target = portal.door.entrance.parent_region
connect_simple_door(world, 'Sanctuary Mirror Route', target, player)
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info): def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info):
dungeon_entrances, split_dungeon_entrances, world, player = builder_info dungeon_entrances, split_dungeon_entrances, world, player = builder_info
@@ -916,9 +929,17 @@ def cross_dungeon(world, player):
if state.visited_at_all(sanctuary): if state.visited_at_all(sanctuary):
reachable_portals.append(portal) reachable_portals.append(portal)
world.sanc_portal[player] = random.choice(reachable_portals) world.sanc_portal[player] = random.choice(reachable_portals)
if world.intensity[player] >= 3:
if player in world.sanc_portal:
portal = world.sanc_portal[player]
else:
portal = world.get_portal('Sanctuary', player)
target = portal.door.entrance.parent_region
connect_simple_door(world, 'Sanctuary Mirror Route', target, player)
check_entrance_fixes(world, player) check_entrance_fixes(world, player)
if world.standardize_palettes[player] == 'standardize':
palette_assignment(world, player) palette_assignment(world, player)
refine_hints(dungeon_builders) refine_hints(dungeon_builders)
@@ -1093,6 +1114,10 @@ def palette_assignment(world, player):
queue.append((ext.connected_region, dist+1)) queue.append((ext.connected_region, dist+1))
visited_regions.add(ext.connected_region) visited_regions.add(ext.connected_region)
sanc = world.get_region('Sanctuary', player)
if sanc.dungeon.name == 'Hyrule Castle':
room = world.get_room(0x12, player)
room.palette = 0x1d
for connection in ['Sanctuary S', 'Sanctuary N']: for connection in ['Sanctuary S', 'Sanctuary N']:
adjacent = world.get_entrance(connection, player) adjacent = world.get_entrance(connection, player)
if adjacent.door.dest and adjacent.door.dest.entrance.parent_region.type == RegionType.Dungeon: if adjacent.door.dest and adjacent.door.dest.entrance.parent_region.type == RegionType.Dungeon:
@@ -1143,7 +1168,7 @@ def convert_to_sectors(region_names, world, player):
if existing not in matching_sectors: if existing not in matching_sectors:
matching_sectors.append(existing) matching_sectors.append(existing)
else: else:
if door and not door.controller and not door.dest and not door.entranceFlag: if door and not door.controller and not door.dest and not door.entranceFlag and door.type != DoorType.Logical:
outstanding_doors.append(door) outstanding_doors.append(door)
sector = Sector() sector = Sector()
if not new_sector: if not new_sector:
@@ -1418,7 +1443,8 @@ def find_key_door_candidates(region, checked, world, player):
valid = True valid = True
if valid and d not in candidates: if valid and d not in candidates:
candidates.append(d) candidates.append(d)
if ext.connected_region.type != RegionType.Dungeon or ext.connected_region.dungeon == dungeon: connected = ext.connected_region
if connected and (connected.type != RegionType.Dungeon or connected.dungeon == dungeon):
queue.append((ext.connected_region, d, current)) queue.append((ext.connected_region, d, current))
if d is not None: if d is not None:
checked_doors.append(d) checked_doors.append(d)
@@ -1815,6 +1841,8 @@ class DROptions(Flag):
Map_Info = 0x04 Map_Info = 0x04
Debug = 0x08 Debug = 0x08
Rails = 0x10 # If on, draws rails Rails = 0x10 # If on, draws rails
OriginalPalettes = 0x20
Reserved = 0x40 # Reserved for PoD sliding wall?
Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required

View File

@@ -120,6 +120,7 @@ def create_doors(world, player):
# logically one way the sanc, but should be linked - also toggle # logically one way the sanc, but should be linked - also toggle
create_door(player, 'Sanctuary N', Nrml).dir(No, 0x12, Mid, High).no_exit().toggler().pos(0), create_door(player, 'Sanctuary N', Nrml).dir(No, 0x12, Mid, High).no_exit().toggler().pos(0),
create_door(player, 'Sanctuary S', Nrml).dir(So, 0x12, Mid, High).pos(2).portal(Z, 0x22, 1), create_door(player, 'Sanctuary S', Nrml).dir(So, 0x12, Mid, High).pos(2).portal(Z, 0x22, 1),
create_door(player, 'Sanctuary Mirror Route', Lgcl),
# Eastern Palace # Eastern Palace
create_door(player, 'Eastern Lobby S', Nrml).dir(So, 0xc9, Mid, High).pos(4).portal(Z, 0x20), create_door(player, 'Eastern Lobby S', Nrml).dir(So, 0xc9, Mid, High).pos(4).portal(Z, 0x20),

View File

@@ -266,7 +266,7 @@ def generate_itempool(world, player):
amt = world.pool_adjustment[player] amt = world.pool_adjustment[player]
if amt < 0: if amt < 0:
for _ in range(amt, 0): for _ in range(amt, 0):
pool.remove('Rupees (20)') pool.remove(next(iter([x for x in pool if x in ['Rupees (20)', 'Rupees (5)', 'Rupee (1)']])))
elif amt > 0: elif amt > 0:
for _ in range(0, amt): for _ in range(0, amt):
pool.append('Rupees (20)') pool.append('Rupees (20)')

View File

@@ -978,11 +978,22 @@ def filter_big_chest(locations):
return [x for x in locations if '- Big Chest' not in x.name] return [x for x in locations if '- Big Chest' not in x.name]
def count_locations_exclude_logic(locations, key_logic):
cnt = 0
for loc in locations:
if loc not in key_logic.bk_restricted and not loc.forced_item and not prize_or_event(loc):
cnt += 1
return cnt
def prize_or_event(loc):
return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
def count_free_locations(state): def count_free_locations(state):
cnt = 0 cnt = 0
for loc in state.found_locations: for loc in state.found_locations:
if '- Prize' not in loc.name and loc.name not in dungeon_events and not loc.forced_item: if not prize_or_event(loc) and not loc.forced_item:
if loc.name not in ['Agahnim 1', 'Agahnim 2']:
cnt += 1 cnt += 1
return cnt return cnt
@@ -990,8 +1001,7 @@ def count_free_locations(state):
def count_locations_exclude_big_chest(state): def count_locations_exclude_big_chest(state):
cnt = 0 cnt = 0
for loc in state.found_locations: for loc in state.found_locations:
if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events: if '- Big Chest' not in loc.name and not loc.forced_item and not prize_or_event(loc):
if not loc.forced_item and loc.name not in ['Agahnim 1', 'Agahnim 2']:
cnt += 1 cnt += 1
return cnt return cnt

View File

@@ -24,7 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
from ItemList import generate_itempool, difficulties, fill_prizes, fill_specific_items from ItemList import generate_itempool, difficulties, fill_prizes, fill_specific_items
from Utils import output_path, parse_player_names from Utils import output_path, parse_player_names
__version__ = '0.2.0.10u' __version__ = '0.2.0.11u'
class EnemizerError(RuntimeError): class EnemizerError(RuntimeError):
pass pass
@@ -68,6 +68,7 @@ def main(args, seed=None, fish=None):
world.fish = fish world.fish = fish
world.keydropshuffle = args.keydropshuffle.copy() world.keydropshuffle = args.keydropshuffle.copy()
world.mixed_travel = args.mixed_travel.copy() world.mixed_travel = args.mixed_travel.copy()
world.standardize_palettes = args.standardize_palettes.copy()
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
@@ -126,6 +127,7 @@ def main(args, seed=None, fish=None):
else: else:
mark_dark_world_regions(world, player) mark_dark_world_regions(world, player)
logger.info(world.fish.translate("cli","cli","generating.itempool")) logger.info(world.fish.translate("cli","cli","generating.itempool"))
logger.info(world.fish.translate("cli","cli","generating.itempool"))
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
generate_itempool(world, player) generate_itempool(world, player)
@@ -379,6 +381,7 @@ def copy_world(world):
ret.experimental = world.experimental.copy() ret.experimental = world.experimental.copy()
ret.keydropshuffle = world.keydropshuffle.copy() ret.keydropshuffle = world.keydropshuffle.copy()
ret.mixed_travel = world.mixed_travel.copy() ret.mixed_travel = world.mixed_travel.copy()
ret.standardize_palettes = world.standardize_palettes.copy()
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
if world.mode[player] != 'inverted': if world.mode[player] != 'inverted':

View File

@@ -71,6 +71,16 @@ The rooms are left alone and it is up to the discretion of the player whether to
The two disjointed sections are forced to be in the same dungeon but never logically required to complete that game. The two disjointed sections are forced to be in the same dungeon but never logically required to complete that game.
### Standardize Palettes (--standardize_palettes)
No effect if door shuffle is not on crossed
#### Standardize
Rooms in the same dungeon have their palettes changed to match. Hyrule Castle is split between Sewer and HC palette.
Rooms adjacent to sanctuary get their coloring to match sanc.
#### Original
Room keep their original palettes.
## Map/Compass/Small Key/Big Key shuffle (aka Keysanity) ## Map/Compass/Small Key/Big Key shuffle (aka Keysanity)

View File

@@ -36,6 +36,13 @@ otherwise unconnected logically can be reach using these glitches. To prevent th
# Bug Fixes # Bug Fixes
* 2.0.11u
* Option to keep original palettes in crossed dungeon mode
* If sanc if in a DW dungeon because of crossed+ ER, then you start in bunny form
* Mirroring from sanc to the portal is now in logic
* Another fix for animated tiles (fairy fountains)
* GT Big Key stat fixed on credits
* Todo: Standard logic fixes for lobbies
* 2.0.10u * 2.0.10u
* Fix POD, TR, GT and SKULL 3 entrance if sanc ends up in that dungeon in crossed ER+ * Fix POD, TR, GT and SKULL 3 entrance if sanc ends up in that dungeon in crossed ER+
* TR Lobbies that need a bomb and can be entered before bombing should be pre-opened * TR Lobbies that need a bomb and can be entered before bombing should be pre-opened

View File

@@ -4,7 +4,7 @@ from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType
def create_regions(world, player): def create_regions(world, player):
world.regions += [ world.regions += [
create_lw_region(player, 'Menu', None, ['Links House S&Q', 'Sanctuary S&Q', 'Old Man S&Q']), create_menu_region(player, 'Menu', None, ['Links House S&Q', 'Sanctuary S&Q', 'Old Man S&Q']),
create_lw_region(player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest'], create_lw_region(player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest'],
["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Zoras River', 'Kings Grave Outer Rocks', 'Dam', ["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Zoras River', 'Kings Grave Outer Rocks', 'Dam',
'Links House', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave', 'Links House', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave',
@@ -273,7 +273,7 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Sewers Pull Switch', 'Hyrule Castle', None, ['Sewers Pull Switch N', 'Sewers Pull Switch S']), create_dungeon_region(player, 'Sewers Pull Switch', 'Hyrule Castle', None, ['Sewers Pull Switch N', 'Sewers Pull Switch S']),
create_dungeon_region(player, 'Sanctuary', 'Hyrule Castle', create_dungeon_region(player, 'Sanctuary', 'Hyrule Castle',
['Sanctuary'] if not std_flag else ['Sanctuary', 'Zelda Drop Off'], ['Sanctuary'] if not std_flag else ['Sanctuary', 'Zelda Drop Off'],
['Sanctuary S', 'Sanctuary N']), ['Sanctuary S', 'Sanctuary N', 'Sanctuary Mirror Route']),
# Eastern Palace # Eastern Palace
create_dungeon_region(player, 'Eastern Lobby', 'Eastern Palace', None, ['Eastern Lobby N', 'Eastern Lobby S', 'Eastern Lobby NW', 'Eastern Lobby NE']), create_dungeon_region(player, 'Eastern Lobby', 'Eastern Palace', None, ['Eastern Lobby N', 'Eastern Lobby S', 'Eastern Lobby NW', 'Eastern Lobby NE']),
@@ -788,15 +788,22 @@ def create_dungeon_regions(world, player):
world.get_region('GT Crystal Circles', player).crystal_switch = True world.get_region('GT Crystal Circles', player).crystal_switch = True
def create_menu_region(player, name, locations=None, exits=None):
return _create_region(player, name, RegionType.Menu, 'Menu', locations, exits)
def create_lw_region(player, name, locations=None, exits=None): def create_lw_region(player, name, locations=None, exits=None):
return _create_region(player, name, RegionType.LightWorld, 'Light World', locations, exits) return _create_region(player, name, RegionType.LightWorld, 'Light World', locations, exits)
def create_dw_region(player, name, locations=None, exits=None): def create_dw_region(player, name, locations=None, exits=None):
return _create_region(player, name, RegionType.DarkWorld, 'Dark World', locations, exits) return _create_region(player, name, RegionType.DarkWorld, 'Dark World', locations, exits)
def create_cave_region(player, name, hint='Hyrule', locations=None, exits=None): def create_cave_region(player, name, hint='Hyrule', locations=None, exits=None):
return _create_region(player, name, RegionType.Cave, hint, locations, exits) return _create_region(player, name, RegionType.Cave, hint, locations, exits)
def create_dungeon_region(player, name, hint='Hyrule', locations=None, exits=None): def create_dungeon_region(player, name, hint='Hyrule', locations=None, exits=None):
return _create_region(player, name, RegionType.Dungeon, hint, locations, exits) return _create_region(player, name, RegionType.Dungeon, hint, locations, exits)

31
Rom.py
View File

@@ -14,6 +14,7 @@ import bps.io
from BaseClasses import CollectionState, ShopType, Region, Location, DoorType, RegionType from BaseClasses import CollectionState, ShopType, Region, Location, DoorType, RegionType
from DoorShuffle import compass_data, DROptions, boss_indicator from DoorShuffle import compass_data, DROptions, boss_indicator
from Dungeons import dungeon_music_addresses from Dungeons import dungeon_music_addresses
from KeyDoorShuffle import count_locations_exclude_logic
from Regions import location_table from Regions import location_table
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
from Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts from Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts
@@ -24,7 +25,7 @@ from EntranceShuffle import door_addresses, exit_ids
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'e16cc8659527baecc02e3b49b83fa49b' RANDOMIZERBASEHASH = 'f6be3fdaac906a2217e7ee328e27b95b'
class JsonRom(object): class JsonRom(object):
@@ -623,11 +624,13 @@ def patch_rom(world, rom, player, team, enemized):
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal
if world.doorShuffle[player] == 'crossed': if world.doorShuffle[player] == 'crossed':
dr_flags |= DROptions.Map_Info dr_flags |= DROptions.Map_Info
if world.experimental[player]: if world.experimental[player] and world.goal[player] != 'triforcehunt':
dr_flags |= DROptions.Debug dr_flags |= DROptions.Debug
if world.doorShuffle[player] == 'crossed' and world.logic[player] != 'nologic'\ if world.doorShuffle[player] == 'crossed' and world.logic[player] != 'nologic'\
and world.mixed_travel[player] == 'prevent': and world.mixed_travel[player] == 'prevent':
dr_flags |= DROptions.Rails dr_flags |= DROptions.Rails
if world.standardize_palettes[player] == 'original':
dr_flags |= DROptions.OriginalPalettes
# fix hc big key problems (map and compass too) # fix hc big key problems (map and compass too)
@@ -656,6 +659,9 @@ def patch_rom(world, rom, player, team, enemized):
rom.write_byte(0x13f038+offset*2, bk_status) rom.write_byte(0x13f038+offset*2, bk_status)
if player in world.sanc_portal.keys(): if player in world.sanc_portal.keys():
rom.write_byte(0x159a6, world.sanc_portal[player].ent_offset) rom.write_byte(0x159a6, world.sanc_portal[player].ent_offset)
sanc_region = world.sanc_portal[player].door.entrance.parent_region
if sanc_region.is_dark_world and not sanc_region.is_light_world:
rom.write_byte(0x13ff00, 1)
for room in world.rooms: for room in world.rooms:
if room.player == player and room.palette is not None: if room.player == player and room.palette is not None:
rom.write_byte(0x13f200+room.index, room.palette) rom.write_byte(0x13f200+room.index, room.palette)
@@ -723,10 +729,10 @@ def patch_rom(world, rom, player, team, enemized):
# bot: $7A is 1, 7B is 2, etc so 7D=4, 82=9 (zero unknown...) # bot: $7A is 1, 7B is 2, etc so 7D=4, 82=9 (zero unknown...)
return 0x53+num, 0x79+num return 0x53+num, 0x79+num
# collection rate address: 238C37
if world.keydropshuffle[player]: if world.keydropshuffle[player]:
rom.write_byte(0x140000, 1) rom.write_byte(0x140000, 1)
rom.write_byte(0x187010, 249) # dynamic credits
# collection rate address: 238C37
mid_top, mid_bot = credits_digit(4) mid_top, mid_bot = credits_digit(4)
last_top, last_bot = credits_digit(9) last_top, last_bot = credits_digit(9)
# top half # top half
@@ -736,6 +742,23 @@ def patch_rom(world, rom, player, team, enemized):
rom.write_byte(0x118C71, mid_bot) rom.write_byte(0x118C71, mid_bot)
rom.write_byte(0x118C72, last_bot) rom.write_byte(0x118C72, last_bot)
if world.keydropshuffle[player] or world.doorShuffle[player] != 'vanilla':
gt = world.dungeon_layouts[player]['Ganons Tower']
gt_logic = world.key_logic[player]['Ganons Tower']
total = 0
for region in gt.master_sector.regions:
total += count_locations_exclude_logic(region.locations, gt_logic)
rom.write_byte(0x187012, total) # dynamic credits
# gt big key address: 238B59
mid_top, mid_bot = credits_digit(total // 10)
last_top, last_bot = credits_digit(total % 10)
# top half
rom.write_byte(0x118B75, mid_top)
rom.write_byte(0x118B76, last_top)
# bottom half
rom.write_byte(0x118B93, mid_bot)
rom.write_byte(0x118B94, last_bot)
# patch medallion requirements # patch medallion requirements
if world.required_medallions[player][0] == 'Bombos': if world.required_medallions[player][0] == 'Bombos':
rom.write_byte(0x180022, 0x00) # requirement rom.write_byte(0x180022, 0x00) # requirement

View File

@@ -832,8 +832,10 @@ def standard_rules(world, player):
set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player)) set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
# these are because of rails # these are because of rails
if world.shuffle[player] != 'vanilla': if world.shuffle[player] != 'vanilla':
# todo:
set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.has('Zelda Delivered', player)) set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.has('Zelda Delivered', player))
set_rule(world.get_entrance('Hyrule Castle Exit (West)', player), lambda state: state.has('Zelda Delivered', player)) set_rule(world.get_entrance('Hyrule Castle Exit (West)', player), lambda state: state.has('Zelda Delivered', player))
set_rule(world.get_entrance('Sanctuary Exit', player), lambda state: state.has('Zelda Delivered', player))
# too restrictive for crossed? # too restrictive for crossed?
def uncle_item_rule(item): def uncle_item_rule(item):
@@ -874,7 +876,7 @@ def standard_rules(world, player):
rule_list, debug_path = find_rules_for_zelda_delivery(world, player) rule_list, debug_path = find_rules_for_zelda_delivery(world, player)
set_rule(world.get_location('Zelda Drop Off', player), lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list)) set_rule(world.get_location('Zelda Drop Off', player), lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list))
for location in ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest']: for location in ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest', 'Maze Race']:
add_rule(world.get_location(location, player), lambda state: state.has('Zelda Delivered', player)) add_rule(world.get_location(location, player), lambda state: state.has('Zelda Delivered', player))
# Bonk Fairy (Light) is a notable omission in ER shuffles/Retro # Bonk Fairy (Light) is a notable omission in ER shuffles/Retro

View File

@@ -34,6 +34,7 @@ incsrc overrides.asm
incsrc edges.asm incsrc edges.asm
incsrc math.asm incsrc math.asm
incsrc hudadditions.asm incsrc hudadditions.asm
incsrc dr_lobby.asm
warnpc $279700 warnpc $279700
incsrc doortables.asm incsrc doortables.asm

View File

@@ -641,3 +641,8 @@ db $00,$07,$20,$20,$07,$07,$07,$07,$07,$20,$20,$07,$20,$20,$20,$20
db $07,$07,$02,$02,$02,$02,$07,$07,$07,$20,$20,$07,$20,$20,$20,$07 db $07,$07,$02,$02,$02,$02,$07,$07,$07,$20,$20,$07,$20,$20,$20,$07
;27f300 ;27f300
;
org $27ff00
SancDarkWorldFlag:
db 0

9
asm/dr_lobby.asm Normal file
View File

@@ -0,0 +1,9 @@
CheckDarkWorldSanc:
STA $A0 : STA $048E ; what we wrote over
LDA.l SancDarkWorldFlag : BEQ +
SEP #$30
LDA $A0 : CMP #$12 : BNE ++
LDA.l $7EF357 : BNE ++ ; moon pearl?
LDA #$17 : STA $5D : INC $02E0 : LDA.b #$40 : STA !DARK_WORLD
++ REP #$30
+ RTL

View File

@@ -153,6 +153,9 @@ JSL StoreTempBunnyState
org $08c450 ; <- ancilla_receive_item.asm : 146-148 (STY $5D : STZ $02D8) org $08c450 ; <- ancilla_receive_item.asm : 146-148 (STY $5D : STZ $02D8)
JSL RetrieveBunnyState : NOP JSL RetrieveBunnyState : NOP
org $02d9ce ; <- Bank02.asm : Dungeon_LoadEntrance 10829 (STA $A0 : STA $048E)
JSL CheckDarkWorldSanc : NOP
; These two, if enabled together, have implications for vanilla BK doors in IP/Hera/Mire ; These two, if enabled together, have implications for vanilla BK doors in IP/Hera/Mire
; IPBJ is common enough to consider not doing this. Mire is not a concern for vanilla - maybe glitched modes ; IPBJ is common enough to consider not doing this. Mire is not a concern for vanilla - maybe glitched modes
; Hera BK door back can be seen with Pot clipping - likely useful for no logic seeds ; Hera BK door back can be seen with Pot clipping - likely useful for no logic seeds

View File

@@ -34,7 +34,8 @@ GfxFixer:
} }
FixAnimatedTiles: FixAnimatedTiles:
LDA.L DRMode : cmp #$02 : bne + LDA.L DRMode : CMP #$02 : BNE +
LDA $040C : CMP.b #$FF : BEQ +
PHX PHX
LDX $A0 : LDA.l TilesetTable, x LDX $A0 : LDA.l TilesetTable, x
CMP $0AA1 : beq ++ CMP $0AA1 : beq ++
@@ -74,6 +75,7 @@ CgramAuxToMain: ; ripped this from bank02 because it ended with rts
OverridePaletteHeader: OverridePaletteHeader:
lda.l DRMode : cmp #$02 : bne + lda.l DRMode : cmp #$02 : bne +
lda.l DRFlags : and #$20 : bne +
cpx #$01c2 : !bge + cpx #$01c2 : !bge +
rep #$20 rep #$20
txa : lsr : tax txa : lsr : tax

View File

@@ -121,12 +121,9 @@ KeyGet:
lda $a0 : cmp #$87 : bne + lda $a0 : cmp #$87 : bne +
jsr ShouldKeyBeCountedForDungeon : bcc - jsr ShouldKeyBeCountedForDungeon : bcc -
jsl CountChestKeyLong : bra - jsl CountChestKeyLong : bra -
+ phy + sty $00
jsr KeyGetPlayer : sta !MULTIWORLD_ITEM_PLAYER_ID jsr KeyGetPlayer : sta !MULTIWORLD_ITEM_PLAYER_ID
jsl.l $0791b3 ; Player_HaltDashAttackLong lda !MULTIWORLD_ITEM_PLAYER_ID : bne .receive
jsl.l Link_ReceiveItem
pla : sta $00
lda !MULTIWORLD_ITEM_PLAYER_ID : bne .end
phx phx
lda $040c : lsr : tax lda $040c : lsr : tax
lda $00 : cmp KeyTable, x : bne + lda $00 : cmp KeyTable, x : bne +
@@ -134,7 +131,9 @@ KeyGet:
+ cmp #$af : beq - ; universal key + cmp #$af : beq - ; universal key
cmp #$24 : beq - ; small key for this dungeon cmp #$24 : beq - ; small key for this dungeon
plx plx
.end .receive
jsl.l $0791b3 ; Player_HaltDashAttackLong
jsl.l Link_ReceiveItem
pla : dec : rtl pla : dec : rtl
} }

Binary file not shown.

View File

@@ -70,6 +70,12 @@
"force" "force"
] ]
}, },
"standardize_palettes" : {
"choices": [
"standardize",
"original"
]
},
"timer": { "timer": {
"choices": [ "choices": [
"none", "none",

View File

@@ -251,6 +251,11 @@
"Allow: Take the rails off, \"I know what I'm doing\"", "Allow: Take the rails off, \"I know what I'm doing\"",
"Force: Force these troublesome connections to be in the same dungeon (but not in logic). No rails will appear" "Force: Force these troublesome connections to be in the same dungeon (but not in logic). No rails will appear"
], ],
"standardize_palettes": [
"In cross dungeon shuffle, we can keep the rooms original palette or attempt to standardize them",
"Standardize: Attempts to make the palette the same between dungeons",
"Original: Dungeons rooms retain original palettes"
],
"retro": [ "retro": [
"Keys are universal, shooting arrows costs rupees,", "Keys are universal, shooting arrows costs rupees,",
"and a few other little things make this more like Zelda-1. (default: %(default)s)" "and a few other little things make this more like Zelda-1. (default: %(default)s)"

View File

@@ -77,6 +77,10 @@
"randomizer.dungeon.mixed_travel.allow": "Allow Mixed Dungeon Travel", "randomizer.dungeon.mixed_travel.allow": "Allow Mixed Dungeon Travel",
"randomizer.dungeon.mixed_travel.force": "Force Reachable Areas to Same Dungeon", "randomizer.dungeon.mixed_travel.force": "Force Reachable Areas to Same Dungeon",
"randomizer.dungeon.standardize_palettes": "Crossed Dungeon Palette ",
"randomizer.dungeon.standardize_palettes.standardize": "Standardize Palettes",
"randomizer.dungeon.standardize_palettes.original": "Keep Original Palettes",
"randomizer.enemizer.potshuffle": "Pot Shuffle", "randomizer.enemizer.potshuffle": "Pot Shuffle",
"randomizer.enemizer.enemyshuffle": "Enemy Shuffle", "randomizer.enemizer.enemyshuffle": "Enemy Shuffle",

View File

@@ -35,7 +35,7 @@
}, },
"mixed_travel": { "mixed_travel": {
"type" : "selectbox", "type" : "selectbox",
"default": "auto", "default": "prevent",
"options": [ "options": [
"prevent", "prevent",
"allow", "allow",
@@ -44,6 +44,17 @@
"config": { "config": {
"width": 35 "width": 35
} }
},
"standardize_palettes" : {
"type": "selectbox",
"default": "standardize",
"options": [
"standardize",
"original"
],
"config": {
"width": 35
}
} }
} }
} }

View File

@@ -91,7 +91,8 @@ SETTINGSTOPROCESS = {
"dungeonintensity": "intensity", "dungeonintensity": "intensity",
"experimental": "experimental", "experimental": "experimental",
"dungeon_counters": "dungeon_counters", "dungeon_counters": "dungeon_counters",
"mixed_travel": "mixed_travel" "mixed_travel": "mixed_travel",
"standardize_palettes": "standardize_palettes",
}, },
"gameoptions": { "gameoptions": {
"hints": "hints", "hints": "hints",