Key Hud redesign continued

Keydropshuffle prototype
This commit is contained in:
aerinon
2020-10-23 12:59:18 -06:00
parent 7aca24b10f
commit d89801c72c
18 changed files with 429 additions and 129 deletions

View File

@@ -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
View File

@@ -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,

View File

@@ -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'),

View File

@@ -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),

View File

@@ -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:

View File

@@ -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 = [

View File

@@ -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)',

View File

@@ -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: ")

View File

@@ -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':

View File

@@ -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
View File

@@ -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

View File

@@ -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):

View File

@@ -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")

View File

@@ -37,3 +37,7 @@ incsrc hudadditions.asm
warnpc $279700
incsrc doortables.asm
warnpc $288000
; deals with own hooks
incsrc keydropshuffle.asm

View File

@@ -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
View 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

View File

@@ -59,6 +59,10 @@
"expert"
]
},
"keydropshuffle" : {
"action": "store_true",
"type": "bool"
},
"timer": {
"choices": [
"none",