Merge branch 'main' into new-options-api

This commit is contained in:
CookieCat
2023-12-08 09:14:08 -05:00
committed by GitHub
8 changed files with 93 additions and 47 deletions

View File

@@ -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]

View File

@@ -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

View File

@@ -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),

View File

@@ -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."""

View File

@@ -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

View File

@@ -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):

View File

@@ -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

View File

@@ -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} "