Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LTTP: Key Drop Shuffle #282

Merged
merged 53 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
b7c13a6
Key Drop Shuffle initial implementation
Alchav Feb 11, 2022
3533a89
Key Drop Shuffle updates
Alchav Feb 15, 2022
21a0c22
Key Drop Shuffle episode 3
Alchav Feb 15, 2022
ead2e83
get_filler_item_name fix
Alchav Feb 15, 2022
a0ce77f
Remove keydrop items from state before going into fill_restrictive fr…
Alchav Feb 16, 2022
89713d8
Merge remote-tracking branch 'origin/key_drop_shuffle' into key_drop_…
Alchav Feb 16, 2022
d83b937
key drop shuffle episode 6
Alchav Feb 16, 2022
9d28a53
key drop shuffle chapter 7
Alchav Feb 17, 2022
982cb90
Key Drop Shuffle part 8: Universal Keys
Alchav Feb 18, 2022
d9ac8a9
Keydrop shuffle (#3)
espeon65536 Feb 19, 2022
0567018
Key drop shuffle number 9
Alchav Feb 19, 2022
65f4879
revert MM key logic to always 2 keys for map and main lobby, regardle…
espeon65536 Mar 29, 2022
c1714af
TR keydrop logic
espeon65536 Mar 29, 2022
941361a
chain chomp south door also considers bk in pokey 1 to be frontlocked
espeon65536 Mar 29, 2022
c7781d6
rework GT key logic again
espeon65536 Apr 10, 2022
d4d3727
Merge pull request #4 from espeon65536/key_drop_shuffle
Alchav Apr 14, 2022
f660a38
Merge remote-tracking branch 'origin/key_drop_shuffle' into key_drop_…
Alchav Apr 14, 2022
3a765b6
Merge branch 'main' into key_drop_shuffle
Alchav Apr 14, 2022
825bafb
Fix TR connections
Alchav Apr 17, 2022
eb4277a
Fix SW unit tests
Alchav Apr 17, 2022
d89ad73
Key Drop Shuffle updates
Alchav Apr 18, 2022
2d5090d
KDS parenthesis fix
Alchav May 15, 2022
9455df4
Merge branch 'main' into key_drop_shuffle
Alchav Jun 5, 2022
0ba188e
Add KDS keys to get_pre_fill_items
Alchav Jun 10, 2022
b73d3cd
Merge branch 'main' into key_drop_shuffle
Alchav Jun 19, 2022
bb1ec8b
Finally fix Key Drop Shuffle?
Alchav Jun 19, 2022
3e8b8cb
KDS unit test fixes
Alchav Jun 19, 2022
5b7acfe
KDS fixes
Alchav Jun 22, 2022
4dbbdea
Hyrule key logic fix
Alchav Jun 28, 2022
554133c
Merge branch 'main' into kds2023
Alchav Mar 12, 2023
2689eef
Various fixes
Alchav Mar 12, 2023
d622b41
KDS Turtle Rock updates
Alchav Mar 12, 2023
56bd922
Merge branch 'main' into key_drop_shuffle
Alchav Mar 12, 2023
731544f
KDS fixes
Alchav Mar 12, 2023
226e975
Remove commented-out code
Alchav Mar 12, 2023
bbd4c0d
Add KDS to playerSettings.yaml
Alchav Mar 12, 2023
4a841ef
Merge branch 'main' into key_drop_shuffle
Alchav Aug 27, 2023
7b41c4e
Merge branch 'main' into key_drop_shuffle
Alchav Sep 10, 2023
e65e68a
Fix issues after merge
Alchav Sep 10, 2023
b785e1e
Update worlds/alttp/ItemPool.py
Alchav Sep 10, 2023
d883adc
update yaml
Alchav Sep 10, 2023
1e669da
re-disable problematic always allow rule
Alchav Sep 10, 2023
5ed7403
re-enable commented out code that is kinda important
Alchav Sep 11, 2023
df4afb1
fix thing
Alchav Sep 11, 2023
dd2e035
Various Rules fixes
Alchav Sep 11, 2023
7e699ab
Merge remote-tracking branch 'origin/key_drop_shuffle' into key_drop_…
Alchav Sep 11, 2023
2a183c1
Theives' Town Big Chest fix
Alchav Sep 11, 2023
8099908
Theives' Town unit test fix
Alchav Sep 11, 2023
5db2907
Logic adjustments
Alchav Sep 18, 2023
1e72e8b
Remove unnecessary Bombs (10) logic in can_kill_most_things to fix un…
Alchav Sep 18, 2023
6d798cc
Revert logic changes until bomb logic
Alchav Sep 19, 2023
c0c5219
Key drop shuffle in slot data
Alchav Sep 24, 2023
9cf953f
Pot Shuffle can move Conveyor Cross Pot Key into the Hookshot room
Alchav Sep 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions Fill.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,8 +753,6 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None:
else: # not reachable with swept state
non_early_locations[loc.player].append(loc.name)

# TODO: remove. Preferably by implementing key drop
from worlds.alttp.Regions import key_drop_data
world_name_lookup = world.world_name_lookup

block_value = typing.Union[typing.List[str], typing.Dict[str, typing.Any], str]
Expand Down Expand Up @@ -897,10 +895,6 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None:
for item_name in items:
item = world.worlds[player].create_item(item_name)
for location in reversed(candidates):
if location in key_drop_data:
warn(
f"Can't place '{item_name}' at '{placement.location}', as key drop shuffle locations are not supported yet.")
continue
if not location.item:
if location.item_rule(item):
if location.can_fill(world.state, item, False):
Expand Down
5 changes: 4 additions & 1 deletion playerSettings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ name: YourName{number} # Your name in-game. Spaces will be replaced with undersc
game: # Pick a game to play
A Link to the Past: 1
requires:
version: 0.3.3 # Version of Archipelago required for this yaml to work as expected.
version: 0.4.3 # Version of Archipelago required for this yaml to work as expected.
A Link to the Past:
progression_balancing:
# A system that can move progression earlier, to try and prevent the player from getting stuck and bored early.
Expand Down Expand Up @@ -114,6 +114,9 @@ A Link to the Past:
different_world: 0
universal: 0
start_with: 0
key_drop_shuffle: # Shuffle keys found in pots or dropped from killed enemies
off: 50
on: 0
compass_shuffle: # Compass Placement
original_dungeon: 50
own_dungeons: 0
Expand Down
14 changes: 10 additions & 4 deletions test/TestBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ def get_state(self, items):
state = CollectionState(self.multiworld)
for item in items:
item.classification = ItemClassification.progression
state.collect(item)
state.collect(item, event=True)
state.sweep_for_events()
state.update_reachable_regions(1)
self._state_cache[self.multiworld, tuple(items)] = state
return state

Expand All @@ -53,15 +54,19 @@ def run_location_tests(self, access_pool):
with self.subTest(msg="Reach Location", location=location, access=access, items=items,
all_except=all_except, path=path, entry=i):

self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access)
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access,
f"failed {self.multiworld.get_location(location, 1)} with: {item_pool}")

# check for partial solution
if not all_except and access: # we are not supposed to be able to reach location with partial inventory
for missing_item in item_pool[0]:
with self.subTest(msg="Location reachable without required item", location=location,
items=item_pool[0], missing_item=missing_item, entry=i):
state = self._get_items_partial(item_pool, missing_item)
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), False)

self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), False,
f"failed {self.multiworld.get_location(location, 1)}: succeeded with "
f"{missing_item} removed from: {item_pool}")

def run_entrance_tests(self, access_pool):
for i, (entrance, access, *item_pool) in enumerate(access_pool):
Expand All @@ -80,7 +85,8 @@ def run_entrance_tests(self, access_pool):
with self.subTest(msg="Entrance reachable without required item", entrance=entrance,
items=item_pool[0], missing_item=missing_item, entry=i):
state = self._get_items_partial(item_pool, missing_item)
self.assertEqual(self.multiworld.get_entrance(entrance, 1).can_reach(state), False)
self.assertEqual(self.multiworld.get_entrance(entrance, 1).can_reach(state), False,
f"failed {self.multiworld.get_entrance(entrance, 1)} with: {item_pool}")

def _get_items(self, item_pool, all_except):
if all_except and len(all_except) > 0:
Expand Down
2 changes: 1 addition & 1 deletion worlds/alttp/Client.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
"Hyrule Castle - Zelda's Chest": (0x80, 0x10),
'Hyrule Castle - Big Key Drop': (0x80, 0x400),
'Sewers - Dark Cross': (0x32, 0x10),
'Hyrule Castle - Key Rat Key Drop': (0x21, 0x400),
'Sewers - Key Rat Key Drop': (0x21, 0x400),
'Sewers - Secret Room - Left': (0x11, 0x10),
'Sewers - Secret Room - Middle': (0x11, 0x20),
'Sewers - Secret Room - Right': (0x11, 0x40),
Expand Down
59 changes: 37 additions & 22 deletions worlds/alttp/Dungeons.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from .Bosses import BossFactory, Boss
from .Items import ItemFactory
from .Regions import lookup_boss_drops
from .Regions import lookup_boss_drops, key_drop_data
from .Options import smallkey_shuffle

if typing.TYPE_CHECKING:
Expand Down Expand Up @@ -81,15 +81,17 @@ def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dunge
return dungeon

ES = make_dungeon('Hyrule Castle', None, ['Hyrule Castle', 'Sewers', 'Sewer Drop', 'Sewers (Dark)', 'Sanctuary'],
None, [ItemFactory('Small Key (Hyrule Castle)', player)],
ItemFactory('Big Key (Hyrule Castle)', player),
ItemFactory(['Small Key (Hyrule Castle)'] * 4, player),
[ItemFactory('Map (Hyrule Castle)', player)])
EP = make_dungeon('Eastern Palace', 'Armos Knights', ['Eastern Palace'],
ItemFactory('Big Key (Eastern Palace)', player), [],
ItemFactory('Big Key (Eastern Palace)', player),
ItemFactory(['Small Key (Eastern Palace)'] * 2, player),
ItemFactory(['Map (Eastern Palace)', 'Compass (Eastern Palace)'], player))
DP = make_dungeon('Desert Palace', 'Lanmolas',
['Desert Palace North', 'Desert Palace Main (Inner)', 'Desert Palace Main (Outer)',
'Desert Palace East'], ItemFactory('Big Key (Desert Palace)', player),
[ItemFactory('Small Key (Desert Palace)', player)],
ItemFactory(['Small Key (Desert Palace)'] * 4, player),
ItemFactory(['Map (Desert Palace)', 'Compass (Desert Palace)'], player))
ToH = make_dungeon('Tower of Hera', 'Moldorm',
['Tower of Hera (Bottom)', 'Tower of Hera (Basement)', 'Tower of Hera (Top)'],
Expand All @@ -105,60 +107,63 @@ def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dunge
ItemFactory(['Small Key (Palace of Darkness)'] * 6, player),
ItemFactory(['Map (Palace of Darkness)', 'Compass (Palace of Darkness)'], player))
TT = make_dungeon('Thieves Town', 'Blind', ['Thieves Town (Entrance)', 'Thieves Town (Deep)', 'Blind Fight'],
ItemFactory('Big Key (Thieves Town)', player), [ItemFactory('Small Key (Thieves Town)', player)],
ItemFactory('Big Key (Thieves Town)', player),
ItemFactory(['Small Key (Thieves Town)'] * 3, player),
ItemFactory(['Map (Thieves Town)', 'Compass (Thieves Town)'], player))
SW = make_dungeon('Skull Woods', 'Mothula', ['Skull Woods Final Section (Entrance)', 'Skull Woods First Section',
'Skull Woods Second Section', 'Skull Woods Second Section (Drop)',
'Skull Woods Final Section (Mothula)',
'Skull Woods First Section (Right)',
'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)'],
ItemFactory('Big Key (Skull Woods)', player),
ItemFactory(['Small Key (Skull Woods)'] * 3, player),
ItemFactory(['Small Key (Skull Woods)'] * 5, player),
ItemFactory(['Map (Skull Woods)', 'Compass (Skull Woods)'], player))
SP = make_dungeon('Swamp Palace', 'Arrghus',
['Swamp Palace (Entrance)', 'Swamp Palace (First Room)', 'Swamp Palace (Starting Area)',
'Swamp Palace (Center)', 'Swamp Palace (North)'], ItemFactory('Big Key (Swamp Palace)', player),
[ItemFactory('Small Key (Swamp Palace)', player)],
'Swamp Palace (West)', 'Swamp Palace (Center)', 'Swamp Palace (North)'],
ItemFactory('Big Key (Swamp Palace)', player),
ItemFactory(['Small Key (Swamp Palace)'] * 6, player),
ItemFactory(['Map (Swamp Palace)', 'Compass (Swamp Palace)'], player))
IP = make_dungeon('Ice Palace', 'Kholdstare',
['Ice Palace (Entrance)', 'Ice Palace (Main)', 'Ice Palace (East)', 'Ice Palace (East Top)',
'Ice Palace (Kholdstare)'], ItemFactory('Big Key (Ice Palace)', player),
ItemFactory(['Small Key (Ice Palace)'] * 2, player),
['Ice Palace (Entrance)', 'Ice Palace (Second Section)', 'Ice Palace (Main)', 'Ice Palace (East)',
'Ice Palace (East Top)', 'Ice Palace (Kholdstare)'], ItemFactory('Big Key (Ice Palace)', player),
ItemFactory(['Small Key (Ice Palace)'] * 6, player),
ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player))
MM = make_dungeon('Misery Mire', 'Vitreous',
['Misery Mire (Entrance)', 'Misery Mire (Main)', 'Misery Mire (West)', 'Misery Mire (Final Area)',
'Misery Mire (Vitreous)'], ItemFactory('Big Key (Misery Mire)', player),
ItemFactory(['Small Key (Misery Mire)'] * 3, player),
ItemFactory(['Small Key (Misery Mire)'] * 6, player),
ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player))
TR = make_dungeon('Turtle Rock', 'Trinexx',
['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)',
'Turtle Rock (Pokey Room)',
'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)',
'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'],
ItemFactory('Big Key (Turtle Rock)', player),
ItemFactory(['Small Key (Turtle Rock)'] * 4, player),
ItemFactory(['Small Key (Turtle Rock)'] * 6, player),
ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player))

if multiworld.mode[player] != 'inverted':
AT = make_dungeon('Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None,
ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), [])
ItemFactory(['Small Key (Agahnims Tower)'] * 4, player), [])
GT = make_dungeon('Ganons Tower', 'Agahnim2',
['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)',
'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)',
'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)',
'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'],
ItemFactory('Big Key (Ganons Tower)', player),
ItemFactory(['Small Key (Ganons Tower)'] * 4, player),
ItemFactory(['Small Key (Ganons Tower)'] * 8, player),
ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player))
else:
AT = make_dungeon('Inverted Agahnims Tower', 'Agahnim', ['Inverted Agahnims Tower', 'Agahnim 1'], None,
ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), [])
ItemFactory(['Small Key (Agahnims Tower)'] * 4, player), [])
GT = make_dungeon('Inverted Ganons Tower', 'Agahnim2',
['Inverted Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)',
'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)',
'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)',
'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)',
'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)', player),
ItemFactory(['Small Key (Ganons Tower)'] * 4, player),
ItemFactory(['Small Key (Ganons Tower)'] * 8, player),
ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player))

GT.bosses['bottom'] = BossFactory('Armos Knights', player)
Expand Down Expand Up @@ -195,10 +200,11 @@ def fill_dungeons_restrictive(multiworld: MultiWorld):
dungeon_specific: set = set()
for subworld in multiworld.get_game_worlds("A Link to the Past"):
player = subworld.player
localized |= {(player, item_name) for item_name in
subworld.dungeon_local_item_names}
dungeon_specific |= {(player, item_name) for item_name in
subworld.dungeon_specific_item_names}
if player not in multiworld.groups:
localized |= {(player, item_name) for item_name in
subworld.dungeon_local_item_names}
dungeon_specific |= {(player, item_name) for item_name in
subworld.dungeon_specific_item_names}

if localized:
in_dungeon_items = [item for item in get_dungeon_item_pool(multiworld) if (item.player, item.name) in localized]
Expand Down Expand Up @@ -249,7 +255,16 @@ def fill_dungeons_restrictive(multiworld: MultiWorld):
if all_state_base.has("Triforce", player):
all_state_base.remove(multiworld.worlds[player].create_item("Triforce"))

fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, True, True, allow_excluded=True)
for (player, key_drop_shuffle) in enumerate(multiworld.key_drop_shuffle.values(), start=1):
if not key_drop_shuffle and player not in multiworld.groups:
for key_loc in key_drop_data:
key_data = key_drop_data[key_loc]
all_state_base.remove(ItemFactory(key_data[3], player))
loc = multiworld.get_location(key_loc, player)

if loc in all_state_base.events:
all_state_base.events.remove(loc)
fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, True, True)


dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
Expand Down
Loading