Implement (most) Hollow Knight Options
This commit is contained in:
parent
8c6c7bc575
commit
f2a1858b59
|
@ -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' % (
|
||||
|
|
2
Fill.py
2
Fill.py
|
@ -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():
|
||||
|
|
6
Main.py
6
Main.py
|
@ -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.')
|
||||
|
|
|
@ -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'
|
||||
|
|
72
Options.py
72
Options.py
|
@ -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
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue