Added the --shufflelinks option

Moved bunny spawn to experimental
Baserom bugfixes
This commit is contained in:
aerinon
2021-05-24 15:14:23 -06:00
parent e77ae23359
commit fff7cd691e
18 changed files with 174 additions and 45 deletions

3
CLI.py
View File

@@ -98,7 +98,7 @@ def parse_cli(argv, no_defaults=False):
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
'triforce_min_difference', 'triforce_goal', 'triforce_pool',
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks',
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
@@ -143,6 +143,7 @@ def parse_settings():
"openpyramid": False,
"shuffleganon": True,
"shuffle": "vanilla",
"shufflelinks": False,
"shufflepots": False,
"shuffleenemies": "none",

View File

@@ -2021,6 +2021,7 @@ class DROptions(Flag):
Open_PoD_Wall = 0x40 # If on, pre opens the PoD wall, no bow required
Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required
Hide_Total = 0x100
DarkWorld_Spawns = 0x200
# DATA GOES DOWN HERE

View File

@@ -5,7 +5,6 @@ from collections import defaultdict
def link_entrances(world, player):
connect_two_way(world, 'Links House', 'Links House Exit', player) # unshuffled. For now
connect_exit(world, 'Chris Houlihan Room Exit', 'Links House', player) # should always match link's house, except for plandos
Dungeon_Exits = Dungeon_Exits_Base.copy()
@@ -80,7 +79,7 @@ def link_entrances(world, player):
single_doors = list(Single_Cave_Doors)
bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors)
blacksmith_doors = list(Blacksmith_Single_Cave_Doors)
blacksmith_doors = list(Blacksmith_Single_Cave_Doors) + ['Links House']
door_targets = list(Single_Cave_Targets)
# we shuffle all 2 entrance caves as pairs as a start
@@ -103,6 +102,18 @@ def link_entrances(world, player):
connect_two_way(world, entrance1, exit1, player)
connect_two_way(world, entrance2, exit2, player)
# place links house
if world.mode[player] == 'standard' or not world.shufflelinks[player]:
links_house = 'Links House'
else:
links_house_doors = [i for i in LW_Single_Cave_Doors if i not in Isolated_LH_Doors_Open]
links_house = random.choice(links_house_doors)
connect_two_way(world, links_house, 'Links House Exit', player)
if links_house in bomb_shop_doors:
bomb_shop_doors.remove(links_house)
if links_house in blacksmith_doors:
blacksmith_doors.remove(links_house)
# at this point only Light World death mountain entrances remain
# place old man, has limited options
remaining_entrances = ['Old Man Cave (West)', 'Old Man House (Bottom)', 'Death Mountain Return Cave (West)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'Paradox Cave (Top)',
@@ -155,6 +166,16 @@ def link_entrances(world, player):
blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
door_targets = list(Single_Cave_Targets)
# place links house
if world.mode[player] == 'standard' or not world.shufflelinks[player]:
links_house = 'Links House'
else:
links_house_doors = [i for i in lw_entrances if i not in Isolated_LH_Doors_Open]
links_house = random.choice(links_house_doors)
connect_two_way(world, links_house, 'Links House Exit', player)
if links_house in lw_entrances:
lw_entrances.remove(links_house)
# tavern back door cannot be shuffled yet
connect_doors(world, ['Tavern North'], ['Tavern'], player)
@@ -300,6 +321,17 @@ def link_entrances(world, player):
dw_entrances.append('Ganons Tower')
caves.append('Ganons Tower Exit')
# place links house
if world.mode[player] == 'standard' or not world.shufflelinks[player]:
links_house = 'Links House'
else:
links_house_doors = [i for i in lw_entrances + lw_must_exits if i not in Isolated_LH_Doors_Open]
links_house = random.choice(links_house_doors)
connect_two_way(world, links_house, 'Links House Exit', player)
if links_house in lw_entrances:
lw_entrances.remove(links_house)
if links_house in lw_must_exits:
lw_must_exits.remove(links_house)
# we randomize which world requirements we fulfill first so we get better dungeon distribution
#we also places the Old Man House at this time to make sure he can be connected to the desert one way
@@ -406,6 +438,22 @@ def link_entrances(world, player):
entrances.append('Ganons Tower')
caves.append('Ganons Tower Exit')
# place links house
if world.mode[player] == 'standard' or not world.shufflelinks[player]:
links_house = 'Links House'
else:
links_house_doors = [i for i in entrances + must_exits if i not in Isolated_LH_Doors_Open]
if world.doorShuffle[player] == 'crossed' and world.intensity[player] >= 3:
exclusions = DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors\
+ DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + ['Ganons Tower']
links_house_doors = [i for i in links_house_doors if i not in exclusions]
links_house = random.choice(list(links_house_doors))
connect_two_way(world, links_house, 'Links House Exit', player)
if links_house in entrances:
entrances.remove(links_house)
elif links_house in must_exits:
must_exits.remove(links_house)
#place must-exit caves
connect_mandatory_exits(world, entrances, caves, must_exits, player)
@@ -852,6 +900,23 @@ def link_entrances(world, player):
entrances.append('Hyrule Castle Entrance (South)')
caves.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'))
# place links house
if world.mode[player] == 'standard' or not world.shufflelinks[player]:
links_house = 'Links house'
else:
links_house_doors = [i for i in entrances + entrances_must_exits if i not in Isolated_LH_Doors_Open]
if world.doorShuffle[player] == 'crossed' and world.intensity[player] >= 3:
exclusions = DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors \
+ DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + ['Ganons Tower']
links_house_doors = [i for i in links_house_doors if i not in exclusions]
links_house = random.choice(links_house_doors)
connect_two_way(world, links_house, 'Links House Exit', player)
if links_house in entrances:
entrances.remove(links_house)
elif links_house in entrances_must_exits:
entrances_must_exits.remove(links_house)
doors.remove(links_house)
# now let's deal with mandatory reachable stuff
def extract_reachable_exit(cavelist):
random.shuffle(cavelist)
@@ -1196,8 +1261,11 @@ def link_inverted_entrances(world, player):
connect_two_way(world, entrance2, exit2, player)
# place links house
links_house_doors = [i for i in bomb_shop_doors + blacksmith_doors if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors]
links_house = random.choice(list(links_house_doors))
if not world.shufflelinks[player]:
links_house = 'Inverted Links House'
else:
links_house_doors = [i for i in DW_Single_Cave_Doors if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors]
links_house = random.choice(links_house_doors)
connect_two_way(world, links_house, 'Inverted Links House Exit', player)
if links_house in bomb_shop_doors:
bomb_shop_doors.remove(links_house)
@@ -1273,15 +1341,14 @@ def link_inverted_entrances(world, player):
door_targets = list(Inverted_Single_Cave_Targets)
# place links house
links_house_doors = [i for i in lw_entrances + dw_entrances + lw_must_exits if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors]
links_house = random.choice(list(links_house_doors))
if not world.shufflelinks[player]:
links_house = 'Inverted Links House'
else:
links_house_doors = [i for i in dw_entrances if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors]
links_house = random.choice(links_house_doors)
connect_two_way(world, links_house, 'Inverted Links House Exit', player)
if links_house in lw_entrances:
lw_entrances.remove(links_house)
elif links_house in dw_entrances:
if links_house in dw_entrances:
dw_entrances.remove(links_house)
elif links_house in lw_must_exits:
lw_must_exits.remove(links_house)
# place dark sanc
sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in dw_entrances]
@@ -1403,15 +1470,14 @@ def link_inverted_entrances(world, player):
caves.remove('Inverted Agahnims Tower Exit')
# place links house
links_house_doors = [i for i in lw_entrances + dw_entrances + lw_must_exits if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors]
links_house = random.choice(list(links_house_doors))
if not world.shufflelinks[player]:
links_house = 'Inverted Links House'
else:
links_house_doors = [i for i in dw_entrances if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors]
links_house = random.choice(links_house_doors)
connect_two_way(world, links_house, 'Inverted Links House Exit', player)
if links_house in lw_entrances:
lw_entrances.remove(links_house)
if links_house in dw_entrances:
dw_entrances.remove(links_house)
if links_house in lw_must_exits:
lw_must_exits.remove(links_house)
# place dark sanc
sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in dw_entrances]
@@ -1525,7 +1591,7 @@ def link_inverted_entrances(world, player):
hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', 'Inverted Ganons Tower']
# shuffle aga door. if it's on hc ledge, then one other hc ledge door has to be must_exit
aga_door = random.choice(list(entrances))
aga_door = random.choice(entrances)
if aga_door in hc_ledge_entrances:
hc_ledge_entrances.remove(aga_door)
@@ -1539,10 +1605,12 @@ def link_inverted_entrances(world, player):
connect_two_way(world, aga_door, 'Inverted Agahnims Tower Exit', player)
caves.remove('Inverted Agahnims Tower Exit')
# place links house
if not world.shufflelinks[player]:
links_house = 'Inverted Links House'
else:
links_house_doors = [i for i in entrances + must_exits if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors]
links_house = random.choice(list(links_house_doors))
links_house = random.choice(links_house_doors)
connect_two_way(world, links_house, 'Inverted Links House Exit', player)
if links_house in entrances:
entrances.remove(links_house)
@@ -1674,8 +1742,11 @@ def link_inverted_entrances(world, player):
caves.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'))
# place links house and dark sanc
if not world.shufflelinks[player]:
links_house = 'Inverted Links House'
else:
links_house_doors = [i for i in entrances + entrances_must_exits if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors]
links_house = random.choice(list(links_house_doors))
links_house = random.choice(links_house_doors)
connect_two_way(world, links_house, 'Inverted Links House Exit', player)
if links_house in entrances:
entrances.remove(links_house)
@@ -2436,7 +2507,19 @@ LW_Single_Cave_Doors = ['Blinds Hideout',
'Kings Grave',
'Bonk Fairy (Light)',
'Hookshot Fairy',
'Mimic Cave']
'Mimic Cave',
'Links House']
Isolated_LH_Doors_Open = ['Mimic Cave',
'Kings Grave',
'Waterfall of Wishing',
'Desert Palace Entrance (South)',
'Desert Palace Entrance (North)',
'Capacity Upgrade',
'Ice Palace',
'Skull Woods Final Section',
'Dark World Hammer Peg Cave',
'Turtle Rock Isolated Ledge Entrance']
DW_Single_Cave_Doors = ['Bonk Fairy (Dark)',
'Dark Sanctuary Hint',
@@ -2724,7 +2807,7 @@ Inverted_Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)',
Inverted_Blacksmith_Multi_Cave_Doors = Blacksmith_Multi_Cave_Doors # same as non-inverted
Inverted_LW_Single_Cave_Doors = LW_Single_Cave_Doors + ['Inverted Big Bomb Shop']
Inverted_LW_Single_Cave_Doors = [x for x in LW_Single_Cave_Doors if x != 'Links House'] + ['Inverted Big Bomb Shop']
Inverted_DW_Single_Cave_Doors = ['Bonk Fairy (Dark)',
'Inverted Dark Sanctuary',
@@ -3215,7 +3298,9 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'),
('Bomb Hut Outer Bushes', 'Bomb Hut Area'),
('Bomb Hut Mirror Spot', 'West Dark World')]
# non-shuffled entrance links
default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'),
default_connections = [('Links House', 'Links House'),
('Links House Exit', 'Light World'),
('Waterfall of Wishing', 'Waterfall of Wishing'),
("Blinds Hideout", "Blinds Hideout"),
('Dam', 'Dam'),
('Lumberjack House', 'Lumberjack House'),

View File

@@ -27,7 +27,7 @@ from Fill import sell_potions, sell_keys, balance_multiworld_progression, balanc
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
from Utils import output_path, parse_player_names
__version__ = '0.4.0.3-u'
__version__ = '0.4.0.4-u'
class EnemizerError(RuntimeError):
@@ -89,6 +89,7 @@ def main(args, seed=None, fish=None):
world.standardize_palettes = args.standardize_palettes.copy()
world.treasure_hunt_count = args.triforce_goal.copy()
world.treasure_hunt_total = args.triforce_pool.copy()
world.shufflelinks = args.shufflelinks.copy()
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}

View File

@@ -142,6 +142,7 @@ def roll_settings(weights):
if ret.dungeon_counters == 'default':
ret.dungeon_counters = 'pickup' if ret.door_shuffle != 'vanilla' or ret.compassshuffle == 'on' else 'off'
ret.shufflelinks = get_choice('shufflelinks') == 'on'
ret.shopsanity = get_choice('shopsanity') == 'on'
ret.keydropshuffle = get_choice('keydropshuffle') == 'on'
ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent'

View File

@@ -1,11 +1,19 @@
# New Features
## Shuffle Links House
Links house can now be shuffled in different ER settings. It will be limited to the Light World (or Dark World in inverted) if Crossed or Insanity shuffle is not one. It it also limited if door shuffle settings allow the Sanctuary to be in the dark world. (This is prevent having no Light World spawn points in Open modes) This setting is ignored by standard mode. THe CLI parameter is --shufflelinks
## OWG Glitch Logic
Thanks to qadan, cheuer, & compiling
# Bug Fixes and Notes.
* 0.4.0.4
* Added --shufflelinks option
* Moved spawning as a bunny indoors to experimental
* Baserom bug fixes
* 0.4.0.3
* Fixed a bug where Sanctuary could be chosen as a lobby for a DW dungeon in non-crossed ER modes
* 0.4.0.2

21
Rom.py
View File

@@ -27,7 +27,7 @@ from EntranceShuffle import door_addresses, exit_ids
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '5c5111bcb73b033ddf72be5b8ea08a8e'
RANDOMIZERBASEHASH = '32b289d8294deba1603c75bb1321d3da'
class JsonRom(object):
@@ -655,6 +655,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
dr_flags |= DROptions.Rails
if world.standardize_palettes[player] == 'original':
dr_flags |= DROptions.OriginalPalettes
if world.experimental[player]:
dr_flags |= DROptions.DarkWorld_Spawns
# fix hc big key problems (map and compass too)
@@ -665,6 +667,23 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_byte(0x1597b, sanctuary.dungeon.dungeon_id*2)
update_compasses(rom, world, player)
def should_be_bunny(region, mode):
if mode != 'inverted':
return region.is_dark_world and not region.is_light_world
else:
return region.is_light_world and not region.is_dark_world
# dark world spawns
sanc_region = world.get_region('Sanctuary', player)
if should_be_bunny(sanc_region, world.mode[player]):
rom.write_bytes(0x13fff2, [0x12, 0x00])
lh_name = 'Links House' if world.mode[player] != 'inverted' else 'Inverted Links House'
links_house = world.get_region(lh_name, player)
if should_be_bunny(links_house, world.mode[player]):
rom.write_bytes(0x13fff0, [0x04, 0x01])
# patch doors
if world.doorShuffle[player] == 'crossed':
rom.write_byte(0x138002, 2)

View File

@@ -1669,7 +1669,7 @@ def set_bunny_rules(world, player, inverted):
# Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing.
bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave',
'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)']
bunny_accessible_locations = ['Link\'s House', 'Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree',
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree',
'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid',
'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins',
'Spectacle Rock', 'Bombos Tablet', 'Ether Tablet', 'Purple Chest', 'Blacksmith',

View File

@@ -669,6 +669,10 @@ db $07,$07,$02,$02,$02,$02,$07,$07,$07,$20,$20,$07,$20,$20,$20,$07
;27f300
;
org $27ff00
SancDarkWorldFlag:
db 0
;org $27ff00
org $27fff0
LinksHouseDarkWorld:
dw $ffff
SanctuaryDarkWorld:
dw $ffff

View File

@@ -1,10 +1,9 @@
CheckDarkWorldSanc:
CheckDarkWorldSpawn:
STA $A0 : STA $048E ; what we wrote over
LDA.l InvertedMode : AND #$00FF : BNE +
LDA.l SancDarkWorldFlag : AND #$00FF : 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
LDA.l DRFlags : AND #$0200 : BEQ + ; skip if the flag isn't set
LDA.l $7EF357 : AND #$00FF : BNE + ; moon pearl?
LDA.l LinksHouseDarkWorld : CMP $A0 : BEQ ++
LDA.l SanctuaryDarkWorld : CMP $A0 : BNE +
++ SEP #$30 : LDA #$17 : STA $5D
INC $02E0 : LDA.b #$40 : STA !DARK_WORLD : REP #$30
+ RTL

View File

@@ -165,7 +165,7 @@ org $08c450 ; <- ancilla_receive_item.asm : 146-148 (STY $5D : STZ $02D8)
JSL RetrieveBunnyState : NOP
org $02d9ce ; <- Bank02.asm : Dungeon_LoadEntrance 10829 (STA $A0 : STA $048E)
JSL CheckDarkWorldSanc : NOP
JSL CheckDarkWorldSpawn : NOP
org $01891e ; <- Bank 01.asm : 991 Dungeon_LoadType2Object (LDA $00 : XBA : AND.w #$00FF)
JSL RainPrevention : NOP #2

View File

@@ -8,7 +8,7 @@ DrHudOverride:
HudAdditions:
{
lda.l DRFlags : and #$0008 : beq ++
LDA.w #$28A4 : STA !GOAL_DRAW_ADDRESS
; LDA.w #$28A4 : STA !GOAL_DRAW_ADDRESS
lda $7EF423
jsr HudHexToDec4DigitCopy
LDX.b $05 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+2 ; draw 100's digit

Binary file not shown.

View File

@@ -297,6 +297,10 @@
"dest": "shuffleganon",
"help": "suppress"
},
"shufflelinks": {
"action": "store_true",
"type": "bool"
},
"calc_playthrough": {
"action": "store_false",
"type": "bool"

View File

@@ -278,6 +278,9 @@
"Include the Ganon's Tower and Pyramid Hole in the",
"entrance shuffle pool. (default: %(default)s)"
],
"shufflelink": [
"Include Link's House in the entrance shuffle pool. (default: %(default)s)"
],
"heartbeep": [
"Select the rate at which the heart beep sound is played at",
"low health. (default: %(default)s)"

View File

@@ -111,6 +111,7 @@
"randomizer.entrance.openpyramid": "Pre-open Pyramid Hole",
"randomizer.entrance.shuffleganon": "Include Ganon's Tower and Pyramid Hole in shuffle pool",
"randomizer.entrance.shufflelinks": "Include Link's House in the shuffle pool",
"randomizer.entrance.entranceshuffle": "Entrance Shuffle",
"randomizer.entrance.entranceshuffle.vanilla": "Vanilla",

View File

@@ -18,6 +18,7 @@
"dungeonsfull",
"dungeonssimple"
]
}
},
"shufflelinks": { "type": "checkbox" }
}
}

View File

@@ -73,6 +73,7 @@ SETTINGSTOPROCESS = {
"entrance": {
"openpyramid": "openpyramid",
"shuffleganon": "shuffleganon",
"shufflelinks": "shufflelinks",
"entranceshuffle": "shuffle"
},
"enemizer": {