HK: add grub hunt goal (#3203)
* makes grub hunt goal option that calculates the total available grubs (including item link replacements) and requires all of them to be gathered for goal completion * update slot data name for grub count * add option to set number needed for grub hub * updates to grub hunt goal based on review * copy/paste fix * account for 'any' goal and fix overriding non-grub goals * making sure godhome is in logic for any and removing redundancy on completion condition * fix typing * i hate typing * move to stage_pre_fill * modify "any" goal so all goals are in logic under minimal settings * rewrite grub counting to create lookups for grubs and groups that can be reused * use generator instead of list comprehension * fix whitespace merging wrong * minor code cleanup
This commit is contained in:
parent
575c338aa3
commit
6803c373e5
|
@ -405,9 +405,20 @@ class Goal(Choice):
|
||||||
option_radiance = 3
|
option_radiance = 3
|
||||||
option_godhome = 4
|
option_godhome = 4
|
||||||
option_godhome_flower = 5
|
option_godhome_flower = 5
|
||||||
|
option_grub_hunt = 6
|
||||||
default = 0
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
|
class GrubHuntGoal(NamedRange):
|
||||||
|
"""The amount of grubs required to finish Grub Hunt.
|
||||||
|
On 'All' any grubs from item links replacements etc. will be counted"""
|
||||||
|
display_name = "Grub Hunt Goal"
|
||||||
|
range_start = 1
|
||||||
|
range_end = 46
|
||||||
|
special_range_names = {"all": -1}
|
||||||
|
default = 46
|
||||||
|
|
||||||
|
|
||||||
class WhitePalace(Choice):
|
class WhitePalace(Choice):
|
||||||
"""
|
"""
|
||||||
Whether or not to include White Palace or not. Note: Even if excluded, the King Fragment check may still be
|
Whether or not to include White Palace or not. Note: Even if excluded, the King Fragment check may still be
|
||||||
|
@ -522,7 +533,7 @@ hollow_knight_options: typing.Dict[str, type(Option)] = {
|
||||||
**{
|
**{
|
||||||
option.__name__: option
|
option.__name__: option
|
||||||
for option in (
|
for option in (
|
||||||
StartLocation, Goal, WhitePalace, ExtraPlatforms, AddUnshuffledLocations, StartingGeo,
|
StartLocation, Goal, GrubHuntGoal, WhitePalace, ExtraPlatforms, AddUnshuffledLocations, StartingGeo,
|
||||||
DeathLink, DeathLinkShade, DeathLinkBreaksFragileCharms,
|
DeathLink, DeathLinkShade, DeathLinkBreaksFragileCharms,
|
||||||
MinimumGeoPrice, MaximumGeoPrice,
|
MinimumGeoPrice, MaximumGeoPrice,
|
||||||
MinimumGrubPrice, MaximumGrubPrice,
|
MinimumGrubPrice, MaximumGrubPrice,
|
||||||
|
|
|
@ -5,6 +5,7 @@ import typing
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
import itertools
|
import itertools
|
||||||
import operator
|
import operator
|
||||||
|
from collections import defaultdict, Counter
|
||||||
|
|
||||||
logger = logging.getLogger("Hollow Knight")
|
logger = logging.getLogger("Hollow Knight")
|
||||||
|
|
||||||
|
@ -12,12 +13,12 @@ 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, _hk_can_beat_thk, _hk_siblings_ending, _hk_can_beat_radiance
|
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, HKOptions
|
shop_to_option, HKOptions, GrubHuntGoal
|
||||||
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
|
||||||
|
|
||||||
from BaseClasses import Region, Location, MultiWorld, Item, LocationProgressType, Tutorial, ItemClassification
|
from BaseClasses import Region, Location, MultiWorld, Item, LocationProgressType, Tutorial, ItemClassification, CollectionState
|
||||||
from worlds.AutoWorld import World, LogicMixin, WebWorld
|
from worlds.AutoWorld import World, LogicMixin, WebWorld
|
||||||
|
|
||||||
path_of_pain_locations = {
|
path_of_pain_locations = {
|
||||||
|
@ -155,6 +156,7 @@ class HKWorld(World):
|
||||||
ranges: typing.Dict[str, typing.Tuple[int, int]]
|
ranges: typing.Dict[str, typing.Tuple[int, int]]
|
||||||
charm_costs: typing.List[int]
|
charm_costs: typing.List[int]
|
||||||
cached_filler_items = {}
|
cached_filler_items = {}
|
||||||
|
grub_count: int
|
||||||
|
|
||||||
def __init__(self, multiworld, player):
|
def __init__(self, multiworld, player):
|
||||||
super(HKWorld, self).__init__(multiworld, player)
|
super(HKWorld, self).__init__(multiworld, player)
|
||||||
|
@ -164,6 +166,7 @@ class HKWorld(World):
|
||||||
self.ranges = {}
|
self.ranges = {}
|
||||||
self.created_shop_items = 0
|
self.created_shop_items = 0
|
||||||
self.vanilla_shop_costs = deepcopy(vanilla_shop_costs)
|
self.vanilla_shop_costs = deepcopy(vanilla_shop_costs)
|
||||||
|
self.grub_count = 0
|
||||||
|
|
||||||
def generate_early(self):
|
def generate_early(self):
|
||||||
options = self.options
|
options = self.options
|
||||||
|
@ -201,7 +204,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.options.Goal in [Goal.option_godhome, Goal.option_godhome_flower]:
|
if self.options.Goal in [Goal.option_godhome, Goal.option_godhome_flower, Goal.option_any]:
|
||||||
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))
|
||||||
|
|
||||||
|
@ -441,12 +444,67 @@ class HKWorld(World):
|
||||||
multiworld.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:
|
||||||
multiworld.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player)
|
multiworld.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player)
|
||||||
|
elif goal == Goal.option_grub_hunt:
|
||||||
|
pass # will set in stage_pre_fill()
|
||||||
else:
|
else:
|
||||||
# Any goal
|
# Any goal
|
||||||
multiworld.completion_condition[player] = lambda state: _hk_can_beat_thk(state, player) or _hk_can_beat_radiance(state, player)
|
multiworld.completion_condition[player] = lambda state: _hk_siblings_ending(state, player) and \
|
||||||
|
_hk_can_beat_radiance(state, player) and state.count("Godhome_Flower_Quest", player)
|
||||||
|
|
||||||
set_rules(self)
|
set_rules(self)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def stage_pre_fill(cls, multiworld: "MultiWorld"):
|
||||||
|
def set_goal(player, grub_rule: typing.Callable[[CollectionState], bool]):
|
||||||
|
world = multiworld.worlds[player]
|
||||||
|
|
||||||
|
if world.options.Goal == "grub_hunt":
|
||||||
|
multiworld.completion_condition[player] = grub_rule
|
||||||
|
else:
|
||||||
|
old_rule = multiworld.completion_condition[player]
|
||||||
|
multiworld.completion_condition[player] = lambda state: old_rule(state) and grub_rule(state)
|
||||||
|
|
||||||
|
worlds = [world for world in multiworld.get_game_worlds(cls.game) if world.options.Goal in ["any", "grub_hunt"]]
|
||||||
|
if worlds:
|
||||||
|
grubs = [item for item in multiworld.get_items() if item.name == "Grub"]
|
||||||
|
all_grub_players = [world.player for world in multiworld.worlds.values() if world.options.GrubHuntGoal == GrubHuntGoal.special_range_names["all"]]
|
||||||
|
|
||||||
|
if all_grub_players:
|
||||||
|
group_lookup = defaultdict(set)
|
||||||
|
for group_id, group in multiworld.groups.items():
|
||||||
|
for player in group["players"]:
|
||||||
|
group_lookup[group_id].add(player)
|
||||||
|
|
||||||
|
grub_count_per_player = Counter()
|
||||||
|
per_player_grubs_per_player = defaultdict(Counter)
|
||||||
|
|
||||||
|
for grub in grubs:
|
||||||
|
player = grub.player
|
||||||
|
if player in group_lookup:
|
||||||
|
for real_player in group_lookup[player]:
|
||||||
|
per_player_grubs_per_player[real_player][player] += 1
|
||||||
|
else:
|
||||||
|
per_player_grubs_per_player[player][player] += 1
|
||||||
|
|
||||||
|
if grub.location and grub.location.player in group_lookup.keys():
|
||||||
|
for real_player in group_lookup[grub.location.player]:
|
||||||
|
grub_count_per_player[real_player] += 1
|
||||||
|
else:
|
||||||
|
grub_count_per_player[player] += 1
|
||||||
|
|
||||||
|
for player, count in grub_count_per_player.items():
|
||||||
|
multiworld.worlds[player].grub_count = count
|
||||||
|
|
||||||
|
for player, grub_player_count in per_player_grubs_per_player.items():
|
||||||
|
if player in all_grub_players:
|
||||||
|
set_goal(player, lambda state, g=grub_player_count: all(state.has("Grub", owner, count) for owner, count in g.items()))
|
||||||
|
|
||||||
|
for world in worlds:
|
||||||
|
if world.player not in all_grub_players:
|
||||||
|
world.grub_count = world.options.GrubHuntGoal.value
|
||||||
|
player = world.player
|
||||||
|
set_goal(player, lambda state, p=player, c=world.grub_count: state.has("Grub", p, c))
|
||||||
|
|
||||||
def fill_slot_data(self):
|
def fill_slot_data(self):
|
||||||
slot_data = {}
|
slot_data = {}
|
||||||
|
|
||||||
|
@ -484,6 +542,8 @@ class HKWorld(World):
|
||||||
|
|
||||||
slot_data["notch_costs"] = self.charm_costs
|
slot_data["notch_costs"] = self.charm_costs
|
||||||
|
|
||||||
|
slot_data["grub_count"] = self.grub_count
|
||||||
|
|
||||||
return slot_data
|
return slot_data
|
||||||
|
|
||||||
def create_item(self, name: str) -> HKItem:
|
def create_item(self, name: str) -> HKItem:
|
||||||
|
|
Loading…
Reference in New Issue