Merge branch 'DRFix' of https://github.com/Catobat/ALttPDoorRandomizer into Catobat-DRFix

This commit is contained in:
aerinon
2021-11-02 15:19:07 -06:00
13 changed files with 156 additions and 38 deletions

View File

@@ -97,6 +97,7 @@ class World(object):
set_player_attr('player_names', []) set_player_attr('player_names', [])
set_player_attr('remote_items', False) set_player_attr('remote_items', False)
set_player_attr('required_medallions', ['Ether', 'Quake']) set_player_attr('required_medallions', ['Ether', 'Quake'])
set_player_attr('bottle_refills', ['Bottle (Green Potion)', 'Bottle (Green Potion)'])
set_player_attr('swamp_patch_required', False) set_player_attr('swamp_patch_required', False)
set_player_attr('powder_patch_required', False) set_player_attr('powder_patch_required', False)
set_player_attr('ganon_at_pyramid', True) set_player_attr('ganon_at_pyramid', True)
@@ -2272,6 +2273,7 @@ class Spoiler(object):
self.doorTypes = {} self.doorTypes = {}
self.lobbies = {} self.lobbies = {}
self.medallions = {} self.medallions = {}
self.bottles = {}
self.playthrough = {} self.playthrough = {}
self.unreachables = [] self.unreachables = []
self.startinventory = [] self.startinventory = []
@@ -2315,6 +2317,15 @@ class Spoiler(object):
self.medallions[f'Misery Mire ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][0] self.medallions[f'Misery Mire ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][0]
self.medallions[f'Turtle Rock ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][1] self.medallions[f'Turtle Rock ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][1]
self.bottles = OrderedDict()
if self.world.players == 1:
self.bottles['Waterfall Bottle'] = self.world.bottle_refills[1][0]
self.bottles['Pyramid Bottle'] = self.world.bottle_refills[1][1]
else:
for player in range(1, self.world.players + 1):
self.bottles[f'Waterfall Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][0]
self.bottles[f'Pyramid Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][1]
self.startinventory = list(map(str, self.world.precollected_items)) self.startinventory = list(map(str, self.world.precollected_items))
self.locations = OrderedDict() self.locations = OrderedDict()
@@ -2435,6 +2446,7 @@ class Spoiler(object):
out.update(self.locations) out.update(self.locations)
out['Starting Inventory'] = self.startinventory out['Starting Inventory'] = self.startinventory
out['Special'] = self.medallions out['Special'] = self.medallions
out['Bottles'] = self.bottles
if self.hashes: if self.hashes:
out['Hashes'] = {f"{self.world.player_names[player][team]} (Team {team+1})": hash for (player, team), hash in self.hashes.items()} out['Hashes'] = {f"{self.world.player_names[player][team]} (Team {team+1})": hash for (player, team), hash in self.hashes.items()}
if self.shops: if self.shops:
@@ -2521,6 +2533,9 @@ class Spoiler(object):
outfile.write('\n\nMedallions:\n') outfile.write('\n\nMedallions:\n')
for dungeon, medallion in self.medallions.items(): for dungeon, medallion in self.medallions.items():
outfile.write(f'\n{dungeon}: {medallion} Medallion') outfile.write(f'\n{dungeon}: {medallion} Medallion')
outfile.write('\n\nBottle Refills:\n')
for fairy, bottle in self.bottles.items():
outfile.write(f'\n{fairy}: {bottle}')
if self.startinventory: if self.startinventory:
outfile.write('\n\nStarting Inventory:\n\n') outfile.write('\n\nStarting Inventory:\n\n')
outfile.write('\n'.join(self.startinventory)) outfile.write('\n'.join(self.startinventory))

View File

@@ -370,6 +370,15 @@ def generate_itempool(world, player):
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
world.required_medallions[player] = (mm_medallion, tr_medallion) world.required_medallions[player] = (mm_medallion, tr_medallion)
# shuffle bottle refills
if world.difficulty[player] in ['hard', 'expert']:
waterfall_bottle = hardbottles[random.randint(0, 5)]
pyramid_bottle = hardbottles[random.randint(0, 5)]
else:
waterfall_bottle = normalbottles[random.randint(0, 6)]
pyramid_bottle = normalbottles[random.randint(0, 6)]
world.bottle_refills[player] = (waterfall_bottle, pyramid_bottle)
set_up_shops(world, player) set_up_shops(world, player)
if world.retro[player]: if world.retro[player]:

View File

@@ -364,6 +364,7 @@ def copy_world(world):
ret.player_names = copy.deepcopy(world.player_names) ret.player_names = copy.deepcopy(world.player_names)
ret.remote_items = world.remote_items.copy() ret.remote_items = world.remote_items.copy()
ret.required_medallions = world.required_medallions.copy() ret.required_medallions = world.required_medallions.copy()
ret.bottle_refills = world.bottle_refills.copy()
ret.swamp_patch_required = world.swamp_patch_required.copy() ret.swamp_patch_required = world.swamp_patch_required.copy()
ret.ganon_at_pyramid = world.ganon_at_pyramid.copy() ret.ganon_at_pyramid = world.ganon_at_pyramid.copy()
ret.powder_patch_required = world.powder_patch_required.copy() ret.powder_patch_required = world.powder_patch_required.copy()

View File

@@ -99,7 +99,8 @@ def get_weights(path):
raise Exception(f'Failed to read weights file: {e}') raise Exception(f'Failed to read weights file: {e}')
def roll_settings(weights): def roll_settings(weights):
def get_choice(option, root=weights): def get_choice(option, root=None):
root = weights if root is None else root
if option not in root: if option not in root:
return None return None
if type(root[option]) is not dict: if type(root[option]) is not dict:
@@ -114,13 +115,24 @@ def roll_settings(weights):
return default return default
return choice return choice
while True:
subweights = weights.get('subweights', {})
if len(subweights) == 0:
break
chances = ({k: int(v['chance']) for (k, v) in subweights.items()})
subweight_name = random.choices(list(chances.keys()), weights=list(chances.values()))[0]
subweights = weights.get('subweights', {}).get(subweight_name, {}).get('weights', {})
subweights['subweights'] = subweights.get('subweights', {})
weights = {**weights, **subweights}
ret = argparse.Namespace() ret = argparse.Namespace()
glitches_required = get_choice('glitches_required') glitches_required = get_choice('glitches_required')
if glitches_required not in ['none', 'no_logic']: if glitches_required is not None:
print("Only NMG and No Logic supported") if glitches_required not in ['none', 'no_logic']:
glitches_required = 'none' print("Only NMG and No Logic supported")
ret.logic = {'none': 'noglitches', 'no_logic': 'nologic'}[glitches_required] glitches_required = 'none'
ret.logic = {'none': 'noglitches', 'no_logic': 'nologic'}[glitches_required]
item_placement = get_choice('item_placement') item_placement = get_choice('item_placement')
# not supported in ER # not supported in ER
@@ -152,26 +164,27 @@ def roll_settings(weights):
ret.standardize_palettes = get_choice('standardize_palettes') if 'standardize_palettes' in weights else 'standardize' ret.standardize_palettes = get_choice('standardize_palettes') if 'standardize_palettes' in weights else 'standardize'
goal = get_choice('goals') goal = get_choice('goals')
ret.goal = {'ganon': 'ganon', if goal is not None:
'fast_ganon': 'crystals', ret.goal = {'ganon': 'ganon',
'dungeons': 'dungeons', 'fast_ganon': 'crystals',
'pedestal': 'pedestal', 'dungeons': 'dungeons',
'triforce-hunt': 'triforcehunt' 'pedestal': 'pedestal',
}[goal] 'triforce-hunt': 'triforcehunt'
}[goal]
ret.openpyramid = goal == 'fast_ganon' if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False ret.openpyramid = goal == 'fast_ganon' if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False
ret.crystals_gt = get_choice('tower_open') ret.crystals_gt = get_choice('tower_open')
ret.crystals_ganon = get_choice('ganon_open') ret.crystals_ganon = get_choice('ganon_open')
if ret.goal == 'triforcehunt': goal_min = get_choice_default('triforce_goal_min', default=20)
goal_min = get_choice_default('triforce_goal_min', default=20) goal_max = get_choice_default('triforce_goal_max', default=20)
goal_max = get_choice_default('triforce_goal_max', default=20) pool_min = get_choice_default('triforce_pool_min', default=30)
pool_min = get_choice_default('triforce_pool_min', default=30) pool_max = get_choice_default('triforce_pool_max', default=30)
pool_max = get_choice_default('triforce_pool_max', default=30) ret.triforce_goal = random.randint(int(goal_min), int(goal_max))
ret.triforce_goal = random.randint(int(goal_min), int(goal_max)) min_diff = get_choice_default('triforce_min_difference', default=10)
min_diff = get_choice_default('triforce_min_difference', default=10) ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max))
ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max))
ret.mode = get_choice('world_state') ret.mode = get_choice('world_state')
if ret.mode == 'retro': if ret.mode == 'retro':
ret.mode = 'open' ret.mode = 'open'
@@ -182,11 +195,13 @@ def roll_settings(weights):
ret.hints = get_choice('hints') == 'on' ret.hints = get_choice('hints') == 'on'
ret.swords = {'randomized': 'random', swords = get_choice('weapons')
'assured': 'assured', if swords is not None:
'vanilla': 'vanilla', ret.swords = {'randomized': 'random',
'swordless': 'swordless' 'assured': 'assured',
}[get_choice('weapons')] 'vanilla': 'vanilla',
'swordless': 'swordless'
}[swords]
ret.difficulty = get_choice('item_pool') ret.difficulty = get_choice('item_pool')

10
Rom.py
View File

@@ -32,7 +32,7 @@ from source.classes.SFX import randomize_sfx
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '11daec4f3e1afc96cd044585dfba9df8' RANDOMIZERBASEHASH = 'bcce69ecfff6c169371afc84193c0402'
class JsonRom(object): class JsonRom(object):
@@ -1066,12 +1066,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
]) ])
# set Fountain bottle exchange items # set Fountain bottle exchange items
if world.difficulty[player] in ['hard', 'expert']: rom.write_byte(0x348FF, ItemFactory(world.bottle_refills[player][0], player).code)
rom.write_byte(0x348FF, [0x16, 0x2B, 0x2C, 0x2D, 0x3C, 0x48][random.randint(0, 5)]) rom.write_byte(0x3493B, ItemFactory(world.bottle_refills[player][1], player).code)
rom.write_byte(0x3493B, [0x16, 0x2B, 0x2C, 0x2D, 0x3C, 0x48][random.randint(0, 5)])
else:
rom.write_byte(0x348FF, [0x16, 0x2B, 0x2C, 0x2D, 0x3C, 0x3D, 0x48][random.randint(0, 6)])
rom.write_byte(0x3493B, [0x16, 0x2B, 0x2C, 0x2D, 0x3C, 0x3D, 0x48][random.randint(0, 6)])
#enable Fat Fairy Chests #enable Fat Fairy Chests
rom.write_bytes(0x1FC16, [0xB1, 0xC6, 0xF9, 0xC9, 0xC6, 0xF9]) rom.write_bytes(0x1FC16, [0xB1, 0xC6, 0xF9, 0xC9, 0xC6, 0xF9])

View File

@@ -9,6 +9,7 @@
; Normal doors use $FE to store the trap door indicator ; Normal doors use $FE to store the trap door indicator
; Normal doors use $045e to store Y coordinate when transitioning to in-room stairs ; Normal doors use $045e to store Y coordinate when transitioning to in-room stairs
; Normal doors use $045f to determine the order in which supertile quadrants are drawn ; Normal doors use $045f to determine the order in which supertile quadrants are drawn
; Straight stairs use $046d to store X coordinate on animation start
; 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

@@ -681,6 +681,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
DungeonTilesets:
db $04,$04,$05,$12,$04,$08,$07,$0C,$09,$0B,$05,$0A,$0D,$0E,$06,$06
; ;
;org $27ff00 ;org $27ff00

View File

@@ -76,6 +76,9 @@ nop : jsl OverridePaletteHeader
org $02817e ; Bank02.asm : 414 (LDA $02811E, X) org $02817e ; Bank02.asm : 414 (LDA $02811E, X)
jsl FixAnimatedTiles jsl FixAnimatedTiles
org $0aef43 ; UnderworldMap_RecoverGFX
jsl FixCloseDungeonMap
org $028a06 ; Bank02.asm : 1941 Dungeon_ResetTorchBackgroundAndPlayer org $028a06 ; Bank02.asm : 1941 Dungeon_ResetTorchBackgroundAndPlayer
JSL FixWallmasterLamp JSL FixWallmasterLamp

View File

@@ -45,6 +45,16 @@ FixAnimatedTiles:
+ LDA $02802E, X ; what we wrote over + LDA $02802E, X ; what we wrote over
RTL RTL
FixCloseDungeonMap:
LDA.l DRMode : CMP #$02 : BNE .vanilla
LDA $040C : BMI .vanilla
LSR : TAX
LDA.l DungeonTilesets,x
RTL
.vanilla
LDA $7EC20E
RTL
FixWallmasterLamp: FixWallmasterLamp:
ORA $0458 ORA $0458
STY $1C : STA $1D : RTL ; what we wrote over STY $1C : STA $1D : RTL ; what we wrote over

View File

@@ -150,15 +150,14 @@ LoadRoomVert:
.notEdge .notEdge
lda $01 : and #$03 : cmp #$03 : bne .normal lda $01 : and #$03 : cmp #$03 : bne .normal
jsr ScrollToInroomStairs jsr ScrollToInroomStairs
stz $046d
bra .end bra .end
.normal .normal
ldy #$01 : jsr ShiftVariablesMainDir ldy #$01 : jsr ShiftVariablesMainDir
jsr PrepScrollToNormal jsr PrepScrollToNormal
.scroll .scroll
lda $01 : and #$40 : pha lda $01 : and #$40 : sta $046d
jsr ScrollX jsr ScrollX
pla : beq .end
ldy #$00 : jsr ApplyScroll
.end .end
plb ; restore db register plb ; restore db register
rts rts
@@ -291,6 +290,11 @@ StraightStairsAdj:
stx $0464 : sty $012e ; what we wrote over stx $0464 : sty $012e ; what we wrote over
lda.l DRMode : beq + lda.l DRMode : beq +
lda $045e : bne .toInroom lda $045e : bne .toInroom
lda $046d : beq .noScroll
sta $22
ldy #$00 : jsr ApplyScroll
stz $046d
.noScroll
jsr GetTileAttribute : tax jsr GetTileAttribute : tax
lda $11 : cmp #$12 : beq .goingNorth lda $11 : cmp #$12 : beq .goingNorth
lda $a2 : cmp #$51 : bne ++ lda $a2 : cmp #$51 : bne ++
@@ -338,9 +342,10 @@ db $d0, $f6, $10, $1a, $f0, $00
StraightStairsFix: StraightStairsFix:
{ {
pha
lda.l DRMode : bne + lda.l DRMode : bne +
!add $20 : sta $20 ;what we wrote over pla : !add $20 : sta $20 : rtl ;what we wrote over
+ rtl + pla : rtl
} }
StraightStairLayerFix: StraightStairLayerFix:

View File

@@ -168,7 +168,11 @@ ScrollX: ;change the X offset variables
pla : sta $00 pla : sta $00
sep #$30 sep #$30
lda $04 : sta $22 lda $04 : ldx $046d : bne .straight
sta $22 : bra +
.straight
sta $046d ; set X position later
+
lda $00 : sta $23 : sta $0609 : sta $060d lda $00 : sta $23 : sta $0609 : sta $060d
lda $01 : sta $a9 lda $01 : sta $a9
lda $0e : asl : ora $ac : sta $ac lda $0e : asl : ora $ac : sta $ac

Binary file not shown.

View File

@@ -0,0 +1,57 @@
description: Example for subweights
glitches_required: none
world_state: open
goals: ganon
weapons: randomized
entrance_shuffle: none
intensity: 3
subweights:
vanilla:
chance: 25
weights:
door_shuffle: vanilla
keydropshuffle:
on: 40
off: 60
basic:
chance: 25
weights:
door_shuffle: basic
keydropshuffle:
on: 70
off: 30
crossed:
chance: 25
weights:
door_shuffle: crossed
keydropshuffle:
on: 90
off: 10
chaos:
chance: 25
weights:
door_shuffle: crossed
entrance_shuffle:
none: 30
crossed: 70
keydropshuffle:
on: 90
off: 10
shopsanity:
on: 50
off: 50
bombbag:
on: 25
off: 75
subweights:
normal:
chance: 40
weights: {}
swordless:
chance: 20
weights:
weapons: swordless
keysanity:
chance: 40
weights:
dungeon_items: full