Skip to content

Commit 2b5db17

Browse files
committed
Full Release: Version 6.2.0
2 parents f0d4447 + 3eaedc5 commit 2b5db17

18 files changed

+107
-173
lines changed

Fill.py

+13-18
Original file line numberDiff line numberDiff line change
@@ -388,37 +388,32 @@ def fill_restrictive(window, worlds, base_search, locations, itempool, count=-1)
388388
max_search.collect_locations()
389389

390390
# perform_access_check checks location reachability
391-
if worlds[0].settings.reachable_locations == 'all':
392-
perform_access_check = True
393-
extra_location_checks = []
394-
elif worlds[0].settings.reachable_locations == 'goals':
395-
# for All Goals Reachable, we have to track whether any goal items have been placed,
396-
# since we then have to start checking their reachability.
397-
perform_access_check = item_to_place.goalitem or not max_search.can_beat_game(scan_for_items=False)
398-
extra_location_checks = [location for world in worlds for location in world.get_filled_locations() if location.item.goalitem]
391+
if worlds[0].check_beatable_only:
392+
if worlds[0].settings.reachable_locations == 'goals':
393+
# If this item is required for a goal, it must be placed somewhere reachable.
394+
# We also need to check to make sure the game is beatable, since custom goals might not imply that.
395+
predicate = lambda state: state.won() and state.has_all_item_goals()
396+
else:
397+
# If the game is not beatable without this item, it must be placed somewhere reachable.
398+
predicate = State.won
399+
perform_access_check = not max_search.can_beat_game(scan_for_items=False, predicate=predicate)
399400
else:
400-
# if any world can not longer be beatable with the remaining items
401-
# then we must check for reachability no matter what.
402-
# This way the reachability test is monotonic. If we were to later
403-
# stop checking, then we could place an item needed in one world
404-
# in an unreachable place in another world.
405-
# scan_for_items would cause an unnecessary copy+collect
406-
perform_access_check = not max_search.can_beat_game(scan_for_items=False)
407-
extra_location_checks = []
401+
# All items must be placed somewhere reachable.
402+
perform_access_check = True
408403

409404
# find a location that the item can be placed. It must be a valid location
410405
# in the world we are placing it (possibly checking for reachability)
411406
spot_to_fill = None
412407
for location in l2cations:
413-
if location.can_fill(max_search.state_list[location.world.id], item_to_place, perform_access_check, extra_location_checks):
408+
if location.can_fill(max_search.state_list[location.world.id], item_to_place, perform_access_check):
414409
# for multiworld, make it so that the location is also reachable
415410
# in the world the item is for. This is to prevent early restrictions
416411
# in one world being placed late in another world. If this is not
417412
# done then one player may be waiting a long time for other players.
418413
if location.world.id != item_to_place.world.id:
419414
try:
420415
source_location = item_to_place.world.get_location(location.name)
421-
if not source_location.can_fill(max_search.state_list[item_to_place.world.id], item_to_place, perform_access_check, extra_location_checks):
416+
if not source_location.can_fill(max_search.state_list[item_to_place.world.id], item_to_place, perform_access_check):
422417
# location wasn't reachable in item's world, so skip it
423418
continue
424419
except KeyError:

GUI/package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "ootr-electron-gui",
33
"description": "GUI for Ocarina of Time Randomizer",
4-
"version": "6.0.0",
4+
"version": "6.2.0",
55
"homepage": "https://www.ootrandomizer.com",
66
"author": "ZeldaSpeedRuns <[email protected]>",
77
"main": "electron/dist/main.js",
@@ -82,6 +82,9 @@
8282
"internetEnabled": true
8383
}
8484
},
85+
"engines": {
86+
"node": ">=10 <=15"
87+
},
8588
"dependencies": {
8689
"commander": "2.20.0",
8790
"electron-window-state": "5.0.3",

GUI/package_release.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "ootr-electron-gui",
33
"description": "GUI for Ocarina of Time Randomizer",
4-
"version": "6.0.0",
4+
"version": "6.2.0",
55
"homepage": "https://www.ootrandomizer.com",
66
"author": "ZeldaSpeedRuns <[email protected]>",
77
"main": "electron/dist/main.js",
@@ -83,6 +83,9 @@
8383
"internetEnabled": true
8484
}
8585
},
86+
"engines": {
87+
"node": ">=10 <=15"
88+
},
8689
"dependencies": {
8790
"commander": "2.20.0",
8891
"electron-window-state": "5.0.3",

GUI/src/app/@theme/components/footer/footer.component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export class FooterComponent {
6161

6262
promptUpdate() {
6363
this.dialogService.open(ConfirmationWindow, {
64-
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?" }
64+
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)") }
6565
}).onClose.subscribe(confirmed => {
6666

6767
if (confirmed) {

GUI/src/app/providers/GUIGlobal.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ export class GUIGlobal {
515515

516516
this.globalEmitter.emit({ name: "local_version_checked", version: res });
517517

518-
let branch = res.includes("Release") ? "master" : "Dev";
518+
let branch = res.includes("Release") ? "release" : "Dev";
519519
var remoteFile = await this.http.get("https://raw.githubusercontent.com/TestRunnerSRL/OoT-Randomizer/" + branch + "/version.py", { responseType: "text" }).toPromise();
520520

521521
let remoteVersion = remoteFile.substr(remoteFile.indexOf("'") + 1);

Goals.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def __init__(self, world, name, hint_text, color, items=None, locations=None, lo
4040
self._item_cache = {}
4141

4242
def copy(self):
43-
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)
43+
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)
4444
return new_goal
4545

4646
def get_item(self, item):
@@ -98,7 +98,7 @@ def get_goal(self, goal):
9898
def is_beaten(self, search):
9999
# if the category requirements are already satisfied by starting items (such as Links Pocket),
100100
# do not generate hints for other goals in the category
101-
starting_goals = search.can_beat_goals_fast({ self.name: self })
101+
starting_goals = search.beatable_goals_fast({ self.name: self })
102102
return all(map(lambda s: len(starting_goals[self.name]['stateReverse'][s.world.id]) >= self.minimum_goals, search.state_list))
103103

104104

@@ -184,7 +184,7 @@ def update_goal_items(spoiler):
184184
reachable_goals = {}
185185
# Goals are changed for beatable-only accessibility per-world
186186
category.update_reachable_goals(search, full_search)
187-
reachable_goals = full_search.can_beat_goals_fast({ cat_name: category }, cat_world.id)
187+
reachable_goals = full_search.beatable_goals_fast({ cat_name: category }, cat_world.id)
188188
identified_locations = search_goals({ cat_name: category }, reachable_goals, search, priority_locations, all_locations, item_locations, always_locations, _maybe_set_light_arrows)
189189
# Multiworld can have all goals for one player's bridge entirely
190190
# locked by another player's bridge. Therefore, we can't assume
@@ -208,7 +208,7 @@ def update_goal_items(spoiler):
208208
full_search.collect_locations()
209209
for cat_name, category in worlds[0].unlocked_goal_categories.items():
210210
category.update_reachable_goals(search, full_search)
211-
reachable_goals = full_search.can_beat_goals_fast(worlds[0].unlocked_goal_categories)
211+
reachable_goals = full_search.beatable_goals_fast(worlds[0].unlocked_goal_categories)
212212
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)
213213
required_locations.update(identified_locations)
214214
woth_locations = list(required_locations['way of the hero'])
@@ -300,7 +300,7 @@ def search_goals(categories, reachable_goals, search, priority_locations, all_lo
300300
location.item = None
301301
# copies state! This is very important as we're in the middle of a search
302302
# already, but beneficially, has search it can start from
303-
valid_goals = search.can_beat_goals(categories)
303+
valid_goals = search.beatable_goals(categories)
304304
for cat_name, category in categories.items():
305305
# Exit early if no goals are beatable with category locks
306306
if category.name in reachable_goals and reachable_goals[category.name]:

Hints.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,7 @@ def buildWorldGossipHints(spoiler, world, checkedLocations=None):
896896

897897
if checkedLocations is None:
898898
checkedLocations = set()
899+
checkedAlwaysLocations = set()
899900

900901
stoneIDs = list(gossipLocations.keys())
901902

@@ -1020,7 +1021,7 @@ def buildWorldGossipHints(spoiler, world, checkedLocations=None):
10201021
alwaysLocations = getHintGroup('always', world)
10211022
for hint in alwaysLocations:
10221023
location = world.get_location(hint.name)
1023-
checkedLocations.add(hint.name)
1024+
checkedAlwaysLocations.add(hint.name)
10241025
if location.item.name in bingoBottlesForHints and world.settings.hint_dist == 'bingo':
10251026
always_item = 'Bottle'
10261027
else:
@@ -1059,7 +1060,7 @@ def buildWorldGossipHints(spoiler, world, checkedLocations=None):
10591060
raise Exception('User-provided item hints were requested, but copies per named-item hint is zero')
10601061
else:
10611062
for i in range(0, len(world.named_item_pool)):
1062-
hint = get_specific_item_hint(spoiler, world, checkedLocations)
1063+
hint = get_specific_item_hint(spoiler, world, checkedLocations | checkedAlwaysLocations)
10631064
if hint:
10641065
gossip_text, location = hint
10651066
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):
11161117
except IndexError:
11171118
raise Exception('Not enough valid hints to fill gossip stone locations.')
11181119

1119-
hint = hint_func[hint_type](spoiler, world, checkedLocations)
1120+
allCheckedLocations = checkedLocations | checkedAlwaysLocations
1121+
if hint_type == 'barren':
1122+
hint = hint_func[hint_type](spoiler, world, checkedLocations)
1123+
else:
1124+
hint = hint_func[hint_type](spoiler, world, allCheckedLocations)
1125+
checkedLocations.update(allCheckedLocations - checkedAlwaysLocations)
11201126

11211127
if hint == None:
11221128
index = hint_types.index(hint_type)

Location.py

+6-9
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,14 @@ def set_rule(self, lambda_rule):
6868
self.access_rules = [lambda_rule]
6969

7070

71-
def can_fill(self, state, item, check_access=True, extra_location_checks=()):
71+
def can_fill(self, state, item, check_access=True):
7272
if self.minor_only and item.majoritem:
7373
return False
74-
if self.is_disabled() or not self.can_fill_fast(item) or (check_access and not state.search.spot_access(self, 'either')):
75-
return False
76-
if not extra_location_checks:
77-
return True
78-
search_with_this = state.search.copy()
79-
search_with_this.collect(item)
80-
search_with_this.collect_locations(list(chain(search_with_this.progression_locations(), extra_location_checks)))
81-
return all(map(search_with_this.visited, extra_location_checks))
74+
return (
75+
not self.is_disabled() and
76+
self.can_fill_fast(item) and
77+
(not check_access or state.search.spot_access(self, 'either'))
78+
)
8279

8380

8481
def can_fill_fast(self, item, manual=False):

LocationList.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def shop_address(shop_id, shelf_id):
7272
("KF Midos Bottom Right Chest", ("Chest", 0x28, 0x03, None, 'Recovery Heart', ("Kokiri Forest", "Forest",))),
7373
("KF Kokiri Sword Chest", ("Chest", 0x55, 0x00, None, 'Kokiri Sword', ("Kokiri Forest", "Forest",))),
7474
("KF Storms Grotto Chest", ("Chest", 0x3E, 0x0C, None, 'Rupees (20)', ("Kokiri Forest", "Forest", "Grottos"))),
75-
("KF Links House Cow", ("NPC", 0x34, 0x15, None, 'Milk', ("KF Links House", "Forest", "Cow", "Minigames"))),
75+
("KF Links House Cow", ("NPC", 0x34, 0x15, None, 'Milk', ("Kokiri Forest", "Forest", "Cow", "Minigames"))),
7676
("KF GS Know It All House", ("GS Token", 0x0C, 0x02, None, 'Gold Skulltula Token', ("Kokiri Forest", "Skulltulas",))),
7777
("KF GS Bean Patch", ("GS Token", 0x0C, 0x01, None, 'Gold Skulltula Token', ("Kokiri Forest", "Skulltulas",))),
7878
("KF GS House of Twins", ("GS Token", 0x0C, 0x04, None, 'Gold Skulltula Token', ("Kokiri Forest", "Skulltulas",))),
@@ -165,7 +165,7 @@ def shop_address(shop_id, shelf_id):
165165
("HC GS Storms Grotto", ("GS Token", 0x0E, 0x02, None, 'Gold Skulltula Token', ("Hyrule Castle", "Skulltulas", "Grottos"))),
166166

167167
# Lon Lon Ranch
168-
("LLR Talons Chickens", ("NPC", 0x4C, 0x14, None, 'Bottle with Milk', ("Lon Lon Ranch", "Kakariko", "Minigames"))),
168+
("LLR Talons Chickens", ("NPC", 0x4C, 0x14, None, 'Bottle with Milk', ("Lon Lon Ranch", "Minigames"))),
169169
("LLR Freestanding PoH", ("Collectable", 0x4C, 0x01, None, 'Piece of Heart', ("Lon Lon Ranch",))),
170170
("LLR Deku Scrub Grotto Left", ("GrottoNPC", 0xFC, 0x30, None, 'Buy Deku Nut (5)', ("Lon Lon Ranch", "Deku Scrub", "Grottos"))),
171171
("LLR Deku Scrub Grotto Center", ("GrottoNPC", 0xFC, 0x33, None, 'Buy Deku Seeds (30)', ("Lon Lon Ranch", "Deku Scrub", "Grottos"))),
@@ -368,8 +368,8 @@ def shop_address(shop_id, shelf_id):
368368
("Colossus GS Hill", ("GS Token", 0x15, 0x04, None, 'Gold Skulltula Token', ("Desert Colossus", "Skulltulas",))),
369369

370370
# Outside Ganon's Castle
371-
("OGC Great Fairy Reward", ("Cutscene", 0xFF, 0x15, None, 'Double Defense', ("outside Ganon's Castle", "Market", "Fairies"))),
372-
("OGC GS", ("GS Token", 0x0E, 0x01, None, 'Gold Skulltula Token', ("outside Ganon's Castle", "Skulltulas",))),
371+
("OGC Great Fairy Reward", ("Cutscene", 0xFF, 0x15, None, 'Double Defense', ("Outside Ganon's Castle", "Market", "Fairies"))),
372+
("OGC GS", ("GS Token", 0x0E, 0x01, None, 'Gold Skulltula Token', ("Outside Ganon's Castle", "Skulltulas",))),
373373

374374
## Dungeons
375375
# Deku Tree vanilla

README.md

+28-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This is a randomizer for _The Legend of Zelda: Ocarina of Time_ for the Nintendo
88
* [Settings](#settings)
99
* [Known Issues](#known-issues)
1010
* [Changelog](#changelog)
11+
* [6.2](#62)
1112
* [6.1](#61)
1213
* [6.0](#60)
1314
* [5.2](#52)
@@ -24,7 +25,7 @@ https://ootrandomizer.com
2425
If you wish to run the script raw, clone this repository and either run ```Gui.py``` for a
2526
graphical interface or ```OoTRandomizer.py``` for the command line version. They both require Python 3.6+. This will be fully featured,
2627
but the seeds you generate will have different random factors than the bundled release.
27-
To use the GUI, [NodeJS](https://nodejs.org/download/release/v14.15.1/) (v14, with npm) will additionally need to be installed.
28+
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.
2829
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.
2930
Built-in WAD injection is only supported on the website. To create a WAD from a seed created locally, either use
3031
[gzinject](https://github.com/krimtonz/gzinject/tree/0.2.0) or output a patch file and run that through the website.
@@ -99,6 +100,32 @@ do that.
99100

100101
## Changelog
101102

103+
### 6.2
104+
105+
#### Bug fixes
106+
107+
* Fix seed generation for multiworld with random trials.
108+
* Fix seed generation for All Goals Reachable.
109+
* Fix a minor optimization for counting needed Skulltula Tokens.
110+
* Fix some erroneous category tags for locations.
111+
112+
#### Other changes
113+
* Allow foolish hints to apply even if an area has an Always hint (but no other types).
114+
* Renamed setting `Enable Useful Cutscenes` to `Enable Specific Glitch-Useful Cutscenes` for clarity.
115+
116+
### 6.1
117+
118+
#### Bug fixes
119+
120+
* Fix seed generation for multiworld with random trials.
121+
* Fix seed generation for All Goals Reachable.
122+
* Fix a minor optimization for counting needed Skulltula Tokens.
123+
* Fix some erroneous category tags for locations.
124+
125+
#### Other changes
126+
* Allow foolish hints to apply even if an area has an Always hint (but no other types).
127+
* Renamed setting `Enable Useful Cutscenes` to `Enable Specific Glitch-Useful Cutscenes` for clarity.
128+
102129
### 6.1
103130

104131
#### New Features

0 commit comments

Comments
 (0)