Generation improvements
Hera Lobby fix Added gauntlet 3 to cut carpet
This commit is contained in:
@@ -1419,10 +1419,9 @@ class Sector(object):
|
||||
self.branch_factor -= cnt_dead - 1
|
||||
for region in self.regions:
|
||||
for ent in region.entrances:
|
||||
if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] or ent.parent_region.name == 'Sewer Drop':
|
||||
# same sector as another entrance
|
||||
if region.name not in ['Skull Pot Circle', 'Skull Back Drop', 'Desert East Lobby', 'Desert West Lobby']:
|
||||
if (ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] and ent.parent_region.name != 'Menu') or ent.parent_region.name == 'Sewer Drop':
|
||||
self.branch_factor += 1
|
||||
break # you only ever get one allowance for an entrance region, multiple entrances don't help
|
||||
return self.branch_factor
|
||||
|
||||
def branches(self):
|
||||
|
||||
@@ -51,10 +51,7 @@ def link_doors(world, player):
|
||||
for portal in world.dungeon_portals[player]:
|
||||
connect_portal(portal, world, player)
|
||||
world.get_portal('Desert East', player).destination = True
|
||||
world.get_portal('Desert Back', player).deadEnd = True
|
||||
world.get_portal('Skull 1', player).deadEnd = True
|
||||
world.get_portal('Skull 2 West', player).destination = True
|
||||
world.get_portal('Skull 3', player).deadEnd = True
|
||||
world.get_portal('Turtle Rock Lazy Eyes', player).destination = True
|
||||
world.get_portal('Turtle Rock Eye Bridge', player).destination = True
|
||||
|
||||
@@ -343,10 +340,12 @@ def choose_portals(world, player):
|
||||
for portal in portal_list:
|
||||
placeholder = world.get_region(portal + ' Placeholder', player)
|
||||
portal_region = placeholder.exits[0].connected_region
|
||||
name = portal_region.name
|
||||
if portal_region.type == RegionType.LightWorld:
|
||||
world.get_portal(portal, player).light_world = True
|
||||
if portal_region.name in world.inaccessible_regions[player]:
|
||||
region_map[portal_region.name].append(portal)
|
||||
if name in world.inaccessible_regions[player]:
|
||||
name_key = 'Desert Ledge' if name == 'Desert Palace Entrance (North) Spot' else name
|
||||
region_map[name_key].append(portal)
|
||||
inaccessible_portals.append(portal)
|
||||
else:
|
||||
reachable_portals.append(portal)
|
||||
@@ -403,6 +402,9 @@ def choose_portals(world, player):
|
||||
if not sanctuary_door.entranceFlag:
|
||||
world.get_room(0x12, player).delete(3)
|
||||
world.get_room(0x12, player).change(2, DoorKind.NormalLow)
|
||||
hera_door = world.get_door('Hera Lobby S', player)
|
||||
if not hera_door.entranceFlag:
|
||||
world.get_room(0x77, player).change(0, DoorKind.NormalLow2)
|
||||
|
||||
if not world.swamp_patch_required[player]:
|
||||
swamp_region = world.get_entrance('Swamp Palace', player).connected_region
|
||||
@@ -425,10 +427,12 @@ def connect_portal(portal, world, player):
|
||||
else:
|
||||
edit_entrance = world.get_entrance(ent, player)
|
||||
entrance_region = portal_entrance.parent_region
|
||||
old_region = edit_entrance.connected_region
|
||||
edit_entrance.connected_region = entrance_region
|
||||
entrance_region.exits.remove(portal_entrance)
|
||||
entrance_region.exits.append(target_exit)
|
||||
entrance_region.entrances.append(edit_entrance)
|
||||
old_region.entrances.remove(edit_entrance)
|
||||
world.regions.remove(placeholder)
|
||||
|
||||
|
||||
@@ -485,7 +489,7 @@ def assign_portal(candidates, possible_portals, world, player):
|
||||
old_door = portal.door
|
||||
if old_door:
|
||||
old_door.entranceFlag = False
|
||||
if old_door.name not in ['Hyrule Castle Lobby S', 'Sanctuary S']:
|
||||
if old_door.name not in ['Hyrule Castle Lobby S', 'Sanctuary S', 'Hera Lobby S']:
|
||||
old_door_kind = DoorKind.NormalLow if old_door.layer or old_door.pseudo_bg else DoorKind.Normal
|
||||
world.get_room(old_door.roomIndex, player).change(old_door.doorListPos, old_door_kind)
|
||||
portal.change_door(candidate)
|
||||
@@ -510,6 +514,7 @@ def clean_up_portal_assignment(portal_assignment, dungeon, portal, master_door_l
|
||||
def create_dungeon_entrances(world, player):
|
||||
entrance_map = defaultdict(list)
|
||||
split_map: DefaultDict[str, DefaultDict[str, List]] = defaultdict(lambda: defaultdict(list))
|
||||
originating: DefaultDict[str, DefaultDict[str, Dict]] = defaultdict(lambda: defaultdict(dict))
|
||||
for key, portal_list in dungeon_portals.items():
|
||||
if world.mode[player] == 'standard' and key in standard_starts.keys():
|
||||
portal = world.get_portal('Hyrule Castle South', player)
|
||||
@@ -523,25 +528,36 @@ def create_dungeon_entrances(world, player):
|
||||
the_rest = []
|
||||
for portal_name in portal_list:
|
||||
portal = world.get_portal(portal_name, player)
|
||||
r_name = portal.door.entrance.parent_region.name
|
||||
entrance_map[key].append(r_name)
|
||||
entrance_map[key].append(portal.door.entrance.parent_region.name)
|
||||
if portal.deadEnd:
|
||||
dead_ends.append(r_name)
|
||||
dead_ends.append(portal)
|
||||
elif portal.destination:
|
||||
destinations.append(r_name)
|
||||
destinations.append(portal)
|
||||
else:
|
||||
the_rest.append(r_name)
|
||||
the_rest.append(portal)
|
||||
choices = list(split_portals[key])
|
||||
for r_name in dead_ends:
|
||||
for portal in dead_ends:
|
||||
choice = random.choice(choices)
|
||||
choices.remove(choice)
|
||||
r_name = portal.door.entrance.parent_region.name
|
||||
split_map[key][choice].append(r_name)
|
||||
for r_name in the_rest:
|
||||
for portal in the_rest:
|
||||
if len(choices) == 0:
|
||||
choices.append('Extra')
|
||||
choice = random.choice(choices)
|
||||
p_entrance = portal.door.entrance
|
||||
r_name = p_entrance.parent_region.name
|
||||
split_map[key][choice].append(r_name)
|
||||
originating[key][choice][p_entrance.connected_region.name] = None
|
||||
dest_choices = [x for x in choices if len(split_map[key][x]) > 0]
|
||||
for r_name in destinations:
|
||||
choice = random.choice(dest_choices)
|
||||
for portal in destinations:
|
||||
restricted = portal.door.entrance.connected_region.name in world.inaccessible_regions[player]
|
||||
if restricted:
|
||||
filtered_choices = [x for x in choices if any(y not in world.inaccessible_regions[player] for y in originating[key][x].keys())]
|
||||
else:
|
||||
filtered_choices = dest_choices
|
||||
choice = random.choice(filtered_choices)
|
||||
r_name = portal.door.entrance.parent_region.name
|
||||
split_map[key][choice].append(r_name)
|
||||
else:
|
||||
for portal_name in portal_list:
|
||||
@@ -605,7 +621,10 @@ def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map,
|
||||
split_builders = split_dungeon_builder(builder, split_list, builder_info)
|
||||
dungeon_builders.update(split_builders)
|
||||
for sub_name, split_entrances in split_list.items():
|
||||
sub_builder = dungeon_builders[name+' '+sub_name]
|
||||
key = name+' '+sub_name
|
||||
if key not in dungeon_builders:
|
||||
continue
|
||||
sub_builder = dungeon_builders[key]
|
||||
sub_builder.split_flag = True
|
||||
entrance_list = list(split_entrances)
|
||||
for ent in entrances_map[name]:
|
||||
@@ -700,8 +719,9 @@ def determine_entrance_list_2(world, player):
|
||||
if parent.name not in world.inaccessible_regions[player]:
|
||||
entrance_map[key].append(region_name)
|
||||
else:
|
||||
if ent.parent_region not in potential_entrances.keys():
|
||||
if parent not in potential_entrances.keys():
|
||||
potential_entrances[parent] = []
|
||||
if region_name not in potential_entrances[parent]:
|
||||
potential_entrances[parent].append(region_name)
|
||||
connections[region_name] = parent
|
||||
return entrance_map, potential_entrances, connections
|
||||
@@ -1047,7 +1067,9 @@ def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
|
||||
|
||||
|
||||
def valid_region_to_explore(region, world, player):
|
||||
return region and (region.type == RegionType.Dungeon or region.name in world.inaccessible_regions[player])
|
||||
return region and (region.type == RegionType.Dungeon
|
||||
or region.name in world.inaccessible_regions[player]
|
||||
or (region.name == 'Hyrule Castle Ledge' and world.mode[player] == 'standard'))
|
||||
|
||||
|
||||
def shuffle_key_doors(builder, world, player):
|
||||
@@ -1503,6 +1525,9 @@ def valid_inaccessible_region(r):
|
||||
|
||||
|
||||
def add_inaccessible_doors(world, player):
|
||||
if world.mode[player] == 'standard':
|
||||
create_door(world, player, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Ledge')
|
||||
create_door(world, player, 'Hyrule Castle Entrance (West)', 'Hyrule Castle Ledge')
|
||||
# todo: ignore standard mode hyrule castle ledge?
|
||||
for inaccessible_region in world.inaccessible_regions[player]:
|
||||
region = world.get_region(inaccessible_region, player)
|
||||
|
||||
@@ -30,6 +30,12 @@ class GraphPiece:
|
||||
# Dungeons shouldn't be generated until all entrances are appropriately accessible
|
||||
def pre_validate(builder, entrance_region_names, split_dungeon, world, player):
|
||||
entrance_regions = convert_regions(entrance_region_names, world, player)
|
||||
excluded = {}
|
||||
for region in entrance_regions:
|
||||
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region == region), None)
|
||||
if portal and portal.destination:
|
||||
excluded[region] = None
|
||||
entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
|
||||
proposed_map = {}
|
||||
doors_to_connect = {}
|
||||
all_regions = set()
|
||||
@@ -90,6 +96,12 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
|
||||
logger = logging.getLogger('')
|
||||
name = builder.name
|
||||
entrance_regions = convert_regions(entrance_region_names, world, player)
|
||||
excluded = {}
|
||||
for region in entrance_regions:
|
||||
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region == region), None)
|
||||
if portal and portal.destination:
|
||||
excluded[region] = None
|
||||
entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
|
||||
doors_to_connect = {}
|
||||
all_regions = set()
|
||||
bk_needed = False
|
||||
@@ -1076,14 +1088,18 @@ def special_big_key_found(state, world, player):
|
||||
def valid_region_to_explore_in_regions(region, all_regions, world, player):
|
||||
if region is None:
|
||||
return False
|
||||
return (region.type == RegionType.Dungeon and region in all_regions) or region.name in world.inaccessible_regions[player]
|
||||
return (region.type == RegionType.Dungeon and region in all_regions)\
|
||||
or region.name in world.inaccessible_regions[player]\
|
||||
or (region.name == 'Hyrule Castle Ledge' and world.mode[player] == 'standard')
|
||||
|
||||
|
||||
# cross-utility methods
|
||||
def valid_region_to_explore(region, name, world, player):
|
||||
if region is None:
|
||||
return False
|
||||
return (region.type == RegionType.Dungeon and region.dungeon.name in name) or region.name in world.inaccessible_regions[player]
|
||||
return (region.type == RegionType.Dungeon and region.dungeon.name in name)\
|
||||
or region.name in world.inaccessible_regions[player]\
|
||||
or (region.name == 'Hyrule Castle Ledge' and world.mode[player] == 'standard')
|
||||
|
||||
|
||||
def get_doors(world, region, player):
|
||||
@@ -1159,9 +1175,7 @@ class DungeonBuilder(object):
|
||||
self.key_door_proposal = None
|
||||
|
||||
self.allowance = None
|
||||
if name in dungeon_dead_end_allowance.keys():
|
||||
self.allowance = dungeon_dead_end_allowance[name]
|
||||
elif 'Stonewall' in name:
|
||||
if 'Stonewall' in name:
|
||||
self.allowance = 1
|
||||
elif 'Prewall' in name:
|
||||
orig_name = name[:-8]
|
||||
@@ -2597,6 +2611,8 @@ def categorize_groupings(sectors):
|
||||
|
||||
|
||||
def valid_assignment(builder, sector_list, builder_info):
|
||||
if not valid_entrance(builder, sector_list, builder_info):
|
||||
return False
|
||||
if not valid_c_switch(builder, sector_list):
|
||||
return False
|
||||
if not valid_polarized_assignment(builder, sector_list):
|
||||
@@ -2605,6 +2621,34 @@ def valid_assignment(builder, sector_list, builder_info):
|
||||
return resolved
|
||||
|
||||
|
||||
def valid_entrance(builder, sector_list, builder_info):
|
||||
is_dead_end = False
|
||||
if len(builder.sectors) == 0:
|
||||
is_dead_end = True
|
||||
else:
|
||||
entrances, splits, world, player = builder_info
|
||||
if builder.name not in entrances.keys():
|
||||
name_parts = builder.name.rsplit(' ', 1)
|
||||
entrance_list = splits[name_parts[0]][name_parts[1]]
|
||||
entrances = []
|
||||
for sector in builder.sectors:
|
||||
if sector.is_entrance_sector():
|
||||
sector.region_set()
|
||||
entrances.append(sector)
|
||||
all_dead = True
|
||||
for sector in entrances:
|
||||
for region in entrance_list:
|
||||
if region in sector.region_set():
|
||||
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == region))
|
||||
if not portal.deadEnd:
|
||||
all_dead = False
|
||||
break
|
||||
if not all_dead:
|
||||
break
|
||||
is_dead_end = all_dead
|
||||
return len(sector_list) == 0 if is_dead_end else True
|
||||
|
||||
|
||||
def valid_c_switch(builder, sector_list):
|
||||
if builder.c_switch_present:
|
||||
return True
|
||||
@@ -2699,17 +2743,38 @@ def split_dungeon_builder(builder, split_list, builder_info):
|
||||
builder.split_dungeon_map[name].valid_proposal = proposal
|
||||
return builder.split_dungeon_map # we made this earlier in gen, just use it
|
||||
|
||||
attempts, comb_w_replace = 0, None
|
||||
attempts, comb_w_replace, merge_attempt = 0, None, False
|
||||
while attempts < 5: # does not solve coin flips 3% of the time
|
||||
try:
|
||||
candidate_sectors = dict.fromkeys(builder.sectors)
|
||||
global_pole = GlobalPolarity(candidate_sectors)
|
||||
|
||||
dungeon_map = {}
|
||||
dungeon_map, sub_builder, merge_keys = {}, None, []
|
||||
if merge_attempt:
|
||||
candidates = []
|
||||
for name, split_entrances in split_list.items():
|
||||
if len(split_entrances) > 1:
|
||||
candidates.append(name)
|
||||
continue
|
||||
elif len(split_entrances) <= 0:
|
||||
continue
|
||||
x, y, world, player = builder_info
|
||||
r_name = split_entrances[0]
|
||||
p = next(x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == r_name)
|
||||
if not p.deadEnd:
|
||||
candidates.append(name)
|
||||
merge_keys = random.sample(candidates, 2)
|
||||
for name, split_entrances in split_list.items():
|
||||
key = builder.name + ' ' + name
|
||||
if merge_keys and name in merge_keys:
|
||||
other_key = builder.name + ' ' + [x for x in merge_keys if x != name][0]
|
||||
if other_key in dungeon_map:
|
||||
key = other_key
|
||||
sub_builder = dungeon_map[other_key]
|
||||
sub_builder.all_entrances.extend(split_entrances)
|
||||
if key not in dungeon_map:
|
||||
dungeon_map[key] = sub_builder = DungeonBuilder(key)
|
||||
sub_builder.all_entrances = split_entrances
|
||||
sub_builder.all_entrances = list(split_entrances)
|
||||
for r_name in split_entrances:
|
||||
assign_sector(find_sector(r_name, candidate_sectors), sub_builder, candidate_sectors, global_pole)
|
||||
comb_w_replace = len(dungeon_map) ** len(candidate_sectors)
|
||||
@@ -2719,6 +2784,9 @@ def split_dungeon_builder(builder, split_list, builder_info):
|
||||
attempts += 5 # all the combinations were tried already, no use repeating
|
||||
else:
|
||||
attempts += 1
|
||||
if attempts >= 5 and not merge_attempt:
|
||||
merge_attempt, attempts = True, 0
|
||||
|
||||
raise GenerationException('Unable to resolve in 5 attempts')
|
||||
|
||||
|
||||
@@ -2735,8 +2803,8 @@ def balance_split(candidate_sectors, dungeon_map, global_pole, builder_info):
|
||||
for i, choice in enumerate(choices):
|
||||
chosen_sectors[choice].append(main_sector_list[i])
|
||||
all_valid = True
|
||||
for name, sector_list in chosen_sectors.items():
|
||||
if not valid_assignment(dungeon_map[name], sector_list, builder_info):
|
||||
for name, builder in dungeon_map.items():
|
||||
if not valid_assignment(builder, chosen_sectors[name], builder_info):
|
||||
all_valid = False
|
||||
break
|
||||
if all_valid:
|
||||
@@ -3287,7 +3355,7 @@ def find_priority_equation(equations, access_id, current_access):
|
||||
if len(filtered_candidates) == 1:
|
||||
return filtered_candidates[0]
|
||||
|
||||
neutral_candidates = [x for x in filtered_candidates if x[0].neutral_profit() or x[0].neutral()]
|
||||
neutral_candidates = [x for x in filtered_candidates if (x[0].neutral_profit() or x[0].neutral()) and x[0].profit(current_access) == local_profit_map[x[2]]]
|
||||
if len(neutral_candidates) == 0:
|
||||
neutral_candidates = filtered_candidates
|
||||
if len(neutral_candidates) == 1:
|
||||
|
||||
2
Rom.py
2
Rom.py
@@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses, exit_ids
|
||||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = '98e8426901b441858631ae444c12d4fc'
|
||||
RANDOMIZERBASEHASH = '7d4881c390855dbe2f9c308bed2e61ef'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
|
||||
9
Rules.py
9
Rules.py
@@ -904,7 +904,9 @@ def find_rules_for_zelda_delivery(world, player):
|
||||
region, path_rules, path = queue.popleft()
|
||||
for ext in region.exits:
|
||||
connect = ext.connected_region
|
||||
if connect and connect.type == RegionType.Dungeon and connect not in visited:
|
||||
valid_region = connect and connect not in visited and\
|
||||
(connect.type == RegionType.Dungeon or connect.name == 'Hyrule Castle Ledge')
|
||||
if valid_region:
|
||||
rule = ext.access_rule
|
||||
rule_list = list(path_rules)
|
||||
next_path = list(path)
|
||||
@@ -1562,6 +1564,7 @@ def add_key_logic_rules(world, player):
|
||||
for d_name, d_logic in key_logic.items():
|
||||
for door_name, keys in d_logic.door_rules.items():
|
||||
spot = world.get_entrance(door_name, player)
|
||||
if not world.retro[player] or world.mode[player] != 'standard' or not retro_in_hc(spot):
|
||||
add_rule(spot, create_advanced_key_rule(d_logic, player, keys))
|
||||
if keys.opposite:
|
||||
add_rule(spot, create_advanced_key_rule(d_logic, player, keys.opposite), 'or')
|
||||
@@ -1576,6 +1579,10 @@ def add_key_logic_rules(world, player):
|
||||
add_rule(world.get_location(chest.name, player), create_rule(d_logic.bk_name, player))
|
||||
|
||||
|
||||
def retro_in_hc(spot):
|
||||
return spot.parent_region.dungeon.name == 'Hyrule Castle' if spot.parent_region.dungeon else False
|
||||
|
||||
|
||||
def create_rule(item_name, player):
|
||||
return lambda state: state.has(item_name, player)
|
||||
|
||||
|
||||
@@ -31,11 +31,11 @@ def main(args=None):
|
||||
['Std ', ' --mode standard'],
|
||||
['Inv ', ' --mode inverted']]:
|
||||
|
||||
basecommand = f"py -{py_version} DungeonRandomizer.py --door_shuffle {args.dr} --intensity {args.tense} --suppress_rom --suppress_spoiler"
|
||||
basecommand = f"py DungeonRandomizer.py --door_shuffle {args.dr} --intensity {args.tense} --suppress_rom --suppress_spoiler"
|
||||
|
||||
def gen_seed():
|
||||
taskcommand = basecommand + " " + command + mode[1]
|
||||
return subprocess.run(taskcommand, capture_output=True, shell=False, text=True)
|
||||
return subprocess.run(taskcommand, capture_output=True, shell=True, text=True)
|
||||
|
||||
for x in range(1, max_attempts + 1):
|
||||
task = pool.submit(gen_seed)
|
||||
@@ -80,7 +80,7 @@ def main(args=None):
|
||||
success = f"{testname}{mode} Rate: {(len(alive) / len(dead_or_alive)) * 100:.2f}%"
|
||||
successes.append(success)
|
||||
print(success)
|
||||
result += f"{(len(alive)/len(dead_or_alive))*100:.2f}% "
|
||||
result += f"{(len(alive)/len(dead_or_alive))*100:.2f}%\t"
|
||||
return result.strip()
|
||||
|
||||
results = []
|
||||
|
||||
196
Utils.py
196
Utils.py
@@ -457,7 +457,201 @@ def print_graph(world):
|
||||
ET.dump(root)
|
||||
|
||||
|
||||
def extract_data_from_us_rom(rom):
|
||||
with open(rom, 'rb') as stream:
|
||||
rom_data = bytearray(stream.read())
|
||||
|
||||
rooms = [0x9a, 0x69, 0x78, 0x79, 0x7a, 0x88, 0x8a, 0xad]
|
||||
for room in rooms:
|
||||
b2idx = room*2
|
||||
b3idx = room*3
|
||||
headerptr = 0x110000 + b2idx # zscream specific
|
||||
headerloc = rom_data[headerptr] + rom_data[headerptr+1]*0x100 + 0x108000 # zscream specific
|
||||
header, objectdata, spritedata, secretdata = [], [], [], []
|
||||
for i in range(0, 14):
|
||||
header.append(rom_data[headerloc+i])
|
||||
objectptr = 0xF8000 + b3idx
|
||||
objectloc = rom_data[objectptr] + rom_data[objectptr+1]*0x100 + rom_data[objectptr+2]*0x10000
|
||||
objectloc -= 0x100000
|
||||
stop, idx = False, 0
|
||||
ffcnt = 0
|
||||
mode = 0
|
||||
while ffcnt < 2:
|
||||
b1 = rom_data[objectloc+idx]
|
||||
b2 = rom_data[objectloc+idx+1]
|
||||
b3 = rom_data[objectloc+idx+2]
|
||||
objectdata.append(b1)
|
||||
objectdata.append(b2)
|
||||
if b1 == 0xff and b2 == 0xff:
|
||||
ffcnt += 1
|
||||
mode = 0
|
||||
if b1 == 0xf0 and b2 == 0xff:
|
||||
mode = 1
|
||||
if not mode and ffcnt < 2:
|
||||
objectdata.append(b3)
|
||||
idx += 3
|
||||
else:
|
||||
idx += 2
|
||||
spriteptr = 0x4d62e + b2idx
|
||||
spriteloc = rom_data[spriteptr] + rom_data[spriteptr+1]*0x100 + 0x40000
|
||||
done, idx = False, 0
|
||||
while not done:
|
||||
b1 = rom_data[spriteloc+idx]
|
||||
spritedata.append(b1)
|
||||
if b1 == 0xff:
|
||||
done = True
|
||||
idx += 1
|
||||
secretptr = 0xdb69 + b2idx
|
||||
secretloc = rom_data[secretptr] + rom_data[secretptr+1]*0x100
|
||||
done, idx = False, 0
|
||||
while not done:
|
||||
b1 = rom_data[secretloc+idx]
|
||||
b2 = rom_data[secretloc+idx+1]
|
||||
b3 = rom_data[secretloc+idx+2]
|
||||
secretdata.append(b1)
|
||||
secretdata.append(b2)
|
||||
if b1 == 0xff and b2 == 0xff:
|
||||
done = True
|
||||
else:
|
||||
secretdata.append(b3)
|
||||
idx += 3
|
||||
|
||||
print(f'Room {room:02x}')
|
||||
print(f'db {",".join([f"${x:02x}" for x in header])}')
|
||||
print(f'Obj Length: {len(objectdata)}')
|
||||
print_data_block(objectdata)
|
||||
print('Sprites')
|
||||
print_data_block(spritedata)
|
||||
print('Secrets')
|
||||
print_data_block(secretdata)
|
||||
|
||||
blockdata, torchdata = [], []
|
||||
blockloc = 0x271de
|
||||
for i in range(0, 128):
|
||||
idx = i*4
|
||||
b1 = rom_data[blockloc+idx]
|
||||
b2 = rom_data[blockloc+idx+1]
|
||||
room_idx = b1 + b2*0x100
|
||||
if room_idx in rooms:
|
||||
blockdata.append(b1)
|
||||
blockdata.append(b2)
|
||||
blockdata.append(rom_data[blockloc+idx+2])
|
||||
blockdata.append(rom_data[blockloc+idx+3])
|
||||
torchloc = 0x2736A
|
||||
nomatch = False
|
||||
append = False
|
||||
for i in range(0, 192):
|
||||
idx = i*2
|
||||
b1 = rom_data[torchloc+idx]
|
||||
b2 = rom_data[torchloc+idx+1]
|
||||
if nomatch:
|
||||
if b1 == 0xff and b2 == 0xff:
|
||||
nomatch = False
|
||||
elif not append:
|
||||
room_idx = b1 + b2*0x100
|
||||
if room_idx in rooms:
|
||||
append = True
|
||||
else:
|
||||
nomatch = True
|
||||
if append:
|
||||
torchdata.append(b1)
|
||||
torchdata.append(b2)
|
||||
if b1 == 0xff and b2 == 0xff:
|
||||
append = False
|
||||
print('Blocks')
|
||||
print_data_block(blockdata)
|
||||
print('Torches')
|
||||
print_data_block(torchdata)
|
||||
print()
|
||||
|
||||
|
||||
def print_data_block(block):
|
||||
for i in range(0, len(block)//16 + 1):
|
||||
slice = block[i*16:i*16+16]
|
||||
print(f'db {",".join([f"${x:02x}" for x in slice])}')
|
||||
|
||||
|
||||
def extract_data_from_jp_rom(rom):
|
||||
with open(rom, 'rb') as stream:
|
||||
rom_data = bytearray(stream.read())
|
||||
|
||||
rooms = [0x7b, 0x7c, 0x7d, 0x8b, 0x8c, 0x8d, 0x9b, 0x9c, 0x9d]
|
||||
for room in rooms:
|
||||
b2idx = room*2
|
||||
b3idx = room*3
|
||||
headerptr = 0x271e2 + b2idx
|
||||
headerloc = rom_data[headerptr] + rom_data[headerptr+1]*0x100 + 0x18000
|
||||
header, objectdata, spritedata, secretdata = [], [], [], []
|
||||
for i in range(0, 14):
|
||||
header.append(rom_data[headerloc+i])
|
||||
objectptr = 0xF8000 + b3idx
|
||||
objectloc = rom_data[objectptr] + rom_data[objectptr+1]*0x100 + rom_data[objectptr+2]*0x10000
|
||||
objectloc -= 0x100000
|
||||
stop, idx = False, 0
|
||||
ffcnt = 0
|
||||
mode = 0
|
||||
while ffcnt < 2:
|
||||
b1 = rom_data[objectloc+idx]
|
||||
b2 = rom_data[objectloc+idx+1]
|
||||
b3 = rom_data[objectloc+idx+2]
|
||||
objectdata.append(b1)
|
||||
objectdata.append(b2)
|
||||
if b1 == 0xff and b2 == 0xff:
|
||||
ffcnt += 1
|
||||
mode = 0
|
||||
if b1 == 0xf0 and b2 == 0xff:
|
||||
mode = 1
|
||||
if not mode and ffcnt < 2:
|
||||
objectdata.append(b3)
|
||||
idx += 3
|
||||
else:
|
||||
idx += 2
|
||||
spriteptr = 0x4d62e + b2idx
|
||||
spriteloc = rom_data[spriteptr] + rom_data[spriteptr+1]*0x100 + 0x40000
|
||||
secretptr = 0xdb67 + b2idx
|
||||
secretloc = rom_data[secretptr] + rom_data[secretptr+1]*0x100
|
||||
done, idx = False, 0
|
||||
while not done:
|
||||
b1 = rom_data[spriteloc+idx]
|
||||
spritedata.append(b1)
|
||||
if b1 == 0xff:
|
||||
done = True
|
||||
idx += 1
|
||||
done, idx = False, 0
|
||||
while not done:
|
||||
b1 = rom_data[secretloc+idx]
|
||||
b2 = rom_data[secretloc+idx+1]
|
||||
b3 = rom_data[secretloc+idx+2]
|
||||
secretdata.append(b1)
|
||||
secretdata.append(b2)
|
||||
if b1 == 0xff and b2 == 0xff:
|
||||
done = True
|
||||
else:
|
||||
secretdata.append(b3)
|
||||
idx += 3
|
||||
print(f'Room {room:02x}')
|
||||
print(f'HeaderPtr {headerptr:06x}')
|
||||
print(f'HeaderLoc {headerloc:06x}')
|
||||
print(f'db {",".join([f"${x:02x}" for x in header])}')
|
||||
print(f'Obj Length: {len(objectdata)}')
|
||||
print(f'ObjectPtr {objectptr:06x}')
|
||||
print(f'ObjectLoc {objectloc:06x}')
|
||||
for i in range(0, len(objectdata)//16 + 1):
|
||||
slice = objectdata[i*16:i*16+16]
|
||||
print(f'db {",".join([f"${x:02x}" for x in slice])}')
|
||||
print(f'SpritePtr {spriteptr:06x}')
|
||||
print(f'SpriteLoc {spriteloc:06x}')
|
||||
print_data_block(spritedata)
|
||||
print(f'SecretPtr {secretptr:06x}')
|
||||
print(f'SecretLoc {secretloc:06x}')
|
||||
print_data_block(secretdata)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# make_new_base2current()
|
||||
# read_entrance_data(old_rom=sys.argv[1])
|
||||
room_palette_data(old_rom=sys.argv[1])
|
||||
# room_palette_data(old_rom=sys.argv[1])
|
||||
extract_data_from_us_rom(sys.argv[1])
|
||||
# extract_data_from_jp_rom(sys.argv[1])
|
||||
|
||||
|
||||
@@ -88,10 +88,10 @@ SuctionOverworldFix:
|
||||
+ rtl
|
||||
|
||||
; TT Alcove, Mire bridges, pod falling, SW torch room, TR Pipe room, Bob's Room, Ice Many Pots, Mire Hub
|
||||
; swamp waterfall
|
||||
; swamp waterfall, Gauntlet 3
|
||||
CutoffRooms:
|
||||
db $bc, $a2, $1a, $49, $14, $8c, $9f, $c2
|
||||
db $66
|
||||
db $66, $5d
|
||||
|
||||
CutoffEntranceRug:
|
||||
pha : phx
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user