Skip to content

Commit 78f0ed0

Browse files
qwintAustinSumigray
authored andcommitted
HK: add grub hunt goal (ArchipelagoMW#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
1 parent ad5a4bf commit 78f0ed0

File tree

2 files changed

+76
-5
lines changed

2 files changed

+76
-5
lines changed

worlds/hk/Options.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,20 @@ class Goal(Choice):
405405
option_radiance = 3
406406
option_godhome = 4
407407
option_godhome_flower = 5
408+
option_grub_hunt = 6
408409
default = 0
409410

410411

412+
class GrubHuntGoal(NamedRange):
413+
"""The amount of grubs required to finish Grub Hunt.
414+
On 'All' any grubs from item links replacements etc. will be counted"""
415+
display_name = "Grub Hunt Goal"
416+
range_start = 1
417+
range_end = 46
418+
special_range_names = {"all": -1}
419+
default = 46
420+
421+
411422
class WhitePalace(Choice):
412423
"""
413424
Whether or not to include White Palace or not. Note: Even if excluded, the King Fragment check may still be
@@ -522,7 +533,7 @@ class CostSanityHybridChance(Range):
522533
**{
523534
option.__name__: option
524535
for option in (
525-
StartLocation, Goal, WhitePalace, ExtraPlatforms, AddUnshuffledLocations, StartingGeo,
536+
StartLocation, Goal, GrubHuntGoal, WhitePalace, ExtraPlatforms, AddUnshuffledLocations, StartingGeo,
526537
DeathLink, DeathLinkShade, DeathLinkBreaksFragileCharms,
527538
MinimumGeoPrice, MaximumGeoPrice,
528539
MinimumGrubPrice, MaximumGrubPrice,

worlds/hk/__init__.py

+64-4
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@
55
from copy import deepcopy
66
import itertools
77
import operator
8+
from collections import defaultdict, Counter
89

910
logger = logging.getLogger("Hollow Knight")
1011

1112
from .Items import item_table, lookup_type_to_names, item_name_groups
1213
from .Regions import create_regions
1314
from .Rules import set_rules, cost_terms, _hk_can_beat_thk, _hk_siblings_ending, _hk_can_beat_radiance
1415
from .Options import hollow_knight_options, hollow_knight_randomize_options, Goal, WhitePalace, CostSanity, \
15-
shop_to_option, HKOptions
16+
shop_to_option, HKOptions, GrubHuntGoal
1617
from .ExtractedData import locations, starts, multi_locations, location_to_region_lookup, \
1718
event_names, item_effects, connectors, one_ways, vanilla_shop_costs, vanilla_location_costs
1819
from .Charms import names as charm_names
1920

20-
from BaseClasses import Region, Location, MultiWorld, Item, LocationProgressType, Tutorial, ItemClassification
21+
from BaseClasses import Region, Location, MultiWorld, Item, LocationProgressType, Tutorial, ItemClassification, CollectionState
2122
from worlds.AutoWorld import World, LogicMixin, WebWorld
2223

2324
path_of_pain_locations = {
@@ -155,6 +156,7 @@ class HKWorld(World):
155156
ranges: typing.Dict[str, typing.Tuple[int, int]]
156157
charm_costs: typing.List[int]
157158
cached_filler_items = {}
159+
grub_count: int
158160

159161
def __init__(self, multiworld, player):
160162
super(HKWorld, self).__init__(multiworld, player)
@@ -164,6 +166,7 @@ def __init__(self, multiworld, player):
164166
self.ranges = {}
165167
self.created_shop_items = 0
166168
self.vanilla_shop_costs = deepcopy(vanilla_shop_costs)
169+
self.grub_count = 0
167170

168171
def generate_early(self):
169172
options = self.options
@@ -201,7 +204,7 @@ def create_regions(self):
201204

202205
# check for any goal that godhome events are relevant to
203206
all_event_names = event_names.copy()
204-
if self.options.Goal in [Goal.option_godhome, Goal.option_godhome_flower]:
207+
if self.options.Goal in [Goal.option_godhome, Goal.option_godhome_flower, Goal.option_any]:
205208
from .GodhomeData import godhome_event_names
206209
all_event_names.update(set(godhome_event_names))
207210

@@ -441,12 +444,67 @@ def set_rules(self):
441444
multiworld.completion_condition[player] = lambda state: state.count("Defeated_Pantheon_5", player)
442445
elif goal == Goal.option_godhome_flower:
443446
multiworld.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player)
447+
elif goal == Goal.option_grub_hunt:
448+
pass # will set in stage_pre_fill()
444449
else:
445450
# Any goal
446-
multiworld.completion_condition[player] = lambda state: _hk_can_beat_thk(state, player) or _hk_can_beat_radiance(state, player)
451+
multiworld.completion_condition[player] = lambda state: _hk_siblings_ending(state, player) and \
452+
_hk_can_beat_radiance(state, player) and state.count("Godhome_Flower_Quest", player)
447453

448454
set_rules(self)
449455

456+
@classmethod
457+
def stage_pre_fill(cls, multiworld: "MultiWorld"):
458+
def set_goal(player, grub_rule: typing.Callable[[CollectionState], bool]):
459+
world = multiworld.worlds[player]
460+
461+
if world.options.Goal == "grub_hunt":
462+
multiworld.completion_condition[player] = grub_rule
463+
else:
464+
old_rule = multiworld.completion_condition[player]
465+
multiworld.completion_condition[player] = lambda state: old_rule(state) and grub_rule(state)
466+
467+
worlds = [world for world in multiworld.get_game_worlds(cls.game) if world.options.Goal in ["any", "grub_hunt"]]
468+
if worlds:
469+
grubs = [item for item in multiworld.get_items() if item.name == "Grub"]
470+
all_grub_players = [world.player for world in multiworld.worlds.values() if world.options.GrubHuntGoal == GrubHuntGoal.special_range_names["all"]]
471+
472+
if all_grub_players:
473+
group_lookup = defaultdict(set)
474+
for group_id, group in multiworld.groups.items():
475+
for player in group["players"]:
476+
group_lookup[group_id].add(player)
477+
478+
grub_count_per_player = Counter()
479+
per_player_grubs_per_player = defaultdict(Counter)
480+
481+
for grub in grubs:
482+
player = grub.player
483+
if player in group_lookup:
484+
for real_player in group_lookup[player]:
485+
per_player_grubs_per_player[real_player][player] += 1
486+
else:
487+
per_player_grubs_per_player[player][player] += 1
488+
489+
if grub.location and grub.location.player in group_lookup.keys():
490+
for real_player in group_lookup[grub.location.player]:
491+
grub_count_per_player[real_player] += 1
492+
else:
493+
grub_count_per_player[player] += 1
494+
495+
for player, count in grub_count_per_player.items():
496+
multiworld.worlds[player].grub_count = count
497+
498+
for player, grub_player_count in per_player_grubs_per_player.items():
499+
if player in all_grub_players:
500+
set_goal(player, lambda state, g=grub_player_count: all(state.has("Grub", owner, count) for owner, count in g.items()))
501+
502+
for world in worlds:
503+
if world.player not in all_grub_players:
504+
world.grub_count = world.options.GrubHuntGoal.value
505+
player = world.player
506+
set_goal(player, lambda state, p=player, c=world.grub_count: state.has("Grub", p, c))
507+
450508
def fill_slot_data(self):
451509
slot_data = {}
452510

@@ -484,6 +542,8 @@ def fill_slot_data(self):
484542

485543
slot_data["notch_costs"] = self.charm_costs
486544

545+
slot_data["grub_count"] = self.grub_count
546+
487547
return slot_data
488548

489549
def create_item(self, name: str) -> HKItem:

0 commit comments

Comments
 (0)