From 5dce2daa0b02debf83cb7abf0c20157fbece7c4c Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 14 Jun 2022 12:29:57 -0600 Subject: [PATCH] Standard throne room changes --- DoorShuffle.py | 32 +++++++- DungeonGenerator.py | 167 +++++++++++++++++++++++++++++++----------- Main.py | 2 +- RELEASENOTES.md | 8 ++ Rom.py | 2 +- data/base2current.bps | Bin 93021 -> 93062 bytes 6 files changed, 164 insertions(+), 47 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 0d36a2b0..23df9b58 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -14,7 +14,7 @@ from Items import ItemFactory from RoomData import DoorKind, PairedDoor, reset_rooms from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths, drop_entrances from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances -from DungeonGenerator import dungeon_portals, dungeon_drops, GenerationException +from DungeonGenerator import dungeon_portals, dungeon_drops, GenerationException, connect_doors from DungeonGenerator import valid_region_to_explore as valid_region_to_explore_lim from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock from Utils import ncr, kth_combination @@ -685,6 +685,13 @@ def create_dungeon_entrances(world, player): choice = random.choice(filtered_choices) r_name = portal.door.entrance.parent_region.name split_map[key][choice].append(r_name) + elif key == 'Hyrule Castle' and world.mode[player] == 'standard': + for portal_name in portal_list: + portal = world.get_portal(portal_name, player) + choice = 'Sewers' if portal_name == 'Sanctuary' else 'Dungeon' + r_name = portal.door.entrance.parent_region.name + split_map[key][choice].append(r_name) + entrance_map[key].append(r_name) else: for portal_name in portal_list: portal = world.get_portal(portal_name, player) @@ -774,7 +781,8 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_ logging.getLogger('').info(world.fish.translate("cli", "cli", "generating.dungeon")) while len(sector_queue) > 0: builder = sector_queue.popleft() - split_dungeon = builder.name.startswith('Desert Palace') or builder.name.startswith('Skull Woods') + split_dungeon = (builder.name.startswith('Desert Palace') or builder.name.startswith('Skull Woods') + or (builder.name.startswith('Hyrule Castle') and world.mode[player] == 'standard')) name = builder.name if split_dungeon: name = ' '.join(builder.name.split(' ')[:-1]) @@ -782,6 +790,7 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_ del dungeon_builders[builder.name] continue origin_list = list(builder.entrance_list) + find_standard_origins(builder, recombinant_builders, origin_list) find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name) split_dungeon = treat_split_as_whole_dungeon(split_dungeon, name, origin_list, world, player) if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player): @@ -798,7 +807,7 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_ builder.master_sector = ds builder.layout_starts = origin_list if len(builder.entrance_list) <= 0 else builder.entrance_list last_key = None - combine_layouts(recombinant_builders, dungeon_builders, entrances_map) + combine_layouts(recombinant_builders, dungeon_builders, entrances_map, world, player) world.dungeon_layouts[player] = {} for builder in dungeon_builders.values(): builder.entrance_list = builder.layout_starts = builder.path_entrances = find_accessible_entrances(world, player, builder) @@ -872,6 +881,14 @@ def add_shuffled_entrances(sectors, region_list, entrance_list): entrance_list.append(region.name) +def find_standard_origins(builder, recomb_builders, origin_list): + if builder.name == 'Hyrule Castle Sewers': + throne_door = recomb_builders['Hyrule Castle'].throne_door + sewer_entrance = throne_door.entrance.parent_region.name + if sewer_entrance not in origin_list: + origin_list.append(sewer_entrance) + + def find_enabled_origins(sectors, enabled, entrance_list, entrance_map, key): for sector in sectors: for region in sector.regions: @@ -1378,7 +1395,7 @@ def merge_sectors(all_sectors, world, player): # those with split region starts like Desert/Skull combine for key layouts -def combine_layouts(recombinant_builders, dungeon_builders, entrances_map): +def combine_layouts(recombinant_builders, dungeon_builders, entrances_map, world, player): for recombine in recombinant_builders.values(): queue = deque(dungeon_builders.values()) while len(queue) > 0: @@ -1390,6 +1407,10 @@ def combine_layouts(recombinant_builders, dungeon_builders, entrances_map): recombine.master_sector.name = recombine.name else: recombine.master_sector.regions.extend(builder.master_sector.regions) + if recombine.name == 'Hyrule Castle': + recombine.master_sector.regions.extend(recombine.throne_sector.regions) + throne_n = world.get_door('Hyrule Castle Throne Room N', player) + connect_doors(throne_n, recombine.throne_door) recombine.layout_starts = list(entrances_map[recombine.name]) dungeon_builders[recombine.name] = recombine @@ -1487,11 +1508,14 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True proposal = kth_combination(sample_list[itr], builder.candidates, builder.key_doors_num) # eliminate start region if portal marked as destination + std_flag = world.mode[player] == 'standard' and builder.name == 'Hyrule Castle' excluded = {} for region in start_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 + if std_flag and (not portal or portal.find_portal_entrance().parent_region.name != 'Hyrule Castle Courtyard'): + excluded[region] = None start_regions = [x for x in start_regions if x not in excluded.keys()] key_layout = build_key_layout(builder, start_regions, proposal, world, player) diff --git a/DungeonGenerator.py b/DungeonGenerator.py index f0038f45..68b82732 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -43,7 +43,10 @@ def pre_validate(builder, entrance_region_names, split_dungeon, world, player): for sector in builder.sectors: for door in sector.outstanding_doors: doors_to_connect[door.name] = door - all_regions.update(sector.regions) + if world.mode[player] == 'standard' and builder.name == 'Hyrule Castle Dungeon': + all_regions.update([x for x in sector.regions if x.name != 'Hyrule Castle Behind Tapestry']) + else: + all_regions.update(sector.regions) bk_special |= check_for_special(sector.regions) bk_needed = False for sector in builder.sectors: @@ -91,18 +94,27 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon p_region = portal.door.entrance.connected_region access_region = next(x.parent_region for x in p_region.entrances if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]) + if ((access_region.name in world.inaccessible_regions[player] and + region.name not in world.enabled_entrances[player]) + or (world.mode[player] == 'standard' and access_region.name != 'Hyrule Castle Courtyard' + and 'Hyrule Castle' in builder.name)): + excluded[region] = None + else: # for non-portals, holes and sewers in std + access_region = next((x.parent_region for x in region.entrances + if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] + or x.parent_region.name == 'Sewer Drop'), None) + if access_region is None: + if builder.sewers_access is None: + excluded[region] = None + else: + if access_region.name == 'Sewer Drop': + if world.mode[player] == 'standard' and (builder.sewers_access is None + or builder.sewers_access.entrance.parent_region != region): + excluded[region] = None + access_region = next(x.parent_region for x in access_region.entrances) if (access_region.name in world.inaccessible_regions[player] and region.name not in world.enabled_entrances[player]): excluded[region] = None - elif len(region.entrances) == 1: # for holes - access_region = next(x.parent_region for x in region.entrances - if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] - or x.parent_region.name == 'Sewer Drop') - if access_region.name == 'Sewer Drop': - access_region = next(x.parent_region for x in access_region.entrances) - if (access_region.name in world.inaccessible_regions[player] and - region.name not in world.enabled_entrances[player]): - excluded[region] = None entrance_regions = [x for x in entrance_regions if x not in excluded.keys()] doors_to_connect = {} all_regions = set() @@ -143,7 +155,11 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon dungeon, hangers, hooks = gen_dungeon_info(name, builder.sectors, entrance_regions, all_regions, proposed_map, doors_to_connect, bk_needed, bk_special, world, player) dungeon_cache[depth] = dungeon, hangers, hooks - valid = check_valid(name, dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions, + if len(proposed_map) != len(doors_to_connect) and builder.name == 'Hyrule Castle Dungeon': + check_regions = all_regions.difference({world.get_region('Hyrule Castle Behind Tapestry', player)}) + else: + check_regions = all_regions + valid = check_valid(name, dungeon, hangers, hooks, proposed_map, doors_to_connect, check_regions, bk_needed, bk_special, paths, entrance_regions, world, player) else: dungeon, hangers, hooks = dungeon_cache[depth] @@ -565,9 +581,15 @@ def determine_paths_for_dungeon(world, player, all_regions, name): non_hole_portals.append(portal.door.entrance.parent_region.name) if portal.destination: paths.append(portal.door.entrance.parent_region.name) - if world.mode[player] == 'standard' and name == 'Hyrule Castle': - paths.append('Hyrule Dungeon Cellblock') - paths.append(('Hyrule Dungeon Cellblock', 'Sanctuary')) + if world.mode[player] == 'standard': + if name == 'Hyrule Castle': + paths.append('Hyrule Dungeon Cellblock') + paths.append(('Hyrule Dungeon Cellblock', 'Sanctuary')) + if name == 'Hyrule Castle Sewers': + paths.append('Sanctuary') + if name == 'Hyrule Castle Dungeon': + paths.append('Hyrule Dungeon Cellblock') + paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room')) if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town': paths.append('Thieves Attic Window') elif 'Thieves Attic Window' in all_r_names: @@ -1206,6 +1228,11 @@ class DungeonBuilder(object): self.split_dungeon_map = None self.exception_list = [] + self.throne_door = None + self.throne_sector = None + self.chosen_lobby = None + self.sewers_access = None + def polarity_complement(self): pol = Polarity() for sector in self.sectors: @@ -1258,7 +1285,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, for r_name in dungeon_boss_sectors[key]: assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors, global_pole) if key == 'Hyrule Castle' and world.mode[player] == 'standard': - for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary']: # need to deliver zelda + for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary', 'Hyrule Castle Throne Room']: # need to deliver zelda assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors, global_pole) if key == 'Thieves Town' and world.get_dungeon("Thieves Town", player).boss.enemizer_name == 'Blind': @@ -2077,16 +2104,18 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, builde while len(problem_builders) > 0: for name, builder in problem_builders.items(): candidates = find_branching_candidates(builder, neutral_choices, builder_info) - valid, choice = False, None + valid, choice, package = False, None, None while not valid: if len(candidates) <= 0: raise GenerationException('Cross Dungeon Builder: Complex branch problems: %s' % name) - choice = random.choice(candidates) - candidates.remove(choice) + choice, package = random.choice(candidates) + candidates.remove((choice, package)) valid = global_pole.is_valid_choice(dungeon_map, builder, choice) and valid_polarized_assignment(builder, choice) neutral_choices.remove(choice) for sector in choice: assign_sector(sector, builder, polarized_sectors, global_pole) + if package: + builder.throne_door, builder.throne_sector, builder.chosen_lobby = package builder.unfulfilled.clear() problem_builders = identify_branching_issues(problem_builders, builder_info) @@ -2107,16 +2136,21 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, builde chosen_sectors = defaultdict(list) for i, choice in enumerate(choices): chosen_sectors[choice].extend(neutral_choices[i]) - all_valid = True + all_valid, package_map = True, {} for name, sector_list in chosen_sectors.items(): - if not valid_assignment(dungeon_map[name], sector_list, builder_info): + flag, package = valid_assignment(dungeon_map[name], sector_list, builder_info) + if not flag: all_valid = False break + if package: + package_map[dungeon_map[name]] = package if all_valid: for i, choice in enumerate(choices): builder = dungeon_map[choice] for sector in neutral_choices[i]: assign_sector(sector, builder, polarized_sectors, global_pole) + if builder in package_map: + builder.throne_door, builder.throne_sector, builder.chosen_lobby = package_map[builder] tries += 1 @@ -2629,9 +2663,9 @@ def weed_candidates(builder, candidates, best_charge): def find_branching_candidates(builder, neutral_choices, builder_info): candidates = [] for choice in neutral_choices: - resolved, problem_list = check_for_valid_layout(builder, choice, builder_info) + resolved, problem_list, package = check_for_valid_layout(builder, choice, builder_info) if resolved: - candidates.append(choice) + candidates.append((choice, package)) return candidates @@ -2786,13 +2820,13 @@ def categorize_groupings(sectors): def valid_assignment(builder, sector_list, builder_info): if not valid_entrance(builder, sector_list, builder_info): - return False + return False, None if not valid_c_switch(builder, sector_list): - return False + return False, None if not valid_polarized_assignment(builder, sector_list): - return False - resolved, problems = check_for_valid_layout(builder, sector_list, builder_info) - return resolved + return False, None + resolved, problems, package = check_for_valid_layout(builder, sector_list, builder_info) + return resolved, package def valid_entrance(builder, sector_list, builder_info): @@ -2898,31 +2932,56 @@ def assign_the_rest(dungeon_map, neutral_sectors, global_pole, builder_info): chosen_sectors = defaultdict(list) for i, choice in enumerate(choices): chosen_sectors[choice].append(neutral_sector_list[i]) - all_valid = True + all_valid, package_map = True, {} for name, sector_list in chosen_sectors.items(): - if not valid_assignment(dungeon_map[name], sector_list, builder_info): + flag, package = valid_assignment(dungeon_map[name], sector_list, builder_info) + if not flag: all_valid = False break + if package: + package_map[dungeon_map[name]] = package if all_valid: for name, sector_list in chosen_sectors.items(): builder = dungeon_map[name] for sector in sector_list: assign_sector(sector, builder, neutral_sectors, global_pole) + if builder in package_map: + builder.throne_door, builder.throne_sector, builder.chosen_lobby = package_map[builder] tries += 1 def split_dungeon_builder(builder, split_list, builder_info): + ents, splits, c_tuple, world, player = builder_info if builder.split_dungeon_map and len(builder.exception_list) == 0: for name, proposal in builder.valid_proposal.items(): builder.split_dungeon_map[name].valid_proposal = proposal + if builder.name == 'Hyrule Castle': + builder.chosen_lobby.outstanding_doors.remove(builder.throne_door) + builder.throne_sector.outstanding_doors.remove(world.get_door('Hyrule Castle Throne Room N', player)) return builder.split_dungeon_map # we made this earlier in gen, just use it attempts, comb_w_replace, merge_attempt, merge_limit = 0, None, 0, len(split_list) - 1 while attempts < 5: # does not solve coin flips 3% of the time try: candidate_sectors = dict.fromkeys(builder.sectors) - global_pole = GlobalPolarity(candidate_sectors) + if builder.name == 'Hyrule Castle': + throne_sector = find_sector('Hyrule Castle Throne Room', candidate_sectors) + chosen_lobbies = {r_name for x in split_list.values() for r_name in x} + choices = {} + for sector in candidate_sectors: + if sector.adj_outflow() > 1 and sector != throne_sector: + for door in sector.outstanding_doors: + if door.direction == Direction.South and door.entrance.parent_region not in chosen_lobbies: + choices[door] = sector + chosen_door = random.choice(list(choices.keys())) + split_list['Sewers'].append(chosen_door.entrance.parent_region.name) + choices[chosen_door].outstanding_doors.remove(chosen_door) + builder.throne_door = chosen_door + builder.throne_sector = throne_sector + builder.chosen_lobby = choices[chosen_door] + throne_sector.outstanding_doors.remove(world.get_door('Hyrule Castle Throne Room N', player)) + global_pole = GlobalPolarity(candidate_sectors) dungeon_map, sub_builder, merge_keys = {}, None, [] if merge_attempt > 0: candidates = [] @@ -2932,7 +2991,6 @@ def split_dungeon_builder(builder, split_list, builder_info): continue elif len(split_entrances) <= 0: continue - ents, splits, c_tuple, 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), None) if p and not p.deadEnd: @@ -2953,6 +3011,13 @@ def split_dungeon_builder(builder, split_list, builder_info): 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) + if builder.name == 'Hyrule Castle': + assign_sector(find_sector('Hyrule Castle Throne Room', candidate_sectors), + dungeon_map['Hyrule Castle Dungeon'], candidate_sectors, global_pole) + assign_sector(find_sector('Hyrule Dungeon Cellblock', candidate_sectors), + dungeon_map['Hyrule Castle Dungeon'], candidate_sectors, global_pole) + dungeon_map['Hyrule Castle Dungeon'].throne_door = world.get_door('Hyrule Castle Throne Room N', player) + dungeon_map['Hyrule Castle Sewers'].sewers_access = builder.throne_door comb_w_replace = len(dungeon_map) ** len(candidate_sectors) return balance_split(candidate_sectors, dungeon_map, global_pole, builder_info) except (GenerationException, NeutralizingException): @@ -2960,7 +3025,13 @@ 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 merge_attempt < merge_limit: + if builder.throne_door: + previous = find_sector(builder.throne_door.entrance.parent_region.name, builder.sectors) + previous.outstanding_doors.append(builder.throne_door) + builder.throne_sector.outstanding_doors.append(world.get_door('Hyrule Castle Throne Room N', player)) + split_list['Sewers'].remove(builder.throne_door.entrance.parent_region.name) + builder.throne_door = None + if attempts >= 5 and merge_attempt < merge_limit and builder.name != 'Hyrule Castle': merge_attempt, attempts = merge_attempt + 1, 0 raise GenerationException('Unable to resolve in 5 attempts') @@ -2981,16 +3052,21 @@ def balance_split(candidate_sectors, dungeon_map, global_pole, builder_info): chosen_sectors = defaultdict(list) for i, choice in enumerate(choices): chosen_sectors[choice].append(main_sector_list[i]) - all_valid = True + all_valid, package_map = True, {} for name, builder in dungeon_map.items(): - if not valid_assignment(builder, chosen_sectors[name], builder_info): + flag, package = valid_assignment(builder, chosen_sectors[name], builder_info) + if not flag: all_valid = False break + if package: + package_map[builder] = package if all_valid: for name, sector_list in chosen_sectors.items(): builder = dungeon_map[name] for sector in sector_list: assign_sector(sector, builder, candidate_sectors, global_pole) + if builder in package_map: + builder.throne_door, builder.throne_sector, builder.chosen_lobby = package_map[builder] return dungeon_map tries += 1 raise GenerationException('Split Dungeon Builder: Impossible dungeon. Ref %s' % next(iter(dungeon_map.keys()))) @@ -3380,7 +3456,7 @@ class DungeonAccess: def identify_branching_issues(dungeon_map, builder_info): unconnected_builders = {} for name, builder in dungeon_map.items(): - resolved, unreached_doors = check_for_valid_layout(builder, [], builder_info) + resolved, unreached_doors, package = check_for_valid_layout(builder, [], builder_info) if not resolved: unconnected_builders[name] = builder for hook, door_list in unreached_doors.items(): @@ -3421,16 +3497,27 @@ def check_for_valid_layout(builder, sector_list, builder_info): proposal = generate_dungeon_find_proposal(split_build, entrance_regions, split, world, player) # record split proposals builder.valid_proposal[name] = proposal + package = None + if temp_builder.name == 'Hyrule Castle': + temp_builder.chosen_lobby.outstanding_doors.append(temp_builder.throne_door) + temp_builder.throne_sector.outstanding_doors.append(world.get_door('Hyrule Castle Throne Room N', player)) + package = temp_builder.throne_door, temp_builder.throne_sector, temp_builder.chosen_lobby + split_list['Sewers'].remove(temp_builder.throne_door.entrance.parent_region.name) builder.exception_list = list(sector_list) - return True, {} + return True, {}, package except (GenerationException, NeutralizingException): builder.split_dungeon_map = None builder.valid_proposal = None + if temp_builder.name == 'Hyrule Castle' and temp_builder.throne_door: + temp_builder.chosen_lobby.outstanding_doors.append(temp_builder.throne_door) + temp_builder.throne_sector.outstanding_doors.append(world.get_door('Hyrule Castle Throne Room N', player)) + old_entrance = temp_builder.throne_door.entrance.parent_region.name + split_dungeon_entrances[builder.name]['Sewers'].remove(old_entrance) unreached_doors = resolve_equations(builder, sector_list) - return False, unreached_doors + return False, unreached_doors, None else: unreached_doors = resolve_equations(builder, sector_list) - return len(unreached_doors) == 0, unreached_doors + return len(unreached_doors) == 0, unreached_doors, None def find_independent_entrances(entrance_regions, world, player): @@ -3831,9 +3918,7 @@ def find_free_equation(equations): def copy_door_equations(builder, sector_list): equations = {} for sector in builder.sectors + sector_list: - if sector.equations is None: - # todo: sort equations? - sector.equations = calc_sector_equations(sector) + sector.equations = calc_sector_equations(sector) curr_list = equations[sector] = [] for equation in sector.equations: curr_list.append(equation.copy()) diff --git a/Main.py b/Main.py index f3dcb0ee..32b7dd7f 100644 --- a/Main.py +++ b/Main.py @@ -31,7 +31,7 @@ from Utils import output_path, parse_player_names from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config from source.tools.BPS import create_bps_from_data -__version__ = '1.0.2.4-v' +__version__ = '1.0.2.4-x' from source.classes.BabelFish import BabelFish diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 31d1e10e..286704e6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -155,8 +155,16 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o ## Notes and Bug Fixes +#### StandardThrone + + * Changed standard dungeon generation to always have Throne Room in hyrule castle and always have sanctuary behind it + * S&Q/death in standard after moving the tapestry but before delivering Zelda will result in spawning at the tapestry + * Mirror scroll will return you to Zelda's cell instead of last entrance. This reverts to normal behavior once the tapestry open trigger is reach + #### Volatile +* 1.0.2.5 + * Some textual changes for hints * 1.0.2.4 * Updated tourney winners (included Doors Async League winners) * Fixed a couple issues with dungeon counters and the DungeonCompletion field for autotracking diff --git a/Rom.py b/Rom.py index 1c9d82a7..f062e4d3 100644 --- a/Rom.py +++ b/Rom.py @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '01166fb16b38b49ef79acc9993dc4f02' +RANDOMIZERBASEHASH = '4941faf4496875a373a51119f2bedf4c' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index bd6417a47ef665eabc70d7f6055e50cdcb4562a4..4005551c18297514faad1e8e8ed9872d957d261b 100644 GIT binary patch delta 1773 zcmW;MeNaZ(aH``v8a5+tx5*aL2>CW3#?m~Zs55cWLQ8T z;k`)2Ktha%BtoEhBcvLThUu%TZDpd-7PGURjjeWDSpJY56et$(gLXUB-Dm$fzqucC z=bSV5-s!JHPW>xntP}|+OOxDO1=@}faDcBts0rMH3F5kq?3!d37WOhm@|}~8$PW`c z&18agsMf#;ubIiup|$-7nf&(ycFtY| zVrhnC4}V?}Z6zCFzcfJ{y3B54Y~%qrE=`Hx&n9h)?3v!u6_!j|f>CJ_8iuc=yJfG= z@)cilTFm4zh>^WOt(-C}U4WXof`~5#(XJI|c2nw14kl%i_|b0c|HEl94mAgNyS6Q`nXfZ1-^K_wJdI)T}Wo` zmtBIcsLiN?Qh;68VXTCbY>%AJS9QVoQGmrvd>^^+oDB#S!)v*@ zXd0;8AS#6{RX#eyb*O66Q6BfW?g@URxN6QL_TGM?3kZV|9;v;pyuCLy+;e6iD7TYK zkEj{qmqk1n;VtGjpc|lLCYtao?qY6S?X6)_gMvcEEYvoUF>!XA z*Iv}NYnR1r^XLtjN57l?(O8Jh!bnpVuDlQPO^0!QZ}y2TI6tX5!Q#;?AZ=F5`l?kQ z80#Le&XN_VXeOgx_mB4=I^0NO%OM^>w7~Q3$PPc)o8vcWk1*rQ!T>%d(RjjGR2DN6 zqIrYHv76k@8?BJlT8Zj`ZH>>nY$?2*M84)5Lj`zrg>BTz20Go` zhf$t7Lmzb>^R_Xmej+Hkj1eP?lp7=F9#KBkO&vM%0(Z3~K7-nfRp*I}RcPWxJ5~YP zvFeoYih0-YDtKdfmAspH3EnNd$-HU2sk}RRGk2^ylB&Wd2NcVK!jVPqvLn3`n6_Rt z2eEXDygFmme2%VhpT%gGYA2M?rPA0#Gv=~OKZQCQ;1fCvPs@cL>G*wHSBBs7_Jg`$ z_56@GqhQsrb*1k;!wlgpRIeIdxqSBI$Zx6`+fg@m0rPQT)r`TP>fC)`)zG@)eE(&* z$h10|;rGl&T=EoJ8B*E&jA?Y~nMRA=_0}^>+_R-Wl)d=#_lL{jZ%lvGW-0ygUjF{7 zX9&u@){Jc`BMKM_H2Uq5;-&nx|9SI^g!ehOUKJrvdBD=^{DilaleZglxF^=zTO!IU zy9SgV9JA<(+5p{!zkk{zu9BLVc9%v9?_7%)x5!M)X_rpMnXWwwiARy9K9N#Cuq+x+ zl?QO@LS9yfO-Wfkd!lnDW`V=c;D-je^*8fSVtA|xM6o7D4WHVC8V{m0j*Ycl?m$r+ z|2oiljB*^%a)w(KNYq+L57vsf`Oh+uj5=ttDhzRydg(9Rwux&KBcD$X;)vO$j rL&qHB;V1_W)n0iXj$R2B{i>RA>{)XqG7_mu#5u9ozy0}P|Jwfp4>1UU delta 1774 zcmW;Ndr(tX9tZH8n-G!!fkhtS6~h%#T-gDEE%HnOONywGQMaYRMMXhVcc@w(0?ECB zh{41VeufC)aYv09(GA6IQV}{x1NIAqwt|g4C6Z`#_KF(o30i?pBVEMVcjg=F+UxLkyW;j!;&UbBmOpETeW zXy8Q&Y!4{6wAF;)cLBxI`}TP#f`cbP3cnOZz-fLK_QC~U@Fj7*d9#$L{M6>bDS?jD zY{H)nU2=~Y@Yh3^s=E#NN3+yi=Q;=!r18U<1?7YZ-vV0%t9TFRsr958Uj=o7#6V_o zT=|r0?@C&r@c(`X*95B(0WLwN-%pPyxBH6KXu$J8;`bVr&@25nu#ucD3B1D&|8T*Y zUDtJ9ysmXbi<_q+Ii1G{EnZG%uHa+g!iUsyJp$JScLB32rYk2H;JwgykQlCoej*;2 zqd5IKe0J6n8GSZh*9AD_g#!6-+g_k7O$W zYha{iBZ`E@nq64Rw@_3YgM?63n~m6Tu{PbW>~k{Cm_^twkNLrqT0H#7BpK4ZE9eC2 z4;_#&Wg6lge>n*20Ixe3nrx0GBoje@W!W&9;}DrN%`TpO1XLX!Av2pDopNft!CA%2zJpF+!1s@@>hHc&lLpx(M|R?+PmaqjX}d z={F^n@b`vntgaBE8uNrHsXv%xm&c?UYs62wN>DcLLlXF^Q5+UQ8jHtOM(Mb!=Pb9U zi#sE6++I?3G>KMD^OjqR?iOr}Y{3MV$L`$6wA9#LCI{Y)dx>xOJq zuJ~w~n`B$oya}YtdO-ZMse&!qkI`X7FIA4ebVIi)Iyy1r{^VKF;jp|b;d$-o_O}xf zrOZ`&HzVl2HWluuicvVMR7a}Gr%D@3a_gCLO!}z`AAF7VtK*(Sbeh+?I?W4ljERiv z8Pgb38M7I)7&kE%Fm7QiX57iRgRzWJ!MLBXBF$^rUg7Eu^014^rw)&0_gQm?z6cCBR&@`{%EOE%z!6N$KPX{$oDkzlC7!+4t`dV@LhRDLoav+uz zt62+|`y4%RtjK%rf@4pSSIb)H?$gdltaXK6ZSeJu=0UWdG?&|01FSJRy^7RMBv~tq zz1orm>&4}`M3z|MH+%c4wO8lnP6jQNJ5q=w55KSEh`mc`|Jm>-j&dMv>En=$ukx2v z?EwxZo%@D5_Yr?kIzCuZ!7Y+6ihoYl_G`#mqo$wz+$j5g-cRv5(4$hoKgpi31_AML z0doyxGZLk3g@F~TkZdNy!K6v$H+X7-zlr){n~nanMj9w&&r(Flgv8NE3tMy{bacn< zguvvAQ@tWP#xknpYDl~bOE=E&Hu~wwmi{b1Fy0jNw)yMHll>+B^!1z1e8f9L_1#>N zrq{zAO%ggW<-pO*R?WtHV< z^(mWEu4e8SI>%C4>Kp0bf3_QO*A^4!3w-I|&(o1#?l!$CNE@w`%}3BXr*0x{SjQ%U zc~8Tg$;`v_w(~lh`