HK: Options API updates, et al. (#3428)
* updates HK to consistently use world.random, use world.options, don't use world = self.multiworld, and remove some things from the logicMixin * Update HK to new options dataclass * Move completion condition helpers to Rules.py * updates from review
This commit is contained in:
parent
ab0903679c
commit
e764da3dc6
|
@ -1,10 +1,12 @@
|
||||||
import typing
|
import typing
|
||||||
import re
|
import re
|
||||||
|
from dataclasses import dataclass, make_dataclass
|
||||||
|
|
||||||
from .ExtractedData import logic_options, starts, pool_options
|
from .ExtractedData import logic_options, starts, pool_options
|
||||||
from .Rules import cost_terms
|
from .Rules import cost_terms
|
||||||
from schema import And, Schema, Optional
|
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
|
from .Charms import vanilla_costs, names as charm_names
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
|
@ -538,3 +540,5 @@ hollow_knight_options: typing.Dict[str, type(Option)] = {
|
||||||
},
|
},
|
||||||
**cost_sanity_weights
|
**cost_sanity_weights
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HKOptions = make_dataclass("HKOptions", [(name, option) for name, option in hollow_knight_options.items()], bases=(PerGameCommonOptions,))
|
||||||
|
|
|
@ -49,3 +49,42 @@ def set_rules(hk_world: World):
|
||||||
if term == "GEO": # No geo logic!
|
if term == "GEO": # No geo logic!
|
||||||
continue
|
continue
|
||||||
add_rule(location, lambda state, term=term, amount=amount: state.count(term, player) >= amount)
|
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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
@ -10,9 +10,9 @@ logger = logging.getLogger("Hollow Knight")
|
||||||
|
|
||||||
from .Items import item_table, lookup_type_to_names, item_name_groups
|
from .Items import item_table, lookup_type_to_names, item_name_groups
|
||||||
from .Regions import create_regions
|
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, \
|
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, \
|
from .ExtractedData import locations, starts, multi_locations, location_to_region_lookup, \
|
||||||
event_names, item_effects, connectors, one_ways, vanilla_shop_costs, vanilla_location_costs
|
event_names, item_effects, connectors, one_ways, vanilla_shop_costs, vanilla_location_costs
|
||||||
from .Charms import names as charm_names
|
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.
|
As the enigmatic Knight, you’ll traverse the depths, unravel its mysteries and conquer its evils.
|
||||||
""" # from https://www.hollowknight.com
|
""" # from https://www.hollowknight.com
|
||||||
game: str = "Hollow Knight"
|
game: str = "Hollow Knight"
|
||||||
option_definitions = hollow_knight_options
|
options_dataclass = HKOptions
|
||||||
|
options: HKOptions
|
||||||
|
|
||||||
web = HKWeb()
|
web = HKWeb()
|
||||||
|
|
||||||
|
@ -155,8 +156,8 @@ class HKWorld(World):
|
||||||
charm_costs: typing.List[int]
|
charm_costs: typing.List[int]
|
||||||
cached_filler_items = {}
|
cached_filler_items = {}
|
||||||
|
|
||||||
def __init__(self, world, player):
|
def __init__(self, multiworld, player):
|
||||||
super(HKWorld, self).__init__(world, player)
|
super(HKWorld, self).__init__(multiworld, player)
|
||||||
self.created_multi_locations: typing.Dict[str, typing.List[HKLocation]] = {
|
self.created_multi_locations: typing.Dict[str, typing.List[HKLocation]] = {
|
||||||
location: list() for location in multi_locations
|
location: list() for location in multi_locations
|
||||||
}
|
}
|
||||||
|
@ -165,29 +166,29 @@ class HKWorld(World):
|
||||||
self.vanilla_shop_costs = deepcopy(vanilla_shop_costs)
|
self.vanilla_shop_costs = deepcopy(vanilla_shop_costs)
|
||||||
|
|
||||||
def generate_early(self):
|
def generate_early(self):
|
||||||
world = self.multiworld
|
options = self.options
|
||||||
charm_costs = world.RandomCharmCosts[self.player].get_costs(world.random)
|
charm_costs = options.RandomCharmCosts.get_costs(self.random)
|
||||||
self.charm_costs = world.PlandoCharmCosts[self.player].get_costs(charm_costs)
|
self.charm_costs = options.PlandoCharmCosts.get_costs(charm_costs)
|
||||||
# world.exclude_locations[self.player].value.update(white_palace_locations)
|
# options.exclude_locations.value.update(white_palace_locations)
|
||||||
for term, data in cost_terms.items():
|
for term, data in cost_terms.items():
|
||||||
mini = getattr(world, f"Minimum{data.option}Price")[self.player]
|
mini = getattr(options, f"Minimum{data.option}Price")
|
||||||
maxi = getattr(world, f"Maximum{data.option}Price")[self.player]
|
maxi = getattr(options, f"Maximum{data.option}Price")
|
||||||
# if minimum > maximum, set minimum to maximum
|
# if minimum > maximum, set minimum to maximum
|
||||||
mini.value = min(mini.value, maxi.value)
|
mini.value = min(mini.value, maxi.value)
|
||||||
self.ranges[term] = 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))
|
True, None, "Event", self.player))
|
||||||
|
|
||||||
def white_palace_exclusions(self):
|
def white_palace_exclusions(self):
|
||||||
exclusions = set()
|
exclusions = set()
|
||||||
wp = self.multiworld.WhitePalace[self.player]
|
wp = self.options.WhitePalace
|
||||||
if wp <= WhitePalace.option_nopathofpain:
|
if wp <= WhitePalace.option_nopathofpain:
|
||||||
exclusions.update(path_of_pain_locations)
|
exclusions.update(path_of_pain_locations)
|
||||||
if wp <= WhitePalace.option_kingfragment:
|
if wp <= WhitePalace.option_kingfragment:
|
||||||
exclusions.update(white_palace_checks)
|
exclusions.update(white_palace_checks)
|
||||||
if wp == WhitePalace.option_exclude:
|
if wp == WhitePalace.option_exclude:
|
||||||
exclusions.add("King_Fragment")
|
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
|
# 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_transitions)
|
||||||
exclusions.update(white_palace_events)
|
exclusions.update(white_palace_events)
|
||||||
|
@ -200,7 +201,7 @@ class HKWorld(World):
|
||||||
|
|
||||||
# check for any goal that godhome events are relevant to
|
# check for any goal that godhome events are relevant to
|
||||||
all_event_names = event_names.copy()
|
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
|
from .GodhomeData import godhome_event_names
|
||||||
all_event_names.update(set(godhome_event_names))
|
all_event_names.update(set(godhome_event_names))
|
||||||
|
|
||||||
|
@ -230,12 +231,12 @@ class HKWorld(World):
|
||||||
pool: typing.List[HKItem] = []
|
pool: typing.List[HKItem] = []
|
||||||
wp_exclusions = self.white_palace_exclusions()
|
wp_exclusions = self.white_palace_exclusions()
|
||||||
junk_replace: typing.Set[str] = set()
|
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"))
|
junk_replace.update(("Abyss_Shriek", "Shade_Soul", "Descending_Dark"))
|
||||||
|
|
||||||
randomized_starting_items = set()
|
randomized_starting_items = set()
|
||||||
for attr, items in randomizable_starting_items.items():
|
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)
|
randomized_starting_items.update(items)
|
||||||
|
|
||||||
# noinspection PyShadowingNames
|
# noinspection PyShadowingNames
|
||||||
|
@ -257,7 +258,7 @@ class HKWorld(World):
|
||||||
if item_name in junk_replace:
|
if item_name in junk_replace:
|
||||||
item_name = self.get_filler_item_name()
|
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 location_name == "Start":
|
||||||
if item_name in randomized_starting_items:
|
if item_name in randomized_starting_items:
|
||||||
|
@ -281,55 +282,55 @@ class HKWorld(World):
|
||||||
location.progress_type = LocationProgressType.EXCLUDED
|
location.progress_type = LocationProgressType.EXCLUDED
|
||||||
|
|
||||||
for option_key, option in hollow_knight_randomize_options.items():
|
for option_key, option in hollow_knight_randomize_options.items():
|
||||||
randomized = getattr(self.multiworld, option_key)[self.player]
|
randomized = getattr(self.options, option_key)
|
||||||
if all([not randomized, option_key in logicless_options, not self.multiworld.AddUnshuffledLocations[self.player]]):
|
if all([not randomized, option_key in logicless_options, not self.options.AddUnshuffledLocations]):
|
||||||
continue
|
continue
|
||||||
for item_name, location_name in zip(option.items, option.locations):
|
for item_name, location_name in zip(option.items, option.locations):
|
||||||
if item_name in junk_replace:
|
if item_name in junk_replace:
|
||||||
item_name = self.get_filler_item_name()
|
item_name = self.get_filler_item_name()
|
||||||
|
|
||||||
if (item_name == "Crystal_Heart" and self.multiworld.SplitCrystalHeart[self.player]) or \
|
if (item_name == "Crystal_Heart" and self.options.SplitCrystalHeart) or \
|
||||||
(item_name == "Mothwing_Cloak" and self.multiworld.SplitMothwingCloak[self.player]):
|
(item_name == "Mothwing_Cloak" and self.options.SplitMothwingCloak):
|
||||||
_add("Left_" + item_name, location_name, randomized)
|
_add("Left_" + item_name, location_name, randomized)
|
||||||
_add("Right_" + item_name, "Split_" + location_name, randomized)
|
_add("Right_" + item_name, "Split_" + location_name, randomized)
|
||||||
continue
|
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("Left_" + item_name, "Left_" + location_name, randomized)
|
||||||
_add("Right_" + item_name, "Right_" + location_name, randomized)
|
_add("Right_" + item_name, "Right_" + location_name, randomized)
|
||||||
continue
|
continue
|
||||||
if item_name == "Shade_Cloak" and self.multiworld.SplitMothwingCloak[self.player]:
|
if item_name == "Shade_Cloak" and self.options.SplitMothwingCloak:
|
||||||
if self.multiworld.random.randint(0, 1):
|
if self.random.randint(0, 1):
|
||||||
item_name = "Left_Mothwing_Cloak"
|
item_name = "Left_Mothwing_Cloak"
|
||||||
else:
|
else:
|
||||||
item_name = "Right_Mothwing_Cloak"
|
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)
|
_add("Grimmchild1", location_name, randomized)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
_add(item_name, location_name, randomized)
|
_add(item_name, location_name, randomized)
|
||||||
|
|
||||||
if self.multiworld.RandomizeElevatorPass[self.player]:
|
if self.options.RandomizeElevatorPass:
|
||||||
randomized = True
|
randomized = True
|
||||||
_add("Elevator_Pass", "Elevator_Pass", randomized)
|
_add("Elevator_Pass", "Elevator_Pass", randomized)
|
||||||
|
|
||||||
for shop, locations in self.created_multi_locations.items():
|
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)
|
loc = self.create_location(shop)
|
||||||
unfilled_locations += 1
|
unfilled_locations += 1
|
||||||
|
|
||||||
# Balance the pool
|
# Balance the pool
|
||||||
item_count = len(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.
|
# Add additional shop items, as needed.
|
||||||
if additional_shop_items > 0:
|
if additional_shop_items > 0:
|
||||||
shops = list(shop for shop, locations in self.created_multi_locations.items() if len(locations) < 16)
|
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')
|
shops.remove('Egg_Shop')
|
||||||
|
|
||||||
if shops:
|
if shops:
|
||||||
for _ in range(additional_shop_items):
|
for _ in range(additional_shop_items):
|
||||||
shop = self.multiworld.random.choice(shops)
|
shop = self.random.choice(shops)
|
||||||
loc = self.create_location(shop)
|
loc = self.create_location(shop)
|
||||||
unfilled_locations += 1
|
unfilled_locations += 1
|
||||||
if len(self.created_multi_locations[shop]) >= 16:
|
if len(self.created_multi_locations[shop]) >= 16:
|
||||||
|
@ -355,7 +356,7 @@ class HKWorld(World):
|
||||||
loc.costs = costs
|
loc.costs = costs
|
||||||
|
|
||||||
def apply_costsanity(self):
|
def apply_costsanity(self):
|
||||||
setting = self.multiworld.CostSanity[self.player].value
|
setting = self.options.CostSanity.value
|
||||||
if not setting:
|
if not setting:
|
||||||
return # noop
|
return # noop
|
||||||
|
|
||||||
|
@ -369,10 +370,10 @@ class HKWorld(World):
|
||||||
|
|
||||||
return {k: v for k, v in weights.items() if v}
|
return {k: v for k, v in weights.items() if v}
|
||||||
|
|
||||||
random = self.multiworld.random
|
random = self.random
|
||||||
hybrid_chance = getattr(self.multiworld, f"CostSanityHybridChance")[self.player].value
|
hybrid_chance = getattr(self.options, f"CostSanityHybridChance").value
|
||||||
weights = {
|
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()
|
for data in cost_terms.values()
|
||||||
}
|
}
|
||||||
weights_geoless = dict(weights)
|
weights_geoless = dict(weights)
|
||||||
|
@ -427,22 +428,22 @@ class HKWorld(World):
|
||||||
location.sort_costs()
|
location.sort_costs()
|
||||||
|
|
||||||
def set_rules(self):
|
def set_rules(self):
|
||||||
world = self.multiworld
|
multiworld = self.multiworld
|
||||||
player = self.player
|
player = self.player
|
||||||
goal = world.Goal[player]
|
goal = self.options.Goal
|
||||||
if goal == Goal.option_hollowknight:
|
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:
|
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:
|
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:
|
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:
|
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:
|
else:
|
||||||
# Any goal
|
# 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)
|
set_rules(self)
|
||||||
|
|
||||||
|
@ -450,8 +451,8 @@ class HKWorld(World):
|
||||||
slot_data = {}
|
slot_data = {}
|
||||||
|
|
||||||
options = slot_data["options"] = {}
|
options = slot_data["options"] = {}
|
||||||
for option_name in self.option_definitions:
|
for option_name in hollow_knight_options:
|
||||||
option = getattr(self.multiworld, option_name)[self.player]
|
option = getattr(self.options, option_name)
|
||||||
try:
|
try:
|
||||||
optionvalue = int(option.value)
|
optionvalue = int(option.value)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -460,10 +461,10 @@ class HKWorld(World):
|
||||||
options[option_name] = optionvalue
|
options[option_name] = optionvalue
|
||||||
|
|
||||||
# 32 bit int
|
# 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)
|
# 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():
|
for shop, terms in shop_cost_types.items():
|
||||||
unit = cost_terms[next(iter(terms))].option
|
unit = cost_terms[next(iter(terms))].option
|
||||||
if unit == "Geo":
|
if unit == "Geo":
|
||||||
|
@ -498,7 +499,7 @@ class HKWorld(World):
|
||||||
basename = name
|
basename = name
|
||||||
if name in shop_cost_types:
|
if name in shop_cost_types:
|
||||||
costs = {
|
costs = {
|
||||||
term: self.multiworld.random.randint(*self.ranges[term])
|
term: self.random.randint(*self.ranges[term])
|
||||||
for term in shop_cost_types[name]
|
for term in shop_cost_types[name]
|
||||||
}
|
}
|
||||||
elif name in vanilla_location_costs:
|
elif name in vanilla_location_costs:
|
||||||
|
@ -512,7 +513,7 @@ class HKWorld(World):
|
||||||
|
|
||||||
region = self.multiworld.get_region("Menu", self.player)
|
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,
|
loc = HKLocation(self.player, name,
|
||||||
None, region, costs=costs, vanilla=vanilla,
|
None, region, costs=costs, vanilla=vanilla,
|
||||||
basename=basename)
|
basename=basename)
|
||||||
|
@ -560,26 +561,26 @@ class HKWorld(World):
|
||||||
return change
|
return change
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def stage_write_spoiler(cls, world: MultiWorld, spoiler_handle):
|
def stage_write_spoiler(cls, multiworld: MultiWorld, spoiler_handle):
|
||||||
hk_players = world.get_game_players(cls.game)
|
hk_players = multiworld.get_game_players(cls.game)
|
||||||
spoiler_handle.write('\n\nCharm Notches:')
|
spoiler_handle.write('\n\nCharm Notches:')
|
||||||
for player in hk_players:
|
for player in hk_players:
|
||||||
name = world.get_player_name(player)
|
name = multiworld.get_player_name(player)
|
||||||
spoiler_handle.write(f'\n{name}\n')
|
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):
|
for charm_number, cost in enumerate(hk_world.charm_costs):
|
||||||
spoiler_handle.write(f"\n{charm_names[charm_number]}: {cost}")
|
spoiler_handle.write(f"\n{charm_names[charm_number]}: {cost}")
|
||||||
|
|
||||||
spoiler_handle.write('\n\nShop Prices:')
|
spoiler_handle.write('\n\nShop Prices:')
|
||||||
for player in hk_players:
|
for player in hk_players:
|
||||||
name = world.get_player_name(player)
|
name = multiworld.get_player_name(player)
|
||||||
spoiler_handle.write(f'\n{name}\n')
|
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(
|
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
|
if loc.costs
|
||||||
), key=operator.attrgetter('name')
|
), key=operator.attrgetter('name')
|
||||||
):
|
):
|
||||||
|
@ -603,15 +604,15 @@ class HKWorld(World):
|
||||||
'RandomizeGeoRocks', 'RandomizeSoulTotems', 'RandomizeLoreTablets', 'RandomizeJunkPitChests',
|
'RandomizeGeoRocks', 'RandomizeSoulTotems', 'RandomizeLoreTablets', 'RandomizeJunkPitChests',
|
||||||
'RandomizeRancidEggs'
|
'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
|
fillers.extend(item for item in hollow_knight_randomize_options[group].items if item not in
|
||||||
exclusions)
|
exclusions)
|
||||||
self.cached_filler_items[self.player] = fillers
|
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:
|
def create_region(multiworld: MultiWorld, player: int, name: str, location_names=None) -> Region:
|
||||||
ret = Region(name, player, world)
|
ret = Region(name, player, multiworld)
|
||||||
if location_names:
|
if location_names:
|
||||||
for location in location_names:
|
for location in location_names:
|
||||||
loc_id = HKWorld.location_name_to_id.get(location, None)
|
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)
|
return sum(self.multiworld.worlds[player].charm_costs[notch] for notch in notches)
|
||||||
|
|
||||||
def _hk_option(self, player: int, option_name: str) -> int:
|
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:
|
def _hk_start(self, player, start_location: str) -> bool:
|
||||||
return self.multiworld.StartLocation[player] == start_location
|
return self.multiworld.worlds[player].options.StartLocation == 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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
Loading…
Reference in New Issue