Implement (most) Hollow Knight Options

This commit is contained in:
Fabian Dill 2021-03-21 00:47:17 +01:00
parent 8c6c7bc575
commit f2a1858b59
7 changed files with 150 additions and 48 deletions

View File

@ -131,16 +131,18 @@ class MultiWorld():
set_player_attr('plando_connections', [])
set_player_attr('game', "A Link to the Past")
set_player_attr('completion_condition', lambda state: True)
for hk_logic in {"MILDSKIPS", "SPICYSKIPS", "FIREBALLSKIPS", "ACIDSKIPS", "SPIKETUNNELS",
"DARKROOMS", "CURSED", "SHADESKIPS"}:
set_player_attr(hk_logic, False)
set_player_attr("NOTCURSED", True)
import Options
for hk_option in Options.hollow_knight_options:
set_player_attr(hk_option, False)
self.worlds = []
#for i in range(players):
# self.worlds.append(worlds.alttp.ALTTPWorld({}, i))
@property
def NOTCURSED(self): # not here to stay
return {player: not cursed for player, cursed in self.CURSED.items()}
def secure(self):
self.random = secrets.SystemRandom()
@ -264,7 +266,7 @@ class MultiWorld():
soft_collect(item)
if keys:
for p in range(1, self.players + 1):
for p in self.alttp_player_ids:
from worlds.alttp.Items import ItemFactory
for item in ItemFactory(
['Small Key (Hyrule Castle)', 'Big Key (Eastern Palace)', 'Big Key (Desert Palace)',
@ -310,7 +312,7 @@ class MultiWorld():
if location.can_fill(self.state, item, False):
location.item = item
item.location = location
item.world = self
item.world = self # try to not have this here anymore
if collect:
self.state.collect(item, location.event, location)
@ -954,10 +956,7 @@ class Region(object):
return False
def can_fill(self, item: Item):
inside_dungeon_item = ((item.smallkey and not self.world.keyshuffle[item.player])
or (item.bigkey and not self.world.bigkeyshuffle[item.player])
or (item.map and not self.world.mapshuffle[item.player])
or (item.compass and not self.world.compassshuffle[item.player]))
inside_dungeon_item = item.locked_dungeon_item
sewer_hack = self.world.mode[item.player] == 'standard' and item.name == 'Small Key (Hyrule Castle)'
if sewer_hack or inside_dungeon_item:
return self.dungeon and self.dungeon.is_dungeon_item(item) and item.player == self.player
@ -1037,7 +1036,7 @@ class Dungeon(object):
return self.dungeon_items + self.keys
def is_dungeon_item(self, item: Item) -> bool:
return item.player == self.player and item.name in [dungeon_item.name for dungeon_item in self.all_items]
return item.player == self.player and item.name in (dungeon_item.name for dungeon_item in self.all_items)
def __eq__(self, other: Dungeon) -> bool:
if not other:
@ -1072,7 +1071,7 @@ class Location():
def __init__(self, player: int, name: str = '', address:int = None, parent=None):
self.name = name
self.address = address
self.parent_region = parent
self.parent_region: Region = parent
self.recursion_count = 0
self.player = player
self.item = None
@ -1162,6 +1161,31 @@ class Item():
def compass(self) -> bool:
return self.type == 'Compass'
@property
def dungeon_item(self) -> Optional[str]:
if self.game == "A Link to the Past" and self.type in {"SmallKey", "BigKey", "Map", "Compass"}:
return self.type
@property
def shuffled_dungeon_item(self) -> bool:
dungeon_item_type = self.dungeon_item
if dungeon_item_type:
return {"SmallKey" : self.world.keyshuffle,
"BigKey": self.world.bigkeyshuffle,
"Map": self.world.mapshuffle,
"Compass": self.world.compassshuffle}[dungeon_item_type][self.player]
return False
@property
def locked_dungeon_item(self) -> bool:
dungeon_item_type = self.dungeon_item
if dungeon_item_type:
return not {"SmallKey" : self.world.keyshuffle,
"BigKey": self.world.bigkeyshuffle,
"Map": self.world.mapshuffle,
"Compass": self.world.compassshuffle}[dungeon_item_type][self.player]
return False
def __repr__(self):
return self.__str__()
@ -1367,6 +1391,11 @@ class Spoiler(object):
outfile.write('Progression Balanced: %s\n' % (
'Yes' if self.metadata['progression_balancing'][player] else 'No'))
outfile.write('Accessibility: %s\n' % self.metadata['accessibility'][player])
if player in self.world.hk_player_ids:
import Options
for hk_option in Options.hollow_knight_options:
res = getattr(self.world, hk_option)[player]
outfile.write(f'{hk_option+":":33}{res}\n')
if player in self.world.alttp_player_ids:
for team in range(self.world.teams):
outfile.write('%s%s\n' % (

View File

@ -50,8 +50,6 @@ def fill_restrictive(world, base_state: CollectionState, locations, itempool, si
break
else:
# fill in name of world for item
item_to_place.world = world
# we filled all reachable spots. Maybe the game can be beaten anyway?
unplaced_items.append(item_to_place)
if world.accessibility[item_to_place.player] != 'none' and world.can_beat_game():

View File

@ -129,6 +129,9 @@ def main(args, seed=None):
world.restrict_dungeon_item_on_boss = args.restrict_dungeon_item_on_boss.copy()
world.required_medallions = args.required_medallions.copy()
world.game = args.game.copy()
import Options
for hk_option in Options.hollow_knight_options:
setattr(world, hk_option, getattr(args, hk_option))
world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)}
@ -258,6 +261,9 @@ def main(args, seed=None):
logger.info("Running Item Plando")
for item in world.itempool:
item.world = world
distribute_planned(world)
logger.info('Placing Dungeon Prizes.')

View File

@ -447,6 +447,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
ret = argparse.Namespace()
ret.name = get_choice('name', weights)
ret.accessibility = get_choice('accessibility', weights)
ret.progression_balancing = get_choice('progression_balancing', weights, True)
ret.game = get_choice("game", weights, "A Link to the Past")
ret.local_items = set()
@ -495,10 +496,6 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
ret.restrict_dungeon_item_on_boss = get_choice('restrict_dungeon_item_on_boss', weights, False)
ret.progression_balancing = get_choice('progression_balancing', weights, True)
# item_placement = get_choice('item_placement')
# not supported in ER
dungeon_items = get_choice('dungeon_items', weights)
if dungeon_items == 'full' or dungeon_items == True:
dungeon_items = 'mcsb'

View File

@ -10,11 +10,11 @@ class AssembleOptions(type):
options.update(base.options)
name_lookup.update(name_lookup)
new_options = {name[7:].lower(): option_id for name, option_id in attrs.items() if
name.startswith("option_")}
name.startswith("option_")}
attrs["name_lookup"].update({option_id: name for name, option_id in new_options.items()})
options.update(new_options)
#apply aliases, without name_lookup
# apply aliases, without name_lookup
options.update({name[6:].lower(): option_id for name, option_id in attrs.items() if
name.startswith("alias_")})
return super(AssembleOptions, cls).__new__(cls, name, bases, attrs)
@ -77,6 +77,12 @@ class Toggle(Option):
else:
return self.value > other
def __bool__(self):
return bool(self.value)
def __int__(self):
return int(self.value)
def get_option_name(self):
return bool(self.value)
@ -108,18 +114,21 @@ class Logic(Choice):
class Objective(Choice):
option_crystals = 0
#option_pendants = 1
# option_pendants = 1
option_triforce_pieces = 2
option_pedestal = 3
option_bingo = 4
local_objective = Toggle # local triforce pieces, local dungeon prizes etc.
local_objective = Toggle # local triforce pieces, local dungeon prizes etc.
class Goal(Choice):
option_kill_ganon = 0
option_kill_ganon_and_gt_agahnim = 1
option_hand_in = 2
class Accessibility(Choice):
option_locations = 0
option_items = 1
@ -185,31 +194,44 @@ RandomizeSoulTotems = Toggle
RandomizePalaceTotems = Toggle
RandomizeLoreTablets = Toggle
RandomizeLifebloodCocoons = Toggle
RandomizeFlames = Toggle
hollow_knight_randomize_options: typing.Dict[str, Option] = {
"RandomizeDreamers" : RandomizeDreamers,
"RandomizeSkills" : RandomizeSkills,
"RandomizeCharms" : RandomizeCharms,
"RandomizeKeys" : RandomizeKeys,
"RandomizeGeoChests" : RandomizeGeoChests,
"RandomizeMaskShards" : RandomizeMaskShards,
"RandomizeVesselFragments" : RandomizeVesselFragments,
"RandomizeCharmNotches" : RandomizeCharmNotches,
"RandomizePaleOre" : RandomizePaleOre,
"RandomizeRancidEggs" : RandomizeRancidEggs,
"RandomizeRelics" : RandomizeRelics,
"RandomizeMaps" : RandomizeMaps,
"RandomizeStags" : RandomizeStags,
"RandomizeGrubs" : RandomizeGrubs,
"RandomizeWhisperingRoots" : RandomizeWhisperingRoots,
"RandomizeRocks" : RandomizeRocks,
"RandomizeSoulTotems" : RandomizeSoulTotems,
"RandomizePalaceTotems" : RandomizePalaceTotems,
"RandomizeLoreTablets" : RandomizeLoreTablets,
"RandomizeLifebloodCocoons" : RandomizeLifebloodCocoons,
"RandomizeDreamers": RandomizeDreamers,
"RandomizeSkills": RandomizeSkills,
"RandomizeCharms": RandomizeCharms,
"RandomizeKeys": RandomizeKeys,
"RandomizeGeoChests": RandomizeGeoChests,
"RandomizeMaskShards": RandomizeMaskShards,
"RandomizeVesselFragments": RandomizeVesselFragments,
"RandomizeCharmNotches": RandomizeCharmNotches,
"RandomizePaleOre": RandomizePaleOre,
"RandomizeRancidEggs": RandomizeRancidEggs,
"RandomizeRelics": RandomizeRelics,
"RandomizeMaps": RandomizeMaps,
"RandomizeStags": RandomizeStags,
"RandomizeGrubs": RandomizeGrubs,
"RandomizeWhisperingRoots": RandomizeWhisperingRoots,
"RandomizeRocks": RandomizeRocks,
"RandomizeSoulTotems": RandomizeSoulTotems,
"RandomizePalaceTotems": RandomizePalaceTotems,
"RandomizeLoreTablets": RandomizeLoreTablets,
"RandomizeLifebloodCocoons": RandomizeLifebloodCocoons,
"RandomizeFlames": RandomizeFlames
}
hollow_knight_options: typing.Dict[str, Option] = {**hollow_knight_randomize_options}
hollow_knight_skip_options: typing.Dict[str, type(Option)] = {
"MILDSKIPS": Toggle,
"SPICYSKIPS": Toggle,
"FIREBALLSKIPS": Toggle,
"ACIDSKIPS": Toggle,
"SPIKETUNNELS": Toggle,
"DARKROOMS": Toggle,
"CURSED": Toggle,
"SHADESKIPS": Toggle,
}
hollow_knight_options: typing.Dict[str, Option] = {**hollow_knight_randomize_options, **hollow_knight_skip_options}
if __name__ == "__main__":
import argparse

View File

@ -115,7 +115,10 @@ def fill_dungeons(world):
def get_dungeon_item_pool(world):
return [item for dungeon in world.dungeons for item in dungeon.all_items]
items = [item for dungeon in world.dungeons for item in dungeon.all_items]
for item in items:
item.world = world
return items
def fill_dungeons_restrictive(world):
"""Places dungeon-native items into their dungeons, places nothing if everything is shuffled outside."""

View File

@ -33,8 +33,9 @@ class HKLocation(Location):
class HKItem(Item):
game = "Hollow Knight"
def __init__(self, name, advancement, code, player: int = None):
def __init__(self, name, advancement, code, type, player: int = None):
super(HKItem, self).__init__(name, advancement, code, player)
self.type = type
def gen_hollow(world: MultiWorld, player: int):
@ -48,12 +49,33 @@ def gen_hollow(world: MultiWorld, player: int):
def link_regions(world: MultiWorld, player: int):
world.get_entrance('Hollow Nest S&Q', player).connect(world.get_region('Hollow Nest', player))
not_shufflable_types = {"Essence_Boss"}
option_to_type_lookup = {
"Root": "RandomizeWhisperingRoots",
"Dreamer": "RandomizeDreamers",
"Geo": "RandomizeGeoChests",
"Skill": "RandomizeSkills",
"Map": "RandomizeMaps",
"Relic": "RandomizeRelics",
"Charm": "RandomizeCharms",
"Notch": "RandomizeCharmNotches",
"Key": "RandomizeKeys",
"Stag": "RandomizeStags",
"Flame": "RandomizeFlames",
"Grub": "RandomizeGrubs",
"Cocoon": "RandomizeLifebloodCocoons",
"Mask": "RandomizeMaskShards",
"Ore": "RandomizePaleOre",
"Egg": "RandomizeRancidEggs",
"Vessel": "RandomizeVesselFragments",
}
def gen_items(world: MultiWorld, player: int):
pool = []
for item_name, item_data in item_table.items():
item = HKItem(item_name, item_data.advancement, item_data.id, player=player)
item = HKItem(item_name, item_data.advancement, item_data.id, item_data.type, player=player)
if item_data.type == "Event":
event_location = world.get_location(item_name, player)
@ -62,10 +84,35 @@ def gen_items(world: MultiWorld, player: int):
event_location.locked = True
if item.name == "King's_Pass":
world.push_precollected(item)
elif item_data.type == "Cursed":
if world.CURSED[player]:
raise Exception("Cursed is not implemented yet.")
# implement toss_junk for HK first
else:
event_location = world.get_location(item_name, player)
world.push_item(event_location, item)
event_location.event = True
event_location.locked = True
world.push_precollected(item)
elif item_data.type == "Fake":
pass
elif item_data.type in not_shufflable_types:
location = world.get_location(item_name, player)
world.push_item(location, item)
location.event = item.advancement
location.locked = True
else:
pool.append(item)
target = option_to_type_lookup[item.type]
shuffle_it = getattr(world, target)
if shuffle_it[player]:
pool.append(item)
else:
location = world.get_location(item_name, player)
world.push_item(location, item)
location.event = item.advancement
location.locked = True
logger.debug(f"Placed {item_name} to vanilla for player {player}")
world.itempool += pool