From 97e4a02beedd67383de1733ea4a086615d973101 Mon Sep 17 00:00:00 2001 From: Kara Alexandra Date: Thu, 23 Dec 2021 23:46:53 -0800 Subject: [PATCH] Add cane-only modes --- BaseClasses.py | 128 +++++++++++++++++++++++++++++------- Bosses.py | 44 ++++++------- ItemList.py | 36 ++++++++-- Items.py | 12 +++- Rom.py | 78 +++++++++++++++++++--- Rules.py | 30 +++++++-- data/base2current.bps | Bin 89497 -> 89819 bytes resources/app/cli/args.json | 3 + 8 files changed, 262 insertions(+), 69 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index b8554ed8..2b23197f 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -365,6 +365,19 @@ class World(object): ret.prog_items['L2 Bombs', item.player] += 1 else: ret.prog_items['L1 Bombs', item.player] += 1 + elif 'Cane' in item.name: + if ret.has('L5 Cane', item.player): + pass + elif ret.has('L4 Cane', item.player): + ret.prog_items['L5 Cane', item.player] += 1 + elif ret.has('L3 Cane', item.player): + ret.prog_items['L4 Cane', item.player] += 1 + elif ret.has('L2 Cane', item.player): + ret.prog_items['L3 Cane', item.player] += 1 + elif ret.has('L1 Cane', item.player): + ret.prog_items['L2 Cane', item.player] += 1 + else: + ret.prog_items['L1 Cane', item.player] += 1 elif 'Glove' in item.name: if ret.has('Titans Mitts', item.player): pass @@ -393,6 +406,11 @@ class World(object): ret.prog_items[item.name, item.player] += 1 elif item.advancement or item.smallkey or item.bigkey: ret.prog_items[item.name, item.player] += 1 + if item.name.endswith(' Cane'): + if ret.world.swords[item.player] == 'byrna' and not ret.has('Cane of Byrna', item.player): + ret.prog_items['Cane of Byrna', item.player] += 1 + if ret.world.swords[item.player] == 'somaria' and not ret.has('Cane of Somaria', item.player): + ret.prog_items['Cane of Somaria', item.player] += 1 for item in self.itempool: soft_collect(item) @@ -1239,8 +1257,8 @@ class CollectionState(object): return basemagic >= smallmagic def can_kill_most_things(self, player, enemies=5): - return (self.bomb_mode_check(player, 1) and - (self.has_blunt_weapon(player) or self.has_bomb_level(player, 1) + return (self.special_weapon_check(player, 1) and + (self.has_blunt_weapon(player) or self.has_special_weapon_level(player, 1) or self.has('Cane of Somaria', player) or (self.has('Cane of Byrna', player) and (enemies < 6 or self.can_extend_magic(player))) or self.can_shoot_arrows(player) @@ -1259,7 +1277,7 @@ class CollectionState(object): # In the future, this can be used to check if the player starts without bombs def can_use_bombs(self, player): if self.world.swords[player] == 'bombs': - return self.has_bomb_level(player, 1) + return self.has_special_weapon_level(player, 1) return (not self.world.bombbag[player] or self.has('Bomb Upgrade (+10)', player)) and ((hasattr(self.world, "override_bomb_check") and self.world.override_bomb_check) or self.can_farm_bombs(player)) def can_hit_crystal(self, player): @@ -1316,23 +1334,41 @@ class CollectionState(object): return False; return self.has_sword(player, level) - def has_bomb_level(self, player, level): - if self.world.swords[player] != 'bombs': + def has_special_weapon_level(self, player, level): + if self.world.swords[player] == 'bombs': + if level == 5: + return self.has('L5 Bombs', player) + elif level == 4: + return self.has('L5 Bombs', player) or self.has('L4 Bombs', player) + elif level == 3: + return self.has('L5 Bombs', player) or self.has('L4 Bombs', player) or self.has('L3 Bombs', player) + elif level == 2: + return self.has('L5 Bombs', player) or self.has('L4 Bombs', player) or self.has('L3 Bombs', player) or self.has('L2 Bombs', player) + elif level == 1: + return self.has('L5 Bombs', player) or self.has('L4 Bombs', player) or self.has('L3 Bombs', player) or self.has('L2 Bombs', player) or self.has('L1 Bombs', player) + return True + elif self.world.swords[player] in ['byrna', 'somaria', 'cane']: + if self.world.swords[player] == 'cane' and not self.has('Cane of Somaria', player) and not self.has('Cane of Byrna', player): + return False + if level == 5: + return self.has('L5 Cane', player) + elif level == 4: + return self.has('L5 Cane', player) or self.has('L4 Cane', player) + elif level == 3: + return self.has('L5 Cane', player) or self.has('L4 Cane', player) or self.has('L3 Cane', player) + elif level == 2: + return self.has('L5 Cane', player) or self.has('L4 Cane', player) or self.has('L3 Cane', player) or self.has('L2 Cane', player) + elif level == 1: + return self.has('L5 Cane', player) or self.has('L4 Cane', player) or self.has('L3 Cane', player) or self.has('L2 Cane', player) or self.has('L1 Cane', player) + return True + else: return False - if level == 5: - return self.has('L5 Bombs', player) - elif level == 4: - return self.has('L5 Bombs', player) or self.has('L4 Bombs', player) - elif level == 3: - return self.has('L5 Bombs', player) or self.has('L4 Bombs', player) or self.has('L3 Bombs', player) - elif level == 2: - return self.has('L5 Bombs', player) or self.has('L4 Bombs', player) or self.has('L3 Bombs', player) or self.has('L2 Bombs', player) - elif level == 1: - return self.has('L5 Bombs', player) or self.has('L4 Bombs', player) or self.has('L3 Bombs', player) or self.has('L2 Bombs', player) or self.has('L1 Bombs', player) - return True - def bomb_mode_check(self, player, level): - return self.world.swords[player] != 'bombs' or self.has_bomb_level(player, level) + def special_weapon_check(self, player, level): + if self.world.swords[player] in ['bombs', 'byrna', 'somaria', 'cane']: + return self.has_special_weapon_level(player, level) + else: + return True def can_hit_stunned_ganon(self, player): ganon_item = self.world.ganon_item[player] @@ -1375,7 +1411,7 @@ class CollectionState(object): return False def can_use_medallions(self, player): - return self.has_sword(player) or self.world.swords[player] == 'bombs' + return self.has_sword(player) or self.world.swords[player] in ['bombs', 'byrna', 'somaria', 'cane'] def has_blunt_weapon(self, player): return self.has_real_sword(player) or self.has('Hammer', player) @@ -1491,6 +1527,24 @@ class CollectionState(object): else: self.prog_items['L1 Bombs', item.player] += 1 changed = True + elif 'Cane' in item.name: + if self.has('L5 Cane', item.player): + pass + elif self.has('L4 Cane', item.player): + self.prog_items['L5 Cane', item.player] += 1 + changed = True + elif self.has('L3 Cane', item.player): + self.prog_items['L4 Cane', item.player] += 1 + changed = True + elif self.has('L2 Cane', item.player): + self.prog_items['L3 Cane', item.player] += 1 + changed = True + elif self.has('L1 Cane', item.player): + self.prog_items['L2 Cane', item.player] += 1 + changed = True + else: + self.prog_items['L1 Cane', item.player] += 1 + changed = True elif 'Glove' in item.name: if self.has('Titans Mitts', item.player): pass @@ -1541,6 +1595,13 @@ class CollectionState(object): elif event or item.advancement: self.prog_items[item.name, item.player] += 1 changed = True + if item.name.endswith(' Cane'): + if self.world.swords[item.player] == 'byrna' and not self.has('Cane of Byrna', item.player): + self.prog_items['Cane of Byrna', item.player] += 1 + changed = True + if self.world.swords[item.player] == 'somaria' and not self.has('Cane of Somaria', item.player): + self.prog_items['Cane of Somaria', item.player] += 1 + changed = True self.stale[item.player] = True @@ -1576,6 +1637,19 @@ class CollectionState(object): to_remove = 'L1 Bombs' else: to_remove = None + elif 'Cane' in to_remove: + if self.has('L5 Cane', item.player): + to_remove = 'L5 Cane' + elif self.has('L4 Cane', item.player): + to_remove = 'L4 Cane' + elif self.has('L3 Cane', item.player): + to_remove = 'L3 Cane' + elif self.has('L2 Cane', item.player): + to_remove = 'L2 Cane' + elif self.has('L1 Cane', item.player): + to_remove = 'L1 Cane' + else: + to_remove = None elif 'Glove' in item.name: if self.has('Titans Mitts', item.player): to_remove = 'Titans Mitts' @@ -1601,7 +1675,15 @@ class CollectionState(object): to_remove = None if to_remove is not None: - + if to_remove.endswith(' Cane') and not self.has('L5 Cane', item.player) and not self.has('L4 Cane', item.player) and not self.has('L3 Cane', item.player) and not self.has('L2 Cane', item.player) and not self.has('L1 Cane', item.player): + if self.world.swords[item.player] == 'byrna': + self.prog_items['Cane of Byrna', item.player] -= 1 + if self.prog_items['Cane of Byrna', item.player] < 1: + del (self.prog_items['Cane of Byrna', item.player]) + if self.world.swords[item.player] == 'somaria': + self.prog_items['Cane of Somaria', item.player] -= 1 + if self.prog_items['Cane of Somaria', item.player] < 1: + del (self.prog_items['Cane of Somaria', item.player]) self.prog_items[to_remove, item.player] -= 1 if self.prog_items[to_remove, item.player] < 1: del (self.prog_items[to_remove, item.player]) @@ -2940,8 +3022,8 @@ class Spoiler(object): 'experimental': self.world.experimental, 'keydropshuffle': self.world.keydropshuffle, 'shopsanity': self.world.shopsanity, - 'triforcegoal': self.world.treasure_hunt_count, - 'triforcepool': self.world.treasure_hunt_total, + 'triforcegoal': self.world.treasure_hunt_count, + 'triforcepool': self.world.treasure_hunt_total, 'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)} } @@ -3320,7 +3402,7 @@ er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "lite": 4, "le # byte 1: LLLW WSSS (logic, mode, sword) logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4} world_mode = {"open": 0, "standard": 1, "inverted": 2} -sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3, "bombs": 4, "pseudo": 5, "assured_pseudo": 6} +sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3, "bombs": 4, "pseudo": 5, "assured_pseudo": 5, "byrna": 6, "somaria": 6, "cane": 6} # byte 2: GGGD DFFH (goal, diff, item_func, hints) goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "crystals": 4} diff --git a/Bosses.py b/Bosses.py index 96128591..d266531f 100644 --- a/Bosses.py +++ b/Bosses.py @@ -16,7 +16,7 @@ def BossFactory(boss, player): def ArmosKnightsDefeatRule(state, player): # Magic amounts are probably a bit overkill - return (state.bomb_mode_check(player, 1) and + return (state.special_weapon_check(player, 1) and (state.has_blunt_weapon(player) or state.can_shoot_arrows(player) or (state.has('Cane of Somaria', player) and state.can_extend_magic(player, 10)) or @@ -25,38 +25,38 @@ def ArmosKnightsDefeatRule(state, player): (state.has('Fire Rod', player) and state.can_extend_magic(player, 32)) or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player) or - state.has_bomb_level(player, 1))) + state.has_special_weapon_level(player, 1))) def LanmolasDefeatRule(state, player): - return (state.bomb_mode_check(player, 1) and + return (state.special_weapon_check(player, 1) and (state.has_blunt_weapon(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player) or state.has('Cane of Byrna', player) or state.can_shoot_arrows(player) or - state.has_bomb_level(player, 1))) + state.has_special_weapon_level(player, 1))) def MoldormDefeatRule(state, player): - return (state.bomb_mode_check(player, 1) and - (state.has_blunt_weapon(player) or state.has_bomb_level(player, 1))) + return (state.special_weapon_check(player, 1) and + (state.has_blunt_weapon(player) or state.has_special_weapon_level(player, 1))) def HelmasaurKingDefeatRule(state, player): - return (state.bomb_mode_check(player, 2) and + return (state.special_weapon_check(player, 2) and (state.has('Hammer', player) or state.can_use_bombs(player)) and - (state.has_real_sword(player) or state.can_shoot_arrows(player) or state.has_bomb_level(player, 2))) + (state.has_real_sword(player) or state.can_shoot_arrows(player) or state.has_special_weapon_level(player, 2))) def ArrghusDefeatRule(state, player): if not state.has('Hookshot', player): return False - if not state.bomb_mode_check(player, 2): + if not state.special_weapon_check(player, 2): return False # TODO: ideally we would have a check for bow and silvers, which combined with the # hookshot is enough. This is not coded yet because the silvers that only work in pyramid feature # makes this complicated - if state.has_blunt_weapon(player) or state.has_bomb_level(player, 2): + if state.has_blunt_weapon(player) or state.has_special_weapon_level(player, 2): return True return ((state.has('Fire Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 12))) or #assuming mostly gitting two puff with one shot @@ -64,7 +64,7 @@ def ArrghusDefeatRule(state, player): def MothulaDefeatRule(state, player): - return (state.bomb_mode_check(player, 1) and + return (state.special_weapon_check(player, 1) and (state.has_blunt_weapon(player) or (state.has('Fire Rod', player) and state.can_extend_magic(player, 10)) or # TODO: Not sure how much (if any) extend magic is needed for these two, since they only apply @@ -72,15 +72,15 @@ def MothulaDefeatRule(state, player): (state.has('Cane of Somaria', player) and state.can_extend_magic(player, 16)) or (state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) or state.can_get_good_bee(player) or - state.has_bomb_level(player, 1))) + state.has_special_weapon_level(player, 1))) def BlindDefeatRule(state, player): - return (state.bomb_mode_check(player, 1) and + return (state.special_weapon_check(player, 1) and (state.has_blunt_weapon(player) or state.has('Cane of Somaria', player) or - state.has('Cane of Byrna', player) or state.has_bomb_level(player, 1))) + state.has('Cane of Byrna', player) or state.has_special_weapon_level(player, 1))) def KholdstareDefeatRule(state, player): - return (state.bomb_mode_check(player, 2) and + return (state.special_weapon_check(player, 2) and ( state.has('Fire Rod', player) or ( @@ -90,7 +90,7 @@ def KholdstareDefeatRule(state, player): ) ) and ( - state.has_bomb_level(player, 2) or state.has_blunt_weapon(player) or + state.has_special_weapon_level(player, 2) or state.has_blunt_weapon(player) or (state.has('Fire Rod', player) and state.can_extend_magic(player, 20)) or # FIXME: this actually only works for the vanilla location for swordless ( @@ -102,21 +102,21 @@ def KholdstareDefeatRule(state, player): )) def VitreousDefeatRule(state, player): - return (state.bomb_mode_check(player, 2) and + return (state.special_weapon_check(player, 2) and (state.can_shoot_arrows(player) or state.has_blunt_weapon(player) or - state.has_bomb_level(player, 2))) + state.has_special_weapon_level(player, 2))) def TrinexxDefeatRule(state, player): if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)): return False - if not state.bomb_mode_check(player, 2): + if not state.special_weapon_check(player, 2): return False return (state.has('Hammer', player) or state.has_real_sword(player, 3) or - state.has_bomb_level(player, 4) or - ((state.has_real_sword(player, 2) or state.has_bomb_level(player, 3)) + state.has_special_weapon_level(player, 4) or + ((state.has_real_sword(player, 2) or state.has_special_weapon_level(player, 3)) and state.can_extend_magic(player, 16)) or - ((state.has_real_sword(player) or state.has_bomb_level(player, 2)) + ((state.has_real_sword(player) or state.has_special_weapon_level(player, 2)) and state.can_extend_magic(player, 32))) def AgahnimDefeatRule(state, player): diff --git a/ItemList.py b/ItemList.py index 3301dc6b..74ecab77 100644 --- a/ItemList.py +++ b/ItemList.py @@ -34,7 +34,7 @@ normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2 Difficulty = namedtuple('Difficulty', ['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield', - 'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'bombs_only', + 'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'bombs_only', 'cane_only', 'progressivesword', 'basicsword', 'basicbow', 'timedohko', 'timedother', 'retro', 'bombbag', 'extras', 'progressive_sword_limit', 'progressive_shield_limit', @@ -55,6 +55,7 @@ difficulties = { basicarmor = ['Blue Mail', 'Red Mail'], swordless = ['Rupees (20)'] * 4, bombs_only = ['Progressive Bombs'] * 4, + cane_only = ['Progressive Cane'] * 3 + ['Rupees (20)'], progressivesword = ['Progressive Sword'] * 4, basicsword = ['Fighter Sword', 'Master Sword', 'Tempered Sword', 'Golden Sword'], basicbow = ['Bow', 'Silver Arrows'], @@ -82,6 +83,7 @@ difficulties = { basicarmor = ['Progressive Armor'] * 2, # neither will count swordless = ['Rupees (20)'] * 4, bombs_only = ['Progressive Bombs'] * 4, + cane_only = ['Progressive Cane'] * 3 + ['Rupees (20)'], progressivesword = ['Progressive Sword'] * 4, basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword', 'Tempered Sword'], basicbow = ['Bow'] * 2, @@ -109,6 +111,7 @@ difficulties = { basicarmor = ['Progressive Armor'] * 2, # neither will count swordless = ['Rupees (20)'] * 4, bombs_only = ['Progressive Bombs'] * 4, + cane_only = ['Progressive Cane'] * 3 + ['Rupees (20)'], progressivesword = ['Progressive Sword'] * 4, basicsword = ['Fighter Sword', 'Fighter Sword', 'Master Sword', 'Master Sword'], basicbow = ['Bow'] * 2, @@ -283,8 +286,7 @@ def generate_itempool(world, player): for item in precollected_items: world.push_precollected(ItemFactory(item, player)) - if (world.mode[player] == 'standard' and not (world.state.has_bomb_level(player, 1) if world.swords[player] else world.state.has_blunt_weapon(player)) - and not world.state.has_bomb_level(player, 1)): + if world.mode[player] == 'standard' and not (world.state.has_special_weapon_level(player, 1) if world.swords[player] in ['bombs', 'byrna', 'somaria', 'cane'] else world.state.has_blunt_weapon(player)): if world.swords[player] == 'bombs' and "Link's Uncle" not in placed_items: possible_weapons = [] for item in pool: @@ -293,6 +295,22 @@ def generate_itempool(world, player): starting_weapon = random.choice(possible_weapons) placed_items["Link's Uncle"] = starting_weapon pool.remove(starting_weapon) + elif world.swords[player] in ['byrna', 'somaria'] and "Link's Uncle" not in placed_items: + possible_weapons = [] + for item in pool: + if item in ['Progressive Cane', 'L1 Cane', 'L2 Cane', 'L3 Cane', 'L4 Cane', 'L5 Cane']: + possible_weapons.append(item) + starting_weapon = random.choice(possible_weapons) + placed_items["Link's Uncle"] = starting_weapon + pool.remove(starting_weapon) + elif world.swords[player] in ['cane'] and "Link's Uncle" not in placed_items: + possible_weapons = [] + for item in pool: + if item in ['Cane of Byrna', 'Cane of Somaria']: + possible_weapons.append(item) + starting_weapon = random.choice(possible_weapons) + placed_items["Link's Uncle"] = starting_weapon + pool.remove(starting_weapon) elif "Link's Uncle" not in placed_items: found_sword = False found_bow = False @@ -866,6 +884,14 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, pool.extend(diff.swordless) elif swords == 'bombs': pool.extend(diff.bombs_only) + elif swords == 'byrna': + pool = [item.replace('Cane of Byrna', 'Progressive Cane') for item in pool] + pool.extend(diff.cane_only) + elif swords == 'somaria': + pool = [item.replace('Cane of Somaria', 'Progressive Cane') for item in pool] + pool.extend(diff.cane_only) + elif swords == 'cane': + pool.extend(diff.cane_only) elif swords == 'vanilla': swords_to_use = diff.progressivesword.copy() if want_progressives() else diff.basicsword.copy() random.shuffle(swords_to_use) @@ -974,7 +1000,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s itemtotal = itemtotal + customitemarray["generickeys"] customitems = [ - "Bow", "Silver Arrows", "Blue Boomerang", "Red Boomerang", "Hookshot", "Mushroom", "Magic Powder", "Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake", "Lamp", "Hammer", "Shovel", "Ocarina", "Bug Catching Net", "Book of Mudora", "Cane of Somaria", "Cane of Byrna", "Cape", "Pegasus Boots", "Power Glove", "Titans Mitts", "Progressive Glove", "Flippers", "Piece of Heart", "Boss Heart Container", "Sanctuary Heart Container", "Master Sword", "Tempered Sword", "Golden Sword", "L1 Bombs", "L2 Bombs", "L3 Bombs", "L4 Bombs", "L5 Bombs", "Progressive Bombs", "Blue Shield", "Red Shield", "Mirror Shield", "Progressive Shield", "Blue Mail", "Red Mail", "Progressive Armor", "Magic Upgrade (1/2)", "Magic Upgrade (1/4)", "Bomb Upgrade (+5)", "Bomb Upgrade (+10)", "Arrow Upgrade (+5)", "Arrow Upgrade (+10)", "Single Arrow", "Arrows (10)", "Single Bomb", "Bombs (3)", "Rupee (1)", "Rupees (5)", "Rupees (20)", "Rupees (50)", "Rupees (100)", "Rupees (300)", "Rupoor", "Blue Clock", "Green Clock", "Red Clock", "Progressive Bow", "Bombs (10)", "Triforce Piece", "Triforce" + "Bow", "Silver Arrows", "Blue Boomerang", "Red Boomerang", "Hookshot", "Mushroom", "Magic Powder", "Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake", "Lamp", "Hammer", "Shovel", "Ocarina", "Bug Catching Net", "Book of Mudora", "Cane of Somaria", "Cane of Byrna", "Cape", "Pegasus Boots", "Power Glove", "Titans Mitts", "Progressive Glove", "Flippers", "Piece of Heart", "Boss Heart Container", "Sanctuary Heart Container", "Master Sword", "Tempered Sword", "Golden Sword", "L1 Bombs", "L2 Bombs", "L3 Bombs", "L4 Bombs", "L5 Bombs", "Progressive Bombs", "L1 Cane", "L2 Cane", "L3 Cane", "L4 Cane", "L5 Cane", "Progressive Cane", "Blue Shield", "Red Shield", "Mirror Shield", "Progressive Shield", "Blue Mail", "Red Mail", "Progressive Armor", "Magic Upgrade (1/2)", "Magic Upgrade (1/4)", "Bomb Upgrade (+5)", "Bomb Upgrade (+10)", "Arrow Upgrade (+5)", "Arrow Upgrade (+10)", "Single Arrow", "Arrows (10)", "Single Bomb", "Bombs (3)", "Rupee (1)", "Rupees (5)", "Rupees (20)", "Rupees (50)", "Rupees (100)", "Rupees (300)", "Rupoor", "Blue Clock", "Green Clock", "Red Clock", "Progressive Bow", "Bombs (10)", "Triforce Piece", "Triforce" ] for customitem in customitems: pool.extend([customitem] * customitemarray[get_custom_array_key(customitem)]) @@ -1049,7 +1075,7 @@ def test(): for goal in ['ganon', 'triforcehunt', 'pedestal']: for timer in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']: for mode in ['open', 'standard', 'inverted', 'retro']: - for swords in ['random', 'assured', 'swordless', 'vanilla', 'bombs']: + for swords in ['random', 'assured', 'swordless', 'vanilla', 'bombs', 'byrna', 'somaria', 'cane']: for progressive in ['on', 'off']: for shuffle in ['vanilla', 'full', 'crossed', 'insanity']: for logic in ['noglitches', 'minorglitches', 'owglitches', 'nologic']: diff --git a/Items.py b/Items.py index 9f732523..f10d2aca 100644 --- a/Items.py +++ b/Items.py @@ -168,10 +168,16 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Bee Trap': (False, False, None, 0xB0, 50, 'We will sting your face a whole lot!', 'and the sting buddies', 'the beekeeper kid', 'insects for sale', 'shroom pollenation', 'bottle boy has mad bees again', 'friendship'), 'L1 Bombs': (True, False, 'SwordBomb', 0xB1, 50, 'Some basic\nexplosives\nrest here!', 'the basic grenades', 'the bomb-holding kid', 'booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'basic bombs'), 'L2 Bombs': (True, False, 'SwordBomb', 0xB2, 100, 'Some decent\nexplosives\nrest here!', 'the decent grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), - 'L3 Bombs': (True, False, 'SwordBomb', 0xB3, 150, 'Some good\nexplosives\nrest here!', 'the good grenades sword', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), + 'L3 Bombs': (True, False, 'SwordBomb', 0xB3, 150, 'Some good\nexplosives\nrest here!', 'the good grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), 'L4 Bombs': (True, False, 'SwordBomb', 0xB4, 200, 'The golden\nexplosives\nrest here!', 'the golden grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), - 'L5 Bombs': (True, False, 'SwordBomb', 0xB5, 200, 'The golden\nexplosives\nrest here!', 'the golden grenades', 'the bomb-holding kid', 'better booms sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), - 'Progressive Bombs': (True, False, 'SwordBomb', 0xB6, 200, 'throw more\npowerful\nexplosives', 'the unknown grenades', 'the bomb-holding kid', 'better booms sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), + 'L5 Bombs': (True, False, 'SwordBomb', 0xB5, 200, 'The golden\nexplosives\nrest here!', 'the golden grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), + 'Progressive Bombs': (True, False, 'SwordBomb', 0xB6, 200, 'throw more\npowerful\nexplosives', 'the unknown grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), + 'L1 Cane': (True, False, 'SwordCane', 0xB7, 50, 'A basic\nstick\nrests here!', 'the basic stick', 'the stick-holding kid', 'stick for sale', 'fungus into stick', 'cane boy improves again', 'basic cane'), + 'L2 Cane': (True, False, 'SwordCane', 0xB8, 100, 'A decent\nstick\nrests here!', 'the decent stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'), + 'L3 Cane': (True, False, 'SwordCane', 0xB9, 150, 'A good\nstick\nrests here!', 'the good stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'), + 'L4 Cane': (True, False, 'SwordCane', 0xBA, 200, 'A golden\nstick\nrests here!', 'the golden stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'), + 'L5 Cane': (True, False, 'SwordCane', 0xBB, 200, 'A golden\nstick\nrests here!', 'the golden stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'), + 'Progressive Cane': (True, False, 'SwordCane', 0xBC, 200, 'a better\nstick\nrests here!', 'the unknown stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'), 'Red Potion': (False, False, None, 0x2E, 150, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a red potion'), 'Green Potion': (False, False, None, 0x2F, 60, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a green potion'), 'Blue Potion': (False, False, None, 0x30, 160, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a blue potion'), diff --git a/Rom.py b/Rom.py index 657b1176..f70c9d3e 100644 --- a/Rom.py +++ b/Rom.py @@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '7c1873254dcd5fb8b18934d806cd1949' +RANDOMIZERBASEHASH = 'b61fd3ea2d9c4c0465317052fa30a721' class JsonRom(object): @@ -1006,7 +1006,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): TRIFORCE_PIECE = ItemFactory('Triforce Piece', player).code GREEN_CLOCK = ItemFactory('Green Clock', player).code - rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on + if world.swords[player] in ['byrna', 'cane']: + rom.write_byte(0x18004F, 0x00) # Byrna Invulnerability: off + else: + rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on # handle difficulty_adjustments if world.difficulty_adjustments[player] == 'hard': @@ -1060,8 +1063,6 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x180085, 0x80) # full #Cape magic cost rom.write_bytes(0x3ADA7, [0x04, 0x08, 0x10]) - # Byrna Invulnerability: on - rom.write_byte(0x18004F, 0x01) #Enable catching fairies rom.write_byte(0x34FD6, 0xF0) # Rupoor negative value @@ -1267,6 +1268,64 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_bytes(0x118188, credits_string_bot("TEMPERED BOMBS")) rom.write_bytes(0x1181A6, credits_string_top("GOLD BOMBS")) rom.write_bytes(0x1181C4, credits_string_bot("GOLD BOMBS")) + elif world.swords[player] in ['byrna', 'somaria', 'cane']: + rom.write_byte(0x180040, 0x01) # open curtains + rom.write_byte(0x180041, 0x02) # swordless medallions (always) + rom.write_byte(0x180034, 0x00) # starting max bombs = 0 + + # remove magic cost of cane(s) + if world.swords[player] in ['byrna', 'cane']: + rom.write_bytes(0x045C42, [0x00, 0x00, 0x00]) + rom.write_bytes(0x045CC7, [0xEA, 0xEA]) + rom.write_byte(0x045CCD, 0x81) + rom.write_bytes(0x03B088, [0x00, 0x00, 0x00]) + rom.write_bytes(0x03B0A8, [0xEA, 0xEA]) + rom.write_byte(0x03B0AE, 0x81) + rom.write_bytes(0x18016B, [0x00, 0x00, 0x00]) + if world.swords[player] in ['somaria', 'cane']: + rom.write_bytes(0x03B07C, [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + rom.write_bytes(0x03B0A8, [0xEA, 0xEA]) + rom.write_byte(0x03B0AE, 0x81) + + if world.swords[player] == 'byrna': + rom.write_byte(0x18002F, 0x03) + colr = 0x2C + elif world.swords[player] == 'somaria': + rom.write_byte(0x18002F, 0x04) + colr = 0x24 + elif world.swords[player] == 'cane': + rom.write_byte(0x18002F, 0x05) + colr = 0x28 + + spritedata = [ + 0xF5, 0x20, 0xF5, 0x20, 0xF5, 0x20, 0xF5, 0x20, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x17, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x18, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x19, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x1A, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x1B, colr, + 0xF5, 0x20, 0xF5, 0x20, 0xF5, 0x20, 0xF5, 0x20, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x17, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x18, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x19, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x1A, colr, + 0xDC, colr, 0xDD, colr, 0xEC, colr, 0x1B, colr, + ]; + rom.write_bytes(0x06FC51, spritedata) + + # update sword references in credits to cane + rom.write_bytes(0x11807A, credits_string_top("FIRST CANE ")) + rom.write_bytes(0x118098, credits_string_bot("FIRST CANE ")) + rom.write_bytes(0x1180B6, credits_string_top("CANELESS ")) + rom.write_bytes(0x1180D4, credits_string_bot("CANELESS ")) + rom.write_bytes(0x1180F2, credits_string_top("FIGHTER'S CANE ")) + rom.write_bytes(0x118110, credits_string_bot("FIGHTER'S CANE ")) + rom.write_bytes(0x11812E, credits_string_top("MASTER CANE ")) + rom.write_bytes(0x11814C, credits_string_bot("MASTER CANE ")) + rom.write_bytes(0x11816A, credits_string_top("TEMPERED CANE ")) + rom.write_bytes(0x118188, credits_string_bot("TEMPERED CANE ")) + rom.write_bytes(0x1181A6, credits_string_top("GOLD CANE ")) + rom.write_bytes(0x1181C4, credits_string_bot("GOLD CANE ")) # set up clocks for timed modes if world.shuffle[player] == 'vanilla': @@ -1387,15 +1446,15 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): elif startingstate.has('Fighter Sword', player): equip[0x359] = 1 - if startingstate.has('L5 Bombs', player): + if startingstate.has('L5 Bombs', player) or startingstate.has('L5 Cane', player): equip[0x38F] = 5 - elif startingstate.has('L4 Bombs', player): + elif startingstate.has('L4 Bombs', player) or startingstate.has('L4 Cane', player): equip[0x38F] = 4 - elif startingstate.has('L3 Bombs', player): + elif startingstate.has('L3 Bombs', player) or startingstate.has('L3 Cane', player): equip[0x38F] = 3 - elif startingstate.has('L2 Bombs', player): + elif startingstate.has('L2 Bombs', player) or startingstate.has('L2 Cane', player): equip[0x38F] = 2 - elif startingstate.has('L1 Bombs', player): + elif startingstate.has('L1 Bombs', player) or startingstate.has('L1 Cane', player) or world.swords[player] == 'cane': equip[0x38F] = 1 if startingstate.has('Mirror Shield', player): @@ -1425,6 +1484,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): 'Titans Mitts', 'Power Glove', 'Progressive Glove', 'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword', 'L5 Bombs', 'L4 Bombs', 'L3 Bombs', 'L2 Bombs', 'L1 Bombs', 'Progressive Bombs', + 'L5 Cane', 'L4 Cane', 'L3 Cane', 'L2 Cane', 'L1 Cane', 'Progressive Cane', 'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield', 'Red Mail', 'Blue Mail', 'Progressive Armor', 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']: diff --git a/Rules.py b/Rules.py index 135f8230..33455a0b 100644 --- a/Rules.py +++ b/Rules.py @@ -337,11 +337,11 @@ def global_rules(world, player): set_rule(world.get_entrance('Mire Lobby Gap', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player)) set_rule(world.get_entrance('Mire Post-Gap Gap', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player)) set_rule(world.get_entrance('Mire Falling Bridge WN', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player)) # this is due to the fact the the door opposite is blocked - set_rule(world.get_entrance('Mire 2 NE', player), lambda state: state.bomb_mode_check(player, 1) and + set_rule(world.get_entrance('Mire 2 NE', player), lambda state: state.special_weapon_check(player, 1) and (state.has_real_sword(player) or (state.has('Fire Rod', player) and (state.can_use_bombs(player) or state.can_extend_magic(player, 9))) or # 9 fr shots or 8 with some bombs (state.has('Ice Rod', player) and state.can_use_bombs(player)) or # freeze popo and throw, bomb to finish - state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player) or state.has_bomb_level(player, 1))) # need to defeat wizzrobes, bombs don't work ... + state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player) or state.has_special_weapon_level(player, 1))) # need to defeat wizzrobes, bombs don't work ... # byrna could work with sufficient magic set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player)) set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player)) @@ -593,7 +593,7 @@ def global_rules(world, player): set_rule( world.get_location('Ganon', player), - lambda state: (state.has_real_sword(player, 2) or state.has_bomb_level(player, 3)) + lambda state: (state.has_real_sword(player, 2) or state.has_special_weapon_level(player, 3)) and state.has_fire_source(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player) and (state.has_real_sword(player, 3) or @@ -604,7 +604,7 @@ def global_rules(world, player): set_rule( world.get_entrance('Ganon Drop', player), - lambda state: state.has_real_sword(player, 2) or state.has_bomb_level(player, 3)) + lambda state: state.has_real_sword(player, 2) or state.has_special_weapon_level(player, 3)) # need to damage ganon to get tiles to drop def bomb_rules(world, player): @@ -860,6 +860,8 @@ def default_rules(world, player): swordless_rules(world, player) if world.swords[player] == 'bombs': bomb_mode_rules(world, player) + if world.swords[player] in ['byrna', 'somaria', 'cane']: + cane_mode_rules(world, player) if world.swords[player] in ['pseudo', 'assured_pseudo']: pseudo_sword_mode_rules(world, player) @@ -1531,11 +1533,25 @@ def bomb_mode_rules(world, player): set_rule(world.get_entrance('Tower Altar NW', player), lambda state: True) set_rule(world.get_entrance('Skull Vines NW', player), lambda state: True) - set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_bomb_level(player, 2)) - set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_bomb_level(player, 2)) + set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_special_weapon_level(player, 2)) + set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_special_weapon_level(player, 2)) if world.mode[player] != 'inverted': - set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_bomb_level(player, 2) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle + set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_special_weapon_level(player, 2) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle + + set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock Ledge', 'Region', player)) # sword not required to use medallion in bomb-only + add_bunny_rule(world.get_entrance('Turtle Rock', player), player) + add_bunny_rule(world.get_entrance('Misery Mire', player), player) + +def cane_mode_rules(world, player): + set_rule(world.get_entrance('Tower Altar NW', player), lambda state: True) + set_rule(world.get_entrance('Skull Vines NW', player), lambda state: True) + + set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_special_weapon_level(player, 2)) + set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_special_weapon_level(player, 2)) + + if world.mode[player] != 'inverted': + set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_special_weapon_level(player, 2) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock Ledge', 'Region', player)) # sword not required to use medallion in bomb-only add_bunny_rule(world.get_entrance('Turtle Rock', player), player) diff --git a/data/base2current.bps b/data/base2current.bps index e136579bf3e328280338444387b5755d9aee2c31..efd73101092423fedc56f0916ba2024f9ec560f7 100644 GIT binary patch delta 14965 zcmX}T30xD$`#-+B2@t}4C^v)!xr!1zQBk>66y)+k#ERk(jrT1onT@O(AS7Xg6;{Xs zK@5lisihtW-o~S*Ra>i7kk)<>wN0zFEp6pD>Hqcr@_J>Td1hv3=XmCMo_Rk*)dj1k zT~_LR{AbOoy~LqbO>fM^$(`IS#f46;RDn{*EUAAKTW{$+fs#B?qSGnKWo=w#a^!1q zyIxG#Sz$QFrmbp+^YAI7dMmj&O4*$O#LlQT;&Lf=uC&OVW>qmooxWh8h7>%5r|{SX z0e83zpdkfMxIufMSb2w2!-*ka|7j&jS8@9v>B(787xPL$2HnScuP~V~oa<`5f z9Na@`$UEP1gQz2aeaqcdk_SsegNs{9-^VbP@C$0J;7qx4Wv==S=U!Duu2AR(Eq~XP zT&ykviC7JXi`l2j=QKKwT;|h<Rats>qAV z&^D{Vi;7s5>r)g}b4iq9Mm6UP6yzV(?C6{#F;HYya|@^n@{cO6&GXxBONu;gX$1!A zk5!Oo-~ww=Ol~9hu^}c?>5f`;GIDK7Qefn=(JXxXoO`2i9OOKy&bjn8#lR~r?r;TZ z)Nw}98^z4W@RYUx8vjdNo@F`7^zWE_rAb4!KIZbqYn8cZ%wl;BxkbmBiM+u`T1k37 zjvygk}F8b4QBI_@{6o^emPBsq=UH6;^YeP4K72??ET|2MtFZuqvG@<&bOw5 zT>lJ;Z9IteeQ=JAUs8pRlb~6OJdZxS1@-crj?*drcb)rDNygmZM3j;|tm7g}XcB1S z1ZAo6Dl)B42k=4rL(0bD@Hkx!S*C+6Hqo-m@437kpkn0v!f@Q=S){1daSv4iFX_QS z9aWM|DdHPBzMKyKW6RMh^7@hH!NIoFgNoe5UWl6na>b82Zbzdcq>;;D74c7XNr$Ng zYz9GzREn@Kb;lGA=eZt*Bg=`jO7hfgPEx6uahuyAFJJCfo=Q=?@rNk5R1gx;@eP-{ z5RGQ__d4MBZnTFo1}(yoXWU~YsehsSM?)U%)eXK;lE>cUVJ4KIaBg zPbr?jM}qNQ;YQB)pmx=IP4clCvP1o!@Y}a4$wo-pMv5Q5=K!Bv|2r3lK2uxGsd<)N zeWNV&q2eUX-Blde&B z^YFtUH&7Xo&U`Nf0i)#)&yxF&B1s=qhmpDEFi#jDl%io-R7Bl#s1S~|^?1)kBEenu z9$piQWW~>PzzS6#9ON|QCumyiAHh!-Lnb__2rbzv0s4B`3*C-#g^{O@gXr0oceVMAq=TW60OSs41E*OJ^)d(-xjSBq&X}3YK zoJrUW@~&z~tXMbL#uS8`ULeZq5J&K-@a$0ue6t%fBPW=XHnrd#a zZ9tQI6OHK>onEp4Xfrk(?t!HkpffZBO0xPf*G4UF@Ry4;8gg|dH|YK^?L_W|i=}`b zR4+hNe(*Ka+ixZ&7@)U92x0peCOAwWN~_>rhZwiDnOa3jJJ+Wq(Q^l29T#fhWrt}3 zeH)jD6zD~0?HEB6zwa39_&s6Uk7hlLQb_xuhl}5O!|&|0Jyi{#E-v|kChykS3&c zu8#69-2ob0vr0eHkkcQ-X|B=HDkf;p8C?ZA|Ah_+w5~Ztdr+76lygTFWNrBa(V{95 zs3E;jA+kQss594~*7X#xJZb|>a~nlCzJ$BnCi#H@F1Q4yFD;Kj0#@C}OOeeZM=Om%Jhs-r-4tGH9?{KtjL{JTq$dp-;ji?NA8zzIKEavdc0=5eGgd#!&$D$ z^?J*pC3unB@fz-T45!({mFhN#_LwUfp_g+anI3ZL7Yrw>qUzQa|MOXU{&`60_Q<1wVE6GLqQ?X@3`G_{@a_k5lNDh3D1 zEQKx@$$U|XZtw`b{SIR{ve&Z)=}T2;*k@W#srHn`B;TdUw;j=cdomTI9O;+Aq&fxP z?9bS4XpK3f_^9Ks6BXnVi^0t<){uW8hai<^a<){V~&ZJT2~&o65kH< z<8Gt;G4Vd}3n^o}dW5mF&BYJ}#75i{t;bcy3|T6Ap;H}f>}nJJSHLq{8~J7-a7D$L z?+Sqs(F{ZhxFSBzm;I6}7?McILa4KJ!DNp*BoZA}qUQ(L6(1UsdGvlI5UU;jE448& zK#a;#cONmPnH&xK-W8gy^y%zMC`|An=WY5$Gi(y*=dcqu{X$_agBoOw^%<-afV`M~ zW^PPBoS6{Go9m;5I}*whL)FM4%l`$R8CROzNn6OFhF^=0LjABxjfR0fk#BM)ufqtY z>4>7u;k#wb5pEA#V!UgBhhmn8v&?`Epn<8{k}lIbLe-Tl~WfOdCicd zBqjGp+I9?8Fj?8M+U05fL81H8C~EJBr=8fc8y=MHp8s7Er>Hu1i2q3eJEQ-BOybnF zl_RK1cYga=us^Pq5l#I(ikfD1sbjkL)3qRd-0k}@?U(GlF75-%&$Qt3PbjxlDkj`h z&Td&v7KD{!uymGXC!=*6WW8K+l-8bqwR{C>R74%#A6Pvd7CnsvE%^kW2V~E|2x3|wf<929+lg^MI zfcif!oz5xB+wdzS6JFf2K898d;Kyj*HJ*vDAEtRNQn;j~(H1E!&Jg#mP^xOBP0nm6 zP4SyVS5?}oafaP!~f=8ztDGuh*2_U{Qdd-t@f`8}VjlX^N4y{KM#7|Xkf zwf)tE+2kdwa)1K`AI=K37F0-51r-pV?O|`on*5ad+#shu?~p^^*$ePSxOKL_M|D9` z`B}>;MJOUG^?YR{4k}k~Z-Xah2k>lMp2AzR7ZYn7VPJCPNbh4@?uVN?I0jVkQWcDW zQyqqWAuvBVB5nNCC(NQE7jU0x^f9}&+VhRWCZ|Tus?KC>*h+~__NhaahI$}2jZAf6 znB8*rPugkMEE^; za!!WV_}u4j2{liTOAU9?x{^s%9PKA1y^;GjcW!<*%bg?v%aP~nIThN?$6paWA*K;< z@;%Dw^!kUtIs%OB(AbbQRW_cfG1W2TP9xg=(kD42^5&VidwyUZ$DIV93&{FbP92e) zW56S`Jw%UK6@kxSdDgN=6ZdLUZlBl0Gs4<6AoJ5*@VO7#=dLGwQ)P<%F^$mS% zElWKts>ot{bt6ov!%DFQ;FOw~Gx}ihF&>=i* zD0M=OvA=1S6xyaPB_`CsHL0V-$s8*wqDEdA4JhYcxiXnES~Z2rsrVnZ$S*7{vhZO| zvuG;e2l<5=G-Ba})EF0(JGCjRh?*BMs=DE>8hLmxo7G)(@NvQfz!QK_CUHGVWrVUs^Ma*lFS$yF0E(DDR6 zqee6GI+0XAMr!=i6ci3Q%B{s_m~uch1n2`FTY36$sXjs~wN9Ej%Z(z7;l0c;;Vwrh zw+2rfu%(KpCnZcX{ZX$fqCTLm{A&tt7=;7M2F4M9ehSH~iPIBpfOY|LasD*tYALy_|7Nh!ueFM$Ur{{-y zq@N-WonTgnJb|CaJyivH0!6YQnUTUb^Zgv3p5#;!J%y^VJ%tkTBlK7h#B#3GuFymy|t}!^t*pG{~_aXx3XQ|6#n(0BzU~S44t4 z*^z*jCPNQjK#!BaEKgtzFns&I&}?sb{Jsz=0C;CX$b@`9%{_@!bf>i~=!&SZKRM== z6Grzp4lt6Unt^^1D!3x5%?+d}X>(d4OF1^nq0?L#varC0KFtlu=+m%iVLVaj4+j@c zvfp?|!B>rh6E8EXW8vsUq239}MoJ1_DBKN;;Br4ug{$xk8L(#8`@>C(0*Lq=s9H2S zz+~V|KS}{bF+pp4*o35q(Ud_9#8K{Yv6)Z)0-r8QA>v(N*y1Hl8Dodbv7FNy>62n% zGb4fO#bL9Sw{y+il6Qpx?(YkSW~^&ROO%?~-o2omb4F+SoMtfo!gN3pDmk^XLr!hf z$}eZ5HvlhYNNR!SkOg*V_#^~=Ts)e{34)`urPHQ1Rf#Y&Uyor;87CFeCxY@gx$=5| z+WW+ciLKpx$AV`h4Rv8dQo~GSR;=}cku)aN9~nVo+LhzrqwG(Jnad!tBnv+d7cLQn zCb?Z#@=|Yis6JSMa2zuS>z%`tMI$*3^St!=neGGc3d4u7OUL2yC8V|KjM?Ghad>S> z5IJ~;$=op1DlpsXL&7c{KT~7v!9;EtQvR44_8IJ>_^M}6kTYhA-&w9^(6u`nkqEc$ znTV`+>t2FLvs?E@)sVuin^GN8xOZ3l_tgLQ>{1=7#^%DUIexeas&Zsw_GK6qpF~GO zLU$&!h>jl9;Xc}^tz$IR{cJ6xi30g!jE0d>#(VQOA#(-%geg<798;ugC4+$te6#nb2j1Tn7$ zcs~ijJKDZy*d*kv8y!9OTAhahxxvoAo@131rj@h#_@8hS_9vX5D<<5cAf1~Wj8HB^ zW`+zYKPxiucPnWVqoxCDyz?-|XFLKSIRTm3}mCi4^~p7 zUE##TUDhJktD^Rv7UXJ}>{8w@hG~bC@J}mgLu;$uB~*v}-+O=2(M$01@(5XSx5XZ% zD!Yw5L;`w@d=s8mNzQGra5md_g>}RH@`^rZ_4}@vrnyI{A=NL;($IS~PFBW375cTL zh25)hKxy|^na0qqoz zS5#bO{ME|*QFFTQt+HwK4i}h(`UA8;-%Z;Bfu3f*)FKPZhjNN$QnD^lvsf~(Q#orc zMlj@MFzf6O`thOUU7#q3NfuIZhR)<@JPI^nhIMTK$aMk3Yrl35 zzR6D@GOt7Nst}pq7yTmIdX-JGVQGnWL0O$;)rT5zp^#bXQd&O4l^y|*^ta?KzP{x8 zvg_C|2EZ_um#0<_4-Y@dNv5qEMaGqF!Umk-%V<3iSY9QI&O0PK?6%3ZXQiwWlsE#N^wKtf^2*6Gl4UE%naKHv-kbhWQYD@VsC>ae}jlm}094^>$ z;wq@OoBpIymW4puu|}OV2hPzn>|c!s4&36d^VPfo@O$^GTO1>s@%=igSSW1n9=ydV z2Qs?fz?wDV{T|+EeUpK5yRgidmx3BBQ`wi%MP$Ad)-apm;m>P=WTqRcvnBaJ@ypjY zR4YN(4Qr&ovTn76NpX>A(l0ldnf8Ff4OJAuxE_Samg9`_D?*ywSZqW91^M?a3ljeG zE!6vl#SJBGR5ILkqa9a`L((=rLq?7?awlAQy-G~_$)fOjdvVhbY zIkh0*#DwIBFhmNEttFS-Fal?z7&FGWI^j}$)%=NxVrIS)YR^&!EN zBSBrS3I$)#iOh=Gmv{GEe6NG2)PQnqMV9=ADFrZReaOghUn1OTfuW2&QIq-v`KQ7* z2gdCHtXv;3!uoL+m~H_dVqW2M2+wu6Dz0fPyuCi#cEXoPSUnlPX!wcLn}|q+v!#&% zrT3Gc2rL+rnJ0gV_1K#nRHK337N|!cu^aSeumhlvo$h1=C3qMiWtQBCcsT}0U<0*K+R_gC#JtAZjh_9gYhN;9x6P&lf zKPca1bYefFoRMwwWwn$oTTTVA<wK-@0~`--#-YgIf+x2AzoOLfjTd7<)5kT{%DA&RV#~7Caz*NCf zh1xM$RrCx-ZyqTqd;s^L+&Y-H+1qJjX(OJ8q!F!!ArNkZyEpH%_Zm-)2I~2%EN0}UZ%h8C$uka{&2ZjEK#gLow}6sepehf!8KUGTDg}&(?OXiqKLr}TDF9`B zfZ_1zmVglj-#AI%7pl+?xTBD{sNfscHW4~)9iJnZ0UD*Mo$m@;c|4O>lfW5tIU>Y~ zAoRGB$0PR)IY`-s;kT6pW^!aA__y0JV-IFV;aB9bqMXm*CY>f0j5b&vzfp~$bG)o}QSj0FDtgULHW#BPD=75=%I^K|3fTt=Yl%vAk(OxT zTwkCkQ{Qr=4Z_uXD_Vm5K>7USCQ1T7^#kpzh*i8RVkx=ogP&npN5C!0{fB%J=-i;J zs}gb!7PLq8DGY&a+rngt$R+a7F4zUAE~bx}hvG-Ti4Eg{vo>9*X2U5?s>f=299Rzm zz}bLGV$x82ZP{GVv>+J-fSGeZ(_+0gpVIvJ7^xEWX+?oVdXaNzC~OG;Npk>wG8z7& zvgxS6Rp}Wr3A@>QkVz2Umu~Q+GHyTwu{EkAYNBV^^VFYT*`wdYCeQXUFXBgY*% z{K%o{7bBg?OYMRWw~K_ApK$+ME>$RHSK-I)0W!h%!)M#p80Mk#UJ`Oot&z_y1uCzb zjjG#716#65CcWVMMhl8>6q)!cBO5liSK(sB;h~CKD9_pS!*Xk?a>DdpJvp}&Sq)63 z$&l6B6a7kb_Kv#b3mVBj1D8ADzP}-low5(Sy!yf~7fpPZ+QB$1Gx2lNdi{)8ZdR|K zVFf$?7II15A5|a|OquMNnMJCwA{y}Fc_mtaWOzv9NP0DL@NFH^%D|r7jnZFey%qyi zt~by}m~kl@DZ(MAbvT|50L#|`I5z^^Scon;beDhs%Z{t`(eIr=QSH>py{dE+`z*dN z(g9{uaQUpab+$<0pE>0&GcfM5J{Y~zeRf0|)o2!@o_Lxfu81nf^W;=u8B)xZa%u#k z&Ye459;;>!RQmz6EFmc`VR{7UG78Ls?tgCJxqgjSQu0@wb{bUf3`$+RdO+c%7goxa z6;+8H^y(UuVuNKBsUfKa1un35d*`)QqK^sD$E?xE z2w-PXKSo0Bq9>%PYJ^&(>;?*+ewdB%h0k_*&YMdA^;nHdjo+Yim#36mhwg9ib89}c zHiTQud9LO&>l4KIa*e3X?TT}UgSn#L8AKS?FzZif3=u$lvL1uyccNkv-H(8~V8ZU{ zgsKn9cTbu*^BxtfiUp0$-Ao^M4{4`Zb|d+xX1rdI+`>c#{#nx|1|i1TCQ>yKH00c+ z+^ZKkA414aIdu6_(^WY;UuganYG>e@oVM96rYtV#+i0E zWUjzLihR)u_}!jBqTomP&z>w|+W^ei8&06N_U+}!PyV1=!r-WF;Ck9)?D&AMw2j|$ zOl_qf9tOP1-19KR8BQw*jjLW4cUpn2z_9E8Xl%3AEAiCluMA`0@*JQvB^aWh=TanE z2qIZwD0^3^02tb+SLv#+J$%tkmnt|vNM6+joU}o-gcv9nxwD<+H!OUcBhcggr z_$T}hjCQo0^+9YW+qRZxF0 zmk{2CHbogu_*s&&a32P*@elOUEvDjITP$w|3)|GE>%>^deg7UR@(7h79Nx+QC`UfiY%R|;D{?7;O%s(+7Opg7Lvii+R@Xb_U z?F`-BvjOX47i; zd2=kWA`!lC&g9t_mcrzggM1cGtb+GjX7DC&=!Gt)JbC+*s^BN5Rzz$*&D~!P#7!qk z&T4IVrPbD+@*1;sm)OxrNShX>99{5LSL?Cz;ge{{4S62bcD}Ze~QT$mcm>i zx-?X8)Fs0U6?_Q_sY;mq)>1KLfg@Oj%-*&K!=qpCP^ z+%ZeUQ_wFWmsWCNlFAhUnmPnvAkUw?RE4}FMVLgYI?Celr5b12SDO4wls`oqmbZ)- zQZ4dd7{jgRI0+DMKkKm#2)<%UAKe#JAC<-VLKo^-r>*Nhr;w-9U;7JDWx4hb-3aq z_w6Y?XR@mwU(NM7i++M2+EB_6dpaD?y5j!flS;qKHZ0+7L5CKTeZ4Qs{plr!;fX#)uf&v+ogH7Wx6^9HgZ6Ypw3Ej_w~QJ(SXPqO@uKba=8STrh2_iZ)GR z_B4FggA<7|`1G`S_Skzq4(WrxkA)^WO$?o%5UJCw1h4rSCDtu+PrIl)a| zPYD{A@b|g!I1IT@8^TfeaCtbeVm#A@fJ(SB6UqH;X7sp@yI6;`>6Atzq>0!YPcsEqwoEkrnxZ-5`eP zPrYV{9&u#g87_zOB+C$vYT2zrj3cEwsze7M5j%fH*^9%N$vw;hw4w?I41^)r8aEaI zJgjNK3fS~?;&_Ko%dU1bBCppv=cJ{Goyb>d_?BxIT8+}lu<@O06QJg(< zdKr^59lCrUD~n%QMonSl8gxZ##TV?36=fQPH5fwK-5F&i=Swb>oW5M_6pG*m^c#{O z<*^Z|Nq$P%W!Ec#hUWK$>jWs)g&bfM4~rkGhOsf~R`)A`jTn;i&8WEPXE;i#qE(w3 z^46;wzMxKNgzR(-q{eG0Smv0&4KMMZw{U~D8KIs3l z5i}k4Jqz(%`>mn?tY!qW)YPYNbR@um{d^gM3p)It`{NPl-dxAV{69v39Kk7U0eSs2 z7iIxk&DI7hbo0Tg8^O0I)>eXO$C_RjUcfS*N{^Wx4NWK#!N<&%(KG5*o9Z>l0GdLW z;Jl8?{!)V2xg7?-6cNF@J7&GSLJ&z3C>-#`x5ChY`@z9MfSDf(v}YR8vE^&j=fypR z+HD`}`h2lA`)*V3Sa#Qb=>2N>CzCVPR2UP1PI{jE%cvzc<&*^d)^*6soi>!h%?mY} z139N26Yxoc5b9o)%f5Y~lS+&@LYyMenJ?PxXS6q`fW^t|%6V^?g(H5fT;S9bL|nl$?HHGPiY8UpGm8p^9}Z5}Iu zhu-GIO-jh_%{MD~dTS{&G0#jnuwDga)X(hP17&c#&(kVfgqhtjZ|n$I2P>rKoE=Ts z?4}*hDBDV6?O{bcn9aoBW73vuUPhb6VB&w*5BVmCuDSMT*a2wRIX)l|o>EwuyqH60 zg($-s%>w}hqHE7tt26?1j+}>Xr6hs3>>*0X$d%(#vF9l1h&DhVs_mzB2k7N4CK{Zz zL%a=on}@21mND1hgxBGOl>^LsohWm6WT_ljOurKUeWXs0SD9=t)aE%=#^=lLlvg-O zs?-j7-J=gu@!fJtd|yuaAPCs4TV6wWO3$@97Oea#U~%bh5t$%G7D^eLC2H2o9T**- zSfs7F`^XC&Z8s#<^JzO~!*Z&TFk5v`erqk&TWKRP741^bWF3U-rhOOzw7lESzv=;h ziZt0GkU0_*vKs@$&63`sew~Ro1jY`b3}(-C*$2JR#q0%QBq-WEo}GQTm5-tVVZaGp zmkX3Ydrd_onp*4$Jgez;`aw6;G+IMTCF~JKwz~|8iZA25r>t>=bzL`73&+OZx(x>v00J3Bgl-p(T;XTq@G8u6X*%iqQmYh{rCPDw;hg79595j(Nt(z`fZnBrQc5P2z> zpp6}Z-z#up?l!3XkixT<9qV}eVK+gf3E-N)!imXoQ1e%CMq*C25^XL!t!+_N_^{Mq z4MLf@%d44`4k%l6gbhzsS4VtuuTk2E@!I%W%v{LkimSh>J#U}u`Go_DSM|0D!kvtc zYU0){R>k^-L#v^9!f7H*9@av-hy5*|FvMxUwdb$V)fdG_>frBxM%F z;NV$XA(2}A8Aj;IblKvQh_Wx454QLNhtb;_s%#9mk){>E1I#x?``a&)~FZf zR-`9t7Ugz6@WDTKb*^;9sudW9Upla$-3~Z?##E2N^|s2)RhUirk_L*I7lnHTEzh{d zuH07AoH?TSYVN#GK&>R~euM3{2b9AdIql+7IXZ5zQxS!?y7+8}s-X#z0GnrbRYkkF zxZpwK>b*yFY1bECWisROKdjTe&$MLiX3`SyNj^*WU%Q7YVJ^IJL70n+GmblBh+iTtK3`*^LBT>o{h)wMKzh|I~ayJw_qvs z46ya1630Yh)e-2m?tASX$ulNC6(2bf!^9p9UnyK%cvpS?Zn53m3$xetDMgqK&Ppgc z(-m%{>KCt6(ph9>l0$0%sK1a$F)oY+P)I}#VpxOVFFHE0WmS<%)1hXg(SP7!HVq*( z)6VuZZKc^j^gm%*@<>B8xF3Cx#>Dmc4z1LDcKHWwJk>Gs1LHUk_v6iX0?e3sxTlP< zd)p-b<#py?4t{gGI{DrZFbyAcFSAF<&XjsyLDItMx@fl zql@Ke0E~oM3j&X%Q*Ks4pmU`oumLWIoj~3pL;Obu-_QC0Y(Lu&H)KdWSi5aYN$215 Q@Jc5E>}wg7W)i6XAIU#}IsgCw delta 14866 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