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:
@@ -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
3
CLI.py
@@ -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": "",
|
||||||
|
|||||||
@@ -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,10 +929,18 @@ 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)
|
||||||
|
|
||||||
palette_assignment(world, player)
|
if world.standardize_palettes[player] == 'standardize':
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
Doors.py
1
Doors.py
@@ -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),
|
||||||
|
|||||||
@@ -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)')
|
||||||
|
|||||||
@@ -978,21 +978,31 @@ 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
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
5
Main.py
5
Main.py
@@ -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':
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
11
Regions.py
11
Regions.py
@@ -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
31
Rom.py
@@ -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
|
||||||
|
|||||||
4
Rules.py
4
Rules.py
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
9
asm/dr_lobby.asm
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -121,20 +121,19 @@ 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
|
phx
|
||||||
pla : sta $00
|
lda $040c : lsr : tax
|
||||||
lda !MULTIWORLD_ITEM_PLAYER_ID : bne .end
|
lda $00 : cmp KeyTable, x : bne +
|
||||||
phx
|
- plx : pla : rtl
|
||||||
lda $040c : lsr : tax
|
+ cmp #$af : beq - ; universal key
|
||||||
lda $00 : cmp KeyTable, x : bne +
|
cmp #$24 : beq - ; small key for this dungeon
|
||||||
- plx : pla : rtl
|
plx
|
||||||
+ cmp #$af : beq - ; universal key
|
.receive
|
||||||
cmp #$24 : beq - ; small key for this dungeon
|
jsl.l $0791b3 ; Player_HaltDashAttackLong
|
||||||
plx
|
jsl.l Link_ReceiveItem
|
||||||
.end
|
|
||||||
pla : dec : rtl
|
pla : dec : rtl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -70,6 +70,12 @@
|
|||||||
"force"
|
"force"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"standardize_palettes" : {
|
||||||
|
"choices": [
|
||||||
|
"standardize",
|
||||||
|
"original"
|
||||||
|
]
|
||||||
|
},
|
||||||
"timer": {
|
"timer": {
|
||||||
"choices": [
|
"choices": [
|
||||||
"none",
|
"none",
|
||||||
|
|||||||
@@ -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)"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user