Ocarina of Time: long-awaited bugfixes (#2344)
- Added location name groups, so you can make your entire Water Temple priority to annoy everyone else - Significant improvement to ER generation success rate (~80% to >99%) - Changed `adult_trade_start` option to a choice option instead of a list (this shouldn't actually break any YAMLs though, due to the lesser-known property of lists parsing as a uniformly-weighted choice) - Major improvements to the option tooltips where needed. (Possibly too much text now) - Changed default hint distribution to `async` to help people's generation times. The tooltip explains that it removes WOTH hints so people hopefully don't get tripped up. - Makes stick and nut capacity upgrades useful items - Added shop prices and required trials to spoiler log - Added Cojiro to adult trade item group, because it had been forgotten previously - Fixed size-modified chests not being moved properly due to trap appearance changing the size - Fixed Thieves Hideout keyring not being allowed in start inventory - Fixed hint generation not accurately flagging barren locations on certain dungeon item shuffle settings - Fixed bug where you could plando arbitrarily-named items into the world, breaking everything
This commit is contained in:
parent
50244342d9
commit
724999fc43
|
@ -350,7 +350,7 @@ def generate_itempool(ootworld):
|
||||||
ootworld.itempool = [ootworld.create_item(item) for item in pool]
|
ootworld.itempool = [ootworld.create_item(item) for item in pool]
|
||||||
for (location_name, item) in placed_items.items():
|
for (location_name, item) in placed_items.items():
|
||||||
location = world.get_location(location_name, player)
|
location = world.get_location(location_name, player)
|
||||||
location.place_locked_item(ootworld.create_item(item))
|
location.place_locked_item(ootworld.create_item(item, allow_arbitrary_name=True))
|
||||||
|
|
||||||
|
|
||||||
def get_pool_core(world):
|
def get_pool_core(world):
|
||||||
|
@ -675,7 +675,7 @@ def get_pool_core(world):
|
||||||
world.remove_from_start_inventory.append('Scarecrow Song')
|
world.remove_from_start_inventory.append('Scarecrow Song')
|
||||||
|
|
||||||
if world.no_epona_race:
|
if world.no_epona_race:
|
||||||
world.multiworld.push_precollected(world.create_item('Epona'))
|
world.multiworld.push_precollected(world.create_item('Epona', allow_arbitrary_name=True))
|
||||||
world.remove_from_start_inventory.append('Epona')
|
world.remove_from_start_inventory.append('Epona')
|
||||||
|
|
||||||
if world.shuffle_smallkeys == 'vanilla':
|
if world.shuffle_smallkeys == 'vanilla':
|
||||||
|
|
|
@ -2,6 +2,8 @@ from enum import Enum
|
||||||
from .LocationList import location_table
|
from .LocationList import location_table
|
||||||
from BaseClasses import Location
|
from BaseClasses import Location
|
||||||
|
|
||||||
|
non_indexed_location_types = {'Boss', 'Event', 'Drop', 'HintStone', 'Hint'}
|
||||||
|
|
||||||
location_id_offset = 67000
|
location_id_offset = 67000
|
||||||
locnames_pre_70 = {
|
locnames_pre_70 = {
|
||||||
"Gift from Sages",
|
"Gift from Sages",
|
||||||
|
@ -18,7 +20,7 @@ new_name_order = sorted(location_table.keys(),
|
||||||
else 0)
|
else 0)
|
||||||
|
|
||||||
location_name_to_id = {name: (location_id_offset + index) for (index, name) in enumerate(new_name_order)
|
location_name_to_id = {name: (location_id_offset + index) for (index, name) in enumerate(new_name_order)
|
||||||
if location_table[name][0] not in {'Boss', 'Event', 'Drop', 'HintStone', 'Hint'}}
|
if location_table[name][0] not in non_indexed_location_types}
|
||||||
|
|
||||||
class DisableType(Enum):
|
class DisableType(Enum):
|
||||||
ENABLED = 0
|
ENABLED = 0
|
||||||
|
@ -83,3 +85,57 @@ def LocationFactory(locations, player: int):
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def build_location_name_groups() -> dict:
|
||||||
|
|
||||||
|
def fix_sing(t) -> tuple:
|
||||||
|
if isinstance(t, str):
|
||||||
|
return (t,)
|
||||||
|
return t
|
||||||
|
|
||||||
|
def rename(d, k1, k2) -> None:
|
||||||
|
d[k2] = d[k1]
|
||||||
|
del d[k1]
|
||||||
|
|
||||||
|
# whoever wrote the location table didn't realize they need to add a comma to mark a singleton as a tuple
|
||||||
|
# so we have to check types unfortunately
|
||||||
|
tags = set()
|
||||||
|
for v in location_table.values():
|
||||||
|
if v[5] is not None:
|
||||||
|
tags.update(fix_sing(v[5]))
|
||||||
|
|
||||||
|
sorted_tags = sorted(list(tags))
|
||||||
|
|
||||||
|
ret = {
|
||||||
|
tag: {k for k, v in location_table.items()
|
||||||
|
if v[5] is not None
|
||||||
|
and tag in fix_sing(v[5])
|
||||||
|
and v[0] not in non_indexed_location_types}
|
||||||
|
for tag in sorted_tags
|
||||||
|
}
|
||||||
|
|
||||||
|
# Delete tags which are a combination of other tags
|
||||||
|
del ret['Death Mountain']
|
||||||
|
del ret['Forest']
|
||||||
|
del ret['Gerudo']
|
||||||
|
del ret['Kakariko']
|
||||||
|
del ret['Market']
|
||||||
|
|
||||||
|
# Delete Vanilla and MQ tags because they are just way too broad
|
||||||
|
del ret['Vanilla']
|
||||||
|
del ret['Master Quest']
|
||||||
|
|
||||||
|
rename(ret, 'Beehive', 'Beehives')
|
||||||
|
rename(ret, 'Cow', 'Cows')
|
||||||
|
rename(ret, 'Crate', 'Crates')
|
||||||
|
rename(ret, 'Deku Scrub', 'Deku Scrubs')
|
||||||
|
rename(ret, 'FlyingPot', 'Flying Pots')
|
||||||
|
rename(ret, 'Freestanding', 'Freestanding Items')
|
||||||
|
rename(ret, 'Pot', 'Pots')
|
||||||
|
rename(ret, 'RupeeTower', 'Rupee Groups')
|
||||||
|
rename(ret, 'SmallCrate', 'Small Crates')
|
||||||
|
rename(ret, 'the Market', 'Market')
|
||||||
|
rename(ret, 'the Graveyard', 'Graveyard')
|
||||||
|
rename(ret, 'the Lost Woods', 'Lost Woods')
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
|
@ -238,7 +238,7 @@ location_table = OrderedDict([
|
||||||
("Market Night Green Rupee Crate 1", ("Crate", 0x21, (0,0,24), None, 'Rupee (1)', ("the Market", "Market", "Crate"))),
|
("Market Night Green Rupee Crate 1", ("Crate", 0x21, (0,0,24), None, 'Rupee (1)', ("the Market", "Market", "Crate"))),
|
||||||
("Market Night Green Rupee Crate 2", ("Crate", 0x21, (0,0,25), None, 'Rupee (1)', ("the Market", "Market", "Crate"))),
|
("Market Night Green Rupee Crate 2", ("Crate", 0x21, (0,0,25), None, 'Rupee (1)', ("the Market", "Market", "Crate"))),
|
||||||
("Market Night Green Rupee Crate 3", ("Crate", 0x21, (0,0,26), None, 'Rupee (1)', ("the Market", "Market", "Crate"))),
|
("Market Night Green Rupee Crate 3", ("Crate", 0x21, (0,0,26), None, 'Rupee (1)', ("the Market", "Market", "Crate"))),
|
||||||
("Market Dog Lady House Crate", ("Crate", 0x35, (0,0,3), None, 'Rupees (5)', ("Market", "Market", "Crate"))),
|
("Market Dog Lady House Crate", ("Crate", 0x35, (0,0,3), None, 'Rupees (5)', ("the Market", "Market", "Crate"))),
|
||||||
("Market Guard House Child Crate", ("Crate", 0x4D, (0,0,6), None, 'Rupee (1)', ("the Market", "Market", "Crate"))),
|
("Market Guard House Child Crate", ("Crate", 0x4D, (0,0,6), None, 'Rupee (1)', ("the Market", "Market", "Crate"))),
|
||||||
("Market Guard House Child Pot 1", ("Pot", 0x4D, (0,0,9), None, 'Rupee (1)', ("the Market", "Market", "Pot"))),
|
("Market Guard House Child Pot 1", ("Pot", 0x4D, (0,0,9), None, 'Rupee (1)', ("the Market", "Market", "Pot"))),
|
||||||
("Market Guard House Child Pot 2", ("Pot", 0x4D, (0,0,10), None, 'Rupee (1)', ("the Market", "Market", "Pot"))),
|
("Market Guard House Child Pot 2", ("Pot", 0x4D, (0,0,10), None, 'Rupee (1)', ("the Market", "Market", "Pot"))),
|
||||||
|
|
|
@ -30,7 +30,17 @@ class TrackRandomRange(Range):
|
||||||
|
|
||||||
|
|
||||||
class Logic(Choice):
|
class Logic(Choice):
|
||||||
"""Set the logic used for the generator."""
|
"""Set the logic used for the generator.
|
||||||
|
Glitchless: Normal gameplay. Can enable more difficult logical paths using the Logic Tricks option.
|
||||||
|
Glitched: Many powerful glitches expected, such as bomb hovering and clipping.
|
||||||
|
Glitched is incompatible with the following settings:
|
||||||
|
- All forms of entrance randomizer
|
||||||
|
- MQ dungeons
|
||||||
|
- Pot shuffle
|
||||||
|
- Freestanding item shuffle
|
||||||
|
- Crate shuffle
|
||||||
|
- Beehive shuffle
|
||||||
|
No Logic: No logic is used when placing items. Not recommended for most players."""
|
||||||
display_name = "Logic Rules"
|
display_name = "Logic Rules"
|
||||||
option_glitchless = 0
|
option_glitchless = 0
|
||||||
option_glitched = 1
|
option_glitched = 1
|
||||||
|
@ -38,12 +48,16 @@ class Logic(Choice):
|
||||||
|
|
||||||
|
|
||||||
class NightTokens(Toggle):
|
class NightTokens(Toggle):
|
||||||
"""Nighttime skulltulas will logically require Sun's Song."""
|
"""When enabled, nighttime skulltulas logically require Sun's Song."""
|
||||||
display_name = "Nighttime Skulltulas Expect Sun's Song"
|
display_name = "Nighttime Skulltulas Expect Sun's Song"
|
||||||
|
|
||||||
|
|
||||||
class Forest(Choice):
|
class Forest(Choice):
|
||||||
"""Set the state of Kokiri Forest and the path to Deku Tree."""
|
"""Set the state of Kokiri Forest and the path to Deku Tree.
|
||||||
|
Open: Neither the forest exit nor the path to Deku Tree is blocked.
|
||||||
|
Closed Deku: The forest exit is not blocked; the path to Deku Tree requires Kokiri Sword and Deku Shield.
|
||||||
|
Closed: Path to Deku Tree requires sword and shield. The forest exit is blocked until Deku Tree is beaten.
|
||||||
|
Closed forest will force child start, and becomes Closed Deku if interior entrances, overworld entrances, warp songs, or random spawn positions are enabled."""
|
||||||
display_name = "Forest"
|
display_name = "Forest"
|
||||||
option_open = 0
|
option_open = 0
|
||||||
option_closed_deku = 1
|
option_closed_deku = 1
|
||||||
|
@ -53,7 +67,10 @@ class Forest(Choice):
|
||||||
|
|
||||||
|
|
||||||
class Gate(Choice):
|
class Gate(Choice):
|
||||||
"""Set the state of the Kakariko Village gate."""
|
"""Set the state of the Kakariko Village gate for child. The gate is always open as adult.
|
||||||
|
Open: The gate starts open. Happy Mask Shop opens upon receiving Zelda's Letter.
|
||||||
|
Zelda: The gate and Mask Shop open upon receiving Zelda's Letter, without needing to show it to the guard.
|
||||||
|
Closed: Vanilla behavior; the gate and Mask Shop open upon showing Zelda's Letter to the gate guard."""
|
||||||
display_name = "Kakariko Gate"
|
display_name = "Kakariko Gate"
|
||||||
option_open = 0
|
option_open = 0
|
||||||
option_zelda = 1
|
option_zelda = 1
|
||||||
|
@ -61,12 +78,15 @@ class Gate(Choice):
|
||||||
|
|
||||||
|
|
||||||
class DoorOfTime(DefaultOnToggle):
|
class DoorOfTime(DefaultOnToggle):
|
||||||
"""Open the Door of Time by default, without the Song of Time."""
|
"""When enabled, the Door of Time starts opened, without needing Song of Time."""
|
||||||
display_name = "Open Door of Time"
|
display_name = "Open Door of Time"
|
||||||
|
|
||||||
|
|
||||||
class Fountain(Choice):
|
class Fountain(Choice):
|
||||||
"""Set the state of King Zora, blocking the way to Zora's Fountain."""
|
"""Set the state of King Zora, blocking the way to Zora's Fountain.
|
||||||
|
Open: King Zora starts moved as both ages. Ruto's Letter is removed.
|
||||||
|
Adult: King Zora must be moved as child, but is always moved for adult.
|
||||||
|
Closed: Vanilla behavior; King Zora must be shown Ruto's Letter as child to move him as both ages."""
|
||||||
display_name = "Zora's Fountain"
|
display_name = "Zora's Fountain"
|
||||||
option_open = 0
|
option_open = 0
|
||||||
option_adult = 1
|
option_adult = 1
|
||||||
|
@ -75,7 +95,10 @@ class Fountain(Choice):
|
||||||
|
|
||||||
|
|
||||||
class Fortress(Choice):
|
class Fortress(Choice):
|
||||||
"""Set the requirements for access to Gerudo Fortress."""
|
"""Set the requirements for access to Gerudo Fortress.
|
||||||
|
Normal: Vanilla behavior; all four carpenters must be rescued.
|
||||||
|
Fast: Only one carpenter must be rescued, which is the one in the bottom-left of the fortress.
|
||||||
|
Open: The Gerudo Valley bridge starts repaired. Gerudo Membership Card is given to start if not shuffled."""
|
||||||
display_name = "Gerudo Fortress"
|
display_name = "Gerudo Fortress"
|
||||||
option_normal = 0
|
option_normal = 0
|
||||||
option_fast = 1
|
option_fast = 1
|
||||||
|
@ -84,7 +107,14 @@ class Fortress(Choice):
|
||||||
|
|
||||||
|
|
||||||
class Bridge(Choice):
|
class Bridge(Choice):
|
||||||
"""Set the requirements for the Rainbow Bridge."""
|
"""Set the requirements for the Rainbow Bridge.
|
||||||
|
Open: The bridge is always present.
|
||||||
|
Vanilla: Bridge requires Shadow Medallion, Spirit Medallion, and Light Arrows.
|
||||||
|
Stones: Bridge requires a configurable amount of Spiritual Stones.
|
||||||
|
Medallions: Bridge requires a configurable amount of medallions.
|
||||||
|
Dungeons: Bridge requires a configurable amount of rewards (stones + medallions).
|
||||||
|
Tokens: Bridge requires a configurable amount of gold skulltula tokens.
|
||||||
|
Hearts: Bridge requires a configurable amount of hearts."""
|
||||||
display_name = "Rainbow Bridge Requirement"
|
display_name = "Rainbow Bridge Requirement"
|
||||||
option_open = 0
|
option_open = 0
|
||||||
option_vanilla = 1
|
option_vanilla = 1
|
||||||
|
@ -122,8 +152,9 @@ class StartingAge(Choice):
|
||||||
|
|
||||||
|
|
||||||
class InteriorEntrances(Choice):
|
class InteriorEntrances(Choice):
|
||||||
"""Shuffles interior entrances. "Simple" shuffles houses and Great Fairies; "All" includes Windmill, Link's House,
|
"""Shuffles interior entrances.
|
||||||
Temple of Time, and Kak potion shop."""
|
Simple: Houses and Great Fairies are shuffled.
|
||||||
|
All: In addition to Simple, includes Windmill, Link's House, Temple of Time, and the Kakariko potion shop."""
|
||||||
display_name = "Shuffle Interior Entrances"
|
display_name = "Shuffle Interior Entrances"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_simple = 1
|
option_simple = 1
|
||||||
|
@ -137,7 +168,9 @@ class GrottoEntrances(Toggle):
|
||||||
|
|
||||||
|
|
||||||
class DungeonEntrances(Choice):
|
class DungeonEntrances(Choice):
|
||||||
"""Shuffles dungeon entrances. Opens Deku, Fire and BotW to both ages. "All" includes Ganon's Castle."""
|
"""Shuffles dungeon entrances. When enabled, both ages will have access to Fire Temple, Bottom of the Well, and Deku Tree.
|
||||||
|
Simple: Shuffle dungeon entrances except for Ganon's Castle.
|
||||||
|
All: Include Ganon's Castle as well."""
|
||||||
display_name = "Shuffle Dungeon Entrances"
|
display_name = "Shuffle Dungeon Entrances"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_simple = 1
|
option_simple = 1
|
||||||
|
@ -146,7 +179,9 @@ class DungeonEntrances(Choice):
|
||||||
|
|
||||||
|
|
||||||
class BossEntrances(Choice):
|
class BossEntrances(Choice):
|
||||||
"""Shuffles boss entrances. "Limited" prevents age-mixing of bosses."""
|
"""Shuffles boss entrances.
|
||||||
|
Limited: Bosses will be limited to the ages that typically fight them.
|
||||||
|
Full: Bosses may be fought as different ages than usual. Child can defeat Phantom Ganon and Bongo Bongo."""
|
||||||
display_name = "Shuffle Boss Entrances"
|
display_name = "Shuffle Boss Entrances"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_limited = 1
|
option_limited = 1
|
||||||
|
@ -178,19 +213,19 @@ class SpawnPositions(Choice):
|
||||||
alias_true = 3
|
alias_true = 3
|
||||||
|
|
||||||
|
|
||||||
class MixEntrancePools(Choice):
|
# class MixEntrancePools(Choice):
|
||||||
"""Shuffles entrances into a mixed pool instead of separate ones. "indoor" keeps overworld entrances separate; "all"
|
# """Shuffles entrances into a mixed pool instead of separate ones. "indoor" keeps overworld entrances separate; "all"
|
||||||
mixes them in."""
|
# mixes them in."""
|
||||||
display_name = "Mix Entrance Pools"
|
# display_name = "Mix Entrance Pools"
|
||||||
option_off = 0
|
# option_off = 0
|
||||||
option_indoor = 1
|
# option_indoor = 1
|
||||||
option_all = 2
|
# option_all = 2
|
||||||
|
|
||||||
|
|
||||||
class DecoupleEntrances(Toggle):
|
# class DecoupleEntrances(Toggle):
|
||||||
"""Decouple entrances when shuffling them. Also adds the one-way entrance from Gerudo Valley to Lake Hylia if
|
# """Decouple entrances when shuffling them. Also adds the one-way entrance from Gerudo Valley to Lake Hylia if
|
||||||
overworld is shuffled."""
|
# overworld is shuffled."""
|
||||||
display_name = "Decouple Entrances"
|
# display_name = "Decouple Entrances"
|
||||||
|
|
||||||
|
|
||||||
class TriforceHunt(Toggle):
|
class TriforceHunt(Toggle):
|
||||||
|
@ -216,13 +251,17 @@ class ExtraTriforces(Range):
|
||||||
|
|
||||||
|
|
||||||
class LogicalChus(Toggle):
|
class LogicalChus(Toggle):
|
||||||
"""Bombchus are properly considered in logic. The first found pack will have 20 chus; Kokiri Shop and Bazaar sell
|
"""Bombchus are properly considered in logic.
|
||||||
refills; bombchus open Bombchu Bowling."""
|
The first found pack will always have 20 chus.
|
||||||
|
Kokiri Shop and Bazaar will sell refills at reduced cost.
|
||||||
|
Bombchus open Bombchu Bowling."""
|
||||||
display_name = "Bombchus Considered in Logic"
|
display_name = "Bombchus Considered in Logic"
|
||||||
|
|
||||||
|
|
||||||
class DungeonShortcuts(Choice):
|
class DungeonShortcuts(Choice):
|
||||||
"""Shortcuts to dungeon bosses are available without any requirements."""
|
"""Shortcuts to dungeon bosses are available without any requirements.
|
||||||
|
If enabled, this will impact the logic of dungeons where shortcuts are available.
|
||||||
|
Choice: Use the option "dungeon_shortcuts_list" to choose shortcuts."""
|
||||||
display_name = "Dungeon Boss Shortcuts Mode"
|
display_name = "Dungeon Boss Shortcuts Mode"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_choice = 1
|
option_choice = 1
|
||||||
|
@ -246,7 +285,11 @@ class DungeonShortcutsList(OptionSet):
|
||||||
|
|
||||||
|
|
||||||
class MQDungeons(Choice):
|
class MQDungeons(Choice):
|
||||||
"""Choose between vanilla and Master Quest dungeon layouts."""
|
"""Choose between vanilla and Master Quest dungeon layouts.
|
||||||
|
Vanilla: All layouts are vanilla.
|
||||||
|
MQ: All layouts are Master Quest.
|
||||||
|
Specific: Use the option "mq_dungeons_list" to choose which dungeons are MQ.
|
||||||
|
Count: Use the option "mq_dungeons_count" to choose a number of random dungeons as MQ."""
|
||||||
display_name = "MQ Dungeon Mode"
|
display_name = "MQ Dungeon Mode"
|
||||||
option_vanilla = 0
|
option_vanilla = 0
|
||||||
option_mq = 1
|
option_mq = 1
|
||||||
|
@ -255,7 +298,7 @@ class MQDungeons(Choice):
|
||||||
|
|
||||||
|
|
||||||
class MQDungeonList(OptionSet):
|
class MQDungeonList(OptionSet):
|
||||||
"""Chosen dungeons to be MQ layout."""
|
"""With MQ dungeons as Specific: chosen dungeons to be MQ layout."""
|
||||||
display_name = "MQ Dungeon List"
|
display_name = "MQ Dungeon List"
|
||||||
valid_keys = {
|
valid_keys = {
|
||||||
"Deku Tree",
|
"Deku Tree",
|
||||||
|
@ -274,41 +317,41 @@ class MQDungeonList(OptionSet):
|
||||||
|
|
||||||
|
|
||||||
class MQDungeonCount(TrackRandomRange):
|
class MQDungeonCount(TrackRandomRange):
|
||||||
"""Number of MQ dungeons, chosen randomly."""
|
"""With MQ dungeons as Count: number of randomly-selected dungeons to be MQ layout."""
|
||||||
display_name = "MQ Dungeon Count"
|
display_name = "MQ Dungeon Count"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 12
|
range_end = 12
|
||||||
default = 0
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
class EmptyDungeons(Choice):
|
# class EmptyDungeons(Choice):
|
||||||
"""Pre-completed dungeons are barren and rewards are given for free."""
|
# """Pre-completed dungeons are barren and rewards are given for free."""
|
||||||
display_name = "Pre-completed Dungeons Mode"
|
# display_name = "Pre-completed Dungeons Mode"
|
||||||
option_none = 0
|
# option_none = 0
|
||||||
option_specific = 1
|
# option_specific = 1
|
||||||
option_count = 2
|
# option_count = 2
|
||||||
|
|
||||||
|
|
||||||
class EmptyDungeonList(OptionSet):
|
# class EmptyDungeonList(OptionSet):
|
||||||
"""Chosen dungeons to be pre-completed."""
|
# """Chosen dungeons to be pre-completed."""
|
||||||
display_name = "Pre-completed Dungeon List"
|
# display_name = "Pre-completed Dungeon List"
|
||||||
valid_keys = {
|
# valid_keys = {
|
||||||
"Deku Tree",
|
# "Deku Tree",
|
||||||
"Dodongo's Cavern",
|
# "Dodongo's Cavern",
|
||||||
"Jabu Jabu's Belly",
|
# "Jabu Jabu's Belly",
|
||||||
"Forest Temple",
|
# "Forest Temple",
|
||||||
"Fire Temple",
|
# "Fire Temple",
|
||||||
"Water Temple",
|
# "Water Temple",
|
||||||
"Shadow Temple",
|
# "Shadow Temple",
|
||||||
"Spirit Temple",
|
# "Spirit Temple",
|
||||||
}
|
# }
|
||||||
|
|
||||||
|
|
||||||
class EmptyDungeonCount(Range):
|
# class EmptyDungeonCount(Range):
|
||||||
display_name = "Pre-completed Dungeon Count"
|
# display_name = "Pre-completed Dungeon Count"
|
||||||
range_start = 1
|
# range_start = 1
|
||||||
range_end = 8
|
# range_end = 8
|
||||||
default = 2
|
# default = 2
|
||||||
|
|
||||||
|
|
||||||
world_options: typing.Dict[str, type(Option)] = {
|
world_options: typing.Dict[str, type(Option)] = {
|
||||||
|
@ -341,59 +384,8 @@ world_options: typing.Dict[str, type(Option)] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# class LacsCondition(Choice):
|
|
||||||
# """Set the requirements for the Light Arrow Cutscene in the Temple of Time."""
|
|
||||||
# display_name = "Light Arrow Cutscene Requirement"
|
|
||||||
# option_vanilla = 0
|
|
||||||
# option_stones = 1
|
|
||||||
# option_medallions = 2
|
|
||||||
# option_dungeons = 3
|
|
||||||
# option_tokens = 4
|
|
||||||
|
|
||||||
|
|
||||||
# class LacsStones(Range):
|
|
||||||
# """Set the number of Spiritual Stones required for LACS."""
|
|
||||||
# display_name = "Spiritual Stones Required for LACS"
|
|
||||||
# range_start = 0
|
|
||||||
# range_end = 3
|
|
||||||
# default = 3
|
|
||||||
|
|
||||||
|
|
||||||
# class LacsMedallions(Range):
|
|
||||||
# """Set the number of medallions required for LACS."""
|
|
||||||
# display_name = "Medallions Required for LACS"
|
|
||||||
# range_start = 0
|
|
||||||
# range_end = 6
|
|
||||||
# default = 6
|
|
||||||
|
|
||||||
|
|
||||||
# class LacsRewards(Range):
|
|
||||||
# """Set the number of dungeon rewards required for LACS."""
|
|
||||||
# display_name = "Dungeon Rewards Required for LACS"
|
|
||||||
# range_start = 0
|
|
||||||
# range_end = 9
|
|
||||||
# default = 9
|
|
||||||
|
|
||||||
|
|
||||||
# class LacsTokens(Range):
|
|
||||||
# """Set the number of Gold Skulltula Tokens required for LACS."""
|
|
||||||
# display_name = "Tokens Required for LACS"
|
|
||||||
# range_start = 0
|
|
||||||
# range_end = 100
|
|
||||||
# default = 40
|
|
||||||
|
|
||||||
|
|
||||||
# lacs_options: typing.Dict[str, type(Option)] = {
|
|
||||||
# "lacs_condition": LacsCondition,
|
|
||||||
# "lacs_stones": LacsStones,
|
|
||||||
# "lacs_medallions": LacsMedallions,
|
|
||||||
# "lacs_rewards": LacsRewards,
|
|
||||||
# "lacs_tokens": LacsTokens,
|
|
||||||
# }
|
|
||||||
|
|
||||||
|
|
||||||
class BridgeStones(Range):
|
class BridgeStones(Range):
|
||||||
"""Set the number of Spiritual Stones required for the rainbow bridge."""
|
"""With Stones bridge: set the number of Spiritual Stones required."""
|
||||||
display_name = "Spiritual Stones Required for Bridge"
|
display_name = "Spiritual Stones Required for Bridge"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 3
|
range_end = 3
|
||||||
|
@ -401,7 +393,7 @@ class BridgeStones(Range):
|
||||||
|
|
||||||
|
|
||||||
class BridgeMedallions(Range):
|
class BridgeMedallions(Range):
|
||||||
"""Set the number of medallions required for the rainbow bridge."""
|
"""With Medallions bridge: set the number of medallions required."""
|
||||||
display_name = "Medallions Required for Bridge"
|
display_name = "Medallions Required for Bridge"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 6
|
range_end = 6
|
||||||
|
@ -409,7 +401,7 @@ class BridgeMedallions(Range):
|
||||||
|
|
||||||
|
|
||||||
class BridgeRewards(Range):
|
class BridgeRewards(Range):
|
||||||
"""Set the number of dungeon rewards required for the rainbow bridge."""
|
"""With Dungeons bridge: set the number of dungeon rewards required."""
|
||||||
display_name = "Dungeon Rewards Required for Bridge"
|
display_name = "Dungeon Rewards Required for Bridge"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 9
|
range_end = 9
|
||||||
|
@ -417,7 +409,7 @@ class BridgeRewards(Range):
|
||||||
|
|
||||||
|
|
||||||
class BridgeTokens(Range):
|
class BridgeTokens(Range):
|
||||||
"""Set the number of Gold Skulltula Tokens required for the rainbow bridge."""
|
"""With Tokens bridge: set the number of Gold Skulltula Tokens required."""
|
||||||
display_name = "Tokens Required for Bridge"
|
display_name = "Tokens Required for Bridge"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 100
|
range_end = 100
|
||||||
|
@ -425,7 +417,7 @@ class BridgeTokens(Range):
|
||||||
|
|
||||||
|
|
||||||
class BridgeHearts(Range):
|
class BridgeHearts(Range):
|
||||||
"""Set the number of hearts required for the rainbow bridge."""
|
"""With Hearts bridge: set the number of hearts required."""
|
||||||
display_name = "Hearts Required for Bridge"
|
display_name = "Hearts Required for Bridge"
|
||||||
range_start = 4
|
range_start = 4
|
||||||
range_end = 20
|
range_end = 20
|
||||||
|
@ -442,7 +434,15 @@ bridge_options: typing.Dict[str, type(Option)] = {
|
||||||
|
|
||||||
|
|
||||||
class SongShuffle(Choice):
|
class SongShuffle(Choice):
|
||||||
"""Set where songs can appear."""
|
"""Set where songs can appear.
|
||||||
|
Song: Songs are shuffled into other song locations.
|
||||||
|
Dungeon: Songs are placed into end-of-dungeon locations:
|
||||||
|
- The 8 boss heart containers
|
||||||
|
- Sheik in Ice Cavern
|
||||||
|
- Lens of Truth chest in Bottom of the Well
|
||||||
|
- Ice Arrows chest in Gerudo Training Ground
|
||||||
|
- Impa at Hyrule Castle
|
||||||
|
Any: Songs can appear anywhere in the multiworld."""
|
||||||
display_name = "Shuffle Songs"
|
display_name = "Shuffle Songs"
|
||||||
option_song = 0
|
option_song = 0
|
||||||
option_dungeon = 1
|
option_dungeon = 1
|
||||||
|
@ -450,8 +450,10 @@ class SongShuffle(Choice):
|
||||||
|
|
||||||
|
|
||||||
class ShopShuffle(Choice):
|
class ShopShuffle(Choice):
|
||||||
"""Randomizes shop contents. "fixed_number" randomizes a specific number of items per shop;
|
"""Randomizes shop contents.
|
||||||
"random_number" randomizes the value for each shop. """
|
Off: Shops are not randomized at all.
|
||||||
|
Fixed Number: Shop contents are shuffled, and a specific number of multiworld locations exist in each shop, controlled by the "shop_slots" option.
|
||||||
|
Random Number: Same as Fixed Number, but the number of locations per shop is random and may differ between shops."""
|
||||||
display_name = "Shopsanity"
|
display_name = "Shopsanity"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_fixed_number = 1
|
option_fixed_number = 1
|
||||||
|
@ -459,15 +461,20 @@ class ShopShuffle(Choice):
|
||||||
|
|
||||||
|
|
||||||
class ShopSlots(Range):
|
class ShopSlots(Range):
|
||||||
"""Number of items per shop to be randomized into the main itempool.
|
"""With Shopsanity fixed number: quantity of multiworld locations per shop to be randomized."""
|
||||||
Only active if Shopsanity is set to "fixed_number." """
|
|
||||||
display_name = "Shuffled Shop Slots"
|
display_name = "Shuffled Shop Slots"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 4
|
range_end = 4
|
||||||
|
|
||||||
|
|
||||||
class ShopPrices(Choice):
|
class ShopPrices(Choice):
|
||||||
"""Controls prices of shop items. "Normal" is a distribution from 0 to 300. "X Wallet" requires that wallet at max. "Affordable" is always 10 rupees."""
|
"""Controls prices of shop locations.
|
||||||
|
Normal: Balanced distribution from 0 to 300.
|
||||||
|
Affordable: Every shop location costs 10 rupees.
|
||||||
|
Starting Wallet: Prices capped at 99 rupees.
|
||||||
|
Adult's Wallet: Prices capped at 200 rupees.
|
||||||
|
Giant's Wallet: Prices capped at 500 rupees.
|
||||||
|
Tycoon's Wallet: Prices capped at 999 rupees."""
|
||||||
display_name = "Shopsanity Prices"
|
display_name = "Shopsanity Prices"
|
||||||
option_normal = 0
|
option_normal = 0
|
||||||
option_affordable = 1
|
option_affordable = 1
|
||||||
|
@ -478,7 +485,10 @@ class ShopPrices(Choice):
|
||||||
|
|
||||||
|
|
||||||
class TokenShuffle(Choice):
|
class TokenShuffle(Choice):
|
||||||
"""Token rewards from Gold Skulltulas are shuffled into the pool."""
|
"""Token rewards from Gold Skulltulas can be shuffled into the pool.
|
||||||
|
Dungeons: Only skulltulas in dungeons are shuffled.
|
||||||
|
Overworld: Only skulltulas on the overworld (all skulltulas not in dungeons) are shuffled.
|
||||||
|
All: Every skulltula is shuffled."""
|
||||||
display_name = "Tokensanity"
|
display_name = "Tokensanity"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_dungeons = 1
|
option_dungeons = 1
|
||||||
|
@ -487,7 +497,11 @@ class TokenShuffle(Choice):
|
||||||
|
|
||||||
|
|
||||||
class ScrubShuffle(Choice):
|
class ScrubShuffle(Choice):
|
||||||
"""Shuffle the items sold by Business Scrubs, and set the prices."""
|
"""Shuffle the items sold by Business Scrubs, and set the prices.
|
||||||
|
Off: Only the three business scrubs that sell one-time upgrades in vanilla will have items at their vanilla prices.
|
||||||
|
Low/"Affordable": All scrub prices are 10 rupees.
|
||||||
|
Regular/"Expensive": All scrub prices are vanilla.
|
||||||
|
Random Prices: All scrub prices are randomized between 0 and 99 rupees."""
|
||||||
display_name = "Scrub Shuffle"
|
display_name = "Scrub Shuffle"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_low = 1
|
option_low = 1
|
||||||
|
@ -513,7 +527,11 @@ class ShuffleOcarinas(Toggle):
|
||||||
|
|
||||||
|
|
||||||
class ShuffleChildTrade(Choice):
|
class ShuffleChildTrade(Choice):
|
||||||
"""Controls the behavior of the start of the child trade quest."""
|
"""Controls the behavior of the start of the child trade quest.
|
||||||
|
Vanilla: Malon will give you the Weird Egg at Hyrule Castle.
|
||||||
|
Shuffle: Malon will give you a random item, and the Weird Egg is shuffled.
|
||||||
|
Skip Child Zelda: The game starts with Zelda already met, Zelda's Letter obtained, and the item from Impa obtained.
|
||||||
|
"""
|
||||||
display_name = "Shuffle Child Trade Item"
|
display_name = "Shuffle Child Trade Item"
|
||||||
option_vanilla = 0
|
option_vanilla = 0
|
||||||
option_shuffle = 1
|
option_shuffle = 1
|
||||||
|
@ -538,30 +556,39 @@ class ShuffleMedigoronCarpet(Toggle):
|
||||||
|
|
||||||
|
|
||||||
class ShuffleFreestanding(Choice):
|
class ShuffleFreestanding(Choice):
|
||||||
"""Shuffles freestanding rupees, recovery hearts, Shadow Temple Spinning Pots, and Goron Pot."""
|
"""Shuffles freestanding rupees, recovery hearts, Shadow Temple Spinning Pots, and Goron Pot drops.
|
||||||
|
Dungeons: Only freestanding items in dungeons are shuffled.
|
||||||
|
Overworld: Only freestanding items in the overworld are shuffled.
|
||||||
|
All: All freestanding items are shuffled."""
|
||||||
display_name = "Shuffle Rupees & Hearts"
|
display_name = "Shuffle Rupees & Hearts"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_all = 1
|
option_dungeons = 1
|
||||||
option_overworld = 2
|
option_overworld = 2
|
||||||
option_dungeons = 3
|
option_all = 3
|
||||||
|
|
||||||
|
|
||||||
class ShufflePots(Choice):
|
class ShufflePots(Choice):
|
||||||
"""Shuffles pots and flying pots which normally contain an item."""
|
"""Shuffles pots and flying pots which normally contain an item.
|
||||||
|
Dungeons: Only pots in dungeons are shuffled.
|
||||||
|
Overworld: Only pots in the overworld are shuffled.
|
||||||
|
All: All pots are shuffled."""
|
||||||
display_name = "Shuffle Pots"
|
display_name = "Shuffle Pots"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_all = 1
|
option_dungeons = 1
|
||||||
option_overworld = 2
|
option_overworld = 2
|
||||||
option_dungeons = 3
|
option_all = 3
|
||||||
|
|
||||||
|
|
||||||
class ShuffleCrates(Choice):
|
class ShuffleCrates(Choice):
|
||||||
"""Shuffles large and small crates containing an item."""
|
"""Shuffles large and small crates containing an item.
|
||||||
|
Dungeons: Only crates in dungeons are shuffled.
|
||||||
|
Overworld: Only crates in the overworld are shuffled.
|
||||||
|
All: All crates are shuffled."""
|
||||||
display_name = "Shuffle Crates"
|
display_name = "Shuffle Crates"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_all = 1
|
option_dungeons = 1
|
||||||
option_overworld = 2
|
option_overworld = 2
|
||||||
option_dungeons = 3
|
option_all = 3
|
||||||
|
|
||||||
|
|
||||||
class ShuffleBeehives(Toggle):
|
class ShuffleBeehives(Toggle):
|
||||||
|
@ -597,72 +624,113 @@ shuffle_options: typing.Dict[str, type(Option)] = {
|
||||||
|
|
||||||
|
|
||||||
class ShuffleMapCompass(Choice):
|
class ShuffleMapCompass(Choice):
|
||||||
"""Control where to shuffle dungeon maps and compasses."""
|
"""Control where to shuffle dungeon maps and compasses.
|
||||||
|
Remove: There will be no maps or compasses in the itempool.
|
||||||
|
Startwith: You start with all maps and compasses.
|
||||||
|
Vanilla: Maps and compasses remain vanilla.
|
||||||
|
Dungeon: Maps and compasses are shuffled within their original dungeon.
|
||||||
|
Regional: Maps and compasses are shuffled only in regions near the original dungeon.
|
||||||
|
Overworld: Maps and compasses are shuffled locally outside of dungeons.
|
||||||
|
Any Dungeon: Maps and compasses are shuffled locally in any dungeon.
|
||||||
|
Keysanity: Maps and compasses can be anywhere in the multiworld."""
|
||||||
display_name = "Maps & Compasses"
|
display_name = "Maps & Compasses"
|
||||||
option_remove = 0
|
option_remove = 0
|
||||||
option_startwith = 1
|
option_startwith = 1
|
||||||
option_vanilla = 2
|
option_vanilla = 2
|
||||||
option_dungeon = 3
|
option_dungeon = 3
|
||||||
option_overworld = 4
|
option_regional = 4
|
||||||
option_any_dungeon = 5
|
option_overworld = 5
|
||||||
option_keysanity = 6
|
option_any_dungeon = 6
|
||||||
option_regional = 7
|
option_keysanity = 7
|
||||||
default = 1
|
default = 1
|
||||||
alias_anywhere = 6
|
alias_anywhere = 7
|
||||||
|
|
||||||
|
|
||||||
class ShuffleKeys(Choice):
|
class ShuffleKeys(Choice):
|
||||||
"""Control where to shuffle dungeon small keys."""
|
"""Control where to shuffle dungeon small keys.
|
||||||
|
Remove/"Keysy": There will be no small keys in the itempool. All small key doors are automatically unlocked.
|
||||||
|
Vanilla: Small keys remain vanilla. You may start with extra small keys in some dungeons to prevent softlocks.
|
||||||
|
Dungeon: Small keys are shuffled within their original dungeon.
|
||||||
|
Regional: Small keys are shuffled only in regions near the original dungeon.
|
||||||
|
Overworld: Small keys are shuffled locally outside of dungeons.
|
||||||
|
Any Dungeon: Small keys are shuffled locally in any dungeon.
|
||||||
|
Keysanity: Small keys can be anywhere in the multiworld."""
|
||||||
display_name = "Small Keys"
|
display_name = "Small Keys"
|
||||||
option_remove = 0
|
option_remove = 0
|
||||||
option_vanilla = 2
|
option_vanilla = 2
|
||||||
option_dungeon = 3
|
option_dungeon = 3
|
||||||
option_overworld = 4
|
option_regional = 4
|
||||||
option_any_dungeon = 5
|
option_overworld = 5
|
||||||
option_keysanity = 6
|
option_any_dungeon = 6
|
||||||
option_regional = 7
|
option_keysanity = 7
|
||||||
default = 3
|
default = 3
|
||||||
alias_keysy = 0
|
alias_keysy = 0
|
||||||
alias_anywhere = 6
|
alias_anywhere = 7
|
||||||
|
|
||||||
|
|
||||||
class ShuffleGerudoKeys(Choice):
|
class ShuffleGerudoKeys(Choice):
|
||||||
"""Control where to shuffle the Thieves' Hideout small keys."""
|
"""Control where to shuffle the Thieves' Hideout small keys.
|
||||||
|
Vanilla: Hideout keys remain vanilla.
|
||||||
|
Regional: Hideout keys are shuffled only in the Gerudo Valley/Desert Colossus area.
|
||||||
|
Overworld: Hideout keys are shuffled locally outside of dungeons.
|
||||||
|
Any Dungeon: Hideout keys are shuffled locally in any dungeon.
|
||||||
|
Keysanity: Hideout keys can be anywhere in the multiworld."""
|
||||||
display_name = "Thieves' Hideout Keys"
|
display_name = "Thieves' Hideout Keys"
|
||||||
option_vanilla = 0
|
option_vanilla = 0
|
||||||
option_overworld = 1
|
option_regional = 1
|
||||||
option_any_dungeon = 2
|
option_overworld = 2
|
||||||
option_keysanity = 3
|
option_any_dungeon = 3
|
||||||
option_regional = 4
|
option_keysanity = 4
|
||||||
alias_anywhere = 3
|
alias_anywhere = 4
|
||||||
|
|
||||||
|
|
||||||
class ShuffleBossKeys(Choice):
|
class ShuffleBossKeys(Choice):
|
||||||
"""Control where to shuffle boss keys, except the Ganon's Castle Boss Key."""
|
"""Control where to shuffle boss keys, except the Ganon's Castle Boss Key.
|
||||||
|
Remove/"Keysy": There will be no boss keys in the itempool. All boss key doors are automatically unlocked.
|
||||||
|
Vanilla: Boss keys remain vanilla. You may start with extra small keys in some dungeons to prevent softlocks.
|
||||||
|
Dungeon: Boss keys are shuffled within their original dungeon.
|
||||||
|
Regional: Boss keys are shuffled only in regions near the original dungeon.
|
||||||
|
Overworld: Boss keys are shuffled locally outside of dungeons.
|
||||||
|
Any Dungeon: Boss keys are shuffled locally in any dungeon.
|
||||||
|
Keysanity: Boss keys can be anywhere in the multiworld."""
|
||||||
display_name = "Boss Keys"
|
display_name = "Boss Keys"
|
||||||
option_remove = 0
|
option_remove = 0
|
||||||
option_vanilla = 2
|
option_vanilla = 2
|
||||||
option_dungeon = 3
|
option_dungeon = 3
|
||||||
option_overworld = 4
|
option_regional = 4
|
||||||
option_any_dungeon = 5
|
option_overworld = 5
|
||||||
option_keysanity = 6
|
option_any_dungeon = 6
|
||||||
option_regional = 7
|
option_keysanity = 7
|
||||||
default = 3
|
default = 3
|
||||||
alias_keysy = 0
|
alias_keysy = 0
|
||||||
alias_anywhere = 6
|
alias_anywhere = 7
|
||||||
|
|
||||||
|
|
||||||
class ShuffleGanonBK(Choice):
|
class ShuffleGanonBK(Choice):
|
||||||
"""Control how to shuffle the Ganon's Castle Boss Key."""
|
"""Control how to shuffle the Ganon's Castle Boss Key (GCBK).
|
||||||
|
Remove: GCBK is removed, and the boss key door is automatically unlocked.
|
||||||
|
Vanilla: GCBK remains vanilla.
|
||||||
|
Dungeon: GCBK is shuffled within its original dungeon.
|
||||||
|
Regional: GCBK is shuffled only in Hyrule Field, Market, and Hyrule Castle areas.
|
||||||
|
Overworld: GCBK is shuffled locally outside of dungeons.
|
||||||
|
Any Dungeon: GCBK is shuffled locally in any dungeon.
|
||||||
|
Keysanity: GCBK can be anywhere in the multiworld.
|
||||||
|
On LACS: GCBK is on the Light Arrow Cutscene, which requires Shadow and Spirit Medallions.
|
||||||
|
Stones: GCBK will be awarded when reaching the target number of Spiritual Stones.
|
||||||
|
Medallions: GCBK will be awarded when reaching the target number of medallions.
|
||||||
|
Dungeons: GCBK will be awarded when reaching the target number of dungeon rewards.
|
||||||
|
Tokens: GCBK will be awarded when reaching the target number of Gold Skulltula Tokens.
|
||||||
|
Hearts: GCBK will be awarded when reaching the target number of hearts.
|
||||||
|
"""
|
||||||
display_name = "Ganon's Boss Key"
|
display_name = "Ganon's Boss Key"
|
||||||
option_remove = 0
|
option_remove = 0
|
||||||
option_vanilla = 2
|
option_vanilla = 2
|
||||||
option_dungeon = 3
|
option_dungeon = 3
|
||||||
option_overworld = 4
|
option_regional = 4
|
||||||
option_any_dungeon = 5
|
option_overworld = 5
|
||||||
option_keysanity = 6
|
option_any_dungeon = 6
|
||||||
option_on_lacs = 7
|
option_keysanity = 7
|
||||||
option_regional = 8
|
option_on_lacs = 8
|
||||||
option_stones = 9
|
option_stones = 9
|
||||||
option_medallions = 10
|
option_medallions = 10
|
||||||
option_dungeons = 11
|
option_dungeons = 11
|
||||||
|
@ -670,7 +738,7 @@ class ShuffleGanonBK(Choice):
|
||||||
option_hearts = 13
|
option_hearts = 13
|
||||||
default = 0
|
default = 0
|
||||||
alias_keysy = 0
|
alias_keysy = 0
|
||||||
alias_anywhere = 6
|
alias_anywhere = 7
|
||||||
|
|
||||||
|
|
||||||
class EnhanceMC(Toggle):
|
class EnhanceMC(Toggle):
|
||||||
|
@ -679,7 +747,7 @@ class EnhanceMC(Toggle):
|
||||||
|
|
||||||
|
|
||||||
class GanonBKMedallions(Range):
|
class GanonBKMedallions(Range):
|
||||||
"""Set how many medallions are required to receive Ganon BK."""
|
"""With medallions GCBK: set how many medallions are required to receive GCBK."""
|
||||||
display_name = "Medallions Required for Ganon's BK"
|
display_name = "Medallions Required for Ganon's BK"
|
||||||
range_start = 1
|
range_start = 1
|
||||||
range_end = 6
|
range_end = 6
|
||||||
|
@ -687,7 +755,7 @@ class GanonBKMedallions(Range):
|
||||||
|
|
||||||
|
|
||||||
class GanonBKStones(Range):
|
class GanonBKStones(Range):
|
||||||
"""Set how many Spiritual Stones are required to receive Ganon BK."""
|
"""With stones GCBK: set how many Spiritual Stones are required to receive GCBK."""
|
||||||
display_name = "Spiritual Stones Required for Ganon's BK"
|
display_name = "Spiritual Stones Required for Ganon's BK"
|
||||||
range_start = 1
|
range_start = 1
|
||||||
range_end = 3
|
range_end = 3
|
||||||
|
@ -695,7 +763,7 @@ class GanonBKStones(Range):
|
||||||
|
|
||||||
|
|
||||||
class GanonBKRewards(Range):
|
class GanonBKRewards(Range):
|
||||||
"""Set how many dungeon rewards are required to receive Ganon BK."""
|
"""With dungeons GCBK: set how many dungeon rewards are required to receive GCBK."""
|
||||||
display_name = "Dungeon Rewards Required for Ganon's BK"
|
display_name = "Dungeon Rewards Required for Ganon's BK"
|
||||||
range_start = 1
|
range_start = 1
|
||||||
range_end = 9
|
range_end = 9
|
||||||
|
@ -703,7 +771,7 @@ class GanonBKRewards(Range):
|
||||||
|
|
||||||
|
|
||||||
class GanonBKTokens(Range):
|
class GanonBKTokens(Range):
|
||||||
"""Set how many Gold Skulltula Tokens are required to receive Ganon BK."""
|
"""With tokens GCBK: set how many Gold Skulltula Tokens are required to receive GCBK."""
|
||||||
display_name = "Tokens Required for Ganon's BK"
|
display_name = "Tokens Required for Ganon's BK"
|
||||||
range_start = 1
|
range_start = 1
|
||||||
range_end = 100
|
range_end = 100
|
||||||
|
@ -711,7 +779,7 @@ class GanonBKTokens(Range):
|
||||||
|
|
||||||
|
|
||||||
class GanonBKHearts(Range):
|
class GanonBKHearts(Range):
|
||||||
"""Set how many hearts are required to receive Ganon BK."""
|
"""With hearts GCBK: set how many hearts are required to receive GCBK."""
|
||||||
display_name = "Hearts Required for Ganon's BK"
|
display_name = "Hearts Required for Ganon's BK"
|
||||||
range_start = 4
|
range_start = 4
|
||||||
range_end = 20
|
range_end = 20
|
||||||
|
@ -719,7 +787,9 @@ class GanonBKHearts(Range):
|
||||||
|
|
||||||
|
|
||||||
class KeyRings(Choice):
|
class KeyRings(Choice):
|
||||||
"""Dungeons have all small keys found at once, rather than individually."""
|
"""A key ring grants all dungeon small keys at once, rather than individually.
|
||||||
|
Choose: Use the option "key_rings_list" to choose which dungeons have key rings.
|
||||||
|
All: All dungeons have key rings instead of small keys."""
|
||||||
display_name = "Key Rings Mode"
|
display_name = "Key Rings Mode"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_choose = 1
|
option_choose = 1
|
||||||
|
@ -728,7 +798,7 @@ class KeyRings(Choice):
|
||||||
|
|
||||||
|
|
||||||
class KeyRingList(OptionSet):
|
class KeyRingList(OptionSet):
|
||||||
"""Select areas with keyrings rather than individual small keys."""
|
"""With key rings as Choose: select areas with key rings rather than individual small keys."""
|
||||||
display_name = "Key Ring Areas"
|
display_name = "Key Ring Areas"
|
||||||
valid_keys = {
|
valid_keys = {
|
||||||
"Thieves' Hideout",
|
"Thieves' Hideout",
|
||||||
|
@ -828,7 +898,8 @@ class BigPoeCount(Range):
|
||||||
|
|
||||||
|
|
||||||
class FAETorchCount(Range):
|
class FAETorchCount(Range):
|
||||||
"""Number of lit torches required to open Shadow Temple."""
|
"""Number of lit torches required to open Shadow Temple.
|
||||||
|
Does not affect logic; use the trick Shadow Temple Entry with Fire Arrows if desired."""
|
||||||
display_name = "Fire Arrow Entry Torch Count"
|
display_name = "Fire Arrow Entry Torch Count"
|
||||||
range_start = 1
|
range_start = 1
|
||||||
range_end = 24
|
range_end = 24
|
||||||
|
@ -853,7 +924,11 @@ timesavers_options: typing.Dict[str, type(Option)] = {
|
||||||
|
|
||||||
|
|
||||||
class CorrectChestAppearance(Choice):
|
class CorrectChestAppearance(Choice):
|
||||||
"""Changes chest textures and/or sizes to match their contents. "Classic" is the old behavior of CSMC."""
|
"""Changes chest textures and/or sizes to match their contents.
|
||||||
|
Off: All chests have their vanilla size/appearance.
|
||||||
|
Textures: Chest textures reflect their contents.
|
||||||
|
Both: Like Textures, but progression items and boss keys get big chests, and other items get small chests.
|
||||||
|
Classic: Old behavior of CSMC; textures distinguish keys from non-keys, and size distinguishes importance."""
|
||||||
display_name = "Chest Appearance Matches Contents"
|
display_name = "Chest Appearance Matches Contents"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_textures = 1
|
option_textures = 1
|
||||||
|
@ -872,15 +947,24 @@ class InvisibleChests(Toggle):
|
||||||
|
|
||||||
|
|
||||||
class CorrectPotCrateAppearance(Choice):
|
class CorrectPotCrateAppearance(Choice):
|
||||||
"""Unchecked pots and crates have a different texture; unchecked beehives will wiggle. With textures_content, pots and crates have an appearance based on their contents; with textures_unchecked, all unchecked pots/crates have the same appearance."""
|
"""Changes the appearance of pots, crates, and beehives that contain items.
|
||||||
|
Off: Vanilla appearance for all containers.
|
||||||
|
Textures (Content): Unchecked pots and crates have a texture reflecting their contents. Unchecked beehives with progression items will wiggle.
|
||||||
|
Textures (Unchecked): Unchecked pots and crates are golden. Unchecked beehives will wiggle.
|
||||||
|
"""
|
||||||
display_name = "Pot, Crate, and Beehive Appearance"
|
display_name = "Pot, Crate, and Beehive Appearance"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_textures_content = 1
|
option_textures_content = 1
|
||||||
option_textures_unchecked = 2
|
option_textures_unchecked = 2
|
||||||
|
default = 2
|
||||||
|
|
||||||
|
|
||||||
class Hints(Choice):
|
class Hints(Choice):
|
||||||
"""Gossip Stones can give hints about item locations."""
|
"""Gossip Stones can give hints about item locations.
|
||||||
|
None: Gossip Stones do not give hints.
|
||||||
|
Mask: Gossip Stones give hints with Mask of Truth.
|
||||||
|
Agony: Gossip Stones give hints wtih Stone of Agony.
|
||||||
|
Always: Gossip Stones always give hints."""
|
||||||
display_name = "Gossip Stones"
|
display_name = "Gossip Stones"
|
||||||
option_none = 0
|
option_none = 0
|
||||||
option_mask = 1
|
option_mask = 1
|
||||||
|
@ -895,7 +979,9 @@ class MiscHints(DefaultOnToggle):
|
||||||
|
|
||||||
|
|
||||||
class HintDistribution(Choice):
|
class HintDistribution(Choice):
|
||||||
"""Choose the hint distribution to use. Affects the frequency of strong hints, which items are always hinted, etc."""
|
"""Choose the hint distribution to use. Affects the frequency of strong hints, which items are always hinted, etc.
|
||||||
|
Detailed documentation on hint distributions can be found on the Archipelago GitHub or OoTRandomizer.com.
|
||||||
|
The Async hint distribution is intended for async multiworlds. It removes Way of the Hero hints to improve generation times, since they are not very useful in asyncs."""
|
||||||
display_name = "Hint Distribution"
|
display_name = "Hint Distribution"
|
||||||
option_balanced = 0
|
option_balanced = 0
|
||||||
option_ddr = 1
|
option_ddr = 1
|
||||||
|
@ -907,10 +993,13 @@ class HintDistribution(Choice):
|
||||||
option_useless = 7
|
option_useless = 7
|
||||||
option_very_strong = 8
|
option_very_strong = 8
|
||||||
option_async = 9
|
option_async = 9
|
||||||
|
default = 9
|
||||||
|
|
||||||
|
|
||||||
class TextShuffle(Choice):
|
class TextShuffle(Choice):
|
||||||
"""Randomizes text in the game for comedic effect."""
|
"""Randomizes text in the game for comedic effect.
|
||||||
|
Except Hints: does not randomize important text such as hints, small/boss key information, and item prices.
|
||||||
|
Complete: randomizes every textbox, including the useful ones."""
|
||||||
display_name = "Text Shuffle"
|
display_name = "Text Shuffle"
|
||||||
option_none = 0
|
option_none = 0
|
||||||
option_except_hints = 1
|
option_except_hints = 1
|
||||||
|
@ -946,7 +1035,8 @@ class HeroMode(Toggle):
|
||||||
|
|
||||||
|
|
||||||
class StartingToD(Choice):
|
class StartingToD(Choice):
|
||||||
"""Change the starting time of day."""
|
"""Change the starting time of day.
|
||||||
|
Daytime starts at Sunrise and ends at Sunset. Default is between Morning and Noon."""
|
||||||
display_name = "Starting Time of Day"
|
display_name = "Starting Time of Day"
|
||||||
option_default = 0
|
option_default = 0
|
||||||
option_sunrise = 1
|
option_sunrise = 1
|
||||||
|
@ -999,7 +1089,11 @@ misc_options: typing.Dict[str, type(Option)] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ItemPoolValue(Choice):
|
class ItemPoolValue(Choice):
|
||||||
"""Changes the number of items available in the game."""
|
"""Changes the number of items available in the game.
|
||||||
|
Plentiful: One extra copy of every major item.
|
||||||
|
Balanced: Original item pool.
|
||||||
|
Scarce: Extra copies of major items are removed. Heart containers are removed.
|
||||||
|
Minimal: All major item upgrades not used for locations are removed. All health is removed."""
|
||||||
display_name = "Item Pool"
|
display_name = "Item Pool"
|
||||||
option_plentiful = 0
|
option_plentiful = 0
|
||||||
option_balanced = 1
|
option_balanced = 1
|
||||||
|
@ -1009,7 +1103,12 @@ class ItemPoolValue(Choice):
|
||||||
|
|
||||||
|
|
||||||
class IceTraps(Choice):
|
class IceTraps(Choice):
|
||||||
"""Adds ice traps to the item pool."""
|
"""Adds ice traps to the item pool.
|
||||||
|
Off: All ice traps are removed.
|
||||||
|
Normal: The vanilla quantity of ice traps are placed.
|
||||||
|
On/"Extra": There is a chance for some extra ice traps to be placed.
|
||||||
|
Mayhem: All added junk items are ice traps.
|
||||||
|
Onslaught: All junk items are replaced by ice traps, even those in the base pool."""
|
||||||
display_name = "Ice Traps"
|
display_name = "Ice Traps"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_normal = 1
|
option_normal = 1
|
||||||
|
@ -1021,34 +1120,27 @@ class IceTraps(Choice):
|
||||||
|
|
||||||
|
|
||||||
class IceTrapVisual(Choice):
|
class IceTrapVisual(Choice):
|
||||||
"""Changes the appearance of ice traps as freestanding items."""
|
"""Changes the appearance of traps, including other games' traps, as freestanding items."""
|
||||||
display_name = "Ice Trap Appearance"
|
display_name = "Trap Appearance"
|
||||||
option_major_only = 0
|
option_major_only = 0
|
||||||
option_junk_only = 1
|
option_junk_only = 1
|
||||||
option_anything = 2
|
option_anything = 2
|
||||||
|
|
||||||
|
|
||||||
class AdultTradeStart(OptionSet):
|
class AdultTradeStart(Choice):
|
||||||
"""Choose the items that can appear to start the adult trade sequence. By default it is Claim Check only."""
|
"""Choose the item that starts the adult trade sequence."""
|
||||||
display_name = "Adult Trade Sequence Items"
|
display_name = "Adult Trade Sequence Start"
|
||||||
default = {"Claim Check"}
|
option_pocket_egg = 0
|
||||||
valid_keys = {
|
option_pocket_cucco = 1
|
||||||
"Pocket Egg",
|
option_cojiro = 2
|
||||||
"Pocket Cucco",
|
option_odd_mushroom = 3
|
||||||
"Cojiro",
|
option_poachers_saw = 4
|
||||||
"Odd Mushroom",
|
option_broken_sword = 5
|
||||||
"Poachers Saw",
|
option_prescription = 6
|
||||||
"Broken Sword",
|
option_eyeball_frog = 7
|
||||||
"Prescription",
|
option_eyedrops = 8
|
||||||
"Eyeball Frog",
|
option_claim_check = 9
|
||||||
"Eyedrops",
|
default = 9
|
||||||
"Claim Check",
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, value: typing.Iterable[str]):
|
|
||||||
if not value:
|
|
||||||
value = self.default
|
|
||||||
super().__init__(value)
|
|
||||||
|
|
||||||
|
|
||||||
itempool_options: typing.Dict[str, type(Option)] = {
|
itempool_options: typing.Dict[str, type(Option)] = {
|
||||||
|
@ -1068,7 +1160,7 @@ class Targeting(Choice):
|
||||||
|
|
||||||
|
|
||||||
class DisplayDpad(DefaultOnToggle):
|
class DisplayDpad(DefaultOnToggle):
|
||||||
"""Show dpad icon on HUD for quick actions (ocarina, hover boots, iron boots)."""
|
"""Show dpad icon on HUD for quick actions (ocarina, hover boots, iron boots, mask)."""
|
||||||
display_name = "Display D-Pad HUD"
|
display_name = "Display D-Pad HUD"
|
||||||
|
|
||||||
|
|
||||||
|
@ -1191,7 +1283,6 @@ oot_options: typing.Dict[str, type(Option)] = {
|
||||||
**world_options,
|
**world_options,
|
||||||
**bridge_options,
|
**bridge_options,
|
||||||
**dungeon_items_options,
|
**dungeon_items_options,
|
||||||
# **lacs_options,
|
|
||||||
**shuffle_options,
|
**shuffle_options,
|
||||||
**timesavers_options,
|
**timesavers_options,
|
||||||
**misc_options,
|
**misc_options,
|
||||||
|
|
|
@ -2094,10 +2094,14 @@ def patch_rom(world, rom):
|
||||||
if not world.dungeon_mq['Ganons Castle']:
|
if not world.dungeon_mq['Ganons Castle']:
|
||||||
chest_name = 'Ganons Castle Light Trial Lullaby Chest'
|
chest_name = 'Ganons Castle Light Trial Lullaby Chest'
|
||||||
location = world.get_location(chest_name)
|
location = world.get_location(chest_name)
|
||||||
if location.item.game == 'Ocarina of Time':
|
if not location.item.trap:
|
||||||
item = read_rom_item(rom, location.item.index)
|
if location.item.game == 'Ocarina of Time':
|
||||||
|
item = read_rom_item(rom, location.item.index)
|
||||||
|
else:
|
||||||
|
item = read_rom_item(rom, AP_PROGRESSION if location.item.advancement else AP_JUNK)
|
||||||
else:
|
else:
|
||||||
item = read_rom_item(rom, AP_PROGRESSION if location.item.advancement else AP_JUNK)
|
looks_like_index = get_override_entry(world, location)[5]
|
||||||
|
item = read_rom_item(rom, looks_like_index)
|
||||||
if item['chest_type'] in (GOLD_CHEST, GILDED_CHEST, SKULL_CHEST_BIG):
|
if item['chest_type'] in (GOLD_CHEST, GILDED_CHEST, SKULL_CHEST_BIG):
|
||||||
rom.write_int16(0x321B176, 0xFC40) # original 0xFC48
|
rom.write_int16(0x321B176, 0xFC40) # original 0xFC48
|
||||||
|
|
||||||
|
@ -2106,10 +2110,14 @@ def patch_rom(world, rom):
|
||||||
chest_name = 'Spirit Temple Compass Chest'
|
chest_name = 'Spirit Temple Compass Chest'
|
||||||
chest_address = 0x2B6B07C
|
chest_address = 0x2B6B07C
|
||||||
location = world.get_location(chest_name)
|
location = world.get_location(chest_name)
|
||||||
if location.item.game == 'Ocarina of Time':
|
if not location.item.trap:
|
||||||
item = read_rom_item(rom, location.item.index)
|
if location.item.game == 'Ocarina of Time':
|
||||||
|
item = read_rom_item(rom, location.item.index)
|
||||||
|
else:
|
||||||
|
item = read_rom_item(rom, AP_PROGRESSION if location.item.advancement else AP_JUNK)
|
||||||
else:
|
else:
|
||||||
item = read_rom_item(rom, AP_PROGRESSION if location.item.advancement else AP_JUNK)
|
looks_like_index = get_override_entry(world, location)[5]
|
||||||
|
item = read_rom_item(rom, looks_like_index)
|
||||||
if item['chest_type'] in (BROWN_CHEST, SILVER_CHEST, SKULL_CHEST_SMALL):
|
if item['chest_type'] in (BROWN_CHEST, SILVER_CHEST, SKULL_CHEST_SMALL):
|
||||||
rom.write_int16(chest_address + 2, 0x0190) # X pos
|
rom.write_int16(chest_address + 2, 0x0190) # X pos
|
||||||
rom.write_int16(chest_address + 6, 0xFABC) # Z pos
|
rom.write_int16(chest_address + 6, 0xFABC) # Z pos
|
||||||
|
@ -2120,10 +2128,14 @@ def patch_rom(world, rom):
|
||||||
chest_address_0 = 0x21A02D0 # Address in setup 0
|
chest_address_0 = 0x21A02D0 # Address in setup 0
|
||||||
chest_address_2 = 0x21A06E4 # Address in setup 2
|
chest_address_2 = 0x21A06E4 # Address in setup 2
|
||||||
location = world.get_location(chest_name)
|
location = world.get_location(chest_name)
|
||||||
if location.item.game == 'Ocarina of Time':
|
if not location.item.trap:
|
||||||
item = read_rom_item(rom, location.item.index)
|
if location.item.game == 'Ocarina of Time':
|
||||||
|
item = read_rom_item(rom, location.item.index)
|
||||||
|
else:
|
||||||
|
item = read_rom_item(rom, AP_PROGRESSION if location.item.advancement else AP_JUNK)
|
||||||
else:
|
else:
|
||||||
item = read_rom_item(rom, AP_PROGRESSION if location.item.advancement else AP_JUNK)
|
looks_like_index = get_override_entry(world, location)[5]
|
||||||
|
item = read_rom_item(rom, looks_like_index)
|
||||||
if item['chest_type'] in (BROWN_CHEST, SILVER_CHEST, SKULL_CHEST_SMALL):
|
if item['chest_type'] in (BROWN_CHEST, SILVER_CHEST, SKULL_CHEST_SMALL):
|
||||||
rom.write_int16(chest_address_0 + 6, 0x0172) # Z pos
|
rom.write_int16(chest_address_0 + 6, 0x0172) # Z pos
|
||||||
rom.write_int16(chest_address_2 + 6, 0x0172) # Z pos
|
rom.write_int16(chest_address_2 + 6, 0x0172) # Z pos
|
||||||
|
|
|
@ -223,9 +223,6 @@ def set_shop_rules(ootworld):
|
||||||
# The goal is to automatically set item rules based on age requirements in case entrances were shuffled
|
# The goal is to automatically set item rules based on age requirements in case entrances were shuffled
|
||||||
def set_entrances_based_rules(ootworld):
|
def set_entrances_based_rules(ootworld):
|
||||||
|
|
||||||
if ootworld.multiworld.accessibility == 'beatable':
|
|
||||||
return
|
|
||||||
|
|
||||||
all_state = ootworld.multiworld.get_all_state(False)
|
all_state = ootworld.multiworld.get_all_state(False)
|
||||||
|
|
||||||
for location in filter(lambda location: location.type == 'Shop', ootworld.get_locations()):
|
for location in filter(lambda location: location.type == 'Shop', ootworld.get_locations()):
|
||||||
|
|
|
@ -10,7 +10,7 @@ from string import printable
|
||||||
|
|
||||||
logger = logging.getLogger("Ocarina of Time")
|
logger = logging.getLogger("Ocarina of Time")
|
||||||
|
|
||||||
from .Location import OOTLocation, LocationFactory, location_name_to_id
|
from .Location import OOTLocation, LocationFactory, location_name_to_id, build_location_name_groups
|
||||||
from .Entrance import OOTEntrance
|
from .Entrance import OOTEntrance
|
||||||
from .EntranceShuffle import shuffle_random_entrances, entrance_shuffle_table, EntranceShuffleError
|
from .EntranceShuffle import shuffle_random_entrances, entrance_shuffle_table, EntranceShuffleError
|
||||||
from .HintList import getRequiredHints
|
from .HintList import getRequiredHints
|
||||||
|
@ -163,11 +163,13 @@ class OOTWorld(World):
|
||||||
"Bottle with Big Poe", "Bottle with Red Potion", "Bottle with Green Potion",
|
"Bottle with Big Poe", "Bottle with Red Potion", "Bottle with Green Potion",
|
||||||
"Bottle with Blue Potion", "Bottle with Fairy", "Bottle with Fish",
|
"Bottle with Blue Potion", "Bottle with Fairy", "Bottle with Fish",
|
||||||
"Bottle with Blue Fire", "Bottle with Bugs", "Bottle with Poe"},
|
"Bottle with Blue Fire", "Bottle with Bugs", "Bottle with Poe"},
|
||||||
"Adult Trade Item": {"Pocket Egg", "Pocket Cucco", "Odd Mushroom",
|
"Adult Trade Item": {"Pocket Egg", "Pocket Cucco", "Cojiro", "Odd Mushroom",
|
||||||
"Odd Potion", "Poachers Saw", "Broken Sword", "Prescription",
|
"Odd Potion", "Poachers Saw", "Broken Sword", "Prescription",
|
||||||
"Eyeball Frog", "Eyedrops", "Claim Check"}
|
"Eyeball Frog", "Eyedrops", "Claim Check"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location_name_groups = build_location_name_groups()
|
||||||
|
|
||||||
def __init__(self, world, player):
|
def __init__(self, world, player):
|
||||||
self.hint_data_available = threading.Event()
|
self.hint_data_available = threading.Event()
|
||||||
self.collectible_flags_available = threading.Event()
|
self.collectible_flags_available = threading.Event()
|
||||||
|
@ -384,6 +386,7 @@ class OOTWorld(World):
|
||||||
self.mq_dungeons_mode = 'count'
|
self.mq_dungeons_mode = 'count'
|
||||||
self.mq_dungeons_count = 0
|
self.mq_dungeons_count = 0
|
||||||
self.dungeon_mq = {item['name']: (item['name'] in mq_dungeons) for item in dungeon_table}
|
self.dungeon_mq = {item['name']: (item['name'] in mq_dungeons) for item in dungeon_table}
|
||||||
|
self.dungeon_mq['Thieves Hideout'] = False # fix for bug in SaveContext:287
|
||||||
|
|
||||||
# Empty dungeon placeholder for the moment
|
# Empty dungeon placeholder for the moment
|
||||||
self.empty_dungeons = {name: False for name in self.dungeon_mq}
|
self.empty_dungeons = {name: False for name in self.dungeon_mq}
|
||||||
|
@ -409,6 +412,9 @@ class OOTWorld(World):
|
||||||
self.starting_tod = self.starting_tod.replace('_', '-')
|
self.starting_tod = self.starting_tod.replace('_', '-')
|
||||||
self.shuffle_scrubs = self.shuffle_scrubs.replace('_prices', '')
|
self.shuffle_scrubs = self.shuffle_scrubs.replace('_prices', '')
|
||||||
|
|
||||||
|
# Convert adult trade option to expected Set
|
||||||
|
self.adult_trade_start = {self.adult_trade_start.title().replace('_', ' ')}
|
||||||
|
|
||||||
# Get hint distribution
|
# Get hint distribution
|
||||||
self.hint_dist_user = read_json(data_path('Hints', f'{self.hint_dist}.json'))
|
self.hint_dist_user = read_json(data_path('Hints', f'{self.hint_dist}.json'))
|
||||||
|
|
||||||
|
@ -446,7 +452,7 @@ class OOTWorld(World):
|
||||||
self.always_hints = [hint.name for hint in getRequiredHints(self)]
|
self.always_hints = [hint.name for hint in getRequiredHints(self)]
|
||||||
|
|
||||||
# Determine items which are not considered advancement based on settings. They will never be excluded.
|
# Determine items which are not considered advancement based on settings. They will never be excluded.
|
||||||
self.nonadvancement_items = {'Double Defense'}
|
self.nonadvancement_items = {'Double Defense', 'Deku Stick Capacity', 'Deku Nut Capacity'}
|
||||||
if (self.damage_multiplier != 'ohko' and self.damage_multiplier != 'quadruple' and
|
if (self.damage_multiplier != 'ohko' and self.damage_multiplier != 'quadruple' and
|
||||||
self.shuffle_scrubs == 'off' and not self.shuffle_grotto_entrances):
|
self.shuffle_scrubs == 'off' and not self.shuffle_grotto_entrances):
|
||||||
# nayru's love may be required to prevent forced damage
|
# nayru's love may be required to prevent forced damage
|
||||||
|
@ -633,16 +639,18 @@ class OOTWorld(World):
|
||||||
self.multiworld.itempool.remove(item)
|
self.multiworld.itempool.remove(item)
|
||||||
self.hinted_dungeon_reward_locations[item.name] = loc
|
self.hinted_dungeon_reward_locations[item.name] = loc
|
||||||
|
|
||||||
def create_item(self, name: str):
|
def create_item(self, name: str, allow_arbitrary_name: bool = False):
|
||||||
if name in item_table:
|
if name in item_table:
|
||||||
return OOTItem(name, self.player, item_table[name], False,
|
return OOTItem(name, self.player, item_table[name], False,
|
||||||
(name in self.nonadvancement_items if getattr(self, 'nonadvancement_items',
|
(name in self.nonadvancement_items if getattr(self, 'nonadvancement_items',
|
||||||
None) else False))
|
None) else False))
|
||||||
return OOTItem(name, self.player, ('Event', True, None, None), True, False)
|
if allow_arbitrary_name:
|
||||||
|
return OOTItem(name, self.player, ('Event', True, None, None), True, False)
|
||||||
|
raise Exception(f"Invalid item name: {name}")
|
||||||
|
|
||||||
def make_event_item(self, name, location, item=None):
|
def make_event_item(self, name, location, item=None):
|
||||||
if item is None:
|
if item is None:
|
||||||
item = self.create_item(name)
|
item = self.create_item(name, allow_arbitrary_name=True)
|
||||||
self.multiworld.push_item(location, item, collect=False)
|
self.multiworld.push_item(location, item, collect=False)
|
||||||
location.locked = True
|
location.locked = True
|
||||||
location.event = True
|
location.event = True
|
||||||
|
@ -800,23 +808,25 @@ class OOTWorld(World):
|
||||||
self.multiworld.itempool.remove(item)
|
self.multiworld.itempool.remove(item)
|
||||||
self.multiworld.random.shuffle(locations)
|
self.multiworld.random.shuffle(locations)
|
||||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), locations, stage_items,
|
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), locations, stage_items,
|
||||||
single_player_placement=True, lock=True)
|
single_player_placement=True, lock=True, allow_excluded=True)
|
||||||
else:
|
else:
|
||||||
for dungeon_info in dungeon_table:
|
for dungeon_info in dungeon_table:
|
||||||
dungeon_name = dungeon_info['name']
|
dungeon_name = dungeon_info['name']
|
||||||
locations = gather_locations(self.multiworld, fill_stage, self.player, dungeon=dungeon_name)
|
locations = gather_locations(self.multiworld, fill_stage, self.player, dungeon=dungeon_name)
|
||||||
if isinstance(locations, list):
|
if isinstance(locations, list):
|
||||||
dungeon_items = list(filter(lambda item: dungeon_name in item.name, stage_items))
|
dungeon_items = list(filter(lambda item: dungeon_name in item.name, stage_items))
|
||||||
|
if not dungeon_items:
|
||||||
|
continue
|
||||||
for item in dungeon_items:
|
for item in dungeon_items:
|
||||||
self.multiworld.itempool.remove(item)
|
self.multiworld.itempool.remove(item)
|
||||||
self.multiworld.random.shuffle(locations)
|
self.multiworld.random.shuffle(locations)
|
||||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), locations, dungeon_items,
|
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), locations, dungeon_items,
|
||||||
single_player_placement=True, lock=True)
|
single_player_placement=True, lock=True, allow_excluded=True)
|
||||||
|
|
||||||
# Place songs
|
# Place songs
|
||||||
# 5 built-in retries because this section can fail sometimes
|
# 5 built-in retries because this section can fail sometimes
|
||||||
if self.shuffle_song_items != 'any':
|
if self.shuffle_song_items != 'any':
|
||||||
tries = 5
|
tries = 10
|
||||||
if self.shuffle_song_items == 'song':
|
if self.shuffle_song_items == 'song':
|
||||||
song_locations = list(filter(lambda location: location.type == 'Song',
|
song_locations = list(filter(lambda location: location.type == 'Song',
|
||||||
self.multiworld.get_unfilled_locations(player=self.player)))
|
self.multiworld.get_unfilled_locations(player=self.player)))
|
||||||
|
@ -852,7 +862,7 @@ class OOTWorld(World):
|
||||||
try:
|
try:
|
||||||
self.multiworld.random.shuffle(song_locations)
|
self.multiworld.random.shuffle(song_locations)
|
||||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), song_locations[:], songs[:],
|
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), song_locations[:], songs[:],
|
||||||
True, True)
|
single_player_placement=True, lock=True, allow_excluded=True)
|
||||||
logger.debug(f"Successfully placed songs for player {self.player} after {6 - tries} attempt(s)")
|
logger.debug(f"Successfully placed songs for player {self.player} after {6 - tries} attempt(s)")
|
||||||
except FillError as e:
|
except FillError as e:
|
||||||
tries -= 1
|
tries -= 1
|
||||||
|
@ -888,7 +898,8 @@ class OOTWorld(World):
|
||||||
self.multiworld.random.shuffle(shop_locations)
|
self.multiworld.random.shuffle(shop_locations)
|
||||||
for item in shop_prog + shop_junk:
|
for item in shop_prog + shop_junk:
|
||||||
self.multiworld.itempool.remove(item)
|
self.multiworld.itempool.remove(item)
|
||||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), shop_locations, shop_prog, True, True)
|
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), shop_locations, shop_prog,
|
||||||
|
single_player_placement=True, lock=True, allow_excluded=True)
|
||||||
fast_fill(self.multiworld, shop_junk, shop_locations)
|
fast_fill(self.multiworld, shop_junk, shop_locations)
|
||||||
for loc in shop_locations:
|
for loc in shop_locations:
|
||||||
loc.locked = True
|
loc.locked = True
|
||||||
|
@ -963,7 +974,7 @@ class OOTWorld(World):
|
||||||
multiworld.itempool.remove(item)
|
multiworld.itempool.remove(item)
|
||||||
multiworld.random.shuffle(locations)
|
multiworld.random.shuffle(locations)
|
||||||
fill_restrictive(multiworld, multiworld.get_all_state(False), locations, group_stage_items,
|
fill_restrictive(multiworld, multiworld.get_all_state(False), locations, group_stage_items,
|
||||||
single_player_placement=False, lock=True)
|
single_player_placement=False, lock=True, allow_excluded=True)
|
||||||
if fill_stage == 'Song':
|
if fill_stage == 'Song':
|
||||||
# We don't want song locations to contain progression unless it's a song
|
# We don't want song locations to contain progression unless it's a song
|
||||||
# or it was marked as priority.
|
# or it was marked as priority.
|
||||||
|
@ -984,7 +995,7 @@ class OOTWorld(World):
|
||||||
multiworld.itempool.remove(item)
|
multiworld.itempool.remove(item)
|
||||||
multiworld.random.shuffle(locations)
|
multiworld.random.shuffle(locations)
|
||||||
fill_restrictive(multiworld, multiworld.get_all_state(False), locations, group_dungeon_items,
|
fill_restrictive(multiworld, multiworld.get_all_state(False), locations, group_dungeon_items,
|
||||||
single_player_placement=False, lock=True)
|
single_player_placement=False, lock=True, allow_excluded=True)
|
||||||
|
|
||||||
def generate_output(self, output_directory: str):
|
def generate_output(self, output_directory: str):
|
||||||
if self.hints != 'none':
|
if self.hints != 'none':
|
||||||
|
@ -1051,7 +1062,10 @@ class OOTWorld(World):
|
||||||
def stage_generate_output(cls, multiworld: MultiWorld, output_directory: str):
|
def stage_generate_output(cls, multiworld: MultiWorld, output_directory: str):
|
||||||
def hint_type_players(hint_type: str) -> set:
|
def hint_type_players(hint_type: str) -> set:
|
||||||
return {autoworld.player for autoworld in multiworld.get_game_worlds("Ocarina of Time")
|
return {autoworld.player for autoworld in multiworld.get_game_worlds("Ocarina of Time")
|
||||||
if autoworld.hints != 'none' and autoworld.hint_dist_user['distribution'][hint_type]['copies'] > 0}
|
if autoworld.hints != 'none'
|
||||||
|
and autoworld.hint_dist_user['distribution'][hint_type]['copies'] > 0
|
||||||
|
and (autoworld.hint_dist_user['distribution'][hint_type]['fixed'] > 0
|
||||||
|
or autoworld.hint_dist_user['distribution'][hint_type]['weight'] > 0)}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
item_hint_players = hint_type_players('item')
|
item_hint_players = hint_type_players('item')
|
||||||
|
@ -1078,10 +1092,10 @@ class OOTWorld(World):
|
||||||
|
|
||||||
if loc.game == "Ocarina of Time" and loc.item.code and (not loc.locked or
|
if loc.game == "Ocarina of Time" and loc.item.code and (not loc.locked or
|
||||||
(oot_is_item_of_type(loc.item, 'Song') or
|
(oot_is_item_of_type(loc.item, 'Song') or
|
||||||
(oot_is_item_of_type(loc.item, 'SmallKey') and multiworld.worlds[loc.player].shuffle_smallkeys == 'any_dungeon') or
|
(oot_is_item_of_type(loc.item, 'SmallKey') and multiworld.worlds[loc.player].shuffle_smallkeys in ('overworld', 'any_dungeon', 'regional')) or
|
||||||
(oot_is_item_of_type(loc.item, 'HideoutSmallKey') and multiworld.worlds[loc.player].shuffle_hideoutkeys == 'any_dungeon') or
|
(oot_is_item_of_type(loc.item, 'HideoutSmallKey') and multiworld.worlds[loc.player].shuffle_hideoutkeys in ('overworld', 'any_dungeon', 'regional')) or
|
||||||
(oot_is_item_of_type(loc.item, 'BossKey') and multiworld.worlds[loc.player].shuffle_bosskeys == 'any_dungeon') or
|
(oot_is_item_of_type(loc.item, 'BossKey') and multiworld.worlds[loc.player].shuffle_bosskeys in ('overworld', 'any_dungeon', 'regional')) or
|
||||||
(oot_is_item_of_type(loc.item, 'GanonBossKey') and multiworld.worlds[loc.player].shuffle_ganon_bosskey == 'any_dungeon'))):
|
(oot_is_item_of_type(loc.item, 'GanonBossKey') and multiworld.worlds[loc.player].shuffle_ganon_bosskey in ('overworld', 'any_dungeon', 'regional')))):
|
||||||
if loc.player in barren_hint_players:
|
if loc.player in barren_hint_players:
|
||||||
hint_area = get_hint_area(loc)
|
hint_area = get_hint_area(loc)
|
||||||
items_by_region[loc.player][hint_area]['weight'] += 1
|
items_by_region[loc.player][hint_area]['weight'] += 1
|
||||||
|
@ -1096,7 +1110,12 @@ class OOTWorld(World):
|
||||||
elif barren_hint_players or woth_hint_players: # Check only relevant oot locations for barren/woth
|
elif barren_hint_players or woth_hint_players: # Check only relevant oot locations for barren/woth
|
||||||
for player in (barren_hint_players | woth_hint_players):
|
for player in (barren_hint_players | woth_hint_players):
|
||||||
for loc in multiworld.worlds[player].get_locations():
|
for loc in multiworld.worlds[player].get_locations():
|
||||||
if loc.item.code and (not loc.locked or oot_is_item_of_type(loc.item, 'Song')):
|
if loc.item.code and (not loc.locked or
|
||||||
|
(oot_is_item_of_type(loc.item, 'Song') or
|
||||||
|
(oot_is_item_of_type(loc.item, 'SmallKey') and multiworld.worlds[loc.player].shuffle_smallkeys in ('overworld', 'any_dungeon', 'regional')) or
|
||||||
|
(oot_is_item_of_type(loc.item, 'HideoutSmallKey') and multiworld.worlds[loc.player].shuffle_hideoutkeys in ('overworld', 'any_dungeon', 'regional')) or
|
||||||
|
(oot_is_item_of_type(loc.item, 'BossKey') and multiworld.worlds[loc.player].shuffle_bosskeys in ('overworld', 'any_dungeon', 'regional')) or
|
||||||
|
(oot_is_item_of_type(loc.item, 'GanonBossKey') and multiworld.worlds[loc.player].shuffle_ganon_bosskey in ('overworld', 'any_dungeon', 'regional')))):
|
||||||
if player in barren_hint_players:
|
if player in barren_hint_players:
|
||||||
hint_area = get_hint_area(loc)
|
hint_area = get_hint_area(loc)
|
||||||
items_by_region[player][hint_area]['weight'] += 1
|
items_by_region[player][hint_area]['weight'] += 1
|
||||||
|
@ -1183,6 +1202,15 @@ class OOTWorld(World):
|
||||||
er_hint_data[self.player][location.address] = main_entrance.name
|
er_hint_data[self.player][location.address] = main_entrance.name
|
||||||
logger.debug(f"Set {location.name} hint data to {main_entrance.name}")
|
logger.debug(f"Set {location.name} hint data to {main_entrance.name}")
|
||||||
|
|
||||||
|
def write_spoiler(self, spoiler_handle: typing.TextIO) -> None:
|
||||||
|
required_trials_str = ", ".join(t for t in self.skipped_trials if not self.skipped_trials[t])
|
||||||
|
spoiler_handle.write(f"\n\nTrials ({self.multiworld.get_player_name(self.player)}): {required_trials_str}\n")
|
||||||
|
|
||||||
|
if self.shopsanity != 'off':
|
||||||
|
spoiler_handle.write(f"\nShop Prices ({self.multiworld.get_player_name(self.player)}):\n")
|
||||||
|
for k, v in self.shop_prices.items():
|
||||||
|
spoiler_handle.write(f"{k}: {v} Rupees\n")
|
||||||
|
|
||||||
# Key ring handling:
|
# Key ring handling:
|
||||||
# Key rings are multiple items glued together into one, so we need to give
|
# Key rings are multiple items glued together into one, so we need to give
|
||||||
# the appropriate number of keys in the collection state when they are
|
# the appropriate number of keys in the collection state when they are
|
||||||
|
@ -1265,25 +1293,13 @@ class OOTWorld(World):
|
||||||
# Specifically ensures that only real items are gotten, not any events.
|
# Specifically ensures that only real items are gotten, not any events.
|
||||||
# In particular, ensures that Time Travel needs to be found.
|
# In particular, ensures that Time Travel needs to be found.
|
||||||
def get_state_with_complete_itempool(self):
|
def get_state_with_complete_itempool(self):
|
||||||
all_state = self.multiworld.get_all_state(use_cache=False)
|
all_state = CollectionState(self.multiworld)
|
||||||
# Remove event progression items
|
for item in self.multiworld.itempool:
|
||||||
for item, player in all_state.prog_items:
|
if item.player == self.player:
|
||||||
if player == self.player and (item not in item_table or item_table[item][2] is None):
|
self.multiworld.worlds[item.player].collect(all_state, item)
|
||||||
all_state.prog_items[(item, player)] = 0
|
|
||||||
# Remove all events and checked locations
|
|
||||||
all_state.locations_checked = {loc for loc in all_state.locations_checked if loc.player != self.player}
|
|
||||||
all_state.events = {loc for loc in all_state.events if loc.player != self.player}
|
|
||||||
# If free_scarecrow give Scarecrow Song
|
# If free_scarecrow give Scarecrow Song
|
||||||
if self.free_scarecrow:
|
if self.free_scarecrow:
|
||||||
all_state.collect(self.create_item("Scarecrow Song"), event=True)
|
all_state.collect(self.create_item("Scarecrow Song"), event=True)
|
||||||
|
|
||||||
# Invalidate caches
|
|
||||||
all_state.child_reachable_regions[self.player] = set()
|
|
||||||
all_state.adult_reachable_regions[self.player] = set()
|
|
||||||
all_state.child_blocked_connections[self.player] = set()
|
|
||||||
all_state.adult_blocked_connections[self.player] = set()
|
|
||||||
all_state.day_reachable_regions[self.player] = set()
|
|
||||||
all_state.dampe_reachable_regions[self.player] = set()
|
|
||||||
all_state.stale[self.player] = True
|
all_state.stale[self.player] = True
|
||||||
|
|
||||||
return all_state
|
return all_state
|
||||||
|
@ -1349,7 +1365,7 @@ def gather_locations(multiworld: MultiWorld,
|
||||||
condition = lambda location: location.name in dungeon_song_locations
|
condition = lambda location: location.name in dungeon_song_locations
|
||||||
locations += filter(condition, multiworld.get_unfilled_locations(player=player))
|
locations += filter(condition, multiworld.get_unfilled_locations(player=player))
|
||||||
else:
|
else:
|
||||||
if any(map(lambda v: v in {'keysanity'}, fill_opts.values())):
|
if any(map(lambda v: v == 'keysanity', fill_opts.values())):
|
||||||
return None
|
return None
|
||||||
for player, option in fill_opts.items():
|
for player, option in fill_opts.items():
|
||||||
condition = functools.partial(valid_dungeon_item_location,
|
condition = functools.partial(valid_dungeon_item_location,
|
||||||
|
|
Loading…
Reference in New Issue