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('plando_connections', [])
|
||||||
set_player_attr('game', "A Link to the Past")
|
set_player_attr('game', "A Link to the Past")
|
||||||
set_player_attr('completion_condition', lambda state: True)
|
set_player_attr('completion_condition', lambda state: True)
|
||||||
|
import Options
|
||||||
for hk_logic in {"MILDSKIPS", "SPICYSKIPS", "FIREBALLSKIPS", "ACIDSKIPS", "SPIKETUNNELS",
|
for hk_option in Options.hollow_knight_options:
|
||||||
"DARKROOMS", "CURSED", "SHADESKIPS"}:
|
set_player_attr(hk_option, False)
|
||||||
set_player_attr(hk_logic, False)
|
|
||||||
set_player_attr("NOTCURSED", True)
|
|
||||||
|
|
||||||
self.worlds = []
|
self.worlds = []
|
||||||
#for i in range(players):
|
#for i in range(players):
|
||||||
# self.worlds.append(worlds.alttp.ALTTPWorld({}, i))
|
# 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):
|
def secure(self):
|
||||||
self.random = secrets.SystemRandom()
|
self.random = secrets.SystemRandom()
|
||||||
|
|
||||||
|
@ -264,7 +266,7 @@ class MultiWorld():
|
||||||
soft_collect(item)
|
soft_collect(item)
|
||||||
|
|
||||||
if keys:
|
if keys:
|
||||||
for p in range(1, self.players + 1):
|
for p in self.alttp_player_ids:
|
||||||
from worlds.alttp.Items import ItemFactory
|
from worlds.alttp.Items import ItemFactory
|
||||||
for item in ItemFactory(
|
for item in ItemFactory(
|
||||||
['Small Key (Hyrule Castle)', 'Big Key (Eastern Palace)', 'Big Key (Desert Palace)',
|
['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):
|
if location.can_fill(self.state, item, False):
|
||||||
location.item = item
|
location.item = item
|
||||||
item.location = location
|
item.location = location
|
||||||
item.world = self
|
item.world = self # try to not have this here anymore
|
||||||
if collect:
|
if collect:
|
||||||
self.state.collect(item, location.event, location)
|
self.state.collect(item, location.event, location)
|
||||||
|
|
||||||
|
@ -954,10 +956,7 @@ class Region(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def can_fill(self, item: Item):
|
def can_fill(self, item: Item):
|
||||||
inside_dungeon_item = ((item.smallkey and not self.world.keyshuffle[item.player])
|
inside_dungeon_item = item.locked_dungeon_item
|
||||||
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]))
|
|
||||||
sewer_hack = self.world.mode[item.player] == 'standard' and item.name == 'Small Key (Hyrule Castle)'
|
sewer_hack = self.world.mode[item.player] == 'standard' and item.name == 'Small Key (Hyrule Castle)'
|
||||||
if sewer_hack or inside_dungeon_item:
|
if sewer_hack or inside_dungeon_item:
|
||||||
return self.dungeon and self.dungeon.is_dungeon_item(item) and item.player == self.player
|
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
|
return self.dungeon_items + self.keys
|
||||||
|
|
||||||
def is_dungeon_item(self, item: Item) -> bool:
|
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:
|
def __eq__(self, other: Dungeon) -> bool:
|
||||||
if not other:
|
if not other:
|
||||||
|
@ -1072,7 +1071,7 @@ class Location():
|
||||||
def __init__(self, player: int, name: str = '', address:int = None, parent=None):
|
def __init__(self, player: int, name: str = '', address:int = None, parent=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.address = address
|
self.address = address
|
||||||
self.parent_region = parent
|
self.parent_region: Region = parent
|
||||||
self.recursion_count = 0
|
self.recursion_count = 0
|
||||||
self.player = player
|
self.player = player
|
||||||
self.item = None
|
self.item = None
|
||||||
|
@ -1162,6 +1161,31 @@ class Item():
|
||||||
def compass(self) -> bool:
|
def compass(self) -> bool:
|
||||||
return self.type == 'Compass'
|
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):
|
def __repr__(self):
|
||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
|
||||||
|
@ -1367,6 +1391,11 @@ class Spoiler(object):
|
||||||
outfile.write('Progression Balanced: %s\n' % (
|
outfile.write('Progression Balanced: %s\n' % (
|
||||||
'Yes' if self.metadata['progression_balancing'][player] else 'No'))
|
'Yes' if self.metadata['progression_balancing'][player] else 'No'))
|
||||||
outfile.write('Accessibility: %s\n' % self.metadata['accessibility'][player])
|
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:
|
if player in self.world.alttp_player_ids:
|
||||||
for team in range(self.world.teams):
|
for team in range(self.world.teams):
|
||||||
outfile.write('%s%s\n' % (
|
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
|
break
|
||||||
|
|
||||||
else:
|
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?
|
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
||||||
unplaced_items.append(item_to_place)
|
unplaced_items.append(item_to_place)
|
||||||
if world.accessibility[item_to_place.player] != 'none' and world.can_beat_game():
|
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.restrict_dungeon_item_on_boss = args.restrict_dungeon_item_on_boss.copy()
|
||||||
world.required_medallions = args.required_medallions.copy()
|
world.required_medallions = args.required_medallions.copy()
|
||||||
world.game = args.game.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)}
|
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")
|
logger.info("Running Item Plando")
|
||||||
|
|
||||||
|
for item in world.itempool:
|
||||||
|
item.world = world
|
||||||
|
|
||||||
distribute_planned(world)
|
distribute_planned(world)
|
||||||
|
|
||||||
logger.info('Placing Dungeon Prizes.')
|
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 = argparse.Namespace()
|
||||||
ret.name = get_choice('name', weights)
|
ret.name = get_choice('name', weights)
|
||||||
ret.accessibility = get_choice('accessibility', 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.game = get_choice("game", weights, "A Link to the Past")
|
||||||
|
|
||||||
ret.local_items = set()
|
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.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)
|
dungeon_items = get_choice('dungeon_items', weights)
|
||||||
if dungeon_items == 'full' or dungeon_items == True:
|
if dungeon_items == 'full' or dungeon_items == True:
|
||||||
dungeon_items = 'mcsb'
|
dungeon_items = 'mcsb'
|
||||||
|
|
24
Options.py
24
Options.py
|
@ -77,6 +77,12 @@ class Toggle(Option):
|
||||||
else:
|
else:
|
||||||
return self.value > other
|
return self.value > other
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return bool(self.value)
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
return int(self.value)
|
||||||
|
|
||||||
def get_option_name(self):
|
def get_option_name(self):
|
||||||
return bool(self.value)
|
return bool(self.value)
|
||||||
|
|
||||||
|
@ -113,13 +119,16 @@ class Objective(Choice):
|
||||||
option_pedestal = 3
|
option_pedestal = 3
|
||||||
option_bingo = 4
|
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):
|
class Goal(Choice):
|
||||||
option_kill_ganon = 0
|
option_kill_ganon = 0
|
||||||
option_kill_ganon_and_gt_agahnim = 1
|
option_kill_ganon_and_gt_agahnim = 1
|
||||||
option_hand_in = 2
|
option_hand_in = 2
|
||||||
|
|
||||||
|
|
||||||
class Accessibility(Choice):
|
class Accessibility(Choice):
|
||||||
option_locations = 0
|
option_locations = 0
|
||||||
option_items = 1
|
option_items = 1
|
||||||
|
@ -185,6 +194,7 @@ RandomizeSoulTotems = Toggle
|
||||||
RandomizePalaceTotems = Toggle
|
RandomizePalaceTotems = Toggle
|
||||||
RandomizeLoreTablets = Toggle
|
RandomizeLoreTablets = Toggle
|
||||||
RandomizeLifebloodCocoons = Toggle
|
RandomizeLifebloodCocoons = Toggle
|
||||||
|
RandomizeFlames = Toggle
|
||||||
|
|
||||||
hollow_knight_randomize_options: typing.Dict[str, Option] = {
|
hollow_knight_randomize_options: typing.Dict[str, Option] = {
|
||||||
"RandomizeDreamers": RandomizeDreamers,
|
"RandomizeDreamers": RandomizeDreamers,
|
||||||
|
@ -207,9 +217,21 @@ hollow_knight_randomize_options: typing.Dict[str, Option] = {
|
||||||
"RandomizePalaceTotems": RandomizePalaceTotems,
|
"RandomizePalaceTotems": RandomizePalaceTotems,
|
||||||
"RandomizeLoreTablets": RandomizeLoreTablets,
|
"RandomizeLoreTablets": RandomizeLoreTablets,
|
||||||
"RandomizeLifebloodCocoons": RandomizeLifebloodCocoons,
|
"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__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
|
|
|
@ -115,7 +115,10 @@ def fill_dungeons(world):
|
||||||
|
|
||||||
|
|
||||||
def get_dungeon_item_pool(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):
|
def fill_dungeons_restrictive(world):
|
||||||
"""Places dungeon-native items into their dungeons, places nothing if everything is shuffled outside."""
|
"""Places dungeon-native items into their dungeons, places nothing if everything is shuffled outside."""
|
||||||
|
|
|
@ -33,8 +33,9 @@ class HKLocation(Location):
|
||||||
class HKItem(Item):
|
class HKItem(Item):
|
||||||
game = "Hollow Knight"
|
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)
|
super(HKItem, self).__init__(name, advancement, code, player)
|
||||||
|
self.type = type
|
||||||
|
|
||||||
|
|
||||||
def gen_hollow(world: MultiWorld, player: int):
|
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):
|
def link_regions(world: MultiWorld, player: int):
|
||||||
world.get_entrance('Hollow Nest S&Q', player).connect(world.get_region('Hollow Nest', player))
|
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):
|
def gen_items(world: MultiWorld, player: int):
|
||||||
pool = []
|
pool = []
|
||||||
for item_name, item_data in item_table.items():
|
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":
|
if item_data.type == "Event":
|
||||||
event_location = world.get_location(item_name, player)
|
event_location = world.get_location(item_name, player)
|
||||||
|
@ -62,10 +84,35 @@ def gen_items(world: MultiWorld, player: int):
|
||||||
event_location.locked = True
|
event_location.locked = True
|
||||||
if item.name == "King's_Pass":
|
if item.name == "King's_Pass":
|
||||||
world.push_precollected(item)
|
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":
|
elif item_data.type == "Fake":
|
||||||
pass
|
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:
|
else:
|
||||||
|
target = option_to_type_lookup[item.type]
|
||||||
|
shuffle_it = getattr(world, target)
|
||||||
|
if shuffle_it[player]:
|
||||||
pool.append(item)
|
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
|
world.itempool += pool
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue