SM64: Move Randomizer Content Update (#2569)

* Super Mario 64: Move Randomizer Update

Co-authored-by: RBman <139954693+RBmans@users.noreply.github.com>
Signed-off-by: Magnemania <magnemight@gmail.com>

* Fixed logic for Vanish Cap Under the Moat

Signed-off-by: Magnemania <magnemight@gmail.com>
This commit is contained in:
Magnemania 2024-02-13 00:14:21 -05:00 committed by GitHub
parent 6f3bc3a7ad
commit 0c8f726393
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 546 additions and 216 deletions

View File

@ -16,6 +16,21 @@ generic_item_table = {
"1Up Mushroom": 3626184
}
action_item_table = {
"Double Jump": 3626185,
"Triple Jump": 3626186,
"Long Jump": 3626187,
"Backflip": 3626188,
"Side Flip": 3626189,
"Wall Kick": 3626190,
"Dive": 3626191,
"Ground Pound": 3626192,
"Kick": 3626193,
"Climb": 3626194,
"Ledge Grab": 3626195
}
cannon_item_table = {
"Cannon Unlock BoB": 3626200,
"Cannon Unlock WF": 3626201,
@ -29,4 +44,4 @@ cannon_item_table = {
"Cannon Unlock RR": 3626214
}
item_table = {**generic_item_table, **cannon_item_table}
item_table = {**generic_item_table, **action_item_table, **cannon_item_table}

View File

@ -1,9 +1,10 @@
import typing
from Options import Option, DefaultOnToggle, Range, Toggle, DeathLink, Choice
from .Items import action_item_table
class EnableCoinStars(DefaultOnToggle):
"""Disable to Ignore 100 Coin Stars. You can still collect them, but they don't do anything"""
"""Disable to Ignore 100 Coin Stars. You can still collect them, but they don't do anything.
Removes 15 locations from the pool."""
display_name = "Enable 100 Coin Stars"
@ -13,56 +14,63 @@ class StrictCapRequirements(DefaultOnToggle):
class StrictCannonRequirements(DefaultOnToggle):
"""If disabled, Stars that expect cannons may have to be acquired without them. Only makes a difference if Buddy
Checks are enabled"""
"""If disabled, Stars that expect cannons may have to be acquired without them.
Has no effect if Buddy Checks and Move Randomizer are disabled"""
display_name = "Strict Cannon Requirements"
class FirstBowserStarDoorCost(Range):
"""How many stars are required at the Star Door to Bowser in the Dark World"""
"""What percent of the total stars are required at the Star Door to Bowser in the Dark World"""
display_name = "First Star Door Cost %"
range_start = 0
range_end = 50
default = 8
range_end = 40
default = 7
class BasementStarDoorCost(Range):
"""How many stars are required at the Star Door in the Basement"""
"""What percent of the total stars are required at the Star Door in the Basement"""
display_name = "Basement Star Door %"
range_start = 0
range_end = 70
default = 30
range_end = 50
default = 25
class SecondFloorStarDoorCost(Range):
"""How many stars are required to access the third floor"""
"""What percent of the total stars are required to access the third floor"""
display_name = 'Second Floor Star Door %'
range_start = 0
range_end = 90
default = 50
range_end = 70
default = 42
class MIPS1Cost(Range):
"""How many stars are required to spawn MIPS the first time"""
"""What percent of the total stars are required to spawn MIPS the first time"""
display_name = "MIPS 1 Star %"
range_start = 0
range_end = 40
default = 15
range_end = 35
default = 12
class MIPS2Cost(Range):
"""How many stars are required to spawn MIPS the second time."""
"""What percent of the total stars are required to spawn MIPS the second time."""
display_name = "MIPS 2 Star %"
range_start = 0
range_end = 80
default = 50
range_end = 70
default = 42
class StarsToFinish(Range):
"""How many stars are required at the infinite stairs"""
display_name = "Endless Stairs Stars"
"""What percent of the total stars are required at the infinite stairs"""
display_name = "Endless Stairs Star %"
range_start = 0
range_end = 100
default = 70
range_end = 90
default = 58
class AmountOfStars(Range):
"""How many stars exist. Disabling 100 Coin Stars removes 15 from the Pool. At least max of any Cost set"""
"""How many stars exist.
If there aren't enough locations to hold the given total, the total will be reduced."""
display_name = "Total Power Stars"
range_start = 35
range_end = 120
default = 120
@ -83,11 +91,13 @@ class BuddyChecks(Toggle):
class ExclamationBoxes(Choice):
"""Include 1Up Exclamation Boxes during randomization"""
"""Include 1Up Exclamation Boxes during randomization.
Adds 29 locations to the pool."""
display_name = "Randomize 1Up !-Blocks"
option_Off = 0
option_1Ups_Only = 1
class CompletionType(Choice):
"""Set goal for game completion"""
display_name = "Completion Goal"
@ -96,17 +106,32 @@ class CompletionType(Choice):
class ProgressiveKeys(DefaultOnToggle):
"""Keys will first grant you access to the Basement, then to the Secound Floor"""
"""Keys will first grant you access to the Basement, then to the Second Floor"""
display_name = "Progressive Keys"
class StrictMoveRequirements(DefaultOnToggle):
"""If disabled, Stars that expect certain moves may have to be acquired without them. Only makes a difference
if Move Randomization is enabled"""
display_name = "Strict Move Requirements"
def getMoveRandomizerOption(action: str):
class MoveRandomizerOption(Toggle):
"""Mario is unable to perform this action until a corresponding item is picked up.
This option is incompatible with builds using a 'nomoverando' branch."""
display_name = f"Randomize {action}"
return MoveRandomizerOption
sm64_options: typing.Dict[str, type(Option)] = {
"AreaRandomizer": AreaRandomizer,
"BuddyChecks": BuddyChecks,
"ExclamationBoxes": ExclamationBoxes,
"ProgressiveKeys": ProgressiveKeys,
"EnableCoinStars": EnableCoinStars,
"AmountOfStars": AmountOfStars,
"StrictCapRequirements": StrictCapRequirements,
"StrictCannonRequirements": StrictCannonRequirements,
"StrictMoveRequirements": StrictMoveRequirements,
"AmountOfStars": AmountOfStars,
"FirstBowserStarDoorCost": FirstBowserStarDoorCost,
"BasementStarDoorCost": BasementStarDoorCost,
"SecondFloorStarDoorCost": SecondFloorStarDoorCost,
@ -114,7 +139,10 @@ sm64_options: typing.Dict[str, type(Option)] = {
"MIPS2Cost": MIPS2Cost,
"StarsToFinish": StarsToFinish,
"death_link": DeathLink,
"BuddyChecks": BuddyChecks,
"ExclamationBoxes": ExclamationBoxes,
"CompletionType" : CompletionType,
"CompletionType": CompletionType,
}
for action in action_item_table:
# HACK: Disable randomization of double jump
if action == 'Double Jump': continue
sm64_options[f"MoveRandomizer{action.replace(' ','')}"] = getMoveRandomizerOption(action)

View File

@ -1,5 +1,4 @@
import typing
from enum import Enum
from BaseClasses import MultiWorld, Region, Entrance, Location
@ -8,7 +7,8 @@ from .Locations import SM64Location, location_table, locBoB_table, locWhomp_tabl
locHMC_table, locLLL_table, locSSL_table, locDDD_table, locSL_table, \
locWDW_table, locTTM_table, locTHI_table, locTTC_table, locRR_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
class SM64Levels(int, Enum):
BOB_OMB_BATTLEFIELD = 91
@ -55,7 +55,7 @@ sm64_level_to_paintings: typing.Dict[SM64Levels, str] = {
SM64Levels.TICK_TOCK_CLOCK: "Tick Tock Clock",
SM64Levels.RAINBOW_RIDE: "Rainbow Ride"
}
sm64_paintings_to_level = { painting: level for (level,painting) in sm64_level_to_paintings.items() }
sm64_paintings_to_level = {painting: level for (level, painting) in sm64_level_to_paintings.items() }
# sm64secrets is a dict of secret areas, same format as sm64paintings
sm64_level_to_secrets: typing.Dict[SM64Levels, str] = {
@ -68,152 +68,163 @@ sm64_level_to_secrets: typing.Dict[SM64Levels, str] = {
SM64Levels.BOWSER_IN_THE_FIRE_SEA: "Bowser in the Fire Sea",
SM64Levels.WING_MARIO_OVER_THE_RAINBOW: "Wing Mario over the Rainbow"
}
sm64_secrets_to_level = { secret: level for (level,secret) in sm64_level_to_secrets.items() }
sm64_secrets_to_level = {secret: level for (level,secret) in sm64_level_to_secrets.items() }
sm64_entrances_to_level = { **sm64_paintings_to_level, **sm64_secrets_to_level }
sm64_level_to_entrances = { **sm64_level_to_paintings, **sm64_level_to_secrets }
sm64_entrances_to_level = {**sm64_paintings_to_level, **sm64_secrets_to_level }
sm64_level_to_entrances = {**sm64_level_to_paintings, **sm64_level_to_secrets }
def create_regions(world: MultiWorld, player: int):
regSS = Region("Menu", player, world, "Castle Area")
create_default_locs(regSS, locSS_table, player)
create_default_locs(regSS, locSS_table)
world.regions.append(regSS)
regBoB = create_region("Bob-omb Battlefield", player, world)
create_default_locs(regBoB, locBoB_table, player)
create_locs(regBoB, "BoB: Big Bob-Omb on the Summit", "BoB: Footrace with Koopa The Quick",
"BoB: Mario Wings to the Sky", "BoB: Behind Chain Chomp's Gate", "BoB: Bob-omb Buddy")
create_subregion(regBoB, "BoB: Island", "BoB: Shoot to the Island in the Sky", "BoB: Find the 8 Red Coins")
if (world.EnableCoinStars[player].value):
regBoB.locations.append(SM64Location(player, "BoB: 100 Coins", location_table["BoB: 100 Coins"], regBoB))
world.regions.append(regBoB)
create_locs(regBoB, "BoB: 100 Coins")
regWhomp = create_region("Whomp's Fortress", player, world)
create_default_locs(regWhomp, locWhomp_table, player)
create_locs(regWhomp, "WF: Chip Off Whomp's Block", "WF: Shoot into the Wild Blue", "WF: Red Coins on the Floating Isle",
"WF: Fall onto the Caged Island", "WF: Blast Away the Wall")
create_subregion(regWhomp, "WF: Tower", "WF: To the Top of the Fortress", "WF: Bob-omb Buddy")
if (world.EnableCoinStars[player].value):
regWhomp.locations.append(SM64Location(player, "WF: 100 Coins", location_table["WF: 100 Coins"], regWhomp))
world.regions.append(regWhomp)
create_locs(regWhomp, "WF: 100 Coins")
regJRB = create_region("Jolly Roger Bay", player, world)
create_default_locs(regJRB, locJRB_table, player)
create_locs(regJRB, "JRB: Plunder in the Sunken Ship", "JRB: Can the Eel Come Out to Play?", "JRB: Treasure of the Ocean Cave",
"JRB: Blast to the Stone Pillar", "JRB: Through the Jet Stream", "JRB: Bob-omb Buddy")
jrb_upper = create_subregion(regJRB, 'JRB: Upper', "JRB: Red Coins on the Ship Afloat")
if (world.EnableCoinStars[player].value):
regJRB.locations.append(SM64Location(player, "JRB: 100 Coins", location_table["JRB: 100 Coins"], regJRB))
world.regions.append(regJRB)
create_locs(jrb_upper, "JRB: 100 Coins")
regCCM = create_region("Cool, Cool Mountain", player, world)
create_default_locs(regCCM, locCCM_table, player)
create_default_locs(regCCM, locCCM_table)
if (world.EnableCoinStars[player].value):
regCCM.locations.append(SM64Location(player, "CCM: 100 Coins", location_table["CCM: 100 Coins"], regCCM))
world.regions.append(regCCM)
create_locs(regCCM, "CCM: 100 Coins")
regBBH = create_region("Big Boo's Haunt", player, world)
create_default_locs(regBBH, locBBH_table, player)
create_locs(regBBH, "BBH: Go on a Ghost Hunt", "BBH: Ride Big Boo's Merry-Go-Round",
"BBH: Secret of the Haunted Books", "BBH: Seek the 8 Red Coins")
bbh_third_floor = create_subregion(regBBH, "BBH: Third Floor", "BBH: Eye to Eye in the Secret Room")
create_subregion(bbh_third_floor, "BBH: Roof", "BBH: Big Boo's Balcony", "BBH: 1Up Block Top of Mansion")
if (world.EnableCoinStars[player].value):
regBBH.locations.append(SM64Location(player, "BBH: 100 Coins", location_table["BBH: 100 Coins"], regBBH))
world.regions.append(regBBH)
create_locs(regBBH, "BBH: 100 Coins")
regPSS = create_region("The Princess's Secret Slide", player, world)
create_default_locs(regPSS, locPSS_table, player)
world.regions.append(regPSS)
create_default_locs(regPSS, locPSS_table)
regSA = create_region("The Secret Aquarium", player, world)
create_default_locs(regSA, locSA_table, player)
world.regions.append(regSA)
create_default_locs(regSA, locSA_table)
regTotWC = create_region("Tower of the Wing Cap", player, world)
create_default_locs(regTotWC, locTotWC_table, player)
world.regions.append(regTotWC)
create_default_locs(regTotWC, locTotWC_table)
regBitDW = create_region("Bowser in the Dark World", player, world)
create_default_locs(regBitDW, locBitDW_table, player)
world.regions.append(regBitDW)
create_default_locs(regBitDW, locBitDW_table)
regBasement = create_region("Basement", player, world)
world.regions.append(regBasement)
create_region("Basement", player, world)
regHMC = create_region("Hazy Maze Cave", player, world)
create_default_locs(regHMC, locHMC_table, player)
create_locs(regHMC, "HMC: Swimming Beast in the Cavern", "HMC: Metal-Head Mario Can Move!",
"HMC: Watch for Rolling Rocks", "HMC: Navigating the Toxic Maze","HMC: 1Up Block Past Rolling Rocks")
hmc_red_coin_area = create_subregion(regHMC, "HMC: Red Coin Area", "HMC: Elevate for 8 Red Coins")
create_subregion(regHMC, "HMC: Pit Islands", "HMC: A-Maze-Ing Emergency Exit", "HMC: 1Up Block above Pit")
if (world.EnableCoinStars[player].value):
regHMC.locations.append(SM64Location(player, "HMC: 100 Coins", location_table["HMC: 100 Coins"], regHMC))
world.regions.append(regHMC)
create_locs(hmc_red_coin_area, "HMC: 100 Coins")
regLLL = create_region("Lethal Lava Land", player, world)
create_default_locs(regLLL, locLLL_table, player)
create_locs(regLLL, "LLL: Boil the Big Bully", "LLL: Bully the Bullies",
"LLL: 8-Coin Puzzle with 15 Pieces", "LLL: Red-Hot Log Rolling")
create_subregion(regLLL, "LLL: Upper Volcano", "LLL: Hot-Foot-It into the Volcano", "LLL: Elevator Tour in the Volcano")
if (world.EnableCoinStars[player].value):
regLLL.locations.append(SM64Location(player, "LLL: 100 Coins", location_table["LLL: 100 Coins"], regLLL))
world.regions.append(regLLL)
create_locs(regLLL, "LLL: 100 Coins")
regSSL = create_region("Shifting Sand Land", player, world)
create_default_locs(regSSL, locSSL_table, player)
create_locs(regSSL, "SSL: In the Talons of the Big Bird", "SSL: Shining Atop the Pyramid", "SSL: Inside the Ancient Pyramid",
"SSL: Free Flying for 8 Red Coins", "SSL: Bob-omb Buddy",
"SSL: 1Up Block Outside Pyramid", "SSL: 1Up Block Pyramid Left Path", "SSL: 1Up Block Pyramid Back")
create_subregion(regSSL, "SSL: Upper Pyramid", "SSL: Stand Tall on the Four Pillars", "SSL: Pyramid Puzzle")
if (world.EnableCoinStars[player].value):
regSSL.locations.append(SM64Location(player, "SSL: 100 Coins", location_table["SSL: 100 Coins"], regSSL))
world.regions.append(regSSL)
create_locs(regSSL, "SSL: 100 Coins")
regDDD = create_region("Dire, Dire Docks", player, world)
create_default_locs(regDDD, locDDD_table, player)
create_locs(regDDD, "DDD: Board Bowser's Sub", "DDD: Chests in the Current", "DDD: Through the Jet Stream",
"DDD: The Manta Ray's Reward", "DDD: Collect the Caps...")
ddd_moving_poles = create_subregion(regDDD, "DDD: Moving Poles", "DDD: Pole-Jumping for Red Coins")
if (world.EnableCoinStars[player].value):
regDDD.locations.append(SM64Location(player, "DDD: 100 Coins", location_table["DDD: 100 Coins"], regDDD))
world.regions.append(regDDD)
create_locs(ddd_moving_poles, "DDD: 100 Coins")
regCotMC = create_region("Cavern of the Metal Cap", player, world)
create_default_locs(regCotMC, locCotMC_table, player)
world.regions.append(regCotMC)
create_default_locs(regCotMC, locCotMC_table)
regVCutM = create_region("Vanish Cap under the Moat", player, world)
create_default_locs(regVCutM, locVCutM_table, player)
world.regions.append(regVCutM)
create_default_locs(regVCutM, locVCutM_table)
regBitFS = create_region("Bowser in the Fire Sea", player, world)
create_default_locs(regBitFS, locBitFS_table, player)
world.regions.append(regBitFS)
create_subregion(regBitFS, "BitFS: Upper", *locBitFS_table.keys())
regFloor2 = create_region("Second Floor", player, world)
world.regions.append(regFloor2)
create_region("Second Floor", player, world)
regSL = create_region("Snowman's Land", player, world)
create_default_locs(regSL, locSL_table, player)
create_default_locs(regSL, locSL_table)
if (world.EnableCoinStars[player].value):
regSL.locations.append(SM64Location(player, "SL: 100 Coins", location_table["SL: 100 Coins"], regSL))
world.regions.append(regSL)
create_locs(regSL, "SL: 100 Coins")
regWDW = create_region("Wet-Dry World", player, world)
create_default_locs(regWDW, locWDW_table, player)
create_locs(regWDW, "WDW: Express Elevator--Hurry Up!")
wdw_top = create_subregion(regWDW, "WDW: Top", "WDW: Shocking Arrow Lifts!", "WDW: Top o' the Town",
"WDW: Secrets in the Shallows & Sky", "WDW: Bob-omb Buddy")
create_subregion(regWDW, "WDW: Downtown", "WDW: Go to Town for Red Coins", "WDW: Quick Race Through Downtown!", "WDW: 1Up Block in Downtown")
if (world.EnableCoinStars[player].value):
regWDW.locations.append(SM64Location(player, "WDW: 100 Coins", location_table["WDW: 100 Coins"], regWDW))
world.regions.append(regWDW)
create_locs(wdw_top, "WDW: 100 Coins")
regTTM = create_region("Tall, Tall Mountain", player, world)
create_default_locs(regTTM, locTTM_table, player)
ttm_middle = create_subregion(regTTM, "TTM: Middle", "TTM: Scary 'Shrooms, Red Coins", "TTM: Blast to the Lonely Mushroom",
"TTM: Bob-omb Buddy", "TTM: 1Up Block on Red Mushroom")
ttm_top = create_subregion(ttm_middle, "TTM: Top", "TTM: Scale the Mountain", "TTM: Mystery of the Monkey Cage",
"TTM: Mysterious Mountainside", "TTM: Breathtaking View from Bridge")
if (world.EnableCoinStars[player].value):
regTTM.locations.append(SM64Location(player, "TTM: 100 Coins", location_table["TTM: 100 Coins"], regTTM))
world.regions.append(regTTM)
create_locs(ttm_top, "TTM: 100 Coins")
regTHIT = create_region("Tiny-Huge Island (Tiny)", player, world)
create_default_locs(regTHIT, locTHI_table, player)
create_region("Tiny-Huge Island (Huge)", player, world)
create_region("Tiny-Huge Island (Tiny)", player, world)
regTHI = create_region("Tiny-Huge Island", player, world)
create_locs(regTHI, "THI: The Tip Top of the Huge Island", "THI: 1Up Block THI Small near Start")
thi_pipes = create_subregion(regTHI, "THI: Pipes", "THI: Pluck the Piranha Flower", "THI: Rematch with Koopa the Quick",
"THI: Five Itty Bitty Secrets", "THI: Wiggler's Red Coins", "THI: Bob-omb Buddy",
"THI: 1Up Block THI Large near Start", "THI: 1Up Block Windy Area")
thi_large_top = create_subregion(thi_pipes, "THI: Large Top", "THI: Make Wiggler Squirm")
if (world.EnableCoinStars[player].value):
regTHIT.locations.append(SM64Location(player, "THI: 100 Coins", location_table["THI: 100 Coins"], regTHIT))
world.regions.append(regTHIT)
regTHIH = create_region("Tiny-Huge Island (Huge)", player, world)
world.regions.append(regTHIH)
create_locs(thi_large_top, "THI: 100 Coins")
regFloor3 = create_region("Third Floor", player, world)
world.regions.append(regFloor3)
regTTC = create_region("Tick Tock Clock", player, world)
create_default_locs(regTTC, locTTC_table, player)
create_locs(regTTC, "TTC: Stop Time for Red Coins")
ttc_lower = create_subregion(regTTC, "TTC: Lower", "TTC: Roll into the Cage", "TTC: Get a Hand", "TTC: 1Up Block Midway Up")
ttc_upper = create_subregion(ttc_lower, "TTC: Upper", "TTC: Timed Jumps on Moving Bars", "TTC: The Pit and the Pendulums")
ttc_top = create_subregion(ttc_upper, "TTC: Top", "TTC: Stomp on the Thwomp", "TTC: 1Up Block at the Top")
if (world.EnableCoinStars[player].value):
regTTC.locations.append(SM64Location(player, "TTC: 100 Coins", location_table["TTC: 100 Coins"], regTTC))
world.regions.append(regTTC)
create_locs(ttc_top, "TTC: 100 Coins")
regRR = create_region("Rainbow Ride", player, world)
create_default_locs(regRR, locRR_table, player)
create_locs(regRR, "RR: Swingin' in the Breeze", "RR: Tricky Triangles!",
"RR: 1Up Block Top of Red Coin Maze", "RR: 1Up Block Under Fly Guy", "RR: Bob-omb Buddy")
rr_maze = create_subregion(regRR, "RR: Maze", "RR: Coins Amassed in a Maze")
create_subregion(regRR, "RR: Cruiser", "RR: Cruiser Crossing the Rainbow", "RR: Somewhere Over the Rainbow")
create_subregion(regRR, "RR: House", "RR: The Big House in the Sky", "RR: 1Up Block On House in the Sky")
if (world.EnableCoinStars[player].value):
regRR.locations.append(SM64Location(player, "RR: 100 Coins", location_table["RR: 100 Coins"], regRR))
world.regions.append(regRR)
create_locs(rr_maze, "RR: 100 Coins")
regWMotR = create_region("Wing Mario over the Rainbow", player, world)
create_default_locs(regWMotR, locWMotR_table, player)
world.regions.append(regWMotR)
create_default_locs(regWMotR, locWMotR_table)
regBitS = create_region("Bowser in the Sky", player, world)
create_default_locs(regBitS, locBitS_table, player)
world.regions.append(regBitS)
create_locs(regBitS, "Bowser in the Sky 1Up Block")
create_subregion(regBitS, "BitS: Top", "Bowser in the Sky Red Coins")
def connect_regions(world: MultiWorld, player: int, source: str, target: str, rule=None):
@ -227,9 +238,30 @@ def connect_regions(world: MultiWorld, player: int, source: str, target: str, ru
sourceRegion.exits.append(connection)
connection.connect(targetRegion)
def create_region(name: str, player: int, world: MultiWorld) -> Region:
return Region(name, player, world)
def create_default_locs(reg: Region, locs, player):
reg_names = [name for name, id in locs.items()]
reg.locations += [SM64Location(player, loc_name, location_table[loc_name], reg) for loc_name in locs]
def create_region(name: str, player: int, world: MultiWorld) -> Region:
region = Region(name, player, world)
world.regions.append(region)
return region
def create_subregion(source_region: Region, name: str, *locs: str) -> Region:
region = Region(name, source_region.player, source_region.multiworld)
connection = Entrance(source_region.player, name, source_region)
source_region.exits.append(connection)
connection.connect(region)
source_region.multiworld.regions.append(region)
create_locs(region, *locs)
return region
def set_subregion_access_rule(world, player, region_name: str, rule):
world.get_entrance(world, player, region_name).access_rule = rule
def create_default_locs(reg: Region, default_locs: dict):
create_locs(reg, *default_locs.keys())
def create_locs(reg: Region, *locs: str):
reg.locations += [SM64Location(reg.player, loc_name, location_table[loc_name], reg) for loc_name in locs]

View File

@ -1,36 +1,59 @@
from ..generic.Rules import add_rule
from .Regions import connect_regions, SM64Levels, sm64_level_to_paintings, sm64_paintings_to_level, sm64_level_to_secrets, sm64_secrets_to_level, sm64_entrances_to_level, sm64_level_to_entrances
from typing import Callable, Union, Dict, Set
def shuffle_dict_keys(world, obj: dict) -> dict:
keys = list(obj.keys())
values = list(obj.values())
from BaseClasses import MultiWorld
from ..generic.Rules import add_rule, set_rule
from .Locations import location_table
from .Regions import connect_regions, SM64Levels, sm64_level_to_paintings, sm64_paintings_to_level,\
sm64_level_to_secrets, sm64_secrets_to_level, sm64_entrances_to_level, sm64_level_to_entrances
from .Items import action_item_table
def shuffle_dict_keys(world, dictionary: dict) -> dict:
keys = list(dictionary.keys())
values = list(dictionary.values())
world.random.shuffle(keys)
return dict(zip(keys,values))
return dict(zip(keys, values))
def fix_reg(entrance_map: dict, entrance: SM64Levels, invalid_regions: set,
swapdict: dict, world):
def fix_reg(entrance_map: Dict[SM64Levels, str], entrance: SM64Levels, invalid_regions: Set[str],
swapdict: Dict[SM64Levels, str], world):
if entrance_map[entrance] in invalid_regions: # Unlucky :C
replacement_regions = [(rand_region, rand_entrance) for rand_region, rand_entrance in swapdict.items()
replacement_regions = [(rand_entrance, rand_region) for rand_entrance, rand_region in swapdict.items()
if rand_region not in invalid_regions]
rand_region, rand_entrance = world.random.choice(replacement_regions)
rand_entrance, rand_region = world.random.choice(replacement_regions)
old_dest = entrance_map[entrance]
entrance_map[entrance], entrance_map[rand_entrance] = rand_region, old_dest
swapdict[rand_region] = entrance
swapdict.pop(entrance_map[entrance]) # Entrance now fixed to rand_region
swapdict[entrance], swapdict[rand_entrance] = rand_region, old_dest
swapdict.pop(entrance)
def set_rules(world, player: int, area_connections: dict):
def set_rules(world, player: int, area_connections: dict, star_costs: dict, move_rando_bitvec: int):
randomized_level_to_paintings = sm64_level_to_paintings.copy()
randomized_level_to_secrets = sm64_level_to_secrets.copy()
valid_move_randomizer_start_courses = [
"Bob-omb Battlefield", "Jolly Roger Bay", "Cool, Cool Mountain",
"Big Boo's Haunt", "Lethal Lava Land", "Shifting Sand Land",
"Dire, Dire Docks", "Snowman's Land"
] # Excluding WF, HMC, WDW, TTM, THI, TTC, and RR
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 not shuffling later, ensure a valid start course on move randomizer
if world.AreaRandomizer[player].value < 3 and move_rando_bitvec > 0:
swapdict = randomized_level_to_paintings.copy()
invalid_start_courses = {course for course in randomized_level_to_paintings.values() if course not in valid_move_randomizer_start_courses}
fix_reg(randomized_level_to_paintings, SM64Levels.BOB_OMB_BATTLEFIELD, invalid_start_courses, swapdict, world)
fix_reg(randomized_level_to_paintings, SM64Levels.WHOMPS_FORTRESS, invalid_start_courses, swapdict, world)
if world.AreaRandomizer[player].value == 2: # Randomize Secrets as well
randomized_level_to_secrets = shuffle_dict_keys(world,sm64_level_to_secrets)
randomized_entrances = { **randomized_level_to_paintings, **randomized_level_to_secrets }
randomized_entrances = {**randomized_level_to_paintings, **randomized_level_to_secrets}
if world.AreaRandomizer[player].value == 3: # Randomize Courses and Secrets in one pool
randomized_entrances = shuffle_dict_keys(world,randomized_entrances)
swapdict = { entrance: level for (level,entrance) in randomized_entrances.items() }
randomized_entrances = shuffle_dict_keys(world, randomized_entrances)
# Guarantee first entrance is a course
fix_reg(randomized_entrances, SM64Levels.BOB_OMB_BATTLEFIELD, sm64_secrets_to_level.keys(), swapdict, world)
swapdict = randomized_entrances.copy()
if move_rando_bitvec == 0:
fix_reg(randomized_entrances, SM64Levels.BOB_OMB_BATTLEFIELD, sm64_secrets_to_level.keys(), swapdict, world)
else:
invalid_start_courses = {course for course in randomized_entrances.values() if course not in valid_move_randomizer_start_courses}
fix_reg(randomized_entrances, SM64Levels.BOB_OMB_BATTLEFIELD, invalid_start_courses, swapdict, world)
fix_reg(randomized_entrances, SM64Levels.WHOMPS_FORTRESS, invalid_start_courses, swapdict, world)
# Guarantee BITFS is not mapped to DDD
fix_reg(randomized_entrances, SM64Levels.BOWSER_IN_THE_FIRE_SEA, {"Dire, Dire Docks"}, swapdict, world)
# Guarantee COTMC is not mapped to HMC, cuz thats impossible. If BitFS -> HMC, also no COTMC -> DDD.
@ -43,27 +66,34 @@ def set_rules(world, player: int, area_connections: dict):
# Cast to int to not rely on availability of SM64Levels enum. Will cause crash in MultiServer otherwise
area_connections.update({int(entrance_lvl): int(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()}
rf = RuleFactory(world, player, move_rando_bitvec)
connect_regions(world, player, "Menu", randomized_entrances_s["Bob-omb Battlefield"])
connect_regions(world, player, "Menu", randomized_entrances_s["Whomp's Fortress"], lambda state: state.has("Power Star", player, 1))
connect_regions(world, player, "Menu", randomized_entrances_s["Jolly Roger Bay"], lambda state: state.has("Power Star", player, 3))
connect_regions(world, player, "Menu", randomized_entrances_s["Cool, Cool Mountain"], lambda state: state.has("Power Star", player, 3))
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", randomized_entrances_s["The Princess's Secret Slide"], lambda state: state.has("Power Star", player, 1))
connect_regions(world, player, "Menu", randomized_entrances_s["The Secret Aquarium"], lambda state: state.has("Power Star", player, 3))
connect_regions(world, player, randomized_entrances_s["Jolly Roger Bay"], randomized_entrances_s["The Secret Aquarium"],
rf.build_rule("SF/BF | TJ & LG | MOVELESS & TJ"))
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", randomized_entrances_s["Bowser in the Dark World"], lambda state: state.has("Power Star", player, world.FirstBowserStarDoorCost[player].value))
connect_regions(world, player, "Menu", randomized_entrances_s["Bowser in the Dark World"],
lambda state: state.has("Power Star", player, star_costs["FirstBowserDoorCost"]))
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", randomized_entrances_s["Hazy Maze Cave"])
connect_regions(world, player, "Basement", randomized_entrances_s["Lethal Lava Land"])
connect_regions(world, player, "Basement", randomized_entrances_s["Shifting Sand Land"])
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, "Basement", randomized_entrances_s["Dire, Dire Docks"],
lambda state: state.has("Power Star", player, star_costs["BasementDoorCost"]))
connect_regions(world, player, "Hazy Maze Cave", randomized_entrances_s["Cavern of the Metal Cap"])
connect_regions(world, player, "Basement", randomized_entrances_s["Vanish Cap under the Moat"])
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))
connect_regions(world, player, "Basement", randomized_entrances_s["Vanish Cap under the Moat"],
rf.build_rule("GP"))
connect_regions(world, player, "Basement", randomized_entrances_s["Bowser in the Fire Sea"],
lambda state: state.has("Power Star", player, star_costs["BasementDoorCost"]) and
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))
@ -72,66 +102,127 @@ def set_rules(world, player: int, area_connections: dict):
connect_regions(world, player, "Second Floor", randomized_entrances_s["Tall, Tall Mountain"])
connect_regions(world, player, "Second Floor", randomized_entrances_s["Tiny-Huge Island (Tiny)"])
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, "Tiny-Huge Island (Tiny)", "Tiny-Huge Island")
connect_regions(world, player, "Tiny-Huge Island (Huge)", "Tiny-Huge Island")
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, star_costs["SecondFloorDoorCost"]))
connect_regions(world, player, "Third Floor", randomized_entrances_s["Tick Tock Clock"])
connect_regions(world, player, "Third Floor", randomized_entrances_s["Rainbow Ride"])
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))
connect_regions(world, player, "Third Floor", "Bowser in the Sky", lambda state: state.has("Power Star", player, star_costs["StarsToFinish"]))
#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("BBH: Eye to Eye in the Secret Room", player), lambda state: state.has("Vanish Cap", player))
add_rule(world.get_location("DDD: Collect the Caps...", player), lambda state: state.has("Vanish Cap", player))
add_rule(world.get_location("DDD: Pole-Jumping for Red Coins", player), lambda state: state.can_reach("Bowser in the Fire Sea", 'Region', player))
# Course Rules
# Bob-omb Battlefield
rf.assign_rule("BoB: Island", "CANN | CANNLESS & WC & TJ | CAPLESS & CANNLESS & LJ")
rf.assign_rule("BoB: Mario Wings to the Sky", "CANN & WC | CAPLESS & CANN")
rf.assign_rule("BoB: Behind Chain Chomp's Gate", "GP | MOVELESS")
# Whomp's Fortress
rf.assign_rule("WF: Tower", "{{WF: Chip Off Whomp's Block}}")
rf.assign_rule("WF: Chip Off Whomp's Block", "GP")
rf.assign_rule("WF: Shoot into the Wild Blue", "WK & TJ/SF | CANN")
rf.assign_rule("WF: Fall onto the Caged Island", "CL & {WF: Tower} | MOVELESS & TJ | MOVELESS & LJ | MOVELESS & CANN")
rf.assign_rule("WF: Blast Away the Wall", "CANN | CANNLESS & LG")
# Jolly Roger Bay
rf.assign_rule("JRB: Upper", "TJ/BF/SF/WK | MOVELESS & LG")
rf.assign_rule("JRB: Red Coins on the Ship Afloat", "CL/CANN/TJ/BF/WK")
rf.assign_rule("JRB: Blast to the Stone Pillar", "CANN+CL | CANNLESS & MOVELESS | CANN & MOVELESS")
rf.assign_rule("JRB: Through the Jet Stream", "MC | CAPLESS")
# Cool, Cool Mountain
rf.assign_rule("CCM: Wall Kicks Will Work", "TJ/WK & CANN | CANNLESS & TJ/WK | MOVELESS")
# Big Boo's Haunt
rf.assign_rule("BBH: Third Floor", "WK+LG | MOVELESS & WK")
rf.assign_rule("BBH: Roof", "LJ | MOVELESS")
rf.assign_rule("BBH: Secret of the Haunted Books", "KK | MOVELESS")
rf.assign_rule("BBH: Seek the 8 Red Coins", "BF/WK/TJ/SF")
rf.assign_rule("BBH: Eye to Eye in the Secret Room", "VC")
# Haze Maze Cave
rf.assign_rule("HMC: Red Coin Area", "CL & WK/LG/BF/SF/TJ | MOVELESS & WK")
rf.assign_rule("HMC: Pit Islands", "TJ+CL | MOVELESS & WK & TJ/LJ | MOVELESS & WK+SF+LG")
rf.assign_rule("HMC: Metal-Head Mario Can Move!", "LJ+MC | CAPLESS & LJ+TJ | CAPLESS & MOVELESS & LJ/TJ/WK")
rf.assign_rule("HMC: Navigating the Toxic Maze", "WK/SF/BF/TJ")
rf.assign_rule("HMC: Watch for Rolling Rocks", "WK")
# Lethal Lava Land
rf.assign_rule("LLL: Upper Volcano", "CL")
# Shifting Sand Land
rf.assign_rule("SSL: Upper Pyramid", "CL & TJ/BF/SF/LG | MOVELESS")
rf.assign_rule("SSL: Free Flying for 8 Red Coins", "TJ/SF/BF & TJ+WC | TJ/SF/BF & CAPLESS | MOVELESS")
# Dire, Dire Docks
rf.assign_rule("DDD: Moving Poles", "CL & {{Bowser in the Fire Sea Key}} | TJ+DV+LG+WK & MOVELESS")
rf.assign_rule("DDD: Through the Jet Stream", "MC | CAPLESS")
rf.assign_rule("DDD: Collect the Caps...", "VC+MC | CAPLESS & VC")
# Snowman's Land
rf.assign_rule("SL: Snowman's Big Head", "BF/SF/CANN/TJ")
rf.assign_rule("SL: In the Deep Freeze", "WK/SF/LG/BF/CANN/TJ")
rf.assign_rule("SL: Into the Igloo", "VC & TJ/SF/BF/WK/LG | MOVELESS & VC")
# Wet-Dry World
rf.assign_rule("WDW: Top", "WK/TJ/SF/BF | MOVELESS")
rf.assign_rule("WDW: Downtown", "NAR & LG & TJ/SF/BF | CANN | MOVELESS & TJ+DV")
rf.assign_rule("WDW: Go to Town for Red Coins", "WK | MOVELESS & TJ")
rf.assign_rule("WDW: Quick Race Through Downtown!", "VC & WK/BF | VC & TJ+LG | MOVELESS & VC & TJ")
rf.assign_rule("WDW: Bob-omb Buddy", "TJ | SF+LG | NAR & BF/SF")
# Tall, Tall Mountain
rf.assign_rule("TTM: Top", "MOVELESS & TJ | LJ/DV & LG/KK | MOVELESS & WK & SF/LG | MOVELESS & KK/DV")
rf.assign_rule("TTM: Blast to the Lonely Mushroom", "CANN | CANNLESS & LJ | MOVELESS & CANNLESS")
# Tiny-Huge Island
rf.assign_rule("THI: Pipes", "NAR | LJ/TJ/DV/LG | MOVELESS & BF/SF/KK")
rf.assign_rule("THI: Large Top", "NAR | LJ/TJ/DV | MOVELESS")
rf.assign_rule("THI: Wiggler's Red Coins", "WK")
rf.assign_rule("THI: Make Wiggler Squirm", "GP | MOVELESS & DV")
# Tick Tock Clock
rf.assign_rule("TTC: Lower", "LG/TJ/SF/BF/WK")
rf.assign_rule("TTC: Upper", "CL | SF+WK")
rf.assign_rule("TTC: Top", "CL | SF+WK")
rf.assign_rule("TTC: Stomp on the Thwomp", "LG & TJ/SF/BF")
rf.assign_rule("TTC: Stop Time for Red Coins", "NAR | {TTC: Lower}")
# Rainbow Ride
rf.assign_rule("RR: Maze", "WK | LJ & SF/BF/TJ | MOVELESS & LG/TJ")
rf.assign_rule("RR: Bob-omb Buddy", "WK | MOVELESS & LG")
rf.assign_rule("RR: Swingin' in the Breeze", "LG/TJ/BF/SF")
rf.assign_rule("RR: Tricky Triangles!", "LG/TJ/BF/SF")
rf.assign_rule("RR: Cruiser", "WK/SF/BF/LG/TJ")
rf.assign_rule("RR: House", "TJ/SF/BF/LG")
rf.assign_rule("RR: Somewhere Over the Rainbow", "CANN")
# Cavern of the Metal Cap
rf.assign_rule("Cavern of the Metal Cap Red Coins", "MC | CAPLESS")
# Vanish Cap Under the Moat
rf.assign_rule("Vanish Cap Under the Moat Switch", "WK/TJ/BF/SF/LG | MOVELESS")
rf.assign_rule("Vanish Cap Under the Moat Red Coins", "TJ/BF/SF/LG/WK & VC | CAPLESS & WK")
# Bowser in the Fire Sea
rf.assign_rule("BitFS: Upper", "CL")
rf.assign_rule("Bowser in the Fire Sea Red Coins", "LG/WK")
rf.assign_rule("Bowser in the Fire Sea 1Up Block Near Poles", "LG/WK")
# Wing Mario Over the Rainbow
rf.assign_rule("Wing Mario Over the Rainbow Red Coins", "TJ+WC")
rf.assign_rule("Wing Mario Over the Rainbow 1Up Block", "TJ+WC")
# Bowser in the Sky
rf.assign_rule("BitS: Top", "CL+TJ | CL+SF+LG | MOVELESS & TJ+WK+LG")
# 100 Coin Stars
if world.EnableCoinStars[player]:
add_rule(world.get_location("DDD: 100 Coins", player), lambda state: state.can_reach("Bowser in the Fire Sea", 'Region', player))
add_rule(world.get_location("SL: Into the Igloo", player), lambda state: state.has("Vanish Cap", player))
add_rule(world.get_location("WDW: Quick Race Through Downtown!", player), lambda state: state.has("Vanish Cap", player))
add_rule(world.get_location("RR: Somewhere Over the Rainbow", player), lambda state: state.has("Cannon Unlock RR", player))
if world.AreaRandomizer[player] or world.StrictCannonRequirements[player]:
# If area rando is on, it may not be possible to modify WDW's starting water level,
# which would make it impossible to reach downtown area without the cannon.
add_rule(world.get_location("WDW: Quick Race Through Downtown!", player), lambda state: state.has("Cannon Unlock WDW", player))
add_rule(world.get_location("WDW: Go to Town for Red Coins", player), lambda state: state.has("Cannon Unlock WDW", player))
add_rule(world.get_location("WDW: 1Up Block in Downtown", player), lambda state: state.has("Cannon Unlock WDW", player))
if world.StrictCapRequirements[player]:
add_rule(world.get_location("BoB: Mario Wings to the Sky", player), lambda state: state.has("Wing Cap", player))
add_rule(world.get_location("HMC: Metal-Head Mario Can Move!", player), lambda state: state.has("Metal Cap", player))
add_rule(world.get_location("JRB: Through the Jet Stream", player), lambda state: state.has("Metal Cap", player))
add_rule(world.get_location("SSL: Free Flying for 8 Red Coins", player), lambda state: state.has("Wing Cap", player))
add_rule(world.get_location("DDD: Through the Jet Stream", player), lambda state: state.has("Metal Cap", player))
add_rule(world.get_location("DDD: Collect the Caps...", player), lambda state: state.has("Metal Cap", player))
add_rule(world.get_location("Vanish Cap Under the Moat Red Coins", player), lambda state: state.has("Vanish Cap", player))
add_rule(world.get_location("Cavern of the Metal Cap Red Coins", player), lambda state: state.has("Metal Cap", player))
if world.StrictCannonRequirements[player]:
add_rule(world.get_location("WF: Blast Away the Wall", player), lambda state: state.has("Cannon Unlock WF", player))
add_rule(world.get_location("JRB: Blast to the Stone Pillar", player), lambda state: state.has("Cannon Unlock JRB", player))
add_rule(world.get_location("CCM: Wall Kicks Will Work", player), lambda state: state.has("Cannon Unlock CCM", player))
add_rule(world.get_location("TTM: Blast to the Lonely Mushroom", player), lambda state: state.has("Cannon Unlock TTM", player))
if world.StrictCapRequirements[player] and world.StrictCannonRequirements[player]:
# Ability to reach the floating island. Need some of those coins to get 100 coin star as well.
add_rule(world.get_location("BoB: Find the 8 Red Coins", player), lambda state: state.has("Cannon Unlock BoB", player) or state.has("Wing Cap", player))
add_rule(world.get_location("BoB: Shoot to the Island in the Sky", player), lambda state: state.has("Cannon Unlock BoB", player) or state.has("Wing Cap", player))
if world.EnableCoinStars[player]:
add_rule(world.get_location("BoB: 100 Coins", player), lambda state: state.has("Cannon Unlock BoB", player) or state.has("Wing Cap", player))
#Rules for Secret Stars
add_rule(world.get_location("Wing Mario Over the Rainbow Red Coins", player), lambda state: state.has("Wing Cap", player))
add_rule(world.get_location("Wing Mario Over the Rainbow 1Up Block", player), lambda state: state.has("Wing Cap", player))
rf.assign_rule("BoB: 100 Coins", "CANN & WC | CANNLESS & WC & TJ")
rf.assign_rule("WF: 100 Coins", "GP | MOVELESS")
rf.assign_rule("JRB: 100 Coins", "GP & {JRB: Upper}")
rf.assign_rule("HMC: 100 Coins", "GP")
rf.assign_rule("SSL: 100 Coins", "{SSL: Upper Pyramid} | GP")
rf.assign_rule("DDD: 100 Coins", "GP")
rf.assign_rule("SL: 100 Coins", "VC | MOVELESS")
rf.assign_rule("WDW: 100 Coins", "GP | {WDW: Downtown}")
rf.assign_rule("TTC: 100 Coins", "GP")
rf.assign_rule("THI: 100 Coins", "GP")
rf.assign_rule("RR: 100 Coins", "GP & WK")
# Castle Stars
add_rule(world.get_location("Toad (Basement)", player), lambda state: state.can_reach("Basement", 'Region', player) and state.has("Power Star", player, 12))
add_rule(world.get_location("Toad (Second Floor)", player), lambda state: state.can_reach("Second Floor", 'Region', player) and state.has("Power Star", player, 25))
add_rule(world.get_location("Toad (Third Floor)", player), lambda state: state.can_reach("Third Floor", 'Region', player) and state.has("Power Star", player, 35))
if world.MIPS1Cost[player].value > world.MIPS2Cost[player].value:
(world.MIPS2Cost[player].value, world.MIPS1Cost[player].value) = (world.MIPS1Cost[player].value, world.MIPS2Cost[player].value)
add_rule(world.get_location("MIPS 1", player), lambda state: state.can_reach("Basement", 'Region', player) and state.has("Power Star", player, world.MIPS1Cost[player].value))
add_rule(world.get_location("MIPS 2", player), lambda state: state.can_reach("Basement", 'Region', player) and state.has("Power Star", player, world.MIPS2Cost[player].value))
if star_costs["MIPS1Cost"] > star_costs["MIPS2Cost"]:
(star_costs["MIPS2Cost"], star_costs["MIPS1Cost"]) = (star_costs["MIPS1Cost"], star_costs["MIPS2Cost"])
rf.assign_rule("MIPS 1", "DV | MOVELESS")
rf.assign_rule("MIPS 2", "DV | MOVELESS")
add_rule(world.get_location("MIPS 1", player), lambda state: state.can_reach("Basement", 'Region', player) and state.has("Power Star", player, star_costs["MIPS1Cost"]))
add_rule(world.get_location("MIPS 2", player), lambda state: state.can_reach("Basement", 'Region', player) and state.has("Power Star", player, star_costs["MIPS2Cost"]))
world.completion_condition[player] = lambda state: state.can_reach("BitS: Top", 'Region', player)
if world.CompletionType[player] == "last_bowser_stage":
world.completion_condition[player] = lambda state: state.can_reach("Bowser in the Sky", 'Region', player)
@ -139,3 +230,145 @@ def set_rules(world, player: int, area_connections: dict):
world.completion_condition[player] = lambda state: state.can_reach("Bowser in the Dark World", 'Region', player) and \
state.can_reach("Bowser in the Fire Sea", 'Region', player) and \
state.can_reach("Bowser in the Sky", 'Region', player)
class RuleFactory:
world: MultiWorld
player: int
move_rando_bitvec: bool
area_randomizer: bool
capless: bool
cannonless: bool
moveless: bool
token_table = {
"TJ": "Triple Jump",
"LJ": "Long Jump",
"BF": "Backflip",
"SF": "Side Flip",
"WK": "Wall Kick",
"DV": "Dive",
"GP": "Ground Pound",
"KK": "Kick",
"CL": "Climb",
"LG": "Ledge Grab",
"WC": "Wing Cap",
"MC": "Metal Cap",
"VC": "Vanish Cap"
}
class SM64LogicException(Exception):
pass
def __init__(self, world, player, move_rando_bitvec):
self.world = world
self.player = player
self.move_rando_bitvec = move_rando_bitvec
self.area_randomizer = world.AreaRandomizer[player].value > 0
self.capless = not world.StrictCapRequirements[player]
self.cannonless = not world.StrictCannonRequirements[player]
self.moveless = not world.StrictMoveRequirements[player] or not move_rando_bitvec > 0
def assign_rule(self, target_name: str, rule_expr: str):
target = self.world.get_location(target_name, self.player) if target_name in location_table else self.world.get_entrance(target_name, self.player)
cannon_name = "Cannon Unlock " + target_name.split(':')[0]
try:
rule = self.build_rule(rule_expr, cannon_name)
except RuleFactory.SM64LogicException as exception:
raise RuleFactory.SM64LogicException(
f"Error generating rule for {target_name} using rule expression {rule_expr}: {exception}")
if rule:
set_rule(target, rule)
def build_rule(self, rule_expr: str, cannon_name: str = '') -> Callable:
expressions = rule_expr.split(" | ")
rules = []
for expression in expressions:
or_clause = self.combine_and_clauses(expression, cannon_name)
if or_clause is True:
return None
if or_clause is not False:
rules.append(or_clause)
if rules:
if len(rules) == 1:
return rules[0]
else:
return lambda state: any(rule(state) for rule in rules)
else:
return None
def combine_and_clauses(self, rule_expr: str, cannon_name: str) -> Union[Callable, bool]:
expressions = rule_expr.split(" & ")
rules = []
for expression in expressions:
and_clause = self.make_lambda(expression, cannon_name)
if and_clause is False:
return False
if and_clause is not True:
rules.append(and_clause)
if rules:
if len(rules) == 1:
return rules[0]
return lambda state: all(rule(state) for rule in rules)
else:
return True
def make_lambda(self, expression: str, cannon_name: str) -> Union[Callable, bool]:
if '+' in expression:
tokens = expression.split('+')
items = set()
for token in tokens:
item = self.parse_token(token, cannon_name)
if item is True:
continue
if item is False:
return False
items.add(item)
if items:
return lambda state: state.has_all(items, self.player)
else:
return True
if '/' in expression:
tokens = expression.split('/')
items = set()
for token in tokens:
item = self.parse_token(token, cannon_name)
if item is True:
return True
if item is False:
continue
items.add(item)
if items:
return lambda state: state.has_any(items, self.player)
else:
return False
if '{{' in expression:
return lambda state: state.can_reach(expression[2:-2], "Location", self.player)
if '{' in expression:
return lambda state: state.can_reach(expression[1:-1], "Region", self.player)
item = self.parse_token(expression, cannon_name)
if item in (True, False):
return item
return lambda state: state.has(item, self.player)
def parse_token(self, token: str, cannon_name: str) -> Union[str, bool]:
if token == "CANN":
return cannon_name
if token == "CAPLESS":
return self.capless
if token == "CANNLESS":
return self.cannonless
if token == "MOVELESS":
return self.moveless
if token == "NAR":
return not self.area_randomizer
item = self.token_table.get(token, None)
if not item:
raise Exception(f"Invalid token: '{item}'")
if item in action_item_table:
if self.move_rando_bitvec & (1 << (action_item_table[item] - action_item_table['Double Jump'])) == 0:
# This action item is not randomized.
return True
return item

View File

@ -1,7 +1,7 @@
import typing
import os
import json
from .Items import item_table, cannon_item_table, SM64Item
from .Items import item_table, action_item_table, cannon_item_table, SM64Item
from .Locations import location_table, SM64Location
from .Options import sm64_options
from .Rules import set_rules
@ -35,14 +35,44 @@ class SM64World(World):
item_name_to_id = item_table
location_name_to_id = location_table
data_version = 8
data_version = 9
required_client_version = (0, 3, 5)
area_connections: typing.Dict[int, int]
option_definitions = sm64_options
number_of_stars: int
move_rando_bitvec: int
filler_count: int
star_costs: typing.Dict[str, int]
def generate_early(self):
max_stars = 120
if (not self.multiworld.EnableCoinStars[self.player].value):
max_stars -= 15
self.move_rando_bitvec = 0
for action, itemid in action_item_table.items():
# HACK: Disable randomization of double jump
if action == 'Double Jump': continue
if getattr(self.multiworld, f"MoveRandomizer{action.replace(' ','')}")[self.player].value:
max_stars -= 1
self.move_rando_bitvec |= (1 << (itemid - action_item_table['Double Jump']))
if (self.multiworld.ExclamationBoxes[self.player].value > 0):
max_stars += 29
self.number_of_stars = min(self.multiworld.AmountOfStars[self.player].value, max_stars)
self.filler_count = max_stars - self.number_of_stars
self.star_costs = {
'FirstBowserDoorCost': round(self.multiworld.FirstBowserStarDoorCost[self.player].value * self.number_of_stars / 100),
'BasementDoorCost': round(self.multiworld.BasementStarDoorCost[self.player].value * self.number_of_stars / 100),
'SecondFloorDoorCost': round(self.multiworld.SecondFloorStarDoorCost[self.player].value * self.number_of_stars / 100),
'MIPS1Cost': round(self.multiworld.MIPS1Cost[self.player].value * self.number_of_stars / 100),
'MIPS2Cost': round(self.multiworld.MIPS2Cost[self.player].value * self.number_of_stars / 100),
'StarsToFinish': round(self.multiworld.StarsToFinish[self.player].value * self.number_of_stars / 100)
}
# Nudge MIPS 1 to match vanilla on default percentage
if self.number_of_stars == 120 and self.multiworld.MIPS1Cost[self.player].value == 12:
self.star_costs['MIPS1Cost'] = 15
self.topology_present = self.multiworld.AreaRandomizer[self.player].value
def create_regions(self):
@ -50,7 +80,7 @@ class SM64World(World):
def set_rules(self):
self.area_connections = {}
set_rules(self.multiworld, self.player, self.area_connections)
set_rules(self.multiworld, self.player, self.area_connections, self.star_costs, self.move_rando_bitvec)
if self.topology_present:
# Write area_connections to spoiler log
for entrance, destination in self.area_connections.items():
@ -72,31 +102,29 @@ class SM64World(World):
return item
def create_items(self):
starcount = self.multiworld.AmountOfStars[self.player].value
if (not self.multiworld.EnableCoinStars[self.player].value):
starcount = max(35,self.multiworld.AmountOfStars[self.player].value-15)
starcount = max(starcount, self.multiworld.FirstBowserStarDoorCost[self.player].value,
self.multiworld.BasementStarDoorCost[self.player].value, self.multiworld.SecondFloorStarDoorCost[self.player].value,
self.multiworld.MIPS1Cost[self.player].value, self.multiworld.MIPS2Cost[self.player].value,
self.multiworld.StarsToFinish[self.player].value)
self.multiworld.itempool += [self.create_item("Power Star") for i in range(0,starcount)]
self.multiworld.itempool += [self.create_item("1Up Mushroom") for i in range(starcount,120 - (15 if not self.multiworld.EnableCoinStars[self.player].value else 0))]
# 1Up Mushrooms
self.multiworld.itempool += [self.create_item("1Up Mushroom") for i in range(0,self.filler_count)]
# Power Stars
self.multiworld.itempool += [self.create_item("Power Star") for i in range(0,self.number_of_stars)]
# Keys
if (not self.multiworld.ProgressiveKeys[self.player].value):
key1 = self.create_item("Basement Key")
key2 = self.create_item("Second Floor Key")
self.multiworld.itempool += [key1, key2]
else:
self.multiworld.itempool += [self.create_item("Progressive Key") for i in range(0,2)]
wingcap = self.create_item("Wing Cap")
metalcap = self.create_item("Metal Cap")
vanishcap = self.create_item("Vanish Cap")
self.multiworld.itempool += [wingcap, metalcap, vanishcap]
# Caps
self.multiworld.itempool += [self.create_item(cap_name) for cap_name in ["Wing Cap", "Metal Cap", "Vanish Cap"]]
# Cannons
if (self.multiworld.BuddyChecks[self.player].value):
self.multiworld.itempool += [self.create_item(name) for name, id in cannon_item_table.items()]
else:
# Moves
self.multiworld.itempool += [self.create_item(action)
for action, itemid in action_item_table.items()
if self.move_rando_bitvec & (1 << itemid - action_item_table['Double Jump'])]
def generate_basic(self):
if not (self.multiworld.BuddyChecks[self.player].value):
self.multiworld.get_location("BoB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock BoB"))
self.multiworld.get_location("WF: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock WF"))
self.multiworld.get_location("JRB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock JRB"))
@ -108,9 +136,7 @@ class SM64World(World):
self.multiworld.get_location("THI: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock THI"))
self.multiworld.get_location("RR: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock RR"))
if (self.multiworld.ExclamationBoxes[self.player].value > 0):
self.multiworld.itempool += [self.create_item("1Up Mushroom") for i in range(0,29)]
else:
if (self.multiworld.ExclamationBoxes[self.player].value == 0):
self.multiworld.get_location("CCM: 1Up Block Near Snowman", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.multiworld.get_location("CCM: 1Up Block Ice Pillar", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.multiworld.get_location("CCM: 1Up Block Secret Slide", self.player).place_locked_item(self.create_item("1Up Mushroom"))
@ -147,14 +173,10 @@ class SM64World(World):
def fill_slot_data(self):
return {
"AreaRando": self.area_connections,
"FirstBowserDoorCost": self.multiworld.FirstBowserStarDoorCost[self.player].value,
"BasementDoorCost": self.multiworld.BasementStarDoorCost[self.player].value,
"SecondFloorDoorCost": self.multiworld.SecondFloorStarDoorCost[self.player].value,
"MIPS1Cost": self.multiworld.MIPS1Cost[self.player].value,
"MIPS2Cost": self.multiworld.MIPS2Cost[self.player].value,
"StarsToFinish": self.multiworld.StarsToFinish[self.player].value,
"MoveRandoVec": self.move_rando_bitvec,
"DeathLink": self.multiworld.death_link[self.player].value,
"CompletionType" : self.multiworld.CompletionType[self.player].value,
"CompletionType": self.multiworld.CompletionType[self.player].value,
**self.star_costs
}
def generate_output(self, output_directory: str):