Crossed dungeon palette refinement

Fixed some entrances that require reset otherwise
Fixed TR lobbies that need to be bombed
Fixed animated tiles in lobbies
Fixed wallmaster+lamp problem
Fixed some key rules that caused item requirements to be ignored
Fixed Old Man cave to be properly one-way in the graph
Fixed some odd key logic issues
This commit is contained in:
aerinon
2020-11-12 12:18:00 -07:00
parent 7d3bb07382
commit 11154e1544
14 changed files with 224 additions and 45 deletions

View File

@@ -129,6 +129,7 @@ class World(object):
set_player_attr('keydropshuffle', False)
set_player_attr('mixed_travel', 'prevent')
set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False});
def get_name_string_for_object(self, obj):
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})'

View File

@@ -398,6 +398,14 @@ def choose_portals(world, player):
if not hera_door.entranceFlag:
world.get_room(0x77, player).change(0, DoorKind.NormalLow2)
# tr rock bomb entrances
for portal in world.dungeon_portals[player]:
if not portal.destination and not portal.deadEnd:
if portal.door.name == 'TR Lazy Eyes SE':
world.get_room(0x23, player).change(0, DoorKind.DungeonEntrance)
if portal.door.name == 'TR Eye Bridge SW':
world.get_room(0xd5, player).change(0, DoorKind.DungeonEntrance)
if not world.swamp_patch_required[player]:
swamp_region = world.get_entrance('Swamp Palace', player).connected_region
if swamp_region.name != 'Swamp Lobby':
@@ -905,27 +913,13 @@ def cross_dungeon(world, player):
state.visit_region(start_area)
state.add_all_doors_check_unattached(start_area, world, player)
explore_state(state, world, player)
if state.visited(sanctuary):
if state.visited_at_all(sanctuary):
reachable_portals.append(portal)
world.sanc_portal[player] = random.choice(reachable_portals)
for portal in world.dungeon_portals[player]:
if portal.door.roomIndex >= 0:
room = world.get_room(portal.door.roomIndex, player)
if room.palette is None:
name = portal.door.entrance.parent_region.dungeon.name
room.palette = palette_map[name]
check_entrance_fixes(world, player)
for name, builder in dungeon_builders.items():
for region in builder.master_sector.regions:
for ext in region.exits:
if ext.door and ext.door.roomIndex >= 0:
room = world.get_room(ext.door.roomIndex, player)
if room.palette is None:
room.palette = palette_map[name]
eastfairies = world.get_room(0x89, player)
eastfairies.palette = palette_map[world.get_region('Eastern Courtyard', player).dungeon.name]
# other ones that could use programmatic treatment: Skull Boss x29, Hera Fairies xa7, Ice Boss xde
palette_assignment(world, player)
refine_hints(dungeon_builders)
@@ -1026,6 +1020,91 @@ def reassign_boss(boss_region, boss_key, builder, gt, world, player):
new_dungeon.bosses[boss_key] = gt_boss
def check_entrance_fixes(world, player):
# I believe these modes will be fine
if world.shuffle[player] not in ['insanity', 'insanity_legacy', 'madness_legacy']:
checks = {
'Palace of Darkness': 'pod',
'Skull Woods Final Section': 'sw',
'Turtle Rock': 'tr',
'Ganons Tower': 'gt',
}
if world.mode[player] == 'inverted':
del checks['Ganons Tower']
for ent_name, key in checks.items():
entrance = world.get_entrance(ent_name, player)
if entrance.connected_region.dungeon:
layout = world.dungeon_layouts[player][entrance.connected_region.dungeon.name]
if 'Sanctuary' in layout.master_sector.region_set():
world.force_fix[player][key] = True
def palette_assignment(world, player):
for portal in world.dungeon_portals[player]:
if portal.door.roomIndex >= 0:
room = world.get_room(portal.door.roomIndex, player)
if room.palette is None:
name = portal.door.entrance.parent_region.dungeon.name
room.palette = palette_map[name][0]
for name, builder in world.dungeon_layouts[player].items():
for region in builder.master_sector.regions:
for ext in region.exits:
if ext.door and ext.door.roomIndex >= 0 and ext.door.name not in palette_non_influencers:
room = world.get_room(ext.door.roomIndex, player)
if room.palette is None:
room.palette = palette_map[name][0]
for name, tuple in palette_map.items():
if tuple[1] is not None:
door_name = boss_indicator[name][1]
door = world.get_door(door_name, player)
room = world.get_room(door.roomIndex, player)
room.palette = tuple[1]
if tuple[2]:
leading_door = world.get_door(tuple[2], player)
ent = next(iter(leading_door.entrance.parent_region.entrances))
if ent.door and door.roomIndex:
room = world.get_room(door.roomIndex, player)
room.palette = tuple[1]
rat_path = world.get_region('Sewers Rat Path', player)
visited_rooms = set()
visited_regions = {rat_path}
queue = deque([(rat_path, 0)])
while len(queue) > 0:
region, dist = queue.popleft()
if dist > 5:
continue
for ext in region.exits:
if ext.door and ext.door.roomIndex >= 0 and ext.door.name not in palette_non_influencers:
room_idx = ext.door.roomIndex
if room_idx not in visited_rooms:
room = world.get_room(room_idx, player)
room.palette = 0x1
visited_rooms.add(room_idx)
if ext.door and ext.door.type in [DoorType.SpiralStairs, DoorType.Ladder]:
if ext.door.dest and ext.door.dest.roomIndex:
visited_rooms.add(ext.door.dest.roomIndex)
if ext.connected_region:
visited_regions.add(ext.connected_region)
elif ext.connected_region and ext.connected_region.type == RegionType.Dungeon and ext.connected_region not in visited_regions:
queue.append((ext.connected_region, dist+1))
visited_regions.add(ext.connected_region)
for connection in ['Sanctuary S', 'Sanctuary N']:
adjacent = world.get_entrance(connection, player)
if adjacent.door.dest and adjacent.door.dest.entrance.parent_region.type == RegionType.Dungeon:
if adjacent.door and adjacent.door.dest and adjacent.door.dest.roomIndex >= 0:
room = world.get_room(adjacent.door.dest.roomIndex, player)
room.palette = 0x1d
eastfairies = world.get_room(0x89, player)
eastfairies.palette = palette_map[world.get_region('Eastern Courtyard', player).dungeon.name][0]
# other ones that could use programmatic treatment: Skull Boss x29, Hera Fairies xa7, Ice Boss xde (Ice Fairies!)
def refine_hints(dungeon_builders):
for name, builder in dungeon_builders.items():
for region in builder.master_sector.regions:
@@ -2525,14 +2604,49 @@ boss_indicator = {
'Ganons Tower': (0x1a, 'GT Agahnim 2 SW')
}
# tuples: (non-boss, boss)
# see Utils for other notes
palette_map = {
'Hyrule Castle': 0x0, 'Eastern Palace': 0xb, 'Desert Palace': 0x4, 'Agahnims Tower': 0xc,
'Swamp Palace': 0x8, 'Palace of Darkness': 0x10, 'Misery Mire': 0x11, 'Skull Woods': 0xe,
'Ice Palace': 0x14, 'Tower of Hera': 0x6, 'Thieves Town': 0x17, 'Turtle Rock': 0x19,
'Ganons Tower': 0x1b
'Hyrule Castle': (0x0, None),
'Eastern Palace': (0xb, None),
'Desert Palace': (0x9, 0x4, 'Desert Boss SW'),
'Agahnims Tower': (0x0, 0xc, 'Tower Agahnim 1 SW'), # ancillary 0x26 for F1, F4
'Swamp Palace': (0xa, 0x8, 'Swamp Boss SW'),
'Palace of Darkness': (0xf, 0x10, 'PoD Boss SE'),
'Misery Mire': (0x11, 0x12, 'Mire Boss SW'),
'Skull Woods': (0xd, 0xe, 'Skull Spike Corner SW'),
'Ice Palace': (0x13, 0x14, 'Ice Antechamber NE'),
'Tower of Hera': (0x6, None),
'Thieves Town': (0x17, None), # the attic uses 0x23
'Turtle Rock': (0x18, 0x19, 'TR Boss SW'),
'Ganons Tower': (0x28, 0x1b, 'GT Agahnim 2 SW'), # other palettes: 0x1a (other) 0x24 (Gauntlet - Lanmo) 0x25 (conveyor-torch-wizzrode moldorm pit f5?)
}
# todo: inverted
# implications:
# pipe room -> where lava chest is
# dark alley -> where pod basement is
# conveyor star or hidden star -> where DMs room is
# falling bridge -> where Rando room is
# petting zoo -> where firesnake is
# basement cage -> where tile room is
# bob's room -> where big chest/hope/torch are
# invis bridges -> compass room
palette_non_influencers = {
'PoD Shooter Room Up Stairs', 'TR Lava Dual Pipes EN', 'TR Lava Dual Pipes WN', 'TR Lava Dual Pipes SW',
'TR Lava Escape SE', 'TR Lava Escape NW', 'PoD Arena Ledge ES', 'Swamp Big Key Ledge WN', 'Swamp Hub Dead Ledge EN',
'Swamp Map Ledge EN', 'Skull Pot Prison ES', 'Skull Pot Prison SE', 'PoD Dark Alley NE', 'GT Conveyor Star Pits EN',
'GT Hidden Star ES', 'GT Falling Bridge WN', 'GT Falling Bridge WS', 'GT Petting Zoo SE',
'Hera Basement Cage Up Stairs', "GT Bob's Room SE", 'GT Warp Maze (Pits) ES', 'GT Invisible Bridges WS',
'Mire Over Bridge E', 'Mire Over Bridge W', 'Eastern Courtyard Ledge S', 'Eastern Courtyard Ledge W',
'Eastern Courtyard Ledge E', 'Eastern Map Valley WN', 'Eastern Map Valley SW', 'Mire BK Door Room EN',
'Mire BK Door Room N', 'TR Tile Room SE', 'TR Tile Room NE', 'TR Refill SE', 'Eastern Cannonball Ledge WN',
'Eastern Cannonball Ledge Key Door EN', 'Mire Neglected Room SE', 'Mire Neglected Room NE', 'Mire Chest View NE',
'TR Compass Room NW', 'Desert Dead End Edge', 'Hyrule Dungeon South Abyss Catwalk North Edge',
'Hyrule Dungeon South Abyss Catwalk West Edge'
}
portal_map = {
'Sanctuary': ('Sanctuary', 'Sanctuary Exit'),
'Hyrule Castle West': ('Hyrule Castle Entrance (West)', 'Hyrule Castle Exit (West)'),

View File

@@ -1067,7 +1067,7 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, all_reg
explorable_door = local_state.next_avail_door()
if explorable_door.door.bigKey:
if bk_flag:
big_not_found = not special_big_key_found(local_state, world, player) if local_state.big_key_special else local_state.count_locations_exclude_specials() == 0
big_not_found = not special_big_key_found(local_state) if local_state.big_key_special else local_state.count_locations_exclude_specials() == 0
if big_not_found:
continue # we can't open this door
if explorable_door.door in proposed_map:
@@ -1083,9 +1083,11 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, all_reg
return local_state
def special_big_key_found(state, world, player):
cellblock = world.get_region('Hyrule Dungeon Cellblock', player)
return state.visited(cellblock)
def special_big_key_found(state):
for location in state.found_locations:
if location.forced_item and location.forced_item.bigkey:
return True
return False
def valid_region_to_explore_in_regions(region, all_regions, world, player):

View File

@@ -18,6 +18,8 @@ def link_entrances(world, player):
for exitname, regionname in mandatory_connections:
connect_simple(world, exitname, regionname, player)
connect_custom(world, player)
# if we do not shuffle, set default connections
if world.shuffle[player] == 'vanilla':
for exitname, regionname in default_connections:
@@ -1780,6 +1782,15 @@ def link_inverted_entrances(world, player):
if world.get_entrance('Inverted Ganons Tower', player).connected_region.name != 'GT Lobby':
world.ganonstower_vanilla[player] = False
def connect_custom(world, player):
if hasattr(world, 'custom_entrances') and world.custom_entrances[player]:
for exit_name, region_name in world.custom_entrances[player]:
# doesn't actually change addresses
connect_simple(world, exit_name, region_name, player)
# this needs to remove custom connections from the pool
def connect_simple(world, exitname, regionname, player):
world.get_entrance(exitname, player).connect(world.get_region(regionname, player))
@@ -2863,6 +2874,7 @@ mandatory_connections = [('Links House S&Q', 'Links House'),
('Spectacle Rock Cave Drop', 'Spectacle Rock Cave (Bottom)'),
('Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave (Bottom)'),
('Death Mountain Return Ledge Drop', 'Light World'),
('Old Man Cave Dropdown', 'Old Man Cave'),
('Old Man House Front to Back', 'Old Man House Back'),
('Old Man House Back to Front', 'Old Man House'),
('Broken Bridge (West)', 'East Death Mountain (Bottom)'),
@@ -2972,6 +2984,7 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'),
('Spectacle Rock Cave Drop', 'Spectacle Rock Cave (Bottom)'),
('Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave (Bottom)'),
('Death Mountain Return Ledge Drop', 'Light World'),
('Old Man Cave Dropdown', 'Old Man Cave'),
('Old Man House Front to Back', 'Old Man House Back'),
('Old Man House Back to Front', 'Old Man House'),
('Broken Bridge (West)', 'East Death Mountain (Bottom)'),

View File

@@ -107,7 +107,8 @@ def create_inverted_regions(world, player):
create_lw_region(player, 'Master Sword Meadow', ['Master Sword Pedestal']),
create_cave_region(player, 'Lost Woods Gamble', 'a game of chance'),
create_lw_region(player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Inverted Ganons Tower', 'Hyrule Castle Ledge Courtyard Drop', 'Inverted Pyramid Hole']),
create_cave_region(player, 'Old Man Cave', 'a connector', ['Old Man'], ['Old Man Cave Exit (East)', 'Old Man Cave Exit (West)']),
create_cave_region(player, 'Old Man Cave', 'a connector', ['Old Man'], ['Old Man Cave Exit (East)']),
create_cave_region(player, 'Old Man Cave Ledge', 'a connector', None, ['Old Man Cave Exit (West)', 'Old Man Cave Dropdown']),
create_cave_region(player, 'Old Man House', 'a connector', None, ['Old Man House Exit (Bottom)', 'Old Man House Front to Back']),
create_cave_region(player, 'Old Man House Back', 'a connector', None, ['Old Man House Exit (Top)', 'Old Man House Back to Front']),
create_lw_region(player, 'Death Mountain', None, ['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', 'Death Mountain Return Cave (East)', 'Spectacle Rock Cave',

View File

@@ -1389,7 +1389,7 @@ def create_key_counters(key_layout, world, player):
if door.bigKey or door.name in special_big_key_doors:
key_layout.key_logic.bk_doors.add(door)
# open the door, if possible
if not door.bigKey or not child_state.big_key_special or child_state.visited(special_region):
if not door.bigKey or not child_state.big_key_special or child_state.visited_at_all(special_region):
open_a_door(door, child_state, flat_proposal)
expand_key_state(child_state, flat_proposal, world, player)
code = state_id(child_state, key_layout.flat_prop)

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 Utils import output_path, parse_player_names
__version__ = '0.2.0.9-u'
__version__ = '0.2.0.10u'
class EnemizerError(RuntimeError):
pass

View File

@@ -2,21 +2,22 @@
* Lobby shuffle added as Intensity level 3
* Can now be found in the spoiler
* Palette changes:
* Certain doors/transition no longer have an effect on the palette choice (dead ends mostly or just bridges)
* Sanctuary palette back to the adjacent rooms to Sanctuary (sanctuary stays the dungeon color for now)
* Sewer palette comes back for part of Hyrule Castle for areas "near" the sewer dropdown
* Known issues:
* If a dungeon is vanilla in ER and Sanc is in that dungeon and the dungeon has an entrance that needs to let link out: is broken.
* e.g. PoD, GT, TR
* Some TR lobbies that need a bomb aren't pre-opened.
* Palettes aren't perfect - may add Sanctuary and Sewer palette back. May add a way to turn off palette "fixing"
* Palettes aren't perfect
May add a way to turn off palette "fixing"
* Some ugly colors
* Invisible floors can be see in many palettes
* Animated tiles aren't loaded correctly in lobbies
* If a wallmaster grabs you and the lobby is dark, the lamp doesn't turn on
* --keydropshuffle added (coming to the GUI soon). This add 33 new locations to the game where keys are found under pots
and where enemies drop keys. This includes 32 small key location and the ball and chain guard who normally drop the HC
Big Key.
* Overall location count updated
* Setting mentioned in spoiler
* GT Big Key count / total location count needs to be updated
* Known issue:
* GT Big Key count needs to be updated
* --mixed_travel setting added
* Due to Hammerjump, Hovering in PoD Arena, and the Mire Big Key Chest bomb jump two sections of a supertile that are
otherwise unconnected logically can be reach using these glitches. To prevent the player from unintentionally
@@ -35,6 +36,16 @@ otherwise unconnected logically can be reach using these glitches. To prevent th
# Bug Fixes
* 2.0.10u
* 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
* Animated tiles are loaded correctly in lobbies
* If a wallmaster grabs you and the lobby is dark, the lamp turns on now
* Certain key rules no longer override item requirements (e.g. Somaria behind TR Hub)
* Old Man Cave is correctly one way in the graph
* Some key logic fixes
* 2.0.9-u
* /missing command in MultiClient fixed
* 2.0.8-u
* Player sprite disappears after picking up a key drop in keydropshuffle
* Sewers and Hyrule Castle compass problems

View File

@@ -100,7 +100,8 @@ def create_regions(world, player):
create_lw_region(player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Agahnims Tower', 'Hyrule Castle Ledge Courtyard Drop']),
create_dungeon_region(player, 'Sewer Drop', 'a drop\'s exit', None, ['Sewer Drop']), # This exists only to be referenced for access checks
create_cave_region(player, 'Old Man Cave', 'a connector', ['Old Man'], ['Old Man Cave Exit (East)', 'Old Man Cave Exit (West)']),
create_cave_region(player, 'Old Man Cave', 'a connector', ['Old Man'], ['Old Man Cave Exit (East)']),
create_cave_region(player, 'Old Man Cave Ledge', 'a connector', None, ['Old Man Cave Exit (West)', 'Old Man Cave Dropdown']),
create_cave_region(player, 'Old Man House', 'a connector', None, ['Old Man House Exit (Bottom)', 'Old Man House Front to Back']),
create_cave_region(player, 'Old Man House Back', 'a connector', None, ['Old Man House Exit (Top)', 'Old Man House Back to Front']),
create_lw_region(player, 'Death Mountain', None, ['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', 'Death Mountain Return Cave (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Broken Bridge (West)', 'Death Mountain Teleporter']),

17
Rom.py
View File

@@ -24,7 +24,7 @@ from EntranceShuffle import door_addresses, exit_ids
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '16fab813f3cbef58c66a47595a3858ee'
RANDOMIZERBASEHASH = 'e16cc8659527baecc02e3b49b83fa49b'
class JsonRom(object):
@@ -621,8 +621,9 @@ def patch_rom(world, rom, player, team, enemized):
# setup dr option flags based on experimental, etc.
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal
if world.experimental[player]:
if world.doorShuffle[player] == 'crossed':
dr_flags |= DROptions.Map_Info
if world.experimental[player]:
dr_flags |= DROptions.Debug
if world.doorShuffle[player] == 'crossed' and world.logic[player] != 'nologic'\
and world.mixed_travel[player] == 'prevent':
@@ -703,9 +704,17 @@ def patch_rom(world, rom, player, team, enemized):
if portal.boss_exit_idx > -1:
rom.write_byte(0x7939 + portal.boss_exit_idx, portal.current_room())
# fix skull woods exit, if not fixed during exit patching
if world.fix_skullwoods_exit[player] and world.shuffle[player] == 'vanilla':
world.force_fix[player]['sw'] |= world.fix_skullwoods_exit[player] and world.shuffle[player] == 'vanilla'
# fix exits, if not fixed during exit patching
if world.force_fix[player]['sw']:
write_int16(rom, 0x15DB5 + 2 * exit_ids['Skull Woods Final Section Exit'][1], 0x00F8)
if world.force_fix[player]['pod']:
write_int16(rom, 0x15DB5 + 2 * exit_ids['Palace of Darkness Exit'][1], 0x0640)
if world.force_fix[player]['tr']:
write_int16(rom, 0x15DB5 + 2 * exit_ids['Turtle Rock Exit (Front)'][1], 0x0134)
if world.force_fix[player]['gt']:
write_int16(rom, 0x15DB5 + 2 * exit_ids['Ganons Tower Exit'][1], 0x00A4)
write_custom_shops(rom, world, player)

View File

@@ -74,6 +74,10 @@ def add_rule(spot, rule, combine='and'):
spot.access_rule = lambda state: rule(state) and old_rule(state)
def or_rule(rule1, rule2):
return lambda state: rule1(state) or rule2(state)
def add_lamp_requirement(spot, player):
add_rule(spot, lambda state: state.has('Lamp', player, state.world.lamps_needed_for_dark_rooms))
@@ -1563,9 +1567,10 @@ def add_key_logic_rules(world, player):
for door_name, keys in d_logic.door_rules.items():
spot = world.get_entrance(door_name, player)
if not world.retro[player] or world.mode[player] != 'standard' or not retro_in_hc(spot):
add_rule(spot, create_advanced_key_rule(d_logic, player, keys))
rule = create_advanced_key_rule(d_logic, player, keys)
if keys.opposite:
add_rule(spot, create_advanced_key_rule(d_logic, player, keys.opposite), 'or')
rule = or_rule(rule, create_advanced_key_rule(d_logic, player, keys.opposite))
add_rule(spot, rule)
for location in d_logic.bk_restricted:
if not location.forced_item:
forbid_item(location, d_logic.bk_name, player)

View File

@@ -62,6 +62,12 @@ nop #5
org $01b618 ; Bank01.asm : 7963 Dungeon_LoadHeader (REP #$20 : INY : LDA [$0D], Y)
nop : jsl OverridePaletteHeader
org $02817e ; Bank02.asm : 414 (LDA $02811E, X)
jsl FixAnimatedTiles
org $028a06 ; Bank02.asm : 1941 Dungeon_ResetTorchBackgroundAndPlayer
JSL FixWallmasterLamp
org $00d377 ;Bank 00 line 3185
DecompDungAnimatedTiles:
org $00fda4 ;Bank 00 line 8882

View File

@@ -33,6 +33,22 @@ GfxFixer:
rtl
}
FixAnimatedTiles:
LDA.L DRMode : cmp #$02 : bne +
PHX
LDX $A0 : LDA.l TilesetTable, x
CMP $0AA1 : beq ++
TAX : PLA : BRA +
++
PLX
+ LDA $02802E, X ; what we wrote over
RTL
FixWallmasterLamp:
ORA $0458
STY $1C : STA $1D : RTL ; what we wrote over
CgramAuxToMain: ; ripped this from bank02 because it ended with rts
{
rep #$20

Binary file not shown.