Key Hud redesign continued
Keydropshuffle prototype
This commit is contained in:
@@ -127,6 +127,8 @@ class World(object):
|
||||
set_player_attr('treasure_hunt_icon', 'Triforce Piece')
|
||||
set_player_attr('treasure_hunt_count', 0)
|
||||
|
||||
set_player_attr('keydropshuffle', False)
|
||||
|
||||
def get_name_string_for_object(self, obj):
|
||||
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})'
|
||||
|
||||
@@ -562,15 +564,20 @@ class CollectionState(object):
|
||||
def _do_not_flood_the_keys(self, reachable_events):
|
||||
adjusted_checks = list(reachable_events)
|
||||
for event in reachable_events:
|
||||
if event.name in flooded_keys.keys() and self.world.get_location(flooded_keys[event.name], event.player) not in reachable_events:
|
||||
adjusted_checks.remove(event)
|
||||
if event.name in flooded_keys.keys():
|
||||
flood_location = self.world.get_location(flooded_keys[event.name], event.player)
|
||||
if flood_location.item and flood_location not in self.locations_checked:
|
||||
adjusted_checks.remove(event)
|
||||
if len(adjusted_checks) < len(reachable_events):
|
||||
return adjusted_checks
|
||||
return reachable_events
|
||||
|
||||
def not_flooding_a_key(self, world, location):
|
||||
if location.name in flooded_keys.keys():
|
||||
return world.get_location(flooded_keys[location.name], location.player) in self.locations_checked
|
||||
flood_location = world.get_location(flooded_keys[location.name], location.player)
|
||||
item = flood_location.item
|
||||
item_is_important = False if not item else item.advancement or item.bigkey or item.smallkey
|
||||
return flood_location in self.locations_checked or not item_is_important
|
||||
return True
|
||||
|
||||
def has(self, item, player, count=1):
|
||||
@@ -578,7 +585,7 @@ class CollectionState(object):
|
||||
return (item, player) in self.prog_items
|
||||
return self.prog_items[item, player] >= count
|
||||
|
||||
def has_key(self, item, player, count=1):
|
||||
def has_sm_key(self, item, player, count=1):
|
||||
if self.world.retro[player]:
|
||||
return self.can_buy_unlimited('Small Key (Universal)', player)
|
||||
if count == 1:
|
||||
@@ -1357,7 +1364,6 @@ class Sector(object):
|
||||
self.name = None
|
||||
self.r_name_set = None
|
||||
self.chest_locations = 0
|
||||
self.big_chest_present = False
|
||||
self.key_only_locations = 0
|
||||
self.c_switch = False
|
||||
self.orange_barrier = False
|
||||
@@ -2019,3 +2025,8 @@ flooded_keys = {
|
||||
'Trench 1 Switch': 'Swamp Palace - Trench 1 Pot Key',
|
||||
'Trench 2 Switch': 'Swamp Palace - Trench 2 Pot Key'
|
||||
}
|
||||
|
||||
dungeon_names = [
|
||||
'Hyrule Castle', 'Eastern Palace', 'Desert Palace', 'Tower of Hera', 'Agahnims Tower', 'Palace of Darkness',
|
||||
'Swamp Palace', 'Skull Woods', 'Thieves Town', 'Ice Palace', 'Misery Mire', 'Turtle Rock', 'Ganons Tower'
|
||||
]
|
||||
|
||||
3
CLI.py
3
CLI.py
@@ -95,7 +95,7 @@ def parse_cli(argv, no_defaults=False):
|
||||
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
|
||||
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
|
||||
'remote_items']:
|
||||
'remote_items', 'keydropshuffle']:
|
||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||
if player == 1:
|
||||
setattr(ret, name, {1: value})
|
||||
@@ -135,6 +135,7 @@ def parse_settings():
|
||||
"enemy_health": "default",
|
||||
"enemizercli": os.path.join(".", "EnemizerCLI", "EnemizerCLI.Core"),
|
||||
|
||||
"keydropshuffle": False,
|
||||
"mapshuffle": False,
|
||||
"compassshuffle": False,
|
||||
"keyshuffle": False,
|
||||
|
||||
@@ -8,8 +8,7 @@ from typing import DefaultDict, Dict, List
|
||||
|
||||
from functools import reduce
|
||||
from BaseClasses import RegionType, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo
|
||||
from Regions import key_only_locations
|
||||
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts, flexible_starts
|
||||
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
|
||||
from Dungeons import dungeon_bigs, dungeon_keys, dungeon_hints
|
||||
from Items import ItemFactory
|
||||
from RoomData import DoorKind, PairedDoor
|
||||
@@ -188,7 +187,7 @@ def vanilla_key_logic(world, player):
|
||||
world.key_logic[player][builder.name] = key_layout.key_logic
|
||||
log_key_logic(builder.name, key_layout.key_logic)
|
||||
last_key = None
|
||||
if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items' and not world.retro[player]:
|
||||
if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]:
|
||||
validate_vanilla_key_logic(world, player)
|
||||
|
||||
|
||||
@@ -207,9 +206,9 @@ def switch_dir(direction):
|
||||
return oppositemap[direction]
|
||||
|
||||
|
||||
def convert_key_doors(key_doors, world, player):
|
||||
def convert_key_doors(k_doors, world, player):
|
||||
result = []
|
||||
for d in key_doors:
|
||||
for d in k_doors:
|
||||
if type(d) is tuple:
|
||||
result.append((world.get_door(d[0], player), world.get_door(d[1], player)))
|
||||
else:
|
||||
@@ -866,7 +865,7 @@ def cross_dungeon(world, player):
|
||||
for region in sector.regions:
|
||||
region.dungeon = dungeon_obj
|
||||
for loc in region.locations:
|
||||
if loc.name in key_only_locations:
|
||||
if loc.forced_item:
|
||||
key_name = dungeon_keys[builder.name] if loc.name != 'Hyrule Castle - Big Key Drop' else dungeon_bigs[builder.name]
|
||||
loc.forced_item = loc.item = ItemFactory(key_name, player)
|
||||
recombinant_builders = {}
|
||||
@@ -885,9 +884,12 @@ def cross_dungeon(world, player):
|
||||
at.dungeon_items.append(ItemFactory('Map (Agahnims Tower)', player))
|
||||
|
||||
assign_cross_keys(dungeon_builders, world, player)
|
||||
all_dungeon_items = [y for x in world.dungeons if x.player == player for y in x.all_items]
|
||||
target_items = 34 if world.retro[player] else 63
|
||||
d_items = target_items - len(all_dungeon_items)
|
||||
all_dungeon_items_cnt = len(list(y for x in world.dungeons if x.player == player for y in x.all_items))
|
||||
if world.keydropshuffle[player]:
|
||||
target_items = 35 if world.retro[player] else 96
|
||||
else:
|
||||
target_items = 34 if world.retro[player] else 63
|
||||
d_items = target_items - all_dungeon_items_cnt
|
||||
world.pool_adjustment[player] = d_items
|
||||
smooth_door_pairs(world, player)
|
||||
|
||||
@@ -949,7 +951,11 @@ def cross_dungeon(world, player):
|
||||
def assign_cross_keys(dungeon_builders, world, player):
|
||||
logging.getLogger('').info(world.fish.translate("cli", "cli", "shuffling.keydoors"))
|
||||
start = time.process_time()
|
||||
total_keys = remaining = 29
|
||||
if world.retro[player]:
|
||||
remaining = 61 if world.keydropshuffle[player] else 29
|
||||
else:
|
||||
remaining = len(list(x for dgn in world.dungeons if dgn.player == player for x in dgn.small_keys))
|
||||
total_keys = remaining
|
||||
total_candidates = 0
|
||||
start_regions_map = {}
|
||||
# Step 1: Find Small Key Door Candidates
|
||||
@@ -1027,7 +1033,7 @@ def assign_cross_keys(dungeon_builders, world, player):
|
||||
dungeon.small_keys = []
|
||||
else:
|
||||
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
|
||||
logger.info('%s: %s', world.fish.translate("cli", "cli", "keydoor.shuffle.time.crossed"), time.process_time()-start)
|
||||
logger.info(f'{world.fish.translate("cli", "cli", "keydoor.shuffle.time.crossed")}: {time.process_time()-start}')
|
||||
|
||||
|
||||
def reassign_boss(boss_region, boss_key, builder, gt, world, player):
|
||||
@@ -1176,10 +1182,10 @@ def calc_used_dungeon_items(builder):
|
||||
base = 4
|
||||
if builder.bk_required and not builder.bk_provided:
|
||||
base += 1
|
||||
if builder.name == 'Hyrule Castle':
|
||||
base -= 1 # Missing compass/map
|
||||
if builder.name == 'Agahnims Tower':
|
||||
base -= 2 # Missing both compass/map
|
||||
# if builder.name == 'Hyrule Castle':
|
||||
# base -= 1 # Missing compass/map
|
||||
# if builder.name == 'Agahnims Tower':
|
||||
# base -= 2 # Missing both compass/map
|
||||
# gt can lose map once compasses work
|
||||
return base
|
||||
|
||||
@@ -1690,6 +1696,8 @@ class DROptions(Flag):
|
||||
# DATA GOES DOWN HERE
|
||||
logical_connections = [
|
||||
('Hyrule Dungeon North Abyss Catwalk Dropdown', 'Hyrule Dungeon North Abyss'),
|
||||
('Hyrule Dungeon Cellblock Door', 'Hyrule Dungeon Cell'),
|
||||
('Hyrule Dungeon Cell Exit', 'Hyrule Dungeon Cellblock'),
|
||||
('Hyrule Castle Throne Room Tapestry', 'Hyrule Castle Behind Tapestry'),
|
||||
('Hyrule Castle Tapestry Backwards', 'Hyrule Castle Throne Room'),
|
||||
('Sewers Secret Room Push Block', 'Sewers Secret Room Blocked Path'),
|
||||
@@ -1761,6 +1769,8 @@ logical_connections = [
|
||||
('Thieves Blocked Entry Path', 'Thieves Basement Block'),
|
||||
('Thieves Conveyor Bridge Block Path', 'Thieves Conveyor Block'),
|
||||
('Thieves Conveyor Block Path', 'Thieves Conveyor Bridge'),
|
||||
("Thieves Blind's Cell Door", "Thieves Blind's Cell Interior"),
|
||||
("Thieves Blind's Cell Exit", "Thieves Blind's Cell"),
|
||||
('Ice Cross Bottom Push Block Left', 'Ice Floor Switch'),
|
||||
('Ice Cross Right Push Block Top', 'Ice Bomb Drop'),
|
||||
('Ice Big Key Push Block', 'Ice Dead End'),
|
||||
|
||||
4
Doors.py
4
Doors.py
@@ -92,6 +92,8 @@ def create_doors(world, player):
|
||||
create_door(player, 'Hyrule Dungeon Staircase Up Stairs', Sprl).dir(Up, 0x70, 2, LTH).ss(A, 0x32, 0x94, True),
|
||||
create_door(player, 'Hyrule Dungeon Staircase Down Stairs', Sprl).dir(Dn, 0x70, 1, HTH).ss(A, 0x11, 0x58),
|
||||
create_door(player, 'Hyrule Dungeon Cellblock Up Stairs', Sprl).dir(Up, 0x80, 0, HTH).ss(A, 0x1a, 0x44),
|
||||
create_door(player, 'Hyrule Dungeon Cellblock Door', Lgcl).big_key(),
|
||||
create_door(player, 'Hyrule Dungeon Cell Exit', Lgcl),
|
||||
|
||||
# sewers
|
||||
create_door(player, 'Sewers Behind Tapestry S', Nrml).dir(So, 0x41, Mid, High).no_exit().trap(0x4).pos(0).portal(Z, 0x22),
|
||||
@@ -625,6 +627,8 @@ def create_doors(world, player):
|
||||
create_door(player, 'Thieves Lonely Zazak NW', Intr).dir(No, 0x45, Left, High).pos(1),
|
||||
create_door(player, 'Thieves Lonely Zazak ES', Intr).dir(Ea, 0x45, Right, High).pos(3),
|
||||
create_door(player, 'Thieves Blind\'s Cell WS', Intr).dir(We, 0x45, Right, High).pos(3),
|
||||
create_door(player, "Thieves Blind's Cell Door", Lgcl).big_key(),
|
||||
create_door(player, "Thieves Blind's Cell Exit", Lgcl),
|
||||
create_door(player, 'Thieves Conveyor Bridge EN', Nrml).dir(Ea, 0x44, Top, High).pos(2),
|
||||
create_door(player, 'Thieves Conveyor Bridge ES', Nrml).dir(Ea, 0x44, Bot, High).pos(3),
|
||||
create_door(player, 'Thieves Conveyor Bridge Block Path', Lgcl),
|
||||
|
||||
@@ -196,7 +196,11 @@ def determine_if_bk_needed(sector, split_dungeon, world, player):
|
||||
|
||||
|
||||
def check_for_special(sector):
|
||||
return 'Hyrule Dungeon Cellblock' in sector.region_set()
|
||||
for region in sector.regions:
|
||||
for loc in region.locations:
|
||||
if loc.forced_big_key():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def gen_dungeon_info(name, available_sectors, entrance_regions, all_regions, proposed_map, valid_doors, bk_needed, bk_special, world, player):
|
||||
@@ -661,7 +665,7 @@ def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exceptio
|
||||
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() and x.name not in ['Agahnim 1', 'Agahnim 2']]
|
||||
and not x.forced_item and x.name not in ['Agahnim 1', 'Agahnim 2']]
|
||||
|
||||
|
||||
type_map = {
|
||||
@@ -763,6 +767,9 @@ def connect_simple_door(exit_door, region):
|
||||
exit_door.dest = region
|
||||
|
||||
|
||||
special_big_key_doors = ['Hyrule Dungeon Cellblock Door', "Thieves Blind's Cell Door"]
|
||||
|
||||
|
||||
class ExplorationState(object):
|
||||
|
||||
def __init__(self, init_crystal=CrystalBarrier.Orange, dungeon=None):
|
||||
@@ -844,7 +851,7 @@ class ExplorationState(object):
|
||||
if region.type == RegionType.Dungeon:
|
||||
for location in region.locations:
|
||||
if key_checks and location not in self.found_locations:
|
||||
if location.name in key_only_locations and 'Small Key' in location.item.name:
|
||||
if location.forced_item and 'Small Key' in location.item.name:
|
||||
self.key_locations += 1
|
||||
if location.name not in dungeon_events and '- Prize' not in location.name and location.name not in ['Agahnim 1', 'Agahnim 2']:
|
||||
self.ttl_locations += 1
|
||||
@@ -955,7 +962,7 @@ class ExplorationState(object):
|
||||
if door in key_door_proposal and door not in self.opened_doors:
|
||||
if not self.in_door_list(door, self.small_doors):
|
||||
self.append_door_to_list(door, self.small_doors)
|
||||
elif door.bigKey and not self.big_key_opened:
|
||||
elif (door.bigKey or door.name in special_big_key_doors) and not self.big_key_opened:
|
||||
if not self.in_door_list(door, self.big_doors):
|
||||
self.append_door_to_list(door, self.big_doors)
|
||||
elif door.req_event is not None and door.req_event not in self.events:
|
||||
@@ -989,18 +996,10 @@ class ExplorationState(object):
|
||||
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
|
||||
return True
|
||||
|
||||
def can_traverse_bk_check(self, door, isOrigin):
|
||||
if door.blocked:
|
||||
return False
|
||||
if door.crystal not in [CrystalBarrier.Null, CrystalBarrier.Either]:
|
||||
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
|
||||
return not isOrigin or not door.bigKey or self.count_locations_exclude_specials() > 0
|
||||
# return not door.bigKey or len([x for x in self.found_locations if '- Prize' not in x.name]) > 0
|
||||
|
||||
def count_locations_exclude_specials(self):
|
||||
cnt = 0
|
||||
for loc in self.found_locations:
|
||||
if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events and loc.name not in key_only_locations.keys():
|
||||
if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events and not loc.forced_item:
|
||||
cnt += 1
|
||||
return cnt
|
||||
|
||||
@@ -1410,21 +1409,19 @@ def calc_allowance_and_dead_ends(builder, connections_tuple, portals):
|
||||
|
||||
def define_sector_features(sectors):
|
||||
for sector in sectors:
|
||||
if 'Hyrule Dungeon Cellblock' in sector.region_set():
|
||||
sector.bk_provided = True
|
||||
if 'Thieves Blind\'s Cell' in sector.region_set():
|
||||
sector.bk_required = True
|
||||
for region in sector.regions:
|
||||
for loc in region.locations:
|
||||
if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2', 'Hyrule Castle - Big Key Drop']:
|
||||
if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']:
|
||||
pass
|
||||
elif loc.event and 'Small Key' in loc.item.name:
|
||||
elif loc.forced_item and 'Small Key' in loc.item.name:
|
||||
sector.key_only_locations += 1
|
||||
elif loc.name not in dungeon_events:
|
||||
elif loc.forced_item and loc.forced_item.bigkey:
|
||||
sector.bk_provided = True
|
||||
elif loc.name not in dungeon_events and not loc.forced_item:
|
||||
sector.chest_locations += 1
|
||||
if '- Big Chest' in loc.name:
|
||||
if '- Big Chest' in loc.name or loc.name in ["Hyrule Castle - Zelda's Chest",
|
||||
"Thieves' Town - Blind's Cell"]:
|
||||
sector.bk_required = True
|
||||
sector.big_chest_present = True
|
||||
for ext in region.exits:
|
||||
door = ext.door
|
||||
if door is not None:
|
||||
|
||||
13
Dungeons.py
13
Dungeons.py
@@ -167,9 +167,9 @@ hyrule_castle_regions = [
|
||||
'Hyrule Dungeon Map Room', 'Hyrule Dungeon North Abyss', 'Hyrule Dungeon North Abyss Catwalk',
|
||||
'Hyrule Dungeon South Abyss', 'Hyrule Dungeon South Abyss Catwalk', 'Hyrule Dungeon Guardroom',
|
||||
'Hyrule Dungeon Armory Main', 'Hyrule Dungeon Armory Boomerang', 'Hyrule Dungeon Armory North Branch',
|
||||
'Hyrule Dungeon Staircase', 'Hyrule Dungeon Cellblock', 'Sewers Behind Tapestry', 'Sewers Rope Room',
|
||||
'Sewers Dark Cross', 'Sewers Water', 'Sewers Key Rat', 'Sewers Rat Path', 'Sewers Secret Room Blocked Path',
|
||||
'Sewers Secret Room', 'Sewers Yet More Rats', 'Sewers Pull Switch', 'Sanctuary'
|
||||
'Hyrule Dungeon Staircase', 'Hyrule Dungeon Cellblock', 'Hyrule Dungeon Cell', 'Sewers Behind Tapestry',
|
||||
'Sewers Rope Room', 'Sewers Dark Cross', 'Sewers Water', 'Sewers Key Rat', 'Sewers Rat Path',
|
||||
'Sewers Secret Room Blocked Path', 'Sewers Secret Room', 'Sewers Yet More Rats', 'Sewers Pull Switch', 'Sanctuary'
|
||||
]
|
||||
|
||||
eastern_regions = [
|
||||
@@ -239,9 +239,10 @@ thieves_regions = [
|
||||
'Thieves Big Chest Nook', 'Thieves Hallway', 'Thieves Boss', 'Thieves Pot Alcove Mid', 'Thieves Pot Alcove Bottom',
|
||||
'Thieves Pot Alcove Top', 'Thieves Conveyor Maze', 'Thieves Spike Track', 'Thieves Hellway',
|
||||
'Thieves Hellway N Crystal', 'Thieves Hellway S Crystal', 'Thieves Triple Bypass', 'Thieves Spike Switch',
|
||||
'Thieves Attic', 'Thieves Attic Hint', 'Thieves Cricket Hall Left', 'Thieves Cricket Hall Right', 'Thieves Attic Window',
|
||||
'Thieves Basement Block', 'Thieves Blocked Entry', 'Thieves Lonely Zazak', 'Thieves Blind\'s Cell',
|
||||
'Thieves Conveyor Bridge', 'Thieves Conveyor Block', 'Thieves Big Chest Room', 'Thieves Trap'
|
||||
'Thieves Attic', 'Thieves Attic Hint', 'Thieves Cricket Hall Left', 'Thieves Cricket Hall Right',
|
||||
'Thieves Attic Window', 'Thieves Basement Block', 'Thieves Blocked Entry', 'Thieves Lonely Zazak',
|
||||
"Thieves Blind's Cell", "Thieves Blind's Cell Interior", 'Thieves Conveyor Bridge', 'Thieves Conveyor Block',
|
||||
'Thieves Big Chest Room', 'Thieves Trap'
|
||||
]
|
||||
|
||||
ice_regions = [
|
||||
|
||||
@@ -355,9 +355,12 @@ def generate_itempool(world, player):
|
||||
|
||||
if world.retro[player]:
|
||||
set_up_take_anys(world, player)
|
||||
if world.keydropshuffle[player]:
|
||||
world.itempool += [ItemFactory('Small Key (Universal)', player)] * 32
|
||||
|
||||
create_dynamic_shop_locations(world, player)
|
||||
|
||||
|
||||
take_any_locations = [
|
||||
'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut',
|
||||
'Fortune Teller (Light)', 'Lake Hylia Fortune Teller', 'Lumberjack House', 'Bonk Fairy (Light)',
|
||||
|
||||
@@ -3,9 +3,9 @@ import logging
|
||||
from collections import defaultdict, deque
|
||||
|
||||
from BaseClasses import DoorType
|
||||
from Regions import dungeon_events, key_only_locations
|
||||
from Regions import dungeon_events
|
||||
from Dungeons import dungeon_keys, dungeon_bigs
|
||||
from DungeonGenerator import ExplorationState
|
||||
from DungeonGenerator import ExplorationState, special_big_key_doors
|
||||
|
||||
|
||||
class KeyLayout(object):
|
||||
@@ -190,7 +190,7 @@ def build_key_layout(builder, start_regions, proposal, world, player):
|
||||
key_layout.flat_prop = flatten_pair_list(key_layout.proposal)
|
||||
key_layout.max_drops = count_key_drops(key_layout.sector)
|
||||
key_layout.max_chests = calc_max_chests(builder, key_layout, world, player)
|
||||
key_layout.big_key_special = 'Hyrule Dungeon Cellblock' in key_layout.sector.region_set()
|
||||
key_layout.big_key_special = check_bk_special(key_layout.sector.region_set(), world, player)
|
||||
key_layout.all_locations = find_all_locations(key_layout.sector)
|
||||
return key_layout
|
||||
|
||||
@@ -199,7 +199,7 @@ def count_key_drops(sector):
|
||||
cnt = 0
|
||||
for region in sector.regions:
|
||||
for loc in region.locations:
|
||||
if loc.event and 'Small Key' in loc.item.name:
|
||||
if loc.forced_item and 'Small Key' in loc.item.name:
|
||||
cnt += 1
|
||||
return cnt
|
||||
|
||||
@@ -253,8 +253,6 @@ def analyze_dungeon(key_layout, world, player):
|
||||
odd_counter = create_odd_key_counter(child, key_counter, key_layout, world, player)
|
||||
empty_flag = empty_counter(odd_counter)
|
||||
child_queue.append((child, odd_counter, empty_flag))
|
||||
if child in doors_completed and child in key_logic.door_rules.keys():
|
||||
rule = key_logic.door_rules[child]
|
||||
while len(child_queue) > 0:
|
||||
child, odd_counter, empty_flag = child_queue.popleft()
|
||||
if not child.bigKey and child not in doors_completed:
|
||||
@@ -650,17 +648,17 @@ def find_worst_counter(door, odd_counter, key_counter, key_layout, skip_bk): #
|
||||
def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk, reserve=1):
|
||||
small_doors = []
|
||||
big_doors = []
|
||||
if key_layout.big_key_special:
|
||||
big_key_available = any(x for x in key_counter.other_locations.keys() if x.forced_item and x.forced_item.bigkey)
|
||||
else:
|
||||
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(reserve) > 0
|
||||
for other in key_counter.child_doors:
|
||||
if other not in ignored_doors and other.dest not in ignored_doors:
|
||||
if other.bigKey:
|
||||
if not skip_bk and (not key_layout.big_key_special or key_counter.big_key_opened):
|
||||
if not skip_bk and (not key_layout.big_key_special or big_key_available):
|
||||
big_doors.append(other)
|
||||
elif other.dest not in small_doors:
|
||||
small_doors.append(other)
|
||||
if key_layout.big_key_special:
|
||||
big_key_available = key_counter.big_key_opened
|
||||
else:
|
||||
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(reserve) > 0
|
||||
if len(small_doors) == 0 and (not skip_bk and (len(big_doors) == 0 or not big_key_available)):
|
||||
return None
|
||||
return small_doors + big_doors
|
||||
@@ -885,7 +883,7 @@ def find_worst_counter_wo_bk(small_key_num, accessible_set, door, odd_ctr, key_c
|
||||
|
||||
|
||||
def open_a_door(door, child_state, flat_proposal):
|
||||
if door.bigKey:
|
||||
if door.bigKey or door.name in special_big_key_doors:
|
||||
child_state.big_key_opened = True
|
||||
child_state.avail_doors.extend(child_state.big_doors)
|
||||
child_state.opened_doors.extend(set([d.door for d in child_state.big_doors]))
|
||||
@@ -992,8 +990,7 @@ def count_locations_exclude_big_chest(state):
|
||||
cnt = 0
|
||||
for loc in state.found_locations:
|
||||
if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events:
|
||||
if not loc.forced_item and loc.name not in ['Agahnim 1', 'Agahnim 2', "Hyrule Castle - Zelda's Chest",
|
||||
"Thieves' Town - Blind's Cell"]:
|
||||
if not loc.forced_item and loc.name not in ['Agahnim 1', 'Agahnim 2']:
|
||||
cnt += 1
|
||||
return cnt
|
||||
|
||||
@@ -1372,9 +1369,11 @@ def create_key_counters(key_layout, world, player):
|
||||
else:
|
||||
state.key_locations = world.dungeon_layouts[player][key_layout.sector.name].key_doors_num
|
||||
state.big_key_special, special_region = False, None
|
||||
forced_bk = world.get_region('Hyrule Dungeon Cellblock', player)
|
||||
if forced_bk in key_layout.sector.regions:
|
||||
state.big_key_special, special_region = True, forced_bk
|
||||
for region in key_layout.sector.regions:
|
||||
for location in region.locations:
|
||||
if location.forced_big_key():
|
||||
state.big_key_special = True
|
||||
special_region = region
|
||||
for region in key_layout.start_regions:
|
||||
state.visit_region(region, key_checks=True)
|
||||
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
||||
@@ -1386,7 +1385,7 @@ def create_key_counters(key_layout, world, player):
|
||||
next_key_counter, parent_state = queue.popleft()
|
||||
for door in next_key_counter.child_doors:
|
||||
child_state = parent_state.copy()
|
||||
if door.bigKey:
|
||||
if door.bigKey or door.name in special_big_key_doors:
|
||||
key_layout.key_logic.bk_doors.add(door)
|
||||
# open the door, if possible
|
||||
if not door.bigKey or not child_state.big_key_special or child_state.visited(special_region):
|
||||
@@ -1436,7 +1435,7 @@ def imp_locations_factory(world, player):
|
||||
|
||||
|
||||
def important_location(loc, world, player):
|
||||
return '- Prize' in loc.name or loc.name in imp_locations_factory(world, player) or (loc.forced_item is not None and loc.item.bigkey)
|
||||
return '- Prize' in loc.name or loc.name in imp_locations_factory(world, player) or (loc.forced_big_key())
|
||||
|
||||
|
||||
def create_odd_key_counter(door, parent_counter, key_layout, world, player):
|
||||
@@ -1703,13 +1702,13 @@ def validate_key_placement(key_layout, world, player):
|
||||
max_counter = find_max_counter(key_layout)
|
||||
keys_outside = 0
|
||||
big_key_outside = False
|
||||
dungeon = world.get_dungeon(key_layout.sector.name, player)
|
||||
smallkey_name = 'Small Key (%s)' % (key_layout.sector.name if key_layout.sector.name != 'Hyrule Castle' else 'Escape')
|
||||
smallkey_name = dungeon_keys[key_layout.sector.name]
|
||||
bigkey_name = dungeon_bigs[key_layout.sector.name]
|
||||
if world.keyshuffle[player]:
|
||||
keys_outside = key_layout.max_chests - sum(1 for i in max_counter.free_locations if i.item is not None and i.item.name == smallkey_name and i.item.player == player)
|
||||
if world.bigkeyshuffle[player]:
|
||||
max_counter = find_max_counter(key_layout)
|
||||
big_key_outside = dungeon.big_key not in (l.item for l in max_counter.free_locations)
|
||||
big_key_outside = bigkey_name not in (l.item.name for l in max_counter.free_locations if l.item)
|
||||
|
||||
for code, counter in key_layout.key_counters.items():
|
||||
if len(counter.child_doors) == 0:
|
||||
@@ -1717,7 +1716,7 @@ def validate_key_placement(key_layout, world, player):
|
||||
if key_layout.big_key_special:
|
||||
big_found = any(i.forced_item is not None and i.item.bigkey for i in counter.other_locations) or big_key_outside
|
||||
else:
|
||||
big_found = any(i.item is not None and i.item == dungeon.big_key for i in counter.free_locations if "- Big Chest" not in i.name) or big_key_outside
|
||||
big_found = any(i.item is not None and i.item.name == bigkey_name for i in counter.free_locations if "- Big Chest" not in i.name) or big_key_outside
|
||||
if counter.big_key_opened and not big_found:
|
||||
continue # Can't get to this state
|
||||
found_locations = set(i for i in counter.free_locations if big_found or "- Big Chest" not in i.name)
|
||||
@@ -1727,7 +1726,7 @@ def validate_key_placement(key_layout, world, player):
|
||||
found_keys > counter.used_keys and any(not d.bigKey for d in counter.child_doors)
|
||||
if not can_progress:
|
||||
missing_locations = set(max_counter.free_locations.keys()).difference(found_locations)
|
||||
missing_items = [l for l in missing_locations if l.item is None or (l.item.name != smallkey_name and l.item != dungeon.big_key) or "- Boss" in l.name]
|
||||
missing_items = [l for l in missing_locations if l.item is None or (l.item.name != smallkey_name and l.item.name != bigkey_name) or "- Boss" in l.name]
|
||||
# missing_key_only = set(max_counter.key_only_locations.keys()).difference(counter.key_only_locations.keys()) # do freestanding keys matter for locations?
|
||||
if len(missing_items) > 0: # world.accessibility[player]=='locations' and (len(missing_locations)>0 or len(missing_key_only) > 0):
|
||||
logging.getLogger('').error("Keylock - can't open locations: ")
|
||||
|
||||
7
Main.py
7
Main.py
@@ -11,7 +11,7 @@ import zlib
|
||||
from BaseClasses import World, CollectionState, Item, Region, Location, Shop, Entrance
|
||||
from Items import ItemFactory
|
||||
from KeyDoorShuffle import validate_key_placement
|
||||
from Regions import create_regions, create_shops, mark_light_world_regions, create_dungeon_regions
|
||||
from Regions import create_regions, create_shops, mark_light_world_regions, create_dungeon_regions, adjust_locations
|
||||
from InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
||||
from EntranceShuffle import link_entrances, link_inverted_entrances
|
||||
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, JsonRom, get_hash_string
|
||||
@@ -24,7 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
|
||||
from ItemList import generate_itempool, difficulties, fill_prizes
|
||||
from Utils import output_path, parse_player_names
|
||||
|
||||
__version__ = '0.2.0.1-u'
|
||||
__version__ = '0.2.0.2-u'
|
||||
|
||||
class EnemizerError(RuntimeError):
|
||||
pass
|
||||
@@ -66,6 +66,7 @@ def main(args, seed=None, fish=None):
|
||||
world.experimental = args.experimental.copy()
|
||||
world.dungeon_counters = args.dungeon_counters.copy()
|
||||
world.fish = fish
|
||||
world.keydropshuffle = args.keydropshuffle.copy()
|
||||
|
||||
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
||||
|
||||
@@ -105,6 +106,7 @@ def main(args, seed=None, fish=None):
|
||||
create_doors(world, player)
|
||||
create_rooms(world, player)
|
||||
create_dungeons(world, player)
|
||||
adjust_locations(world, player)
|
||||
|
||||
logger.info(world.fish.translate("cli","cli","shuffling.world"))
|
||||
|
||||
@@ -371,6 +373,7 @@ def copy_world(world):
|
||||
ret.beemizer = world.beemizer.copy()
|
||||
ret.intensity = world.intensity.copy()
|
||||
ret.experimental = world.experimental.copy()
|
||||
ret.keydropshuffle = world.keydropshuffle.copy()
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
if world.mode[player] != 'inverted':
|
||||
|
||||
71
Regions.py
71
Regions.py
@@ -252,10 +252,11 @@ def create_dungeon_regions(world, player):
|
||||
create_dungeon_region(player, 'Hyrule Dungeon Armory Boomerang', 'Hyrule Castle', ['Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Boomerang Guard Key Drop'], ['Hyrule Dungeon Armory Boomerang WS']),
|
||||
create_dungeon_region(player, 'Hyrule Dungeon Armory North Branch', 'Hyrule Castle', None, ['Hyrule Dungeon Armory Interior Key Door S', 'Hyrule Dungeon Armory Down Stairs']),
|
||||
create_dungeon_region(player, 'Hyrule Dungeon Staircase', 'Hyrule Castle', None, ['Hyrule Dungeon Staircase Up Stairs', 'Hyrule Dungeon Staircase Down Stairs']),
|
||||
create_dungeon_region(player, 'Hyrule Dungeon Cellblock', 'Hyrule Castle',
|
||||
['Hyrule Castle - Big Key Drop', 'Hyrule Castle - Zelda\'s Chest'] if not std_flag else
|
||||
['Hyrule Castle - Big Key Drop', 'Hyrule Castle - Zelda\'s Chest', 'Zelda Pickup'],
|
||||
['Hyrule Dungeon Cellblock Up Stairs']),
|
||||
create_dungeon_region(player, 'Hyrule Dungeon Cellblock', 'Hyrule Castle', ['Hyrule Castle - Big Key Drop'], ['Hyrule Dungeon Cellblock Up Stairs', 'Hyrule Dungeon Cellblock Door']),
|
||||
create_dungeon_region(player, 'Hyrule Dungeon Cell', 'Hyrule Castle',
|
||||
["Hyrule Castle - Zelda's Chest"] if not std_flag else
|
||||
["Hyrule Castle - Zelda's Chest", 'Zelda Pickup'],
|
||||
['Hyrule Dungeon Cell Exit']),
|
||||
|
||||
|
||||
create_dungeon_region(player, 'Sewers Behind Tapestry', 'Hyrule Castle', None, ['Sewers Behind Tapestry S', 'Sewers Behind Tapestry Down Stairs']),
|
||||
@@ -510,7 +511,8 @@ def create_dungeon_regions(world, player):
|
||||
create_dungeon_region(player, 'Thieves Basement Block', 'Thieves\' Town', None, ['Thieves Basement Block Up Stairs', 'Thieves Basement Block WN', 'Thieves Basement Block Path']),
|
||||
create_dungeon_region(player, 'Thieves Blocked Entry', 'Thieves\' Town', None, ['Thieves Blocked Entry Path', 'Thieves Blocked Entry SW']),
|
||||
create_dungeon_region(player, 'Thieves Lonely Zazak', 'Thieves\' Town', None, ['Thieves Lonely Zazak WS', 'Thieves Lonely Zazak ES', 'Thieves Lonely Zazak NW']),
|
||||
create_dungeon_region(player, 'Thieves Blind\'s Cell', 'Thieves\' Town', ['Thieves\' Town - Blind\'s Cell', 'Suspicious Maiden'], ['Thieves Blind\'s Cell WS']),
|
||||
create_dungeon_region(player, "Thieves Blind's Cell", 'Thieves\' Town', None, ["Thieves Blind's Cell WS", "Thieves Blind's Cell Door"]),
|
||||
create_dungeon_region(player, "Thieves Blind's Cell Interior", 'Thieves\' Town', ['Thieves\' Town - Blind\'s Cell', 'Suspicious Maiden'], ["Thieves Blind's Cell Exit"]),
|
||||
create_dungeon_region(player, 'Thieves Conveyor Bridge', 'Thieves\' Town', None, ['Thieves Conveyor Bridge EN', 'Thieves Conveyor Bridge ES', 'Thieves Conveyor Bridge WS', 'Thieves Conveyor Bridge Block Path']),
|
||||
create_dungeon_region(player, 'Thieves Conveyor Block', 'Thieves\' Town', None, ['Thieves Conveyor Block Path', 'Thieves Conveyor Block WN']),
|
||||
create_dungeon_region(player, 'Thieves Big Chest Room', 'Thieves\' Town', ['Thieves\' Town - Big Chest'], ['Thieves Big Chest Room ES']),
|
||||
@@ -858,6 +860,29 @@ def create_shops(world, player):
|
||||
for index, item in enumerate(inventory):
|
||||
shop.add_inventory(index, *item)
|
||||
|
||||
|
||||
def adjust_locations(world, player):
|
||||
if world.keydropshuffle[player]:
|
||||
for location in key_only_locations.keys():
|
||||
loc = world.get_location(location, player)
|
||||
key_item = loc.item
|
||||
key_item.location = None
|
||||
|
||||
loc.forced_item = None
|
||||
loc.item = None
|
||||
loc.event = False
|
||||
loc.address = key_drop_data[location][1]
|
||||
loc.player_address = key_drop_data[location][0]
|
||||
|
||||
item_dungeon = key_item.name.split('(')[1][:-1]
|
||||
item_dungeon = 'Hyrule Castle' if item_dungeon == 'Escape' else item_dungeon
|
||||
dungeon = world.get_dungeon(item_dungeon, player)
|
||||
if key_item.smallkey and not world.retro[player]:
|
||||
dungeon.small_keys.append(key_item)
|
||||
elif key_item.bigkey:
|
||||
dungeon.big_key = key_item
|
||||
|
||||
|
||||
# (type, room_id, shopkeeper, custom, locked, [items])
|
||||
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
||||
_basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)]
|
||||
@@ -912,6 +937,42 @@ key_only_locations = {
|
||||
'Ganons Tower - Mini Helmasaur Key Drop': 'Small Key (Ganons Tower)'
|
||||
}
|
||||
|
||||
key_drop_data = {
|
||||
'Hyrule Castle - Map Guard Key Drop': [0x140036, 0x140037],
|
||||
'Hyrule Castle - Boomerang Guard Key Drop': [0x140033, 0x140034],
|
||||
'Hyrule Castle - Key Rat Key Drop': [0x14000c, 0x14000d],
|
||||
'Hyrule Castle - Big Key Drop': [0x14003c, 0x14003d],
|
||||
'Eastern Palace - Dark Square Pot Key': [0x14005a, 0x14005b],
|
||||
'Eastern Palace - Dark Eyegore Key Drop': [0x140048, 0x140049],
|
||||
'Desert Palace - Desert Tiles 1 Pot Key': [0x140030, 0x140031],
|
||||
'Desert Palace - Beamos Hall Pot Key': [0x14002a, 0x14002b],
|
||||
'Desert Palace - Desert Tiles 2 Pot Key': [0x140027, 0x140028],
|
||||
'Castle Tower - Dark Archer Key Drop': [0x140060, 0x140061],
|
||||
'Castle Tower - Circle of Pots Key Drop': [0x140051, 0x140052],
|
||||
'Swamp Palace - Pot Row Pot Key': [0x140018, 0x140019],
|
||||
'Swamp Palace - Trench 1 Pot Key': [0x140015, 0x140016],
|
||||
'Swamp Palace - Hookshot Pot Key': [0x140012, 0x140013],
|
||||
'Swamp Palace - Trench 2 Pot Key': [0x14000f, 0x140010],
|
||||
'Swamp Palace - Waterway Pot Key': [0x140009, 0x14000a],
|
||||
'Skull Woods - West Lobby Pot Key': [0x14002d, 0x14002e],
|
||||
'Skull Woods - Spike Corner Key Drop': [0x14001b, 0x14001c],
|
||||
'Thieves\' Town - Hallway Pot Key': [0x14005d, 0x14005e],
|
||||
'Thieves\' Town - Spike Switch Pot Key': [0x14004e, 0x14004f],
|
||||
'Ice Palace - Jelly Key Drop': [0x140003, 0x140004],
|
||||
'Ice Palace - Conveyor Key Drop': [0x140021, 0x140022],
|
||||
'Ice Palace - Hammer Block Key Drop': [0x140024, 0x140025],
|
||||
'Ice Palace - Many Pots Pot Key': [0x140045, 0x140046],
|
||||
'Misery Mire - Spikes Pot Key': [0x140054, 0x140055],
|
||||
'Misery Mire - Fishbone Pot Key': [0x14004b, 0x14004c],
|
||||
'Misery Mire - Conveyor Crystal Key Drop': [0x140063, 0x140064],
|
||||
'Turtle Rock - Pokey 1 Key Drop': [0x140057, 0x140058],
|
||||
'Turtle Rock - Pokey 2 Key Drop': [0x140006, 0x140007],
|
||||
'Ganons Tower - Conveyor Cross Pot Key': [0x14003f, 0x140040],
|
||||
'Ganons Tower - Double Switch Pot Key': [0x140042, 0x140043],
|
||||
'Ganons Tower - Conveyor Star Pits Pot Key': [0x140039, 0x14003a],
|
||||
'Ganons Tower - Mini Helmasaur Key Drop': [0x14001e, 0x14001f]
|
||||
}
|
||||
|
||||
dungeon_events = [
|
||||
'Trench 1 Switch',
|
||||
'Trench 2 Switch',
|
||||
|
||||
33
Rom.py
33
Rom.py
@@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses, exit_ids
|
||||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = '094edde26279e8084d525135366fa67c'
|
||||
RANDOMIZERBASEHASH = '78aac1dbdce621865572e06cfcd3c112'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
@@ -503,7 +503,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
|
||||
if not location.crystal:
|
||||
if location.item is not None:
|
||||
# Keys in their native dungeon should use the orignal item code for keys
|
||||
# Keys in their native dungeon should use the original item code for keys
|
||||
if location.parent_region.dungeon:
|
||||
if location.parent_region.dungeon.is_dungeon_item(location.item):
|
||||
if location.item.bigkey:
|
||||
@@ -600,29 +600,35 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
dr_flags |= DROptions.Map_Info
|
||||
dr_flags |= DROptions.Debug
|
||||
|
||||
# fix hc big key problems
|
||||
if world.doorShuffle[player] == 'crossed' or world.keydropshuffle[player]:
|
||||
rom.write_byte(0x151f1, 2)
|
||||
rom.write_byte(0x15270, 2)
|
||||
sanctuary = world.get_region('Sanctuary', player)
|
||||
rom.write_byte(0x1597b, sanctuary.dungeon.dungeon_id*2)
|
||||
if compass_code_good(rom):
|
||||
update_compasses(rom, world, player)
|
||||
else:
|
||||
logging.getLogger('').warning('Randomizer rom update! Compasses in crossed are borken')
|
||||
|
||||
# patch doors
|
||||
if world.doorShuffle[player] == 'crossed':
|
||||
rom.write_byte(0x138002, 2)
|
||||
for name, layout in world.key_layout[player].items():
|
||||
offset = compass_data[name][4]//2
|
||||
rom.write_byte(0x13f01c+offset, layout.max_chests + layout.max_drops)
|
||||
rom.write_byte(0x13f02a+offset, layout.max_chests)
|
||||
if world.retro[player]:
|
||||
rom.write_byte(0x13f02a+offset, layout.max_chests + layout.max_drops)
|
||||
else:
|
||||
rom.write_byte(0x13f01c+offset, layout.max_chests + layout.max_drops) # not currently used
|
||||
rom.write_byte(0x13f02a+offset, layout.max_chests)
|
||||
builder = world.dungeon_layouts[player][name]
|
||||
rom.write_byte(0x13f070+offset, builder.location_cnt % 10)
|
||||
rom.write_byte(0x13f07e+offset, builder.location_cnt // 10)
|
||||
bk_status = 1 if builder.bk_required else 0
|
||||
bk_status = 2 if builder.bk_provided else bk_status
|
||||
rom.write_byte(0x13f038+offset*2, bk_status)
|
||||
rom.write_byte(0x151f1, 2)
|
||||
rom.write_byte(0x15270, 2)
|
||||
sanctuary = world.get_region('Sanctuary', player)
|
||||
rom.write_byte(0x1597b, sanctuary.dungeon.dungeon_id*2)
|
||||
if player in world.sanc_portal.keys():
|
||||
rom.write_byte(0x159a6, world.sanc_portal[player].ent_offset)
|
||||
if compass_code_good(rom):
|
||||
update_compasses(rom, world, player)
|
||||
else:
|
||||
logging.getLogger('').warning('Randomizer rom update! Compasses in crossed are borken')
|
||||
for room in world.rooms:
|
||||
if room.player == player and room.palette is not None:
|
||||
rom.write_byte(0x13f200+room.index, room.palette)
|
||||
@@ -677,6 +683,9 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
|
||||
write_custom_shops(rom, world, player)
|
||||
|
||||
if world.keydropshuffle[player]:
|
||||
rom.write_byte(0x140000, 1)
|
||||
|
||||
# patch medallion requirements
|
||||
if world.required_medallions[player][0] == 'Bombos':
|
||||
rom.write_byte(0x180022, 0x00) # requirement
|
||||
|
||||
26
Rules.py
26
Rules.py
@@ -2,7 +2,6 @@ import logging
|
||||
from collections import deque
|
||||
|
||||
from BaseClasses import CollectionState, RegionType, DoorType, Entrance
|
||||
from Regions import key_only_locations
|
||||
from RoomData import DoorKind
|
||||
|
||||
|
||||
@@ -124,13 +123,12 @@ def global_rules(world, player):
|
||||
set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player))
|
||||
set_rule(world.get_location('Sahasrahla', player), lambda state: state.has('Green Pendant', player))
|
||||
|
||||
|
||||
set_rule(world.get_location('Spike Cave', player), lambda state:
|
||||
state.has('Hammer', player) and state.can_lift_rocks(player) and
|
||||
((state.has('Cape', player) and state.can_extend_magic(player, 16, True)) or
|
||||
(state.has('Cane of Byrna', player) and
|
||||
(state.can_extend_magic(player, 12, True) or
|
||||
(state.world.can_take_damage and (state.has_Boots(player) or state.has_hearts(player, 4))))))
|
||||
state.has('Hammer', player) and state.can_lift_rocks(player) and
|
||||
((state.has('Cape', player) and state.can_extend_magic(player, 16, True)) or
|
||||
(state.has('Cane of Byrna', player) and
|
||||
(state.can_extend_magic(player, 12, True) or
|
||||
(state.world.can_take_damage and (state.has_Boots(player) or state.has_hearts(player, 4))))))
|
||||
)
|
||||
|
||||
set_rule(world.get_location('Hookshot Cave - Top Right', player), lambda state: state.has('Hookshot', player))
|
||||
@@ -757,8 +755,8 @@ def no_glitches_rules(world, player):
|
||||
|
||||
def open_rules(world, player):
|
||||
# softlock protection as you can reach the sewers small key door with a guard drop key
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.has_key('Small Key (Escape)', player))
|
||||
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.has_key('Small Key (Escape)', player))
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.has_sm_key('Small Key (Escape)', player))
|
||||
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.has_sm_key('Small Key (Escape)', player))
|
||||
|
||||
|
||||
def swordless_rules(world, player):
|
||||
@@ -1569,7 +1567,7 @@ def add_key_logic_rules(world, player):
|
||||
if keys.opposite:
|
||||
add_rule(spot, create_advanced_key_rule(d_logic, player, keys.opposite), 'or')
|
||||
for location in d_logic.bk_restricted:
|
||||
if location.name not in key_only_locations.keys():
|
||||
if not location.forced_item:
|
||||
forbid_item(location, d_logic.bk_name, player)
|
||||
for location in d_logic.sm_restricted:
|
||||
forbid_item(location, d_logic.small_key_name, player)
|
||||
@@ -1588,23 +1586,23 @@ def create_rule(item_name, player):
|
||||
|
||||
|
||||
def create_key_rule(small_key_name, player, keys):
|
||||
return lambda state: state.has_key(small_key_name, player, keys)
|
||||
return lambda state: state.has_sm_key(small_key_name, player, keys)
|
||||
|
||||
|
||||
def create_key_rule_allow_small(small_key_name, player, keys, location):
|
||||
loc = location.name
|
||||
return lambda state: state.has_key(small_key_name, player, keys) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_key(small_key_name, player, keys-1))
|
||||
return lambda state: state.has_sm_key(small_key_name, player, keys) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys-1))
|
||||
|
||||
|
||||
def create_key_rule_bk_exception(small_key_name, big_key_name, player, keys, bk_keys, bk_locs):
|
||||
chest_names = [x.name for x in bk_locs]
|
||||
return lambda state: (state.has_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_key(small_key_name, player, bk_keys))
|
||||
return lambda state: (state.has_sm_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_sm_key(small_key_name, player, bk_keys))
|
||||
|
||||
|
||||
def create_key_rule_bk_exception_or_allow(small_key_name, big_key_name, player, keys, location, bk_keys, bk_locs):
|
||||
loc = location.name
|
||||
chest_names = [x.name for x in bk_locs]
|
||||
return lambda state: (state.has_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_key(small_key_name, player, keys-1)) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_key(small_key_name, player, bk_keys))
|
||||
return lambda state: (state.has_sm_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys-1)) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_sm_key(small_key_name, player, bk_keys))
|
||||
|
||||
|
||||
def create_advanced_key_rule(key_logic, player, rule):
|
||||
|
||||
@@ -31,7 +31,7 @@ def main(args=None):
|
||||
['Std ', ' --mode standard'],
|
||||
['Inv ', ' --mode inverted']]:
|
||||
|
||||
basecommand = f"py DungeonRandomizer.py --door_shuffle {args.dr} --intensity {args.tense} --suppress_rom --suppress_spoiler"
|
||||
basecommand = f"python3.8 DungeonRandomizer.py --door_shuffle {args.dr} --intensity {args.tense} --suppress_rom --suppress_spoiler"
|
||||
|
||||
def gen_seed():
|
||||
taskcommand = basecommand + " " + command + mode[1]
|
||||
@@ -46,7 +46,7 @@ def main(args=None):
|
||||
|
||||
test("Vanilla ", "--shuffle vanilla")
|
||||
test("Retro ", "--retro --shuffle vanilla")
|
||||
test("Keysanity ", "--shuffle vanilla --keysanity")
|
||||
test("Keysanity ", "--shuffle vanilla --keydropshuffle --keysanity")
|
||||
test("Simple ", "--shuffle simple")
|
||||
test("Full ", "--shuffle full")
|
||||
test("Crossed ", "--shuffle crossed")
|
||||
|
||||
@@ -37,3 +37,7 @@ incsrc hudadditions.asm
|
||||
warnpc $279700
|
||||
|
||||
incsrc doortables.asm
|
||||
warnpc $288000
|
||||
|
||||
; deals with own hooks
|
||||
incsrc keydropshuffle.asm
|
||||
|
||||
@@ -32,8 +32,10 @@ HudAdditions:
|
||||
lda $7ef368 : and.l $0098c0, x : beq .restore
|
||||
txa : lsr : tax
|
||||
|
||||
lda $7ef4e0, x : jsr ConvertToDisplay : sta $7ec7a2
|
||||
lda #$2830 : sta $7ec7a4
|
||||
lda.l GenericKeys : bne +
|
||||
lda $7ef4e0, x : jsr ConvertToDisplay : sta $7ec7a2
|
||||
lda #$2830 : sta $7ec7a4
|
||||
+
|
||||
lda.w ChestKeys, x : jsr ConvertToDisplay : sta $7ec7a6
|
||||
; todo 4b0 no longer in use
|
||||
|
||||
@@ -63,32 +65,58 @@ DrHudDungeonItemsAdditions:
|
||||
phx : phy : php
|
||||
rep #$30
|
||||
|
||||
lda !HUD_FLAG : and.w #$0020 : beq + : bra ++ : +
|
||||
lda HUDDungeonItems : and.w #$0003 : bne + : bra ++ : +
|
||||
lda.w #$24f5 : sta $1606 : sta $1610 : sta $161a : sta $1624
|
||||
sta $1644 : sta $164a : sta $1652 : sta $1662 : sta $1684 : sta $16c4
|
||||
ldx #$0000
|
||||
- sta $1704, x : sta $170e, x : sta $1718, x
|
||||
inx #2 : cpx #$0008 : !blt -
|
||||
|
||||
lda !HUD_FLAG : and.w #$0020 : beq + : brl ++ : +
|
||||
lda HUDDungeonItems : and.w #$0007 : bne + : brl ++ : +
|
||||
; bk symbols
|
||||
lda.w #$2811 : sta $1606 : sta $1610 : sta $161a : sta $1624
|
||||
; sm symbols
|
||||
lda.w #$2810 : sta $160a : sta $1614 : sta $161e : sta $16e4
|
||||
; blank out stuff
|
||||
lda.w #$24f5 : sta $1606 : sta $1610 : sta $161a : sta $1624 : sta $1644
|
||||
sta $164a : sta $1652 : sta $1662
|
||||
ldy #$0000
|
||||
- sta $1706, y : iny #2 : cpy #$001c : bcc -
|
||||
lda.w #$2810 : sta $1684 ; small keys icon
|
||||
lda.w #$2811 : sta $16c4 ; big key icon
|
||||
lda.w #$2810 : sta $1704 ; small keys icon
|
||||
lda.w #$24f5 : sta $1724
|
||||
|
||||
ldx #$0002
|
||||
- lda $7ef368 : and.l $0098c0, x : beq + ; must have map
|
||||
lda.l HudOffsets, x : tay
|
||||
jsr BkStatus : sta $16C6, y ; big key status
|
||||
phx
|
||||
txa : lsr : tax
|
||||
lda.l ChestKeys, x : jsr ConvertToDisplay2 : sta $1706, y ; small key totals
|
||||
plx
|
||||
+ inx #2 : cpx #$001b : bcc -
|
||||
- lda #$0000 : !addl RowOffsets,x : !addl ColumnOffsets, x : tay
|
||||
lda.l DungeonReminderTable, x : sta $1644, y : iny #2
|
||||
lda.w #$24f5 : sta $1644, y
|
||||
lda $7ef368 : and.l $0098c0, x : beq + ; must have map
|
||||
jsr BkStatus : sta $1644, y : bra .smallKey ; big key status
|
||||
+ lda $7ef366 : and.l $0098c0, x : beq .smallKey
|
||||
lda.w #$2826 : sta $1644, y
|
||||
.smallKey
|
||||
+ iny #2
|
||||
cpx #$001a : bne +
|
||||
tya : !add #$003c : tay
|
||||
+ stx $00
|
||||
txa : lsr : tax
|
||||
lda.w #$24f5 : sta $1644, y
|
||||
lda.l $7ef37c, x : beq +
|
||||
jsr ConvertToDisplay2 : sta $1644, y
|
||||
+ iny #2 : lda.w #$24f5 : sta $1644, y
|
||||
phx : ldx $00
|
||||
lda $7ef368 : and.l $0098c0, x : beq + ; must have map
|
||||
plx : lda.l ChestKeys, x : jsr ConvertToDisplay2 : sta $1644, y ; small key totals
|
||||
bra .skipStack
|
||||
+ plx
|
||||
.skipStack iny #2
|
||||
cpx #$000d : beq +
|
||||
lda.w #$24f5 : sta $1644, y
|
||||
+
|
||||
ldx $00
|
||||
+ inx #2 : cpx #$001b : bcs ++ : brl -
|
||||
++
|
||||
lda !HUD_FLAG : and.w #$0020 : bne + : brl ++ : +
|
||||
lda HUDDungeonItems : and.w #$000f : bne + : brl ++ : +
|
||||
lda HUDDungeonItems : and.w #$000c : bne + : brl ++ : +
|
||||
; map symbols (do I want these) ; note compass symbol is 2c20
|
||||
lda.w #$2821 : sta $1606 : sta $1610 : sta $161a : sta $1624
|
||||
; blank out a couple thing from old hud
|
||||
lda.w #$24f5 : sta $16e4 : sta $1724
|
||||
sta $160a : sta $1614 : sta $161e ; blank out sm key indicators
|
||||
ldx #$0002
|
||||
- lda #$0000 ; start of hud area
|
||||
!addl RowOffsets, x : !addl ColumnOffsets, x : tay
|
||||
|
||||
167
asm/keydropshuffle.asm
Normal file
167
asm/keydropshuffle.asm
Normal file
@@ -0,0 +1,167 @@
|
||||
org $06926e ; <- 3126e - sprite_prep.asm : 2664 (LDA $0B9B : STA $0CBA, X)
|
||||
jsl SpriteKeyPrep : nop #2
|
||||
|
||||
org $06d049 ; <- 35049 sprite_absorbable : 31-32 (JSL Sprite_DrawRippleIfInWater : JSR Sprite_DrawAbsorbable)
|
||||
jsl SpriteKeyDrawGFX : bra + : nop : +
|
||||
|
||||
org $06d180
|
||||
jsl BigKeyGet : bcs $07
|
||||
|
||||
org $06d18d ; <- 3518D - sprite_absorbable.asm : 274 (LDA $7EF36F : INC A : STA $7EF36F)
|
||||
jsl KeyGet
|
||||
|
||||
org $06f9f3 ; bank06.asm : 6732 (JSL Sprite_LoadProperties)
|
||||
jsl LoadProperties_PreserveItemMaybe
|
||||
|
||||
|
||||
|
||||
|
||||
org $06d23a
|
||||
Sprite_DrawAbsorbable:
|
||||
org $1eff81
|
||||
Sprite_DrawRippleIfInWater:
|
||||
org $0db818
|
||||
Sprite_LoadProperties:
|
||||
|
||||
org $288000 ;140000
|
||||
ShuffleKeyDrops:
|
||||
db 0
|
||||
ShuffleKeyDropsReserved:
|
||||
db 0
|
||||
|
||||
LootTable: ;PC: 140002
|
||||
db $0e, $00, $24 ;; ice jelly key
|
||||
db $13, $00, $24 ;; pokey 2
|
||||
db $16, $00, $24 ;; swamp waterway pot
|
||||
db $21, $00, $24 ;; key rat
|
||||
db $35, $00, $24 ;; swamp trench 2 pot
|
||||
db $36, $00, $24 ;; hookshot pot
|
||||
db $37, $00, $24 ;; trench 1 pot
|
||||
db $38, $00, $24 ;; pot row pot
|
||||
db $39, $00, $24 ;; skull gibdo
|
||||
db $3d, $00, $24 ;; gt minihelma
|
||||
db $3e, $00, $24 ;; ice conveyor
|
||||
db $3f, $00, $24 ;; ice hammer block ??? is this a dungeon secret?
|
||||
db $43, $00, $24 ;; tiles 2 pot
|
||||
db $53, $00, $24 ;; beamos hall pot
|
||||
db $56, $00, $24 ;; skull west lobby pot
|
||||
db $63, $00, $24 ;; desert tiles 1 pot
|
||||
db $71, $00, $24 ;; boomerang guard
|
||||
db $72, $00, $24 ;; hc map guard
|
||||
db $7b, $00, $24 ;; gt star pits pot
|
||||
db $80, $00, $32 ;; a big key (for the current dungeon)
|
||||
db $8b, $00, $24 ;; gt conv cross block
|
||||
db $9b, $00, $24 ;; gt dlb switch pot
|
||||
db $9f, $00, $24 ;; ice many pots
|
||||
db $99, $00, $24 ;; eastern eyegore
|
||||
db $a1, $00, $24 ;; mire fishbone pot
|
||||
db $ab, $00, $24 ;; tt spike switch pot
|
||||
db $b0, $00, $24 ;; tower circle of pots usain
|
||||
db $b3, $00, $24 ;; mire spikes pot
|
||||
db $b6, $00, $24 ;; pokey 1
|
||||
db $ba, $00, $24 ;; eastern dark pot
|
||||
db $bc, $00, $24 ;; tt hallway pot
|
||||
db $c0, $00, $24 ;; tower dark archer
|
||||
db $c1, $00, $24 ;; mire glitchy jelly
|
||||
db $ff, $00, $ff
|
||||
;140068
|
||||
|
||||
KeyTable:
|
||||
db $a0, $a0, $a2, $a3, $a4, $a5, $a6, $a7, $a8, $a9, $aa, $ab, $ac, $ad
|
||||
|
||||
SpriteKeyPrep:
|
||||
{
|
||||
lda $0b9b : sta $0cba, x ; what we wrote over
|
||||
pha
|
||||
lda.l ShuffleKeyDrops : beq +
|
||||
phx
|
||||
ldx #$fd
|
||||
- inx #3 : lda.l LootTable, x : cmp #$ff : beq ++ : cmp $a0 : bne -
|
||||
inx : lda.l LootTable, x : sta !MULTIWORLD_SPRITEITEM_PLAYER_ID
|
||||
inx : lda.l LootTable, x
|
||||
plx : sta $0e80, x
|
||||
cmp #$24 : beq +
|
||||
jsl PrepDynamicTile : bra +
|
||||
++ plx : lda #$24 : sta $0e80, x
|
||||
+ pla
|
||||
rtl
|
||||
}
|
||||
|
||||
SpriteKeyDrawGFX:
|
||||
{
|
||||
jsl Sprite_DrawRippleIfInWater
|
||||
pha
|
||||
lda.l ShuffleKeyDrops : bne +
|
||||
- pla
|
||||
phk : pea.w .jslrtsreturn-1
|
||||
pea.w $068014 ; an rtl address - 1 in Bank06
|
||||
jml Sprite_DrawAbsorbable
|
||||
.jslrtsreturn
|
||||
rtl
|
||||
+ lda $0e80, x
|
||||
cmp #$24 : beq -
|
||||
jsl DrawDynamicTile ; see DrawHeartPieceGFX if problems
|
||||
cmp #$03 : bne +
|
||||
pha : lda $0e60, x : ora.b #$20 : sta $0E60, x : pla
|
||||
+
|
||||
jsl.l Sprite_DrawShadowLong
|
||||
|
||||
pla : rtl
|
||||
}
|
||||
|
||||
KeyGet:
|
||||
{
|
||||
lda $7ef36f ; what we wrote over
|
||||
pha
|
||||
lda.l ShuffleKeyDrops : bne +
|
||||
pla : rtl
|
||||
+
|
||||
ldy $0e80, x
|
||||
phy
|
||||
jsr KeyGetPlayer : sta !MULTIWORLD_ITEM_PLAYER_ID
|
||||
jsl.l $0791b3 ; Player_HaltDashAttackLong
|
||||
jsl.l Link_ReceiveItem
|
||||
pla : sta $00
|
||||
lda !MULTIWORLD_ITEM_PLAYER_ID : bne .end
|
||||
phx
|
||||
lda $040c : lsr : tax
|
||||
lda $00 : cmp KeyTable, x : bne +
|
||||
- plx : pla : rtl
|
||||
+ cmp #$af : beq - ; universal key
|
||||
cmp #$24 : beq - ; small key for this dungeon
|
||||
plx
|
||||
.end
|
||||
pla : dec : rtl
|
||||
}
|
||||
|
||||
|
||||
BigKeyGet:
|
||||
{
|
||||
lda.l ShuffleKeyDrops : bne +
|
||||
- stz $02e9 : ldy.b #$32 : phx ; what we wrote over
|
||||
clc : rtl
|
||||
+
|
||||
ldy $0e80, x
|
||||
cpy #$32 : beq -
|
||||
+ sec : rtl
|
||||
}
|
||||
|
||||
KeyGetPlayer:
|
||||
{
|
||||
phx
|
||||
ldx #$fd
|
||||
- inx #3 : lda.l LootTable, x : cmp #$ff : beq ++ : cmp $a0 : bne -
|
||||
++ inx : lda.l LootTable, x
|
||||
plx
|
||||
rts
|
||||
}
|
||||
|
||||
LoadProperties_PreserveItemMaybe:
|
||||
{
|
||||
lda.l ShuffleKeyDrops : bne +
|
||||
jsl Sprite_LoadProperties : rtl
|
||||
+ lda $0e80, x : pha
|
||||
jsl Sprite_LoadProperties
|
||||
pla : sta $0e80, x
|
||||
rtl
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -59,6 +59,10 @@
|
||||
"expert"
|
||||
]
|
||||
},
|
||||
"keydropshuffle" : {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"timer": {
|
||||
"choices": [
|
||||
"none",
|
||||
|
||||
Reference in New Issue
Block a user