From 031ec9983f0b10c286146eb1f0ea472bbfa60a59 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 25 Mar 2022 14:54:28 -0600 Subject: [PATCH 1/4] Forfeit multiworld option fix for shops and pots GT junk fill reworked for pottery lottery --- Fill.py | 42 +++++++++++++++++++++++++++++++++++++----- MultiServer.py | 7 +++---- RELEASENOTES.md | 2 ++ 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/Fill.py b/Fill.py index 3645e5d9..6171344b 100644 --- a/Fill.py +++ b/Fill.py @@ -2,6 +2,7 @@ import RaceRandom as random import collections import itertools import logging +import math from BaseClasses import CollectionState, FillError, LocationType from Items import ItemFactory @@ -380,12 +381,21 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None # fill in gtower locations with trash first for player in range(1, world.players + 1): - if not gftower_trash or not world.ganonstower_vanilla[player] or world.doorShuffle[player] == 'crossed' or world.logic[player] in ['owglitches', 'nologic']: + if (not gftower_trash or not world.ganonstower_vanilla[player] + or world.logic[player] in ['owglitches', 'nologic']): continue - max_trash = 8 if world.algorithm == 'dungeon_only' else 15 - gftower_trash_count = (random.randint(15, 50) if world.goal[player] == 'triforcehunt' else random.randint(0, max_trash)) + gt_count, total_count = calc_trash_locations(world, player) + scale_factor = .75 * (world.crystals_needed_for_gt[player] / 7) + if world.algorithm == 'dungeon_only': + reserved_space = sum(1 for i in progitempool+prioitempool if i.player == player) + max_trash = max(0, min(gt_count, total_count - reserved_space)) + else: + max_trash = gt_count + scaled_trash = math.floor(max_trash * scale_factor) + gftower_trash_count = (random.randint(scaled_trash, max_trash) if world.goal[player] == 'triforcehunt' else random.randint(0, scaled_trash)) - gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player] + gtower_locations = [location for location in fill_locations if location.parent_region.dungeon + and location.parent_region.dungeon.name == 'Ganons Tower' and location.player == player] random.shuffle(gtower_locations) trashcnt = 0 while gtower_locations and restitempool and trashcnt < gftower_trash_count: @@ -449,19 +459,41 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None ensure_good_pots(world) +def calc_trash_locations(world, player): + total_count, gt_count = 0, 0 + for loc in world.get_locations(): + if (loc.player == player and loc.item is None + and (loc.type not in {LocationType.Pot, LocationType.Drop, LocationType.Normal} or not loc.forced_item) + and (loc.type != LocationType.Shop or world.shopsanity[player]) + and loc.parent_region.dungeon): + total_count += 1 + if loc.parent_region.dungeon.name == 'Ganons Tower': + gt_count += 1 + return gt_count, total_count + + def ensure_good_pots(world, write_skips=False): for loc in world.get_locations(): # convert Arrows 5 and Nothing when necessary if (loc.item.name in {'Arrows (5)', 'Nothing'} and (loc.type != LocationType.Pot or loc.item.player != loc.player)): loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.item.player) + # can be placed here by multiworld balancing or shop balancing + # change it to something normal for the player it got swapped to + elif (loc.item.name in {'Chicken', 'Big Magic'} + and (loc.type != LocationType.Pot or loc.item.player != loc.player)): + if loc.type == LocationType.Pot: + loc.item.player = loc.player + else: + loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.player) # don't write out all pots to spoiler if write_skips: if loc.type == LocationType.Pot and loc.item.name in valid_pot_items: loc.skip = True -invalid_location_replacement = {'Arrows (5)': 'Arrows (10)', 'Nothing': 'Rupees (5)'} +invalid_location_replacement = {'Arrows (5)': 'Arrows (10)', 'Nothing': 'Rupees (5)', + 'Chicken': 'Rupees (5)', 'Big Magic': 'Small Magic'} def fast_fill_helper(world, item_pool, fill_locations): diff --git a/MultiServer.py b/MultiServer.py index b15390dd..c4e8224b 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -159,8 +159,7 @@ def send_new_items(ctx : Context): client.send_index = len(items) def forfeit_player(ctx : Context, team, slot): - all_locations = {values[0] for values in Regions.location_table.values() if type(values[0]) is int} - all_locations.update({values[1] for values in Regions.key_drop_data.values()}) + all_locations = set(ctx.lookup_id_to_name.keys()) notify_all(ctx, "%s (Team #%d) has forfeited" % (ctx.player_names[(team, slot)], team + 1)) register_location_checks(ctx, team, slot, all_locations) @@ -347,8 +346,8 @@ async def console(ctx : Context): def init_lookups(ctx): - ctx.lookup_id_to_name = {x: y for x, y in Regions.lookup_id_to_name.items()} - ctx.lookup_name_to_id = {x: y for x, y in Regions.lookup_name_to_id.items()} + ctx.lookup_id_to_name = {x: y for x, y in Regions.lookup_id_to_name.items()} + ctx.lookup_name_to_id = {x: y for x, y in Regions.lookup_name_to_id.items()} for location, datum in PotShuffle.key_drop_data.items(): type = datum[0] if type == 'Drop': diff --git a/RELEASENOTES.md b/RELEASENOTES.md index eb6428de..07c498f2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -148,6 +148,8 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o #### Volatile * 1.0.1.12 + * Fix for Multiworld forfeits, shops and pot items now included + * Reworked GT Trash Fill. Base rate is 0-75% of locations fill with 7 crystals entrance requirements. Triforce hunt is 75%-100% of locations. The 75% number will decrease based on the crystal entrance requirement. Dungeon_only algorithm caps it based on how many items need to be placed in dungeons. Cross dungeon shuffle will now work with the trash fill. * Inverted bug * Fix for hammerdashing pots, if sprite limit is reached, items won't spawn, error beep won't play either because of other SFX * Killing enemies freeze + hammer results in the droppable item instead of the freeze prize From 8ec16d46cea1fcc0182cf99b350c0781eac09c39 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 25 Mar 2022 15:35:46 -0600 Subject: [PATCH 2/4] Dashable forbidden Minor logic refinement for Spiky Hint Cave --- DoorShuffle.py | 23 +++++++++++++++++++++-- RELEASENOTES.md | 1 + Rules.py | 7 ++++--- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index f4ee9cfa..b2156357 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1791,12 +1791,30 @@ def stateful_door(door, kind): return False +dashable_forbidden = { + 'Swamp Trench 1 Key Ledge NW', 'Swamp Left Elbow WN', 'Swamp Right Elbow SE', 'Mire Hub WN', 'Mire Hub WS', + 'Mire Hub Top NW', 'Mire Hub NE', 'Ice Dead End WS' +} + +ohko_forbidden = { + 'GT Invisible Catwalk NE', 'GT Falling Bridge WN', 'GT Falling Bridge WS', 'GT Hidden Star ES', 'GT Hookshot EN', + 'GT Torch Cross WN', 'TR Torches WN', 'Mire Falling Bridge WS', 'Mire Falling Bridge W', 'Ice Hookshot Balcony SW', + 'Ice Catwalk WN', 'Ice Catwalk NW', 'Ice Bomb Jump NW', 'GT Cannonball Bridge SE' +} + + +def filter_dashable_candidates(candidates, world): + forbidden_set = dashable_forbidden if world.can_take_damage else ohko_forbidden + return [x for x in candidates if x not in forbidden_set and x.dest not in forbidden_set] + + def shuffle_bombable_dashable(bd_candidates, world, player): if world.doorShuffle[player] == 'basic': for dungeon, candidates in bd_candidates.items(): diff = bomb_dash_counts[dungeon.name][1] if diff > 0: - for chosen in random.sample(candidates, min(diff, len(candidates))): + dash_candidates = filter_dashable_candidates(candidates, world) + for chosen in random.sample(dash_candidates, min(diff, len(candidates))): change_pair_type(chosen, DoorKind.Dashable, world, player) candidates.remove(chosen) diff = bomb_dash_counts[dungeon.name][0] @@ -1808,7 +1826,8 @@ def shuffle_bombable_dashable(bd_candidates, world, player): remove_pair_type_if_present(excluded, world, player) elif world.doorShuffle[player] == 'crossed': all_candidates = sum(bd_candidates.values(), []) - for chosen in random.sample(all_candidates, min(8, len(all_candidates))): + dash_candidates = filter_dashable_candidates(all_candidates, world) + for chosen in random.sample(dash_candidates, min(8, len(all_candidates))): change_pair_type(chosen, DoorKind.Dashable, world, player) all_candidates.remove(chosen) for chosen in random.sample(all_candidates, min(12, len(all_candidates))): diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 07c498f2..1d99f310 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -153,6 +153,7 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o * Inverted bug * Fix for hammerdashing pots, if sprite limit is reached, items won't spawn, error beep won't play either because of other SFX * Killing enemies freeze + hammer results in the droppable item instead of the freeze prize + * Forbid certain doors from being dashable when you either can't dash them open (but bombs would work) or you'd fall into a pit from the recoil in OHKO * 1.0.1.11 * Separated Collection Rate counter from experimental * Added MSU Resume option diff --git a/Rules.py b/Rules.py index e96a483a..7f9c0802 100644 --- a/Rules.py +++ b/Rules.py @@ -736,9 +736,10 @@ def pot_rules(world, player): for l in world.get_region('Palace of Darkness Hint', player).locations: if l.type == LocationType.Pot: add_rule(l, lambda state: state.can_use_bombs(player) or state.has_Boots(player)) - for l in world.get_region('Dark Lake Hylia Ledge Spike Cave', player).locations: - if l.type == LocationType.Pot: - add_rule(l, lambda state: state.world.can_take_damage or state.has('Hookshot', player) + for number in ['1', '2']: + loc = world.get_location_unsafe(f'Dark Lake Hylia Ledge Spike Cave Pot #{number}', player) + if loc and loc.type == LocationType.Pot: + add_rule(loc, lambda state: state.world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or (state.has('Cane of Byrna', player) and state.world.difficulty_adjustments[player] == 'normal')) From 2ca8d61c0b051f0a1cb17c54844c2534c653ee67 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 29 Mar 2022 12:21:09 -0600 Subject: [PATCH 3/4] Arghus splash fix Don't bother checking the ssl cert for that call --- MultiServer.py | 3 ++- RELEASENOTES.md | 2 ++ Rom.py | 2 +- data/base2current.bps | Bin 85664 -> 85662 bytes 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/MultiServer.py b/MultiServer.py index c4e8224b..4108b8dd 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -6,6 +6,7 @@ import json import logging import re import shlex +import ssl import urllib.request import websockets import zlib @@ -405,7 +406,7 @@ async def main(): logging.error('Failed to read multiworld data (%s)' % e) return - ip = urllib.request.urlopen('https://v4.ident.me').read().decode('utf8') if not ctx.host else ctx.host + ip = urllib.request.urlopen('https://v4.ident.me', context=ssl._create_unverified_context()).read().decode('utf8') if not ctx.host else ctx.host logging.info('Hosting game at %s:%d (%s)' % (ip, ctx.port, 'No password' if not ctx.password else 'Password: %s' % ctx.password)) ctx.disable_save = args.disable_save diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1d99f310..52bc8b61 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -150,8 +150,10 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o * 1.0.1.12 * Fix for Multiworld forfeits, shops and pot items now included * Reworked GT Trash Fill. Base rate is 0-75% of locations fill with 7 crystals entrance requirements. Triforce hunt is 75%-100% of locations. The 75% number will decrease based on the crystal entrance requirement. Dungeon_only algorithm caps it based on how many items need to be placed in dungeons. Cross dungeon shuffle will now work with the trash fill. + * MultiServer fix for ssl certs and python * Inverted bug * Fix for hammerdashing pots, if sprite limit is reached, items won't spawn, error beep won't play either because of other SFX + * Arghus splash no longer used for pottery sprites (used apple instead) * Killing enemies freeze + hammer results in the droppable item instead of the freeze prize * Forbid certain doors from being dashable when you either can't dash them open (but bombs would work) or you'd fall into a pit from the recoil in OHKO * 1.0.1.11 diff --git a/Rom.py b/Rom.py index 7c0933e2..dbc7e670 100644 --- a/Rom.py +++ b/Rom.py @@ -35,7 +35,7 @@ from source.item.FillUtil import valid_pot_items JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '57df02038c77f9a3b915af21d3c59b78' +RANDOMIZERBASEHASH = '1e45d174c71b2e079df1c8d5e8d1451b' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index 0e97265aee6578922b9d19587bb29c55dd71d6b6..894baf13fe4a7fff2cde646bfd047b7730098b6c 100644 GIT binary patch delta 62 zcmV-E0KxyDoduqq1+YZ{1OkHneS=8>w@Cp3nhgR_PM75l0bT{=n}UJ$mq-r*BLsov UyMniI4*@4X2<8KtPM>X356N>DlmGw# delta 64 zcmV-G0Kfm9oduws1+YZ{1oD9meuGH?w@Cp3nhgT5Oqb;j0bT{mn}UJcmq-r*BL<5B WfU^LAw{H&tCqD>w^$zaQhFiq)I2ZW< From 8bc446fbc5d9ea59165c75b29b6fcc7581dbfbc7 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 29 Mar 2022 13:28:05 -0600 Subject: [PATCH 4/4] Logic refinements for Skull X Room and GT Falling Torches --- DoorShuffle.py | 2 ++ Doors.py | 2 ++ Dungeons.py | 8 ++++---- ItemList.py | 3 +++ Items.py | 1 + Main.py | 2 +- RELEASENOTES.md | 11 +++++++---- Regions.py | 9 ++++++--- Rules.py | 5 +++++ 9 files changed, 31 insertions(+), 12 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index b2156357..63d50b21 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -2240,6 +2240,8 @@ logical_connections = [ ('Skull Pot Circle Star Path', 'Skull Map Room'), ('Skull Big Chest Hookpath', 'Skull 1 Lobby'), ('Skull Back Drop Star Path', 'Skull Small Hall'), + ('Skull 2 West Lobby Pits', 'Skull 2 West Lobby Ledge'), + ('Skull 2 West Lobby Ledge Pits', 'Skull 2 West Lobby'), ('Thieves Rail Ledge Drop Down', 'Thieves BK Corner'), ('Thieves Hellway Orange Barrier', 'Thieves Hellway S Crystal'), ('Thieves Hellway Crystal Orange Barrier', 'Thieves Hellway'), diff --git a/Doors.py b/Doors.py index de4de94c..26d474cc 100644 --- a/Doors.py +++ b/Doors.py @@ -614,6 +614,8 @@ def create_doors(world, player): create_door(player, 'Skull 2 West Lobby S', Nrml).dir(So, 0x56, Left, High).pos(1).portal(Z, 0x00), create_door(player, 'Skull 2 West Lobby ES', Intr).dir(Ea, 0x56, Bot, High).pos(2), create_door(player, 'Skull 2 West Lobby NW', Intr).dir(No, 0x56, Left, High).small_key().pos(0), + create_door(player, 'Skull 2 West Lobby Pits', Lgcl), + create_door(player, 'Skull 2 West Lobby Ledge Pits', Lgcl), create_door(player, 'Skull X Room SW', Intr).dir(So, 0x56, Left, High).small_key().pos(0), create_door(player, 'Skull Back Drop Star Path', Lgcl), create_door(player, 'Skull 3 Lobby SW', Nrml).dir(So, 0x59, Left, High).pos(1).portal(Z, 0x02), diff --git a/Dungeons.py b/Dungeons.py index d3f0c216..188cf59f 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -124,10 +124,10 @@ swamp_regions = [ skull_regions = [ 'Skull 1 Lobby', 'Skull Map Room', 'Skull Pot Circle', 'Skull Pull Switch', 'Skull Big Chest', 'Skull Pinball', 'Skull Pot Prison', 'Skull Compass Room', 'Skull Left Drop', 'Skull 2 East Lobby', 'Skull Big Key', - 'Skull Lone Pot', 'Skull Small Hall', 'Skull Back Drop', 'Skull 2 West Lobby', 'Skull X Room', 'Skull 3 Lobby', - 'Skull East Bridge', 'Skull West Bridge Nook', 'Skull Star Pits', 'Skull Torch Room', 'Skull Vines', - 'Skull Spike Corner', 'Skull Final Drop', 'Skull Boss', 'Skull 1 Portal', 'Skull 2 East Portal', - 'Skull 2 West Portal', 'Skull 3 Portal' + 'Skull Lone Pot', 'Skull Small Hall', 'Skull Back Drop', 'Skull 2 West Lobby', 'Skull 2 West Lobby Ledge', + 'Skull X Room', 'Skull 3 Lobby', 'Skull East Bridge', 'Skull West Bridge Nook', 'Skull Star Pits', + 'Skull Torch Room', 'Skull Vines', 'Skull Spike Corner', 'Skull Final Drop', 'Skull Boss', + 'Skull 1 Portal', 'Skull 2 East Portal', 'Skull 2 West Portal', 'Skull 3 Portal' ] thieves_regions = [ diff --git a/ItemList.py b/ItemList.py index ea35dd59..b10eb9ae 100644 --- a/ItemList.py +++ b/ItemList.py @@ -245,6 +245,9 @@ def generate_itempool(world, player): world.push_item(world.get_location('Ice Block Drop', player), ItemFactory('Convenient Block', player), False) world.get_location('Ice Block Drop', player).event = True world.get_location('Ice Block Drop', player).locked = True + world.push_item(world.get_location('Skull Star Tile', player), ItemFactory('Hidden Pits', player), False) + world.get_location('Skull Star Tile', player).event = True + world.get_location('Skull Star Tile', player).locked = True if world.mode[player] == 'standard': world.push_item(world.get_location('Zelda Pickup', player), ItemFactory('Zelda Herself', player), False) world.get_location('Zelda Pickup', player).event = True diff --git a/Items.py b/Items.py index 757fc8e5..c88bccf7 100644 --- a/Items.py +++ b/Items.py @@ -188,6 +188,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Maiden Rescued': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Maiden Unmasked': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Convenient Block': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), + 'Hidden Pits': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Zelda Herself': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Zelda Delivered': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), } diff --git a/Main.py b/Main.py index bf49c2f8..347b3521 100644 --- a/Main.py +++ b/Main.py @@ -550,7 +550,7 @@ def create_playthrough(world): # get locations containing progress items prog_locations = [location for location in world.get_filled_locations() if location.item.advancement] - optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop'] + optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile'] state_cache = [None] collection_spheres = [] state = CollectionState(world) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 52bc8b61..185ff5f8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -152,10 +152,13 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o * Reworked GT Trash Fill. Base rate is 0-75% of locations fill with 7 crystals entrance requirements. Triforce hunt is 75%-100% of locations. The 75% number will decrease based on the crystal entrance requirement. Dungeon_only algorithm caps it based on how many items need to be placed in dungeons. Cross dungeon shuffle will now work with the trash fill. * MultiServer fix for ssl certs and python * Inverted bug - * Fix for hammerdashing pots, if sprite limit is reached, items won't spawn, error beep won't play either because of other SFX - * Arghus splash no longer used for pottery sprites (used apple instead) - * Killing enemies freeze + hammer results in the droppable item instead of the freeze prize - * Forbid certain doors from being dashable when you either can't dash them open (but bombs would work) or you'd fall into a pit from the recoil in OHKO + * Fix for hammerdashing pots, if sprite limit is reached, items won't spawn, but error beep won't play either because of other SFX + * Arrghus splash no longer used for pottery sprites (used apple instead) + * Killing enemies via freeze + hammer properly results in the droppable item instead of the freeze prize + * Forbid certain doors from being dashable when you either can't dash them open (but bombs would work) or you'd fall into a pit from the bonk recoil in OHKO + * Logic refinements + * Skull X Room requires Boots or access to Skull Back Drop + * GT Falling Torches requires Boots to get over the falling tile gap (this is a stop-gap measure until more sophisticated crystal switch traversal is possible) * 1.0.1.11 * Separated Collection Rate counter from experimental * Added MSU Resume option diff --git a/Regions.py b/Regions.py index f8cb447b..1d952189 100644 --- a/Regions.py +++ b/Regions.py @@ -523,8 +523,9 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'Skull Big Key', 'Skull Woods', ['Skull Woods - Big Key Chest'], ['Skull Big Key SW', 'Skull Big Key EN']), create_dungeon_region(player, 'Skull Lone Pot', 'Skull Woods', None, ['Skull Lone Pot WN']), create_dungeon_region(player, 'Skull Small Hall', 'Skull Woods', None, ['Skull Small Hall ES', 'Skull Small Hall WS']), - create_dungeon_region(player, 'Skull Back Drop', 'Skull Woods', None, ['Skull Back Drop Star Path']), - create_dungeon_region(player, 'Skull 2 West Lobby', 'Skull Woods', ['Skull Woods - West Lobby Pot Key'], ['Skull 2 West Lobby ES', 'Skull 2 West Lobby NW', 'Skull 2 West Lobby S']), + create_dungeon_region(player, 'Skull Back Drop', 'Skull Woods', ['Skull Star Tile'], ['Skull Back Drop Star Path']), + create_dungeon_region(player, 'Skull 2 West Lobby', 'Skull Woods', ['Skull Woods - West Lobby Pot Key'], ['Skull 2 West Lobby ES', 'Skull 2 West Lobby Pits', 'Skull 2 West Lobby S']), + create_dungeon_region(player, 'Skull 2 West Lobby Ledge', 'Skull Woods', None, ['Skull 2 West Lobby NW', 'Skull 2 West Lobby Ledge Pits']), create_dungeon_region(player, 'Skull X Room', 'Skull Woods', None, ['Skull X Room SW']), create_dungeon_region(player, 'Skull 3 Lobby', 'Skull Woods', None, ['Skull 3 Lobby NW', 'Skull 3 Lobby EN', 'Skull 3 Lobby SW']), create_dungeon_region(player, 'Skull East Bridge', 'Skull Woods', None, ['Skull East Bridge WN', 'Skull East Bridge WS']), @@ -1045,7 +1046,7 @@ def adjust_locations(world, player): # unreal events: for l in ['Ganon', 'Agahnim 1', 'Agahnim 2', 'Dark Blacksmith Ruins', 'Frog', 'Missing Smith', 'Floodgate', 'Trench 1 Switch', 'Trench 2 Switch', 'Swamp Drain', 'Attic Cracked Floor', 'Suspicious Maiden', - 'Revealing Light', 'Ice Block Drop', 'Zelda Pickup', 'Zelda Drop Off']: + 'Revealing Light', 'Ice Block Drop', 'Zelda Pickup', 'Zelda Drop Off', 'Skull Star Tile']: location = world.get_location_unsafe(l, player) if location: location.type = LocationType.Logical @@ -1150,6 +1151,7 @@ dungeon_events = [ 'Suspicious Maiden', 'Revealing Light', 'Ice Block Drop', + 'Skull Star Tile', 'Zelda Pickup', 'Zelda Drop Off' ] @@ -1388,6 +1390,7 @@ location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'), 'Suspicious Maiden': (None, None, False, None), 'Revealing Light': (None, None, False, None), 'Ice Block Drop': (None, None, False, None), + 'Skull Star Tile': (None, None, False, None), 'Zelda Pickup': (None, None, False, None), 'Zelda Drop Off': (None, None, False, None), 'Eastern Palace - Prize': ([0x1209D, 0x53E76, 0x53E77, 0x180052, 0x180070, 0xC6FE, 0x186FE2], None, True, 'Eastern Palace'), diff --git a/Rules.py b/Rules.py index 7f9c0802..0572d679 100644 --- a/Rules.py +++ b/Rules.py @@ -273,6 +273,8 @@ def global_rules(world, player): set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Skull Torch Room WN', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player)) + set_rule(world.get_entrance('Skull 2 West Lobby Pits', player), lambda state: state.has_Boots(player) or state.has('Hidden Pits', player)) + set_rule(world.get_entrance('Skull 2 West Lobby Ledge Pits', player), lambda state: state.has('Hidden Pits', player)) set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Prize', player)) @@ -398,6 +400,9 @@ def global_rules(world, player): set_rule(world.get_entrance('GT Lanmolas 2 NW', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state)) set_rule(world.get_entrance('GT Torch Cross ES', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('GT Falling Torches NE', player), lambda state: state.has_fire_source(player)) + # todo: the following only applies to crystal state propagation from this supertile + # you can also reset the supertile, but I'm not sure how to model that + set_rule(world.get_entrance('GT Falling Torches Down Ladder', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('GT Moldorm Gap', player), lambda state: state.has('Hookshot', player) and world.get_region('GT Moldorm', player).dungeon.bosses['top'].can_defeat(state)) set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))