mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-28 03:43:26 -07:00
change act shuffle starting acts + logic updates
This commit is contained in:
@@ -45,30 +45,30 @@ def adjust_options(world: "HatInTimeWorld"):
|
||||
if world.is_dlc1() and world.options.ShipShapeCustomTaskGoal <= 0:
|
||||
# automatically determine task count based on Tasksanity settings
|
||||
if world.options.Tasksanity:
|
||||
world.options.ShipShapeCustomTaskGoal = world.options.TasksanityCheckCount * world.options.TasksanityTaskStep
|
||||
world.options.ShipShapeCustomTaskGoal.value = world.options.TasksanityCheckCount * world.options.TasksanityTaskStep
|
||||
else:
|
||||
world.options.ShipShapeCustomTaskGoal.value = 18
|
||||
|
||||
# Don't allow Rush Hour goal if DLC2 content is disabled
|
||||
if world.options.EndGoal == EndGoal.option_rush_hour and not world.options.EnableDLC2:
|
||||
world.options.EndGoal.value = 1
|
||||
world.options.EndGoal.value = EndGoal.option_finale
|
||||
|
||||
# Don't allow Seal the Deal goal if Death Wish content is disabled
|
||||
if world.options.EndGoal == EndGoal.option_seal_the_deal and not world.is_dw():
|
||||
world.options.EndGoal.value = 1
|
||||
world.options.EndGoal.value = EndGoal.option_finale
|
||||
|
||||
if world.options.DWEnableBonus:
|
||||
world.options.DWAutoCompleteBonuses.value = 0
|
||||
|
||||
if world.is_dw_only():
|
||||
world.options.EndGoal.value = 3
|
||||
world.options.EndGoal.value = EndGoal.option_seal_the_deal
|
||||
world.options.ActRandomizer.value = 0
|
||||
world.options.ShuffleAlpineZiplines.value = 0
|
||||
world.options.ShuffleSubconPaintings.value = 0
|
||||
world.options.ShuffleStorybookPages.value = 0
|
||||
world.options.ShuffleActContracts.value = 0
|
||||
world.options.EnableDLC1.value = 0
|
||||
world.options.LogicDifficulty.value = -1
|
||||
world.options.LogicDifficulty.value = LogicDifficulty.option_normal
|
||||
world.options.DWTimePieceRequirement.value = 0
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from BaseClasses import Region, Entrance, ItemClassification, Location
|
||||
from BaseClasses import Region, Entrance, ItemClassification, Location, LocationProgressType
|
||||
from .Types import ChapterIndex, Difficulty, HatInTimeLocation, HatInTimeItem
|
||||
from .Locations import location_table, storybook_pages, event_locs, is_location_valid, \
|
||||
shop_locations, TASKSANITY_START_ID, snatcher_coins, zero_jumps, zero_jumps_expert, zero_jumps_hard
|
||||
@@ -10,6 +10,9 @@ if TYPE_CHECKING:
|
||||
from . import HatInTimeWorld
|
||||
|
||||
|
||||
MIN_FIRST_SPHERE_LOCATIONS = 30
|
||||
|
||||
|
||||
# ChapterIndex: region
|
||||
chapter_regions = {
|
||||
ChapterIndex.SPACESHIP: "Spaceship",
|
||||
@@ -217,17 +220,32 @@ chapter_act_info = {
|
||||
"Time Rift - Rumbi Factory": "Metro_CaveRift_RumbiFactory"
|
||||
}
|
||||
|
||||
# Guarantee that the first level a player can access is a location dense area beatable with no items
|
||||
# Some of these may vary depending on options. See is_valid_first_act()
|
||||
guaranteed_first_acts = [
|
||||
"Welcome to Mafia Town",
|
||||
"Barrel Battle",
|
||||
"She Came from Outer Space",
|
||||
"Down with the Mafia!",
|
||||
"Heating Up Mafia Town", # Removed in umbrella logic
|
||||
"Heating Up Mafia Town",
|
||||
"The Golden Vault",
|
||||
|
||||
"Contractual Obligations", # Removed in painting logic
|
||||
"Queen Vanessa's Manor", # Removed in umbrella/painting logic
|
||||
"Dead Bird Studio",
|
||||
"Murder on the Owl Express",
|
||||
"Dead Bird Studio Basement",
|
||||
|
||||
"Contractual Obligations",
|
||||
"The Subcon Well",
|
||||
"Queen Vanessa's Manor",
|
||||
"Your Contract has Expired",
|
||||
|
||||
"Rock the Boat",
|
||||
|
||||
"Time Rift - Mafia of Cooks",
|
||||
"Time Rift - Dead Bird Studio",
|
||||
"Time Rift - Sleepy Subcon",
|
||||
"Time Rift - Alpine Skyline"
|
||||
"Time Rift - Tour",
|
||||
"Time Rift - Rumbi Factory",
|
||||
]
|
||||
|
||||
purple_time_rifts = [
|
||||
@@ -482,29 +500,61 @@ def randomize_act_entrances(world: "HatInTimeWorld"):
|
||||
f"\"{name1}: {name2}\" "
|
||||
f"is an invalid or disallowed act plando combination!")
|
||||
|
||||
first_act_mapped: bool = False
|
||||
# Decide what should be on the first few levels before randomizing the rest
|
||||
first_acts: List[Region] = []
|
||||
first_chapter_name = chapter_regions[ChapterIndex(world.options.StartingChapter)]
|
||||
first_acts.append(get_act_by_number(world, first_chapter_name, 1))
|
||||
# Chapter 3 and 4 only have one level accessible at the start
|
||||
if first_chapter_name == "Mafia Town" or first_chapter_name == "Battle of the Birds":
|
||||
first_acts.append(get_act_by_number(world, first_chapter_name, 2))
|
||||
first_acts.append(get_act_by_number(world, first_chapter_name, 3))
|
||||
|
||||
valid_first_acts: List[Region] = []
|
||||
for candidate in candidate_list:
|
||||
if is_valid_first_act(world, candidate):
|
||||
valid_first_acts.append(candidate)
|
||||
|
||||
total_locations = 0
|
||||
for level in first_acts:
|
||||
if level not in region_list: # make sure it hasn't been plando'd
|
||||
continue
|
||||
|
||||
candidate = valid_first_acts[world.random.randint(0, len(valid_first_acts)-1)]
|
||||
region_list.remove(level)
|
||||
candidate_list.remove(candidate)
|
||||
valid_first_acts.remove(candidate)
|
||||
connect_acts(world, level, candidate, rift_dict)
|
||||
|
||||
# Only allow one purple rift
|
||||
if candidate.name in purple_time_rifts:
|
||||
for act in reversed(valid_first_acts):
|
||||
if act.name in purple_time_rifts:
|
||||
valid_first_acts.remove(act)
|
||||
|
||||
total_locations += get_region_location_count(world, candidate.name)
|
||||
if "Time Rift" not in candidate.name:
|
||||
chapter = act_chapters.get(candidate.name)
|
||||
if chapter == "Mafia Town":
|
||||
total_locations += get_region_location_count(world, "Mafia Town Area (HUMT)")
|
||||
if candidate.name != "Heating Up Mafia Town":
|
||||
total_locations += get_region_location_count(world, "Mafia Town Area")
|
||||
elif chapter == "Subcon Forest":
|
||||
total_locations += get_region_location_count(world, "Subcon Forest Area")
|
||||
elif chapter == "The Arctic Cruise":
|
||||
total_locations += get_region_location_count(world, "Cruise Ship")
|
||||
|
||||
# If we have enough Sphere 1 locations, we can allow the rest to be randomized
|
||||
if total_locations >= MIN_FIRST_SPHERE_LOCATIONS:
|
||||
break
|
||||
|
||||
ignore_certain_rules: bool = False
|
||||
while len(region_list) > 0:
|
||||
region: Region
|
||||
if not first_act_mapped:
|
||||
region = get_first_act(world)
|
||||
else:
|
||||
region = region_list[0]
|
||||
|
||||
region = region_list[0]
|
||||
candidate: Region
|
||||
valid_candidates: List[Region] = []
|
||||
|
||||
# Look for candidates to map this act to
|
||||
for c in candidate_list:
|
||||
# Map the first act before anything
|
||||
if not first_act_mapped:
|
||||
if not is_valid_first_act(world, c):
|
||||
continue
|
||||
|
||||
valid_candidates.append(c)
|
||||
first_act_mapped = True
|
||||
break # we can stop here, as we only need one
|
||||
|
||||
if is_valid_act_combo(world, region, c, ignore_certain_rules):
|
||||
valid_candidates.append(c)
|
||||
|
||||
@@ -545,7 +595,7 @@ def sort_acts(act: Region) -> int:
|
||||
and "Time Rift" not in act.name:
|
||||
return -3
|
||||
|
||||
if act.name == "Contractual Obligations":
|
||||
if act.name == "Contractual Obligations" or act.name == "The Subcon Well":
|
||||
return -2
|
||||
|
||||
world = act.multiworld.worlds[act.player]
|
||||
@@ -558,17 +608,6 @@ def sort_acts(act: Region) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def get_first_act(world: "HatInTimeWorld") -> Region:
|
||||
first_chapter = get_first_chapter_region(world)
|
||||
act: Optional[Region] = None
|
||||
for e in first_chapter.exits:
|
||||
if "Act 1" in e.name or "Free Roam" in e.name:
|
||||
act = e.connected_region
|
||||
break
|
||||
|
||||
return act
|
||||
|
||||
|
||||
def connect_acts(world: "HatInTimeWorld", entrance_act: Region, exit_act: Region, rift_dict: Dict[str, Region]):
|
||||
# Vanilla
|
||||
if exit_act.name == entrance_act.name:
|
||||
@@ -642,32 +681,66 @@ def is_valid_first_act(world: "HatInTimeWorld", act: Region) -> bool:
|
||||
if act.name not in guaranteed_first_acts:
|
||||
return False
|
||||
|
||||
# Not completable without Umbrella
|
||||
if world.options.UmbrellaLogic and (act.name == "Heating Up Mafia Town" or act.name == "Queen Vanessa's Manor"):
|
||||
# If there's only a single level in the starting chapter, only allow Mafia Town or Subcon Forest levels
|
||||
start_chapter = world.options.StartingChapter
|
||||
if start_chapter is ChapterIndex.ALPINE or start_chapter is ChapterIndex.SUBCON:
|
||||
if "Time Rift" in act.name:
|
||||
return False
|
||||
|
||||
if act_chapters[act.name] != "Mafia Town" and act_chapters[act.name] != "Subcon Forest":
|
||||
return False
|
||||
|
||||
if act.name in purple_time_rifts and not world.options.ShuffleStorybookPages:
|
||||
return False
|
||||
|
||||
# Subcon sphere 1 is too small without painting unlocks, and no acts are completable either
|
||||
if world.options.ShuffleSubconPaintings and "Subcon Forest" in act_entrances[act.name]:
|
||||
diff = get_difficulty(world)
|
||||
# Not completable without Umbrella?
|
||||
if world.options.UmbrellaLogic:
|
||||
# Needs to be at least moderate to cross the big dweller wall
|
||||
if act.name == "Queen Vanessa's Manor" and diff < Difficulty.MODERATE:
|
||||
return False
|
||||
elif act.name == "Your Contract has Expired" and diff < Difficulty.EXPERT: # Snatcher Hover
|
||||
return False
|
||||
elif act.name == "Heating Up Mafia Town": # Straight up impossible
|
||||
return False
|
||||
|
||||
if act.name == "Dead Bird Studio":
|
||||
# No umbrella logic = moderate, umbrella logic = expert.
|
||||
if diff < Difficulty.MODERATE or world.options.UmbrellaLogic and diff < Difficulty.EXPERT:
|
||||
return False
|
||||
elif act.name == "Dead Bird Studio Basement" and (diff < Difficulty.EXPERT or world.options.FinaleShuffle):
|
||||
return False
|
||||
elif act.name == "Rock the Boat" and (diff < Difficulty.MODERATE or world.options.FinaleShuffle):
|
||||
return False
|
||||
elif act.name == "The Subcon Well" and diff < Difficulty.MODERATE:
|
||||
return False
|
||||
elif act.name == "Contractual Obligations" and world.options.ShuffleSubconPaintings:
|
||||
return False
|
||||
|
||||
if world.options.ShuffleSubconPaintings and "Time Rift" not in act.name and act_chapters[act.name] == "Subcon Forest":
|
||||
# This requires a cherry hover to enter Subcon
|
||||
if act.name == "Your Contract has Expired":
|
||||
if diff < Difficulty.EXPERT or world.options.NoPaintingSkips:
|
||||
return False
|
||||
else:
|
||||
# Only allow Subcon levels if paintings can be skipped
|
||||
if diff < Difficulty.MODERATE or world.options.NoPaintingSkips:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def connect_time_rift(world: "HatInTimeWorld", time_rift: Region, exit_region: Region):
|
||||
count: int = len(rift_access_regions[time_rift.name])
|
||||
i: int = 1
|
||||
while i <= count:
|
||||
i = 1
|
||||
while i <= len(rift_access_regions[time_rift.name]):
|
||||
name = f"{time_rift.name} Portal - Entrance {i}"
|
||||
entrance: Entrance
|
||||
try:
|
||||
entrance = world.multiworld.get_entrance(name, world.player)
|
||||
reconnect_regions(entrance, entrance.parent_region, exit_region)
|
||||
except KeyError:
|
||||
if len(time_rift.entrances) > 0:
|
||||
entrance = time_rift.entrances[i-1]
|
||||
else:
|
||||
entrance = time_rift.connect(exit_region, name)
|
||||
time_rift.connect(exit_region, name)
|
||||
|
||||
reconnect_regions(entrance, entrance.parent_region, exit_region)
|
||||
i += 1
|
||||
|
||||
|
||||
@@ -862,6 +935,27 @@ def get_shuffled_region(world: "HatInTimeWorld", region: str) -> str:
|
||||
return name
|
||||
|
||||
|
||||
def get_region_location_count(world: "HatInTimeWorld", region_name: str, included_only: bool = True) -> int:
|
||||
count = 0
|
||||
region = world.multiworld.get_region(region_name, world.player)
|
||||
for loc in region.locations:
|
||||
if loc.address is not None and (not included_only or loc.progress_type is not LocationProgressType.EXCLUDED):
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
|
||||
def get_act_by_number(world: "HatInTimeWorld", chapter_name: str, num: int) -> Region:
|
||||
chapter = world.multiworld.get_region(chapter_name, world.player)
|
||||
act: Optional[Region] = None
|
||||
for e in chapter.exits:
|
||||
if f"Act {num}" in e.name or num == 1 and "Free Roam" in e.name:
|
||||
act = e.connected_region
|
||||
break
|
||||
|
||||
return act
|
||||
|
||||
|
||||
def create_thug_shops(world: "HatInTimeWorld"):
|
||||
min_items: int = world.options.NyakuzaThugMinShopItems.value
|
||||
max_items: int = world.options.NyakuzaThugMaxShopItems.value
|
||||
|
||||
@@ -399,6 +399,10 @@ def set_moderate_rules(world: "HatInTimeWorld"):
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Manor Rooftop", world.player),
|
||||
lambda state: has_paintings(state, world, 1))
|
||||
|
||||
# Moderate: Village Time Rift with nothing IF umbrella logic is off
|
||||
if not world.options.UmbrellaLogic:
|
||||
set_rule(world.multiworld.get_location("Act Completion (Time Rift - Village)", world.player), lambda state: True)
|
||||
|
||||
# Moderate: get to Birdhouse/Yellow Band Hills without Brewing Hat
|
||||
set_rule(world.multiworld.get_entrance("-> The Birdhouse", world.player),
|
||||
lambda state: can_use_hookshot(state, world))
|
||||
@@ -478,6 +482,8 @@ def set_hard_rules(world: "HatInTimeWorld"):
|
||||
# No Dweller Mask required
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Dweller Floating Rocks", world.player),
|
||||
lambda state: has_paintings(state, world, 3))
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Dweller Platforming Tree B", world.player),
|
||||
lambda state: has_paintings(state, world, 3))
|
||||
|
||||
# Cherry bridge over boss arena gap (painting still expected)
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player),
|
||||
@@ -494,9 +500,6 @@ def set_hard_rules(world: "HatInTimeWorld"):
|
||||
add_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player),
|
||||
lambda state: can_use_hat(state, world, HatType.SPRINT) and has_paintings(state, world, 2), "or")
|
||||
|
||||
add_rule(world.multiworld.get_location("Subcon Forest - Dweller Platforming Tree B", world.player),
|
||||
lambda state: has_paintings(state, world, 3) and can_use_hat(state, world, HatType.SPRINT), "or")
|
||||
|
||||
add_rule(world.multiworld.get_location("Act Completion (Time Rift - Curly Tail Trail)", world.player),
|
||||
lambda state: can_use_hat(state, world, HatType.SPRINT), "or")
|
||||
|
||||
@@ -581,8 +584,6 @@ def set_expert_rules(world: "HatInTimeWorld"):
|
||||
# Set painting rules only. Skipping paintings is determined in has_paintings
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player),
|
||||
lambda state: has_paintings(state, world, 1, True))
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Dweller Platforming Tree B", world.player),
|
||||
lambda state: has_paintings(state, world, 3, True))
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Magnet Badge Bush", world.player),
|
||||
lambda state: has_paintings(state, world, 3, True))
|
||||
|
||||
@@ -601,6 +602,12 @@ def set_expert_rules(world: "HatInTimeWorld"):
|
||||
and state.has("Metro Ticket - Blue", world.player)
|
||||
and state.has("Metro Ticket - Pink", world.player))
|
||||
|
||||
# Expert: Yellow/Green Manhole with nothing using a Boop Clip
|
||||
set_rule(world.multiworld.get_location("Act Completion (Yellow Overpass Manhole)", world.player),
|
||||
lambda state: True)
|
||||
set_rule(world.multiworld.get_location("Act Completion (Green Clean Manhole)", world.player),
|
||||
lambda state: True)
|
||||
|
||||
|
||||
def set_mafia_town_rules(world: "HatInTimeWorld"):
|
||||
add_rule(world.multiworld.get_location("Mafia Town - Behind HQ Chest", world.player),
|
||||
|
||||
Reference in New Issue
Block a user