mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-27 21:03:27 -07:00
Merge branch 'main' into new-options-api
This commit is contained in:
@@ -160,9 +160,7 @@ def create_dw_regions(world: World):
|
||||
spaceship = world.multiworld.get_region("Spaceship", world.player)
|
||||
dw_map: Region = create_region(world, "Death Wish Map")
|
||||
entrance = connect_regions(spaceship, dw_map, "-> Death Wish Map", world.player)
|
||||
|
||||
add_rule(entrance, lambda state: state.has("Time Piece", world.player,
|
||||
world.options.DWTimePieceRequirement.value))
|
||||
add_rule(entrance, lambda state: state.has("Time Piece", world.player, world.options.DWTimePieceRequirement.value))
|
||||
|
||||
if world.options.DWShuffle.value > 0:
|
||||
dw_list: List[str] = []
|
||||
@@ -173,8 +171,7 @@ def create_dw_regions(world: World):
|
||||
dw_list.append(name)
|
||||
|
||||
world.random.shuffle(dw_list)
|
||||
count = world.random.randint(world.options.DWShuffleCountMin.value,
|
||||
world.options.DWShuffleCountMax.value)
|
||||
count = world.random.randint(world.options.DWShuffleCountMin.value, world.options.DWShuffleCountMax.value)
|
||||
|
||||
dw_shuffle: List[str] = []
|
||||
total = min(len(dw_list), count)
|
||||
@@ -197,6 +194,7 @@ def create_dw_regions(world: World):
|
||||
if i == 0:
|
||||
connect_regions(dw_map, dw, f"-> {name}", world.player)
|
||||
else:
|
||||
# noinspection PyUnboundLocalVariable
|
||||
connect_regions(prev_dw, dw, f"{prev_dw.name} -> {name}", world.player)
|
||||
|
||||
loc_id = death_wishes[name]
|
||||
|
||||
@@ -388,7 +388,6 @@ def create_enemy_events(world: World):
|
||||
|
||||
if area == "Bluefin Tunnel" and not world.is_dlc2():
|
||||
continue
|
||||
|
||||
if world.options.DWShuffle.value > 0 and area in death_wishes.keys() \
|
||||
and area not in world.get_dw_shuffle():
|
||||
continue
|
||||
|
||||
@@ -3,6 +3,7 @@ from .Types import HatDLC, HatType, LocData, Difficulty
|
||||
from typing import Dict
|
||||
from .Options import TasksanityCheckCount
|
||||
|
||||
TASKSANITY_START_ID = 2000300204
|
||||
|
||||
def get_total_locations(world: World) -> int:
|
||||
total: int = 0
|
||||
@@ -96,7 +97,7 @@ def is_location_valid(world: World, location: str) -> bool:
|
||||
|
||||
def get_location_names() -> Dict[str, int]:
|
||||
names = {name: data.id for name, data in location_table.items()}
|
||||
id_start: int = get_tasksanity_start_id()
|
||||
id_start: int = TASKSANITY_START_ID
|
||||
for i in range(TasksanityCheckCount.range_end):
|
||||
names.setdefault(f"Tasksanity Check {i+1}", id_start+i)
|
||||
|
||||
@@ -107,10 +108,6 @@ def get_location_names() -> Dict[str, int]:
|
||||
return names
|
||||
|
||||
|
||||
def get_tasksanity_start_id() -> int:
|
||||
return 2000300204
|
||||
|
||||
|
||||
ahit_locations = {
|
||||
"Spaceship - Rumbi Abuse": LocData(2000301000, "Spaceship", hit_requirement=1),
|
||||
|
||||
@@ -284,7 +281,7 @@ ahit_locations = {
|
||||
# Alpine Skyline
|
||||
"Alpine Skyline - Goat Village: Below Hookpoint": LocData(2000334856, "Alpine Skyline Area (TIHS)"),
|
||||
"Alpine Skyline - Goat Village: Hidden Branch": LocData(2000334855, "Alpine Skyline Area (TIHS)"),
|
||||
"Alpine Skyline - Goat Refinery": LocData(2000333635, "Alpine Skyline Area"),
|
||||
"Alpine Skyline - Goat Refinery": LocData(2000333635, "Alpine Skyline Area (TIHS)"),
|
||||
"Alpine Skyline - Bird Pass Fork": LocData(2000335911, "Alpine Skyline Area (TIHS)"),
|
||||
|
||||
"Alpine Skyline - Yellow Band Hills": LocData(2000335756, "Alpine Skyline Area (TIHS)",
|
||||
@@ -848,6 +845,7 @@ zero_jumps = {
|
||||
dlc_flags=HatDLC.dlc2_dw),
|
||||
}
|
||||
|
||||
# noinspection PyDictDuplicateKeys
|
||||
snatcher_coins = {
|
||||
"Snatcher Coin - Top of HQ": LocData(0, "Down with the Mafia!", dlc_flags=HatDLC.death_wish),
|
||||
"Snatcher Coin - Top of HQ": LocData(0, "Cheating the Race", dlc_flags=HatDLC.death_wish),
|
||||
@@ -900,6 +898,8 @@ event_locs = {
|
||||
"HUMT Access": LocData(0, "Heating Up Mafia Town"),
|
||||
"TOD Access": LocData(0, "Toilet of Doom"),
|
||||
"YCHE Access": LocData(0, "Your Contract has Expired"),
|
||||
"AFR Access": LocData(0, "Alpine Free Roam"),
|
||||
"TIHS Access": LocData(0, "The Illness has Spread"),
|
||||
|
||||
"Birdhouse Cleared": LocData(0, "The Birdhouse", act_event=True),
|
||||
"Lava Cake Cleared": LocData(0, "The Lava Cake", act_event=True),
|
||||
|
||||
@@ -459,6 +459,15 @@ class NyakuzaThugMaxShopItems(Range):
|
||||
default = 4
|
||||
|
||||
|
||||
class NoTicketSkips(Choice):
|
||||
"""Prevent metro gate skips from being in logic on higher difficulties.
|
||||
Rush Hour option will only consider the ticket skips for Rush Hour in logic."""
|
||||
display_name = "No Ticket Skips"
|
||||
option_false = 0
|
||||
option_true = 1
|
||||
option_rush_hour = 2
|
||||
|
||||
|
||||
class BaseballBat(Toggle):
|
||||
"""Replace the Umbrella with the baseball bat from Nyakuza Metro.
|
||||
DLC2 content does not have to be shuffled for this option but Nyakuza Metro still needs to be installed."""
|
||||
|
||||
@@ -2,7 +2,7 @@ from worlds.AutoWorld import World
|
||||
from BaseClasses import Region, Entrance, ItemClassification, Location
|
||||
from .Types import ChapterIndex, Difficulty, HatInTimeLocation, HatInTimeItem
|
||||
from .Locations import location_table, storybook_pages, event_locs, is_location_valid, \
|
||||
shop_locations, get_tasksanity_start_id, snatcher_coins, zero_jumps, zero_jumps_expert, zero_jumps_hard
|
||||
shop_locations, TASKSANITY_START_ID, snatcher_coins, zero_jumps, zero_jumps_expert, zero_jumps_hard
|
||||
import typing
|
||||
from .Rules import set_rift_rules, get_difficulty
|
||||
|
||||
@@ -438,8 +438,8 @@ def create_rift_connections(world: World, region: Region):
|
||||
|
||||
def create_tasksanity_locations(world: World):
|
||||
ship_shape: Region = world.multiworld.get_region("Ship Shape", world.player)
|
||||
id_start: int = get_tasksanity_start_id()
|
||||
for i in range(world.options.TasksanityCheckCount.value):
|
||||
id_start: int = TASKSANITY_START_ID
|
||||
for i in range(world.multiworld.TasksanityCheckCount[world.player].value):
|
||||
location = HatInTimeLocation(world.player, f"Tasksanity Check {i+1}", id_start+i, ship_shape)
|
||||
ship_shape.locations.append(location)
|
||||
|
||||
@@ -500,12 +500,12 @@ def randomize_act_entrances(world: World):
|
||||
region_list.append(region)
|
||||
|
||||
for region in region_list.copy():
|
||||
if "Time Rift" in region.name:
|
||||
if region.name in chapter_finales:
|
||||
region_list.remove(region)
|
||||
region_list.append(region)
|
||||
|
||||
for region in region_list.copy():
|
||||
if region.name in chapter_finales:
|
||||
if "Time Rift" in region.name:
|
||||
region_list.remove(region)
|
||||
region_list.append(region)
|
||||
|
||||
@@ -630,8 +630,8 @@ def randomize_act_entrances(world: World):
|
||||
candidate = c
|
||||
break
|
||||
|
||||
# noinspection PyUnboundLocalVariable
|
||||
shuffled_list.append(candidate)
|
||||
# print(region, candidate)
|
||||
|
||||
# Vanilla
|
||||
if candidate.name == region.name:
|
||||
@@ -825,11 +825,8 @@ def get_shuffled_region(self, region: str) -> str:
|
||||
|
||||
|
||||
def create_thug_shops(world: World):
|
||||
min_items: int = min(world.options.NyakuzaThugMinShopItems.value,
|
||||
world.options.NyakuzaThugMaxShopItems.value)
|
||||
|
||||
max_items: int = max(world.options.NyakuzaThugMaxShopItems.value,
|
||||
world.options.NyakuzaThugMinShopItems.value)
|
||||
min_items: int = min(world.options.NyakuzaThugMinShopItems.value, world.options.NyakuzaThugMaxShopItems.value)
|
||||
max_items: int = max(world.options.NyakuzaThugMaxShopItems.value, world.options.NyakuzaThugMinShopItems.value)
|
||||
count: int = -1
|
||||
step: int = 0
|
||||
old_name: str = ""
|
||||
@@ -876,6 +873,7 @@ def create_events(world: World) -> int:
|
||||
if not is_location_valid(world, name):
|
||||
continue
|
||||
|
||||
item_name: str = name
|
||||
if world.is_dw():
|
||||
if name in snatcher_coins.keys():
|
||||
name = f"{name} ({data.region})"
|
||||
@@ -886,15 +884,15 @@ def create_events(world: World) -> int:
|
||||
if get_difficulty(world) < Difficulty.EXPERT and name in zero_jumps_expert:
|
||||
continue
|
||||
|
||||
event: Location = create_event(name, world.multiworld.get_region(data.region, world.player), world)
|
||||
event: Location = create_event(name, item_name, world.multiworld.get_region(data.region, world.player), world)
|
||||
event.show_in_spoiler = False
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
|
||||
def create_event(name: str, region: Region, world: World) -> Location:
|
||||
def create_event(name: str, item_name: str, region: Region, world: World) -> Location:
|
||||
event = HatInTimeLocation(world.player, name, None, region)
|
||||
region.locations.append(event)
|
||||
event.place_locked_item(HatInTimeItem(name, ItemClassification.progression, None, world.player))
|
||||
event.place_locked_item(HatInTimeItem(item_name, ItemClassification.progression, None, world.player))
|
||||
return event
|
||||
|
||||
@@ -187,7 +187,6 @@ def set_rules(world: World):
|
||||
|
||||
lowest_cost: int = world.options.LowestChapterCost.value
|
||||
highest_cost: int = world.options.HighestChapterCost.value
|
||||
|
||||
cost_increment: int = world.options.ChapterCostIncrement.value
|
||||
min_difference: int = world.options.ChapterCostMinDifference.value
|
||||
last_cost: int = 0
|
||||
@@ -317,9 +316,25 @@ def set_rules(world: World):
|
||||
for loc in world.multiworld.get_region("Alpine Skyline Area (TIHS)", world.player).locations:
|
||||
if "Goat Village" in loc.name:
|
||||
continue
|
||||
# This needs some special handling
|
||||
if loc.name == "Alpine Skyline - Goat Refinery":
|
||||
add_rule(loc, lambda state: state.has("AFR Access", world.player)
|
||||
and can_use_hookshot(state, world)
|
||||
and can_hit(state, world, True))
|
||||
|
||||
difficulty: Difficulty = Difficulty(world.multiworld.LogicDifficulty[world.player].value)
|
||||
if difficulty >= Difficulty.MODERATE:
|
||||
add_rule(loc, lambda state: state.has("TIHS Access", world.player)
|
||||
and can_use_hat(state, world, HatType.SPRINT), "or")
|
||||
elif difficulty >= Difficulty.HARD:
|
||||
add_rule(loc, lambda state: state.has("TIHS Access", world.player, "or"))
|
||||
|
||||
continue
|
||||
|
||||
add_rule(loc, lambda state: can_use_hookshot(state, world))
|
||||
|
||||
dummy_entrances: typing.List[Entrance] = []
|
||||
|
||||
for (key, acts) in act_connections.items():
|
||||
if "Arctic Cruise" in key and not world.is_dlc1():
|
||||
continue
|
||||
@@ -328,7 +343,7 @@ def set_rules(world: World):
|
||||
entrance: Entrance = world.multiworld.get_entrance(key, world.player)
|
||||
region: Region = entrance.connected_region
|
||||
access_rules: typing.List[typing.Callable[[CollectionState], bool]] = []
|
||||
entrance.parent_region.exits.remove(entrance)
|
||||
dummy_entrances.append(entrance)
|
||||
|
||||
# Entrances to this act that we have to set access_rules on
|
||||
entrances: typing.List[Entrance] = []
|
||||
@@ -354,6 +369,9 @@ def set_rules(world: World):
|
||||
for rules in access_rules:
|
||||
add_rule(e, rules)
|
||||
|
||||
for e in dummy_entrances:
|
||||
set_rule(e, lambda state: False)
|
||||
|
||||
set_event_rules(world)
|
||||
|
||||
if world.options.EndGoal.value == 1:
|
||||
@@ -448,13 +466,12 @@ def set_moderate_rules(world: World):
|
||||
# There is a glitched fall damage volume near the Yellow Overpass time piece that warps the player to Pink Paw.
|
||||
# Yellow Overpass time piece can also be reached without Hookshot quite easily.
|
||||
if world.is_dlc2():
|
||||
set_rule(world.multiworld.get_entrance("-> Pink Paw Station", world.player), lambda state: True)
|
||||
# No Hookshot
|
||||
set_rule(world.multiworld.get_location("Act Completion (Yellow Overpass Station)", world.player),
|
||||
lambda state: True)
|
||||
|
||||
# No Dweller, Hookshot, or Time Stop for these
|
||||
set_rule(world.multiworld.get_location("Pink Paw Station - Cat Vacuum", world.player), lambda state: True)
|
||||
|
||||
# The player can quite literally walk past the fan from the side without Time Stop.
|
||||
set_rule(world.multiworld.get_location("Pink Paw Station - Behind Fan", world.player), lambda state: True)
|
||||
|
||||
# Moderate: clear Rush Hour without Hookshot
|
||||
@@ -465,8 +482,10 @@ def set_moderate_rules(world: World):
|
||||
and can_use_hat(state, world, HatType.ICE)
|
||||
and can_use_hat(state, world, HatType.BREWING))
|
||||
|
||||
# Moderate: Bluefin Tunnel without tickets
|
||||
set_rule(world.multiworld.get_entrance("-> Bluefin Tunnel", world.player), lambda state: True)
|
||||
# Moderate: Bluefin Tunnel + Pink Paw Station without tickets
|
||||
if world.multiworld.NoTicketSkips[world.player].value == 0:
|
||||
set_rule(world.multiworld.get_entrance("-> Pink Paw Station", world.player), lambda state: True)
|
||||
set_rule(world.multiworld.get_entrance("-> Bluefin Tunnel", world.player), lambda state: True)
|
||||
|
||||
|
||||
def set_hard_rules(world: World):
|
||||
@@ -483,6 +502,13 @@ def set_hard_rules(world: World):
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player),
|
||||
lambda state: has_paintings(state, world, 1, False) or state.has("YCHE Access", world.player))
|
||||
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Noose Treehouse", world.player),
|
||||
lambda state: has_paintings(state, world, 2, True))
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player),
|
||||
lambda state: has_paintings(state, world, 2, True))
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Tall Tree Hookshot Swing", world.player),
|
||||
lambda state: has_paintings(state, world, 3, True))
|
||||
|
||||
# SDJ
|
||||
add_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player),
|
||||
lambda state: can_sdj(state, world) and has_paintings(state, world, 2), "or")
|
||||
@@ -508,8 +534,15 @@ def set_hard_rules(world: World):
|
||||
lambda state: can_use_hat(state, world, HatType.ICE))
|
||||
|
||||
# Hard: clear Rush Hour with Brewing Hat only
|
||||
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
|
||||
lambda state: can_use_hat(state, world, HatType.BREWING))
|
||||
if world.multiworld.NoTicketSkips[world.player].value != 1:
|
||||
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
|
||||
lambda state: can_use_hat(state, world, HatType.BREWING))
|
||||
else:
|
||||
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
|
||||
lambda state: can_use_hat(state, world, HatType.BREWING)
|
||||
and state.has("Metro Ticket - Yellow", world.player)
|
||||
and state.has("Metro Ticket - Blue", world.player)
|
||||
and state.has("Metro Ticket - Pink", world.player))
|
||||
|
||||
|
||||
def set_expert_rules(world: World):
|
||||
@@ -517,8 +550,10 @@ def set_expert_rules(world: World):
|
||||
set_rule(world.multiworld.get_entrance("Telescope -> Time's End", world.player),
|
||||
lambda state: state.has("Time Piece", world.player, world.get_chapter_cost(ChapterIndex.FINALE)))
|
||||
|
||||
# Expert: Mafia Town - Above Boats with nothing
|
||||
# Expert: Mafia Town - Above Boats, Top of Lighthouse, and Hot Air Balloon with nothing
|
||||
set_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player), lambda state: True)
|
||||
set_rule(world.multiworld.get_location("Mafia Town - Top of Lighthouse", world.player), lambda state: True)
|
||||
set_rule(world.multiworld.get_location("Mafia Town - Hot Air Balloon", world.player), lambda state: True)
|
||||
|
||||
# Expert: Clear Dead Bird Studio with nothing
|
||||
for loc in world.multiworld.get_region("Dead Bird Studio - Post Elevator Area", world.player).locations:
|
||||
@@ -561,13 +596,9 @@ def set_expert_rules(world: World):
|
||||
# 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 - Noose Treehouse", world.player),
|
||||
lambda state: has_paintings(state, world, 2, True))
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player),
|
||||
lambda state: has_paintings(state, world, 2, 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 - Tall Tree Hookshot Swing", world.player),
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Magnet Badge Bush", world.player),
|
||||
lambda state: has_paintings(state, world, 3, True))
|
||||
|
||||
# You can cherry hover to Snatcher's post-fight cutscene, which completes the level without having to fight him
|
||||
@@ -579,7 +610,13 @@ def set_expert_rules(world: World):
|
||||
|
||||
if world.is_dlc2():
|
||||
# Expert: clear Rush Hour with nothing
|
||||
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), lambda state: True)
|
||||
if world.multiworld.NoTicketSkips[world.player].value == 0:
|
||||
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), lambda state: True)
|
||||
else:
|
||||
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
|
||||
lambda state: state.has("Metro Ticket - Yellow", world.player)
|
||||
and state.has("Metro Ticket - Blue", world.player)
|
||||
and state.has("Metro Ticket - Pink", world.player))
|
||||
|
||||
|
||||
def set_mafia_town_rules(world: World):
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from BaseClasses import Item, ItemClassification, Tutorial
|
||||
from .Items import item_table, create_item, relic_groups, act_contracts, create_itempool
|
||||
from .Regions import create_regions, randomize_act_entrances, chapter_act_info, create_events, get_shuffled_region
|
||||
from .Locations import location_table, contract_locations, is_location_valid, get_location_names, get_tasksanity_start_id
|
||||
from .Locations import location_table, contract_locations, is_location_valid, get_location_names, TASKSANITY_START_ID, \
|
||||
get_total_locations
|
||||
from .Rules import set_rules
|
||||
from .Options import AHITOptions, slot_data_options, adjust_options
|
||||
from .Types import HatType, ChapterIndex, HatInTimeItem
|
||||
@@ -87,7 +88,6 @@ class HatInTimeWorld(World):
|
||||
self.topology_present = self.options.ActRandomizer.value
|
||||
|
||||
create_regions(self)
|
||||
|
||||
if self.options.EnableDeathWish.value > 0:
|
||||
create_dw_regions(self)
|
||||
|
||||
@@ -174,7 +174,8 @@ class HatInTimeWorld(World):
|
||||
"Chapter7Cost": chapter_timepiece_costs[self.player][ChapterIndex.METRO],
|
||||
"BadgeSellerItemCount": badge_seller_count[self.player],
|
||||
"SeedNumber": str(self.multiworld.seed), # For shop prices
|
||||
"SeedName": self.multiworld.seed_name}
|
||||
"SeedName": self.multiworld.seed_name,
|
||||
"TotalLocations": get_total_locations(self)}
|
||||
|
||||
if self.options.HatItems.value == 0:
|
||||
slot_data.setdefault("SprintYarnCost", hat_yarn_costs[self.player][HatType.SPRINT])
|
||||
@@ -253,7 +254,7 @@ class HatInTimeWorld(World):
|
||||
|
||||
if self.is_dlc1() and self.options.Tasksanity.value > 0:
|
||||
ship_shape_region = get_shuffled_region(self, "Ship Shape")
|
||||
id_start: int = get_tasksanity_start_id()
|
||||
id_start: int = TASKSANITY_START_ID
|
||||
for i in range(self.options.TasksanityCheckCount.value):
|
||||
new_hint_data[id_start+i] = ship_shape_region
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from worlds.ahit.Regions import act_chapters
|
||||
from worlds.ahit.Rules import act_connections
|
||||
from worlds.ahit.test.TestBase import HatInTimeTestBase
|
||||
|
||||
|
||||
class TestActs(HatInTimeTestBase):
|
||||
def run_default_tests(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def testAllStateCanReachEverything(self):
|
||||
pass
|
||||
|
||||
@@ -24,6 +25,9 @@ class TestActs(HatInTimeTestBase):
|
||||
for name in act_chapters.keys():
|
||||
region = self.multiworld.get_region(name, 1)
|
||||
for entrance in region.entrances:
|
||||
if entrance.name in act_connections.keys():
|
||||
continue
|
||||
|
||||
self.assertTrue(self.can_reach_entrance(entrance.name),
|
||||
f"Can't reach {name} from {entrance}\n"
|
||||
f"{entrance.parent_region.entrances[0]} -> {entrance.parent_region} "
|
||||
|
||||
Reference in New Issue
Block a user