From 8d933b82debbe0f4b4f2f29575f179e1ebf9ece9 Mon Sep 17 00:00:00 2001 From: Fouton Date: Fri, 12 Mar 2021 16:41:35 -0500 Subject: [PATCH 01/17] TFH Futures --- BaseClasses.py | 6 +-- CLI.py | 106 ++++++++++++++++++++++++------------------------- ItemList.py | 31 +++++---------- Main.py | 9 ++--- Mystery.py | 9 +++-- Rom.py | 7 +--- 6 files changed, 76 insertions(+), 92 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index aafe03a8..45017752 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -128,6 +128,7 @@ class World(object): set_player_attr('open_pyramid', False) set_player_attr('treasure_hunt_icon', 'Triforce Piece') set_player_attr('treasure_hunt_count', 0) + set_player_attr('treasure_hunt_total', 0) set_player_attr('potshuffle', False) set_player_attr('pot_contents', None) @@ -1377,11 +1378,6 @@ class Door(object): else: self.passage = False - def kind(self, world): - if self.roomIndex != -1 and self.doorListPos != -1: - return world.get_room(self.roomIndex, self.player).kind(self) - return None - def __eq__(self, other): return isinstance(other, self.__class__) and self.name == other.name diff --git a/CLI.py b/CLI.py index a48031b5..0ceec234 100644 --- a/CLI.py +++ b/CLI.py @@ -6,6 +6,7 @@ import textwrap import shlex import sys +import source.classes.constants as CONST from source.classes.BabelFish import BabelFish from Utils import update_deprecated_args @@ -16,7 +17,6 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): def _get_help_string(self, action): return textwrap.dedent(action.help) - def parse_cli(argv, no_defaults=False): def defval(value): return value if not no_defaults else None @@ -28,49 +28,44 @@ def parse_cli(argv, no_defaults=False): fish = BabelFish(lang=lang) # we need to know how many players we have first - # also if we're loading our own settings file, we should do that now parser = argparse.ArgumentParser(add_help=False) - parser.add_argument('--settingsfile', help="input json file of settings", type=str) parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255)) multiargs, _ = parser.parse_known_args(argv) - if multiargs.settingsfile: - settings = apply_settings_file(settings, multiargs.settingsfile) - parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) # get args args = [] - with open(os.path.join("resources", "app", "cli", "args.json")) as argsFile: - args = json.load(argsFile) - for arg in args: - argdata = args[arg] - argname = "--" + arg - argatts = {} - argatts["help"] = "(default: %(default)s)" - if "action" in argdata: - argatts["action"] = argdata["action"] - if "choices" in argdata: - argatts["choices"] = argdata["choices"] - argatts["const"] = argdata["choices"][0] - argatts["default"] = argdata["choices"][0] - argatts["nargs"] = "?" - if arg in settings: - default = settings[arg] - if "type" in argdata and argdata["type"] == "bool": - default = settings[arg] != 0 - argatts["default"] = defval(default) - arghelp = fish.translate("cli", "help", arg) - if "help" in argdata and argdata["help"] == "suppress": - argatts["help"] = argparse.SUPPRESS - elif not isinstance(arghelp, str): - argatts["help"] = '\n'.join(arghelp).replace("\\'", "'") - else: - argatts["help"] = arghelp + " " + argatts["help"] - parser.add_argument(argname, **argatts) + with open(os.path.join("resources","app","cli","args.json")) as argsFile: + args = json.load(argsFile) + for arg in args: + argdata = args[arg] + argname = "--" + arg + argatts = {} + argatts["help"] = "(default: %(default)s)" + if "action" in argdata: + argatts["action"] = argdata["action"] + if "choices" in argdata: + argatts["choices"] = argdata["choices"] + argatts["const"] = argdata["choices"][0] + argatts["default"] = argdata["choices"][0] + argatts["nargs"] = "?" + if arg in settings: + default = settings[arg] + if "type" in argdata and argdata["type"] == "bool": + default = settings[arg] != 0 + argatts["default"] = defval(default) + arghelp = fish.translate("cli","help",arg) + if "help" in argdata and argdata["help"] == "suppress": + argatts["help"] = argparse.SUPPRESS + elif not isinstance(arghelp,str): + argatts["help"] = '\n'.join(arghelp).replace("\\'","'") + else: + argatts["help"] = arghelp + " " + argatts["help"] + parser.add_argument(argname,**argatts) - parser.add_argument('--seed', default=defval(int(settings["seed"]) if settings["seed"] != "" and settings["seed"] is not None else None), help="\n".join(fish.translate("cli", "help", "seed")), type=int) - parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else 1), help="\n".join(fish.translate("cli", "help", "count")), type=int) + parser.add_argument('--seed', default=defval(int(settings["seed"]) if settings["seed"] != "" and settings["seed"] is not None else None), help="\n".join(fish.translate("cli","help","seed")), type=int) + parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else 1), help="\n".join(fish.translate("cli","help","count")), type=int) parser.add_argument('--customitemarray', default={}, help=argparse.SUPPRESS) # included for backwards compatibility @@ -78,7 +73,6 @@ def parse_cli(argv, no_defaults=False): parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255)) parser.add_argument('--securerandom', default=defval(settings["securerandom"]), action='store_true') parser.add_argument('--teams', default=defval(1), type=lambda value: max(int(value), 1)) - parser.add_argument('--settingsfile', dest="filename", help="input json file of settings", type=str) if multiargs.multi: for player in range(1, multiargs.multi + 1): @@ -92,11 +86,13 @@ def parse_cli(argv, no_defaults=False): if multiargs.multi: defaults = copy.deepcopy(ret) for player in range(1, multiargs.multi + 1): - playerargs = parse_cli(shlex.split(getattr(ret, f"p{player}")), True) + playerargs = parse_cli(shlex.split(getattr(ret,f"p{player}")), True) for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', + 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', + 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', @@ -110,15 +106,6 @@ def parse_cli(argv, no_defaults=False): return ret -def apply_settings_file(settings, settings_path): - if os.path.exists(settings_path): - with open(settings_path) as json_file: - data = json.load(json_file) - for k, v in data.items(): - settings[k] = v - return settings - - def parse_settings(): # set default settings settings = { @@ -162,6 +149,15 @@ def parse_settings(): "dungeon_counters": "default", "mixed_travel": "prevent", "standardize_palettes": "standardize", + + "triforce_pool": 0, + "triforce_goal": 0, + "triforce_pool_min": 0, + "triforce_pool_max": 0, + "triforce_goal_min": 0, + "triforce_goal_max": 0, + "triforce_min_difference": 0, + "code": "", "multi": 1, @@ -175,7 +171,7 @@ def parse_settings(): "quickswap": False, "heartcolor": "red", "heartbeep": "normal", - "sprite": os.path.join(".", "data", "sprites", "official", "001.link.1.zspr"), + "sprite": os.path.join(".","data","sprites","official","001.link.1.zspr"), "fastmenu": "normal", "ow_palettes": "default", "uw_palettes": "default", @@ -281,15 +277,17 @@ def parse_settings(): # read saved settings file if it exists and set these settings_path = os.path.join(".", "resources", "user", "settings.json") - settings = apply_settings_file(settings, settings_path) + if os.path.exists(settings_path): + with(open(settings_path)) as json_file: + data = json.load(json_file) + for k, v in data.items(): + settings[k] = v return settings # Priority fallback is: # 1: CLI -# 2: Settings file(s) +# 2: Settings file # 3: Canned defaults - - def get_args_priority(settings_args, gui_args, cli_args): args = {} args["settings"] = parse_settings() if settings_args is None else settings_args @@ -316,7 +314,7 @@ def get_args_priority(settings_args, gui_args, cli_args): for k in vars(args["cli"]): load_doesnt_have_key = k not in args["load"] cli_val = cli[k] - if isinstance(cli_val, dict) and 1 in cli_val: + if isinstance(cli_val,dict) and 1 in cli_val: cli_val = cli_val[1] different_val = (k in args["load"] and k in cli) and (str(args["load"][k]) != str(cli_val)) cli_has_empty_dict = k in cli and isinstance(cli_val, dict) and len(cli_val) == 0 @@ -325,9 +323,9 @@ def get_args_priority(settings_args, gui_args, cli_args): args["load"][k] = cli_val newArgs = {} - for key in ["settings", "gui", "cli", "load"]: + for key in [ "settings", "gui", "cli", "load" ]: if args[key]: - if isinstance(args[key], dict): + if isinstance(args[key],dict): newArgs[key] = argparse.Namespace(**args[key]) else: newArgs[key] = args[key] diff --git a/ItemList.py b/ItemList.py index d1e44918..495f3897 100644 --- a/ItemList.py +++ b/ItemList.py @@ -37,7 +37,7 @@ Difficulty = namedtuple('Difficulty', ['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield', 'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'progressivesword', 'basicsword', 'basicbow', 'timedohko', 'timedother', - 'triforcehunt', 'triforce_pieces_required', 'retro', + 'retro', 'extras', 'progressive_sword_limit', 'progressive_shield_limit', 'progressive_armor_limit', 'progressive_bottle_limit', 'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit']) @@ -60,8 +60,6 @@ difficulties = { basicbow = ['Bow', 'Silver Arrows'], timedohko = ['Green Clock'] * 25, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, - triforcehunt = ['Triforce Piece'] * 30, - triforce_pieces_required = 20, retro = ['Small Key (Universal)'] * 18 + ['Rupees (20)'] * 10, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 4, @@ -87,8 +85,6 @@ difficulties = { basicbow = ['Bow'] * 2, timedohko = ['Green Clock'] * 25, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, - triforcehunt = ['Triforce Piece'] * 30, - triforce_pieces_required = 20, retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 3, @@ -114,8 +110,6 @@ difficulties = { basicbow = ['Bow'] * 2, timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, - triforcehunt = ['Triforce Piece'] * 30, - triforce_pieces_required = 20, retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 2, @@ -262,7 +256,7 @@ def generate_itempool(world, player): (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.customitemarray) world.rupoor_cost = min(world.customitemarray[player]["rupoorcost"], 9999) else: - (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.doorShuffle[player]) + (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.treasure_hunt_total[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.doorShuffle[player]) if player in world.pool_adjustment.keys(): amt = world.pool_adjustment[player] @@ -320,10 +314,10 @@ def generate_itempool(world, player): if clock_mode is not None: world.clock_mode = clock_mode - if treasure_hunt_count is not None: - world.treasure_hunt_count[player] = treasure_hunt_count - if treasure_hunt_icon is not None: - world.treasure_hunt_icon[player] = treasure_hunt_icon + if world.goal[player] == 'triforcehunt': + world.treasure_hunt_icon[player] = 'Triforce Piece' + if world.custom: + world.treasure_hunt_count[player] = treasure_hunt_count world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player and ((item.smallkey and world.keyshuffle[player]) @@ -640,13 +634,12 @@ shop_transfer = {'Red Potion': 'Rupees (100)', 'Bee': 'Rupees (5)', 'Blue Potion } -def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, door_shuffle): +def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, door_shuffle): pool = [] placed_items = {} precollected_items = [] clock_mode = None - treasure_hunt_count = None - treasure_hunt_icon = None + triforcepool = ['Triforce Piece'] * treasure_hunt_total pool.extend(alwaysitems) @@ -740,10 +733,8 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r extraitems -= len(diff.timedohko) clock_mode = 'countdown-ohko' if goal == 'triforcehunt': - pool.extend(diff.triforcehunt) - extraitems -= len(diff.triforcehunt) - treasure_hunt_count = diff.triforce_pieces_required - treasure_hunt_icon = 'Triforce Piece' + pool.extend(triforcepool) + extraitems -= len(triforcepool) for extra in diff.extras: if extraitems > 0: @@ -771,7 +762,7 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r pool.extend(['Small Key (Universal)']) else: pool.extend(['Small Key (Universal)']) - return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) + return (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, customitemarray): if isinstance(customitemarray,dict) and 1 in customitemarray: diff --git a/Main.py b/Main.py index 0ed2b7eb..c6795d15 100644 --- a/Main.py +++ b/Main.py @@ -22,11 +22,11 @@ from RoomData import create_rooms from Rules import set_rules from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items -from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations +from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from Utils import output_path, parse_player_names -__version__ = '0.3.1.5-u' +__version__ = '0.3.1.4-u' class EnemizerError(RuntimeError): @@ -86,6 +86,8 @@ def main(args, seed=None, fish=None): world.keydropshuffle = args.keydropshuffle.copy() world.mixed_travel = args.mixed_travel.copy() world.standardize_palettes = args.standardize_palettes.copy() + world.treasure_hunt_count = args.triforce_goal.copy() + world.treasure_hunt_total = args.triforce_pool.copy() world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} @@ -165,9 +167,6 @@ def main(args, seed=None, fish=None): sell_potions(world, player) if world.retro[player]: sell_keys(world, player) - else: - lock_shop_locations(world, player) - logger.info(world.fish.translate("cli","cli","placing.dungeon.prizes")) diff --git a/Mystery.py b/Mystery.py index 77ab630a..4be8870a 100644 --- a/Mystery.py +++ b/Mystery.py @@ -171,9 +171,11 @@ def roll_settings(weights): ret.openpyramid = goal == 'fast_ganon' if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False ret.crystals_gt = get_choice('tower_open') - ret.crystals_ganon = get_choice('ganon_open') - + + if ret.goal == 'triforcehunt': + ret.triforce_goal = random.randint(int(get_choice('triforce_goal_min')),int(get_choice('triforce_goal_max'))) + ret.triforce_pool = random.randint(max(int(get_choice('triforce_pool_min')),ret.triforce_goal + int(get_choice('triforce_min_difference'))),int(get_choice('triforce_pool_max'))) ret.mode = get_choice('world_state') if ret.mode == 'retro': ret.mode = 'open' @@ -213,7 +215,8 @@ def roll_settings(weights): ret.shufflepots = get_choice('pot_shuffle') == 'on' ret.beemizer = int(get_choice('beemizer')) if 'beemizer' in weights else 0 - + + inventoryweights = weights.get('startinventory', {}) startitems = [] for item in inventoryweights.keys(): diff --git a/Rom.py b/Rom.py index 7458ee5b..ac031a9b 100644 --- a/Rom.py +++ b/Rom.py @@ -16,7 +16,7 @@ from BaseClasses import CollectionState, ShopType, Region, Location, Door, DoorT from DoorShuffle import compass_data, DROptions, boss_indicator from Dungeons import dungeon_music_addresses from KeyDoorShuffle import count_locations_exclude_logic -from Regions import location_table, shop_to_location_table +from Regions import location_table from RoomData import DoorKind from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable from Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts @@ -1568,10 +1568,7 @@ def write_custom_shops(rom, world, player): break if world.shopsanity[player] or shop.type == ShopType.TakeAny: rom.write_byte(0x186560 + shop.sram_address + index, 1) - loc_item = world.get_location(shop_to_location_table[shop.region.name][index], player).item - if not loc_item: - loc_item = ItemFactory(item['item'], player) - item_id = loc_item.code + item_id = ItemFactory(item['item'], player).code price = int16_as_bytes(item['price']) replace = ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF replace_price = int16_as_bytes(item['replacement_price']) From 1e63d3957df3224e98fc56cc41579b760cbb75de Mon Sep 17 00:00:00 2001 From: Fouton Date: Fri, 12 Mar 2021 16:41:58 -0500 Subject: [PATCH 02/17] TFH Futures --- resources/app/cli/args.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index c112651c..a6d809d6 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -218,6 +218,17 @@ "usestartinventory": { "type": "bool" }, + "triforce_pool": {}, + "triforce_goal": {}, + "triforce_pool_min": {}, + "triforce_pool_max": {}, + "triforce_goal_min": {}, + "triforce_goal_max": {}, + "triforce_min_difference": {}, + "hints": { + "action": "store_false", + "type": "bool" + }, "custom": { "type": "bool", "help": "suppress" From 2a1eb07d73918ec35687568fe87d1cbad7a9b50e Mon Sep 17 00:00:00 2001 From: Fouton Date: Fri, 12 Mar 2021 16:51:16 -0500 Subject: [PATCH 03/17] Add files via upload --- BaseClasses.py | 5 +++ CLI.py | 95 ++++++++++++++++++++++++++++---------------------- 2 files changed, 59 insertions(+), 41 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 45017752..2fc04482 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1378,6 +1378,11 @@ class Door(object): else: self.passage = False + def kind(self, world): + if self.roomIndex != -1 and self.doorListPos != -1: + return world.get_room(self.roomIndex, self.player).kind(self) + return None + def __eq__(self, other): return isinstance(other, self.__class__) and self.name == other.name diff --git a/CLI.py b/CLI.py index 0ceec234..6f9bd097 100644 --- a/CLI.py +++ b/CLI.py @@ -6,7 +6,6 @@ import textwrap import shlex import sys -import source.classes.constants as CONST from source.classes.BabelFish import BabelFish from Utils import update_deprecated_args @@ -28,44 +27,49 @@ def parse_cli(argv, no_defaults=False): fish = BabelFish(lang=lang) # we need to know how many players we have first + # also if we're loading our own settings file, we should do that now parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('--settingsfile', help="input json file of settings", type=str) parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255)) multiargs, _ = parser.parse_known_args(argv) + if multiargs.settingsfile: + settings = apply_settings_file(settings, multiargs.settingsfile) + parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) # get args args = [] - with open(os.path.join("resources","app","cli","args.json")) as argsFile: - args = json.load(argsFile) - for arg in args: - argdata = args[arg] - argname = "--" + arg - argatts = {} - argatts["help"] = "(default: %(default)s)" - if "action" in argdata: - argatts["action"] = argdata["action"] - if "choices" in argdata: - argatts["choices"] = argdata["choices"] - argatts["const"] = argdata["choices"][0] - argatts["default"] = argdata["choices"][0] - argatts["nargs"] = "?" - if arg in settings: - default = settings[arg] - if "type" in argdata and argdata["type"] == "bool": - default = settings[arg] != 0 - argatts["default"] = defval(default) - arghelp = fish.translate("cli","help",arg) - if "help" in argdata and argdata["help"] == "suppress": - argatts["help"] = argparse.SUPPRESS - elif not isinstance(arghelp,str): - argatts["help"] = '\n'.join(arghelp).replace("\\'","'") - else: - argatts["help"] = arghelp + " " + argatts["help"] - parser.add_argument(argname,**argatts) + with open(os.path.join("resources", "app", "cli", "args.json")) as argsFile: + args = json.load(argsFile) + for arg in args: + argdata = args[arg] + argname = "--" + arg + argatts = {} + argatts["help"] = "(default: %(default)s)" + if "action" in argdata: + argatts["action"] = argdata["action"] + if "choices" in argdata: + argatts["choices"] = argdata["choices"] + argatts["const"] = argdata["choices"][0] + argatts["default"] = argdata["choices"][0] + argatts["nargs"] = "?" + if arg in settings: + default = settings[arg] + if "type" in argdata and argdata["type"] == "bool": + default = settings[arg] != 0 + argatts["default"] = defval(default) + arghelp = fish.translate("cli", "help", arg) + if "help" in argdata and argdata["help"] == "suppress": + argatts["help"] = argparse.SUPPRESS + elif not isinstance(arghelp, str): + argatts["help"] = '\n'.join(arghelp).replace("\\'", "'") + else: + argatts["help"] = arghelp + " " + argatts["help"] + parser.add_argument(argname, **argatts) - parser.add_argument('--seed', default=defval(int(settings["seed"]) if settings["seed"] != "" and settings["seed"] is not None else None), help="\n".join(fish.translate("cli","help","seed")), type=int) - parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else 1), help="\n".join(fish.translate("cli","help","count")), type=int) + parser.add_argument('--seed', default=defval(int(settings["seed"]) if settings["seed"] != "" and settings["seed"] is not None else None), help="\n".join(fish.translate("cli", "help", "seed")), type=int) + parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else 1), help="\n".join(fish.translate("cli", "help", "count")), type=int) parser.add_argument('--customitemarray', default={}, help=argparse.SUPPRESS) # included for backwards compatibility @@ -73,6 +77,7 @@ def parse_cli(argv, no_defaults=False): parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255)) parser.add_argument('--securerandom', default=defval(settings["securerandom"]), action='store_true') parser.add_argument('--teams', default=defval(1), type=lambda value: max(int(value), 1)) + parser.add_argument('--settingsfile', dest="filename", help="input json file of settings", type=str) if multiargs.multi: for player in range(1, multiargs.multi + 1): @@ -86,7 +91,7 @@ def parse_cli(argv, no_defaults=False): if multiargs.multi: defaults = copy.deepcopy(ret) for player in range(1, multiargs.multi + 1): - playerargs = parse_cli(shlex.split(getattr(ret,f"p{player}")), True) + playerargs = parse_cli(shlex.split(getattr(ret, f"p{player}")), True) for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', @@ -104,6 +109,16 @@ def parse_cli(argv, no_defaults=False): getattr(ret, name)[player] = value return ret + + +def apply_settings_file(settings, settings_path): + if os.path.exists(settings_path): + with open(settings_path) as json_file: + data = json.load(json_file) + for k, v in data.items(): + settings[k] = v + return settings + def parse_settings(): @@ -171,7 +186,7 @@ def parse_settings(): "quickswap": False, "heartcolor": "red", "heartbeep": "normal", - "sprite": os.path.join(".","data","sprites","official","001.link.1.zspr"), + "sprite": os.path.join(".", "data", "sprites", "official", "001.link.1.zspr"), "fastmenu": "normal", "ow_palettes": "default", "uw_palettes": "default", @@ -277,17 +292,15 @@ def parse_settings(): # read saved settings file if it exists and set these settings_path = os.path.join(".", "resources", "user", "settings.json") - if os.path.exists(settings_path): - with(open(settings_path)) as json_file: - data = json.load(json_file) - for k, v in data.items(): - settings[k] = v + settings = apply_settings_file(settings, settings_path) return settings # Priority fallback is: # 1: CLI -# 2: Settings file +# 2: Settings file(s) # 3: Canned defaults + + def get_args_priority(settings_args, gui_args, cli_args): args = {} args["settings"] = parse_settings() if settings_args is None else settings_args @@ -314,7 +327,7 @@ def get_args_priority(settings_args, gui_args, cli_args): for k in vars(args["cli"]): load_doesnt_have_key = k not in args["load"] cli_val = cli[k] - if isinstance(cli_val,dict) and 1 in cli_val: + if isinstance(cli_val, dict) and 1 in cli_val: cli_val = cli_val[1] different_val = (k in args["load"] and k in cli) and (str(args["load"][k]) != str(cli_val)) cli_has_empty_dict = k in cli and isinstance(cli_val, dict) and len(cli_val) == 0 @@ -323,9 +336,9 @@ def get_args_priority(settings_args, gui_args, cli_args): args["load"][k] = cli_val newArgs = {} - for key in [ "settings", "gui", "cli", "load" ]: + for key in ["settings", "gui", "cli", "load"]: if args[key]: - if isinstance(args[key],dict): + if isinstance(args[key], dict): newArgs[key] = argparse.Namespace(**args[key]) else: newArgs[key] = args[key] From 086528858135fea54d8daa3aabd3b6f65cb8170d Mon Sep 17 00:00:00 2001 From: Fouton Date: Fri, 12 Mar 2021 16:53:08 -0500 Subject: [PATCH 04/17] Add files via upload --- CLI.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CLI.py b/CLI.py index 6f9bd097..a89c4a5a 100644 --- a/CLI.py +++ b/CLI.py @@ -16,6 +16,7 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): def _get_help_string(self, action): return textwrap.dedent(action.help) + def parse_cli(argv, no_defaults=False): def defval(value): return value if not no_defaults else None @@ -109,8 +110,8 @@ def parse_cli(argv, no_defaults=False): getattr(ret, name)[player] = value return ret - - + + def apply_settings_file(settings, settings_path): if os.path.exists(settings_path): with open(settings_path) as json_file: @@ -120,7 +121,6 @@ def apply_settings_file(settings, settings_path): return settings - def parse_settings(): # set default settings settings = { From 2e07fc2dd01e5a216f8878ca3107d6035a76ca70 Mon Sep 17 00:00:00 2001 From: Fouton Date: Fri, 12 Mar 2021 16:57:42 -0500 Subject: [PATCH 05/17] Add files via upload --- Main.py | 5 ++++- Mystery.py | 4 ++-- Rom.py | 7 +++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Main.py b/Main.py index c6795d15..4b6eb927 100644 --- a/Main.py +++ b/Main.py @@ -26,7 +26,7 @@ from Fill import sell_potions, sell_keys, balance_multiworld_progression, balanc from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from Utils import output_path, parse_player_names -__version__ = '0.3.1.4-u' +__version__ = '0.3.1.5-u' class EnemizerError(RuntimeError): @@ -167,6 +167,9 @@ def main(args, seed=None, fish=None): sell_potions(world, player) if world.retro[player]: sell_keys(world, player) + else: + lock_shop_locations(world, player) + logger.info(world.fish.translate("cli","cli","placing.dungeon.prizes")) diff --git a/Mystery.py b/Mystery.py index 4be8870a..9ddb0827 100644 --- a/Mystery.py +++ b/Mystery.py @@ -171,6 +171,7 @@ def roll_settings(weights): ret.openpyramid = goal == 'fast_ganon' if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False ret.crystals_gt = get_choice('tower_open') + ret.crystals_ganon = get_choice('ganon_open') if ret.goal == 'triforcehunt': @@ -215,8 +216,7 @@ def roll_settings(weights): ret.shufflepots = get_choice('pot_shuffle') == 'on' ret.beemizer = int(get_choice('beemizer')) if 'beemizer' in weights else 0 - - + inventoryweights = weights.get('startinventory', {}) startitems = [] for item in inventoryweights.keys(): diff --git a/Rom.py b/Rom.py index ac031a9b..7458ee5b 100644 --- a/Rom.py +++ b/Rom.py @@ -16,7 +16,7 @@ from BaseClasses import CollectionState, ShopType, Region, Location, Door, DoorT from DoorShuffle import compass_data, DROptions, boss_indicator from Dungeons import dungeon_music_addresses from KeyDoorShuffle import count_locations_exclude_logic -from Regions import location_table +from Regions import location_table, shop_to_location_table from RoomData import DoorKind from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable from Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts @@ -1568,7 +1568,10 @@ def write_custom_shops(rom, world, player): break if world.shopsanity[player] or shop.type == ShopType.TakeAny: rom.write_byte(0x186560 + shop.sram_address + index, 1) - item_id = ItemFactory(item['item'], player).code + loc_item = world.get_location(shop_to_location_table[shop.region.name][index], player).item + if not loc_item: + loc_item = ItemFactory(item['item'], player) + item_id = loc_item.code price = int16_as_bytes(item['price']) replace = ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF replace_price = int16_as_bytes(item['replacement_price']) From 08a5e2f27f3bb004918a51c3235ba0ca03a4c493 Mon Sep 17 00:00:00 2001 From: Fouton Date: Fri, 12 Mar 2021 21:22:35 -0500 Subject: [PATCH 06/17] Add files via upload --- BaseClasses.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 2fc04482..bd1dcd4b 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -127,8 +127,8 @@ class World(object): set_player_attr('crystals_gt_orig', {}) set_player_attr('open_pyramid', False) set_player_attr('treasure_hunt_icon', 'Triforce Piece') - set_player_attr('treasure_hunt_count', 0) - set_player_attr('treasure_hunt_total', 0) + set_player_attr('treasure_hunt_count', 20) + set_player_attr('treasure_hunt_total', 30) set_player_attr('potshuffle', False) set_player_attr('pot_contents', None) @@ -2195,8 +2195,8 @@ counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3} access_mode = {"items": 0, "locations": 1, "none": 2} # byte 6: BSMC BBEE (big, small, maps, compass, bosses, enemies) -boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3} -enemy_mode = {"none": 0, "shuffled": 1, "random": 2} +boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3, "chaos": 3} +enemy_mode = {"none": 0, "shuffled": 1, "random": 2, "chaos": 2} # byte 7: HHHD DP?? (enemy_health, enemy_dmg, potshuffle, ?) e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4} From d62b11742b72149269d899ab8690693e52414b86 Mon Sep 17 00:00:00 2001 From: Fouton Date: Fri, 12 Mar 2021 21:45:27 -0500 Subject: [PATCH 07/17] Add files via upload --- ItemList.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ItemList.py b/ItemList.py index 495f3897..75f41501 100644 --- a/ItemList.py +++ b/ItemList.py @@ -315,6 +315,8 @@ def generate_itempool(world, player): world.clock_mode = clock_mode if world.goal[player] == 'triforcehunt': + if world.treasure_hunt_count[player] == 0: + world.treasure_hunt_count[player] = 20 world.treasure_hunt_icon[player] = 'Triforce Piece' if world.custom: world.treasure_hunt_count[player] = treasure_hunt_count @@ -639,6 +641,9 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, placed_items = {} precollected_items = [] clock_mode = None + if goal == 'triforcehunt': + if treasure_hunt_total == 0: + treasure_hunt_total = 30 triforcepool = ['Triforce Piece'] * treasure_hunt_total pool.extend(alwaysitems) From 05413011a90ab45a3bbe72526a2a87fc61269438 Mon Sep 17 00:00:00 2001 From: Fouton Date: Fri, 12 Mar 2021 21:46:54 -0500 Subject: [PATCH 08/17] Add files via upload --- Main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Main.py b/Main.py index 4b6eb927..ede87e62 100644 --- a/Main.py +++ b/Main.py @@ -22,7 +22,7 @@ from RoomData import create_rooms from Rules import set_rules from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items -from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression +from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from Utils import output_path, parse_player_names From 6526d1b87724ccb7d839d709735be97020002e52 Mon Sep 17 00:00:00 2001 From: Fouton Date: Fri, 12 Mar 2021 21:48:51 -0500 Subject: [PATCH 09/17] Add files via upload --- BaseClasses.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index bd1dcd4b..2fc04482 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -127,8 +127,8 @@ class World(object): set_player_attr('crystals_gt_orig', {}) set_player_attr('open_pyramid', False) set_player_attr('treasure_hunt_icon', 'Triforce Piece') - set_player_attr('treasure_hunt_count', 20) - set_player_attr('treasure_hunt_total', 30) + set_player_attr('treasure_hunt_count', 0) + set_player_attr('treasure_hunt_total', 0) set_player_attr('potshuffle', False) set_player_attr('pot_contents', None) @@ -2195,8 +2195,8 @@ counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3} access_mode = {"items": 0, "locations": 1, "none": 2} # byte 6: BSMC BBEE (big, small, maps, compass, bosses, enemies) -boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3, "chaos": 3} -enemy_mode = {"none": 0, "shuffled": 1, "random": 2, "chaos": 2} +boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3} +enemy_mode = {"none": 0, "shuffled": 1, "random": 2} # byte 7: HHHD DP?? (enemy_health, enemy_dmg, potshuffle, ?) e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4} From 1aa770e33a3cb1369ffe84389dd2abab20a4bf3b Mon Sep 17 00:00:00 2001 From: Fouton Date: Fri, 12 Mar 2021 21:58:19 -0500 Subject: [PATCH 10/17] Add files via upload --- ItemList.py | 5 ++--- Rom.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ItemList.py b/ItemList.py index 75f41501..06e9ba64 100644 --- a/ItemList.py +++ b/ItemList.py @@ -190,9 +190,8 @@ def generate_itempool(world, player): if world.goal[player] in ['triforcehunt']: region = world.get_region('Light World',player) - loc = Location(player, "Murahdahla", parent=region) - loc.access_rule = lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= state.world.treasure_hunt_count[player] + loc.access_rule = lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player]) region.locations.append(loc) world.dynamic_locations.append(loc) @@ -644,7 +643,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, if goal == 'triforcehunt': if treasure_hunt_total == 0: treasure_hunt_total = 30 - triforcepool = ['Triforce Piece'] * treasure_hunt_total + triforcepool = ['Triforce Piece'] * int(treasure_hunt_total) pool.extend(alwaysitems) diff --git a/Rom.py b/Rom.py index 7458ee5b..2d592ac6 100644 --- a/Rom.py +++ b/Rom.py @@ -1115,7 +1115,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # set up goals for treasure hunt rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28]) - rom.write_byte(0x180167, world.treasure_hunt_count[player] % 256) + rom.write_byte(0x180167, int(world.treasure_hunt_count[player]) % 256) rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled) rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed @@ -2062,7 +2062,7 @@ def write_strings(rom, world, player, team): tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.' tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' tt['sign_ganon'] = 'Go find the Triforce pieces... Ganon is invincible!' - tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % world.treasure_hunt_count[player] + tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player]) elif world.goal[player] in ['pedestal']: tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Your goal is at the pedestal.' tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' From c47090ce6104ff666653b06c9d2871b86470c45c Mon Sep 17 00:00:00 2001 From: Fouton Date: Fri, 12 Mar 2021 22:15:27 -0500 Subject: [PATCH 11/17] Add files via upload --- BaseClasses.py | 5 +++++ ItemList.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/BaseClasses.py b/BaseClasses.py index 2fc04482..50c97c71 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1971,6 +1971,8 @@ class Spoiler(object): 'experimental': self.world.experimental, 'keydropshuffle': self.world.keydropshuffle, 'shopsanity': self.world.shopsanity, + 'triforcegoal': self.world.treasure_hunt_count, + 'triforcepool': self.world.treasure_hunt_total, 'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)} } @@ -2014,6 +2016,9 @@ class Spoiler(object): outfile.write('Retro: %s\n' % ('Yes' if self.metadata['retro'][player] else 'No')) outfile.write('Swords: %s\n' % self.metadata['weapons'][player]) outfile.write('Goal: %s\n' % self.metadata['goal'][player]) + if self.metadata['goal'][player] == 'triforcehunt': + outfile.write('Triforce Pieces Required: %s\n' % self.metadata['triforcegoal'][player]) + outfile.write('Triforce Pieces Total: %s\n' % self.metadata['triforcepool'][player]) outfile.write('Difficulty: %s\n' % self.metadata['item_pool'][player]) outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player]) outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player]) diff --git a/ItemList.py b/ItemList.py index 06e9ba64..500c1af8 100644 --- a/ItemList.py +++ b/ItemList.py @@ -316,6 +316,8 @@ def generate_itempool(world, player): if world.goal[player] == 'triforcehunt': if world.treasure_hunt_count[player] == 0: world.treasure_hunt_count[player] = 20 + if world.treasure_hunt_total[player] == 0: + world.treasure_hunt_total[player] = 30 world.treasure_hunt_icon[player] = 'Triforce Piece' if world.custom: world.treasure_hunt_count[player] = treasure_hunt_count From 6bf3479e08d7e48d16514d2755dc4dcd6a5821bc Mon Sep 17 00:00:00 2001 From: StructuralMike <66819228+StructuralMike@users.noreply.github.com> Date: Tue, 16 Mar 2021 14:15:56 +0100 Subject: [PATCH 12/17] TFH playthrough fix copy_world overwrites Murahdahla's logic and either needs to be reset through set_rules to work properly. --- ItemList.py | 1 - Rules.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ItemList.py b/ItemList.py index ddb629ab..dacd47b9 100644 --- a/ItemList.py +++ b/ItemList.py @@ -196,7 +196,6 @@ def generate_itempool(world, player): region = world.get_region('Light World',player) loc = Location(player, "Murahdahla", parent=region) - loc.access_rule = lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= state.world.treasure_hunt_count[player] region.locations.append(loc) world.dynamic_locations.append(loc) diff --git a/Rules.py b/Rules.py index ceeeecbf..d61f1a14 100644 --- a/Rules.py +++ b/Rules.py @@ -41,6 +41,8 @@ def set_rules(world, player): elif world.goal[player] == 'ganon': # require aga2 to beat ganon add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player)) + elif world.goal[player] == 'triforcehunt': + add_rule(world.get_location('Murahdahla', player), lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= state.world.treasure_hunt_count[player]) if world.mode[player] != 'inverted': set_big_bomb_rules(world, player) From 40119b3a5fc6992c9de76f8e830d32179ca19907 Mon Sep 17 00:00:00 2001 From: StructuralMike <66819228+StructuralMike@users.noreply.github.com> Date: Tue, 16 Mar 2021 17:34:08 +0100 Subject: [PATCH 13/17] Retro-Standard can't have bow on uncle I don't know if the best solution is to exclude the bow from the options, or to have uncle give bow + arrows. I think the former is better as buying your arrow is part of retro. --- ItemList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ItemList.py b/ItemList.py index dacd47b9..a1edaded 100644 --- a/ItemList.py +++ b/ItemList.py @@ -283,7 +283,7 @@ def generate_itempool(world, player): if not found_sword and world.swords[player] != 'swordless': found_sword = True possible_weapons.append(item) - if item in ['Progressive Bow', 'Bow'] and not found_bow: + if item in ['Progressive Bow', 'Bow'] and not found_bow and not world.retro[player]: found_bow = True possible_weapons.append(item) if item in ['Hammer', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']: From 3c81e377e5afcef07b8d08880e4087fe129d1c56 Mon Sep 17 00:00:00 2001 From: "randall.rupper" Date: Tue, 16 Mar 2021 14:23:52 -0600 Subject: [PATCH 14/17] Fix for intensity 3 inverted lobbies if lobbies are chosen to be vanilla Flooded pots not actually able to be flooded in some potshuffles --- BaseClasses.py | 10 ++++++++-- Main.py | 2 +- RELEASENOTES.md | 6 ++++++ Rom.py | 2 ++ mystery_example.yml | 4 ++++ 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index be77fd71..b5322b5a 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -586,7 +586,8 @@ class CollectionState(object): for event in reachable_events: if event.name in flooded_keys.keys(): flood_location = self.world.get_location(flooded_keys[event.name], event.player) - if flood_location.item and flood_location not in self.locations_checked: + if (flood_location.item and flood_location not in self.locations_checked + and self.location_can_be_flooded(flood_location)): adjusted_checks.remove(event) if len(adjusted_checks) < len(reachable_events): return adjusted_checks @@ -597,9 +598,14 @@ class CollectionState(object): flood_location = world.get_location(flooded_keys[location.name], location.player) item = flood_location.item item_is_important = False if not item else item.advancement or item.bigkey or item.smallkey - return flood_location in self.locations_checked or not item_is_important + return (flood_location in self.locations_checked or not item_is_important + or not self.location_can_be_flooded(flood_location)) return True + @staticmethod + def location_can_be_flooded(location): + return location.parent_region.name in ['Swamp Trench 1 Alcove', 'Swamp Trench 2 Alcove'] + def has(self, item, player, count=1): if count == 1: return (item, player) in self.prog_items diff --git a/Main.py b/Main.py index 0ed2b7eb..4fd1cef2 100644 --- a/Main.py +++ b/Main.py @@ -26,7 +26,7 @@ from Fill import sell_potions, sell_keys, balance_multiworld_progression, balanc from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from Utils import output_path, parse_player_names -__version__ = '0.3.1.5-u' +__version__ = '0.3.1.6-u' class EnemizerError(RuntimeError): diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 377db0fe..e8871542 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -128,6 +128,12 @@ New item counter modified to show total # Bug Fixes and Notes. +* 0.3.1.6-u + * Fix for inverted. AT or GT vanilla lobby in intensity 3 should not softlock on exit in non-ER modes. + * Backward compatibility for "chaos" enemizer flags. (Thanks krebel) + * Fix for potshuffle and swamp trench generation errors (Thanks StructuralMike) + * Fix for TFH playthrough (Thanks StructuralMike) + * Fix for Standard+Retro (Thanks StructuralMike) * 0.3.1.5-u * Ganon hints fixed for shops * Added support for a settings file so SahasrahBot and the main website can use it easier. (Thanks Synack) diff --git a/Rom.py b/Rom.py index 7458ee5b..b03cbcae 100644 --- a/Rom.py +++ b/Rom.py @@ -729,6 +729,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): aga_portal = world.get_portal('Agahnims Tower', player) gt_portal = world.get_portal('Ganons Tower', player) aga_portal.exit_offset, gt_portal.exit_offset = gt_portal.exit_offset, aga_portal.exit_offset + aga_portal.default = False + gt_portal.default = False for portal in world.dungeon_portals[player]: if not portal.default: diff --git a/mystery_example.yml b/mystery_example.yml index cb075090..40416ff3 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -44,6 +44,10 @@ mc: 3 mcs: 2 full: 5 + dungeon_counters: + on: 5 + off: 0 + default: 5 experimental: on: 1 off: 0 From 27ed4ecd0462e4341011b7add503a83f4e627761 Mon Sep 17 00:00:00 2001 From: "randall.rupper" Date: Wed, 17 Mar 2021 14:37:39 -0600 Subject: [PATCH 15/17] Small Shopsanity price adjustments Fixed a subtle error with shop shields and progressive shields - should result in more successful generation --- BaseClasses.py | 7 +++++-- ItemList.py | 16 +------------- Items.py | 56 ++++++++++++++++++++++++------------------------- RELEASENOTES.md | 25 +++++++++++++++++++--- 4 files changed, 56 insertions(+), 48 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 125bd09e..7e18ba60 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -784,14 +784,17 @@ class CollectionState(object): elif 'Shield' in item.name: if self.has('Mirror Shield', item.player): pass - elif self.has('Red Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 3: + elif self.has('Shield Level', item.player, 2) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 3: self.prog_items['Mirror Shield', item.player] += 1 + self.prog_items['Shield Level', item.player] += 1 changed = True - elif self.has('Blue Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 2: + elif self.has('Shield Level', item.player, 1) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 2: self.prog_items['Red Shield', item.player] += 1 + self.prog_items['Shield Level', item.player] += 1 changed = True elif self.world.difficulty_requirements[item.player].progressive_shield_limit >= 1: self.prog_items['Blue Shield', item.player] += 1 + self.prog_items['Shield Level', item.player] += 1 changed = True elif 'Bow' in item.name: if self.has('Silver Arrows', item.player): diff --git a/ItemList.py b/ItemList.py index 5ce4ed64..ccc10b28 100644 --- a/ItemList.py +++ b/ItemList.py @@ -574,7 +574,6 @@ def customize_shops(world, player): loc.item = upgrade upgrade.location = loc change_shop_items_to_rupees(world, player, shops_to_customize) - todays_discounts(world, player) def randomize_price(price): @@ -607,19 +606,6 @@ def change_shop_items_to_rupees(world, player, shops): shop.add_inventory(slot, new_item.name, randomize_price(new_item.price), 1, player=new_item.player) -def todays_discounts(world, player): - locs = [] - for shop, locations in shop_to_location_table.items(): - for slot, loc in enumerate(locations): - locs.append((world.get_location(loc, player), shop, slot)) - discount_number = random.randint(4, 7) - chosen_locations = random.choices(locs, k=discount_number) - for location, shop_name, slot in chosen_locations: - shop = world.get_region(shop_name, player).shop - orig = location.item.price - shop.inventory[slot]['price'] = randomize_price(orig // 5) - - repeatable_shop_items = ['Single Arrow', 'Arrows (10)', 'Bombs (3)', 'Bombs (10)', 'Red Potion', 'Small Heart', 'Blue Shield', 'Red Shield', 'Bee', 'Small Key (Universal)', 'Blue Potion', 'Green Potion'] @@ -629,7 +615,7 @@ cap_replacements = ['Single Arrow', 'Arrows (10)', 'Bombs (3)', 'Bombs (10)'] cap_blacklist = ['Green Potion', 'Red Potion', 'Blue Potion'] -shop_transfer = {'Red Potion': 'Rupees (100)', 'Bee': 'Rupees (5)', 'Blue Potion': 'Rupees (100)', +shop_transfer = {'Red Potion': 'Rupees (50)', 'Bee': 'Rupees (5)', 'Blue Potion': 'Rupees (50)', 'Green Potion': 'Rupees (50)', # money seems a bit too generous with these on # 'Blue Shield': 'Rupees (50)', 'Red Shield': 'Rupees (300)', diff --git a/Items.py b/Items.py index d280d6e5..59b8bdc6 100644 --- a/Items.py +++ b/Items.py @@ -65,8 +65,8 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Blue Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x02], 999, None, None, None, None, None, None, None), 'Red Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x03], 999, None, None, None, None, None, None, None), 'Triforce': (True, False, None, 0x6A, 777, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'the Triforce'), - 'Power Star': (True, False, None, 0x6B, 50, 'a small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'), - 'Triforce Piece': (True, False, None, 0x6C, 50, 'a small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce Piece'), + 'Power Star': (True, False, None, 0x6B, 100, 'a small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'), + 'Triforce Piece': (True, False, None, 0x6C, 100, 'a small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce Piece'), 'Crystal 1': (True, False, 'Crystal', [0x02, 0x34, 0x64, 0x40, 0x7F, 0x06], 999, None, None, None, None, None, None, None), 'Crystal 2': (True, False, 'Crystal', [0x10, 0x34, 0x64, 0x40, 0x79, 0x06], 999, None, None, None, None, None, None, None), 'Crystal 3': (True, False, 'Crystal', [0x40, 0x34, 0x64, 0x40, 0x6C, 0x06], 999, None, None, None, None, None, None, None), @@ -111,56 +111,56 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Multi RNG': (False, True, None, 0x63, 100, 'something you may already have', None, None, None, None, 'unknown boy somethings again', 'a total mystery'), 'Magic Upgrade (1/2)': (True, False, None, 0x4E, 50, 'Your magic\npower has been\ndoubled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'half magic'), # can be required to beat mothula in an open seed in very very rare circumstance 'Magic Upgrade (1/4)': (True, False, None, 0x4F, 100, 'Your magic\npower has been\nquadrupled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'quarter magic'), # can be required to beat mothula in an open seed in very very rare circumstance - 'Small Key (Eastern Palace)': (False, False, 'SmallKey', 0xA2, 30, 'A small key to Armos Knights', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Eastern Palace'), - 'Big Key (Eastern Palace)': (False, False, 'BigKey', 0x9D, 50, 'A big key to Armos Knights', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Eastern Palace'), + 'Small Key (Eastern Palace)': (False, False, 'SmallKey', 0xA2, 40, 'A small key to Armos Knights', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Eastern Palace'), + 'Big Key (Eastern Palace)': (False, False, 'BigKey', 0x9D, 60, 'A big key to Armos Knights', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Eastern Palace'), 'Compass (Eastern Palace)': (False, True, 'Compass', 0x8D, 10, 'Now you can find the Armos Knights!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Eastern Palace'), 'Map (Eastern Palace)': (False, True, 'Map', 0x7D, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Eastern Palace'), - 'Small Key (Desert Palace)': (False, False, 'SmallKey', 0xA3, 30, 'A small key to the desert', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Desert Palace'), - 'Big Key (Desert Palace)': (False, False, 'BigKey', 0x9C, 50, 'A big key to the desert', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Desert Palace'), + 'Small Key (Desert Palace)': (False, False, 'SmallKey', 0xA3, 40, 'A small key to the desert', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Desert Palace'), + 'Big Key (Desert Palace)': (False, False, 'BigKey', 0x9C, 60, 'A big key to the desert', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Desert Palace'), 'Compass (Desert Palace)': (False, True, 'Compass', 0x8C, 10, 'Now you can find Lanmolas!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Desert Palace'), 'Map (Desert Palace)': (False, True, 'Map', 0x7C, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Desert Palace'), - 'Small Key (Tower of Hera)': (False, False, 'SmallKey', 0xAA, 30, 'A small key to Hera', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Tower of Hera'), - 'Big Key (Tower of Hera)': (False, False, 'BigKey', 0x95, 50, 'A big key to Hera', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Tower of Hera'), + 'Small Key (Tower of Hera)': (False, False, 'SmallKey', 0xAA, 40, 'A small key to Hera', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Tower of Hera'), + 'Big Key (Tower of Hera)': (False, False, 'BigKey', 0x95, 60, 'A big key to Hera', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Tower of Hera'), 'Compass (Tower of Hera)': (False, True, 'Compass', 0x85, 10, 'Now you can find Moldorm!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Tower of Hera'), 'Map (Tower of Hera)': (False, True, 'Map', 0x75, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Tower of Hera'), - 'Small Key (Escape)': (False, False, 'SmallKey', 0xA0, 30, 'A small key to the castle', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Hyrule Castle'), - 'Big Key (Escape)': (False, False, 'BigKey', 0x9F, 50, 'A big key to the castle', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Hyrule Castle'), + 'Small Key (Escape)': (False, False, 'SmallKey', 0xA0, 40, 'A small key to the castle', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Hyrule Castle'), + 'Big Key (Escape)': (False, False, 'BigKey', 0x9F, 60, 'A big key to the castle', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Hyrule Castle'), 'Compass (Escape)': (False, True, 'Compass', 0x8F, 10, 'Now you can find no boss!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds null again', 'a compass to Hyrule Castle'), 'Map (Escape)': (False, True, 'Map', 0x7F, 10, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Hyrule Castle'), - 'Small Key (Agahnims Tower)': (False, False, 'SmallKey', 0xA4, 30, 'A small key to Agahnim', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Castle Tower'), - 'Big Key (Agahnims Tower)': (False, False, 'BigKey', 0x9B, 50, 'A big key to Agahnim', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Castle Tower'), + 'Small Key (Agahnims Tower)': (False, False, 'SmallKey', 0xA4, 40, 'A small key to Agahnim', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Castle Tower'), + 'Big Key (Agahnims Tower)': (False, False, 'BigKey', 0x9B, 60, 'A big key to Agahnim', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Castle Tower'), 'Compass (Agahnims Tower)': (False, True, 'Compass', 0x8B, 10, 'Now you can find Aga1!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds null again', 'a compass to Castle Tower'), 'Map (Agahnims Tower)': (False, True, 'Map', 0x7B, 10, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Castle Tower'), - 'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 30, 'A small key to darkness', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Palace of Darkness'), - 'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 50, 'A big key to darkness', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Palace of Darkness'), + 'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 40, 'A small key to darkness', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Palace of Darkness'), + 'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 60, 'A big key to darkness', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Palace of Darkness'), 'Compass (Palace of Darkness)': (False, True, 'Compass', 0x89, 10, 'Now you can find Helmasaur King!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Palace of Darkness'), 'Map (Palace of Darkness)': (False, True, 'Map', 0x79, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Palace of Darkness'), - 'Small Key (Thieves Town)': (False, False, 'SmallKey', 0xAB, 30, 'A small key to thievery', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Thieves\' Town'), - 'Big Key (Thieves Town)': (False, False, 'BigKey', 0x94, 50, 'A big key to thievery', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Thieves\' Town'), + 'Small Key (Thieves Town)': (False, False, 'SmallKey', 0xAB, 40, 'A small key to thievery', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Thieves\' Town'), + 'Big Key (Thieves Town)': (False, False, 'BigKey', 0x94, 60, 'A big key to thievery', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Thieves\' Town'), 'Compass (Thieves Town)': (False, True, 'Compass', 0x84, 10, 'Now you can find Blind!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Thieves\' Town'), 'Map (Thieves Town)': (False, True, 'Map', 0x74, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Thieves\' Town'), - 'Small Key (Skull Woods)': (False, False, 'SmallKey', 0xA8, 30, 'A small key to the woods', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Skull Woods'), - 'Big Key (Skull Woods)': (False, False, 'BigKey', 0x97, 50, 'A big key to the woods', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Skull Woods'), + 'Small Key (Skull Woods)': (False, False, 'SmallKey', 0xA8, 40, 'A small key to the woods', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Skull Woods'), + 'Big Key (Skull Woods)': (False, False, 'BigKey', 0x97, 60, 'A big key to the woods', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Skull Woods'), 'Compass (Skull Woods)': (False, True, 'Compass', 0x87, 10, 'Now you can find Mothula!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Skull Woods'), 'Map (Skull Woods)': (False, True, 'Map', 0x77, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Skull Woods'), - 'Small Key (Swamp Palace)': (False, False, 'SmallKey', 0xA5, 30, 'A small key to the swamp', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Swamp Palace'), - 'Big Key (Swamp Palace)': (False, False, 'BigKey', 0x9A, 50, 'A big key to the swamp', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Swamp Palace'), + 'Small Key (Swamp Palace)': (False, False, 'SmallKey', 0xA5, 40, 'A small key to the swamp', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Swamp Palace'), + 'Big Key (Swamp Palace)': (False, False, 'BigKey', 0x9A, 60, 'A big key to the swamp', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Swamp Palace'), 'Compass (Swamp Palace)': (False, True, 'Compass', 0x8A, 10, 'Now you can find Arrghus!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Swamp Palace'), 'Map (Swamp Palace)': (False, True, 'Map', 0x7A, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Swamp Palace'), - 'Small Key (Ice Palace)': (False, False, 'SmallKey', 0xA9, 30, 'A small key to the iceberg', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Ice Palace'), - 'Big Key (Ice Palace)': (False, False, 'BigKey', 0x96, 50, 'A big key to the iceberg', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Ice Palace'), + 'Small Key (Ice Palace)': (False, False, 'SmallKey', 0xA9, 40, 'A small key to the iceberg', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Ice Palace'), + 'Big Key (Ice Palace)': (False, False, 'BigKey', 0x96, 60, 'A big key to the iceberg', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Ice Palace'), 'Compass (Ice Palace)': (False, True, 'Compass', 0x86, 10, 'Now you can find Kholdstare!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Ice Palace'), 'Map (Ice Palace)': (False, True, 'Map', 0x76, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Ice Palace'), - 'Small Key (Misery Mire)': (False, False, 'SmallKey', 0xA7, 30, 'A small key to the mire', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Misery Mire'), - 'Big Key (Misery Mire)': (False, False, 'BigKey', 0x98, 50, 'A big key to the mire', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Misery Mire'), + 'Small Key (Misery Mire)': (False, False, 'SmallKey', 0xA7, 40, 'A small key to the mire', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Misery Mire'), + 'Big Key (Misery Mire)': (False, False, 'BigKey', 0x98, 60, 'A big key to the mire', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Misery Mire'), 'Compass (Misery Mire)': (False, True, 'Compass', 0x88, 10, 'Now you can find Vitreous!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Misery Mire'), 'Map (Misery Mire)': (False, True, 'Map', 0x78, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Misery Mire'), - 'Small Key (Turtle Rock)': (False, False, 'SmallKey', 0xAC, 30, 'A small key to the pipe maze', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Turtle Rock'), - 'Big Key (Turtle Rock)': (False, False, 'BigKey', 0x93, 50, 'A big key to the pipe maze', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Turtle Rock'), + 'Small Key (Turtle Rock)': (False, False, 'SmallKey', 0xAC, 40, 'A small key to the pipe maze', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Turtle Rock'), + 'Big Key (Turtle Rock)': (False, False, 'BigKey', 0x93, 60, 'A big key to the pipe maze', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Turtle Rock'), 'Compass (Turtle Rock)': (False, True, 'Compass', 0x83, 10, 'Now you can find Trinexx!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Turtle Rock'), 'Map (Turtle Rock)': (False, True, 'Map', 0x73, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Turtle Rock'), - 'Small Key (Ganons Tower)': (False, False, 'SmallKey', 0xAD, 30, 'A small key to the evil tower', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Ganon\'s Tower'), - 'Big Key (Ganons Tower)': (False, False, 'BigKey', 0x92, 50, 'A big key to the evil tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Ganon\'s Tower'), + 'Small Key (Ganons Tower)': (False, False, 'SmallKey', 0xAD, 40, 'A small key to the evil tower', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Ganon\'s Tower'), + 'Big Key (Ganons Tower)': (False, False, 'BigKey', 0x92, 60, 'A big key to the evil tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Ganon\'s Tower'), 'Compass (Ganons Tower)': (False, True, 'Compass', 0x82, 10, 'Now you can find Agahnim!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a comapss to Ganon\'s Tower'), 'Map (Ganons Tower)': (False, True, 'Map', 0x72, 10, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Ganon\'s Tower'), 'Small Key (Universal)': (False, True, None, 0xAF, 100, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key'), diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e8871542..4d941e4f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -51,6 +51,7 @@ All prices range approx. from half the base price to the base price in increment | Major Progression | Hammer, Hookshot, Mirror, Ocarina, Boots, Somaria, Fire Rod, Ice Rod | 250 | 125-250 | | Moon Pearl | 200 | 100-200 | | Lamp, Progressive Bows, Gloves, & Swords | 150 | 75-150 +| | Triforce Piece | 100 | 50-100 | Medallions | Bombos, Ether, Quake | 100 | 50-100 | Safety/Fetch | Cape, Mushroom, Shovel, Powder, Bug Net, Byrna, Progressive Armor & Shields, Half Magic | 50 | 25-50 | Bottles | Empty Bottle or Bee Bottle | 50 | 25-50 @@ -60,8 +61,8 @@ All prices range approx. from half the base price to the base price in increment | Health | Heart Container | 40 | 20-40 | | Sanctuary Heart | 50 | 25-50 | | Piece of Heart | 10 | 5-10 -| Dungeon | Big Keys | 50 | 25-50 -| | Small Keys | 30 | 15-30 +| Dungeon | Big Keys | 60 | 30-60 +| | Small Keys | 40 | 20-40 | | Info Maps | 20 | 10-20 | | Other Maps & Compasses | 10 | 5-10 | Rupees | Green | Free | Free @@ -74,7 +75,7 @@ All prices range approx. from half the base price to the base price in increment | | Single Arrow | 3 | 3 | Original Shop Items | Other Ammo, Refills, Non-Progressive Shields, Capacity Upgrades, Small Hearts, Retro Quiver, Universal Key | Original | Could be Discounted as Above -In addition, 4-7 items are steeply discounted at random. +~~In addition, 4-7 items are steeply discounted at random.~~ Sales are over. #### Rupee Balancing Algorithm @@ -122,6 +123,21 @@ Added to CLI only now. With more testing, this will be added to the GUI to be ab The Mystery.py file has been updated for those who like to use that for generating games. Supports keydropshuffle, shopsanity, and other settings that have been added. +## Triforce Hunt Options + +Thanks to deathFouton! + +--triforce_pool and --triforce_goal added to the CLI. + +Also, to the Mystery.py he added the following options: +* triforce_goal_min +* triforce_goal_max +* triforce_pool_min +* triforce_pool_max +* triforce_min_difference + +See the example yaml file for demonstrated usage. + ## Experimental Item Counter New item counter modified to show total @@ -134,6 +150,9 @@ New item counter modified to show total * Fix for potshuffle and swamp trench generation errors (Thanks StructuralMike) * Fix for TFH playthrough (Thanks StructuralMike) * Fix for Standard+Retro (Thanks StructuralMike) + * New options for TFH in CLI and Mystery (Thanks deathFouton) + * A few price adjustments for Shopsanity + * Fixed a subtle problem with Progressive Shields introduced by Shopsanity * 0.3.1.5-u * Ganon hints fixed for shops * Added support for a settings file so SahasrahBot and the main website can use it easier. (Thanks Synack) From 5073b9e630593bf8ac3c092da47f59799d4ce933 Mon Sep 17 00:00:00 2001 From: StructuralMike <66819228+StructuralMike@users.noreply.github.com> Date: Thu, 18 Mar 2021 16:19:42 +0100 Subject: [PATCH 16/17] Revert "Update .gitignore" This reverts commit f95270587f345524577b3d649f3b65e14fcb341e. --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index bee1283a..35be5751 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,6 @@ weights/ /MultiMystery/ /Players/ /QUsb2Snes/ -/output/ base2current.json From 5b37b32c8d8b0535e6a38ae656156e65e9d15707 Mon Sep 17 00:00:00 2001 From: StructuralMike <66819228+StructuralMike@users.noreply.github.com> Date: Thu, 18 Mar 2021 16:33:25 +0100 Subject: [PATCH 17/17] Revert "Revert "Update .gitignore"" This reverts commit 5073b9e630593bf8ac3c092da47f59799d4ce933. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 35be5751..bee1283a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ weights/ /MultiMystery/ /Players/ /QUsb2Snes/ +/output/ base2current.json