Fixed Swordless rules

Added rule for Freezor chest (for crossed and swordless)
Added more "single exit" caves to possible inaccessible regions
Prevented dungeon gen from assuming you could get GT Big Key at Aga 2
Prevented cross-dungeon contamination during key rule gen
Fixed some key-sphere merging problems (I'm ready to get rid of spheres now)
This commit is contained in:
aerinon
2019-12-12 15:01:12 -07:00
parent b18812ef0e
commit 7422eb5ccc
4 changed files with 68 additions and 51 deletions

View File

@@ -303,7 +303,7 @@ def within_dungeon(world, player):
sector_queue.append((key, sector_list, entrance_list)) sector_queue.append((key, sector_list, entrance_list))
last_key = key last_key = key
else: else:
ds = generate_dungeon(sector_list, origin_list_sans_drops, split_dungeon, world, player) ds = generate_dungeon(key, sector_list, origin_list_sans_drops, split_dungeon, world, player)
find_new_entrances(ds, connections, potentials, enabled_entrances, world, player) find_new_entrances(ds, connections, potentials, enabled_entrances, world, player)
ds.name = key ds.name = key
layout_starts = origin_list if len(entrance_list) <= 0 else entrance_list layout_starts = origin_list if len(entrance_list) <= 0 else entrance_list
@@ -1120,7 +1120,7 @@ def find_inaccessible_regions(world, player):
def valid_inaccessible_region(r): def valid_inaccessible_region(r):
return r.type is not RegionType.Cave or len(r.exits) > 1 or r.name in ['Spiral Cave (Bottom)'] return r.type is not RegionType.Cave or (len(r.exits) > 0 and r.name not in ['Links House', 'Chris Houlihan Room'])
def add_inaccessible_doors(world, player): def add_inaccessible_doors(world, player):
@@ -1178,7 +1178,8 @@ def check_required_paths(paths, world, player):
start_regions = convert_regions(start_regs, world, player) start_regions = convert_regions(start_regs, world, player)
initial = start_regs == tuple(entrances) initial = start_regs == tuple(entrances)
if not initial or cached_initial_state is None: if not initial or cached_initial_state is None:
state = ExplorationState(determine_init_crystal(initial, cached_initial_state, start_regions)) init = determine_init_crystal(initial, cached_initial_state, start_regions)
state = ExplorationState(init, dungeon_name)
for region in start_regions: for region in start_regions:
state.visit_region(region) state.visit_region(region)
state.add_all_doors_check_unattached(region, world, player) state.add_all_doors_check_unattached(region, world, player)

View File

@@ -27,7 +27,7 @@ class GraphPiece:
self.possible_bk_locations = set() self.possible_bk_locations = set()
def generate_dungeon(available_sectors, entrance_region_names, split_dungeon, world, player): def generate_dungeon(name, available_sectors, entrance_region_names, split_dungeon, world, player):
logger = logging.getLogger('') logger = logging.getLogger('')
entrance_regions = convert_regions(entrance_region_names, world, player) entrance_regions = convert_regions(entrance_region_names, world, player)
doors_to_connect = set() doors_to_connect = set()
@@ -52,7 +52,7 @@ def generate_dungeon(available_sectors, entrance_region_names, split_dungeon, wo
if itr > 5000: if itr > 5000:
raise Exception('Generation taking too long. Ref %s' % entrance_region_names[0]) raise Exception('Generation taking too long. Ref %s' % entrance_region_names[0])
if depth not in dungeon_cache.keys(): if depth not in dungeon_cache.keys():
dungeon, hangers, hooks = gen_dungeon_info(available_sectors, entrance_regions, proposed_map, doors_to_connect, bk_needed, world, player) dungeon, hangers, hooks = gen_dungeon_info(name, available_sectors, entrance_regions, proposed_map, doors_to_connect, bk_needed, world, player)
dungeon_cache[depth] = dungeon, hangers, hooks dungeon_cache[depth] = dungeon, hangers, hooks
valid = check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions, bk_needed) valid = check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions, bk_needed)
else: else:
@@ -109,10 +109,10 @@ def determine_if_bk_needed(sector, split_dungeon, world, player):
return False return False
def gen_dungeon_info(available_sectors, entrance_regions, proposed_map, valid_doors, bk_needed, world, player): def gen_dungeon_info(name, available_sectors, entrance_regions, proposed_map, valid_doors, bk_needed, world, player):
# step 1 create dungeon: Dict<DoorName|Origin, GraphPiece> # step 1 create dungeon: Dict<DoorName|Origin, GraphPiece>
dungeon = {} dungeon = {}
original_state = extend_reachable_state_improved(entrance_regions, ExplorationState(), proposed_map, valid_doors, bk_needed, world, player) original_state = extend_reachable_state_improved(entrance_regions, ExplorationState(dungeon=name), proposed_map, valid_doors, bk_needed, world, player)
dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map) dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map)
doors_to_connect = set() doors_to_connect = set()
hanger_set = set() hanger_set = set()
@@ -123,7 +123,7 @@ def gen_dungeon_info(available_sectors, entrance_regions, proposed_map, valid_do
if not door.stonewall and door not in proposed_map.keys(): if not door.stonewall and door not in proposed_map.keys():
hanger_set.add(door) hanger_set.add(door)
parent = parent_region(door, world, player).parent_region parent = parent_region(door, world, player).parent_region
o_state = extend_reachable_state_improved([parent], ExplorationState(), proposed_map, valid_doors, False, world, player) o_state = extend_reachable_state_improved([parent], ExplorationState(dungeon=name), proposed_map, valid_doors, False, world, player)
o_state_cache[door.name] = o_state o_state_cache[door.name] = o_state
piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map) piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map)
dungeon[door.name] = piece dungeon[door.name] = piece
@@ -182,7 +182,7 @@ def check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, valid_do
def explore_blue_state(door, dungeon, o_state, proposed_map, valid_doors, world, player): def explore_blue_state(door, dungeon, o_state, proposed_map, valid_doors, world, player):
parent = parent_region(door, world, player).parent_region parent = parent_region(door, world, player).parent_region
blue_start = ExplorationState(CrystalBarrier.Blue) blue_start = ExplorationState(CrystalBarrier.Blue, o_state.dungeon)
b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, valid_doors, False, world, player) b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, valid_doors, False, world, player)
dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map) dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map)
@@ -257,6 +257,8 @@ def check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_reg
true_origin_hooks = [x for x in dungeon['Origin'].hooks.keys() if not x.bigKey or possible_bks > 0 or not bk_needed] true_origin_hooks = [x for x in dungeon['Origin'].hooks.keys() if not x.bigKey or possible_bks > 0 or not bk_needed]
if len(true_origin_hooks) == 0 and len(proposed_map.keys()) < len(doors_to_connect): if len(true_origin_hooks) == 0 and len(proposed_map.keys()) < len(doors_to_connect):
return False return False
if len(true_origin_hooks) == 0 and bk_needed and possible_bks == 0 and len(proposed_map.keys()) == len(doors_to_connect):
return False
for key in hangers.keys(): for key in hangers.keys():
if len(hooks[key]) > 0 and len(hangers[key]) == 0: if len(hooks[key]) > 0 and len(hangers[key]) == 0:
return False return False
@@ -374,7 +376,7 @@ def create_graph_piece_from_state(door, o_state, b_state, proposed_map):
def filter_for_potential_bk_locations(locations): def filter_for_potential_bk_locations(locations):
return [x for x in locations if '- Big Chest' not in x.name and '- Prize' not in x.name and x.name not in dungeon_events and x.name not in key_only_locations.keys()] return [x for x in locations if '- Big Chest' not in x.name and '- Prize' not in x.name and x.name not in dungeon_events and x.name not in key_only_locations.keys() and x.name not in ['Agahnim 1', 'Agahnim 2']]
def parent_region(door, world, player): def parent_region(door, world, player):
@@ -498,7 +500,7 @@ def connect_simple_door(exit_door, region, world, player):
class ExplorationState(object): class ExplorationState(object):
def __init__(self, init_crystal=CrystalBarrier.Orange): def __init__(self, init_crystal=CrystalBarrier.Orange, dungeon=None):
self.unattached_doors = [] self.unattached_doors = []
self.avail_doors = [] self.avail_doors = []
@@ -527,9 +529,10 @@ class ExplorationState(object):
self.bk_found = set() self.bk_found = set()
self.non_door_entrances = [] self.non_door_entrances = []
self.dungeon = dungeon
def copy(self): def copy(self):
ret = ExplorationState() ret = ExplorationState(dungeon=self.dungeon)
ret.unattached_doors = list(self.unattached_doors) ret.unattached_doors = list(self.unattached_doors)
ret.avail_doors = list(self.avail_doors) ret.avail_doors = list(self.avail_doors)
ret.event_doors = list(self.event_doors) ret.event_doors = list(self.event_doors)
@@ -570,21 +573,22 @@ class ExplorationState(object):
self.visited_orange.append(region) self.visited_orange.append(region)
elif self.crystal == CrystalBarrier.Blue: elif self.crystal == CrystalBarrier.Blue:
self.visited_blue.append(region) self.visited_blue.append(region)
for location in region.locations: if region.type == RegionType.Dungeon:
if key_checks and location not in self.found_locations: for location in region.locations:
if location.name in key_only_locations: if key_checks and location not in self.found_locations:
self.key_locations += 1 if location.name in key_only_locations:
if location.name not in dungeon_events and '- Prize' not in location.name: self.key_locations += 1
self.ttl_locations += 1 if location.name not in dungeon_events and '- Prize' not in location.name:
if location not in self.found_locations: self.ttl_locations += 1
self.found_locations.append(location) if location not in self.found_locations:
if not bk_Flag: self.found_locations.append(location)
self.bk_found.add(location) if not bk_Flag:
if location.name in dungeon_events and location.name not in self.events: self.bk_found.add(location)
if self.flooded_key_check(location): if location.name in dungeon_events and location.name not in self.events:
self.perform_event(location.name, key_region) if self.flooded_key_check(location):
if location.name in flooded_keys_reverse.keys() and self.location_found(flooded_keys_reverse[location.name]): self.perform_event(location.name, key_region)
self.perform_event(flooded_keys_reverse[location.name], key_region) if location.name in flooded_keys_reverse.keys() and self.location_found(flooded_keys_reverse[location.name]):
self.perform_event(flooded_keys_reverse[location.name], key_region)
if key_checks and region.name == 'Hyrule Dungeon Cellblock' and not self.big_key_opened: if key_checks and region.name == 'Hyrule Dungeon Cellblock' and not self.big_key_opened:
self.big_key_opened = True self.big_key_opened = True
self.avail_doors.extend(self.big_doors) self.avail_doors.extend(self.big_doors)
@@ -720,7 +724,7 @@ class ExplorationState(object):
return cnt return cnt
def validate(self, door, region, world, player): def validate(self, door, region, world, player):
return self.can_traverse(door) and not self.visited(region) and valid_region_to_explore(region, world, player) return self.can_traverse(door) and not self.visited(region) and valid_region_to_explore(region, self.dungeon, world, player)
def in_door_list(self, door, door_list): def in_door_list(self, door, door_list):
for d in door_list: for d in door_list:
@@ -771,6 +775,7 @@ class ExplorableDoor(object):
return '%s (%s)' % (self.door.name, self.crystal.name) return '%s (%s)' % (self.door.name, self.crystal.name)
# todo: delete this
def extend_reachable_state(search_regions, state, world, player): def extend_reachable_state(search_regions, state, world, player):
local_state = state.copy() local_state = state.copy()
for region in search_regions: for region in search_regions:
@@ -780,7 +785,7 @@ def extend_reachable_state(search_regions, state, world, player):
explorable_door = local_state.next_avail_door() explorable_door = local_state.next_avail_door()
connect_region = world.get_entrance(explorable_door.door.name, player).connected_region connect_region = world.get_entrance(explorable_door.door.name, player).connected_region
if connect_region is not None: if connect_region is not None:
if valid_region_to_explore(connect_region, world, player) and not local_state.visited(connect_region): if valid_region_to_explore(connect_region, local_state.dungeon, world, player) and not local_state.visited(connect_region):
local_state.visit_region(connect_region) local_state.visit_region(connect_region)
local_state.add_all_doors_check_unattached(connect_region, world, player) local_state.add_all_doors_check_unattached(connect_region, world, player)
return local_state return local_state
@@ -801,7 +806,7 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, valid_d
else: else:
connect_region = world.get_entrance(explorable_door.door.name, player).connected_region connect_region = world.get_entrance(explorable_door.door.name, player).connected_region
if connect_region is not None: if connect_region is not None:
if valid_region_to_explore(connect_region, world, player) and not local_state.visited(connect_region): if valid_region_to_explore(connect_region, local_state.dungeon, world, player) and not local_state.visited(connect_region):
flag = explorable_door.flag or explorable_door.door.bigKey flag = explorable_door.flag or explorable_door.door.bigKey
local_state.visit_region(connect_region, bk_Flag=flag) local_state.visit_region(connect_region, bk_Flag=flag)
local_state.add_all_doors_check_proposed(connect_region, proposed_map, valid_doors, flag, world, player) local_state.add_all_doors_check_proposed(connect_region, proposed_map, valid_doors, flag, world, player)
@@ -809,8 +814,10 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, valid_d
# cross-utility methods # cross-utility methods
def valid_region_to_explore(region, world, player): def valid_region_to_explore(region, name, world, player):
return region is not None and (region.type == RegionType.Dungeon or region.name in world.inaccessible_regions[player]) if region is None:
return False
return (region.type == RegionType.Dungeon and region.dungeon.name == name) or region.name in world.inaccessible_regions[player]
def get_doors(world, region, player): def get_doors(world, region, player):

View File

@@ -32,9 +32,8 @@ class KeySphere(object):
return False return False
if len(set(self.key_only_locations).symmetric_difference(set(other.key_only_locations))) > 0: if len(set(self.key_only_locations).symmetric_difference(set(other.key_only_locations))) > 0:
return False return False
# they only differ in child doors - I don't care if len(set(self.child_doors).symmetric_difference(set(other.child_doors))) > 0:
# if len(set(self.child_doors).symmetric_difference(set(other.child_doors))) > 0: return False
# return False
return True return True
@@ -156,7 +155,7 @@ def analyze_dungeon(key_layout, world, player):
find_bk_locked_sections(key_layout, world) find_bk_locked_sections(key_layout, world)
init_bk = check_special_locations(key_layout.key_spheres['Origin'].free_locations) init_bk = check_special_locations(key_layout.key_spheres['Origin'].free_locations.keys())
key_counter = key_layout.key_counters[counter_id({}, init_bk, key_layout.flat_prop)] key_counter = key_layout.key_counters[counter_id({}, init_bk, key_layout.flat_prop)]
queue = collections.deque([(key_layout.key_spheres['Origin'], key_counter)]) queue = collections.deque([(key_layout.key_spheres['Origin'], key_counter)])
doors_completed = set() doors_completed = set()
@@ -473,7 +472,7 @@ def expand_counter_no_big_doors(door, key_counter, key_layout, ignored_doors):
def create_key_spheres(key_layout, world, player): def create_key_spheres(key_layout, world, player):
key_spheres = {} key_spheres = {}
flat_proposal = key_layout.flat_prop flat_proposal = key_layout.flat_prop
state = ExplorationState() state = ExplorationState(dungeon=key_layout.sector.name)
state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys) state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys)
state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions
for region in key_layout.start_regions: for region in key_layout.start_regions:
@@ -498,12 +497,19 @@ def create_key_spheres(key_layout, world, player):
if empty_sphere(old_sphere) and not empty_sphere(child_kr): if empty_sphere(old_sphere) and not empty_sphere(child_kr):
key_spheres[door.name] = merge_sphere = child_kr key_spheres[door.name] = merge_sphere = child_kr
queue.append((child_kr, child_state)) queue.append((child_kr, child_state))
merge_sphere.bk_locked = old_sphere.bk_locked and child_kr.bk_locked
if not empty_sphere(old_sphere) and not empty_sphere(child_kr) and not old_sphere == child_kr: if not empty_sphere(old_sphere) and not empty_sphere(child_kr) and not old_sphere == child_kr:
# ugly sphere merge function - just union locations - ugh # ugly sphere merge function - just union locations - ugh
merge_sphere.free_locations = {**old_sphere.free_locations, **child_kr.free_locations} if old_sphere.bk_locked != child_kr.bk_locked:
merge_sphere.key_only_locations = {**old_sphere.key_only_locations, **child_kr.key_only_locations} if old_sphere.bk_locked:
# this feels so ugly, key counters are much smarter than this - would love to get rid of spheres merge_sphere.child_doors = child_kr.child_doors
merge_sphere.free_locations = child_kr.free_locations
merge_sphere.key_only_locations = child_kr.key_only_locations
else:
merge_sphere.child_doors = {**old_sphere.child_doors, **child_kr.child_doors}
merge_sphere.free_locations = {**old_sphere.free_locations, **child_kr.free_locations}
merge_sphere.key_only_locations = {**old_sphere.key_only_locations, **child_kr.key_only_locations}
merge_sphere.bk_locked = old_sphere.bk_locked and child_kr.bk_locked
# this feels so ugly, key counters are much smarter than this - would love to get rid of spheres
return key_spheres return key_spheres
@@ -519,9 +525,9 @@ def create_key_sphere(state, parent_sphere, door):
parent_locations.update(p_region.key_only_locations) parent_locations.update(p_region.key_only_locations)
parent_locations.update(p_region.other_locations) parent_locations.update(p_region.other_locations)
p_region = p_region.parent_sphere p_region = p_region.parent_sphere
u_doors = set(unique_doors(state.small_doors+state.big_doors)).difference(parent_doors) u_doors = [x for x in unique_doors(state.small_doors+state.big_doors) if x not in parent_doors]
key_sphere.child_doors.update(dict.fromkeys(u_doors)) key_sphere.child_doors.update(dict.fromkeys(u_doors))
region_locations = list(set(state.found_locations).difference(parent_locations)) region_locations = [x for x in state.found_locations if x not in parent_locations]
for loc in region_locations: for loc in region_locations:
if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']: if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']:
key_sphere.prize_region = True key_sphere.prize_region = True
@@ -712,7 +718,7 @@ def validate_key_layout_ex(key_layout, world, player):
def validate_key_layout_main_loop(key_layout, world, player): def validate_key_layout_main_loop(key_layout, world, player):
flat_proposal = key_layout.flat_prop flat_proposal = key_layout.flat_prop
state = ExplorationState() state = ExplorationState(dungeon=key_layout.sector.name)
state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys) state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys)
state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions
for region in key_layout.start_regions: for region in key_layout.start_regions:
@@ -765,7 +771,7 @@ def validate_key_layout_sub_loop(state, checked_states, flat_proposal, world, pl
def create_key_counters(key_layout, world, player): def create_key_counters(key_layout, world, player):
key_counters = {} key_counters = {}
flat_proposal = key_layout.flat_prop flat_proposal = key_layout.flat_prop
state = ExplorationState() state = ExplorationState(dungeon=key_layout.sector.name)
state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys) state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys)
state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions
for region in key_layout.start_regions: for region in key_layout.start_regions:
@@ -885,11 +891,11 @@ def validate_vanilla_key_logic(world, player):
def val_hyrule(key_logic, world, player): def val_hyrule(key_logic, world, player):
val_rule(key_logic.door_rules['Sewers Secret Room Key Door S'], 2) val_rule(key_logic.door_rules['Sewers Secret Room Key Door S'], 3)
val_rule(key_logic.door_rules['Sewers Dark Cross Key Door N'], 2) val_rule(key_logic.door_rules['Sewers Dark Cross Key Door N'], 3)
val_rule(key_logic.door_rules['Hyrule Dungeon Map Room Key Door S'], 2) val_rule(key_logic.door_rules['Hyrule Dungeon Map Room Key Door S'], 2)
# why is allow_small actually false? - because chest key is forced elsewhere? # why is allow_small actually false? - because chest key is forced elsewhere?
val_rule(key_logic.door_rules['Hyrule Dungeon Armory Interior Key Door N'], 4, True, 'Hyrule Castle - Zelda\'s Chest') val_rule(key_logic.door_rules['Hyrule Dungeon Armory Interior Key Door N'], 3, True, 'Hyrule Castle - Zelda\'s Chest')
# val_rule(key_logic.door_rules['Hyrule Dungeon Armory Interior Key Door N'], 4) # val_rule(key_logic.door_rules['Hyrule Dungeon Armory Interior Key Door N'], 4)

View File

@@ -363,6 +363,7 @@ def global_rules(world, player):
set_rule(world.get_entrance('Ice Spike Room Up Stairs', player), lambda state: state.world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player)) set_rule(world.get_entrance('Ice Spike Room Up Stairs', player), lambda state: state.world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))
set_rule(world.get_entrance('Ice Spike Room Down Stairs', player), lambda state: state.world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player)) set_rule(world.get_entrance('Ice Spike Room Down Stairs', player), lambda state: state.world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))
set_rule(world.get_location('Ice Palace - Spike Room', player), lambda state: state.world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player)) set_rule(world.get_location('Ice Palace - Spike Room', player), lambda state: state.world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))
set_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: state.can_melt_things(player))
set_rule(world.get_entrance('Ice Hookshot Ledge Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Ice Hookshot Ledge Path', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_entrance('Ice Hookshot Balcony Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Ice Hookshot Balcony Path', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_entrance('Ice Switch Room SE', player), lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player)) set_rule(world.get_entrance('Ice Switch Room SE', player), lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player))
@@ -983,10 +984,12 @@ def open_rules(world, player):
def swordless_rules(world, player): def swordless_rules(world, player):
set_rule(world.get_entrance('Agahnim 1', player), lambda state: (state.has('Hammer', player) or state.has('Fire Rod', player) or state.can_shoot_arrows(player) or state.has('Cane of Somaria', player)) and state.has_key('Small Key (Agahnims Tower)', player, 2)) set_rule(world.get_entrance('Tower Altar NW', player), lambda state: True)
set_rule(world.get_entrance('Skull Vines NW', player), lambda state: True)
set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))
set_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))
set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player)) set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player))
set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player)) # no curtain
set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) #in swordless mode bombos pads are present in the relevant parts of ice palace
set_rule(world.get_location('Ganon', player), lambda state: state.has('Hammer', player) and state.has_fire_source(player) and state.has('Silver Arrows', player) and state.can_shoot_arrows(player) and state.has_crystals(world.crystals_needed_for_ganon, player)) set_rule(world.get_location('Ganon', player), lambda state: state.has('Hammer', player) and state.has_fire_source(player) and state.has('Silver Arrows', player) and state.can_shoot_arrows(player) and state.has_crystals(world.crystals_needed_for_ganon, player))
set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop