Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SM64 - Goal Logic and Hint Bugfixes #2886

Merged
merged 12 commits into from
Mar 20, 2024
57 changes: 39 additions & 18 deletions worlds/sm64ex/Regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ class SM64Levels(int, Enum):
BOWSER_IN_THE_FIRE_SEA = 191
WING_MARIO_OVER_THE_RAINBOW = 311


class SM64Region(Region):
subregions: typing.List[Region] = []


# sm64paintings is a dict of entrances, format LEVEL | AREA
sm64_level_to_paintings: typing.Dict[SM64Levels, str] = {
SM64Levels.BOB_OMB_BATTLEFIELD: "Bob-omb Battlefield",
Expand Down Expand Up @@ -81,21 +86,24 @@ def create_regions(world: MultiWorld, player: int):
regBoB = create_region("Bob-omb Battlefield", player, world)
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")
bob_island = create_subregion(regBoB, "BoB: Island", "BoB: Shoot to the Island in the Sky", "BoB: Find the 8 Red Coins")
regBoB.subregions = [bob_island]
if (world.EnableCoinStars[player].value):
create_locs(regBoB, "BoB: 100 Coins")

regWhomp = create_region("Whomp's Fortress", player, world)
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")
wf_tower = create_subregion(regWhomp, "WF: Tower", "WF: To the Top of the Fortress", "WF: Bob-omb Buddy")
regWhomp.subregions = [wf_tower]
if (world.EnableCoinStars[player].value):
create_locs(regWhomp, "WF: 100 Coins")

regJRB = create_region("Jolly Roger Bay", player, world)
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")
regJRB.subregions = [jrb_upper]
if (world.EnableCoinStars[player].value):
create_locs(jrb_upper, "JRB: 100 Coins")

Expand All @@ -108,7 +116,8 @@ def create_regions(world: MultiWorld, player: int):
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")
bbh_roof = create_subregion(bbh_third_floor, "BBH: Roof", "BBH: Big Boo's Balcony", "BBH: 1Up Block Top of Mansion")
regBBH.subregions = [bbh_third_floor, bbh_roof]
if (world.EnableCoinStars[player].value):
create_locs(regBBH, "BBH: 100 Coins")

Expand All @@ -130,29 +139,34 @@ def create_regions(world: MultiWorld, player: int):
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")
hmc_pit_islands = create_subregion(regHMC, "HMC: Pit Islands", "HMC: A-Maze-Ing Emergency Exit", "HMC: 1Up Block above Pit")
regHMC.subregions = [hmc_red_coin_area, hmc_pit_islands]
if (world.EnableCoinStars[player].value):
create_locs(hmc_red_coin_area, "HMC: 100 Coins")

regLLL = create_region("Lethal Lava Land", player, world)
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")
lll_upper_volcano = create_subregion(regLLL, "LLL: Upper Volcano", "LLL: Hot-Foot-It into the Volcano", "LLL: Elevator Tour in the Volcano")
regLLL.subregions = [lll_upper_volcano]
if (world.EnableCoinStars[player].value):
create_locs(regLLL, "LLL: 100 Coins")

regSSL = create_region("Shifting Sand Land", player, world)
create_locs(regSSL, "SSL: In the Talons of the Big Bird", "SSL: Shining Atop the Pyramid", "SSL: Inside the Ancient Pyramid",
create_locs(regSSL, "SSL: In the Talons of the Big Bird", "SSL: Shining Atop the 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")
ssl_upper_pyramid = create_subregion(regSSL, "SSL: Upper Pyramid", "SSL: Inside the Ancient Pyramid",
"SSL: Stand Tall on the Four Pillars", "SSL: Pyramid Puzzle")
regSSL.subregions = [ssl_upper_pyramid]
if (world.EnableCoinStars[player].value):
create_locs(regSSL, "SSL: 100 Coins")

regDDD = create_region("Dire, Dire Docks", player, world)
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")
regDDD.subregions = [ddd_moving_poles]
if (world.EnableCoinStars[player].value):
create_locs(ddd_moving_poles, "DDD: 100 Coins")

Expand All @@ -163,7 +177,8 @@ def create_regions(world: MultiWorld, player: int):
create_default_locs(regVCutM, locVCutM_table)

regBitFS = create_region("Bowser in the Fire Sea", player, world)
create_subregion(regBitFS, "BitFS: Upper", *locBitFS_table.keys())
bitfs_upper = create_subregion(regBitFS, "BitFS: Upper", *locBitFS_table.keys())
regBitFS.subregions = [bitfs_upper]

create_region("Second Floor", player, world)

Expand All @@ -176,7 +191,8 @@ def create_regions(world: MultiWorld, player: int):
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")
wdw_downtown = create_subregion(regWDW, "WDW: Downtown", "WDW: Go to Town for Red Coins", "WDW: Quick Race Through Downtown!", "WDW: 1Up Block in Downtown")
regWDW.subregions = [wdw_top, wdw_downtown]
if (world.EnableCoinStars[player].value):
create_locs(wdw_top, "WDW: 100 Coins")

Expand All @@ -185,17 +201,19 @@ def create_regions(world: MultiWorld, player: int):
"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")
regTTM.subregions = [ttm_middle, ttm_top]
if (world.EnableCoinStars[player].value):
create_locs(ttm_top, "TTM: 100 Coins")

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",
create_locs(regTHI, "THI: 1Up Block THI Small near Start")
thi_pipes = create_subregion(regTHI, "THI: Pipes", "THI: The Tip Top of the Huge Island", "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")
regTHI.subregions = [thi_pipes, thi_large_top]
if (world.EnableCoinStars[player].value):
create_locs(thi_large_top, "THI: 100 Coins")

Expand All @@ -206,15 +224,17 @@ def create_regions(world: MultiWorld, player: int):
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")
regTTC.subregions = [ttc_lower, ttc_upper, ttc_top]
if (world.EnableCoinStars[player].value):
create_locs(ttc_top, "TTC: 100 Coins")

regRR = create_region("Rainbow Ride", player, world)
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")
rr_cruiser = create_subregion(regRR, "RR: Cruiser", "RR: Cruiser Crossing the Rainbow", "RR: Somewhere Over the Rainbow")
rr_house = create_subregion(regRR, "RR: House", "RR: The Big House in the Sky", "RR: 1Up Block On House in the Sky")
regRR.subregions = [rr_maze, rr_cruiser, rr_house]
if (world.EnableCoinStars[player].value):
create_locs(rr_maze, "RR: 100 Coins")

Expand All @@ -223,7 +243,8 @@ def create_regions(world: MultiWorld, player: int):

regBitS = create_region("Bowser in the Sky", player, world)
create_locs(regBitS, "Bowser in the Sky 1Up Block")
create_subregion(regBitS, "BitS: Top", "Bowser in the Sky Red Coins")
bits_top = create_subregion(regBitS, "BitS: Top", "Bowser in the Sky Red Coins")
regBitS.subregions = [bits_top]


def connect_regions(world: MultiWorld, player: int, source: str, target: str, rule=None):
Expand All @@ -232,14 +253,14 @@ def connect_regions(world: MultiWorld, player: int, source: str, target: str, ru
sourceRegion.connect(targetRegion, rule=rule)


def create_region(name: str, player: int, world: MultiWorld) -> Region:
region = Region(name, player, world)
def create_region(name: str, player: int, world: MultiWorld) -> SM64Region:
region = SM64Region(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)
def create_subregion(source_region: Region, name: str, *locs: str) -> SM64Region:
region = SM64Region(name, source_region.player, source_region.multiworld)
connection = Entrance(source_region.player, name, source_region)
source_region.exits.append(connection)
connection.connect(region)
Expand Down
15 changes: 8 additions & 7 deletions worlds/sm64ex/Rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move

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", randomized_entrances_s["Tick Tock Clock"], rf.build_rule("LG/TJ/SF/BF/WK"))
connect_regions(world, player, "Third Floor", randomized_entrances_s["Rainbow Ride"], rf.build_rule("TJ/SF/BF"))
connect_regions(world, player, "Third Floor", randomized_entrances_s["Wing Mario over the Rainbow"], rf.build_rule("TJ/SF/BF"))
connect_regions(world, player, "Third Floor", "Bowser in the Sky", lambda state: state.has("Power Star", player, star_costs["StarsToFinish"]))

# Course Rules
Expand Down Expand Up @@ -146,7 +146,7 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move
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")
rf.assign_rule("SSL: Free Flying for 8 Red Coins", "TJ/SF/BF & TJ+WC | TJ/SF/BF & CAPLESS | MOVELESS & CAPLESS")
# 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")
Expand All @@ -165,6 +165,7 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move
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: 1Up Block THI Small near Start", "NAR | {THI: Pipes}")
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")
Expand Down Expand Up @@ -225,11 +226,11 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move
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)
world.completion_condition[player] = lambda state: state.can_reach("BitS: Top", 'Region', player)
elif world.CompletionType[player] == "all_bowser_stages":
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)
state.can_reach("BitFS: Upper", 'Region', player) and \
state.can_reach("BitS: Top", 'Region', player)


class RuleFactory:
Expand Down
24 changes: 17 additions & 7 deletions worlds/sm64ex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from .Locations import location_table, SM64Location
from .Options import sm64_options
from .Rules import set_rules
from .Regions import create_regions, sm64_level_to_entrances
from BaseClasses import Item, Tutorial, ItemClassification
from .Regions import create_regions, sm64_level_to_entrances, SM64Levels
from BaseClasses import Item, Tutorial, ItemClassification, Region
from ..AutoWorld import World, WebWorld


Expand Down Expand Up @@ -200,11 +200,21 @@ def generate_output(self, output_directory: str):
with open(os.path.join(output_directory, filename), 'w') as f:
json.dump(data, f)

def modify_multidata(self, multidata):
def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]):
if self.topology_present:
er_hint_data = {}
for entrance, destination in self.area_connections.items():
region = self.multiworld.get_region(sm64_level_to_entrances[destination], self.player)
for location in region.locations:
er_hint_data[location.address] = sm64_level_to_entrances[entrance]
multidata['er_hint_data'][self.player] = er_hint_data
regions = [self.multiworld.get_region(sm64_level_to_entrances[destination], self.player)]
if regions[0].name == "Tiny-Huge Island (Huge)":
# Special rules for Tiny-Huge Island's dual entrances
reverse_area_connections = {destination: entrance for entrance, destination in self.area_connections.items()}
entrance_name = sm64_level_to_entrances[reverse_area_connections[SM64Levels.TINY_HUGE_ISLAND_HUGE]] \
+ ' or ' + sm64_level_to_entrances[reverse_area_connections[SM64Levels.TINY_HUGE_ISLAND_TINY]]
regions[0] = self.multiworld.get_region("Tiny-Huge Island", self.player)
else:
entrance_name = sm64_level_to_entrances[entrance]
regions += regions[0].subregions
for region in regions:
for location in region.locations:
er_hint_data[location.address] = entrance_name
hint_data[self.player] = er_hint_data
Loading