sm64ex: Refactor Regions (#2546)

Refactors region code to remove references to course index.
There were bugs somewhere, but I dont know where tbh.
This fixes them but leaves logic otherwise intact, and much cleaner to look at as there's one list less to take care of.

Additionally, this fixes stopping the clock from Big Boos Haunt.
This commit is contained in:
Yussur Mustafa Oraji 2023-12-06 18:19:03 +01:00 committed by GitHub
parent 229a263131
commit 530617c9a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 107 additions and 82 deletions

View File

@ -7,24 +7,41 @@ from .Locations import SM64Location, location_table, locBoB_table, locWhomp_tabl
locPSS_table, locSA_table, locBitDW_table, locTotWC_table, locCotMC_table, \ locPSS_table, locSA_table, locBitDW_table, locTotWC_table, locCotMC_table, \
locVCutM_table, locBitFS_table, locWMotR_table, locBitS_table, locSS_table locVCutM_table, locBitFS_table, locWMotR_table, locBitS_table, locSS_table
# List of all courses, including secrets, without BitS as that one is static # sm64paintings is dict of entrances, format LEVEL | AREA
sm64courses = ["Bob-omb Battlefield", "Whomp's Fortress", "Jolly Roger Bay", "Cool, Cool Mountain", "Big Boo's Haunt", sm64_level_to_paintings = {
"Hazy Maze Cave", "Lethal Lava Land", "Shifting Sand Land", "Dire, Dire Docks", "Snowman's Land", 91: "Bob-omb Battlefield",
"Wet-Dry World", "Tall, Tall Mountain", "Tiny-Huge Island", "Tick Tock Clock", "Rainbow Ride", 241: "Whomp's Fortress",
"The Princess's Secret Slide", "The Secret Aquarium", "Bowser in the Dark World", "Tower of the Wing Cap", 121: "Jolly Roger Bay",
"Cavern of the Metal Cap", "Vanish Cap under the Moat", "Bowser in the Fire Sea", "Wing Mario over the Rainbow"] 51: "Cool, Cool Mountain",
41: "Big Boo's Haunt",
71: "Hazy Maze Cave",
221: "Lethal Lava Land",
81: "Shifting Sand Land",
231: "Dire, Dire Docks",
101: "Snowman's Land",
111: "Wet-Dry World",
361: "Tall, Tall Mountain",
132: "Tiny-Huge Island (Tiny)",
131: "Tiny-Huge Island (Huge)",
141: "Tick Tock Clock",
151: "Rainbow Ride"
}
sm64_paintings_to_level = { painting: level for (level,painting) in sm64_level_to_paintings.items() }
# sm64secrets is list of secret areas, same format
sm64_level_to_secrets = {
271: "The Princess's Secret Slide",
201: "The Secret Aquarium",
171: "Bowser in the Dark World",
291: "Tower of the Wing Cap",
281: "Cavern of the Metal Cap",
181: "Vanish Cap under the Moat",
191: "Bowser in the Fire Sea",
311: "Wing Mario over the Rainbow"
}
sm64_secrets_to_level = { secret: level for (level,secret) in sm64_level_to_secrets.items() }
# sm64paintings is list of entrances, format LEVEL | AREA. String Reference below sm64_entrances_to_level = { **sm64_paintings_to_level, **sm64_secrets_to_level }
sm64paintings = [91,241,121,51,41,71,221,81,231,101,111,361,132,131,141,151] sm64_level_to_entrances = { **sm64_level_to_paintings, **sm64_level_to_secrets }
sm64paintings_s = ["BOB", "WF", "JRB", "CCM", "BBH", "HMC", "LLL", "SSL", "DDD", "SL", "WDW", "TTM", "THI Tiny", "THI Huge", "TTC", "RR"]
# sm64secrets is list of secret areas
sm64secrets = [271, 201, 171, 291, 281, 181, 191, 311]
sm64secrets_s = ["PSS", "SA", "BitDW", "TOTWC", "COTMC", "VCUTM", "BitFS", "WMOTR"]
sm64entrances = sm64paintings + sm64secrets
sm64entrances_s = sm64paintings_s + sm64secrets_s
sm64_internalloc_to_string = dict(zip(sm64paintings+sm64secrets, sm64entrances_s))
sm64_internalloc_to_regionid = dict(zip(sm64paintings+sm64secrets, list(range(13)) + [12,13,14] + list(range(15,15+len(sm64secrets)))))
def create_regions(world: MultiWorld, player: int): def create_regions(world: MultiWorld, player: int):
regSS = Region("Menu", player, world, "Castle Area") regSS = Region("Menu", player, world, "Castle Area")
@ -137,11 +154,13 @@ def create_regions(world: MultiWorld, player: int):
regTTM.locations.append(SM64Location(player, "TTM: 100 Coins", location_table["TTM: 100 Coins"], regTTM)) regTTM.locations.append(SM64Location(player, "TTM: 100 Coins", location_table["TTM: 100 Coins"], regTTM))
world.regions.append(regTTM) world.regions.append(regTTM)
regTHI = create_region("Tiny-Huge Island", player, world) regTHIT = create_region("Tiny-Huge Island (Tiny)", player, world)
create_default_locs(regTHI, locTHI_table, player) create_default_locs(regTHIT, locTHI_table, player)
if (world.EnableCoinStars[player].value): if (world.EnableCoinStars[player].value):
regTHI.locations.append(SM64Location(player, "THI: 100 Coins", location_table["THI: 100 Coins"], regTHI)) regTHIT.locations.append(SM64Location(player, "THI: 100 Coins", location_table["THI: 100 Coins"], regTHIT))
world.regions.append(regTHI) world.regions.append(regTHIT)
regTHIH = create_region("Tiny-Huge Island (Huge)", player, world)
world.regions.append(regTHIH)
regFloor3 = create_region("Third Floor", player, world) regFloor3 = create_region("Third Floor", player, world)
world.regions.append(regFloor3) world.regions.append(regFloor3)

View File

@ -1,77 +1,84 @@
from ..generic.Rules import add_rule from ..generic.Rules import add_rule
from .Regions import connect_regions, sm64courses, sm64paintings, sm64secrets, sm64entrances from .Regions import connect_regions, sm64_level_to_paintings, sm64_paintings_to_level, sm64_level_to_secrets, sm64_entrances_to_level, sm64_level_to_entrances
def fix_reg(entrance_ids, reg, invalidspot, swaplist, world): def shuffle_dict_keys(world, obj: dict) -> dict:
if entrance_ids.index(reg) == invalidspot: # Unlucky :C keys = list(obj.keys())
swaplist.remove(invalidspot) values = list(obj.values())
rand = world.random.choice(swaplist) world.random.shuffle(keys)
entrance_ids[invalidspot], entrance_ids[rand] = entrance_ids[rand], entrance_ids[invalidspot] return dict(zip(keys,values))
swaplist.append(invalidspot)
swaplist.remove(rand)
def set_rules(world, player: int, area_connections): def fix_reg(entrance_ids, entrance, destination, swapdict, world):
destination_regions = list(range(13)) + [12,13,14] + list(range(15,15+len(sm64secrets))) # Two instances of Destination Course THI. Past normal course idx are secret regions if entrance_ids[entrance] == destination: # Unlucky :C
secret_entrance_ids = list(range(len(sm64paintings), len(sm64paintings) + len(sm64secrets))) rand = world.random.choice(swapdict.keys())
course_entrance_ids = list(range(len(sm64paintings))) entrance_ids[entrance], entrance_ids[swapdict[rand]] = rand, entrance_ids[entrance]
if world.AreaRandomizer[player].value >= 1: # Some randomization is happening, randomize Courses swapdict[rand] = entrance_ids[entrance]
world.random.shuffle(course_entrance_ids) swapdict.pop(entrance)
def set_rules(world, player: int, area_connections: dict):
randomized_level_to_paintings = sm64_level_to_paintings.copy()
randomized_level_to_secrets = sm64_level_to_secrets.copy()
if world.AreaRandomizer[player].value == 1: # Some randomization is happening, randomize Courses
randomized_level_to_paintings = shuffle_dict_keys(world,sm64_level_to_paintings)
if world.AreaRandomizer[player].value == 2: # Randomize Secrets as well if world.AreaRandomizer[player].value == 2: # Randomize Secrets as well
world.random.shuffle(secret_entrance_ids) randomized_level_to_secrets = shuffle_dict_keys(world,sm64_level_to_secrets)
entrance_ids = course_entrance_ids + secret_entrance_ids randomized_entrances = { **randomized_level_to_paintings, **randomized_level_to_secrets }
if world.AreaRandomizer[player].value == 3: # Randomize Courses and Secrets in one pool if world.AreaRandomizer[player].value == 3: # Randomize Courses and Secrets in one pool
world.random.shuffle(entrance_ids) randomized_entrances = shuffle_dict_keys(world,randomized_entrances)
# Guarantee first entrance is a course # Guarantee first entrance is a course
swaplist = list(range(len(entrance_ids))) swapdict = { entrance: level for (level,entrance) in randomized_entrances }
if entrance_ids.index(0) > 15: # Unlucky :C if randomized_entrances[91] not in sm64_paintings_to_level.keys(): # Unlucky :C (91 -> BoB Entrance)
rand = world.random.randint(0,15) rand = world.random.choice(sm64_paintings_to_level.values())
entrance_ids[entrance_ids.index(0)], entrance_ids[rand] = entrance_ids[rand], entrance_ids[entrance_ids.index(0)] randomized_entrances[91], randomized_entrances[swapdict[rand]] = rand, randomized_entrances[91]
swaplist.remove(entrance_ids.index(0)) swapdict[rand] = randomized_entrances[91]
swapdict.pop("Bob-omb Battlefield")
# Guarantee COTMC is not mapped to HMC, cuz thats impossible # Guarantee COTMC is not mapped to HMC, cuz thats impossible
fix_reg(entrance_ids, 20, 5, swaplist, world) fix_reg(randomized_entrances, "Cavern of the Metal Cap", "Hazy Maze Cave", swapdict, world)
# Guarantee BITFS is not mapped to DDD # Guarantee BITFS is not mapped to DDD
fix_reg(entrance_ids, 22, 8, swaplist, world) fix_reg(randomized_entrances, "Bowser in the Fire Sea", "Dire, Dire Docks", swapdict, world)
if entrance_ids.index(22) == 5: # If BITFS is mapped to HMC... if randomized_entrances[191] == "Hazy Maze Cave": # If BITFS is mapped to HMC...
fix_reg(entrance_ids, 20, 8, swaplist, world) # ... then dont allow COTMC to be mapped to DDD fix_reg(randomized_entrances, "Cavern of the Metal Cap", "Dire, Dire Docks", swapdict, world) # ... then dont allow COTMC to be mapped to DDD
temp_assign = dict(zip(entrance_ids,destination_regions)) # Used for Rules only
# Destination Format: LVL | AREA with LVL = LEVEL_x, AREA = Area as used in sm64 code # Destination Format: LVL | AREA with LVL = LEVEL_x, AREA = Area as used in sm64 code
area_connections.update({sm64entrances[entrance]: destination for entrance, destination in zip(entrance_ids,sm64entrances)}) area_connections.update({entrance_lvl: sm64_entrances_to_level[destination] for (entrance_lvl,destination) in randomized_entrances.items()})
randomized_entrances_s = {sm64_level_to_entrances[entrance_lvl]: destination for (entrance_lvl,destination) in randomized_entrances.items()}
connect_regions(world, player, "Menu", sm64courses[temp_assign[0]]) # BOB connect_regions(world, player, "Menu", randomized_entrances_s["Bob-omb Battlefield"])
connect_regions(world, player, "Menu", sm64courses[temp_assign[1]], lambda state: state.has("Power Star", player, 1)) # WF connect_regions(world, player, "Menu", randomized_entrances_s["Whomp's Fortress"], lambda state: state.has("Power Star", player, 1))
connect_regions(world, player, "Menu", sm64courses[temp_assign[2]], lambda state: state.has("Power Star", player, 3)) # JRB connect_regions(world, player, "Menu", randomized_entrances_s["Jolly Roger Bay"], lambda state: state.has("Power Star", player, 3))
connect_regions(world, player, "Menu", sm64courses[temp_assign[3]], lambda state: state.has("Power Star", player, 3)) # CCM connect_regions(world, player, "Menu", randomized_entrances_s["Cool, Cool Mountain"], lambda state: state.has("Power Star", player, 3))
connect_regions(world, player, "Menu", sm64courses[temp_assign[4]], lambda state: state.has("Power Star", player, 12)) # BBH connect_regions(world, player, "Menu", randomized_entrances_s["Big Boo's Haunt"], lambda state: state.has("Power Star", player, 12))
connect_regions(world, player, "Menu", sm64courses[temp_assign[16]], lambda state: state.has("Power Star", player, 1)) # PSS connect_regions(world, player, "Menu", randomized_entrances_s["The Princess's Secret Slide"], lambda state: state.has("Power Star", player, 1))
connect_regions(world, player, "Menu", sm64courses[temp_assign[17]], lambda state: state.has("Power Star", player, 3)) # SA connect_regions(world, player, "Menu", randomized_entrances_s["The Secret Aquarium"], lambda state: state.has("Power Star", player, 3))
connect_regions(world, player, "Menu", sm64courses[temp_assign[19]], lambda state: state.has("Power Star", player, 10)) # TOTWC connect_regions(world, player, "Menu", randomized_entrances_s["Tower of the Wing Cap"], lambda state: state.has("Power Star", player, 10))
connect_regions(world, player, "Menu", sm64courses[temp_assign[18]], lambda state: state.has("Power Star", player, world.FirstBowserStarDoorCost[player].value)) # BITDW connect_regions(world, player, "Menu", randomized_entrances_s["Bowser in the Dark World"], lambda state: state.has("Power Star", player, world.FirstBowserStarDoorCost[player].value))
connect_regions(world, player, "Menu", "Basement", lambda state: state.has("Basement Key", player) or state.has("Progressive Key", player, 1)) connect_regions(world, player, "Menu", "Basement", lambda state: state.has("Basement Key", player) or state.has("Progressive Key", player, 1))
connect_regions(world, player, "Basement", sm64courses[temp_assign[5]]) # HMC connect_regions(world, player, "Basement", randomized_entrances_s["Hazy Maze Cave"])
connect_regions(world, player, "Basement", sm64courses[temp_assign[6]]) # LLL connect_regions(world, player, "Basement", randomized_entrances_s["Lethal Lava Land"])
connect_regions(world, player, "Basement", sm64courses[temp_assign[7]]) # SSL connect_regions(world, player, "Basement", randomized_entrances_s["Shifting Sand Land"])
connect_regions(world, player, "Basement", sm64courses[temp_assign[8]], lambda state: state.has("Power Star", player, world.BasementStarDoorCost[player].value)) # DDD connect_regions(world, player, "Basement", randomized_entrances_s["Dire, Dire Docks"], lambda state: state.has("Power Star", player, world.BasementStarDoorCost[player].value))
connect_regions(world, player, "Hazy Maze Cave", sm64courses[temp_assign[20]]) # COTMC connect_regions(world, player, "Hazy Maze Cave", randomized_entrances_s["Cavern of the Metal Cap"])
connect_regions(world, player, "Basement", sm64courses[temp_assign[21]]) # VCUTM connect_regions(world, player, "Basement", randomized_entrances_s["Vanish Cap under the Moat"])
connect_regions(world, player, "Basement", sm64courses[temp_assign[22]], lambda state: state.has("Power Star", player, world.BasementStarDoorCost[player].value) and connect_regions(world, player, "Basement", randomized_entrances_s["Bowser in the Fire Sea"], lambda state: state.has("Power Star", player, world.BasementStarDoorCost[player].value) and
state.can_reach("DDD: Board Bowser's Sub", 'Location', player)) # BITFS state.can_reach("DDD: Board Bowser's Sub", 'Location', player))
connect_regions(world, player, "Menu", "Second Floor", lambda state: state.has("Second Floor Key", player) or state.has("Progressive Key", player, 2)) connect_regions(world, player, "Menu", "Second Floor", lambda state: state.has("Second Floor Key", player) or state.has("Progressive Key", player, 2))
connect_regions(world, player, "Second Floor", sm64courses[temp_assign[9]]) # SL connect_regions(world, player, "Second Floor", randomized_entrances_s["Snowman's Land"])
connect_regions(world, player, "Second Floor", sm64courses[temp_assign[10]]) # WDW connect_regions(world, player, "Second Floor", randomized_entrances_s["Wet-Dry World"])
connect_regions(world, player, "Second Floor", sm64courses[temp_assign[11]]) # TTM connect_regions(world, player, "Second Floor", randomized_entrances_s["Tall, Tall Mountain"])
connect_regions(world, player, "Second Floor", sm64courses[temp_assign[12]]) # THI Tiny connect_regions(world, player, "Second Floor", randomized_entrances_s["Tiny-Huge Island (Tiny)"])
connect_regions(world, player, "Second Floor", sm64courses[temp_assign[13]]) # THI Huge connect_regions(world, player, "Second Floor", randomized_entrances_s["Tiny-Huge Island (Huge)"])
connect_regions(world, player, "Tiny-Huge Island (Tiny)", "Tiny-Huge Island (Huge)")
connect_regions(world, player, "Tiny-Huge Island (Huge)", "Tiny-Huge Island (Tiny)")
connect_regions(world, player, "Second Floor", "Third Floor", lambda state: state.has("Power Star", player, world.SecondFloorStarDoorCost[player].value)) connect_regions(world, player, "Second Floor", "Third Floor", lambda state: state.has("Power Star", player, world.SecondFloorStarDoorCost[player].value))
connect_regions(world, player, "Third Floor", sm64courses[temp_assign[14]]) # TTC connect_regions(world, player, "Third Floor", randomized_entrances_s["Tick Tock Clock"])
connect_regions(world, player, "Third Floor", sm64courses[temp_assign[15]]) # RR connect_regions(world, player, "Third Floor", randomized_entrances_s["Rainbow Ride"])
connect_regions(world, player, "Third Floor", sm64courses[temp_assign[23]]) # WMOTR connect_regions(world, player, "Third Floor", randomized_entrances_s["Wing Mario over the Rainbow"])
connect_regions(world, player, "Third Floor", "Bowser in the Sky", lambda state: state.has("Power Star", player, world.StarsToFinish[player].value)) # BITS connect_regions(world, player, "Third Floor", "Bowser in the Sky", lambda state: state.has("Power Star", player, world.StarsToFinish[player].value))
#Special Rules for some Locations #Special Rules for some Locations
add_rule(world.get_location("BoB: Mario Wings to the Sky", player), lambda state: state.has("Cannon Unlock BoB", player)) add_rule(world.get_location("BoB: Mario Wings to the Sky", player), lambda state: state.has("Cannon Unlock BoB", player))

View File

@ -5,7 +5,7 @@ from .Items import item_table, cannon_item_table, SM64Item
from .Locations import location_table, SM64Location from .Locations import location_table, SM64Location
from .Options import sm64_options from .Options import sm64_options
from .Rules import set_rules from .Rules import set_rules
from .Regions import create_regions, sm64courses, sm64entrances_s, sm64_internalloc_to_string, sm64_internalloc_to_regionid from .Regions import create_regions, sm64_level_to_entrances
from BaseClasses import Item, Tutorial, ItemClassification from BaseClasses import Item, Tutorial, ItemClassification
from ..AutoWorld import World, WebWorld from ..AutoWorld import World, WebWorld
@ -55,8 +55,8 @@ class SM64World(World):
# Write area_connections to spoiler log # Write area_connections to spoiler log
for entrance, destination in self.area_connections.items(): for entrance, destination in self.area_connections.items():
self.multiworld.spoiler.set_entrance( self.multiworld.spoiler.set_entrance(
sm64_internalloc_to_string[entrance] + " Entrance", sm64_level_to_entrances[entrance] + " Entrance",
sm64_internalloc_to_string[destination], sm64_level_to_entrances[destination],
'entrance', self.player) 'entrance', self.player)
def create_item(self, name: str) -> Item: def create_item(self, name: str) -> Item:
@ -182,8 +182,7 @@ class SM64World(World):
if self.topology_present: if self.topology_present:
er_hint_data = {} er_hint_data = {}
for entrance, destination in self.area_connections.items(): for entrance, destination in self.area_connections.items():
regionid = sm64_internalloc_to_regionid[destination] region = self.multiworld.get_region(sm64_level_to_entrances[destination], self.player)
region = self.multiworld.get_region(sm64courses[regionid], self.player)
for location in region.locations: for location in region.locations:
er_hint_data[location.address] = sm64_internalloc_to_string[entrance] er_hint_data[location.address] = sm64_level_to_entrances[entrance]
multidata['er_hint_data'][self.player] = er_hint_data multidata['er_hint_data'][self.player] = er_hint_data