From e4c4fbf4505d49c134767175505130cfe6550453 Mon Sep 17 00:00:00 2001 From: TreZc0 Date: Fri, 12 Nov 2021 22:09:19 +0100 Subject: [PATCH 01/21] update readme and version for post 6.1 dev --- README.md | 5 ++++- version.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ebad90faa..9c362a83e 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ This is a randomizer for _The Legend of Zelda: Ocarina of Time_ for the Nintendo * [Settings](#settings) * [Known Issues](#known-issues) * [Changelog](#changelog) + * [6.1](#61) * [6.0](#60) * [5.2](#52) * [5.1](#51) @@ -100,6 +101,8 @@ do that. ### Dev +### 6.1 + #### New Features * **Objective Settings** @@ -176,7 +179,7 @@ do that. * Added Nayru's Love back to the minimal item pool on high damage settings. * Allow settings with a 'Random' option to be different per-world. (This does not permit settings randomized only by Randomize Main Rules to be different per-world.) * Updated sometimes hints. -* Renamed some regions, locations, items, etc to make vanilla names. This will make Plandomizer files incompatible between versions. +* Renamed some regions, locations, items, etc to match vanilla names. This will make Plandomizer files incompatible between versions. * Gerudo Training **Grounds** -> Gerudo Training **Ground** * Gerudo Fortress -> Thieves' Hideout (when referring to the interior areas or the carpenter rescue quest) * Graveyard Composers' Grave -> Royal Family's Tomb diff --git a/version.py b/version.py index 7f4eb5700..387a29c57 100644 --- a/version.py +++ b/version.py @@ -1 +1 @@ -__version__ = '6.0.138 f.LUM' +__version__ = '6.1.1 f.LUM' From 6edd54128e98596297b813b74c1af9118e9cc77f Mon Sep 17 00:00:00 2001 From: mracsys Date: Mon, 15 Nov 2021 08:10:23 -0500 Subject: [PATCH 02/21] search goal items per-world instead of assuming same items in all worlds --- Search.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Search.py b/Search.py index 443aaf80a..f33116c9a 100644 --- a/Search.py +++ b/Search.py @@ -273,11 +273,12 @@ def test_category_goals(self, goal_categories, world_filter = None): if world_filter is not None and state.world.id != world_filter: continue valid_goals[category_name]['stateReverse'][state.world.id] = [] - for goal in category.goals: + world_category = state.world.goal_categories[category_name] + for goal in world_category.goals: if goal.name not in valid_goals[category_name]: valid_goals[category_name][goal.name] = [] # Check if already beaten - if all(map(lambda i: state.has_full_item_goal(category, goal, i), goal.items)): + if all(map(lambda i: state.has_full_item_goal(world_category, goal, i), goal.items)): valid_goals[category_name][goal.name].append(state.world.id) # Reverse lookup for checking if the category is already beaten. # Only used to check if starting items satisfy the category. From d1563d6dc623c57e302ac293feb9988ff1363de5 Mon Sep 17 00:00:00 2001 From: mracsys Date: Mon, 15 Nov 2021 08:33:23 -0500 Subject: [PATCH 03/21] support MW random trials selecting 0 trials for a world --- World.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/World.py b/World.py index de985bf60..3f0be3e26 100644 --- a/World.py +++ b/World.py @@ -754,7 +754,10 @@ def set_goals(self): trials.goal_count += 1 # Trials category is finalized and saved only if at least one trial is on - if self.settings.trials > 0: + # If random trials are on and one world in multiworld gets 0 trials, still + # add the goal to prevent key errors. Since no items fulfill the goal, it + # will always be invalid for that world and not generate hints. + if self.settings.trials > 0 or self.settings.trials_random: trials.add_goal(trial_goal) self.goal_categories[trials.name] = trials From 89f314850ace45b5cb7cc7f7dfce1144b2a052ad Mon Sep 17 00:00:00 2001 From: mracsys Date: Mon, 15 Nov 2021 08:46:07 -0500 Subject: [PATCH 04/21] fully support 0 random trial MW --- Goals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Goals.py b/Goals.py index d8ba8965d..6ce22a620 100644 --- a/Goals.py +++ b/Goals.py @@ -40,7 +40,7 @@ def __init__(self, world, name, hint_text, color, items=None, locations=None, lo self._item_cache = {} def copy(self): - new_goal = Goal(self.world, self.name, self.hint_text, self.color, self.items, self.locations, self.lock_locations, self.lock_entrances, self.required_locations) + new_goal = Goal(self.world, self.name, self.hint_text, self.color, self.items, self.locations, self.lock_locations, self.lock_entrances, self.required_locations, True) return new_goal def get_item(self, item): From d7173d797fdd483e1a9430a22d4c86b18be10a38 Mon Sep 17 00:00:00 2001 From: Fenhl Date: Thu, 18 Nov 2021 00:33:46 +0000 Subject: [PATCH 05/21] Enforce monotonicity of access checks in AGR and RO --- Fill.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Fill.py b/Fill.py index c9021afc0..fd69a4135 100644 --- a/Fill.py +++ b/Fill.py @@ -368,6 +368,7 @@ def fill_restrictive(window, worlds, base_search, locations, itempool, count=-1) logging.getLogger('').debug(f'Placing {len(itempool)} items among {len(locations)} potential locations.') # loop until there are no items or locations + perform_access_check = False while itempool and locations: # if remaining count is 0, return. Negative means unbounded. if count == 0: @@ -394,7 +395,7 @@ def fill_restrictive(window, worlds, base_search, locations, itempool, count=-1) elif worlds[0].settings.reachable_locations == 'goals': # for All Goals Reachable, we have to track whether any goal items have been placed, # since we then have to start checking their reachability. - perform_access_check = item_to_place.goalitem or not max_search.can_beat_game(scan_for_items=False) + perform_access_check = perform_access_check or item_to_place.goalitem or not max_search.can_beat_game(scan_for_items=False) extra_location_checks = [location for world in worlds for location in world.get_filled_locations() if location.item.goalitem] else: # if any world can not longer be beatable with the remaining items @@ -403,7 +404,7 @@ def fill_restrictive(window, worlds, base_search, locations, itempool, count=-1) # stop checking, then we could place an item needed in one world # in an unreachable place in another world. # scan_for_items would cause an unnecessary copy+collect - perform_access_check = not max_search.can_beat_game(scan_for_items=False) + perform_access_check = perform_access_check or not max_search.can_beat_game(scan_for_items=False) extra_location_checks = [] # find a location that the item can be placed. It must be a valid location From ce1e59e9d0103586f0acd7a6c9b5074970457405 Mon Sep 17 00:00:00 2001 From: Aaron Ofengender Date: Thu, 18 Nov 2021 18:08:16 -0500 Subject: [PATCH 06/21] Change name of Enable Useful Cutscenes to Enable Specfiic Glitch-Useful Cutscenes to more clearly define what the setting does. Also removes outdated reference in tooltip to the twinrova cutscene, which is now default. --- SettingsList.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/SettingsList.py b/SettingsList.py index a2ee1127a..0a5d46fe2 100644 --- a/SettingsList.py +++ b/SettingsList.py @@ -2399,11 +2399,12 @@ def __init__(self, name, gui_text, min, max, default, step=1, ), Checkbutton( name = 'useful_cutscenes', - gui_text = 'Enable Useful Cutscenes', + gui_text = 'Enable Specific Glitch-Useful Cutscenes', gui_tooltip = '''\ - The cutscenes of the Poes in Forest Temple, - Darunia in Fire Temple, and the introduction - to Twinrova will not be skipped. + The cutscenes of the Poes in Forest Temple and Darunia in + Fire Temple will not be skipped. These cutscenes are useful + in glitched gameplay only and do not provide any timesave + for glitchless playthroughs. ''', shared = True, ), From ed693aff1ba2c8f4957e85ffdb436944afa71603 Mon Sep 17 00:00:00 2001 From: Fenhl Date: Fri, 19 Nov 2021 01:13:08 +0000 Subject: [PATCH 07/21] Better AGR fix --- Fill.py | 31 ++++++++++++------------------- Goals.py | 8 ++++---- Location.py | 15 ++++++--------- Search.py | 10 +++++----- SettingsList.py | 2 +- State.py | 8 ++++++++ 6 files changed, 36 insertions(+), 38 deletions(-) diff --git a/Fill.py b/Fill.py index fd69a4135..f08505862 100644 --- a/Fill.py +++ b/Fill.py @@ -368,7 +368,6 @@ def fill_restrictive(window, worlds, base_search, locations, itempool, count=-1) logging.getLogger('').debug(f'Placing {len(itempool)} items among {len(locations)} potential locations.') # loop until there are no items or locations - perform_access_check = False while itempool and locations: # if remaining count is 0, return. Negative means unbounded. if count == 0: @@ -389,29 +388,23 @@ def fill_restrictive(window, worlds, base_search, locations, itempool, count=-1) max_search.collect_locations() # perform_access_check checks location reachability - if worlds[0].settings.reachable_locations == 'all': - perform_access_check = True - extra_location_checks = [] - elif worlds[0].settings.reachable_locations == 'goals': - # for All Goals Reachable, we have to track whether any goal items have been placed, - # since we then have to start checking their reachability. - perform_access_check = perform_access_check or item_to_place.goalitem or not max_search.can_beat_game(scan_for_items=False) - extra_location_checks = [location for world in worlds for location in world.get_filled_locations() if location.item.goalitem] + if worlds[0].check_beatable_only: + if worlds[0].settings.reachable_locations == 'goals': + # If this item is required for a goal, it must be placed somewhere reachable. + predicate = State.has_all_item_goals + else: + # If the game is not beatable without this item, it must be placed somewhere reachable. + predicate = State.won + perform_access_check = not max_search.can_beat_game(scan_for_items=False, predicate=predicate) else: - # if any world can not longer be beatable with the remaining items - # then we must check for reachability no matter what. - # This way the reachability test is monotonic. If we were to later - # stop checking, then we could place an item needed in one world - # in an unreachable place in another world. - # scan_for_items would cause an unnecessary copy+collect - perform_access_check = perform_access_check or not max_search.can_beat_game(scan_for_items=False) - extra_location_checks = [] + # All items must be placed somewhere reachable. + perform_access_check = True # find a location that the item can be placed. It must be a valid location # in the world we are placing it (possibly checking for reachability) spot_to_fill = None for location in l2cations: - if location.can_fill(max_search.state_list[location.world.id], item_to_place, perform_access_check, extra_location_checks): + if location.can_fill(max_search.state_list[location.world.id], item_to_place, perform_access_check): # for multiworld, make it so that the location is also reachable # in the world the item is for. This is to prevent early restrictions # in one world being placed late in another world. If this is not @@ -419,7 +412,7 @@ def fill_restrictive(window, worlds, base_search, locations, itempool, count=-1) if location.world.id != item_to_place.world.id: try: source_location = item_to_place.world.get_location(location.name) - if not source_location.can_fill(max_search.state_list[item_to_place.world.id], item_to_place, perform_access_check, extra_location_checks): + if not source_location.can_fill(max_search.state_list[item_to_place.world.id], item_to_place, perform_access_check): # location wasn't reachable in item's world, so skip it continue except KeyError: diff --git a/Goals.py b/Goals.py index d8ba8965d..3dd683f92 100644 --- a/Goals.py +++ b/Goals.py @@ -98,7 +98,7 @@ def get_goal(self, goal): def is_beaten(self, search): # if the category requirements are already satisfied by starting items (such as Links Pocket), # do not generate hints for other goals in the category - starting_goals = search.can_beat_goals_fast({ self.name: self }) + starting_goals = search.beatable_goals_fast({ self.name: self }) return all(map(lambda s: len(starting_goals[self.name]['stateReverse'][s.world.id]) >= self.minimum_goals, search.state_list)) @@ -184,7 +184,7 @@ def update_goal_items(spoiler): reachable_goals = {} # Goals are changed for beatable-only accessibility per-world category.update_reachable_goals(search, full_search) - reachable_goals = full_search.can_beat_goals_fast({ cat_name: category }, cat_world.id) + reachable_goals = full_search.beatable_goals_fast({ cat_name: category }, cat_world.id) identified_locations = search_goals({ cat_name: category }, reachable_goals, search, priority_locations, all_locations, item_locations, always_locations, _maybe_set_light_arrows) # Multiworld can have all goals for one player's bridge entirely # locked by another player's bridge. Therefore, we can't assume @@ -208,7 +208,7 @@ def update_goal_items(spoiler): full_search.collect_locations() for cat_name, category in worlds[0].unlocked_goal_categories.items(): category.update_reachable_goals(search, full_search) - reachable_goals = full_search.can_beat_goals_fast(worlds[0].unlocked_goal_categories) + reachable_goals = full_search.beatable_goals_fast(worlds[0].unlocked_goal_categories) identified_locations = search_goals(worlds[0].unlocked_goal_categories, reachable_goals, search, priority_locations, all_locations, item_locations, always_locations, _maybe_set_light_arrows, search_woth=True) required_locations.update(identified_locations) woth_locations = list(required_locations['way of the hero']) @@ -300,7 +300,7 @@ def search_goals(categories, reachable_goals, search, priority_locations, all_lo location.item = None # copies state! This is very important as we're in the middle of a search # already, but beneficially, has search it can start from - valid_goals = search.can_beat_goals(categories) + valid_goals = search.beatable_goals(categories) for cat_name, category in categories.items(): # Exit early if no goals are beatable with category locks if category.name in reachable_goals and reachable_goals[category.name]: diff --git a/Location.py b/Location.py index 1aabb2c7a..38a9ce41d 100644 --- a/Location.py +++ b/Location.py @@ -68,17 +68,14 @@ def set_rule(self, lambda_rule): self.access_rules = [lambda_rule] - def can_fill(self, state, item, check_access=True, extra_location_checks=()): + def can_fill(self, state, item, check_access=True): if self.minor_only and item.majoritem: return False - if self.is_disabled() or not self.can_fill_fast(item) or (check_access and not state.search.spot_access(self, 'either')): - return False - if not extra_location_checks: - return True - search_with_this = state.search.copy() - search_with_this.collect(item) - search_with_this.collect_locations(list(chain(search_with_this.progression_locations(), extra_location_checks))) - return all(map(search_with_this.visited, extra_location_checks)) + return ( + not self.is_disabled() and + self.can_fill_fast(item) and + (not check_access or state.search.spot_access(self, 'either')) + ) def can_fill_fast(self, item, manual=False): diff --git a/Search.py b/Search.py index 443aaf80a..70ca59f85 100644 --- a/Search.py +++ b/Search.py @@ -220,10 +220,10 @@ def progression_locations(self): # # Win condition can be a string that gets mapped to a function(state_list) here # or just a function(state_list) - def can_beat_game(self, scan_for_items=True): + def can_beat_game(self, scan_for_items=True, predicate=State.won): # Check if already beaten - if all(map(State.won, self.state_list)): + if all(map(predicate, self.state_list)): return True if scan_for_items: @@ -232,12 +232,12 @@ def can_beat_game(self, scan_for_items=True): search = self.copy() search.collect_locations() # if every state got the Triforce, then return True - return all(map(State.won, search.state_list)) + return all(map(predicate, search.state_list)) else: return False - def can_beat_goals_fast(self, goal_categories, world_filter = None): + def beatable_goals_fast(self, goal_categories, world_filter = None): valid_goals = self.test_category_goals(goal_categories, world_filter) if all(map(State.won, self.state_list)): valid_goals['way of the hero'] = True @@ -246,7 +246,7 @@ def can_beat_goals_fast(self, goal_categories, world_filter = None): return valid_goals - def can_beat_goals(self, goal_categories): + def beatable_goals(self, goal_categories): # collect all available items # make a new search since we might be iterating over one already search = self.copy() diff --git a/SettingsList.py b/SettingsList.py index a2ee1127a..35d72ae90 100644 --- a/SettingsList.py +++ b/SettingsList.py @@ -2236,7 +2236,7 @@ def __init__(self, name, gui_text, min, max, default, step=1, to beat the game, but otherwise behaves like 'Required Only'. Goal items are the items required for the rainbow bridge and/or Ganon's Boss Key, so for example if the bridge is set to 1 Medallion and Ganon's Boss Key to 1 Gold Skulltula Token, all 6 Medallions and all 100 Tokens will - be obtainable. In Triforce Hunt, this will also guarantee that all Triforce Pieces can be obtained. + be obtainable. In Triforce Hunt, this will instead guarantee that all Triforce Pieces can be obtained. 'Required Only': Only items and locations required to beat the game will be guaranteed reachable. ''', diff --git a/State.py b/State.py index a1c08b17a..7fbb94b07 100644 --- a/State.py +++ b/State.py @@ -108,6 +108,14 @@ def has_full_item_goal(self, category, goal, item_goal): return self.prog_items[item_goal['name']] >= per_world_max_quantity + def has_all_item_goals(self): + for category in self.world.goal_categories.values(): + for goal in category.goals: + if not all(map(lambda i: self.has_full_item_goal(category, goal, i), goal.items)): + return False + return True + + def had_night_start(self): stod = self.world.settings.starting_tod # These are all not between 6:30 and 18:00 From 66e144a395cb255b1cafd2456e1d08f38cfbdc6a Mon Sep 17 00:00:00 2001 From: mracsys Date: Thu, 18 Nov 2021 21:12:39 -0500 Subject: [PATCH 08/21] allow foolish hints for areas with always hints but not other hint types --- Hints.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Hints.py b/Hints.py index 974bdb2a2..e54756e0f 100644 --- a/Hints.py +++ b/Hints.py @@ -896,6 +896,7 @@ def buildWorldGossipHints(spoiler, world, checkedLocations=None): if checkedLocations is None: checkedLocations = set() + checkedAlwaysLocations = set() stoneIDs = list(gossipLocations.keys()) @@ -1020,7 +1021,7 @@ def buildWorldGossipHints(spoiler, world, checkedLocations=None): alwaysLocations = getHintGroup('always', world) for hint in alwaysLocations: location = world.get_location(hint.name) - checkedLocations.add(hint.name) + checkedAlwaysLocations.add(hint.name) if location.item.name in bingoBottlesForHints and world.settings.hint_dist == 'bingo': always_item = 'Bottle' else: @@ -1059,7 +1060,7 @@ def buildWorldGossipHints(spoiler, world, checkedLocations=None): raise Exception('User-provided item hints were requested, but copies per named-item hint is zero') else: for i in range(0, len(world.named_item_pool)): - hint = get_specific_item_hint(spoiler, world, checkedLocations) + hint = get_specific_item_hint(spoiler, world, set.union(checkedLocations, checkedAlwaysLocations)) if hint: gossip_text, location = hint place_ok = add_hint(spoiler, world, stoneGroups, gossip_text, hint_dist['named-item'][1], location) @@ -1116,7 +1117,12 @@ def buildWorldGossipHints(spoiler, world, checkedLocations=None): except IndexError: raise Exception('Not enough valid hints to fill gossip stone locations.') - hint = hint_func[hint_type](spoiler, world, checkedLocations) + allCheckedLocations = set.union(checkedLocations, checkedAlwaysLocations) + if hint_type == 'barren': + hint = hint_func[hint_type](spoiler, world, checkedLocations) + else: + hint = hint_func[hint_type](spoiler, world, allCheckedLocations) + checkedLocations.update(allCheckedLocations.difference(set.union(checkedLocations, checkedAlwaysLocations))) if hint == None: index = hint_types.index(hint_type) From 8a917cb308a2a776fb5f50bd293658a82dcc4746 Mon Sep 17 00:00:00 2001 From: Benjamin S Wolf Date: Fri, 19 Nov 2021 11:41:47 -0800 Subject: [PATCH 09/21] Update Search.py comment --- Search.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Search.py b/Search.py index 70ca59f85..ba288e459 100644 --- a/Search.py +++ b/Search.py @@ -218,8 +218,7 @@ def progression_locations(self): # conditions are possible, such as in Triforce Hunt, where only the total # amount of an item across all worlds matter, not specifcally who has it # - # Win condition can be a string that gets mapped to a function(state_list) here - # or just a function(state_list) + # predicate must be a function (state) -> bool, that will be applied to all states def can_beat_game(self, scan_for_items=True, predicate=State.won): # Check if already beaten From d891bf4789dc30892bcef4459e823b0f3af83c8b Mon Sep 17 00:00:00 2001 From: Zannick Date: Fri, 19 Nov 2021 12:22:08 -0800 Subject: [PATCH 10/21] Add trials=random to multiworld test case --- tests/multiworld.sav | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/multiworld.sav b/tests/multiworld.sav index a3656cc4d..2c9449e02 100644 --- a/tests/multiworld.sav +++ b/tests/multiworld.sav @@ -2,7 +2,6 @@ "seed": "TESTTESTTEST", "cosmetics_only": false, "world_count": 3, -"player_num": 1, "create_spoiler": true, "create_cosmetics_log": false, "compress_rom": "None", @@ -17,8 +16,7 @@ "reachable_locations": "all", "bombchus_in_logic": true, "one_item_per_dungeon": false, -"trials_random": false, -"trials": 0, +"trials_random": true, "logic_no_night_tokens_without_suns_song": false, "free_scarecrow": false, "big_poe_count_random": false, From fc1a86d4b3a74c097daaec07e5e0814e25e1187e Mon Sep 17 00:00:00 2001 From: Fenhl Date: Sat, 20 Nov 2021 03:52:23 +0000 Subject: [PATCH 11/21] Adjust AGR predicate to work with custom goals --- Fill.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Fill.py b/Fill.py index f08505862..cec5f8b35 100644 --- a/Fill.py +++ b/Fill.py @@ -391,7 +391,8 @@ def fill_restrictive(window, worlds, base_search, locations, itempool, count=-1) if worlds[0].check_beatable_only: if worlds[0].settings.reachable_locations == 'goals': # If this item is required for a goal, it must be placed somewhere reachable. - predicate = State.has_all_item_goals + # We also need to check to make sure the game is beatable, since custom goals might not imply that. + predicate = lambda state: state.won and state.has_all_item_goals else: # If the game is not beatable without this item, it must be placed somewhere reachable. predicate = State.won From 36f5cb804ba1ab407d2bc3857915bc225ce0594b Mon Sep 17 00:00:00 2001 From: Fenhl Date: Sat, 20 Nov 2021 03:54:15 +0000 Subject: [PATCH 12/21] These aren't properties --- Fill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Fill.py b/Fill.py index cec5f8b35..a9f817c6a 100644 --- a/Fill.py +++ b/Fill.py @@ -392,7 +392,7 @@ def fill_restrictive(window, worlds, base_search, locations, itempool, count=-1) if worlds[0].settings.reachable_locations == 'goals': # If this item is required for a goal, it must be placed somewhere reachable. # We also need to check to make sure the game is beatable, since custom goals might not imply that. - predicate = lambda state: state.won and state.has_all_item_goals + predicate = lambda state: state.won() and state.has_all_item_goals() else: # If the game is not beatable without this item, it must be placed somewhere reachable. predicate = State.won From 4b33fc5c6db30f460a278e71b0de6787425d7512 Mon Sep 17 00:00:00 2001 From: Zannick Date: Sat, 20 Nov 2021 13:36:51 -0800 Subject: [PATCH 13/21] Make one entrance unittest test AGR. --- tests/entrance.sav | 2 +- version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/entrance.sav b/tests/entrance.sav index 053eeb5ea..28ee195f1 100644 --- a/tests/entrance.sav +++ b/tests/entrance.sav @@ -10,7 +10,7 @@ "gerudo_fortress": "fast", "bridge": "medallions", "logic_rules": "glitchless", -"reachable_locations": "all", +"reachable_locations": "goals", "bombchus_in_logic": false, "one_item_per_dungeon": false, "trials_random": false, diff --git a/version.py b/version.py index 387a29c57..e1d2481c3 100644 --- a/version.py +++ b/version.py @@ -1 +1 @@ -__version__ = '6.1.1 f.LUM' +__version__ = '6.1.2 f.LUM' From 4b8ea06692a25f67169371358ea2a82f05007e9d Mon Sep 17 00:00:00 2001 From: TreZc0 Date: Sun, 21 Nov 2021 12:53:24 +0100 Subject: [PATCH 14/21] Update package.json and blacklist Node v16 --- GUI/package.json | 5 ++++- GUI/package_release.json | 5 ++++- README.md | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/GUI/package.json b/GUI/package.json index fc25d5f2e..1112d5cbb 100644 --- a/GUI/package.json +++ b/GUI/package.json @@ -1,7 +1,7 @@ { "name": "ootr-electron-gui", "description": "GUI for Ocarina of Time Randomizer", - "version": "6.0.0", + "version": "6.1.0", "homepage": "https://www.ootrandomizer.com", "author": "ZeldaSpeedRuns ", "main": "electron/dist/main.js", @@ -82,6 +82,9 @@ "internetEnabled": true } }, + "engines": { + "node": ">=10 <=15" + }, "dependencies": { "commander": "2.20.0", "electron-window-state": "5.0.3", diff --git a/GUI/package_release.json b/GUI/package_release.json index daf874dfd..3f7165625 100644 --- a/GUI/package_release.json +++ b/GUI/package_release.json @@ -1,7 +1,7 @@ { "name": "ootr-electron-gui", "description": "GUI for Ocarina of Time Randomizer", - "version": "6.0.0", + "version": "6.1.0", "homepage": "https://www.ootrandomizer.com", "author": "ZeldaSpeedRuns ", "main": "electron/dist/main.js", @@ -83,6 +83,9 @@ "internetEnabled": true } }, + "engines": { + "node": ">=10 <=15" + }, "dependencies": { "commander": "2.20.0", "electron-window-state": "5.0.3", diff --git a/README.md b/README.md index 8a3bb97ce..850bf7904 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ https://ootrandomizer.com If you wish to run the script raw, clone this repository and either run ```Gui.py``` for a graphical interface or ```OoTRandomizer.py``` for the command line version. They both require Python 3.6+. This will be fully featured, but the seeds you generate will have different random factors than the bundled release. -To use the GUI, [NodeJS](https://nodejs.org/download/release/v14.15.1/) (v14, with npm) will additionally need to be installed. +To use the GUI, [NodeJS](https://nodejs.org/download/release/v14.15.1/) (v14, with npm) will additionally need to be installed. NodeJS v16+ is currently not supported. The first time ```Gui.py``` is run it will need to install necessary components, which could take a few minutes. Subsequent instances will run much quicker. Built-in WAD injection is only supported on the website. To create a WAD from a seed created locally, either use [gzinject](https://github.com/krimtonz/gzinject/tree/0.2.0) or output a patch file and run that through the website. From 616eede2a4512156658d891b381ae776f4fe9471 Mon Sep 17 00:00:00 2001 From: ETR_BTF Date: Sun, 21 Nov 2021 20:06:34 +0100 Subject: [PATCH 15/21] fix incorrect category --- LocationList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LocationList.py b/LocationList.py index c6d16d4f8..82a64ddb6 100644 --- a/LocationList.py +++ b/LocationList.py @@ -165,7 +165,7 @@ def shop_address(shop_id, shelf_id): ("HC GS Storms Grotto", ("GS Token", 0x0E, 0x02, None, 'Gold Skulltula Token', ("Hyrule Castle", "Skulltulas", "Grottos"))), # Lon Lon Ranch - ("LLR Talons Chickens", ("NPC", 0x4C, 0x14, None, 'Bottle with Milk', ("Lon Lon Ranch", "Kakariko", "Minigames"))), + ("LLR Talons Chickens", ("NPC", 0x4C, 0x14, None, 'Bottle with Milk', ("Lon Lon Ranch", "Minigames"))), ("LLR Freestanding PoH", ("Collectable", 0x4C, 0x01, None, 'Piece of Heart', ("Lon Lon Ranch",))), ("LLR Deku Scrub Grotto Left", ("GrottoNPC", 0xFC, 0x30, None, 'Buy Deku Nut (5)', ("Lon Lon Ranch", "Deku Scrub", "Grottos"))), ("LLR Deku Scrub Grotto Center", ("GrottoNPC", 0xFC, 0x33, None, 'Buy Deku Seeds (30)', ("Lon Lon Ranch", "Deku Scrub", "Grottos"))), From a68028432d89d29c22945ad6c3474d2534b6e210 Mon Sep 17 00:00:00 2001 From: ETR_BTF Date: Sun, 21 Nov 2021 20:07:25 +0100 Subject: [PATCH 16/21] proper capitalization otherwise it's moved to the bottom of the list --- LocationList.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LocationList.py b/LocationList.py index 82a64ddb6..0ce8dc0bf 100644 --- a/LocationList.py +++ b/LocationList.py @@ -368,8 +368,8 @@ def shop_address(shop_id, shelf_id): ("Colossus GS Hill", ("GS Token", 0x15, 0x04, None, 'Gold Skulltula Token', ("Desert Colossus", "Skulltulas",))), # Outside Ganon's Castle - ("OGC Great Fairy Reward", ("Cutscene", 0xFF, 0x15, None, 'Double Defense', ("outside Ganon's Castle", "Market", "Fairies"))), - ("OGC GS", ("GS Token", 0x0E, 0x01, None, 'Gold Skulltula Token', ("outside Ganon's Castle", "Skulltulas",))), + ("OGC Great Fairy Reward", ("Cutscene", 0xFF, 0x15, None, 'Double Defense', ("Outside Ganon's Castle", "Market", "Fairies"))), + ("OGC GS", ("GS Token", 0x0E, 0x01, None, 'Gold Skulltula Token', ("Outside Ganon's Castle", "Skulltulas",))), ## Dungeons # Deku Tree vanilla From 34cc4afe03f142de01c749a377a9ca96d6c7888b Mon Sep 17 00:00:00 2001 From: ETR_BTF Date: Sun, 21 Nov 2021 20:08:06 +0100 Subject: [PATCH 17/21] remove unnecessary category --- LocationList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LocationList.py b/LocationList.py index 0ce8dc0bf..8554872d2 100644 --- a/LocationList.py +++ b/LocationList.py @@ -72,7 +72,7 @@ def shop_address(shop_id, shelf_id): ("KF Midos Bottom Right Chest", ("Chest", 0x28, 0x03, None, 'Recovery Heart', ("Kokiri Forest", "Forest",))), ("KF Kokiri Sword Chest", ("Chest", 0x55, 0x00, None, 'Kokiri Sword', ("Kokiri Forest", "Forest",))), ("KF Storms Grotto Chest", ("Chest", 0x3E, 0x0C, None, 'Rupees (20)', ("Kokiri Forest", "Forest", "Grottos"))), - ("KF Links House Cow", ("NPC", 0x34, 0x15, None, 'Milk', ("KF Links House", "Forest", "Cow", "Minigames"))), + ("KF Links House Cow", ("NPC", 0x34, 0x15, None, 'Milk', ("Kokiri Forest", "Forest", "Cow", "Minigames"))), ("KF GS Know It All House", ("GS Token", 0x0C, 0x02, None, 'Gold Skulltula Token', ("Kokiri Forest", "Skulltulas",))), ("KF GS Bean Patch", ("GS Token", 0x0C, 0x01, None, 'Gold Skulltula Token', ("Kokiri Forest", "Skulltulas",))), ("KF GS House of Twins", ("GS Token", 0x0C, 0x04, None, 'Gold Skulltula Token', ("Kokiri Forest", "Skulltulas",))), From 7f9021abb6e73cb0eb7b84b7d687d8381878038a Mon Sep 17 00:00:00 2001 From: TreZ Date: Mon, 22 Nov 2021 17:51:10 +0100 Subject: [PATCH 18/21] add warning in GUI for Dev updates and change update branch for release --- GUI/src/app/@theme/components/footer/footer.component.ts | 2 +- GUI/src/app/providers/GUIGlobal.ts | 2 +- version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GUI/src/app/@theme/components/footer/footer.component.ts b/GUI/src/app/@theme/components/footer/footer.component.ts index 8e5e45365..4efa0a6b6 100644 --- a/GUI/src/app/@theme/components/footer/footer.component.ts +++ b/GUI/src/app/@theme/components/footer/footer.component.ts @@ -61,7 +61,7 @@ export class FooterComponent { promptUpdate() { this.dialogService.open(ConfirmationWindow, { - autoFocus: true, closeOnBackdropClick: true, closeOnEsc: true, hasBackdrop: true, hasScroll: false, context: { dialogHeader: "New Version Available!", dialogMessage: "You are on version " + this.localVersion + ", and the latest is version " + this.remoteVersion + ". Do you want to download the latest version now?" } + autoFocus: true, closeOnBackdropClick: true, closeOnEsc: true, hasBackdrop: true, hasScroll: false, context: { dialogHeader: "New Version Available!", dialogMessage: "You are using version " + this.localVersion + ", and the latest is version " + this.remoteVersion + ". Do you want to download the latest version now?" + ((this.remoteVersion.includes("Release")) ? "" : " (Note that you are using a development build and therefore will have to redownload and compile the source off GitHub yourself)") } }).onClose.subscribe(confirmed => { if (confirmed) { diff --git a/GUI/src/app/providers/GUIGlobal.ts b/GUI/src/app/providers/GUIGlobal.ts index 86b89ccd2..685015eed 100644 --- a/GUI/src/app/providers/GUIGlobal.ts +++ b/GUI/src/app/providers/GUIGlobal.ts @@ -515,7 +515,7 @@ export class GUIGlobal { this.globalEmitter.emit({ name: "local_version_checked", version: res }); - let branch = res.includes("Release") ? "master" : "Dev"; + let branch = res.includes("Release") ? "release" : "Dev"; var remoteFile = await this.http.get("https://raw.githubusercontent.com/TestRunnerSRL/OoT-Randomizer/" + branch + "/version.py", { responseType: "text" }).toPromise(); let remoteVersion = remoteFile.substr(remoteFile.indexOf("'") + 1); diff --git a/version.py b/version.py index e1d2481c3..fa832b318 100644 --- a/version.py +++ b/version.py @@ -1 +1 @@ -__version__ = '6.1.2 f.LUM' +__version__ = '6.1.4 f.LUM' From ca093cfe63ae314af83bbae9a447a8605a653b39 Mon Sep 17 00:00:00 2001 From: mracsys Date: Mon, 22 Nov 2021 21:40:24 -0500 Subject: [PATCH 19/21] comments from code review for always-barren exclusions --- Hints.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Hints.py b/Hints.py index e54756e0f..6b43b44b1 100644 --- a/Hints.py +++ b/Hints.py @@ -1060,7 +1060,7 @@ def buildWorldGossipHints(spoiler, world, checkedLocations=None): raise Exception('User-provided item hints were requested, but copies per named-item hint is zero') else: for i in range(0, len(world.named_item_pool)): - hint = get_specific_item_hint(spoiler, world, set.union(checkedLocations, checkedAlwaysLocations)) + hint = get_specific_item_hint(spoiler, world, checkedLocations | checkedAlwaysLocations) if hint: gossip_text, location = hint place_ok = add_hint(spoiler, world, stoneGroups, gossip_text, hint_dist['named-item'][1], location) @@ -1117,12 +1117,12 @@ def buildWorldGossipHints(spoiler, world, checkedLocations=None): except IndexError: raise Exception('Not enough valid hints to fill gossip stone locations.') - allCheckedLocations = set.union(checkedLocations, checkedAlwaysLocations) + allCheckedLocations = checkedLocations | checkedAlwaysLocations if hint_type == 'barren': hint = hint_func[hint_type](spoiler, world, checkedLocations) else: hint = hint_func[hint_type](spoiler, world, allCheckedLocations) - checkedLocations.update(allCheckedLocations.difference(set.union(checkedLocations, checkedAlwaysLocations))) + checkedLocations.update(allCheckedLocations - checkedAlwaysLocations) if hint == None: index = hint_types.index(hint_type) From 3f1c1deea7489a3314238cd17bd69909e869c385 Mon Sep 17 00:00:00 2001 From: Zannick Date: Mon, 22 Nov 2021 21:59:58 -0800 Subject: [PATCH 20/21] Fix the max progression count for tokens. --- README.md | 1 + World.py | 3 ++- version.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 850bf7904..cebf5e533 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ do that. * Fix seed generation for multiworld with random trials. * Fix seed generation for All Goals Reachable. +* Fix a minor optimization for counting needed Skulltula Tokens. ### 6.1 diff --git a/World.py b/World.py index 3f0be3e26..49deba5e7 100644 --- a/World.py +++ b/World.py @@ -192,8 +192,9 @@ def __init__(self, id, settings, resolveRandomizedSettings=True): max_tokens = max(max_tokens, self.settings.ganon_bosskey_tokens) tokens = [50, 40, 30, 20, 10] for t in tokens: - if f'{t} Gold Skulltula Reward' not in self.settings.disabled_locations: + if f'Kak {t} Gold Skulltula Reward' not in self.settings.disabled_locations: max_tokens = max(max_tokens, t) + break self.max_progressions['Gold Skulltula Token'] = max_tokens # Additional Ruto's Letter become Bottle, so we may have to collect two. self.max_progressions['Rutos Letter'] = 2 diff --git a/version.py b/version.py index fa832b318..062a91b43 100644 --- a/version.py +++ b/version.py @@ -1 +1 @@ -__version__ = '6.1.4 f.LUM' +__version__ = '6.1.5 f.LUM' From 3eaedc5973d6c6fd68c346dfb85e3331221b0482 Mon Sep 17 00:00:00 2001 From: TreZ Date: Thu, 25 Nov 2021 19:16:43 +0100 Subject: [PATCH 21/21] remove sglive preset, update to 6.2.1 dev --- data/presets_default.json | 108 -------------------------------------- version.py | 2 +- 2 files changed, 1 insertion(+), 109 deletions(-) diff --git a/data/presets_default.json b/data/presets_default.json index a5aa5dbc2..46cdcd8c9 100644 --- a/data/presets_default.json +++ b/data/presets_default.json @@ -1158,113 +1158,5 @@ "damage_multiplier": "normal", "starting_tod": "default", "starting_age": "adult" - }, - "SpeedGamingLive 2021": { - "world_count": 1, - "create_spoiler": true, - "randomize_settings": false, - "open_forest": "open", - "open_kakariko": "open", - "open_door_of_time": true, - "zora_fountain": "closed", - "gerudo_fortress": "fast", - "bridge": "stones", - "bridge_stones": 3, - "triforce_hunt": false, - "logic_rules": "glitchless", - "reachable_locations": "all", - "bombchus_in_logic": false, - "one_item_per_dungeon": false, - "trials_random": false, - "trials": 0, - "skip_child_zelda": true, - "no_escape_sequence": true, - "no_guard_stealth": true, - "no_epona_race": true, - "skip_some_minigame_phases": true, - "useful_cutscenes": false, - "complete_mask_quest": false, - "fast_chests": true, - "logic_no_night_tokens_without_suns_song": false, - "free_scarecrow": false, - "fast_bunny_hood": true, - "start_with_rupees": false, - "start_with_consumables": true, - "starting_hearts": 3, - "chicken_count_random": false, - "chicken_count": 7, - "big_poe_count_random": false, - "big_poe_count": 1, - "shuffle_kokiri_sword": true, - "shuffle_ocarinas": false, - "shuffle_gerudo_card": false, - "shuffle_song_items": "song", - "shuffle_cows": false, - "shuffle_beans": false, - "shuffle_medigoron_carpet_salesman": false, - "shuffle_interior_entrances": "off", - "shuffle_grotto_entrances": false, - "shuffle_dungeon_entrances": false, - "shuffle_overworld_entrances": false, - "owl_drops": false, - "warp_songs": false, - "spawn_positions": true, - "shuffle_scrubs": "off", - "shopsanity": "off", - "tokensanity": "off", - "shuffle_mapcompass": "startwith", - "shuffle_smallkeys": "dungeon", - "shuffle_hideoutkeys": "vanilla", - "shuffle_bosskeys": "dungeon", - "shuffle_ganon_bosskey": "on_lacs", - "lacs_condition": "vanilla", - "enhance_map_compass": false, - "mq_dungeons_random": false, - "mq_dungeons": 0, - "disabled_locations": [ - "Deku Theater Mask of Truth" - ], - "allowed_tricks": [ - "logic_fewer_tunic_requirements", - "logic_grottos_without_agony", - "logic_child_deadhand", - "logic_man_on_roof", - "logic_dc_jump", - "logic_rusted_switches", - "logic_windmill_poh", - "logic_crater_bean_poh_with_hovers", - "logic_forest_vines", - "logic_lens_botw", - "logic_lens_castle", - "logic_lens_gtg", - "logic_lens_shadow", - "logic_lens_shadow_back", - "logic_lens_spirit" - ], - "logic_earliest_adult_trade": "prescription", - "logic_latest_adult_trade": "claim_check", - "starting_equipment": [ - "deku_shield" - ], - "starting_items": [ - "ocarina" - ], - "starting_songs": [], - "ocarina_songs": false, - "correct_chest_sizes": true, - "clearer_hints": true, - "no_collectible_hearts": false, - "hints": "always", - "hint_dist": "league", - "item_hints": [], - "hint_dist_user": {}, - "text_shuffle": "none", - "misc_hints": true, - "ice_trap_appearance": "junk_only", - "junk_ice_traps": "off", - "item_pool_value": "balanced", - "damage_multiplier": "normal", - "starting_tod": "default", - "starting_age": "random" } } \ No newline at end of file diff --git a/version.py b/version.py index 64199d457..ec469840e 100644 --- a/version.py +++ b/version.py @@ -1 +1 @@ -__version__ = '6.1.8 f.LUM' +__version__ = '6.2.1 f.LUM'