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_godhome = 4
|
||||
option_godhome_flower = 5
|
||||
option_grub_hunt = 6
|
||||
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):
|
||||
"""
|
||||
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
|
||||
for option in (
|
||||
StartLocation, Goal, WhitePalace, ExtraPlatforms, AddUnshuffledLocations, StartingGeo,
|
||||
StartLocation, Goal, GrubHuntGoal, WhitePalace, ExtraPlatforms, AddUnshuffledLocations, StartingGeo,
|
||||
DeathLink, DeathLinkShade, DeathLinkBreaksFragileCharms,
|
||||
MinimumGeoPrice, MaximumGeoPrice,
|
||||
MinimumGrubPrice, MaximumGrubPrice,
|
||||
|
|
|
@ -5,6 +5,7 @@ import typing
|
|||
from copy import deepcopy
|
||||
import itertools
|
||||
import operator
|
||||
from collections import defaultdict, Counter
|
||||
|
||||
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 .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, HKOptions
|
||||
shop_to_option, HKOptions, GrubHuntGoal
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
path_of_pain_locations = {
|
||||
|
@ -155,6 +156,7 @@ class HKWorld(World):
|
|||
ranges: typing.Dict[str, typing.Tuple[int, int]]
|
||||
charm_costs: typing.List[int]
|
||||
cached_filler_items = {}
|
||||
grub_count: int
|
||||
|
||||
def __init__(self, multiworld, player):
|
||||
super(HKWorld, self).__init__(multiworld, player)
|
||||
|
@ -164,6 +166,7 @@ class HKWorld(World):
|
|||
self.ranges = {}
|
||||
self.created_shop_items = 0
|
||||
self.vanilla_shop_costs = deepcopy(vanilla_shop_costs)
|
||||
self.grub_count = 0
|
||||
|
||||
def generate_early(self):
|
||||
options = self.options
|
||||
|
@ -201,7 +204,7 @@ class HKWorld(World):
|
|||
|
||||
# check for any goal that godhome events are relevant to
|
||||
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
|
||||
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)
|
||||
elif goal == Goal.option_godhome_flower:
|
||||
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:
|
||||
# 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)
|
||||
|
||||
@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):
|
||||
slot_data = {}
|
||||
|
||||
|
@ -484,6 +542,8 @@ class HKWorld(World):
|
|||
|
||||
slot_data["notch_costs"] = self.charm_costs
|
||||
|
||||
slot_data["grub_count"] = self.grub_count
|
||||
|
||||
return slot_data
|
||||
|
||||
def create_item(self, name: str) -> HKItem:
|
||||
|
|
Loading…
Reference in New Issue