+
-
+
{{ option.default | default(option.range_start) if option.default != "random" else option.range_start }}
{{ RandomizeButton(option_name, option) }}
diff --git a/WebHostLib/templates/playerOptions/playerOptions.html b/WebHostLib/templates/playerOptions/playerOptions.html
index aeb6e864a5..73de5d56eb 100644
--- a/WebHostLib/templates/playerOptions/playerOptions.html
+++ b/WebHostLib/templates/playerOptions/playerOptions.html
@@ -11,7 +11,7 @@
diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py
index 8e567afc35..75b5fb0202 100644
--- a/WebHostLib/tracker.py
+++ b/WebHostLib/tracker.py
@@ -79,7 +79,7 @@ class TrackerData:
# Normal lookup tables as well.
self.item_name_to_id[game] = game_package["item_name_to_id"]
- self.location_name_to_id[game] = game_package["item_name_to_id"]
+ self.location_name_to_id[game] = game_package["location_name_to_id"]
def get_seed_name(self) -> str:
"""Retrieves the seed name."""
diff --git a/worlds/ahit/Items.py b/worlds/ahit/Items.py
index 3ef83fe81e..54c6e6b5d3 100644
--- a/worlds/ahit/Items.py
+++ b/worlds/ahit/Items.py
@@ -39,7 +39,7 @@ def create_itempool(world: "HatInTimeWorld") -> List[Item]:
continue
else:
if name == "Scooter Badge":
- if world.options.CTRLogic is CTRLogic.option_scooter or get_difficulty(world) >= Difficulty.MODERATE:
+ if world.options.CTRLogic == CTRLogic.option_scooter or get_difficulty(world) >= Difficulty.MODERATE:
item_type = ItemClassification.progression
elif name == "No Bonk Badge" and world.is_dw():
item_type = ItemClassification.progression
diff --git a/worlds/ahit/Regions.py b/worlds/ahit/Regions.py
index c6aeaa3577..8cb3782bde 100644
--- a/worlds/ahit/Regions.py
+++ b/worlds/ahit/Regions.py
@@ -659,6 +659,10 @@ def is_valid_act_combo(world: "HatInTimeWorld", entrance_act: Region,
if exit_act.name not in chapter_finales:
return False
+ exit_chapter: str = act_chapters.get(exit_act.name)
+ # make sure that certain time rift combinations never happen
+ always_block: bool = exit_chapter != "Mafia Town" and exit_chapter != "Subcon Forest"
+ if not ignore_certain_rules or always_block:
if entrance_act.name in rift_access_regions and exit_act.name in rift_access_regions[entrance_act.name]:
return False
@@ -684,9 +688,12 @@ def is_valid_first_act(world: "HatInTimeWorld", act: Region) -> bool:
if act.name not in guaranteed_first_acts:
return False
+ if world.options.ActRandomizer == ActRandomizer.option_light and "Time Rift" in act.name:
+ return False
+
# 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 start_chapter == ChapterIndex.ALPINE or start_chapter == ChapterIndex.SUBCON:
if "Time Rift" in act.name:
return False
@@ -723,7 +730,8 @@ def is_valid_first_act(world: "HatInTimeWorld", act: Region) -> bool:
elif act.name == "Contractual Obligations" and world.options.ShuffleSubconPaintings:
return False
- if world.options.ShuffleSubconPaintings and act_chapters.get(act.name, "") == "Subcon Forest":
+ if world.options.ShuffleSubconPaintings and "Time Rift" not in act.name \
+ and act_chapters.get(act.name, "") == "Subcon Forest":
# Only allow Subcon levels if painting skips are allowed
if diff < Difficulty.MODERATE or world.options.NoPaintingSkips:
return False
diff --git a/worlds/ahit/Rules.py b/worlds/ahit/Rules.py
index b0513c4332..b716b793a7 100644
--- a/worlds/ahit/Rules.py
+++ b/worlds/ahit/Rules.py
@@ -1,7 +1,6 @@
from worlds.AutoWorld import CollectionState
from worlds.generic.Rules import add_rule, set_rule
-from .Locations import location_table, zipline_unlocks, is_location_valid, contract_locations, \
- shop_locations, event_locs
+from .Locations import location_table, zipline_unlocks, is_location_valid, shop_locations, event_locs
from .Types import HatType, ChapterIndex, hat_type_to_item, Difficulty, HitType
from BaseClasses import Location, Entrance, Region
from typing import TYPE_CHECKING, List, Callable, Union, Dict
@@ -148,14 +147,14 @@ def set_rules(world: "HatInTimeWorld"):
if world.is_dlc1():
chapter_list.append(ChapterIndex.CRUISE)
- if world.is_dlc2() and final_chapter is not ChapterIndex.METRO:
+ if world.is_dlc2() and final_chapter != ChapterIndex.METRO:
chapter_list.append(ChapterIndex.METRO)
chapter_list.remove(starting_chapter)
world.random.shuffle(chapter_list)
# Make sure Alpine is unlocked before any DLC chapters are, as the Alpine door needs to be open to access them
- if starting_chapter is not ChapterIndex.ALPINE and (world.is_dlc1() or world.is_dlc2()):
+ if starting_chapter != ChapterIndex.ALPINE and (world.is_dlc1() or world.is_dlc2()):
index1 = 69
index2 = 69
pos: int
@@ -165,7 +164,7 @@ def set_rules(world: "HatInTimeWorld"):
if world.is_dlc1():
index1 = chapter_list.index(ChapterIndex.CRUISE)
- if world.is_dlc2() and final_chapter is not ChapterIndex.METRO:
+ if world.is_dlc2() and final_chapter != ChapterIndex.METRO:
index2 = chapter_list.index(ChapterIndex.METRO)
lowest_index = min(index1, index2)
@@ -242,9 +241,6 @@ def set_rules(world: "HatInTimeWorld"):
if not is_location_valid(world, key):
continue
- if key in contract_locations.keys():
- continue
-
loc = world.multiworld.get_location(key, world.player)
for hat in data.required_hats:
@@ -256,7 +252,7 @@ def set_rules(world: "HatInTimeWorld"):
if data.paintings > 0 and world.options.ShuffleSubconPaintings:
add_rule(loc, lambda state, paintings=data.paintings: has_paintings(state, world, paintings))
- if data.hit_type is not HitType.none and world.options.UmbrellaLogic:
+ if data.hit_type != HitType.none and world.options.UmbrellaLogic:
if data.hit_type == HitType.umbrella:
add_rule(loc, lambda state: state.has("Umbrella", world.player))
@@ -518,7 +514,7 @@ def set_hard_rules(world: "HatInTimeWorld"):
lambda state: can_use_hat(state, world, HatType.ICE))
# Hard: clear Rush Hour with Brewing Hat only
- if world.options.NoTicketSkips is not NoTicketSkips.option_true:
+ if world.options.NoTicketSkips != NoTicketSkips.option_true:
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
lambda state: can_use_hat(state, world, HatType.BREWING))
else:
diff --git a/worlds/ahit/__init__.py b/worlds/ahit/__init__.py
index 15140379b9..dd5e88abbc 100644
--- a/worlds/ahit/__init__.py
+++ b/worlds/ahit/__init__.py
@@ -1,15 +1,16 @@
from BaseClasses import Item, ItemClassification, Tutorial, Location, MultiWorld
from .Items import item_table, create_item, relic_groups, act_contracts, create_itempool, get_shop_trap_name, \
- calculate_yarn_costs
+ calculate_yarn_costs, alps_hooks
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, TASKSANITY_START_ID, \
get_total_locations
-from .Rules import set_rules
+from .Rules import set_rules, has_paintings
from .Options import AHITOptions, slot_data_options, adjust_options, RandomizeHatOrder, EndGoal, create_option_groups
-from .Types import HatType, ChapterIndex, HatInTimeItem, hat_type_to_item
+from .Types import HatType, ChapterIndex, HatInTimeItem, hat_type_to_item, Difficulty
from .DeathWishLocations import create_dw_regions, dw_classes, death_wishes
from .DeathWishRules import set_dw_rules, create_enemy_events, hit_list, bosses
from worlds.AutoWorld import World, WebWorld, CollectionState
+from worlds.generic.Rules import add_rule
from typing import List, Dict, TextIO
from worlds.LauncherComponents import Component, components, icon_paths, launch_subprocess, Type
from Utils import local_path
@@ -86,19 +87,27 @@ class HatInTimeWorld(World):
if self.is_dw_only():
return
- # If our starting chapter is 4 and act rando isn't on, force hookshot into inventory
- # If starting chapter is 3 and painting shuffle is enabled, and act rando isn't, give one free painting unlock
- start_chapter: ChapterIndex = ChapterIndex(self.options.StartingChapter)
+ # Take care of some extremely restrictive starts in other chapters with act shuffle off
+ if not self.options.ActRandomizer:
+ start_chapter = self.options.StartingChapter
+ if start_chapter == ChapterIndex.ALPINE:
+ self.multiworld.push_precollected(self.create_item("Hookshot Badge"))
+ if self.options.UmbrellaLogic:
+ self.multiworld.push_precollected(self.create_item("Umbrella"))
- if start_chapter == ChapterIndex.ALPINE or start_chapter == ChapterIndex.SUBCON:
- if not self.options.ActRandomizer:
- if start_chapter == ChapterIndex.ALPINE:
- self.multiworld.push_precollected(self.create_item("Hookshot Badge"))
- if self.options.UmbrellaLogic:
- self.multiworld.push_precollected(self.create_item("Umbrella"))
-
- if start_chapter == ChapterIndex.SUBCON and self.options.ShuffleSubconPaintings:
+ if self.options.ShuffleAlpineZiplines:
+ ziplines = list(alps_hooks.keys())
+ ziplines.remove("Zipline Unlock - The Twilight Bell Path") # not enough checks from this one
+ self.multiworld.push_precollected(self.create_item(self.random.choice(ziplines)))
+ elif start_chapter == ChapterIndex.SUBCON:
+ if self.options.ShuffleSubconPaintings:
self.multiworld.push_precollected(self.create_item("Progressive Painting Unlock"))
+ elif start_chapter == ChapterIndex.BIRDS:
+ if self.options.UmbrellaLogic:
+ if self.options.LogicDifficulty < Difficulty.EXPERT:
+ self.multiworld.push_precollected(self.create_item("Umbrella"))
+ elif self.options.LogicDifficulty < Difficulty.MODERATE:
+ self.multiworld.push_precollected(self.create_item("Umbrella"))
def create_regions(self):
# noinspection PyClassVar
@@ -119,7 +128,10 @@ class HatInTimeWorld(World):
# place vanilla contract locations if contract shuffle is off
if not self.options.ShuffleActContracts:
for name in contract_locations.keys():
- self.multiworld.get_location(name, self.player).place_locked_item(create_item(self, name))
+ loc = self.get_location(name)
+ loc.place_locked_item(create_item(self, name))
+ if self.options.ShuffleSubconPaintings and loc.name != "Snatcher's Contract - The Subcon Well":
+ add_rule(loc, lambda state: has_paintings(state, self, 1))
def create_items(self):
if self.has_yarn():
@@ -317,7 +329,7 @@ class HatInTimeWorld(World):
def remove(self, state: "CollectionState", item: "Item") -> bool:
old_count: int = state.count(item.name, self.player)
- change = super().collect(state, item)
+ change = super().remove(state, item)
if change and old_count == 1:
if "Stamp" in item.name:
if "2 Stamp" in item.name:
diff --git a/worlds/alttp/SubClasses.py b/worlds/alttp/SubClasses.py
index 769dcc1998..328e28da93 100644
--- a/worlds/alttp/SubClasses.py
+++ b/worlds/alttp/SubClasses.py
@@ -76,10 +76,6 @@ class ALttPItem(Item):
if self.type in {"SmallKey", "BigKey", "Map", "Compass"}:
return self.type
- @property
- def locked_dungeon_item(self):
- return self.location.locked and self.dungeon_item
-
class LTTPRegionType(IntEnum):
LightWorld = 1
diff --git a/worlds/factorio/data/mod_template/control.lua b/worlds/factorio/data/mod_template/control.lua
index 8ce0b45a5f..ace231e12b 100644
--- a/worlds/factorio/data/mod_template/control.lua
+++ b/worlds/factorio/data/mod_template/control.lua
@@ -660,11 +660,18 @@ commands.add_command("ap-get-technology", "Grant a technology, used by the Archi
end
local tech
local force = game.forces["player"]
+ if call.parameter == nil then
+ game.print("ap-get-technology is only to be used by the Archipelago Factorio Client")
+ return
+ end
chunks = split(call.parameter, "\t")
local item_name = chunks[1]
local index = chunks[2]
local source = chunks[3] or "Archipelago"
- if index == -1 then -- for coop sync and restoring from an older savegame
+ if index == nil then
+ game.print("ap-get-technology is only to be used by the Archipelago Factorio Client")
+ return
+ elseif index == -1 then -- for coop sync and restoring from an older savegame
tech = force.technologies[item_name]
if tech.researched ~= true then
game.print({"", "Received [technology=" .. tech.name .. "] as it is already checked."})
diff --git a/worlds/ffmq/Client.py b/worlds/ffmq/Client.py
index 6cb35dd3b4..93688a6116 100644
--- a/worlds/ffmq/Client.py
+++ b/worlds/ffmq/Client.py
@@ -71,7 +71,7 @@ class FFMQClient(SNIClient):
received = await snes_read(ctx, RECEIVED_DATA[0], RECEIVED_DATA[1])
data = await snes_read(ctx, READ_DATA_START, READ_DATA_END - READ_DATA_START)
check_2 = await snes_read(ctx, 0xF53749, 1)
- if check_1 != b'01' or check_2 != b'01':
+ if check_1 != b'\x01' or check_2 != b'\x01':
return
def get_range(data_range):
diff --git a/worlds/hk/Options.py b/worlds/hk/Options.py
index 38be2cd794..e2602036a2 100644
--- a/worlds/hk/Options.py
+++ b/worlds/hk/Options.py
@@ -1,10 +1,12 @@
import typing
import re
+from dataclasses import dataclass, make_dataclass
+
from .ExtractedData import logic_options, starts, pool_options
from .Rules import cost_terms
from schema import And, Schema, Optional
-from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionDict, NamedRange, DeathLink
+from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionDict, NamedRange, DeathLink, PerGameCommonOptions
from .Charms import vanilla_costs, names as charm_names
if typing.TYPE_CHECKING:
@@ -538,3 +540,5 @@ hollow_knight_options: typing.Dict[str, type(Option)] = {
},
**cost_sanity_weights
}
+
+HKOptions = make_dataclass("HKOptions", [(name, option) for name, option in hollow_knight_options.items()], bases=(PerGameCommonOptions,))
diff --git a/worlds/hk/Rules.py b/worlds/hk/Rules.py
index a3c7e13cf0..e162e1dfa8 100644
--- a/worlds/hk/Rules.py
+++ b/worlds/hk/Rules.py
@@ -49,3 +49,42 @@ def set_rules(hk_world: World):
if term == "GEO": # No geo logic!
continue
add_rule(location, lambda state, term=term, amount=amount: state.count(term, player) >= amount)
+
+
+def _hk_nail_combat(state, player) -> bool:
+ return state.has_any({'LEFTSLASH', 'RIGHTSLASH', 'UPSLASH'}, player)
+
+
+def _hk_can_beat_thk(state, player) -> bool:
+ return (
+ state.has('Opened_Black_Egg_Temple', player)
+ and (state.count('FIREBALL', player) + state.count('SCREAM', player) + state.count('QUAKE', player)) > 1
+ and _hk_nail_combat(state, player)
+ and (
+ state.has_any({'LEFTDASH', 'RIGHTDASH'}, player)
+ or state._hk_option(player, 'ProficientCombat')
+ )
+ and state.has('FOCUS', player)
+ )
+
+
+def _hk_siblings_ending(state, player) -> bool:
+ return _hk_can_beat_thk(state, player) and state.has('WHITEFRAGMENT', player, 3)
+
+
+def _hk_can_beat_radiance(state, player) -> bool:
+ return (
+ state.has('Opened_Black_Egg_Temple', player)
+ and _hk_nail_combat(state, player)
+ and state.has('WHITEFRAGMENT', player, 3)
+ and state.has('DREAMNAIL', player)
+ and (
+ (state.has('LEFTCLAW', player) and state.has('RIGHTCLAW', player))
+ or state.has('WINGS', player)
+ )
+ and (state.count('FIREBALL', player) + state.count('SCREAM', player) + state.count('QUAKE', player)) > 1
+ and (
+ (state.has('LEFTDASH', player, 2) and state.has('RIGHTDASH', player, 2)) # Both Shade Cloaks
+ or (state._hk_option(player, 'ProficientCombat') and state.has('QUAKE', player)) # or Dive
+ )
+ )
diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py
index fbc6461f6a..e5065876dd 100644
--- a/worlds/hk/__init__.py
+++ b/worlds/hk/__init__.py
@@ -10,9 +10,9 @@ logger = logging.getLogger("Hollow Knight")
from .Items import item_table, lookup_type_to_names, item_name_groups
from .Regions import create_regions
-from .Rules import set_rules, cost_terms
+from .Rules import set_rules, cost_terms, _hk_can_beat_thk, _hk_siblings_ending, _hk_can_beat_radiance
from .Options import hollow_knight_options, hollow_knight_randomize_options, Goal, WhitePalace, CostSanity, \
- shop_to_option
+ shop_to_option, HKOptions
from .ExtractedData import locations, starts, multi_locations, location_to_region_lookup, \
event_names, item_effects, connectors, one_ways, vanilla_shop_costs, vanilla_location_costs
from .Charms import names as charm_names
@@ -142,7 +142,8 @@ class HKWorld(World):
As the enigmatic Knight, you’ll traverse the depths, unravel its mysteries and conquer its evils.
""" # from https://www.hollowknight.com
game: str = "Hollow Knight"
- option_definitions = hollow_knight_options
+ options_dataclass = HKOptions
+ options: HKOptions
web = HKWeb()
@@ -155,8 +156,8 @@ class HKWorld(World):
charm_costs: typing.List[int]
cached_filler_items = {}
- def __init__(self, world, player):
- super(HKWorld, self).__init__(world, player)
+ def __init__(self, multiworld, player):
+ super(HKWorld, self).__init__(multiworld, player)
self.created_multi_locations: typing.Dict[str, typing.List[HKLocation]] = {
location: list() for location in multi_locations
}
@@ -165,29 +166,29 @@ class HKWorld(World):
self.vanilla_shop_costs = deepcopy(vanilla_shop_costs)
def generate_early(self):
- world = self.multiworld
- charm_costs = world.RandomCharmCosts[self.player].get_costs(world.random)
- self.charm_costs = world.PlandoCharmCosts[self.player].get_costs(charm_costs)
- # world.exclude_locations[self.player].value.update(white_palace_locations)
+ options = self.options
+ charm_costs = options.RandomCharmCosts.get_costs(self.random)
+ self.charm_costs = options.PlandoCharmCosts.get_costs(charm_costs)
+ # options.exclude_locations.value.update(white_palace_locations)
for term, data in cost_terms.items():
- mini = getattr(world, f"Minimum{data.option}Price")[self.player]
- maxi = getattr(world, f"Maximum{data.option}Price")[self.player]
+ mini = getattr(options, f"Minimum{data.option}Price")
+ maxi = getattr(options, f"Maximum{data.option}Price")
# if minimum > maximum, set minimum to maximum
mini.value = min(mini.value, maxi.value)
self.ranges[term] = mini.value, maxi.value
- world.push_precollected(HKItem(starts[world.StartLocation[self.player].current_key],
+ self.multiworld.push_precollected(HKItem(starts[options.StartLocation.current_key],
True, None, "Event", self.player))
def white_palace_exclusions(self):
exclusions = set()
- wp = self.multiworld.WhitePalace[self.player]
+ wp = self.options.WhitePalace
if wp <= WhitePalace.option_nopathofpain:
exclusions.update(path_of_pain_locations)
if wp <= WhitePalace.option_kingfragment:
exclusions.update(white_palace_checks)
if wp == WhitePalace.option_exclude:
exclusions.add("King_Fragment")
- if self.multiworld.RandomizeCharms[self.player]:
+ if self.options.RandomizeCharms:
# If charms are randomized, this will be junk-filled -- so transitions and events are not progression
exclusions.update(white_palace_transitions)
exclusions.update(white_palace_events)
@@ -200,7 +201,7 @@ class HKWorld(World):
# check for any goal that godhome events are relevant to
all_event_names = event_names.copy()
- if self.multiworld.Goal[self.player] in [Goal.option_godhome, Goal.option_godhome_flower]:
+ if self.options.Goal in [Goal.option_godhome, Goal.option_godhome_flower]:
from .GodhomeData import godhome_event_names
all_event_names.update(set(godhome_event_names))
@@ -230,12 +231,12 @@ class HKWorld(World):
pool: typing.List[HKItem] = []
wp_exclusions = self.white_palace_exclusions()
junk_replace: typing.Set[str] = set()
- if self.multiworld.RemoveSpellUpgrades[self.player]:
+ if self.options.RemoveSpellUpgrades:
junk_replace.update(("Abyss_Shriek", "Shade_Soul", "Descending_Dark"))
randomized_starting_items = set()
for attr, items in randomizable_starting_items.items():
- if getattr(self.multiworld, attr)[self.player]:
+ if getattr(self.options, attr):
randomized_starting_items.update(items)
# noinspection PyShadowingNames
@@ -257,7 +258,7 @@ class HKWorld(World):
if item_name in junk_replace:
item_name = self.get_filler_item_name()
- item = self.create_item(item_name) if not vanilla or location_name == "Start" or self.multiworld.AddUnshuffledLocations[self.player] else self.create_event(item_name)
+ item = self.create_item(item_name) if not vanilla or location_name == "Start" or self.options.AddUnshuffledLocations else self.create_event(item_name)
if location_name == "Start":
if item_name in randomized_starting_items:
@@ -281,55 +282,55 @@ class HKWorld(World):
location.progress_type = LocationProgressType.EXCLUDED
for option_key, option in hollow_knight_randomize_options.items():
- randomized = getattr(self.multiworld, option_key)[self.player]
- if all([not randomized, option_key in logicless_options, not self.multiworld.AddUnshuffledLocations[self.player]]):
+ randomized = getattr(self.options, option_key)
+ if all([not randomized, option_key in logicless_options, not self.options.AddUnshuffledLocations]):
continue
for item_name, location_name in zip(option.items, option.locations):
if item_name in junk_replace:
item_name = self.get_filler_item_name()
- if (item_name == "Crystal_Heart" and self.multiworld.SplitCrystalHeart[self.player]) or \
- (item_name == "Mothwing_Cloak" and self.multiworld.SplitMothwingCloak[self.player]):
+ if (item_name == "Crystal_Heart" and self.options.SplitCrystalHeart) or \
+ (item_name == "Mothwing_Cloak" and self.options.SplitMothwingCloak):
_add("Left_" + item_name, location_name, randomized)
_add("Right_" + item_name, "Split_" + location_name, randomized)
continue
- if item_name == "Mantis_Claw" and self.multiworld.SplitMantisClaw[self.player]:
+ if item_name == "Mantis_Claw" and self.options.SplitMantisClaw:
_add("Left_" + item_name, "Left_" + location_name, randomized)
_add("Right_" + item_name, "Right_" + location_name, randomized)
continue
- if item_name == "Shade_Cloak" and self.multiworld.SplitMothwingCloak[self.player]:
- if self.multiworld.random.randint(0, 1):
+ if item_name == "Shade_Cloak" and self.options.SplitMothwingCloak:
+ if self.random.randint(0, 1):
item_name = "Left_Mothwing_Cloak"
else:
item_name = "Right_Mothwing_Cloak"
- if item_name == "Grimmchild2" and self.multiworld.RandomizeGrimmkinFlames[self.player] and self.multiworld.RandomizeCharms[self.player]:
+ if item_name == "Grimmchild2" and self.options.RandomizeGrimmkinFlames and self.options.RandomizeCharms:
_add("Grimmchild1", location_name, randomized)
continue
_add(item_name, location_name, randomized)
- if self.multiworld.RandomizeElevatorPass[self.player]:
+ if self.options.RandomizeElevatorPass:
randomized = True
_add("Elevator_Pass", "Elevator_Pass", randomized)
for shop, locations in self.created_multi_locations.items():
- for _ in range(len(locations), getattr(self.multiworld, shop_to_option[shop])[self.player].value):
+ for _ in range(len(locations), getattr(self.options, shop_to_option[shop]).value):
loc = self.create_location(shop)
unfilled_locations += 1
# Balance the pool
item_count = len(pool)
- additional_shop_items = max(item_count - unfilled_locations, self.multiworld.ExtraShopSlots[self.player].value)
+ additional_shop_items = max(item_count - unfilled_locations, self.options.ExtraShopSlots.value)
# Add additional shop items, as needed.
if additional_shop_items > 0:
shops = list(shop for shop, locations in self.created_multi_locations.items() if len(locations) < 16)
- if not self.multiworld.EggShopSlots[self.player].value: # No eggshop, so don't place items there
+ if not self.options.EggShopSlots: # No eggshop, so don't place items there
shops.remove('Egg_Shop')
if shops:
for _ in range(additional_shop_items):
- shop = self.multiworld.random.choice(shops)
+ shop = self.random.choice(shops)
loc = self.create_location(shop)
unfilled_locations += 1
if len(self.created_multi_locations[shop]) >= 16:
@@ -355,7 +356,7 @@ class HKWorld(World):
loc.costs = costs
def apply_costsanity(self):
- setting = self.multiworld.CostSanity[self.player].value
+ setting = self.options.CostSanity.value
if not setting:
return # noop
@@ -369,10 +370,10 @@ class HKWorld(World):
return {k: v for k, v in weights.items() if v}
- random = self.multiworld.random
- hybrid_chance = getattr(self.multiworld, f"CostSanityHybridChance")[self.player].value
+ random = self.random
+ hybrid_chance = getattr(self.options, f"CostSanityHybridChance").value
weights = {
- data.term: getattr(self.multiworld, f"CostSanity{data.option}Weight")[self.player].value
+ data.term: getattr(self.options, f"CostSanity{data.option}Weight").value
for data in cost_terms.values()
}
weights_geoless = dict(weights)
@@ -427,22 +428,22 @@ class HKWorld(World):
location.sort_costs()
def set_rules(self):
- world = self.multiworld
+ multiworld = self.multiworld
player = self.player
- goal = world.Goal[player]
+ goal = self.options.Goal
if goal == Goal.option_hollowknight:
- world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player)
+ multiworld.completion_condition[player] = lambda state: _hk_can_beat_thk(state, player)
elif goal == Goal.option_siblings:
- world.completion_condition[player] = lambda state: state._hk_siblings_ending(player)
+ multiworld.completion_condition[player] = lambda state: _hk_siblings_ending(state, player)
elif goal == Goal.option_radiance:
- world.completion_condition[player] = lambda state: state._hk_can_beat_radiance(player)
+ multiworld.completion_condition[player] = lambda state: _hk_can_beat_radiance(state, player)
elif goal == Goal.option_godhome:
- world.completion_condition[player] = lambda state: state.count("Defeated_Pantheon_5", player)
+ multiworld.completion_condition[player] = lambda state: state.count("Defeated_Pantheon_5", player)
elif goal == Goal.option_godhome_flower:
- world.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player)
+ multiworld.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player)
else:
# Any goal
- world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player) or state._hk_can_beat_radiance(player)
+ multiworld.completion_condition[player] = lambda state: _hk_can_beat_thk(state, player) or _hk_can_beat_radiance(state, player)
set_rules(self)
@@ -450,8 +451,8 @@ class HKWorld(World):
slot_data = {}
options = slot_data["options"] = {}
- for option_name in self.option_definitions:
- option = getattr(self.multiworld, option_name)[self.player]
+ for option_name in hollow_knight_options:
+ option = getattr(self.options, option_name)
try:
optionvalue = int(option.value)
except TypeError:
@@ -460,10 +461,10 @@ class HKWorld(World):
options[option_name] = optionvalue
# 32 bit int
- slot_data["seed"] = self.multiworld.per_slot_randoms[self.player].randint(-2147483647, 2147483646)
+ slot_data["seed"] = self.random.randint(-2147483647, 2147483646)
# Backwards compatibility for shop cost data (HKAP < 0.1.0)
- if not self.multiworld.CostSanity[self.player]:
+ if not self.options.CostSanity:
for shop, terms in shop_cost_types.items():
unit = cost_terms[next(iter(terms))].option
if unit == "Geo":
@@ -498,7 +499,7 @@ class HKWorld(World):
basename = name
if name in shop_cost_types:
costs = {
- term: self.multiworld.random.randint(*self.ranges[term])
+ term: self.random.randint(*self.ranges[term])
for term in shop_cost_types[name]
}
elif name in vanilla_location_costs:
@@ -512,7 +513,7 @@ class HKWorld(World):
region = self.multiworld.get_region("Menu", self.player)
- if vanilla and not self.multiworld.AddUnshuffledLocations[self.player]:
+ if vanilla and not self.options.AddUnshuffledLocations:
loc = HKLocation(self.player, name,
None, region, costs=costs, vanilla=vanilla,
basename=basename)
@@ -560,26 +561,26 @@ class HKWorld(World):
return change
@classmethod
- def stage_write_spoiler(cls, world: MultiWorld, spoiler_handle):
- hk_players = world.get_game_players(cls.game)
+ def stage_write_spoiler(cls, multiworld: MultiWorld, spoiler_handle):
+ hk_players = multiworld.get_game_players(cls.game)
spoiler_handle.write('\n\nCharm Notches:')
for player in hk_players:
- name = world.get_player_name(player)
+ name = multiworld.get_player_name(player)
spoiler_handle.write(f'\n{name}\n')
- hk_world: HKWorld = world.worlds[player]
+ hk_world: HKWorld = multiworld.worlds[player]
for charm_number, cost in enumerate(hk_world.charm_costs):
spoiler_handle.write(f"\n{charm_names[charm_number]}: {cost}")
spoiler_handle.write('\n\nShop Prices:')
for player in hk_players:
- name = world.get_player_name(player)
+ name = multiworld.get_player_name(player)
spoiler_handle.write(f'\n{name}\n')
- hk_world: HKWorld = world.worlds[player]
+ hk_world: HKWorld = multiworld.worlds[player]
- if world.CostSanity[player].value:
+ if hk_world.options.CostSanity:
for loc in sorted(
(
- loc for loc in itertools.chain(*(region.locations for region in world.get_regions(player)))
+ loc for loc in itertools.chain(*(region.locations for region in multiworld.get_regions(player)))
if loc.costs
), key=operator.attrgetter('name')
):
@@ -603,15 +604,15 @@ class HKWorld(World):
'RandomizeGeoRocks', 'RandomizeSoulTotems', 'RandomizeLoreTablets', 'RandomizeJunkPitChests',
'RandomizeRancidEggs'
):
- if getattr(self.multiworld, group):
+ if getattr(self.options, group):
fillers.extend(item for item in hollow_knight_randomize_options[group].items if item not in
exclusions)
self.cached_filler_items[self.player] = fillers
- return self.multiworld.random.choice(self.cached_filler_items[self.player])
+ return self.random.choice(self.cached_filler_items[self.player])
-def create_region(world: MultiWorld, player: int, name: str, location_names=None) -> Region:
- ret = Region(name, player, world)
+def create_region(multiworld: MultiWorld, player: int, name: str, location_names=None) -> Region:
+ ret = Region(name, player, multiworld)
if location_names:
for location in location_names:
loc_id = HKWorld.location_name_to_id.get(location, None)
@@ -684,42 +685,7 @@ class HKLogicMixin(LogicMixin):
return sum(self.multiworld.worlds[player].charm_costs[notch] for notch in notches)
def _hk_option(self, player: int, option_name: str) -> int:
- return getattr(self.multiworld, option_name)[player].value
+ return getattr(self.multiworld.worlds[player].options, option_name).value
def _hk_start(self, player, start_location: str) -> bool:
- return self.multiworld.StartLocation[player] == start_location
-
- def _hk_nail_combat(self, player: int) -> bool:
- return self.has_any({'LEFTSLASH', 'RIGHTSLASH', 'UPSLASH'}, player)
-
- def _hk_can_beat_thk(self, player: int) -> bool:
- return (
- self.has('Opened_Black_Egg_Temple', player)
- and (self.count('FIREBALL', player) + self.count('SCREAM', player) + self.count('QUAKE', player)) > 1
- and self._hk_nail_combat(player)
- and (
- self.has_any({'LEFTDASH', 'RIGHTDASH'}, player)
- or self._hk_option(player, 'ProficientCombat')
- )
- and self.has('FOCUS', player)
- )
-
- def _hk_siblings_ending(self, player: int) -> bool:
- return self._hk_can_beat_thk(player) and self.has('WHITEFRAGMENT', player, 3)
-
- def _hk_can_beat_radiance(self, player: int) -> bool:
- return (
- self.has('Opened_Black_Egg_Temple', player)
- and self._hk_nail_combat(player)
- and self.has('WHITEFRAGMENT', player, 3)
- and self.has('DREAMNAIL', player)
- and (
- (self.has('LEFTCLAW', player) and self.has('RIGHTCLAW', player))
- or self.has('WINGS', player)
- )
- and (self.count('FIREBALL', player) + self.count('SCREAM', player) + self.count('QUAKE', player)) > 1
- and (
- (self.has('LEFTDASH', player, 2) and self.has('RIGHTDASH', player, 2)) # Both Shade Cloaks
- or (self._hk_option(player, 'ProficientCombat') and self.has('QUAKE', player)) # or Dive
- )
- )
+ return self.multiworld.worlds[player].options.StartLocation == start_location
diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml
index 1c9f4e551d..950fd32674 100644
--- a/worlds/lingo/data/LL1.yaml
+++ b/worlds/lingo/data/LL1.yaml
@@ -1556,6 +1556,8 @@
room: Owl Hallway
door: Shortcut to Hedge Maze
Roof: True
+ The Incomparable:
+ door: Observant Entrance
panels:
DOWN:
id: Maze Room/Panel_down_up
@@ -1967,6 +1969,9 @@
door: Eight Door
Orange Tower Sixth Floor:
painting: True
+ Hedge Maze:
+ room: Hedge Maze
+ door: Observant Entrance
panels:
Achievement:
id: Countdown Panels/Panel_incomparable_incomparable
@@ -7649,6 +7654,8 @@
LEAP:
id: Double Room/Panel_leap_leap
tag: midwhite
+ required_door:
+ door: Door to Cross
doors:
Door to Cross:
id: Double Room Area Doors/Door_room_4a
diff --git a/worlds/lingo/data/generated.dat b/worlds/lingo/data/generated.dat
index d221b8168d..9a49d3d9d4 100644
Binary files a/worlds/lingo/data/generated.dat and b/worlds/lingo/data/generated.dat differ
diff --git a/worlds/sc2/requirements.txt b/worlds/sc2/requirements.txt
index 9b84863c45..5bc808b639 100644
--- a/worlds/sc2/requirements.txt
+++ b/worlds/sc2/requirements.txt
@@ -1,2 +1 @@
nest-asyncio >= 1.5.5
-six >= 1.16.0
\ No newline at end of file
diff --git a/worlds/smz3/__init__.py b/worlds/smz3/__init__.py
index 6056a171d3..d78c9f7d82 100644
--- a/worlds/smz3/__init__.py
+++ b/worlds/smz3/__init__.py
@@ -215,7 +215,6 @@ class SMZ3World(World):
niceItems = TotalSMZ3Item.Item.CreateNicePool(self.smz3World)
junkItems = TotalSMZ3Item.Item.CreateJunkPool(self.smz3World)
- allJunkItems = niceItems + junkItems
self.junkItemsNames = [item.Type.name for item in junkItems]
if (self.smz3World.Config.Keysanity):
@@ -228,7 +227,8 @@ class SMZ3World(World):
self.multiworld.push_precollected(SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item))
itemPool = [SMZ3Item(item.Type.name, ItemClassification.progression, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in progressionItems] + \
- [SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in allJunkItems]
+ [SMZ3Item(item.Type.name, ItemClassification.useful, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in niceItems] + \
+ [SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in junkItems]
self.smz3DungeonItems = [SMZ3Item(item.Type.name, ItemClassification.progression, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in self.dungeon]
self.multiworld.itempool += itemPool
diff --git a/worlds/stardew_valley/data/locations.csv b/worlds/stardew_valley/data/locations.csv
index 0d7a10f954..608b6a5f57 100644
--- a/worlds/stardew_valley/data/locations.csv
+++ b/worlds/stardew_valley/data/locations.csv
@@ -2212,7 +2212,7 @@ id,region,name,tags,mod_name
3808,Shipping,Shipsanity: Mystery Box,"SHIPSANITY",
3809,Shipping,Shipsanity: Golden Tag,"SHIPSANITY",
3810,Shipping,Shipsanity: Deluxe Bait,"SHIPSANITY",
-3811,Shipping,Shipsanity: Moss,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",
+3811,Shipping,Shipsanity: Moss,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
3812,Shipping,Shipsanity: Mossy Seed,"SHIPSANITY",
3813,Shipping,Shipsanity: Sonar Bobber,"SHIPSANITY",
3814,Shipping,Shipsanity: Tent Kit,"SHIPSANITY",
diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py
index b3aa1e6a34..5253e99514 100644
--- a/worlds/tunic/__init__.py
+++ b/worlds/tunic/__init__.py
@@ -121,7 +121,7 @@ class TunicWorld(World):
cls.seed_groups[group] = SeedGroup(logic_rules=tunic.options.logic_rules.value,
laurels_at_10_fairies=tunic.options.laurels_location == 3,
fixed_shop=bool(tunic.options.fixed_shop),
- plando=multiworld.plando_connections[tunic.player])
+ plando=tunic.options.plando_connections)
continue
# lower value is more restrictive
@@ -134,9 +134,9 @@ class TunicWorld(World):
if tunic.options.fixed_shop:
cls.seed_groups[group]["fixed_shop"] = True
- if multiworld.plando_connections[tunic.player]:
+ if tunic.options.plando_connections:
# loop through the connections in the player's yaml
- for cxn in multiworld.plando_connections[tunic.player]:
+ for cxn in tunic.options.plando_connections:
new_cxn = True
for group_cxn in cls.seed_groups[group]["plando"]:
# if neither entrance nor exit match anything in the group, add to group