From 01e4c2bff7eb317b739f12690374a30b8b79b53b Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Mon, 1 Nov 2021 17:58:01 +0100 Subject: [PATCH 1/4] Include bottle refills in spoiler --- BaseClasses.py | 15 +++++++++++++++ ItemList.py | 9 +++++++++ Main.py | 1 + Rom.py | 8 ++------ 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 58530558..d7625a33 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -97,6 +97,7 @@ class World(object): set_player_attr('player_names', []) set_player_attr('remote_items', False) set_player_attr('required_medallions', ['Ether', 'Quake']) + set_player_attr('bottle_refills', ['Bottle (Green Potion)', 'Bottle (Green Potion)']) set_player_attr('swamp_patch_required', False) set_player_attr('powder_patch_required', False) set_player_attr('ganon_at_pyramid', True) @@ -2272,6 +2273,7 @@ class Spoiler(object): self.doorTypes = {} self.lobbies = {} self.medallions = {} + self.bottles = {} self.playthrough = {} self.unreachables = [] self.startinventory = [] @@ -2315,6 +2317,15 @@ class Spoiler(object): self.medallions[f'Misery Mire ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][0] self.medallions[f'Turtle Rock ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][1] + self.bottles = OrderedDict() + if self.world.players == 1: + self.bottles['Waterfall Bottle'] = self.world.bottle_refills[1][0] + self.bottles['Pyramid Bottle'] = self.world.bottle_refills[1][1] + else: + for player in range(1, self.world.players + 1): + self.bottles[f'Waterfall Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][0] + self.bottles[f'Pyramid Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][1] + self.startinventory = list(map(str, self.world.precollected_items)) self.locations = OrderedDict() @@ -2434,6 +2445,7 @@ class Spoiler(object): out.update(self.locations) out['Starting Inventory'] = self.startinventory out['Special'] = self.medallions + out['Bottles'] = self.bottles if self.hashes: out['Hashes'] = {f"{self.world.player_names[player][team]} (Team {team+1})": hash for (player, team), hash in self.hashes.items()} if self.shops: @@ -2519,6 +2531,9 @@ class Spoiler(object): outfile.write('\n\nMedallions:\n') for dungeon, medallion in self.medallions.items(): outfile.write(f'\n{dungeon}: {medallion} Medallion') + outfile.write('\n\nBottle Refills:\n') + for fairy, bottle in self.bottles.items(): + outfile.write(f'\n{fairy}: {bottle}') if self.startinventory: outfile.write('\n\nStarting Inventory:\n\n') outfile.write('\n'.join(self.startinventory)) diff --git a/ItemList.py b/ItemList.py index c0f2f86c..9e0b0411 100644 --- a/ItemList.py +++ b/ItemList.py @@ -370,6 +370,15 @@ def generate_itempool(world, player): tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] world.required_medallions[player] = (mm_medallion, tr_medallion) + # shuffle bottle refills + if world.difficulty[player] in ['hard', 'expert']: + waterfall_bottle = hardbottles[random.randint(0, 5)] + pyramid_bottle = hardbottles[random.randint(0, 5)] + else: + waterfall_bottle = normalbottles[random.randint(0, 6)] + pyramid_bottle = normalbottles[random.randint(0, 6)] + world.bottle_refills[player] = (waterfall_bottle, pyramid_bottle) + set_up_shops(world, player) if world.retro[player]: diff --git a/Main.py b/Main.py index 75fe9242..e13eda0d 100644 --- a/Main.py +++ b/Main.py @@ -364,6 +364,7 @@ def copy_world(world): ret.player_names = copy.deepcopy(world.player_names) ret.remote_items = world.remote_items.copy() ret.required_medallions = world.required_medallions.copy() + ret.bottle_refills = world.bottle_refills.copy() ret.swamp_patch_required = world.swamp_patch_required.copy() ret.ganon_at_pyramid = world.ganon_at_pyramid.copy() ret.powder_patch_required = world.powder_patch_required.copy() diff --git a/Rom.py b/Rom.py index 2ea49059..fad83d84 100644 --- a/Rom.py +++ b/Rom.py @@ -1066,12 +1066,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): ]) # set Fountain bottle exchange items - if world.difficulty[player] in ['hard', 'expert']: - rom.write_byte(0x348FF, [0x16, 0x2B, 0x2C, 0x2D, 0x3C, 0x48][random.randint(0, 5)]) - rom.write_byte(0x3493B, [0x16, 0x2B, 0x2C, 0x2D, 0x3C, 0x48][random.randint(0, 5)]) - else: - rom.write_byte(0x348FF, [0x16, 0x2B, 0x2C, 0x2D, 0x3C, 0x3D, 0x48][random.randint(0, 6)]) - rom.write_byte(0x3493B, [0x16, 0x2B, 0x2C, 0x2D, 0x3C, 0x3D, 0x48][random.randint(0, 6)]) + rom.write_byte(0x348FF, ItemFactory(world.bottle_refills[player][0], player).code) + rom.write_byte(0x3493B, ItemFactory(world.bottle_refills[player][1], player).code) #enable Fat Fairy Chests rom.write_bytes(0x1FC16, [0xB1, 0xC6, 0xF9, 0xC9, 0xC6, 0xF9]) From 5bac1ba5cc10126a496db1a597b6a2e578f4903c Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Mon, 1 Nov 2021 21:04:54 +0100 Subject: [PATCH 2/4] Add support for Subweights --- Mystery.py | 13 +++++++- mystery_example_subweights.yml | 57 ++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 mystery_example_subweights.yml diff --git a/Mystery.py b/Mystery.py index 73644500..d3d05fa0 100644 --- a/Mystery.py +++ b/Mystery.py @@ -99,7 +99,8 @@ def get_weights(path): raise Exception(f'Failed to read weights file: {e}') def roll_settings(weights): - def get_choice(option, root=weights): + def get_choice(option, root=None): + root = weights if root is None else root if option not in root: return None if type(root[option]) is not dict: @@ -114,6 +115,16 @@ def roll_settings(weights): return default return choice + while True: + subweights = weights.get('subweights', {}) + if len(subweights) == 0: + break + chances = ({k: int(v['chance']) for (k, v) in subweights.items()}) + subweight_name = random.choices(list(chances.keys()), weights=list(chances.values()))[0] + subweights = weights.get('subweights', {}).get(subweight_name, {}).get('weights', {}) + subweights['subweights'] = subweights.get('subweights', {}) + weights = {**weights, **subweights} + ret = argparse.Namespace() glitches_required = get_choice('glitches_required') diff --git a/mystery_example_subweights.yml b/mystery_example_subweights.yml new file mode 100644 index 00000000..1ec43cac --- /dev/null +++ b/mystery_example_subweights.yml @@ -0,0 +1,57 @@ + description: Example for subweights + glitches_required: none + world_state: open + goals: ganon + weapons: randomized + entrance_shuffle: none + intensity: 3 + subweights: + vanilla: + chance: 25 + weights: + door_shuffle: vanilla + keydropshuffle: + on: 40 + off: 60 + basic: + chance: 25 + weights: + door_shuffle: basic + keydropshuffle: + on: 70 + off: 30 + crossed: + chance: 25 + weights: + door_shuffle: crossed + keydropshuffle: + on: 90 + off: 10 + chaos: + chance: 25 + weights: + door_shuffle: crossed + entrance_shuffle: + none: 30 + crossed: 70 + keydropshuffle: + on: 90 + off: 10 + shopsanity: + on: 50 + off: 50 + bombbag: + on: 25 + off: 75 + subweights: + normal: + chance: 40 + weights: {} + swordless: + chance: 20 + weights: + weapons: swordless + keysanity: + chance: 40 + weights: + dungeon_items: full From 59dd62e3ce2aae97e8c017991e293fb9e85c1dde Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Mon, 1 Nov 2021 21:24:54 +0100 Subject: [PATCH 3/4] Fix some settings being mandatory in weight files --- Mystery.py | 52 ++++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/Mystery.py b/Mystery.py index d3d05fa0..9cf0e729 100644 --- a/Mystery.py +++ b/Mystery.py @@ -128,10 +128,11 @@ def roll_settings(weights): ret = argparse.Namespace() glitches_required = get_choice('glitches_required') - if glitches_required not in ['none', 'no_logic']: - print("Only NMG and No Logic supported") - glitches_required = 'none' - ret.logic = {'none': 'noglitches', 'no_logic': 'nologic'}[glitches_required] + if glitches_required is not None: + if glitches_required not in ['none', 'no_logic']: + print("Only NMG and No Logic supported") + glitches_required = 'none' + ret.logic = {'none': 'noglitches', 'no_logic': 'nologic'}[glitches_required] item_placement = get_choice('item_placement') # not supported in ER @@ -163,26 +164,27 @@ def roll_settings(weights): ret.standardize_palettes = get_choice('standardize_palettes') if 'standardize_palettes' in weights else 'standardize' goal = get_choice('goals') - ret.goal = {'ganon': 'ganon', - 'fast_ganon': 'crystals', - 'dungeons': 'dungeons', - 'pedestal': 'pedestal', - 'triforce-hunt': 'triforcehunt' - }[goal] + if goal is not None: + ret.goal = {'ganon': 'ganon', + 'fast_ganon': 'crystals', + 'dungeons': 'dungeons', + 'pedestal': 'pedestal', + 'triforce-hunt': 'triforcehunt' + }[goal] ret.openpyramid = goal == 'fast_ganon' if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False ret.crystals_gt = get_choice('tower_open') ret.crystals_ganon = get_choice('ganon_open') - - if ret.goal == 'triforcehunt': - goal_min = get_choice_default('triforce_goal_min', default=20) - goal_max = get_choice_default('triforce_goal_max', default=20) - pool_min = get_choice_default('triforce_pool_min', default=30) - pool_max = get_choice_default('triforce_pool_max', default=30) - ret.triforce_goal = random.randint(int(goal_min), int(goal_max)) - min_diff = get_choice_default('triforce_min_difference', default=10) - ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max)) + + goal_min = get_choice_default('triforce_goal_min', default=20) + goal_max = get_choice_default('triforce_goal_max', default=20) + pool_min = get_choice_default('triforce_pool_min', default=30) + pool_max = get_choice_default('triforce_pool_max', default=30) + ret.triforce_goal = random.randint(int(goal_min), int(goal_max)) + min_diff = get_choice_default('triforce_min_difference', default=10) + ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max)) + ret.mode = get_choice('world_state') if ret.mode == 'retro': ret.mode = 'open' @@ -193,11 +195,13 @@ def roll_settings(weights): ret.hints = get_choice('hints') == 'on' - ret.swords = {'randomized': 'random', - 'assured': 'assured', - 'vanilla': 'vanilla', - 'swordless': 'swordless' - }[get_choice('weapons')] + swords = get_choice('weapons') + if swords is not None: + ret.swords = {'randomized': 'random', + 'assured': 'assured', + 'vanilla': 'vanilla', + 'swordless': 'swordless' + }[swords] ret.difficulty = get_choice('item_pool') From 1b1292f6b8f5c05ce0a3511dbcad42a872adb50d Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Mon, 1 Nov 2021 23:29:06 +0100 Subject: [PATCH 4/4] ROM fixes --- Rom.py | 2 +- asm/doorrando.asm | 1 + asm/doortables.asm | 2 ++ asm/drhooks.asm | 3 +++ asm/gfx.asm | 10 ++++++++++ asm/normal.asm | 15 ++++++++++----- asm/scroll.asm | 6 +++++- data/base2current.bps | Bin 136114 -> 136201 bytes 8 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Rom.py b/Rom.py index fad83d84..1153da24 100644 --- a/Rom.py +++ b/Rom.py @@ -32,7 +32,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '11daec4f3e1afc96cd044585dfba9df8' +RANDOMIZERBASEHASH = 'bcce69ecfff6c169371afc84193c0402' class JsonRom(object): diff --git a/asm/doorrando.asm b/asm/doorrando.asm index 807095c7..8cceeba0 100644 --- a/asm/doorrando.asm +++ b/asm/doorrando.asm @@ -9,6 +9,7 @@ ; Normal doors use $FE to store the trap door indicator ; Normal doors use $045e to store Y coordinate when transitioning to in-room stairs ; Normal doors use $045f to determine the order in which supertile quadrants are drawn +; Straight stairs use $046d to store X coordinate on animation start ; Spiral doors use $045e to store stair type ; Gfx uses $b1 to for sub-sub-sub-module thing diff --git a/asm/doortables.asm b/asm/doortables.asm index eae16430..90678ca3 100644 --- a/asm/doortables.asm +++ b/asm/doortables.asm @@ -681,6 +681,8 @@ db $00,$07,$20,$20,$07,$07,$07,$07,$07,$20,$20,$07,$20,$20,$20,$20 db $07,$07,$02,$02,$02,$02,$07,$07,$07,$20,$20,$07,$20,$20,$20,$07 ;27f300 +DungeonTilesets: +db $04,$04,$05,$12,$04,$08,$07,$0C,$09,$0B,$05,$0A,$0D,$0E,$06,$06 ; ;org $27ff00 diff --git a/asm/drhooks.asm b/asm/drhooks.asm index 1d3b485b..f067dad4 100644 --- a/asm/drhooks.asm +++ b/asm/drhooks.asm @@ -76,6 +76,9 @@ nop : jsl OverridePaletteHeader org $02817e ; Bank02.asm : 414 (LDA $02811E, X) jsl FixAnimatedTiles +org $0aef43 ; UnderworldMap_RecoverGFX +jsl FixCloseDungeonMap + org $028a06 ; Bank02.asm : 1941 Dungeon_ResetTorchBackgroundAndPlayer JSL FixWallmasterLamp diff --git a/asm/gfx.asm b/asm/gfx.asm index b22fba62..94cb8848 100644 --- a/asm/gfx.asm +++ b/asm/gfx.asm @@ -45,6 +45,16 @@ FixAnimatedTiles: + LDA $02802E, X ; what we wrote over RTL +FixCloseDungeonMap: + LDA.l DRMode : CMP #$02 : BNE .vanilla + LDA $040C : BMI .vanilla + LSR : TAX + LDA.l DungeonTilesets,x + RTL + .vanilla + LDA $7EC20E + RTL + FixWallmasterLamp: ORA $0458 STY $1C : STA $1D : RTL ; what we wrote over diff --git a/asm/normal.asm b/asm/normal.asm index 3bdf9622..13323d88 100644 --- a/asm/normal.asm +++ b/asm/normal.asm @@ -150,15 +150,14 @@ LoadRoomVert: .notEdge lda $01 : and #$03 : cmp #$03 : bne .normal jsr ScrollToInroomStairs + stz $046d bra .end .normal ldy #$01 : jsr ShiftVariablesMainDir jsr PrepScrollToNormal .scroll - lda $01 : and #$40 : pha + lda $01 : and #$40 : sta $046d jsr ScrollX - pla : beq .end - ldy #$00 : jsr ApplyScroll .end plb ; restore db register rts @@ -291,6 +290,11 @@ StraightStairsAdj: stx $0464 : sty $012e ; what we wrote over lda.l DRMode : beq + lda $045e : bne .toInroom + lda $046d : beq .noScroll + sta $22 + ldy #$00 : jsr ApplyScroll + stz $046d + .noScroll jsr GetTileAttribute : tax lda $11 : cmp #$12 : beq .goingNorth lda $a2 : cmp #$51 : bne ++ @@ -338,9 +342,10 @@ db $d0, $f6, $10, $1a, $f0, $00 StraightStairsFix: { + pha lda.l DRMode : bne + - !add $20 : sta $20 ;what we wrote over - + rtl + pla : !add $20 : sta $20 : rtl ;what we wrote over + + pla : rtl } StraightStairLayerFix: diff --git a/asm/scroll.asm b/asm/scroll.asm index f66918c8..435b5c98 100644 --- a/asm/scroll.asm +++ b/asm/scroll.asm @@ -168,7 +168,11 @@ ScrollX: ;change the X offset variables pla : sta $00 sep #$30 - lda $04 : sta $22 + lda $04 : ldx $046d : bne .straight + sta $22 : bra + + .straight + sta $046d ; set X position later + + lda $00 : sta $23 : sta $0609 : sta $060d lda $01 : sta $a9 lda $0e : asl : ora $ac : sta $ac diff --git a/data/base2current.bps b/data/base2current.bps index a0d85faa0dc758ea3e0d3f04179760dc1e401269..7d88566a4996c6ae95d21586ca043435130550ef 100644 GIT binary patch delta 2460 zcmW+$dsGuw9-cdyJO~Mdpcr0t7}RNeW~mRXtfkaNMAZ7MMWpE#TiWQctMnnYa3^8F z2q78nh5=GccuX}=1dLm?iv?}Fg`Tp;-PInqwY9QPiLX&X)YIw?{bzoU@80izXYTKQ z#}2FPM^s%IU`bEq3q?i!Zt89|9SWI~p_{6zrXfJhY0pD0L>V0P*7AC4xr6QkX48=U z{8z0cyzhsV5LRYnugLFJum{n8yNmIM`{6^y-tZ7phaheFiuYP!JYpiZ7^ z)xh6jB1Bj@>Tx9-P1C^aSZin7>-(l zG_p+aZ?sWXF=JIVy~58?tz~rR2YX8eZE|x|v{d}cQ46GiNy?r@k7WzMlE8doF8~(; z&GLmf_!@nuya+C#qpG|JT{ZosKs`J8tEk!40YljH0E`53)h_|CD{w4)9thJh4(do1 zjXeom)qEoZFQX1!I%p2u(%pC(Yz%zAbTa^E^oM0Rz#J%9_VHi9jlj%~hf7M&GXgds z3#!R-XJOY$)(2lG6nuy1f4&M8Ebha|-xvWHp*XEo(f34D3V^h7P%eej92sweGIEJc z!qcKmR4-bELO*Ufsx%1?#kd&?zd zJ1YdAF>)X>s4lotaMeGZ{h1vyOI#*C9A%BH#ODLVjbwu~^ux#+b5gDzq8aP-u)DA$ zp9mIo>>@-pOQD73m!vbv%eFQlYCtb02+afQgSxY%-{95Cf7QZCr1!RshOt+$pjz-r zDB)iW!&`6lVIsKJJ8Gk|_^kfz=!0M4OMkJA&4qc1XK*uiKh6-78oaNI6S1sL9MVB$ zA&Q+~Eroo_oL`${!YxZL^#!n?Y=>KnC=6<{Jju{OuxkrzR)#~j<*1}lR@3LVe3DOy zVS|&E^Iz5bpd)AsxM{!WG5Wpy&^WWN;Qr3?(F$&WnPc;@MH~{Lx)RkYFh&lg2~9*knhT z!l8B6qIID+Rcbq*#AWjvj`Vs?$I`9G35;vkNC_YJsh55*g>&W5FbfCIGJsfP#vq32$nhl{2M8y zfLvNh-aSzKs2y40@JIQ?cj8;tBj!fNVl~H7 z{xKLXy`oGh{YR32f+bsW<@F4X_SB%T*Kugi`TIeSE(eE>oP$OMO84@+(Zd@lAOp?4 z83&f3l{b?$dk)kKc?o1ij=ZZ-keV--j)P^^SHu5|g zy-6vwEsV^QhoWw!l*XDL49Z#gsUERSkT)6>adC}timRlV$1@(Veoz~^Z z**wOGFjzCw$618#;QE%#QawDhR$y~~LNhyWZ)$D`=R03c)I(>Jn;fVb>9%-gv~mUA zBV88n+k_tSGF>!eqyBq%Cmsz2qOA~uN4@h+5cj@Y^|Pi!LztA=ZN1n%+R_ECpU9akK@Q6oT+Am`S;ehldbfc-n7KJ z!OJcwkL5_cYZ4Y@tglS*#ne-$2_p=K2?Prye}r0r@a`{nliOU9OL{kT2UeYIoLpd# z79dR%E2_PVe3Tm+Bm*>~(e^DTFTCXvWRm%r$$c(?ByBEZIz|qn>N|-U3rsU5dkA}5Q_^+sgN@1I*pOokyD-DTff8(kFM$f`bBrYBh`1 zWlK`wWpf{fiBCIuCYiT7&LSAgAE^Hh!APEuG0@CFB);V*3WW z=VP?#MSDF4f^_Hdd+jFvr)h7ZHDfTUk#{!pMZ4gc)~`!Wn?$k^{W#`|_Sd5QjG)AV zvK)V{KkDhu^l-$CPbZVyw4wBmZu&^CS)}V%#Ps+3$7e{fMY~vRQjzbYzVVrQMXKmo zW3xy0?yt?-`H4e8cA|;#jObt7o_BN&oJ3i~qYTJs__tBc{keF29V)(`2!>JJ{k5~{ z&%48=VeT3$iStUIN4w!mCc8b?hjkueu*09$7n@n1qnhAT%t$veH{unAhdpbWrVV<3%J;$>hW@}qs#7P{2!--AQ}Jw delta 2460 zcmX9;dsGuw8lRa=9)!dY&=@f4I8eu^RF)P=wXI0CrHGAS0`q{=g7`d0z+gYVRNPFqVxIZNiimoSs==PHUn zC&+4_*<;-R>>rj>xRoVqpbOI#6qivrV9C7CTR!n%z3s9yJHb7q&?&go5=g$E^=6m=UMWRjk?(Ubi*jT zsG?ou3s5b28SR1_B;`rE8frz*MIJ4qM*eHJms2So7by`pzq!cSVn9S<^WlVK5sHTv z;d@cEDby@|7DJE1Z{%O0Ct$lGZ^|{6`h+LtlQ%_`tdM5-qj}o~qAQeNUi#51x8B~vI!12W>Z^0x10 zfyPotVaFCG05&M#n*`eHxGY;!5fomsx@S z|Ddd-j@fy{QAQ&$-pU;)NB~}hZ))vE{9Ms)nq@G30apyaAIMGIVddrku@~0YsBPh} zCZf#0k$*EdoB4scWfqC0a7MU(@D(go3XctDqnYr>!PW0ln_~e?%8dmBZ=BhNM>Y~y zZLPdE94i?3rtrE*%q1eI_vaq|RShPQo?A8w#NR+7NcyKX@Zn@u!23O z_DA2I2EvC?r!*LY0^BtGHex1L9tk5tQdlk+!lnLbVWrWN zR{Eyj+NduVxhyJoYB@LDTYO6bWN^udLAO0w3;dhsYXML42$RT0$yl}HC_FH-Td}`_ zQ?t!`SvWMZPz~)cqX8D^y>?=v0?xVfT&V(`0UiYq;+IOkHJg!%8o{zn12RF)$dvkm zR;D$8I?g**Lx!z%9356awd_NFhm3!oO9fA^ap*@?~~IJnHQAl z3kZe?-q9D33<>mb?-6@!@Cj!+mJFu;Srd;WR_Y7j_jgpf*;V2xf{}}ApIt?KQ&IG! z4eq4fcMYf%&b;fuQg*?!cQY4%?PSQ{D2NuPEzZS||C$pVXNYFA)JtQ~wSj zbCGq;v8A!#_!^$sd>y9wALP61qq+8{X2t?Xqlc)i9z199CAGNn&kc51{LkaDfXmgy z0Y>}nNfQeuly12dD;P#QqdX}f?w!62HYdO4Xi^*(0$OWS zTHrpI47_IJ1Zo;UxR)-}JoA7%{MSEz68el2$SEEn;vs|aa&aSO;@cTD$f+(LuAOMW zh!-G(@G5SlVcd(G1xXM=xy3uu#;!8>%@lJt(#J@Kx)1g-^2rp3MP0PQVLc<|apG&| zP`9^_iH1$A%52p5-kbvqACfZDoaumxgU*M9atmX-|zybpm=$sl#vB2#e9+>0Q>JO@W7a#q#>K|ELCZGk{T6u}vztBfB* zZmTD#Km<#B;%EtRh8sVy&h9$_-_5mq5i~-zzxZ=o3U~c4h0rkyv~O{aCT`z5;NzB0 zOS)48;w?x#a=x~|78cXI9EnJB_SXir^V&Bn>$`$6^H`;hovLGJ>Db#khhGYkrZ-_o z5JB{%_m5KgQl*DF)ngVY?+RUifACR~7@Mgyp;1AsgxJ_qu|&GyTWzyzdWvha-agIB zh+_EMSZ3ULk53a*?-GBuoP(KwR&h#LGqw;L;^4)xndnFG+p#tCKj=Oeo!#2gFnE+dZb2;1LYvT*29FqE&ODPAACG4TOg6W~^@nm^y zHdsn@9=N^bu(wma;!bVehv!4z{_*})b*b%=j^4V9-lYO-c6V-%L*t&7N9A?WD?Tmk lJhU8jPs8H&a_UW}0b9DJbHOI`tBZ;+eo07s)1UnQ{{Ucq8Pxy)