some option updates
This commit is contained in:
parent
03bcd2aed7
commit
3d93f659e3
|
@ -24,24 +24,13 @@ class MultiWorld():
|
||||||
plando_connections: List[PlandoConnection]
|
plando_connections: List[PlandoConnection]
|
||||||
er_seeds: Dict[int, str]
|
er_seeds: Dict[int, str]
|
||||||
|
|
||||||
def __init__(self, players: int, shuffle, logic, mode, swords, difficulty, item_functionality, timer,
|
def __init__(self, players: int):
|
||||||
progressive,
|
|
||||||
goal, algorithm, accessibility, shuffle_ganon, retro, custom, customitemarray, hints):
|
|
||||||
|
|
||||||
self.random = random.Random() # world-local random state is saved in case of future use a
|
self.random = random.Random() # world-local random state is saved in case of future use a
|
||||||
# persistently running program with multiple worlds rolling concurrently
|
# persistently running program with multiple worlds rolling concurrently
|
||||||
self.players = players
|
self.players = players
|
||||||
self.teams = 1
|
self.teams = 1
|
||||||
self.shuffle = shuffle.copy()
|
self.algorithm = 'balanced'
|
||||||
self.logic = logic.copy()
|
|
||||||
self.mode = mode.copy()
|
|
||||||
self.swords = swords.copy()
|
|
||||||
self.difficulty = difficulty.copy()
|
|
||||||
self.item_functionality = item_functionality.copy()
|
|
||||||
self.timer = timer.copy()
|
|
||||||
self.progressive = progressive
|
|
||||||
self.goal = goal.copy()
|
|
||||||
self.algorithm = algorithm
|
|
||||||
self.dungeons = []
|
self.dungeons = []
|
||||||
self.regions = []
|
self.regions = []
|
||||||
self.shops = []
|
self.shops = []
|
||||||
|
@ -60,13 +49,9 @@ class MultiWorld():
|
||||||
self.aga_randomness = True
|
self.aga_randomness = True
|
||||||
self.lock_aga_door_in_escape = False
|
self.lock_aga_door_in_escape = False
|
||||||
self.save_and_quit_from_boss = True
|
self.save_and_quit_from_boss = True
|
||||||
self.accessibility = accessibility.copy()
|
self.custom = False
|
||||||
self.shuffle_ganon = shuffle_ganon
|
self.customitemarray = []
|
||||||
self.fix_gtower_exit = self.shuffle_ganon
|
self.shuffle_ganon = True
|
||||||
self.retro = retro.copy()
|
|
||||||
self.custom = custom
|
|
||||||
self.customitemarray: List[int] = customitemarray
|
|
||||||
self.hints = hints.copy()
|
|
||||||
self.dynamic_regions = []
|
self.dynamic_regions = []
|
||||||
self.dynamic_locations = []
|
self.dynamic_locations = []
|
||||||
self.spoiler = Spoiler(self)
|
self.spoiler = Spoiler(self)
|
||||||
|
@ -75,6 +60,18 @@ class MultiWorld():
|
||||||
def set_player_attr(attr, val):
|
def set_player_attr(attr, val):
|
||||||
self.__dict__.setdefault(attr, {})[player] = val
|
self.__dict__.setdefault(attr, {})[player] = val
|
||||||
set_player_attr('_region_cache', {})
|
set_player_attr('_region_cache', {})
|
||||||
|
set_player_attr('shuffle', "vanilla")
|
||||||
|
set_player_attr('logic', "noglitches")
|
||||||
|
set_player_attr('mode', 'open')
|
||||||
|
set_player_attr('swords', 'random')
|
||||||
|
set_player_attr('difficulty', 'normal')
|
||||||
|
set_player_attr('item_functionality', 'normal')
|
||||||
|
set_player_attr('timer', False)
|
||||||
|
set_player_attr('goal', 'ganon')
|
||||||
|
set_player_attr('progressive', 'on')
|
||||||
|
set_player_attr('accessibility', 'items')
|
||||||
|
set_player_attr('retro', False)
|
||||||
|
set_player_attr('hints', True)
|
||||||
set_player_attr('player_names', [])
|
set_player_attr('player_names', [])
|
||||||
set_player_attr('remote_items', False)
|
set_player_attr('remote_items', False)
|
||||||
set_player_attr('required_medallions', ['Ether', 'Quake'])
|
set_player_attr('required_medallions', ['Ether', 'Quake'])
|
||||||
|
@ -144,6 +141,10 @@ class MultiWorld():
|
||||||
#for i in range(players):
|
#for i in range(players):
|
||||||
# self.worlds.append(worlds.alttp.ALTTPWorld({}, i))
|
# self.worlds.append(worlds.alttp.ALTTPWorld({}, i))
|
||||||
|
|
||||||
|
def copy(self) -> MultiWorld:
|
||||||
|
import copy
|
||||||
|
return copy.deepcopy(self)
|
||||||
|
|
||||||
def secure(self):
|
def secure(self):
|
||||||
self.random = secrets.SystemRandom()
|
self.random = secrets.SystemRandom()
|
||||||
|
|
||||||
|
|
2
Gui.py
2
Gui.py
|
@ -153,7 +153,7 @@ def guiMain(args=None):
|
||||||
goalFrame = Frame(drowDownFrame)
|
goalFrame = Frame(drowDownFrame)
|
||||||
goalVar = StringVar()
|
goalVar = StringVar()
|
||||||
goalVar.set('ganon')
|
goalVar.set('ganon')
|
||||||
goalOptionMenu = OptionMenu(goalFrame, goalVar, 'ganon', 'pedestal', 'dungeons', 'triforcehunt',
|
goalOptionMenu = OptionMenu(goalFrame, goalVar, 'ganon', 'pedestal', 'bosses', 'triforcehunt',
|
||||||
'localtriforcehunt', 'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal')
|
'localtriforcehunt', 'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal')
|
||||||
goalOptionMenu.pack(side=RIGHT)
|
goalOptionMenu.pack(side=RIGHT)
|
||||||
goalLabel = Label(goalFrame, text='Game goal')
|
goalLabel = Label(goalFrame, text='Game goal')
|
||||||
|
|
48
Main.py
48
Main.py
|
@ -56,9 +56,7 @@ def main(args, seed=None):
|
||||||
start = time.perf_counter()
|
start = time.perf_counter()
|
||||||
|
|
||||||
# initialize the world
|
# initialize the world
|
||||||
world = MultiWorld(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty,
|
world = MultiWorld(args.multi)
|
||||||
args.item_functionality, args.timer, args.progressive.copy(), args.goal, args.algorithm,
|
|
||||||
args.accessibility, args.shuffleganon, args.retro, args.custom, args.customitemarray, args.hints)
|
|
||||||
|
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
world.seed = get_seed(seed)
|
world.seed = get_seed(seed)
|
||||||
|
@ -67,6 +65,26 @@ def main(args, seed=None):
|
||||||
else:
|
else:
|
||||||
world.random.seed(world.seed)
|
world.random.seed(world.seed)
|
||||||
|
|
||||||
|
world.shuffle = args.shuffle.copy()
|
||||||
|
world.logic = args.logic.copy()
|
||||||
|
world.mode = args.mode.copy()
|
||||||
|
world.swords = args.swords.copy()
|
||||||
|
world.difficulty = args.difficulty.copy()
|
||||||
|
world.item_functionality = args.item_functionality.copy()
|
||||||
|
world.timer = args.timer.copy()
|
||||||
|
world.progressive = args.progressive.copy()
|
||||||
|
world.goal = args.goal.copy()
|
||||||
|
if hasattr(args, "algorithm"): # current GUI options
|
||||||
|
world.algorithm = args.algorithm
|
||||||
|
world.shuffleganon = args.shuffleganon
|
||||||
|
world.custom = args.custom
|
||||||
|
world.customitemarray = args.customitemarray
|
||||||
|
|
||||||
|
world.accessibility = args.accessibility.copy()
|
||||||
|
world.retro = args.retro.copy()
|
||||||
|
|
||||||
|
world.hints = args.hints.copy()
|
||||||
|
|
||||||
world.remote_items = args.remote_items.copy()
|
world.remote_items = args.remote_items.copy()
|
||||||
world.mapshuffle = args.mapshuffle.copy()
|
world.mapshuffle = args.mapshuffle.copy()
|
||||||
world.compassshuffle = args.compassshuffle.copy()
|
world.compassshuffle = args.compassshuffle.copy()
|
||||||
|
@ -100,14 +118,14 @@ def main(args, seed=None):
|
||||||
world.triforce_pieces_required = args.triforce_pieces_required.copy()
|
world.triforce_pieces_required = args.triforce_pieces_required.copy()
|
||||||
world.shop_shuffle = args.shop_shuffle.copy()
|
world.shop_shuffle = args.shop_shuffle.copy()
|
||||||
world.shop_shuffle_slots = args.shop_shuffle_slots.copy()
|
world.shop_shuffle_slots = args.shop_shuffle_slots.copy()
|
||||||
world.progression_balancing = {player: not balance for player, balance in args.skip_progression_balancing.items()}
|
world.progression_balancing = args.progression_balancing.copy
|
||||||
world.shuffle_prizes = args.shuffle_prizes.copy()
|
world.shuffle_prizes = args.shuffle_prizes.copy()
|
||||||
world.sprite_pool = args.sprite_pool.copy()
|
world.sprite_pool = args.sprite_pool.copy()
|
||||||
world.dark_room_logic = args.dark_room_logic.copy()
|
world.dark_room_logic = args.dark_room_logic.copy()
|
||||||
world.plando_items = args.plando_items.copy()
|
world.plando_items = args.plando_items.copy()
|
||||||
world.plando_texts = args.plando_texts.copy()
|
world.plando_texts = args.plando_texts.copy()
|
||||||
world.plando_connections = args.plando_connections.copy()
|
world.plando_connections = args.plando_connections.copy()
|
||||||
world.er_seeds = args.er_seeds.copy()
|
world.er_seeds = getattr(args, "er_seeds", {})
|
||||||
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()
|
||||||
|
@ -124,8 +142,6 @@ def main(args, seed=None):
|
||||||
world.er_seeds[player] = "vanilla"
|
world.er_seeds[player] = "vanilla"
|
||||||
elif seed.startswith("group-"): # renamed from team to group to not confuse with existing team name use
|
elif seed.startswith("group-"): # renamed from team to group to not confuse with existing team name use
|
||||||
world.er_seeds[player] = get_same_seed(world, (shuffle, seed, world.retro[player], world.mode[player], world.logic[player]))
|
world.er_seeds[player] = get_same_seed(world, (shuffle, seed, world.retro[player], world.mode[player], world.logic[player]))
|
||||||
elif seed.startswith("team-"): # TODO: remove on breaking_changes
|
|
||||||
world.er_seeds[player] = get_same_seed(world, (shuffle, seed, world.retro[player], world.mode[player], world.logic[player]))
|
|
||||||
elif not args.race:
|
elif not args.race:
|
||||||
world.er_seeds[player] = seed
|
world.er_seeds[player] = seed
|
||||||
elif seed: # race but with a set seed, ignore set seed and use group logic instead
|
elif seed: # race but with a set seed, ignore set seed and use group logic instead
|
||||||
|
@ -155,11 +171,6 @@ def main(args, seed=None):
|
||||||
item = ItemFactory(tok.strip(), player)
|
item = ItemFactory(tok.strip(), player)
|
||||||
if item:
|
if item:
|
||||||
world.push_precollected(item)
|
world.push_precollected(item)
|
||||||
# item in item_table gets checked in mystery, but not CLI - so we double-check here
|
|
||||||
world.local_items[player] = {item.strip() for item in args.local_items[player].split(',') if
|
|
||||||
item.strip() in item_table}
|
|
||||||
world.non_local_items[player] = {item.strip() for item in args.non_local_items[player].split(',') if
|
|
||||||
item.strip() in item_table}
|
|
||||||
|
|
||||||
# enforce pre-defined local items.
|
# enforce pre-defined local items.
|
||||||
if world.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]:
|
if world.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]:
|
||||||
|
@ -255,7 +266,7 @@ def main(args, seed=None):
|
||||||
|
|
||||||
logger.info('Placing Dungeon Items.')
|
logger.info('Placing Dungeon Items.')
|
||||||
|
|
||||||
if args.algorithm in ['balanced', 'vt26'] or any(
|
if world.algorithm in ['balanced', 'vt26'] or any(
|
||||||
list(args.mapshuffle.values()) + list(args.compassshuffle.values()) +
|
list(args.mapshuffle.values()) + list(args.compassshuffle.values()) +
|
||||||
list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())):
|
list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())):
|
||||||
fill_dungeons_restrictive(world)
|
fill_dungeons_restrictive(world)
|
||||||
|
@ -264,13 +275,13 @@ def main(args, seed=None):
|
||||||
|
|
||||||
logger.info('Fill the world.')
|
logger.info('Fill the world.')
|
||||||
|
|
||||||
if args.algorithm == 'flood':
|
if world.algorithm == 'flood':
|
||||||
flood_items(world) # different algo, biased towards early game progress items
|
flood_items(world) # different algo, biased towards early game progress items
|
||||||
elif args.algorithm == 'vt25':
|
elif world.algorithm == 'vt25':
|
||||||
distribute_items_restrictive(world, False)
|
distribute_items_restrictive(world, False)
|
||||||
elif args.algorithm == 'vt26':
|
elif world.algorithm == 'vt26':
|
||||||
distribute_items_restrictive(world, True)
|
distribute_items_restrictive(world, True)
|
||||||
elif args.algorithm == 'balanced':
|
elif world.algorithm == 'balanced':
|
||||||
distribute_items_restrictive(world, True)
|
distribute_items_restrictive(world, True)
|
||||||
|
|
||||||
logger.info("Filling Shop Slots")
|
logger.info("Filling Shop Slots")
|
||||||
|
@ -517,6 +528,7 @@ def main(args, seed=None):
|
||||||
|
|
||||||
def copy_world(world):
|
def copy_world(world):
|
||||||
# ToDo: Not good yet
|
# ToDo: Not good yet
|
||||||
|
# delete now?
|
||||||
ret = MultiWorld(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.item_functionality, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints)
|
ret = MultiWorld(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.item_functionality, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints)
|
||||||
ret.teams = world.teams
|
ret.teams = world.teams
|
||||||
ret.player_names = copy.deepcopy(world.player_names)
|
ret.player_names = copy.deepcopy(world.player_names)
|
||||||
|
@ -663,7 +675,7 @@ def copy_dynamic_regions_and_locations(world, ret):
|
||||||
def create_playthrough(world):
|
def create_playthrough(world):
|
||||||
# create a copy as we will modify it
|
# create a copy as we will modify it
|
||||||
old_world = world
|
old_world = world
|
||||||
world = copy_world(world)
|
world = world.copy()
|
||||||
|
|
||||||
# get locations containing progress items
|
# get locations containing progress items
|
||||||
prog_locations = {location for location in world.get_filled_locations() if location.item.advancement}
|
prog_locations = {location for location in world.get_filled_locations() if location.item.advancement}
|
||||||
|
|
118
Mystery.py
118
Mystery.py
|
@ -13,10 +13,11 @@ from worlds.generic import PlandoItem, PlandoConnection
|
||||||
ModuleUpdate.update()
|
ModuleUpdate.update()
|
||||||
|
|
||||||
from Utils import parse_yaml
|
from Utils import parse_yaml
|
||||||
from worlds.alttp.Rom import Sprite
|
|
||||||
from worlds.alttp.EntranceRandomizer import parse_arguments
|
from worlds.alttp.EntranceRandomizer import parse_arguments
|
||||||
from Main import main as ERmain
|
from Main import main as ERmain
|
||||||
from Main import get_seed, seeddigits
|
from Main import get_seed, seeddigits
|
||||||
|
import Options
|
||||||
|
from worlds import lookup_any_item_name_to_id
|
||||||
from worlds.alttp.Items import item_name_groups, item_table
|
from worlds.alttp.Items import item_name_groups, item_table
|
||||||
from worlds.alttp import Bosses
|
from worlds.alttp import Bosses
|
||||||
from worlds.alttp.Text import TextTable
|
from worlds.alttp.Text import TextTable
|
||||||
|
@ -173,16 +174,13 @@ def main(args=None, callback=ERmain):
|
||||||
weights_cache[path][key] = option
|
weights_cache[path][key] = option
|
||||||
|
|
||||||
name_counter = Counter()
|
name_counter = Counter()
|
||||||
|
erargs.player_settings = {}
|
||||||
for player in range(1, args.multi + 1):
|
for player in range(1, args.multi + 1):
|
||||||
path = player_path_cache[player]
|
path = player_path_cache[player]
|
||||||
if path:
|
if path:
|
||||||
try:
|
try:
|
||||||
settings = settings_cache[path] if settings_cache[path] else \
|
settings = settings_cache[path] if settings_cache[path] else \
|
||||||
roll_settings(weights_cache[path], args.plando)
|
roll_settings(weights_cache[path], args.plando)
|
||||||
if settings.sprite and not os.path.isfile(settings.sprite) and not Sprite.get_sprite_from_name(
|
|
||||||
settings.sprite):
|
|
||||||
logging.warning(
|
|
||||||
f"Warning: The chosen sprite, \"{settings.sprite}\", for yaml \"{path}\", does not exist.")
|
|
||||||
if args.pre_roll:
|
if args.pre_roll:
|
||||||
import yaml
|
import yaml
|
||||||
if path == args.weights:
|
if path == args.weights:
|
||||||
|
@ -206,7 +204,10 @@ def main(args=None, callback=ERmain):
|
||||||
yaml.dump(pre_rolled, f)
|
yaml.dump(pre_rolled, f)
|
||||||
for k, v in vars(settings).items():
|
for k, v in vars(settings).items():
|
||||||
if v is not None:
|
if v is not None:
|
||||||
|
try:
|
||||||
getattr(erargs, k)[player] = v
|
getattr(erargs, k)[player] = v
|
||||||
|
except AttributeError:
|
||||||
|
setattr(erargs, k, {player: v})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"File {path} is destroyed. Please fix your yaml.") from e
|
raise ValueError(f"File {path} is destroyed. Please fix your yaml.") from e
|
||||||
else:
|
else:
|
||||||
|
@ -251,9 +252,6 @@ def main(args=None, callback=ERmain):
|
||||||
with open(os.path.join(args.outputpath if args.outputpath else ".", f"mystery_result_{seed}.yaml"), "wt") as f:
|
with open(os.path.join(args.outputpath if args.outputpath else ".", f"mystery_result_{seed}.yaml"), "wt") as f:
|
||||||
yaml.dump(important, f)
|
yaml.dump(important, f)
|
||||||
|
|
||||||
erargs.skip_progression_balancing = {player: not balanced for player, balanced in
|
|
||||||
erargs.progression_balancing.items()}
|
|
||||||
del (erargs.progression_balancing)
|
|
||||||
callback(erargs, seed)
|
callback(erargs, seed)
|
||||||
|
|
||||||
|
|
||||||
|
@ -311,14 +309,10 @@ available_boss_locations: typing.Set[str] = {f"{loc.lower()}{f' {level}' if leve
|
||||||
|
|
||||||
boss_shuffle_options = {None: 'none',
|
boss_shuffle_options = {None: 'none',
|
||||||
'none': 'none',
|
'none': 'none',
|
||||||
'simple': 'basic',
|
|
||||||
'basic': 'basic',
|
'basic': 'basic',
|
||||||
'full': 'normal',
|
|
||||||
'normal': 'normal',
|
'normal': 'normal',
|
||||||
'random': 'chaos',
|
|
||||||
'chaos': 'chaos',
|
'chaos': 'chaos',
|
||||||
'singularity': 'singularity',
|
'singularity': 'singularity'
|
||||||
'duality': 'singularity'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -420,7 +414,7 @@ def get_plando_bosses(boss_shuffle: str, plando_options: typing.Set[str]) -> str
|
||||||
raise Exception(f"Boss Shuffle {boss_shuffle} is unknown and boss plando is turned off.")
|
raise Exception(f"Boss Shuffle {boss_shuffle} is unknown and boss plando is turned off.")
|
||||||
|
|
||||||
|
|
||||||
def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("bosses"))):
|
def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("bosses", ))):
|
||||||
if "pre_rolled" in weights:
|
if "pre_rolled" in weights:
|
||||||
pre_rolled = weights["pre_rolled"]
|
pre_rolled = weights["pre_rolled"]
|
||||||
|
|
||||||
|
@ -459,9 +453,37 @@ 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.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()
|
||||||
|
for item_name in weights.get('local_items', []):
|
||||||
|
items = item_name_groups.get(item_name, {item_name})
|
||||||
|
for item in items:
|
||||||
|
if item in lookup_any_item_name_to_id:
|
||||||
|
ret.local_items.add(item)
|
||||||
|
else:
|
||||||
|
raise Exception(f"Could not force item {item} to be world-local, as it was not recognized.")
|
||||||
|
|
||||||
|
ret.non_local_items = set()
|
||||||
|
for item_name in weights.get('non_local_items', []):
|
||||||
|
items = item_name_groups.get(item_name, {item_name})
|
||||||
|
for item in items:
|
||||||
|
if item in lookup_any_item_name_to_id:
|
||||||
|
ret.non_local_items.add(item)
|
||||||
|
else:
|
||||||
|
raise Exception(f"Could not force item {item} to be world-non-local, as it was not recognized.")
|
||||||
|
|
||||||
|
if ret.game == "A Link to the Past":
|
||||||
|
roll_alttp_settings(ret, weights, plando_options)
|
||||||
|
elif ret.game == "Hollow Knight":
|
||||||
|
for option_name, option in Options.hollow_knight_options.items():
|
||||||
|
setattr(ret, option_name, option.from_any(get_choice(option_name, weights, True)))
|
||||||
|
else:
|
||||||
|
raise Exception(f"Unsupported game {ret.game}")
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||||
glitches_required = get_choice('glitches_required', weights)
|
glitches_required = get_choice('glitches_required', weights)
|
||||||
if glitches_required not in [None, 'none', 'no_logic', 'overworld_glitches', 'minor_glitches']:
|
if glitches_required not in [None, 'none', 'no_logic', 'overworld_glitches', 'minor_glitches']:
|
||||||
logging.warning("Only NMG, OWG and No Logic supported")
|
logging.warning("Only NMG, OWG and No Logic supported")
|
||||||
|
@ -500,8 +522,6 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
'universal' if 'u' in dungeon_items else 's' in dungeon_items)
|
'universal' if 'u' in dungeon_items else 's' in dungeon_items)
|
||||||
ret.bigkeyshuffle = get_choice('bigkey_shuffle', weights, 'b' in dungeon_items)
|
ret.bigkeyshuffle = get_choice('bigkey_shuffle', weights, 'b' in dungeon_items)
|
||||||
|
|
||||||
ret.accessibility = get_choice('accessibility', weights)
|
|
||||||
|
|
||||||
entrance_shuffle = get_choice('entrance_shuffle', weights, 'vanilla')
|
entrance_shuffle = get_choice('entrance_shuffle', weights, 'vanilla')
|
||||||
if entrance_shuffle.startswith('none-'):
|
if entrance_shuffle.startswith('none-'):
|
||||||
ret.shuffle = 'vanilla'
|
ret.shuffle = 'vanilla'
|
||||||
|
@ -510,12 +530,11 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
|
|
||||||
goal = get_choice('goals', weights, 'ganon')
|
goal = get_choice('goals', weights, 'ganon')
|
||||||
ret.goal = {'ganon': 'ganon',
|
ret.goal = {'ganon': 'ganon',
|
||||||
'fast_ganon': 'crystals',
|
'crystals': 'crystals',
|
||||||
'dungeons': 'dungeons',
|
'bosses': 'bosses',
|
||||||
'pedestal': 'pedestal',
|
'pedestal': 'pedestal',
|
||||||
'ganon_pedestal': 'ganonpedestal',
|
'ganon_pedestal': 'ganonpedestal',
|
||||||
'triforce_hunt': 'triforcehunt',
|
'triforce_hunt': 'triforcehunt',
|
||||||
'triforce-hunt': 'triforcehunt', # deprecated, moving all goals to `_`
|
|
||||||
'local_triforce_hunt': 'localtriforcehunt',
|
'local_triforce_hunt': 'localtriforcehunt',
|
||||||
'ganon_triforce_hunt': 'ganontriforcehunt',
|
'ganon_triforce_hunt': 'ganontriforcehunt',
|
||||||
'local_ganon_triforce_hunt': 'localganontriforcehunt',
|
'local_ganon_triforce_hunt': 'localganontriforcehunt',
|
||||||
|
@ -582,36 +601,12 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
boss_shuffle = get_choice('boss_shuffle', weights)
|
boss_shuffle = get_choice('boss_shuffle', weights)
|
||||||
ret.shufflebosses = get_plando_bosses(boss_shuffle, plando_options)
|
ret.shufflebosses = get_plando_bosses(boss_shuffle, plando_options)
|
||||||
|
|
||||||
ret.enemy_shuffle = {'none': False,
|
ret.enemy_shuffle = bool(get_choice('enemy_shuffle', weights, False))
|
||||||
'shuffled': 'shuffled',
|
|
||||||
'random': 'chaos',
|
|
||||||
'chaosthieves': 'chaosthieves',
|
|
||||||
'chaos': 'chaos',
|
|
||||||
True: True,
|
|
||||||
False: False,
|
|
||||||
None: False
|
|
||||||
}[get_choice('enemy_shuffle', weights, False)]
|
|
||||||
|
|
||||||
ret.killable_thieves = get_choice('killable_thieves', weights, False)
|
ret.killable_thieves = get_choice('killable_thieves', weights, False)
|
||||||
ret.tile_shuffle = get_choice('tile_shuffle', weights, False)
|
ret.tile_shuffle = get_choice('tile_shuffle', weights, False)
|
||||||
ret.bush_shuffle = get_choice('bush_shuffle', weights, False)
|
ret.bush_shuffle = get_choice('bush_shuffle', weights, False)
|
||||||
|
|
||||||
# legacy support for enemy shuffle
|
|
||||||
if type(ret.enemy_shuffle) == str:
|
|
||||||
if ret.enemy_shuffle == 'shuffled':
|
|
||||||
ret.killable_thieves = True
|
|
||||||
elif ret.enemy_shuffle == 'chaos':
|
|
||||||
ret.killable_thieves = True
|
|
||||||
ret.bush_shuffle = True
|
|
||||||
ret.tile_shuffle = True
|
|
||||||
elif ret.enemy_shuffle == "chaosthieves":
|
|
||||||
ret.killable_thieves = bool(random.randint(0, 1))
|
|
||||||
ret.bush_shuffle = True
|
|
||||||
ret.tile_shuffle = True
|
|
||||||
ret.enemy_shuffle = True
|
|
||||||
|
|
||||||
# end of legacy block
|
|
||||||
|
|
||||||
ret.enemy_damage = {None: 'default',
|
ret.enemy_damage = {None: 'default',
|
||||||
'default': 'default',
|
'default': 'default',
|
||||||
'shuffled': 'shuffled',
|
'shuffled': 'shuffled',
|
||||||
|
@ -648,7 +643,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
get_choice("turtle_rock_medallion", weights, "random")]
|
get_choice("turtle_rock_medallion", weights, "random")]
|
||||||
|
|
||||||
for index, medallion in enumerate(ret.required_medallions):
|
for index, medallion in enumerate(ret.required_medallions):
|
||||||
ret.required_medallions[index] = {"ether": "Ether", "quake": "Quake", "bombos": "Bombos", "random": "random"}\
|
ret.required_medallions[index] = {"ether": "Ether", "quake": "Quake", "bombos": "Bombos", "random": "random"} \
|
||||||
.get(medallion.lower(), None)
|
.get(medallion.lower(), None)
|
||||||
if not ret.required_medallions[index]:
|
if not ret.required_medallions[index]:
|
||||||
raise Exception(f"unknown Medallion {medallion} for {'misery mire' if index == 0 else 'turtle rock'}")
|
raise Exception(f"unknown Medallion {medallion} for {'misery mire' if index == 0 else 'turtle rock'}")
|
||||||
|
@ -672,30 +667,8 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
|
|
||||||
if get_choice("local_keys", weights, "l" in dungeon_items):
|
if get_choice("local_keys", weights, "l" in dungeon_items):
|
||||||
# () important for ordering of commands, without them the Big Keys section is part of the Small Key else
|
# () important for ordering of commands, without them the Big Keys section is part of the Small Key else
|
||||||
ret.local_items = (item_name_groups["Small Keys"] if ret.keyshuffle else set()) \
|
ret.local_items |= item_name_groups["Small Keys"] if ret.keyshuffle else set()
|
||||||
| item_name_groups["Big Keys"] if ret.bigkeyshuffle else set()
|
ret.local_items |= item_name_groups["Big Keys"] if ret.bigkeyshuffle else set()
|
||||||
else:
|
|
||||||
ret.local_items = set()
|
|
||||||
for item_name in weights.get('local_items', []):
|
|
||||||
items = item_name_groups.get(item_name, {item_name})
|
|
||||||
for item in items:
|
|
||||||
if item in item_table:
|
|
||||||
ret.local_items.add(item)
|
|
||||||
else:
|
|
||||||
raise Exception(f"Could not force item {item} to be world-local, as it was not recognized.")
|
|
||||||
|
|
||||||
ret.local_items = ",".join(ret.local_items)
|
|
||||||
|
|
||||||
ret.non_local_items = set()
|
|
||||||
for item_name in weights.get('non_local_items', []):
|
|
||||||
items = item_name_groups.get(item_name, {item_name})
|
|
||||||
for item in items:
|
|
||||||
if item in item_table:
|
|
||||||
ret.non_local_items.add(item)
|
|
||||||
else:
|
|
||||||
raise Exception(f"Could not force item {item} to be world-non-local, as it was not recognized.")
|
|
||||||
|
|
||||||
ret.non_local_items = ",".join(ret.non_local_items)
|
|
||||||
|
|
||||||
ret.plando_items = []
|
ret.plando_items = []
|
||||||
if "items" in plando_options:
|
if "items" in plando_options:
|
||||||
|
@ -704,7 +677,8 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
if item not in item_table:
|
if item not in item_table:
|
||||||
raise Exception(f"Could not plando item {item} as the item was not recognized")
|
raise Exception(f"Could not plando item {item} as the item was not recognized")
|
||||||
if location not in location_table and location not in key_drop_data:
|
if location not in location_table and location not in key_drop_data:
|
||||||
raise Exception(f"Could not plando item {item} at location {location} as the location was not recognized")
|
raise Exception(
|
||||||
|
f"Could not plando item {item} at location {location} as the location was not recognized")
|
||||||
ret.plando_items.append(PlandoItem(item, location, location_world, from_pool, force))
|
ret.plando_items.append(PlandoItem(item, location, location_world, from_pool, force))
|
||||||
|
|
||||||
options = weights.get("plando_items", [])
|
options = weights.get("plando_items", [])
|
||||||
|
@ -798,8 +772,6 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
else:
|
else:
|
||||||
ret.quickswap = True
|
ret.quickswap = True
|
||||||
ret.sprite = "Link"
|
ret.sprite = "Link"
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
62
Options.py
62
Options.py
|
@ -39,6 +39,10 @@ class Option(metaclass=AssembleOptions):
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
return bool(self.value)
|
return bool(self.value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_any(cls, data: typing.Any):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class Toggle(Option):
|
class Toggle(Option):
|
||||||
option_false = 0
|
option_false = 0
|
||||||
|
@ -54,6 +58,13 @@ class Toggle(Option):
|
||||||
else:
|
else:
|
||||||
return cls(1)
|
return cls(1)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_any(cls, data: typing.Any):
|
||||||
|
if type(data) == str:
|
||||||
|
return cls.from_text(data)
|
||||||
|
else:
|
||||||
|
return cls(data)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if isinstance(other, Toggle):
|
if isinstance(other, Toggle):
|
||||||
return self.value == other.value
|
return self.value == other.value
|
||||||
|
@ -75,7 +86,6 @@ class Choice(Option):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_text(cls, text: str) -> Choice:
|
def from_text(cls, text: str) -> Choice:
|
||||||
|
|
||||||
for optionname, value in cls.options.items():
|
for optionname, value in cls.options.items():
|
||||||
if optionname == text.lower():
|
if optionname == text.lower():
|
||||||
return cls(value)
|
return cls(value)
|
||||||
|
@ -83,6 +93,10 @@ class Choice(Option):
|
||||||
f'Could not find option "{text}" for "{cls.__name__}", '
|
f'Could not find option "{text}" for "{cls.__name__}", '
|
||||||
f'known options are {", ".join(f"{option}" for option in cls.name_lookup.values())}')
|
f'known options are {", ".join(f"{option}" for option in cls.name_lookup.values())}')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_any(cls, data: typing.Any):
|
||||||
|
return cls.from_text(data)
|
||||||
|
|
||||||
|
|
||||||
class Logic(Choice):
|
class Logic(Choice):
|
||||||
option_no_glitches = 0
|
option_no_glitches = 0
|
||||||
|
@ -151,6 +165,52 @@ keyshuffle = Toggle
|
||||||
bigkeyshuffle = Toggle
|
bigkeyshuffle = Toggle
|
||||||
hints = Toggle
|
hints = Toggle
|
||||||
|
|
||||||
|
RandomizeDreamers = Toggle
|
||||||
|
RandomizeSkills = Toggle
|
||||||
|
RandomizeCharms = Toggle
|
||||||
|
RandomizeKeys = Toggle
|
||||||
|
RandomizeGeoChests = Toggle
|
||||||
|
RandomizeMaskShards = Toggle
|
||||||
|
RandomizeVesselFragments = Toggle
|
||||||
|
RandomizeCharmNotches = Toggle
|
||||||
|
RandomizePaleOre = Toggle
|
||||||
|
RandomizeRancidEggs = Toggle
|
||||||
|
RandomizeRelics = Toggle
|
||||||
|
RandomizeMaps = Toggle
|
||||||
|
RandomizeStags = Toggle
|
||||||
|
RandomizeGrubs = Toggle
|
||||||
|
RandomizeWhisperingRoots = Toggle
|
||||||
|
RandomizeRocks = Toggle
|
||||||
|
RandomizeSoulTotems = Toggle
|
||||||
|
RandomizePalaceTotems = Toggle
|
||||||
|
RandomizeLoreTablets = Toggle
|
||||||
|
RandomizeLifebloodCocoons = 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
hollow_knight_options: typing.Dict[str, Option] = {**hollow_knight_randomize_options}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
|
@ -27,11 +27,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Fast Ganon (Pyramid Always Open)",
|
"name": "Fast Ganon (Pyramid Always Open)",
|
||||||
"value": "fast_ganon"
|
"value": "crystals"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "All Dungeons",
|
"name": "All Bosses",
|
||||||
"value": "dungeons"
|
"value": "bosses"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Master Sword Pedestal",
|
"name": "Master Sword Pedestal",
|
||||||
|
|
|
@ -356,9 +356,9 @@
|
||||||
"description": "Kill Ganon in his lair. The hole is always open, but you may still require some crystals to damage him.",
|
"description": "Kill Ganon in his lair. The hole is always open, but you may still require some crystals to damage him.",
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
},
|
},
|
||||||
"dungeons": {
|
"bosses": {
|
||||||
"keyString": "goals.dungeons",
|
"keyString": "goals.bosses",
|
||||||
"friendlyName": "All Dungeons",
|
"friendlyName": "All Bosses",
|
||||||
"description": "Defeat the boss of all dungeons, defeat Agahnim in both Castle Tower and Ganon's Tower, then defeat Ganon in his lair.",
|
"description": "Defeat the boss of all dungeons, defeat Agahnim in both Castle Tower and Ganon's Tower, then defeat Ganon in his lair.",
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,8 +12,7 @@ from worlds.alttp.Rules import set_rules
|
||||||
|
|
||||||
class TestDungeon(unittest.TestCase):
|
class TestDungeon(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'noglitches'}, {1: 'open'}, {1: 'random'}, {1: 'normal'}, {1: 'normal'}, {1:False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
|
self.world = MultiWorld(1)
|
||||||
True, {1:False}, False, None, {1:False})
|
|
||||||
self.starting_regions = [] # Where to start exploring
|
self.starting_regions = [] # Where to start exploring
|
||||||
self.remove_exits = [] # Block dungeon exits
|
self.remove_exits = [] # Block dungeon exits
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
|
|
|
@ -7,8 +7,7 @@ from test.TestBase import TestBase
|
||||||
|
|
||||||
class TestVanilla(TestBase):
|
class TestVanilla(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'noglitches'}, {1: 'open'}, {1: 'random'}, {1: 'normal'}, {1: 'normal'}, {1:False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
|
self.world = MultiWorld(1)
|
||||||
True, {1:False}, False, None, {1:False})
|
|
||||||
self.world.game[1] = "Hollow Knight"
|
self.world.game[1] = "Hollow Knight"
|
||||||
create_regions(self.world, 1)
|
create_regions(self.world, 1)
|
||||||
gen_hollow(self.world, 1)
|
gen_hollow(self.world, 1)
|
|
@ -12,9 +12,9 @@ from test.TestBase import TestBase
|
||||||
|
|
||||||
class TestInverted(TestBase):
|
class TestInverted(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'noglitches'}, {1: 'inverted'}, {1: 'random'}, {1: 'normal'}, {1: 'normal'}, {1:False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
|
self.world = MultiWorld(1)
|
||||||
True, {1:False}, False, None, {1:False})
|
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
|
self.world.mode[1] = "inverted"
|
||||||
create_inverted_regions(self.world, 1)
|
create_inverted_regions(self.world, 1)
|
||||||
create_dungeons(self.world, 1)
|
create_dungeons(self.world, 1)
|
||||||
create_shops(self.world, 1)
|
create_shops(self.world, 1)
|
||||||
|
|
|
@ -12,8 +12,8 @@ from worlds.alttp.Rules import set_inverted_big_bomb_rules
|
||||||
class TestInvertedBombRules(unittest.TestCase):
|
class TestInvertedBombRules(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'noglitches'}, {1: 'inverted'}, {1: 'random'}, {1: 'normal'}, {1: 'normal'}, {1:False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
|
self.world = MultiWorld(1)
|
||||||
True, {1:False}, False, None, {1:False})
|
self.world.mode[1] = "inverted"
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
create_inverted_regions(self.world, 1)
|
create_inverted_regions(self.world, 1)
|
||||||
create_dungeons(self.world, 1)
|
create_dungeons(self.world, 1)
|
||||||
|
|
|
@ -12,9 +12,9 @@ from test.TestBase import TestBase
|
||||||
|
|
||||||
class TestInvertedMinor(TestBase):
|
class TestInvertedMinor(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'minorglitches'}, {1: 'inverted'}, {1: 'random'}, {1: 'normal'},
|
self.world = MultiWorld(1)
|
||||||
{1: 'normal'}, {1: False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
|
self.world.mode[1] = "inverted"
|
||||||
True, {1: False}, False, None, {1: False})
|
self.world.logic[1] = "minorglitches"
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
create_inverted_regions(self.world, 1)
|
create_inverted_regions(self.world, 1)
|
||||||
create_dungeons(self.world, 1)
|
create_dungeons(self.world, 1)
|
||||||
|
|
|
@ -12,8 +12,9 @@ from test.TestBase import TestBase
|
||||||
|
|
||||||
class TestInvertedOWG(TestBase):
|
class TestInvertedOWG(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'owglitches'}, {1: 'inverted'}, {1: 'random'}, {1: 'normal'}, {1: 'normal'}, {1:False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
|
self.world = MultiWorld(1)
|
||||||
True, {1:False}, False, None, {1:False})
|
self.world.logic[1] = "owglitches"
|
||||||
|
self.world.mode[1] = "inverted"
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
create_inverted_regions(self.world, 1)
|
create_inverted_regions(self.world, 1)
|
||||||
create_dungeons(self.world, 1)
|
create_dungeons(self.world, 1)
|
||||||
|
|
|
@ -12,9 +12,8 @@ from test.TestBase import TestBase
|
||||||
|
|
||||||
class TestMinor(TestBase):
|
class TestMinor(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'minorglitches'}, {1: 'open'}, {1: 'random'}, {1: 'normal'},
|
self.world = MultiWorld(1)
|
||||||
{1: 'normal'}, {1: False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
|
self.world.logic[1] = "minorglitches"
|
||||||
True, {1: False}, False, None, {1: False})
|
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
create_regions(self.world, 1)
|
create_regions(self.world, 1)
|
||||||
create_dungeons(self.world, 1)
|
create_dungeons(self.world, 1)
|
||||||
|
|
|
@ -12,9 +12,9 @@ from test.TestBase import TestBase
|
||||||
|
|
||||||
class TestVanillaOWG(TestBase):
|
class TestVanillaOWG(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'owglitches'}, {1: 'open'}, {1: 'random'}, {1: 'normal'}, {1: 'normal'}, {1:False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
|
self.world = MultiWorld(1)
|
||||||
True, {1:False}, False, None, {1:False})
|
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
|
self.world.logic[1] = "owglitches"
|
||||||
create_regions(self.world, 1)
|
create_regions(self.world, 1)
|
||||||
create_dungeons(self.world, 1)
|
create_dungeons(self.world, 1)
|
||||||
create_shops(self.world, 1)
|
create_shops(self.world, 1)
|
||||||
|
|
|
@ -12,8 +12,8 @@ from test.TestBase import TestBase
|
||||||
|
|
||||||
class TestVanilla(TestBase):
|
class TestVanilla(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'noglitches'}, {1: 'open'}, {1: 'random'}, {1: 'normal'}, {1: 'normal'}, {1:False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
|
self.world = MultiWorld(1)
|
||||||
True, {1:False}, False, None, {1:False})
|
self.world.logic[1] = "noglitches"
|
||||||
self.world.difficulty_requirements[1] = difficulties['normal']
|
self.world.difficulty_requirements[1] = difficulties['normal']
|
||||||
create_regions(self.world, 1)
|
create_regions(self.world, 1)
|
||||||
create_dungeons(self.world, 1)
|
create_dungeons(self.world, 1)
|
||||||
|
|
|
@ -8,6 +8,7 @@ __all__ = {"lookup_any_item_id_to_name",
|
||||||
from .alttp.Items import lookup_id_to_name as alttp
|
from .alttp.Items import lookup_id_to_name as alttp
|
||||||
from .hk.Items import lookup_id_to_name as hk
|
from .hk.Items import lookup_id_to_name as hk
|
||||||
lookup_any_item_id_to_name = {**alttp, **hk}
|
lookup_any_item_id_to_name = {**alttp, **hk}
|
||||||
|
lookup_any_item_name_to_id = {name: id for id, name in lookup_any_item_id_to_name.items()}
|
||||||
|
|
||||||
|
|
||||||
from .alttp import Regions
|
from .alttp import Regions
|
||||||
|
|
|
@ -59,7 +59,7 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
Vanilla: Swords are in vanilla locations.
|
Vanilla: Swords are in vanilla locations.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--goal', default=defval('ganon'), const='ganon', nargs='?',
|
parser.add_argument('--goal', default=defval('ganon'), const='ganon', nargs='?',
|
||||||
choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'localtriforcehunt', 'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal'],
|
choices=['ganon', 'pedestal', 'bosses', 'triforcehunt', 'localtriforcehunt', 'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal'],
|
||||||
help='''\
|
help='''\
|
||||||
Select completion goal. (default: %(default)s)
|
Select completion goal. (default: %(default)s)
|
||||||
Ganon: Collect all crystals, beat Agahnim 2 then
|
Ganon: Collect all crystals, beat Agahnim 2 then
|
||||||
|
@ -317,8 +317,8 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--suppress_rom', help='Do not create an output rom file.', action='store_true')
|
parser.add_argument('--suppress_rom', help='Do not create an output rom file.', action='store_true')
|
||||||
parser.add_argument('--gui', help='Launch the GUI', action='store_true')
|
parser.add_argument('--gui', help='Launch the GUI', action='store_true')
|
||||||
parser.add_argument('--skip_progression_balancing', action='store_true', default=defval(False),
|
parser.add_argument('--progression_balancing', action='store_true', default=defval(False),
|
||||||
help="Skip Multiworld Progression balancing.")
|
help="Enable Multiworld Progression balancing.")
|
||||||
parser.add_argument('--skip_playthrough', action='store_true', default=defval(False))
|
parser.add_argument('--skip_playthrough', action='store_true', default=defval(False))
|
||||||
parser.add_argument('--enemizercli', default=defval('EnemizerCLI/EnemizerCLI.Core'))
|
parser.add_argument('--enemizercli', default=defval('EnemizerCLI/EnemizerCLI.Core'))
|
||||||
parser.add_argument('--shufflebosses', default=defval('none'), choices=['none', 'basic', 'normal', 'chaos',
|
parser.add_argument('--shufflebosses', default=defval('none'), choices=['none', 'basic', 'normal', 'chaos',
|
||||||
|
@ -407,7 +407,7 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
'local_items', 'non_local_items', 'retro', 'accessibility', 'hints', 'beemizer',
|
'local_items', 'non_local_items', 'retro', 'accessibility', 'hints', 'beemizer',
|
||||||
'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
|
'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
||||||
'heartbeep', "skip_progression_balancing", "triforce_pieces_available",
|
'heartbeep', "progression_balancing", "triforce_pieces_available",
|
||||||
"triforce_pieces_required", "shop_shuffle", "shop_shuffle_slots",
|
"triforce_pieces_required", "shop_shuffle", "shop_shuffle_slots",
|
||||||
"required_medallions",
|
"required_medallions",
|
||||||
"plando_items", "plando_texts", "plando_connections", "er_seeds",
|
"plando_items", "plando_texts", "plando_connections", "er_seeds",
|
||||||
|
|
|
@ -226,7 +226,7 @@ for diff in {'easy', 'normal', 'hard', 'expert'}:
|
||||||
def generate_itempool(world, player: int):
|
def generate_itempool(world, player: int):
|
||||||
if world.difficulty[player] not in difficulties:
|
if world.difficulty[player] not in difficulties:
|
||||||
raise NotImplementedError(f"Diffulty {world.difficulty[player]}")
|
raise NotImplementedError(f"Diffulty {world.difficulty[player]}")
|
||||||
if world.goal[player] not in {'ganon', 'pedestal', 'dungeons', 'triforcehunt', 'localtriforcehunt', 'icerodhunt',
|
if world.goal[player] not in {'ganon', 'pedestal', 'bosses', 'triforcehunt', 'localtriforcehunt', 'icerodhunt',
|
||||||
'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal'}:
|
'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal'}:
|
||||||
raise NotImplementedError(f"Goal {world.goal[player]}")
|
raise NotImplementedError(f"Goal {world.goal[player]}")
|
||||||
if world.mode[player] not in {'open', 'standard', 'inverted'}:
|
if world.mode[player] not in {'open', 'standard', 'inverted'}:
|
||||||
|
|
|
@ -779,7 +779,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||||
rom.write_int16(0x15DB5 + 2 * offset, 0x0640)
|
rom.write_int16(0x15DB5 + 2 * offset, 0x0640)
|
||||||
elif room_id == 0x00d6 and world.fix_trock_exit[player]:
|
elif room_id == 0x00d6 and world.fix_trock_exit[player]:
|
||||||
rom.write_int16(0x15DB5 + 2 * offset, 0x0134)
|
rom.write_int16(0x15DB5 + 2 * offset, 0x0134)
|
||||||
elif room_id == 0x000c and world.fix_gtower_exit: # fix ganons tower exit point
|
elif room_id == 0x000c and world.shuffle_ganon: # fix ganons tower exit point
|
||||||
rom.write_int16(0x15DB5 + 2 * offset, 0x00A4)
|
rom.write_int16(0x15DB5 + 2 * offset, 0x00A4)
|
||||||
else:
|
else:
|
||||||
rom.write_int16(0x15DB5 + 2 * offset, link_y)
|
rom.write_int16(0x15DB5 + 2 * offset, link_y)
|
||||||
|
@ -1403,8 +1403,8 @@ def patch_rom(world, rom, player, team, enemized):
|
||||||
rom.write_byte(0x18003E, 0x05) # make ganon invincible until enough triforce pieces are collected
|
rom.write_byte(0x18003E, 0x05) # make ganon invincible until enough triforce pieces are collected
|
||||||
elif world.goal[player] in ['ganonpedestal']:
|
elif world.goal[player] in ['ganonpedestal']:
|
||||||
rom.write_byte(0x18003E, 0x06)
|
rom.write_byte(0x18003E, 0x06)
|
||||||
elif world.goal[player] in ['dungeons']:
|
elif world.goal[player] in ['bosses']:
|
||||||
rom.write_byte(0x18003E, 0x02) # make ganon invincible until all dungeons are beat
|
rom.write_byte(0x18003E, 0x02) # make ganon invincible until all bosses are beat
|
||||||
elif world.goal[player] in ['crystals']:
|
elif world.goal[player] in ['crystals']:
|
||||||
rom.write_byte(0x18003E, 0x04) # make ganon invincible until all crystals
|
rom.write_byte(0x18003E, 0x04) # make ganon invincible until all crystals
|
||||||
else:
|
else:
|
||||||
|
@ -2234,8 +2234,8 @@ def write_strings(rom, world, player, team):
|
||||||
else:
|
else:
|
||||||
tt['sign_ganons_tower'] = f'You need {world.crystals_needed_for_gt[player]} crystals to enter.'
|
tt['sign_ganons_tower'] = f'You need {world.crystals_needed_for_gt[player]} crystals to enter.'
|
||||||
|
|
||||||
if world.goal[player] == 'dungeons':
|
if world.goal[player] == 'bosses':
|
||||||
tt['sign_ganon'] = 'You need to complete all the dungeons.'
|
tt['sign_ganon'] = 'You need to kill all bosses, Ganon last.'
|
||||||
elif world.goal[player] == 'ganonpedestal':
|
elif world.goal[player] == 'ganonpedestal':
|
||||||
tt['sign_ganon'] = 'You need to pull the pedestal to defeat Ganon.'
|
tt['sign_ganon'] = 'You need to pull the pedestal to defeat Ganon.'
|
||||||
elif world.goal[player] == "ganon":
|
elif world.goal[player] == "ganon":
|
||||||
|
|
|
@ -59,8 +59,8 @@ def set_rules(world, player):
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(f'Not implemented yet: Logic - {world.logic[player]}')
|
raise NotImplementedError(f'Not implemented yet: Logic - {world.logic[player]}')
|
||||||
|
|
||||||
if world.goal[player] == 'dungeons':
|
if world.goal[player] == 'bosses':
|
||||||
# require all dungeons to beat ganon
|
# require all bosses to beat ganon
|
||||||
add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player) and state.has('Beat Agahnim 1', player) and state.has('Beat Agahnim 2', player) and state.has_crystals(7, player))
|
add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player) and state.has('Beat Agahnim 1', player) and state.has('Beat Agahnim 2', player) and state.has_crystals(7, player))
|
||||||
elif world.goal[player] == 'ganon':
|
elif world.goal[player] == 'ganon':
|
||||||
# require aga2 to beat ganon
|
# require aga2 to beat ganon
|
||||||
|
|
Loading…
Reference in New Issue