Files
dockipelago/worlds/mk64/Rules.py
Jonathan Tinney 7971961166
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
add schedule I, sonic 1/frontiers/heroes, spirit island
2026-04-02 23:46:36 -07:00

287 lines
16 KiB
Python

from typing import TYPE_CHECKING
from BaseClasses import CollectionState
from ..generic.Rules import add_rule, set_rule
from .Locations import course_locations, Group, shared_hazard_locations, cup_locations
from .Options import Opt, GameMode, ShuffleDriftAbilities
from .Items import item_name_groups
if TYPE_CHECKING:
from . import MK64World
karts = item_name_groups["Karts"]
qualify_item_score_values = {
"Triple Red Shell Power": 1,
"Blue Shell Power": 0.5,
"Lightning Power": 1,
"Star Power": 1,
"Mushroom Power": 0.5,
"Triple Mushroom Power": 1,
"Super Mushroom Power": 1
}
win_item_score_values = {
"Red Shell Power": 0.5,
"Triple Red Shell Power": 1,
"Blue Shell Power": 0.5,
"Lightning Power": 0.5,
"Star Power": 0.5,
"Mushroom Power": 0.5,
"Triple Mushroom Power": 1,
"Super Mushroom Power": 0.5
}
def item_qualify_score(state: CollectionState, player: int) -> int: # 0 to 6
thing1 = [rating if state.has(item, player) else 0 for item, rating in qualify_item_score_values.items()]
thing2 = [rating if state.has("P2 " + item, player) else 0 for item, rating in qualify_item_score_values.items()]
sum1 = sum(thing1)
sum2 = sum(thing2)
maxsum = max(sum1, sum2)
return maxsum
def item_win_score(state: CollectionState, player: int) -> int: # 0 to 5
return max(sum([rating for item, rating in win_item_score_values.items() if state.has(item, player)]),
sum([rating for item, rating in win_item_score_values.items() if state.has("P2 " + item, player)]))
def kart_drift_score(state: CollectionState, player: int, opt: Opt, kart: str) -> int:
match opt.drift:
case ShuffleDriftAbilities.option_off:
return 2
case ShuffleDriftAbilities.option_free_drift:
return 1 + state.has("Progressive Drift " + kart, player)
case ShuffleDriftAbilities.option_free_mini_turbo:
return 2 * state.has("Progressive Drift " + kart, player)
case _: # ShuffleDriftAbilities.option_on or ShuffleDriftAbilities.plentiful
return min(state.count("Progressive Drift " + kart, player), 2)
def track_score(state: CollectionState, player: int, opt: Opt) -> int: # 0 to 2
return max((kart_drift_score(state, player, opt, kart) for kart in karts if state.has(kart, player)), default=0)
def off_road_score(state: CollectionState, player: int, opt: Opt) -> int: # 0 to 3
best = 0
for kart in karts:
if not state.has(kart, player):
continue
if opt.traction:
current = state.has("Off-Road Tires " + kart, player)
else:
current = 1
current += kart_drift_score(state, player, opt, kart)
best = max(best, current)
return best
def winter_score(state: CollectionState, player: int, opt: Opt) -> int: # 0 to 3
best = 0
for kart in karts:
if not state.has(kart, player):
continue
if (not opt.traction) or state.has("Winter Tires " + kart, player):
current = 2
elif state.has("Off-Road Tires " + kart, player):
current = 1
else:
current = 0
current += kart_drift_score(state, player, opt, kart)
best = max(best, current)
return best
def fence_score(state: CollectionState, player: int, opt: Opt) -> int: # 0 to 8 # TODO: Check for feather item boxes on course
if not opt.fences:
return 8
switch_ratings = [0, 4, 6, 7, 8]
feather_ratings = [2, 1, 0, 0, 0]
switch_count = state.count_group_unique("Switches", player)
return (switch_ratings[switch_count]
+ (state.has_any({"Feather Power", "P2 Feather Power"}, player) and feather_ratings[switch_count]))
def score_track_qualify(state: CollectionState, player: int, opt: Opt) -> int: # 0 to 16
return fence_score(state, player, opt) + track_score(state, player, opt) + item_qualify_score(state, player)
def score_off_road_qualify(state: CollectionState, player: int, opt: Opt) -> int: # 0 to 17
return fence_score(state, player, opt) + off_road_score(state, player, opt) + item_qualify_score(state, player)
def score_winter_qualify(state: CollectionState, player: int, opt: Opt) -> int: # 0 to 18
return fence_score(state, player, opt) + winter_score(state, player, opt) + item_qualify_score(state, player)
def score_track_win(state: CollectionState, player: int, opt: Opt) -> int: # 0 to 15
return fence_score(state, player, opt) + track_score(state, player, opt) + item_win_score(state, player)
def score_off_road_win(state: CollectionState, player: int, opt: Opt) -> int: # 0 to 16
return fence_score(state, player, opt) + off_road_score(state, player, opt) + item_win_score(state, player)
def score_winter_win(state: CollectionState, player: int, opt: Opt) -> int: # 0 to 17
return fence_score(state, player, opt) + winter_score(state, player, opt) + item_win_score(state, player)
course_qualify_rules = [ # TODO: Refactor with coupling among score types after more play testing & timing
# lambda: score threshhold <= fence_score + terrain score + item power score + optional railings score
lambda state, player, ease, opt: True, # Luigi Raceway
lambda state, player, ease, opt: ease - 1 <= score_track_qualify(state, player, opt), # Moo Moo Farm
lambda state, player, ease, opt: ease + 3 <= score_track_qualify(state, player, opt), # Koopa Troopa Beach
lambda state, player, ease, opt: (ease - 1 <= score_off_road_qualify(state, player, opt)) # Kalimari Desert
or (state.has("Yellow Switch", player)
and state.has_any({"Star Power", "P2 Star Power"}, player)),
lambda state, player, ease, opt: ease - 1 <= score_track_qualify(state, player, opt), # Toad's Turnpike
lambda state, player, ease, opt: ease + 4 <= score_winter_qualify(state, player, opt), # Frappe Snowland
lambda state, player, ease, opt: ease + 0 <= score_off_road_qualify(state, player, opt), # Choco Mountain
lambda state, player, ease, opt: ease + 0 <= score_off_road_qualify(state, player, opt), # Mario Raceway
lambda state, player, ease, opt: ease + 2 <= score_off_road_qualify(state, player, opt), # Wario Stadium
lambda state, player, ease, opt: ease + 2 <= score_winter_qualify(state, player, opt), # Sherbet Land
lambda state, player, ease, opt: ease + 3 <= score_off_road_qualify(state, player, opt), # Royal Raceway
lambda state, player, ease, opt: ease + 3 <= score_off_road_qualify(state, player, opt), # Bowser's Castle
lambda state, player, ease, opt: ease + 2 <= (score_off_road_qualify(state, player, opt) # D.K.'s Jungle Parkway
+ state.has("Railings D.K.'s Jungle Parkway", player)),
lambda state, player, ease, opt: ease + 5 <= (score_off_road_qualify(state, player, opt) # Yoshi Valley
+ state.has("Railings Yoshi Valley Main Track", player)
+ state.has("Railings Yoshi Valley Maze", player)),
lambda state, player, ease, opt: ease + 3 <= (score_track_qualify(state, player, opt) # Banshee Boardwalk
+ state.has("Railings Banshee Boardwalk North", player)
+ state.has("Railings Banshee Boardwalk South", player)),
lambda state, player, ease, opt: ease + 4 <= (score_track_qualify(state, player, opt) # Rainbow Road
+ 2 * state.has("Railings Rainbow Road 1", player)
+ 2 * state.has("Railings Rainbow Road 2", player)
+ 2 * state.has("Railings Rainbow Road 3", player)
+ state.has("Railings Rainbow Road 5", player))
]
course_win_rules = [ # TODO: Refactor with coupling among score types after more play testing & timing
# lambda: score threshhold <= fence_score + terrain score + item power score + optional railings score
lambda state, player, ease, opt: True, # Luigi Raceway
lambda state, player, ease, opt: ease + 1 <= score_track_win(state, player, opt), # Moo Moo Farm
lambda state, player, ease, opt: ease + 5 <= score_track_win(state, player, opt), # Koopa Troopa Beach
lambda state, player, ease, opt: (ease + 1 <= score_off_road_win(state, player, opt)) # Kalimari Desert
or (state.has("Yellow Switch", player)
and state.has_any({"Star Power", "P2 Star Power"}, player)),
lambda state, player, ease, opt: ease + 1 <= score_track_win(state, player, opt), # Toad's Turnpike
lambda state, player, ease, opt: ease + 6 <= score_winter_win(state, player, opt), # Frappe Snowland
lambda state, player, ease, opt: ease + 3 <= score_off_road_win(state, player, opt), # Choco Mountain
lambda state, player, ease, opt: ease + 3 <= score_off_road_win(state, player, opt), # Mario Raceway
lambda state, player, ease, opt: ease + 4 <= score_off_road_win(state, player, opt), # Wario Stadium
lambda state, player, ease, opt: ease + 4 <= score_winter_win(state, player, opt), # Sherbet Land
lambda state, player, ease, opt: ease + 5 <= score_off_road_win(state, player, opt), # Royal Raceway
lambda state, player, ease, opt: ease + 5 <= score_off_road_win(state, player, opt), # Bowser's Castle
lambda state, player, ease, opt: ease + 4 <= (score_off_road_win(state, player, opt) # D.K.'s Jungle Parkway
+ state.has("Railings D.K.'s Jungle Parkway", player)),
lambda state, player, ease, opt: ease + 7 <= (score_off_road_win(state, player, opt) # Yoshi Valley
+ state.has("Railings Yoshi Valley Main Track", player)
+ state.has("Railings Yoshi Valley Maze", player)),
lambda state, player, ease, opt: ease + 5 <= (score_track_win(state, player, opt) # Banshee Boardwalk
+ state.has("Railings Banshee Boardwalk North", player)
+ state.has("Railings Banshee Boardwalk South", player)),
lambda state, player, ease, opt: ease + 6 <= (score_track_win(state, player, opt) # Rainbow Road
+ 2 * state.has("Railings Rainbow Road 1", player)
+ 2 * state.has("Railings Rainbow Road 2", player)
+ 2 * state.has("Railings Rainbow Road 3", player)
+ state.has("Railings Rainbow Road 5", player))
]
def can_win_trophy(state: CollectionState, player: int,
courses: frozenset[int], trophy_class: int, ease: int, opt: Opt) -> bool:
return trophy_class <= sum(course_win_rules[course](state, player, ease, opt) for course in courses)
def set_star_access_rule(world: "MK64World", loc_name: str, player: int, opt: Opt) -> None:
# Relevant Option
if opt.two_player:
set_rule(world.get_location(loc_name),
lambda state: state.has_any({"Star Power", "P2 Star Power"}, player))
else:
set_rule(world.get_location(loc_name), lambda state: state.has("Star Power", player))
def create_rules(world: "MK64World") -> None:
multiworld = world.multiworld
player = world.player
opt = world.opt
order = world.course_order
# Make starting kart(s) required, which has them show up in the spoiler log Playthrough.
if opt.mode == GameMode.option_cups:
first_entrance = world.multiworld.get_region("Menu", player).get_exits()[0]
if len(world.starting_karts) == 1:
first_entrance.access_rule = lambda state, kart=world.starting_karts[0]: state.has(kart, player)
else:
first_entrance.access_rule = lambda state, kts=frozenset(world.starting_karts): state.has_all(kts, player)
# Region (Entrance) Rules (handled in Regions.py instead for now)
# if opt_game_mode == GameMode.option_cups:
# set_rule(multiworld.get_entrance("Flower Cup 1", player),
# lambda state: state.has("Progressive Cup Unlock", player, 1))
# set_rule(multiworld.get_entrance("Star Cup 1", player),
# lambda state: state.has("Progressive Cup Unlock", player, 2))
# set_rule(multiworld.get_entrance("Special Cup 1", player),
# lambda state: state.has("Progressive Cup Unlock", player, 3))
# elif opt_game_mode == GameMode.option_courses:
# pass
# Base Course Rules # TODO: Clean this up, probably combine with Star Access Rules section
for locations in course_locations.values():
for name, (code, group) in locations.items():
if group == Group.base:
if code % 3 < 2:
set_rule(world.get_location(name),
lambda state, c=code: course_win_rules[(c - 4660000) // 3](state, player, opt.logic, opt))
else:
set_rule(world.get_location(name),
lambda state, c=code: course_qualify_rules[(c - 4660000) // 3](state, player, opt.logic, opt))
# Item Spot Access Rules moved to Regions.py for context that knows which item box spots to apply rules to
# Koopa Troopa Beach Rock Access
if opt.special_boxes and opt.fences:
set_rule(world.get_location("Koopa Troopa Beach Rock"),
lambda state: state.has_all({"Yellow Switch", "Blue Switch"}, player)
or state.has_all({"Red Switch", "Green Switch"}, player))
if opt.secrets and opt.fences:
# Kalimari Desert Secret Access
set_rule(world.get_location("Kalimari Desert Secret"),
lambda state: state.has_any({"Yellow Switch", "Red Switch", "Blue Switch",
"Feather Power", "P2 Feather Power"}, player))
# Marty's Secret Access
set_rule(world.get_location("Marty's Secret"),
lambda state: state.has_any({"Green Switch", "Feather Power", "P2 Feather Power"}, player))
# Hazard Access, all use Star Power
if opt.hazards:
for locations in course_locations.values():
for name, (_, group) in locations.items():
if group == Group.hazard:
set_star_access_rule(world, name, player, opt)
for name, _ in shared_hazard_locations.items():
set_star_access_rule(world, name, player, opt)
# Add Blue Fence rule to Mario sign
if opt.fences:
add_rule(world.get_location("Destroy Mario Sign"), lambda state: state.has("Blue Switch", player))
# Cup Trophy Rules
cup_courses = [frozenset(order[i:i + 4]) for i in range(0, len(order), 4)]
if opt.mode == GameMode.option_cups:
trophy_class_mapping = {"Bronze": 1, "Silver": 2, "Gold": 3}
engine_class_mapping = {"100cc": 2, "150cc": 3} # 50cc is 0
for locations, courses in zip(cup_locations.values(), cup_courses):
for loc_name in locations.keys():
difficulty, trophy = loc_name.rsplit(" ", 2)[-2:]
trophy_class = trophy_class_mapping[trophy]
ease = opt.logic + engine_class_mapping.get(difficulty, 0)
set_rule(world.get_location(loc_name),
lambda state, c=courses, t=trophy_class, e=ease: can_win_trophy(state, player, c, t, e, opt))
# Completion Condition (Victory Rule)
multiworld.completion_condition[player] = lambda state: state.has("Victory", player)