MSU Scrolling bug

Crystaroller Stairs fixed
More Full ER support
Added DungeonGen check for hangers without enough hooks
DungeonGen doesn't consider BK door problems unless starting from origin
--This could cause some longer gen times - as the origin is hooked up last
Skull 3 Exit - attempt to fix
This commit is contained in:
aerinon
2019-11-19 16:00:55 -07:00
parent 48494a09ba
commit 6a276ca0b8
7 changed files with 65 additions and 36 deletions

View File

@@ -287,15 +287,25 @@ def within_dungeon(world, player):
enabled_entrances = [] enabled_entrances = []
dungeon_layouts = [] dungeon_layouts = []
for key, sector_list, entrance_list in dungeon_sectors: sector_queue = collections.deque(dungeon_sectors)
last_key = None
while len(sector_queue) > 0:
key, sector_list, entrance_list = sector_queue.popleft()
origin_list = list(entrance_list) origin_list = list(entrance_list)
find_enabled_origins(sector_list, enabled_entrances, origin_list) find_enabled_origins(sector_list, enabled_entrances, origin_list)
origin_list = remove_drop_origins(origin_list) origin_list = remove_drop_origins(origin_list)
ds = generate_dungeon(sector_list, origin_list, world, player) if len(origin_list) <= 0:
find_new_entrances(ds, connections, potentials, enabled_entrances) if last_key == key:
ds.name = key raise Exception('Infinte loop detected %s' % key)
layout_starts = origin_list if len(entrance_list) <= 0 else entrance_list sector_queue.append((key, sector_list, entrance_list))
dungeon_layouts.append((ds, layout_starts)) last_key = key
else:
ds = generate_dungeon(sector_list, origin_list, world, player)
find_new_entrances(ds, connections, potentials, enabled_entrances)
ds.name = key
layout_starts = origin_list if len(entrance_list) <= 0 else entrance_list
dungeon_layouts.append((ds, layout_starts))
last_key = None
combine_layouts(dungeon_layouts, entrances_map) combine_layouts(dungeon_layouts, entrances_map)
world.dungeon_layouts[player] = {} world.dungeon_layouts[player] = {}

View File

@@ -99,7 +99,7 @@ def generate_dungeon(available_sectors, entrance_region_names, world, player):
def gen_dungeon_info(available_sectors, entrance_regions, proposed_map, valid_doors, world, player): def gen_dungeon_info(available_sectors, entrance_regions, proposed_map, valid_doors, world, player):
# step 1 create dungeon: Dict<DoorName|Origin, GraphPiece> # step 1 create dungeon: Dict<DoorName|Origin, GraphPiece>
dungeon = {} dungeon = {}
original_state = extend_reachable_state_improved(entrance_regions, ExplorationState(), proposed_map, valid_doors, world, player) original_state = extend_reachable_state_improved(entrance_regions, ExplorationState(), proposed_map, valid_doors, True, world, player)
dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map) dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map)
doors_to_connect = set() doors_to_connect = set()
hanger_set = set() hanger_set = set()
@@ -110,7 +110,7 @@ def gen_dungeon_info(available_sectors, entrance_regions, proposed_map, valid_do
if not door.stonewall and door not in proposed_map.keys(): if not door.stonewall and door not in proposed_map.keys():
hanger_set.add(door) hanger_set.add(door)
parent = parent_region(door, world, player).parent_region parent = parent_region(door, world, player).parent_region
o_state = extend_reachable_state_improved([parent], ExplorationState(), proposed_map, valid_doors, world, player) o_state = extend_reachable_state_improved([parent], ExplorationState(), proposed_map, valid_doors, False, world, player)
o_state_cache[door.name] = o_state o_state_cache[door.name] = o_state
piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map) piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map)
dungeon[door.name] = piece dungeon[door.name] = piece
@@ -170,7 +170,7 @@ def check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, valid_do
def explore_blue_state(door, dungeon, o_state, proposed_map, valid_doors, world, player): def explore_blue_state(door, dungeon, o_state, proposed_map, valid_doors, world, player):
parent = parent_region(door, world, player).parent_region parent = parent_region(door, world, player).parent_region
blue_start = ExplorationState(CrystalBarrier.Blue) blue_start = ExplorationState(CrystalBarrier.Blue)
b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, valid_doors, world, player) b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, valid_doors, False, world, player)
dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map) dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map)
@@ -244,6 +244,18 @@ def check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_reg
if len(hooks[key]) > 0 and len(hangers[key]) == 0: if len(hooks[key]) > 0 and len(hangers[key]) == 0:
return False return False
# todo: stonewall - check that there's no hook-only that is without a matching hanger # todo: stonewall - check that there's no hook-only that is without a matching hanger
must_hang = defaultdict(list)
all_hooks = set()
for key in hooks.keys():
for hook in hooks[key]:
all_hooks.add(hook[0])
for key in hangers.keys():
for hanger in hangers[key]:
if hanger not in all_hooks:
must_hang[key].append(hanger)
for key in must_hang.keys():
if len(must_hang[key]) > len(hooks[key]):
return False
outstanding_doors = defaultdict(list) outstanding_doors = defaultdict(list)
for d in doors_to_connect: for d in doors_to_connect:
if d not in proposed_map.keys(): if d not in proposed_map.keys():
@@ -595,9 +607,9 @@ class ExplorationState(object):
elif not self.in_door_list(door, self.avail_doors): elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors) self.append_door_to_list(door, self.avail_doors)
def add_all_doors_check_proposed(self, region, proposed_map, valid_doors, world, player): def add_all_doors_check_proposed(self, region, proposed_map, valid_doors, isOrigin, world, player):
for door in get_doors(world, region, player): for door in get_doors(world, region, player):
if self.can_traverse_bk_check(door): if self.can_traverse_bk_check(door, isOrigin):
if door.controller is not None: if door.controller is not None:
door = door.controller door = door.controller
if door.dest is None and door not in proposed_map.keys() and door in valid_doors: if door.dest is None and door not in proposed_map.keys() and door in valid_doors:
@@ -659,12 +671,12 @@ class ExplorationState(object):
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
return True return True
def can_traverse_bk_check(self, door): def can_traverse_bk_check(self, door, isOrigin):
if door.blocked: if door.blocked:
return False return False
if door.crystal not in [CrystalBarrier.Null, CrystalBarrier.Either]: if door.crystal not in [CrystalBarrier.Null, CrystalBarrier.Either]:
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
return not door.bigKey or len(self.found_locations) > 0 return not isOrigin or not door.bigKey or len(self.found_locations) > 0
# return not door.bigKey or len([x for x in self.found_locations if '- Prize' not in x.name]) > 0 # return not door.bigKey or len([x for x in self.found_locations if '- Prize' not in x.name]) > 0
def validate(self, door, region, world): def validate(self, door, region, world):
@@ -733,11 +745,11 @@ def extend_reachable_state(search_regions, state, world, player):
return local_state return local_state
def extend_reachable_state_improved(search_regions, state, proposed_map, valid_doors, world, player): def extend_reachable_state_improved(search_regions, state, proposed_map, valid_doors, isOrigin, world, player):
local_state = state.copy() local_state = state.copy()
for region in search_regions: for region in search_regions:
local_state.visit_region(region) local_state.visit_region(region)
local_state.add_all_doors_check_proposed(region, proposed_map, valid_doors, world, player) local_state.add_all_doors_check_proposed(region, proposed_map, valid_doors, isOrigin, world, player)
while len(local_state.avail_doors) > 0: while len(local_state.avail_doors) > 0:
explorable_door = local_state.next_avail_door() explorable_door = local_state.next_avail_door()
if explorable_door.door in proposed_map: if explorable_door.door in proposed_map:
@@ -747,7 +759,7 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, valid_d
if connect_region is not None: if connect_region is not None:
if valid_region_to_explore(connect_region, world) and not local_state.visited(connect_region): if valid_region_to_explore(connect_region, world) and not local_state.visited(connect_region):
local_state.visit_region(connect_region) local_state.visit_region(connect_region)
local_state.add_all_doors_check_proposed(connect_region, proposed_map, valid_doors, world, player) local_state.add_all_doors_check_proposed(connect_region, proposed_map, valid_doors, isOrigin, world, player)
return local_state return local_state

13
Rom.py
View File

@@ -18,7 +18,7 @@ from EntranceShuffle import door_addresses, exit_ids
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'f96bdced8c89426bd4d1380db8a02220' RANDOMIZERBASEHASH = '1292e65465342d79bee038eb58270e64'
class JsonRom(object): class JsonRom(object):
@@ -543,8 +543,15 @@ def patch_rom(world, player, rom):
for paired_door in world.paired_doors[player]: for paired_door in world.paired_doors[player]:
rom.write_bytes(paired_door.address_a(world, player), paired_door.rom_data_a(world, player)) rom.write_bytes(paired_door.address_a(world, player), paired_door.rom_data_a(world, player))
rom.write_bytes(paired_door.address_b(world, player), paired_door.rom_data_b(world, player)) rom.write_bytes(paired_door.address_b(world, player), paired_door.rom_data_b(world, player))
if world.fix_skullwoods_exit: if world.fix_skullwoods_exit and world.shuffle in ['vanilla', 'simple', 'restricted', 'dungeonssimple']:
rom.write_int16(0x15DB5 + 2 * exit_ids['Skull Woods Final Section Exit'][1], 0x00F8) connect = world.get_entrance('Skull Woods Final Section', player).connected_region
exit_name = None
for ext in connect.exits:
if ext.connected_region.name == 'Skull Woods Forest (West)':
exit_name = ext.name
break
if exit_name is not None and exit_name == 'Skull Woods Final Section Exit':
rom.write_int16(0x15DB5 + 2 * exit_ids['Skull Woods Final Section Exit'][1], 0x00F8)
# todo: fix other exits if ER enabled and similar situation happens # todo: fix other exits if ER enabled and similar situation happens
write_custom_shops(rom, world, player) write_custom_shops(rom, world, player)

View File

@@ -2,7 +2,7 @@
!sub = "sec : sbc" !sub = "sec : sbc"
; Free RAM notes ; Free RAM notes
; Normal doors use $0127 for scrolling indicator ; Normal doors use $FE for scrolling indicator
; Normal doors use $AB to store the trap door indicator ; Normal doors use $AB to store the trap door indicator
; Spiral doors use $045e to store stair type ; Spiral doors use $045e to store stair type
; Gfx uses $b1 to for sub-sub-sub-module thing ; Gfx uses $b1 to for sub-sub-sub-module thing

View File

@@ -21,7 +21,7 @@ dw $0207
org $279E00 org $279E00
SpiralOffset: SpiralOffset:
; 0 1 2 3 4 5 6 7 8 9 a b c d e f --Offset Ruler ; 0 1 2 3 4 5 6 7 8 9 a b c d e f --Offset Ruler
db $00,$01,$02,$00,$00,$03,$00,$04,$00,$05,$07,$00,$08,$00,$0b,$00 db $00,$01,$02,$00,$03,$00,$00,$04,$00,$05,$07,$00,$08,$00,$0b,$00
db $00,$0c,$00,$00,$00,$0d,$0e,$0f,$00,$00,$11,$00,$13,$14,$15,$00 db $00,$0c,$00,$00,$00,$0d,$0e,$0f,$00,$00,$11,$00,$13,$14,$15,$00
db $00,$00,$00,$00,$00,$00,$16,$19,$1b,$00,$00,$00,$00,$00,$00,$00 db $00,$00,$00,$00,$00,$00,$16,$19,$1b,$00,$00,$00,$00,$00,$00,$00
db $00,$1c,$00,$00,$1f,$00,$00,$00,$20,$00,$21,$00,$00,$00,$00,$22 db $00,$1c,$00,$00,$1f,$00,$00,$00,$20,$00,$21,$00,$00,$00,$00,$22

View File

@@ -152,7 +152,7 @@ ShiftLowCoord:
{ {
lda $01 : and.b #$03 ; high byte index lda $01 : and.b #$03 ; high byte index
jsr CalcOpposingShift jsr CalcOpposingShift
lda $0127 : and.b #$f0 : cmp.b #$20 : bne .lowDone lda $fe : and.b #$f0 : cmp.b #$20 : bne .lowDone
lda OppCoordIndex,y : tax lda OppCoordIndex,y : tax
lda #$80 : !add $20,x : sta $20,x lda #$80 : !add $20,x : sta $20,x
.lowDone .lowDone
@@ -166,7 +166,7 @@ ShiftLowCoord:
; q - quadrant, if set, then quadrant needs to be modified ; q - quadrant, if set, then quadrant needs to be modified
CalcOpposingShift: CalcOpposingShift:
{ {
stz $0127 ; set up (can you zero out 127 alone?) stz $fe ; set up (can you zero out 127 alone?)
cmp.b $04 : beq .noOffset ; (equal, no shifts to do) cmp.b $04 : beq .noOffset ; (equal, no shifts to do)
phy : tay ; reserve these phy : tay ; reserve these
lda $04 : tax : tya : !sub $04 : sta $04 : cmp.b #$00 : bpl .shiftPos lda $04 : tax : tya : !sub $04 : sta $04 : cmp.b #$00 : bpl .shiftPos
@@ -174,8 +174,8 @@ CalcOpposingShift:
cpx.b #$01 : beq .skipNegQuad cpx.b #$01 : beq .skipNegQuad
ora #$08 ora #$08
.skipNegQuad .skipNegQuad
sta $0127 : lda $04 : cmp.b #$FE : beq .done ;already set $0127 sta $fe : lda $04 : cmp.b #$FE : beq .done ;already set $0127
lda $0127 : eor #$60 lda $fe : eor #$60
bra .setDone bra .setDone
.shiftPos .shiftPos
@@ -183,10 +183,10 @@ CalcOpposingShift:
cpy.b #$01 : beq .skipPosQuad cpy.b #$01 : beq .skipPosQuad
ora #$08 ora #$08
.skipPosQuad .skipPosQuad
sta $0127 : lda $04 : cmp.b #$02 : bcs .done ;already set $0127 sta $fe : lda $04 : cmp.b #$02 : bcs .done ;already set $0127
lda $0127 : eor #$60 lda $fe : eor #$60
.setDone sta $0127 .setDone sta $fe
.done ply .done ply
.noOffset rts .noOffset rts
} }
@@ -194,9 +194,9 @@ CalcOpposingShift:
ShiftQuad: ShiftQuad:
{ {
lda $0127 : and #$08 : cmp.b #$00 : beq .quadDone lda $fe : and #$08 : cmp.b #$00 : beq .quadDone
lda ShiftQuadIndex,y : tax ; X should be set to either 1 (vertical) or 2 (horizontal) (for a9,aa quadrant) lda ShiftQuadIndex,y : tax ; X should be set to either 1 (vertical) or 2 (horizontal) (for a9,aa quadrant)
lda $0127 : and #$01 : cmp.b #$00 : beq .decQuad lda $fe : and #$01 : cmp.b #$00 : beq .decQuad
inc $02 inc $02
txa : sta $a8, x ; alter a9/aa txa : sta $a8, x ; alter a9/aa
bra .quadDone bra .quadDone
@@ -224,8 +224,8 @@ ShiftCameraBounds:
{ {
lda CamBoundIndex,y : tax ; should be 0 for horz travel (vert bounds) or 4 for vert travel (horz bounds) lda CamBoundIndex,y : tax ; should be 0 for horz travel (vert bounds) or 4 for vert travel (horz bounds)
rep #$30 rep #$30
lda $0127 : and #$00f0 : asl #2 : sta $06 lda $fe : and #$00f0 : asl #2 : sta $06
lda $0127 : and #$0001 : cmp #$0000 : beq .subIt lda $fe : and #$0001 : cmp #$0000 : beq .subIt
lda $0618, x : !add $06 : sta $0618, x lda $0618, x : !add $06 : sta $0618, x
lda $061A, x : !add $06 : sta $061A, x lda $061A, x : !add $06 : sta $061A, x
sep #$30 sep #$30
@@ -239,18 +239,18 @@ ShiftCameraBounds:
AdjustTransition: AdjustTransition:
{ {
lda $0127 : and #$00F0 : lsr lda $fe : and #$00f0 : lsr
sep #$20 : cmp $0126 : bcc .reset sep #$20 : cmp $0126 : bcc .reset
rep #$20 rep #$20
phy : ldy #$06 ; operating on vertical registers during horizontal trans phy : ldy #$06 ; operating on vertical registers during horizontal trans
cpx.b #$02 : bcs .horizontalScrolling cpx.b #$02 : bcs .horizontalScrolling
ldy #$00 ; operate on horizontal regs during vert trans ldy #$00 ; operate on horizontal regs during vert trans
.horizontalScrolling .horizontalScrolling
lda $0127 : and #$0001 : asl : tax lda $fe : and #$0001 : asl : tax
lda.l OffsetTable,x : adc $00E2,y : and.w #$FFFE : sta $00E2,y : sta $00E0,y lda.l OffsetTable,x : adc $00E2,y : and.w #$FFFE : sta $00E2,y : sta $00E0,y
ply : bra .done ply : bra .done
.reset ; clear the 0127 variable so to not disturb intra-tile doors .reset ; clear the 0127 variable so to not disturb intra-tile doors
stz $0127 stz $fe
.done .done
rep #$20 : lda $00 : and #$01fc rep #$20 : lda $00 : and #$01fc
rtl rtl

File diff suppressed because one or more lines are too long