From 855194aceaf440db68d32ba898a3a41d805e788e Mon Sep 17 00:00:00 2001 From: duck Date: Fri, 29 Aug 2025 01:59:33 +0500 Subject: [PATCH] Initial commit for tabletop We'll branch off of main game for a cool quick side project idea for a moment --- build.zig | 83 ++++++++--- build.zig.zon | 6 +- data/hand.png | Bin 0 -> 353 bytes data/shaders/basic.frag | 3 + data/shaders/basic.vert | 1 + data/yakuza.png | Bin 0 -> 67486 bytes src/{main.zig => client.zig} | 0 src/entity.zig | 123 ---------------- src/game.zig | 25 +++- src/graphics.zig | 166 +++++++++++++-------- src/graphics/camera.zig | 83 ++++++++++- src/math.zig | 30 +++- src/mouse.zig | 12 +- src/offline.zig | 5 + src/server.zig | 5 + src/time.zig | 51 ------- src/world.zig | 277 ++++++++++++++++++++++++++++------- 17 files changed, 554 insertions(+), 316 deletions(-) create mode 100644 data/hand.png create mode 100644 data/yakuza.png rename src/{main.zig => client.zig} (100%) create mode 100644 src/offline.zig create mode 100644 src/server.zig delete mode 100644 src/time.zig diff --git a/build.zig b/build.zig index 642cbf1..8a850ad 100644 --- a/build.zig +++ b/build.zig @@ -11,42 +11,93 @@ pub fn build(b: *Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const exe = stepBuildMain(b, target, optimize); - b.installArtifact(exe); + const sdl_module, const sdl_step = stepSdlModule(b, target, optimize); + + const client_exe = stepBuildClient(b, target, optimize, sdl_module, sdl_step); + const client_install = b.addInstallArtifact(client_exe, .{}); + + const server_exe = stepBuildServer(b, target, optimize); + const server_install = b.addInstallArtifact(server_exe, .{}); + + const offline_exe = stepBuildOffline(b, target, optimize, sdl_module, sdl_step); + const offline_install = b.addInstallArtifact(offline_exe, .{}); const copy_data = stepCopyData(b, target, optimize); + + b.getInstallStep().dependOn(&client_install.step); + b.getInstallStep().dependOn(&server_install.step); + b.getInstallStep().dependOn(&offline_install.step); b.getInstallStep().dependOn(copy_data); - const run = b.addRunArtifact(exe); - run.step.dependOn(b.getInstallStep()); + const run = b.addRunArtifact(offline_exe); + run.step.dependOn(&offline_install.step); + run.step.dependOn(copy_data); - // Why is this not the default behavoir? + // Why is this not the default behavior? run.setCwd(b.path(std.fs.path.relative(b.allocator, b.build_root.path.?, b.exe_dir) catch unreachable)); if (b.args) |args| { run.addArgs(args); } - const run_step = b.step("run", "Run the app"); + const run_step = b.step("run", "Build and Run tabletop in offline mode"); run_step.dependOn(&run.step); - const check_step = b.step("check", "Check for build errors"); - check_step.dependOn(&exe.step); + const check_step = b.step("check", "Check for build errors (offline build only)"); + check_step.dependOn(&offline_exe.step); } -fn stepBuildMain( +fn stepBuildClient( + b: *Build, + target: Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + sdl_module: *Build.Module, + sdl_step: *Build.Step, +) *Build.Step.Compile { + const exe = b.addExecutable(.{ + .name = "tabletop_client", + .root_source_file = b.path("src/client.zig"), + .target = target, + .optimize = optimize, + }); + + exe.root_module.addImport("sdl", sdl_module); + exe.step.dependOn(sdl_step); + + exe.addIncludePath(b.path("lib/clibs")); + + return exe; +} + +fn stepBuildServer( b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, ) *Build.Step.Compile { const exe = b.addExecutable(.{ - .name = "spacefarer", - .root_source_file = b.path("src/main.zig"), + .name = "tabletop_server", + .root_source_file = b.path("src/server.zig"), + .target = target, + .optimize = optimize, + }); + + return exe; +} + +fn stepBuildOffline( + b: *Build, + target: Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + sdl_module: *Build.Module, + sdl_step: *Build.Step, +) *Build.Step.Compile { + const exe = b.addExecutable(.{ + .name = "tabletop", + .root_source_file = b.path("src/offline.zig"), .target = target, .optimize = optimize, }); - const sdl_module, const sdl_step = stepSdlModule(b, target, optimize); exe.root_module.addImport("sdl", sdl_module); exe.step.dependOn(sdl_step); @@ -58,13 +109,12 @@ fn stepBuildMain( fn stepBuildSdlTranslator( b: *Build, target: Build.ResolvedTarget, - optimize: std.builtin.OptimizeMode, ) *Build.Step.Compile { const sdl_translator = b.addExecutable(.{ .name = "sdl_header_translator", .root_source_file = b.path("utils/sdl_translator.zig"), .target = target, - .optimize = optimize, + .optimize = .Debug, }); sdl_translator.linkSystemLibrary("SDL3"); return sdl_translator; @@ -73,9 +123,8 @@ fn stepBuildSdlTranslator( fn stepTranslateSdl( b: *Build, target: Build.ResolvedTarget, - optimize: std.builtin.OptimizeMode, ) struct { *Build.Step, Build.LazyPath } { - const sdl_translator = stepBuildSdlTranslator(b, target, optimize); + const sdl_translator = stepBuildSdlTranslator(b, target); const translate = b.addRunArtifact(sdl_translator); const sdl_rename = translate.addOutputFileArg("sdl_rename.h"); return .{ @@ -97,7 +146,7 @@ fn stepSdlModule( }); sdl_module.linkSystemLibrary("SDL3", .{}); - const translate_step, const sdl_rename = stepTranslateSdl(b, target, optimize); + const translate_step, const sdl_rename = stepTranslateSdl(b, target); sdl_module.addIncludePath(sdl_rename.dirname()); return .{ diff --git a/build.zig.zon b/build.zig.zon index a480fc1..4c61daa 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,8 +1,8 @@ .{ - .name = .spacefarer, + .name = .tabletop, .version = "0.0.0", - .fingerprint = 0x946ddccb5911fb15, - .minimum_zig_version = "0.15.0", + .fingerprint = 0x9467f2a6727b4ca5, + .minimum_zig_version = "0.14.0", .dependencies = .{}, .paths = .{ "build.zig", diff --git a/data/hand.png b/data/hand.png new file mode 100644 index 0000000000000000000000000000000000000000..d9e507e6bc530a400790233a46dc14cebe2ac43c GIT binary patch literal 353 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}jKx9jP7LeL$-HD>V3hWB zaSVxQeLCHc?~nnHYrWuh4>i97CysDRSf(AHw?J4~J)!6v=w`Oz1%I(R5?$pA?8Dv#Mk461xic|HuLoFr>VZ2vG2vkH#)cWaMa5r9OYdeW)qebalzt3)YFYczMfN?<_TmmtlXNl zbfZ1@g`z!P&Dqa%mn}HPntd^iF)51MMXKgx-IQsG9ElHnPgd)1OFk<<{gM{b69q5! z?x>Yq>q@Kh?52E+keswiPiG-_1)KM`ZUIkw(MiYijaF?5F`48sSL;*St(PJ^ z;@2g;B}(sBdaRwh@v+OB^EU)He*gFXLBY4azlG=i&zUT_F==f?Hao+gZ|3d)Ih;5Y zTfQ{O*X`Nve0To5dGC%|B-GZ{wy&J~Yvuj#)tro9gZHnT`|J1ediLIWCl19W^?yD* z-YZ)x`Qyb2hmKz>?Z1H4*gO2Kz4qVhg}Eb#Vv7LC%n8pDSr`;s1THz3?fDe^V!H!K z;{2N{()VV}%KXCmf5XmaU%zrFwoH)tcvt?_`t&3N36o!PPKI**+;iRkUb*kOV*amg zhrfR>o>yf6nfUU}19pvNy}wrOUmalob+^UxwE_Q^Z0qD;kbY!&;&XtA?Rvg#*AuL7 z-#k~gZL@7#+4Lh<{!iL*!PYuf?#Qbzk=71>n`-!KFQ0$EfByW&)fsJ_G7QTaoi7?3 zReFAvw{lDD`Lb=B=f2HJujYB{T6i-(Z0GcO%m+*oB4T2E-s< z35d&BMG)hqu#*517GuU_r{MStHc z&1Ps=-2RM#q5S>&2QL;{-r2i<(w6&s_cv)j_h-IwZRzXtXBZdoiQStq%ad2_O0w(u z)y@-%@JRh_xxX9F1`Krzi*8dU%9tG4-DU| zmRx@$M!RO-%dej{?3IT^i}h{i?de}*bjtpdoy)f-TV75+e8qOxqWC@kDvI)7SH6F^_U+zw#vFI%lCR&_OZ{VC z_s)Co{`PdaMCp!Hw*+}icCduzq#9f%Ont=2oU z{ML8*JDu+QaZ7GJnWOj8T=CfcU3W{*R<5}(BiZ|ZTk@Z{c&R^2*S+Le^>KaGU;dc8 z?Y8RktgB_O-j-kdJYJ2dA@}iqjhU7VpV&-O_li{Qe!K72&d_&fAt^Au`o%Y;+L)QY z#Vq5t{4W({WN3-!UL*DIQ(TQJ!;1AcO{zHdxG=n_u5CDO-#?jyVc{#O8#lgm=NY~` z`da_k{(buk3nd-Ozc5d}zt@V9A;+DOW1r0dhaFoVJo@{;;^*}%g)tAVghB!CYGkd;!U%FBT%6{9QhfU^WxXISgyEN8fpV4d=#w(o+44ZiWP1s*=D&8Qa zs4!#Ce}|JC47U#)G}&M%d;Pp-^V_rKB-=xo&6xb4rkX&3eRWm7#JHu)@Y zH+lN=I%h1WgMsA#SDzYf)c$v8?J+cMw$I61uRrBr>z12+r?-Y0@Xpv2*Z&q`_qQ9j zSijCOn(=P__g}BJ+Hs0j$#*||AH6^8PxtrxC1=F#zN~)uzv{;RydUp(8q9ZL{BfZD z-`C0ewM)-^IqN^G##*!H-@mV0|Epc;VzB%0>-+k3f5T5k3SCfH`0u}b{LG_omtLLn zQQwjwMv-m550{Rfo}UlXk4yUdYfbOO+x*)k^We?W>>Wk_OpZM+eD;2S0AItNgRQ3z zw%750wJSPfB9R11S(|N_l?DF~6`TJzU;5?aS@pupY#3tG{vVvHx%mIZW^v)kOiOOZ z%$s}g`04%2S!ocF()8e`I>_0?ZOo8#vT z6g>Ug?^%Cp@&CVjPYO>l3%~b&#usk2_0PVXRJ5C4FWM0OzMkh-sI2k(zngOZoK9)5 z(%kTUL*d{0(4-6c9b%EU_ix;y{q$|PHq(i+>0i3P|9`VI+jGVLo8H$yTwR+F!jm{a zMcAabYg8Rt)fjx5xwq=RuaNxWIOSdH#rN<6-RJG4XNL^F-~7MoMQQ)nT_8U^ccjQ4h?|*Cgy6Hy$b}zec#hGCEZ})%>#S7#TjXi)Te&E4=*I=Xm!V z@#gS$Sn5$?tC(81?!?~L-}Xm_GO*lz9a#P$`SYSHJJ-+qdf!!)LDB5*Dz>dm3=Jy$ z4OWf;rri79{%yOGU-f^I_<<)M*H31R?+D$mEAKGvUfp`+x4jIX_5bht zzkPidV$$&H=lfN4ivHefe!UJdTbX|C|Nm2|44-YJUD>?1Wq&Pp=6`krR5Q+fo1=eg zot*Zi`@WJegPw-8Rb<%&Rkr;pUh;g~ZJPAh8uSX*-Z$Y?xY1Mp*Gf3| z@_IE^mTl^cyZ-!5_hnjfY`-@130B`ff4iq0Oqk%s^`eUD0ITo6zuw#GOV8F%um5kQ z=DYD_x$i~K29PV*BqE!;l#X|QI5x$$bJDxF6Kdgw`Zn*`Tc)eF{ktw2dd;^jF8k#q zwJ)ogcL$%Jw@j{yr^)&9l7GG3r&qf7?6|gGpFKd{N$0-pg**5ECm75yCuRXB#xmt>(z!QtDjXuCO1UH@Go8sg&=U zH@!6evZ>jpcd61JHh}7U>)SWwR@rh#*S}c(|72``IUD{L ze6?p}_;+_^J@bNREDQ#g2?e!r7aN1FZG{vey!5c(TR{>cw#OC!zJd5BDxLzL>mnZ)^c;VDDRoke)88xWo&R2m z7kibz{NIH;d-pf({vOUXfBtTk1E!UV{Vf7c9`YaV%7?`=O_(0TSz!KSb zzoy^aex_QN`D<|f#Ok2>&A&cRdVSUP`b%xjgoPXopmyaK;oy^NZDLx?jtTL3ft-9L zp6%89zJH6`pA~$$5VN*=ist9%-E*s&b!P?y|ERNd;!sp^yCHvV@f(G$*#Y)nze~Pa z{!b#1_y5tOM~|tQrA&I{cJQ+#BdB>g;nWr0#4}F&!*k+98h)*u|0{IG{9myb+5YCX z3WM9A8!vr-;spt)+NJM*omRV^%{_-f0py+6FMm5s1?gG2fBl!(`kS3yoiYq-m_Wvw z_`lEsYkw)<16JHIRV#AAZkDgX_FseR<6rLnXBEn@>WR*Wsm~LXm>NK>=OwvcDkpFS zfaK>d{8#rnT@>v1ip3{=&iba!C}aH^yr1i9@P6$T_H}<#4fg&In3d(r@F%hO?9W+; zZ0o;0zw_Mg`_A*V@5<};^Z9<+Z}w|mOeey1SG5>Q)tqW{m{-^^F5t6?;d3&p`_GT0 zN6Yu?e!W7&2F>dJ8EmVr`2XGa-Vqemg)jIWZ+3Qh2{SBXWLIX~alZCl@%-O+^6!0) zUvk!bnStANxd}54+pnDet8~TuU#p$|zP+?Q_}kyz|NeAsc)WeT z?3?SlPQS=ona_NI_Free^Zt7!|5o|Od$D(#f6c!7Uw+*`b%q1`tv~1Ye80L;@?GV8=c})u z)Yg5txSXLuI=&{YX8x{!FD5Z`EpItr^UQeu&oj^U?7sIJ&Fis>>wj1~6u(vJ|7-z9 z6+Oo{uecrMUa4hDuz>^h-rmaeuQpZ>K7$QghWQZA^=vde9`=3iH3hF22*_1*=C z)i2~=u)SSy@4;c$vZSlYn?C-oKk;S72Z)gt{l_a}YS%e+{0m>6Q?ztVt5-5g@672_j~-5buZUPu`;ZfwtYd? zy5>#4ij^61n9{b-^ze{oC_CW@-1Y&Fe0{ob=D`-+%T`*RQQ!QS~~jpi+N9 z3X4P7wnWK@7tE9PFuKbbRu@&wIbI1yt7XFHcMB}~pUX5~PT=|q=KuQf)y7{1KCw;B zy|#Yy^FuYS7)#>!|B{Zd|GE2$^NLrzWtG0c9si0~zx=Q2y7y4-_4S*#{V}^X?Y`f) z-T!`v2P!LcT}u1A==-UkmvSY|OpWEv8Hha!w>(EE5l9xN>mX zuQO1e+Q=RLs5Ir{2lI>n&89LdaEHHE`TQ<>SG*+ugsOECe}2c^xBB!IkWSsY0SL2 zhOy67loZPTGHm$vH80`)!?knOzZ-nMr++QGd*Asr-=o>&e|5)9c8~rtedd!3>v0xBEQvd`;ft&3c#)&cW^eSX;`gTW5jT!x889+3x_2` zPSE~>H@_yP{Jz*MextrXo#DZ`_WP0VA9gPT^{BSTNj2QR?>+03AJdLyoPS^aeDpV5 zo#9%3pFwR=#-4NLzOVgl{`tq%J+J1^&)q+VXU5F`*DChv*Zld*eBiKg|Hh45xETs+ zQm<95&5wCK{qwoFU2TiopH2Gu-<|&~b1 zrxx+A`otCsNlZ599{bAaJAsU{Mif)hy0jKR?0Jci<#ly zQ=4%5hxn4e7n{Xbb}-DJ?|k`J@3H*?2UQ$|e|`OYm9ydN>}a`%620})e_buUI^qA3 z59U4$FTQ_py!=akUS$t+_;=OY43j(=5~`IH*dAa1dcR&;jp5Y`kkk0pzLd6AzRijb z{PySOeDS(zjx+YHw}<9Zi~iphdcPyjyK-Foe@kOFs};*Dhvl87lUE&FpLg6z_?PM( z`5Q)`e2?vac}Kuxq5b6_*Kg%`v(!9{+MoBQe$Sumq(8IIarb)v)qMQ__1)-W`(FJG zpFjW3ZL8x2YyD?k&}ZAfZ}0OX#cwP>$}kjE=b4uJYQ($}cm8+k=VeXh|9{KxAN>7( z-(-nD?)+z2zyIS7{qlSoV~Jo_L|gCu|A&uk=2`cp7Mu{C8P89y+4eO(^~2r&Vy^4W z?}#Uv{Hfje|G~LV4~9~whLYLmpU3;!ep%8Go09wUt*&#toYko%4q9Fdlyh|6dR6{j z`u4!pT8o0xcq7gW?C0WkP0Oh-eLcJ3{gIwmU-hFZ6VtCb-j+IKzAWv-u`4}>e=b~$ zn|7hu9A6|})_|&|*zhj4uAgjU)L55dC z4Qlr6^M764@bByPcTeUiZ;9Ri?ytGxPlhEXbM%{Z>@V&8-;^1|@b>?|&~I}p_+otD z-;ax$dxDXnrL+ynOOfhFuT;|J(hZ;lpq91+U{HB0PBhZBi9qE65PZ z*>b8)DNNHY#_$mWiv*RkCnDDy)tNzXW zUgsIk7$D3LHHjfFFK=7a{?5nRn|JKpGAn2Hi&O98Yh$mz)|ab~Z_W4p&E-EaK?Wd7D5AWBP{+%hv5EOYtT`Dk{G9|JD8))~flw++O>A<6pIy*ZPZf+s-fSs>^0+ z=)Q1xkJ(>2tsWMI76BpY)ZePLK@LZ+$^Uv)zgvVcpk-Ud=2=a@R@!sFTK-SZ`7f(7 zgHyWVxlcP@{O)0S0JVNu-|F+eRsUK8{^sA@aNI1=Mfz)SJ*Z1lUg#IS@SkVy#Zbl- zuObaynLv$Ny$ipmTm79P5DjU}mdt;#ilODx0eFjN_3veM?)URINjY5p`~KIjUwuLO zE1cJ?U;lnV-7EgTc23|H=-+Gdv7!u(6G07Xr^_wR<2hnkAGFKAe>-E|JilJXUrjv! zR?Pq9ynnhtfc;nIEBDhu0Vh!7%5>yeh z|K+(J;|k%b3{Yj5^P*Wzk6WQd;F3t`^!cJ+e>~S^%-Hq+sZhf|o@Tk|*0 zX~Iqxh86NvE4EE_?J2!mao+fy@9$T}iY)?@TG#&9u)Z0y{6FW-|3aX#_T5jWF_z42 z-QmU3aKHo91Mq!#`m_F%ukF6y_vo=5A8#DD6n(Ra0!4@J}$APip>O{Y?JHA1%JV z5U#z(7qI_A_}@SOe*ctwF)O#Dl^u&s)LqzDeU&{SOZjiADcSCz`*QIcwIczf!&%cJ6%n#Ln=we6Jfr zgMNIqFhjwso54NDlb0`Cv+U{9yh3R^yMa>*IGYGtAgmu_hcbfd1{j zwPD?>Iyu8v;%6B)Z2M!zkdXE-n&HKt`}Nyf!W`EnXBu*?q2`dx+Adu z)Q|sF+Y4_`Ww&wrd+6=Xt4Ds^FMd?{Dd6_&1zDiVQu421y z4;?_yJHPhPk5f5s&pVp_5f>9v>!}ZgjHY)(N7L(`{>cQ@YU%p1-rqv^?fS~gpz=2I z*5cJn3eZ%Q^zF~@H}$M{KVM{vY5m7s_{#q4o6Mv?+`r@I%RJfnq3QUN_$_aC8qCkS zUtgduXIn8bYjW|fC7?F^w2%4^|G(OHnsY+L*Yu5b#Z!#_y_Txq_`xMD z5OCsH=_Gi5|GxVrDh&@7|6kix;n?Q9`S{sqQ_HXFy;l_3_hmkF!^O*;-;Ue&Z+!oi zQSQHt8uvE-zpD&4KR;yiP5zG5zE#KeyRiH?cKy5kzQ{AL-)H@;4&3wi@BjDzgE!Si zM*ik!kk|jub!SeD+@r(7_qP1&-0)xh`?j)ga`)!3On9#T9n}9gv*$Z^L*5HTRd(N;|U9va*6MvzO~Fzd`*tMxW1lk)%W|? zt}VZmG5`GjSyeNpv2K~W_38HQy>*kn$Iq1Tn8x{qWBR;$hKp|Y9IrkxIL6vda8F}# zIH7X{mPn60_}Rad%OTLI@28RqLHee*CED z@q5n?3+w+rdG9sXIMyKP0IOa7f1{NXQ=fR(G0YDXxEG+z5K=Ua#l&;P|NqCs(+aCI zO;i8m>%1*0tFIM0AjbQ^d;gzrq3ico{7JPuaMSyGkv0Q^U>Ku-1g8Us;t}9kT$~Qr7^yRC5)usMbk%X1 zYISDY-!4>x|`0`CufGAx~=cY zc=heBU&&rf8u#i~GAP`Rzx#_V{_ZceP6msr485D@qL{Y8Mq{*lF0`6E$xjgZ81&-d z2iw(t7|9hPDD-p1&kldYU8+2F$b;U!gATaj*QR zvD{E&xV7$n1Y5!Thn*`JKK?8BwKrIqxJYGz+w{8suUHs1q`tVncmJd(OcOjAGp~Cy zzWQZc%<$sJ8K;=^v-Xh8<|TN3|N1xooz8?ZbkzIVR(F9$7FIGa_(=VaO)`*J!nPsl z#rvHG{qFp661M+hKi75rdpUc4jz3ezL#H+f+3;WQ6}wvRO_kUGuYRf2`?7i}!%-(bqb;K8r9D(CjQ%<8>1 z;H0xev!?Q2>l#=CPwdEJ{h61R%7otKXov_beZl!Z>;_>j?XyG#Oae6EYXNTX z{eJ)HdwwsA!>u3Yk`A#K^j9q9ySk(yQkWt3;`&z;G8pduVBhug_jSX}SG)c#nri#$ z-~V(!1`a)8!Kc0qJ5K%c=TL0f;q*Q}#z#nGra=Ct|4&&QW|_S_fB8}>GslT*m%hH= zf1;efBz%A0y4C!@e*gJ>J@fGY`c9P$r6z|j^|0|VHih{5l2C>jf9DH0d3YqPWE0rM zb>PaC5R=qzP6C_%tvR@gr{VRbu)pg%Lig)C>xTWcI{fg$&HOmkw7<3g{{B8R_1_F3 z&Q&hqtPTYm*)EsZf?6}K6LbFiK3~welFK2Qt*QCof{BfVYks+|mvFdpU;gi+t?PgJ ze(|;6|KFtcdi|FRF_Q!~a9rbTSaY!5?uN##I5sy>oAIOPnR)f||E}-d#?u07jn4nI za{u~(`t6{WSAeE;Q$*{GR|djP9549|zuVjWikDt|Q6n<`ru&Ndpux`HUiZI(ym<20 zFTKRM3>wl&GeoN*(sYw_6hyZjx7Jx(Crsf|GEml zo_TNfznwSk?O%W1Uv-xj1Cuf%hhobQl`YTXIqv?ScJ%oJ)z@2{QX7}mz3P9s6&$9a z3|*6EGB3z`vG7*dlo_wVVXtTS)n4&eJZNwp+@?SBs1Y=(>z~x=knJtXFh6moqLaf) zaE#;z)NlVf-FMw}F{5IJ8$yDfXA?{JCdO4qzFPAW60o6~kACJ`u{)gjx1GyS#gS_! zt9c+9PMp-?jFwSke~uuU^4&6o1c*J@g;MQ78InF85OCsn zslHJ8!mTh?1+iat0g!&ddu_v)<*6H1e?Pf9{q<@F4!vs$v(~A%2smw8@%U%?)&Co~ zKu)++m&E;_y=W2x)5A>yyE|A;NUsre;!t$DdZf`Rm{DZ~I%Gc4waU!x!?EC9Z&HWolKo)ZLD%jlcI_ zI=-e&I=&|C#~)DVDX!X+LvhLdmUFI$wHTh>xO3;ovu9~9whQur`Wnms?GtLQn|*xW zijVP6Y;IeIetoXT7%)-lu*0r>8`kdS3qAa6CUb*;lZR}~lLZ@nxk8Sn|6i@#Id$2x zWpnP|RyE=%`fKO9e_n#twhIxjzOR<=cx{{!W&i)*@4vs_zy2P-m8s!<>HpNPNDaISB1r0QRd^vl5#QNTeR=>`J*UtD9&AYRjcUSAK{q+nH z(RI=c2FCx_e%%#OxbS{ip7e^(|M#CM-MHxW$ylZb*4&1h{|h^DaC+)oWr$#1AW?cf zewKmc|KD-dU_{oa{AOg+aRFPI-F{HyU~o^wcvP>~)m?Rr)yo zR*I0qll%pmf6cD!@4c|Uc0nX$RB`3oncwRe1^yX-SQ@YT<^CS8%m4p(&yoIBe8Ik1 z3EcGE5z(BvYo34;$H_$=+x{;rzOaGILFUD={kzt>vzBnyGSs~JB`CAIj^#K1JD<1d zv6s_AbTuFUUwb&3|L{h=m+TWh{d>LUeowuoL#)gHTc2)sK9~1@r+WPAw{^AOAAhVk z^rODwnIWiY{_RfIt37&I>c5s4i~uS*s;jZqJFAN{kDG>!v9B?a5l_~(YU6{VDw^RrF~ah!>P(ujqCjw7@`9i znp~KUJYp?cDu09PxAd7Yr>8z>*vju_tFl5 zvvj)s_4U1a;UGZ=t>zy}Cq60P{jEPkjrGF6_wlLcJD(r8`sLr-rB_wH#4q*!|9{D) zqv(Ub376m7a4HDsaT|iu61W;tU#7*-d{E)m*8l4!7WhTpUO(-R@R#I$ybIMX9*#Wv z;d*b~*BKXD&pXz=*Saj;?>lGtcHZ{GelNuJ&Yyem_-TA_zn!7+|Fw62uy32rX0vnp zDdr2VUpyM@6tn+_rer#-5%FZ2A<3BFt)y~~?WW$PxGB5j#GZksXoFg{vj5HQV&S;> zU(9hnOThKITaz+&X36ul&zLof%S`a&kLMpdZy9pe|=SH z=GIg#NSN5dMPL%((%Ui8+r<1Bd4A-<*0f&1$Jveu(Vk2=Xs(D8{&NHF3DEi{yj zwR8yo)mV~YzajO{>$rML^%5nWgROO<7nfH3Nwq%qq`vfP_s7kU9=^)s`sjk6zqc=% z!mLx%s~+?3pZT;hRZfQf^=H(M{XcBnzqpsH;O}?-9O?fD%C7yNv0@U70diN_IT$>E z;~}PK``@hQ+kUIaZ0p?PvvR(_WtHnczVhq8tQ-F=zt%qV4|;#m<@Oim+w%|3<==mJ z<7@W~{~w$KuXveU_3%~o+e!a){=IttZ~fc3eJ{-=|Gm2PU%!8U^M0@WhN+wX{(4%j zFZWO7_W#ow3|Bv|*Z-Tbx$a_ZO{&=T=V8H<85&gB@7$Wk*{Jt2{4@hobX>(&l?73J z0gD*c&9Tpo{a1YWlU3?%*2#5OZ^xH>QpRW(acz`{4ApqW`cg~p3uu5iDdzXN_xpF% zeYNgCx;j=Y{Qmw`_0RUk_uSt2_ZwsRucNQ^MH!F0Uhl?y#3TOSztFi=Pn_?i!)C(| znnV=L?C{`sztSK6^$YWi8=$tutOKqp4srD>=0D!`|L(_A{wx1(l|@qv$6WMVCB0_pyullx4Tc%L$Cc}{`dR-I$wq-7r$~g*zC$f!j|KBd_y6J7XTu{r>NhjM7RH`;d6rFE*Re!q8c3Y>? ztrhcs?FOx4=q|p+_0>OXvmRrCVAYK4%kDE5Ob7KuI0{>JpZf?~GSqzC|4Rtu?iQ2= zrjz@EpRek#U%y^1Qspe0Qd?Wg{x!J%#dkq2Q2Xco_J3z&7)qV_uWs9PWCPn|0Vj|D z9g6I}C;m+b^>Obm6%A#`DVParzlC4ne6BzFFKjGXRc^w|moM%8VlFu5#L2%}UU&b! zD$5oBzst&Ay`TT@#VUrB%70&<8_g33_fbwNE&JCV3MxGXF0m|~#IVcHXUY8h!_P7o zf|}wT*{{?V=}K`1sBtC;|9#JIfA981@$>C{6;5cw)H4MctWw!!(Fa;Vy^CGmX?zX# zIqR0%u%~X%{k~;yoE&)6{*uT(oBvK>pw&gGk`AB&B+UolkzvsT)_It{~^8dH59^?=3!7|1#kQCDlYKi_nGNHLGl)*#r z*yF-i7uA2QkzG)?aQ&~Jf4_e=zF>M=u5!yFt_EH`VTBffM_pDA|L7Gn{CHLWRh2wx;*U$|ZE+x1><&wP3Me!Zw} z?1lKUNzPZ7#7Ac_Ey$^!%GqFb%%->6i9^vY!-&0M{Xg@3CI|UH9Iq5Iva(K9_+^-x zo2%bC#mA6*-CnAffgyh1H#LR_S2izaxFhLc_*xt>KFpBEyTI&4{Qd%V27}s<5)J=e zh(9T|*uJan^ycXQ&)J$C7nz=BxRlvVomJM`^%$Z3(qpIPPv7ze1-eR!-mHMX>$|Le zL`Fn-@K)t8FP^c&+8}%PzgQe_k$*&4gY+Z8!Z{~Z|%&BP{vIX3abz?GMXHaaJV723fWAJvD zgcJY5U1fjh=;-+H)R~!^3nxd*&beiy@4IF7j&CPguKnL$|4aPjx5x9h-#&U<|6VNnjhkD-7Jlt+muGO3?+aRAj96`bWaDf1g6D_d&sopRw>IA94@1NI_o}lmFZTC( zxxy>s@AI(j|L524`IEir@6SK0JCDcPx<~A{JoM#c;Jo0Bp1;L*=k~8R zsx7YC8UJ6q`9I^rul6>dEpFNsl*%(K;rY)T{+%&=qE&rzrZ}j{3LZ^f*z=Fy?w3iX z_qXy!+SqE0 zESAg9J+L?Xt999Ydry67QNrItvOe)P*E$|vSG$&vx+Ct$J9s2jAJVKPIh!OQ-OpXowz7 zTOE^koQf?wj!i3G)+xjAYvZzC_mw;uLceVAn0g_*@v7C3b(; zu5a=l?(7U6mH%_I7nCMl&FB6te!jkIx-gUR`@fS+w#M#Pn1A@~&Z{X*4%-+Lk~78o z)}K)mW|+i(p!J{5TZbJn3|TD=pmr?7iT}U1yD_Aw3TR1pt>AasRIu^eq{+-J0#2)f zj(>iyB%~1XKl{*=uN@byO-xNweg1?`pYdh2=YK!j>RDX>#jhmmO=CW?Al|$tG=INB zAcK&gLP?;#LFOOt*;bqZ=3Fct&h>(zX7AkvDy(aqeSTei%HXCVmGC}sjc)WJHZP&c z|7^a^shAhVccIkh*#2EL3jKFf8MN|!&PuS0Zu@4nql+bh4^)w)o;aoXSew0}@BF`T z4dWl#hkoceO`bP>#@E%y_U~gkU}6~1dgDv?cX@|XC)m~0S-&i03hnUy|Lf%asaEdO zxI$FT{x4reA_*C^2OTtgUN>Ow!A;GAt!^PSC*+oe%AMKVG2=k`)mC9ex5$#36B5lm%JHP z2`sLg+PgH?tLeb=mB}Yl9C|YtF3z9MVqlW=huLEiv)IunHUWcnxoMfV`u5pQ!jq6UXktZs=k@u>u#F;JNh78ndzIH*}DJhy?B1H zUR28dZ9c7R>7KunL;~)vTODuu!&&7ke|~vBw)r|L*TnVJ@*`2>ZA)zJxit?JwIlFNRrK z3?5qw7c!EM>agm&dR&l~LZdBJs2bX@7w^n-`zvutqhXyxx zeOWf)euK()Z(a6^BK@gf)_4DEJlXTlNN_>>@7)*jxqk9z{yF_!KlOiCT=L;4HUn+$ zjkm(K*{qfH~gsHGMq;*p6;|CBD~pzx}Iz|NXzB-!HBc{`33!_xmiVU(7=_7kIpD1n-41 z=elRSnFqZS+NQ;z75ly833vg*mHXizYL&p$LAMa|Ngr5Yh6Mw!--Q0Yt~!q+=^>K8rKc@ zn;$#tQ-jFf_Z2pJa};-Sf*OeTrh^8u55LSWTD0iI-uL(758nN)-THo}B;$g|#@vO- z-Khvx(9+?~=@%|?rFN|2g02{_<9z#5-0Ay%tLB3X0-BaCW3yk#9ts&%tPH3(|0-{5 zGF|1yYoVEf42sAxuJP#S_3yL0SUOg6IY>J3U8|Yuej2nKLg7D!t@yiW@`A8K*lTpa2n=Zf3VEMm&m&*U9u4dT5b34C?(;N5Dz3kRgOum1w z$RExQ1TFXep~bLJBbrqKU#}Uo9a7UxT3KNcVlo`M#KJF5ECXY}{dp*3hbq$|9gH}> zRsW-dB_mq2L4etK?!y9!H(OWE|K;rb_lkVizSsSYpn>LBpn+zj5&dM*hNPUlyrkk{ z<1e>4*gy$8xIUh9t$hiLODJdsl;UOqb5Tyvy7v^pOP}?(tmSgxIiYak>u-_2RZS~r zyz*nd@;=&L;ClLnEp`9?e*gXb{`L3!J(BiHC@GwJ)sINGoi{(r7mGIBD0nF-)$4XG zd1Km~Z%+kZ@2*ogcYcB9-^CX-KEGEK`|&&7h2iV-*P!9Mj}u#WtbFqy+%=ly3Yv<4 zzgmi6HNy{g)3SZAabcz#ModYcSrR*E4m7U4Nwo z{yq=8UQk)~yS`guf9%!Voc>bYw#=v1wrV?VbtF512uHfa5; z$|+bQ@nY_6+uZfxQ+9#dJRQAOZncui_N(o`T>Ss8;_31$`|Ku63~;jDVxackD=sX> z)WrCF^72>V=S3ga-+vu_T>k%_QXe(OFWI%1rqtd3X~emqu+Z{*mHdxO>!trU`7q5` z_uX;wujR{@tr2F3y#DjZ^g4!!-&N)tzJIuOZgTaE%J<^0x91VWjcnuIKm5_Wg=!oA6ivrknY{tGTvX3%p-6A5FgA|KP?~q&JAZ~tD8+%Ne*s9{&fzpYQV3!k^2cgMSL_1oCr?~XtI z*z)oJhcn4{=0zWUyYuRm%GfJ>4C(r@hr_q;J9yLk`lQ^O;kU15J0;9w(G1cvDCcxo zp>ssp*1RD54NpJ9 z28(Up+wTxQzE1w&Vs`yW9SrwD%ZSx`>PvtBu0CnGa2m78LVIq*Kg<2|rp9o;{h8yn z|Kft%)fT3V3A>Jce}3`HcaTG6cI?u>dB6CPrP;JH)yWLU!_(&O@e8f_|1a73s^_cdKgZ4xhz>{uX8DG%A)Kar>o+?jWw0-8NcQWfaWGED`!dk@w~BrpRDq_ zFRQcv$=g;;l=#mOG@0qw&V_|b?`QpyzLn$6;`G32|DV0x-=PD>694sgnEboG@7~_j zFWnFIy&0w~Wyp?yZx$!b6%HE8p0j1;?P@=pOuAG@TNJiFqr{JhE>8+8`n|NqX%?V2X>JO1vkV>fGK z7gqo7&d~ol|8~0ABdeSLA#+TjF;5m0PW>C-v6jnW#jFdmeh{0ekFcV^-q`sw3&V^@%*dxnzz;&b}oRl z_wGp>{?GWZ5Y!6pG>eqFkY=RJ@TyI2)prfAy9dP~%O0Gjn*R57Womg*-@Tw(Aw>CP znA^$F-vt(1^z1(fzSH$)@Udjj`~RbVHqWp z{;r>B#kM7Uf2PubmNoz5!;6?!OlHo#yxyOIG1~LLiju>g|Ifp&fB5)m{YlGLPa1aF zEI9is{oZfYSVjjS#&@=Je7IID{9m;0)-|)gYJKa^Jo*+?_Gtgo^8eAB)~fO4EUv4B zEKff9NB=aF+u}eWRfQG8EbDIN*L=L$^3O_GVWuYo;>m)~BHs<6c^}9d*f7)ukScm2GID`oHqZMg3#vf1MRv=Fc~|=YQ^@tMBi3EtGP|JIBz!407=^dyD?= z=bT>u|G>77{0b*o1%!8fjCc9pcmLnB5As*r_8e@{{IlW3wmSd6UqfObtLQ>6{uZ7- z?>~5T$L_~d=Lh}%wZME^LA~h5rj)6ULAM)f{MY4PSU;VWNA?^4kBj=xSIY$&emZ*C zUWRXS)kDQ=hkpI|cQrdzvcZR;xRU{0~3;&-tlS zhS&Z%+L8aReyG3tVLE7hdiv)htM%h5Zi?~mjZ$RDxc}e&HH$--Skl(DIn@_A8}8gzMrT)%gf6f!Yn#O<$Io5`fWvhd4sGe!oJ z#rI8q3O{CHc<}zcyg~bq_8&Smwf~!ZSU7$eHB30E5c@j+G7CdMZ2d~khD~on)fiq$ zd@0;Ca+F$?g|LH^9ShV`L)rc)sV_=!y*)i)3qrwHyNckm}eMJm2&K2JM zU&N%q`!%?J`og+b=bL{0UJ6>|>ZB71p2};!vFgH)cQ^)}yVr3sfah~Nd(~Dk9ti_C zq80BRmtveS)xl%l>22bQX8b&gEfeYlt(eY+|KGiD-@e5cq+cn#e*M}S)I#6i9{^s= zx*t5f4qB7K{&CBUd8b{NUYrNDO2wA_bGyI&tGVx2d%NF|MH17)LKrt3Ol4{jaEb|3 zpR?cRTfJh5XoGy+p6L}1%a$(>PHtS6^!Zl++G^u;H-;;`wjsB>CO!Rmeg5f+IOJ*w zybNWRf1aen^~jFZE(|wpbie!&cH$`Xz3Z|CI?@k1n*L(dWBhpIb8=xeof0_ZbSo!bbD=ZE- zziA0Gl(NTzQ_K!G@Uo;SGZ{2)$CZKxo^RNN#vp6T}M>xwQO z488j6G-y49Z`l6D?{DAA{jTY>;@|ew&GHLon7vy5Z=KWMEAJo8+2-_j@-0vk7-@8$ zcQwNVrJm!DuPpWlpZVbw|Mm0l_s`Q8WN)+mbYo_D6KE!+P>}NgIP|-MOy}%h$+0Ei z@A^})@#mAZx0$|zc2^!>9p582ecqjjud{jfe5`nLr~9tj->mAp=N+%F6@FE$VSIAp z_v#Zm%o)+n5v9@lm;bAB*eb)ogcyScEyNB#&$xhJET>ta^nx&L zmv4KCx>A|p+rj+lU%I~~U+-sNcvtg7w&6`ZzaNBN&EwAT0il2yOTJY)E6`!ntg=%s} zTdyz7Qel8pZq3~?CXw_0YyEgrA9}^{{GmgKjy%XOE;csYcJ<)Ov!5@y?C#3$_u=^a z|6~20X*ut!Yj^+sc>ebD?(Ok={%@*%bu)PR!UKw5`0ZwTZ~HEPM{Zf4oy^j>=QqA8 zS0Gk2gZ9o@#D`zPYRC33@Dt)F-TPqU%jp9DZN;AjgNCV(fYw#y7Wqy8vfAhV9ixA$f8y_!nKBA( z{-<*-zc1)meduF-v%gs#^-V^W35!xkWv>6tkKaMe8 ziBn?uw%6{!Vq-tS}Z@~&%3Lf3<=i!4GUgp z?yeIpeQQ6VkB?!(r+>GvIlMly>b}^w{5cF2n?zNOKHt;#48Hx9`;+CJdC|UV3@OqK zX4A?Z{y3E*#H#RkL&g1PatNB zTb6O2-!D)t$}rbPKY%0TaJhJZ#|3VMn z7h}Cp_V=}V{r0;*rm6mkJ}dm8Xr;sLudh4$bd=-Qax92XR}TC8XX`C( z`8$@0UtUjUD(#4^zr4lq|I?e2HGBSKzk1ju@%8+f`q{aEyn_y?e?Mc%>?HshRz^7? zq!zTONtl!IGbl#~nXzo-io7}f>vN-<|08)D1e`p?AgeNkUnv+YQwiAD5xYO@$L@J7 z43949%hfuX{`ktt`1j7LBTM3KelY~is|${Pbw}#Rl6Zs4tmXcEle-wz-h<8pS^sKA z<@@QmkK@&tuXr}@ZSQ^kf3;9^o#U2{bkN)nsPlGQRmtJ*x>fg07=@%hKJaE`xcTqZ zHS-^@u9tKqsrj> zeSf7lLk{zT88(gwQcmn&t;%5d>iTrX7jFA?ST7v@e>FSz$eQ^2_n?g=4NG_yC|qS~ z(R*dC$GSn{f9%EelXN;Sc>O&&iQzVj!|kuq)0y7b+6am+X#e;6`t6Qb*y`r1_v>$} zG}r~GGc@Qr|9|&)s)~4n50}U8eLN1= zy3pf#3z@Qi@9W>bzPmDm;p*r6p?{s`P22S=)}KM7SM9{^{F_VWMgIR^tj-XVuDDK! z5wfkqBlyRsKirjn6EFYGzp2u&Q3-k`$eyNklNpXRWePcLko^CvRPENX_?~)4HRJ<9 z48mMaCm49BvXrv9zCDO%ZjzQ=5fK_iHokTsCgw=kC`l)t)p`HHiE@`COT@F5`V29}x^Os~AJ z`}ItHW&4Gv@8cwR*o4{ovi_9M4DoCDnIC-Y|E=r0o%bwF0Bs8eFQQDbbkIq0^#{#t zzDbW=t+ct<(ZSNPRg^n9`=Opf1&&N->M5z zfA>c-GQ26X$>t3a{ZAF55Lm3W?y6*ZGjenDNq;aud}~s~ruP z)Cn3%UU{GG>-XsT+b7O22AF>d$kp~b177b|G95aYaWP|x%8l2`(9zeW?|<3kKR3ZQk=U^?bSGGZuyicHD(>8!Hk(d(b9FC|#)ezP~iH=s=K(b-l0?(ft?&(ohVY*1CIO3qS?ERWc-SEuyaA7k)5^eT~$e}3x- zzS#YLt+P()B4@|HSMH~;nE&f{$J#aX>-^#*4=j4V`+ybWgz2pjY|^Q>P6wz8EqIf{ zsMsQ)bmeXMtS_LgK&7`L*pr5@V-0(wmJgdUAti2v-;>HR?tG{BYQ#1tnK&w3z)K&X@Vu=1jwozLHJtcEB{+w z)N_Ick$I|K)Sp<&aE-HJkIs?MTipV)+1mu1JoaN}`orSm1WUW}WY+wb&G&b5e|1v2Uk?t9EiOiWa)C~-!g5?Qt8XTFxug14az zyFe$NfS1A7s%USFyZ5Ws@Kg7?&-zvSx=%KP2FH=sjc1EC1bngOHi0-io^xUR_b0o* zy_vqV{-zegOhJZiE-sI91FlOy+d6&eO7PZ5mF@}i>J?M|N3+Jy>~U`Qy|tk3)py3P z!Sy{~V)y^scct`SW=h?Hq`R^Uuf338C0vYkQ_`7iBDtlvg(3U8a{9rzpc|mR{LsG~2w}&#UdI(#)eRjv) z7WW)y}gw z_(y-1^Sy*a(-L=_01u%sl;8XhO03~8{&shJG$0M3R4r2fsB&UK{I&3Hr3F*X6Rv+< z{k;C?t=TI;O|Bh3q`e^(vcUH7Sc<>OaJ9Hi}<39Z{+x$6Hd z?d^p%sb-(=m<2rlUqAD;k4P_DhS4Oa>&dZ96Q;NB@X<(`8RVj8V=zZXp+z8Q>MFOr zt34V5{;uye9{N@LU*lZ^hci2rI$zTU5LyI*e4fq%`>{}x-z*QM`m*PnH5z7crX{?FgNOEgPV z9E5*e{d|6H-rxHZI2afpYi$;Ow7PGyHh#~)icg=c?^jo3SHHiO@3@FD$(SkjAJW+% zi@G=p*2bT+u6`-iCpXKxh`~ei!0zwiL2tv)&G(CaKiNo_VHaps9LL7BM^#z`F6Abj zv=!dhs3e?m^znHT8VHug#_Y-`ZB? zC;laUV_mVx+K=yp_J6FtQ(du1_x-H4%wNCjE1nf*fli8K0L{|xu-zW@`(NqDzrXuM zFRrU+i%d~tG-(wAuV@e|mQ8cwP`nG-SeKn_Bj-N*gI55%t#6Bc_wo4qeXVL=R=53K z%&srASl&U1F~Q25#rMT_ssC+emxUPT@3^+U`oDeLKhf9b>cR}iKGh#wS{Hix*7e=n z_xxS;J-)WCNPnw|!j^g{$1YK z_x1mpe^;+KtFa|5cK0TFR|DaF}ygQw3X(mjb-<<#V(63hOW7*5}&$G`ax zg7+7`t!JCWy+Hf^|F=6sK_{Pp&d79S$kBOw=;PFzD(VeSm|vXwZ~wREB|pfy0=+&6EnexUUm zvV&05d8+AuJtfAj2aEsfHra75s1nVIToSvLzuno#*LUeX=fL;<+|Uy|z$5BjjIS*J zf17XXz3ujj`p1uq%r?YwI;>|{uGKN`}_aZg(($z z(7I7{#$+z@fs(%0|F&oFHAo2RcbsHS`OGynmU&gYdRrY&;(xm@r+=<;59z7-_>aH+ zu!5yhP2K-jTPJZls4+PGWt(*+-}?QXS5X_b{qm+A3gp)e$TH*tybK=>8ISF2mTKmQ6kWT8@+!C}v2hHVA`qO}26j&d?AsQmkC>oU#;ftE}1w^08(6E1heZuY2>)ZZf8Bl-B($ITKm_ZSKQbVC2r$Du6gIS^$^U}wM7<7vA zIA&eT-(|P#e8aEVm*qF-9sGX3eqwgbr78b*)f?|CSZn|O&YJAnODEu?|5G^_W-o6A z_c7M3-2G=whDyU)`!84jmt`_7;E0{apy4!~_kmKte(Artmvukqm$PQt7H>q>vSq1KhLxbD?^ttRz9p z!*Ie&DMVFdBah*_gP^*lHtB?;@FvK5kMc}cQ3mN4HPF^=(b7XdPpAABcM50x@$h(c zCwod&m6fU33oWzt{9l9jzyB`8^lEusu=UsP{-BMnn{597DwaIY4Q~6S2p0c83^}2% z&@oNu0P;%fdAx{Vp53u6L&YHswA}jd!{gP~zaIU(An4BlUUCjLzTwyJHTK3#4)Q)P zUh{mubrt)%bJguU3tsCSDJ^1vRQHGuH*6$bxtz1%UYxp4Ab3#OcJaJpwxH3r3%|w1 zC$m&~Gkht#4qeGD^3UhikqR5V61lIz_2SU8B;EIyayo>uJ>0agBUMQF=~aI{s~xMN z>t_l$dDx$PRsN+mdiPs9SDYt)l!P+0NL))0a^iR?Z}@CMq9{XBl8xMQx!OhNZ!x|S z|FvrV{d#+Q=Ge_|Cm#2^-Dtint!H5icnzvHJKJ?hGE z;LstbwZFH37UMMhy1f9`xO&NLj<1zdWk*b#YJqygYM% zJ1mh`y;TKG8wq>sN!|aOKb<3YhQRk8x5b>y2VCYKYCNg(s{H=e`;L(FJ$7f$@&%1$ z_WZY1pQp~h^)4$nH}^{S>5y|hV$57N!h4yJBR%33^?8JDeA?2zaV?);==x?Wh1^B4 zN0&Z)AN>FRznAeUxBKhw<=y}O>%fh=S2u$hZr`iFStiN)Ab5YpC&mXCo5fFl``3K? z@Rf`0i?6%}?H7c!AFT`D{FQ!-v0n-@e_>Cw+Ul`}`;Sw(t6Jr9Q9p z_y4=cPw!U)4UlK$iudoYS_?fp`_A|It^R)>on&XQ3EcO8@9q2hD}GKco`2_d)gBx7 zUbcdd_6L^#YfqO`zYUt`ehc#GZT5BVm>Z@%tbg#z?!l{Xzbh-A8G=S~zunR5TYtt( zPH^F?|Ddx-%RZiC_OSV^cWeC|`(Ewm{*OU}H~UV3hsKxKdRLc(GO&P`VSR+|7w@aE z6B8Hb=H+s}U-182`+Y|6VRT#H+wU+oOf!>+Ho-KE6NnquX)6#DC2xCmDrf|Ndp}f}INzG?`7W({*>34m-oP z-v5hI+!$VTlrwr%i{-oxUGX|Gkq@-cI8+0?kgktKpytbdtyDJaN34>3XXVUihu;g` zy@!4Jk8A(uRXv#hzw>9S&!4Fxu(bgHe$HoDyj=Kff4yAt{nyPQEe^3YumAu3|MS1v z?cGz0>+*Nxop1fAV&q`@PgUsv>%Oo2|4;u<+xPQ+ZoXLLKZYHV|KCLapMLzl=eGY| zme0LYqjUGKS@V}Y&W-)foIf?Isv2rv*FUfS>1esh{pP%ohKJ2?;k z|69KO-?UHr%ClJ-tadP{?d1lq^@J@Wb8=tM`l{Y{OX}U^;Ou|Nm)5KQ4L0M1 zege<0(!=4gVhSA(*UP`|s{3o~p)0vz%gzp_PDjYf@2>{B+$)@XeqEn-|DM?|-+T3` zNeWw+EMPNyd493x`T8|0PBXKV{&bMDuKPXvaD9lhPc!TE&W5aYJrx|uF2AxB)P5S<(mipC|OmTXm8N2IbFHQuXMRI#qvGB%M)4$J+ z`py4Dk8wgObL`K5PycpbkB?5Lnv>VKsXtP1k+PyZzypTfT2^c(vf#fM&$i~jS!br3dMe=#-f+5WR;|5QPT zkvyBjD4Ud=DK0m!vZp;=&cEW{Ql*CMq?upB1YswUC^Kx@BoKb{+WVFJg3^|{V3vlwAG5VsLq0al?u z;1D*f`JW}yu%POH<619~0(@ZI03DVhx}|Gjt1u?AQ$DTIhU zLRunwV->>$iwTW$;#d!~H=FR=cGXhm?zqcIee2JtDgMj4>5x#ZbbvMJr@}HWj&(O?)}Jy1 zjf9>2H$N0Kb-{6Rk%uWm{LTNY_d^*x?#!wBCKA8zTUrn!!;NqM#AhmGNH-r;czQ7C zG}9}&`meLrEqFD5-_Mg}DhIx29$x?Y*#54QAH?7PhaWD|UjO#>^sh#7Zj3AD{RbT` zviGLvwg0PMeond^U-C&ody~Vy|3(Y{MgC_ln$B>A({{_<%ipRhUo>l|aj1ayO6}@Q zKP~mM(wkw{yZY05j2)t!Kd1Y=4C*`eRj45%p!5YNW9Z}ir5q(XFFYApXR^Hb^gTb8 zuVKr_e4~b43*rO3WmkDJZdPO1wdnp983*%!jd}n6m6rH!e;!u&YknVdhxh(&PKJdU zsr`HY|DP$yuq*&{JS5ll)!#L`R=@dQ30lX{0XlAF-~B5pjvfD|Z(+XrVq@j+EvQFX0y+TX}~TH$N~x4*m=WGw9rt-n^j-ktS|BKTmE4T6j9)BY~I z@32YbfS}>)?~d2Mrn@nuq$*A;ZFjwW`V#Wei6$o|8GZK{5kLO^}OzX_SG|aK78+sw|4vcDnIt#{r{i$|GOW}TmSsq*5oVsPOBJd zD*w!#V|-e0b=}|Z{d@P?FU zQ$@g&$k(omFYS50?__mK;#`IRZyv_U_gWXd&59O8I^e?T>;Bwy8#(cF6YjYF`?P%j z@%VpVzAwD*X7H}&M{JPXhJK%To3@wT$DQ`~CdAB}>nX(eZQ{(I`RbFHzI8H(e}8!3 zYAskbbS?V%^Jf?ZjJONun*N9Gq;j?S@B5tLg_9`5)#iom1*Z(7cfX9dy`b)u_=@?z ze2=f#Fa7K3-diOvpNsBYUS~f&ka4CkgToG;BcV!+r~Je~do!Kx)*UTniF%>M;3WOp zd3VdNmHWX5Xv|#mq-xoxTD^^T?;mw>c!sw49x~FbuuO}g^yAZ$ou!~5{rAkTmj9D@ zx#{1cwu;E+ye`zr+ydDQ?-I^~fzIY^xbphA1gYlP7_9iv`S{dK|Yvq3ZFR}Z3 zj~+exz+0whI>QQBonD%u1zM=eaZ+i?^ZRY*|Ao5?y`HMu7?Ro`TosVg^lPO&xYuG- z;#xoRNL8*9QY=FbR5A0Md~d#42;+jfSNY%_s{PURlc%g@N|?*Q@Ty&IRjzC2PCjGB zmI?2Kg8vII`ky;%Uwd5;^u+Rgi`)OT$8b%sWMuep-bn`0gVF{0S6JsJTYavCND%nY znCp$dZm$L{B2+mT7wc2yT+24olEEoWaoy%;kZt}UOFN$Le<{NdyMige+_@<7yhl#t zhL!Vwy`JO#_ewn5tA6kOy{)aR%b)&1UjA-a{qJ@ZWPQ|a9UUE({yO-yuih(z=Xcu$ zoH#CaLXzi;>LOLp><@J5{p8m(Pei|#R}yb1<#f0y;2gZWYW<=e9}Ya>JqNlLNILZJ z?^su+mRI$$kE^im=^dm zwV3>CoWt3$#>Hh*{Fc2MhF8H|V)>+3^-f>*=cZf8Bw5I`)kz-&C5XCL@3nI;+)t4_ zAPqaar6!vXG-tEv-3Fg(!BrIrGeF0%bZEc&W?ZVw==62}x|P-&G8X35wK9@CJD=#+|Em7;)W5#} zBIuCtl)pB+R)H=60S)Tqe|`MBdd9wr9H;&NBuhd;Q~ojY&dzsvv#QQ!C%etg>$gEi zy6WZVygg;Re)X@|5Jmx?$x`bkF-*I7<{u0d|BARQbS`}$CA8Jh6Gj*|_*j&kz*K}B#UL19H zSov3s`RB^alP5f?uDoTRRdU(n(&d89x(5Sp=R5xGT7GVAaQ*p}#j9TZX6%SA%?rKz zD=hY&)qeZuN0>K9GLDR!TeldGT9eqtvh4pIRq) z*9U)jHRWpkU;Fh5Z(gMD{Kh$R(=UBHLv{v>e@7h|?*97A-td0UCtHS&ORrDA@|XG? zpDxP~Tfd6gVMAnx#%acYt)E>V&l7O!$o=xXn^)aYu6@Vp=lZe?g(ZJqeVab7%Iw>{ zjqLL!zH?7q#rELD%#$~MKN88 zEr{OmkYQp{L-OSJQs23!YVkERb3a~r+pJM*|9{tS-b!aLtUt}U;I-j327y=WoN^Zb zvY!judwD7-_00aWHyNB}u_~l2VNTurc}mKk{Q9cDR(F1v@Pgb@#I$DZ+RD9ddS95E zC*GgB`SV0!hqD|Xa(S*Tj5q(c)OQYp-G1hEUuFmVH?OJx&ba+o^0EI@*KSNb$@QU@ zH|Di{$H&I9`~N>{t*ih4IcN5Fs~KGk(`4BuY>mHV>RHM#gG8J@wg#+eYMiB#hMKA*O?A%v)%Y@q^7oTp z$=tCl)L+G_Ajzm`$&{lw%WvN9j@sSoi$4B05`EAc&Asv4=jHwB_x|fVcL})6kQS<_ zcR7wldL~2WZ_vpGTB&IZ4Rfb|nI(Sz{QY&xvJSRy?Ky8Q^EjeW5~8b|uEyR||MlyE zr+YUpWX#>cH}ly3ZENx_v;ED~iGO>%=f9a`omS`f!rSu>Zd+3;b#AWk_oUa#?Q%>E z&(y>W%{RVyJjH`4I5>FYejABwh8rHXXR*y+t61T`!yA%GW=+&b$pu zniDFIH_Vb@cqPCfdXniwBG1k%`fY#vVrMHi$KTkPt=t%L?{lE=fu(heKd%2|AVn|Gcy@`?oRo{4s+@v56*s@1J@j_GZrpS!?~A{!NU#8Z4L^cpuxVoVp_5v6gYd z6fcd(JfF9$KCa<4)$sosMh6bXB|6KVyU#UcsIja6{NOa_f^_*6tW&OJw6EPcwcqxK zN*KFCDbtGgj9+6WTYX*a`k&zlPu*2Xh1*z(KRx~ld6^!fPHcib{|)!F`<-*SdS zA=0rl@cV!Jh8YXx4;!c~c=#t|G);;|C(QuE||H?P(Id5FDH~YEn z<_~tWE~b0?C1;CWFb|t6-+$4V(dpQ>I?aE}l~@^O`(&{-^lKeix9Y#KaD#x8hg7A~ z|6jXS`!ockGhUd>@M_1QC5BVXZddP>Yin;WwpPjs58LwWrV1s0J(CYjo)E=&I%_V& zQnQ9Adxuh~mR0eK=4&l>uL^W)aQW}At9Nt`Q@)>_r0(onsqsH$6hr|`1Q2- z>Wcj?e^MEZc6~uY3KQ`B!MV`$Zm(#mow;xe6zS98sDp zAT2nt}a$UkRW5ak=#W$~3NmUhTix+1VSf)FmudSdew) zwArg1#?nVRUw<1G>AIblJMd}d*M$s8CysNmRvp^*Zv{t^^Ri#XKmVF9@?qEbv5NV@ zr{DXHcC(iI-8p=Y`*w2Mek;`l`7!_hGfN+{Z^(#n@R(9q#$wLhkf05U3J%Tg%x#j= z6aOze!z7@zOk=`IEu(-nTqT$4lHRmW_!i$@SK5~!DW)8+^mA`qe!{d4s}-OBt^WOr zk0Jg(hv;{UueV_9GruWa+ zZ`idKs)1#+}x}4pZuBl zqJDO%x?nL=#?gPbs&v0(>^S*g`rBJ2yjLU{y53yglAR#Kq;O-M)0}dShOT>=Nl)g3 zwk@nmn=w`LHUE|Wf6huVT`&`*(R=hIB8kOYp&S+ zY*+64-gTP(zFvIm>sh|_zDHj$oLJ=)vurO%X7azUrPGr^N79rqOK(lT^zD52`sewx zkNxjC0ZR@4VX7i?zO@C$Ul_JsmNX@`u>ps-O~@>jx`p=+7MvZZoj znl2)U;xO#`=km(R%HVavS&FAmpPs#9{;%5k?fNS~2Up(y6`aiw;%ggnyG!Vh1gQCT zSGh&X#BTon76B)YO#R*&tOr=yLF4BeU8k1(Q@Z+}_5NmtnlGPc2ePD;lzcheC>zzG z`O5k4mH6*Fe>D7BX}@yruieY-cSJIrI&(ND%D)=44K~E7>R*8vXy~zJ$ASPshuf{4 zU*7u8P}1!IZ3o!sd(~-6r7>fd2xG~|KYw35H#aX;{j^Ydq5$Zao9B<1UoEd|__ceD zy(zQ9@{Hw+xfFZA^DeedYaD~0e+HkN^Hy~$+X4OX=}8lt7oA>~Jj+g>I}LPLXQ6BC z#CpZIl@Z53PrnS_6L42~GlxRLc7ecY%nP!fcY~%e1a7}LFD~uZWW!fD{rr6u_5&9# zTyTh%j);yvy;b&WaJ}@xx>x6!zm~h#KNDcM9on(^#4LNu{O7CQ`~mF<*yVex*gf8S z>+HVZmRq3DT!=(1D zTesebpZ;P~S;Mc^b+4ZPegDk#g6Zskn=+2*FsNDhGzd6#=&oAw`Su#dfa{;b<(U?^ zae_8toUhHw()#*W;N&Y2)4=`fG;^GN?hBfV>Mw`0(Mab#ZZF;o;doW#<3-{nw7+!KL8yObmUtKUlJ}v$rnW^y`xTE&iB$+zdOu zeX8D?&Hm?gyj?LbgF)3dhvi?mi)8(D^&Wox*n8j3uq-B?U;dY?F@w(8{R|8evunf| zqW=E}-Tt|=^XQFV2U`mn-tPIo=r5?AZ>Zg^&cI-vr<-v99p{}LTLre>-?6pf^YZ@X zb@DxY3@<}oh_-caS$+JJ_soscA!(;|?M;SL*BCy0{o|{&g~KDRt-XD%1>>w$_r(-n zPUkCFf8G1;xwPl@Du3-V7S)+vioB}8=Uac~$No*G+ic3d7&Da3Wmx!9JmhfwlM9D? zzuo*=R>&e?yi$^(D&6UA&XdgW{y>p%a0q!wDZ2ctwX5&muxpo;tK*e(D>c7faNX^7 zS8%1)umAtd{asJro_{Z6|M!qJ&u6&N@$Yoe-AIPeFZ_GW1a;rv*m_ZRZ}soM zr0V}q>)sUe#oX)Oy_`0(e1(o?zSOe5Uzuf=(ce6BmRo(expMIMg-**4KKGZTd zJLJ-T|Lu#Onyb(6nHE3yzfJgVmp}fxeAlcZ_uV@FVq%H=MBY^|_J8h=&ymTPT{Qpw zeV?4^8v9-T7XE+s>;B{1D>)6j>X=u(m@oNrdPbz_qwuutD%;-g*&utM?W29si-%RW zTYEq6EPf;xmbN`1FG-MH{Mg)h_R#<3aX;g(=v}O22u!)>_WS4MroQRFc z-|KO8{0s&~X+qlR&li7_-EHRX$KbeaUw3uq|RLX`K9A&+XVgiT{;`VvH(z~Vg?X|J3Sxi7V|}mYd;RWD<>ly?Wipas$kmAFP7|vdw-|~*Z!>NtKBzAvBZ_s~V53>Ww#~V_3O=9;?c*?*aaGD`wH?xA~ zqxF2;CyE$S_!t9LZsyv!)`#I518DhK%a&P2rQ!d@oo28mWM!R_kX*I;)jq$uvg;>& zncY+W*LL=gYx)=K74m*Ce~e<>wDuLthQ|EAr`_*qZ0Y3@-O!rz&vjNCK9-MI&%a>% zO{Fcbm5$jrG&VIvO2{%e6p21kdYcs;cWIUW>-Dbi7OtXgeT_~1GRm;YBe9|-$mukv?e$G_j4NAF*WWYTf{?|eyL#nsfk|H~pt<{Y>G z&KK6dlCb`Dv+bk)Ifkp68*=?0{rJDBWkI}v?xb7$jdrVwy`8?O;Z;%W=7m=pUH+%C zF+|p-KeM_wbEjM?!<*Z-g1XCjt37m?IBu+Widn|iklCXOZXs>#U3-&Z(}^m%U3LG` zt}+;u{%~5!qWdEGU+z@~j==pH0p0&st|(<__~iPbVZ)c#&+8t1ier$N!RV02EpTgv zy_tx^GENhT|G5{}D_MP6`7gzz;UwdQlD|d&HfAzhoyowH{Qv7;-WM~Ok-WXK)l!4*U<@H$?<}&R1$RA?N(754?`rQ3jSUtLagqt=r6nvfiig89y zeeHi0r|bGNO6EkxboKm?TYvr6|Nl$1qmyqkWIfrx_E)QpZs^zc7Yq!WOkXVd`_DW} zi~&*iFU;J`(9$LGCrEbZ_2_=CTk}{IL?+E)Xgc}h`giX|nZK?sYB+I~jaZ$rISq4R+VkVn&YyrprZ2$Rt{p2Zv1%JAm84g{XU+&hp=l0W5 zCX+ldhVZNZBSE8O9#W~t{#-YecHr2|@Z{iCi;DqD{*hM}#+z1!G{>u+WLVW~^;O;V zulqS4Ci`DkPFg-WxV7%fc{^DXA`*1LE^%6o0z8aAEy-S5Vn3$ME3o_I>;cyB+di?LmS0Kj!f&v_vvAeC>0c znE5S6tn*V~gI3Dl%!^vlYyp?Qyq07P8 zzn&g~2fW$8r{_fW1blsczdq;pv&ZFjn|@g?e09BZ*}vHvtM4p(F+Hgv|M?xeQYnW6 zb)t{<7=Y~cnERve&+8vm|7M-&>e$f}*J8i|J{1LYyJ&gzegj+E*s5PHwf56z@~@O+ z$PDdBy_0zF+n$ilFPp(*JDZ-=)?Kl6O^7`oFI-i)*W^>#l}T1+iY1>WEvS3N4r&eC zcbmPse*D_(3#L;|@A)3zSXCmV zlHtM8Q|_zewZH%0cjy1aSI1XLO8*K^+a6&t`BTW%fYSfc+r_wY=UcKkEY09b52ll{{aA?LYP#GB3#JN(|avwPq0_sJQpd z-*2_U2G^e`iLDPS+`EUbS7+|FzRAXCE`OnR8IScE3yfLrhtCJbC z^F_Ry*GfnC2!G}}t7nc3eS+;Ch(y^WU-p#7rt*(^rDi_qv!1Og-aa-Wu`4d5#R#I0A{oFp2 zQ{a{kgC2+Nug6lLVE>kS!SqZ)V=)CvrFXFoE!dWH@FW~FhsT3L7ae9mR zK~Q7iUyFcKhu6$o#>?JLiu?TN_s?ii>4{xN$0MSnyRBB+Ev$R>y%2n?`S->4Z)>i8 zU$%Vt;{7(i#U378+v;?#>p>dh3G22^8DHz6Vfy5`|LinI1(2!skml^g7SK$XoKq+G zsCTt}#usXA>wQIZ*NF%HTE+`XlkF<^MsVnCH zy4@Ca#$QJA*Dk-mr{xJ<)x+5rd zuH^JTfAi-Zy8r*#%Anud9RFX)7M7^ewPn5LA`Ncm?)d#X-TUoruD}0FxXb^)3|m=y zd%^#6_H~Wc|9`lGIQ3~y;th;H=`a13+x&0O(XW%bez&fgVMnkql0Kbyq<9GC+Iv)gLCEVW>!}&=Io?Sj=5{`)aZ%q>uFK zNcofa4}bq$-546Pa%HmpcKf@zf33^bmW$f2shG3u)$z~O*W&G0l+9T7s=oM>C&PiI zUB@$5)#X&z{QnrZ^sq_j{}T(_UvnE@?YkWEx3c?ns-?}QFX4ChYs}vF^LF*V5a~%) z+-BT@F9o^&{?&pW99o)?Y}T;m`NDm@Z=b(krf%1__I2C7B|+=fty5d~dPm=n8CDL< zruhH0+5Adk#+Ue?U#rYtz6dv0l?%^U{qK3*hf;%QFI`ymjHq*t~_=KeBlcqJ(2_KQ=o<;Jm);$yD74ZG&;ugqrfSRSv`pjf)D zE1-mD#jd|gm(T0nTmI&t`mw)GOW#OD?EJ5su9p11J9xp_U!l$AKTk++{2qGTL3o{` z=-=gH(---GMtWaF_+HQr`TLLgz@qIgt#xPI+#h+BOz_#UorReni|0}oo?&b>)4&JH!+x7hJQ)d`gx-zU@2AX5uXvk)q z2`c7QdO>}Ts|!B<{F5u?IaeXtb!Exp_}FWknQ9(7?Z3xpS5T?X7jsYAms8_)=I<-5 z*?$cm|Nm(#eVHli{Z>!=hKMim=|5LhX87FQac#fJehBWsk_3x|yEneA$zp%=THIFseC!cx*GhS-<>d-`vG?W-|9!o#gjzeV?-W>M6FFsW znWp}CyIAiSK34Hxp@;vyd+R>&BEP~Z7LQxk*!8|h_x^9$wnUMsG%0%jpZ(rn{$Krh zUU>1pg@5#JTv?wLwQEzU?JNC>^@U6)UjLoFaUtWrb?w}LSAATs*Dq5gQOvU9_m4x% z>I$>w+Sl65VtnxD<>QK%AL{m$RM?-JZ^y@QV|%qy)VH@B+RYo;9KuACB+Da~*z%_; z8Gt89j);ZMHx4xhje~zSvayk=QFI9RV<^>dIC0~eKHL6zj0>!poEG-2dZL&d{eSjk z|6|fq7Bwg)IoD75mhV)^rE~T;H}kJ!ZU2_M{2!LK-G!C${hS@*Z$uim?f5l80+qQ-YKlYbY*f&)DXV0;^*L#_P>Ek(< znW>izedkYaz1Nwf23p!5eWUBob5-yFrTKf4hOFg>r$5wJDQ&oPtZLQVLrRw&UfzzY zdUbOKv%_3Affeh8BevHSGQ5yv__fP|Att_k*BABN{WWUuSR`z#6}H74FJHj-ccTCz4tfzHnpk2NSWcmw^>(K*5@S&D!0oef#x}kHcVtykYsqolX3cc z{CzvkNA-`dIZY81Sj4Q5>2>hVWd^3J!TeW~pDfLK-faq6%`j!*nHlxreoXS`@7uhP z71$b_XtW@A>-Va?M;@}fE{j~%#KY*ZiV-}qBQ3WeFFEV~=Jhrm)10sEFDjGMb_n4K z==qF~cMA zf4SK753l|_zi9T#pwZ^7^}merEi$YAZCpJ6Xgb4 z@4#sd3R{^OBC_j$uYDgaTQ}X3Wx+eGBWA1q8-MZ#&H05M`*V8^Z^LGWC+`0L*W6Mx zk*weKQT|j&gWi|*Yu6qP`rTNX49bLti{rLkT+X^i`1Y^RCs94~Yz0yoJ-*y8|7d@3 zS>4ZbtFQiZK7RH4{SFI85ucD?sU`(kG=eo?>vdiOK#{~z@01>bHCz4_lc zH8S`9stoSPdMW+759QL@@)Bl7TiMsypZpOX{9j-8PQUon?|Pc2D{nbp*S}xg`QdmQ z^PT;Y-G9DU*}nOIxPQuC_bE@Gl!Zol{Br$%u(j}vua27^Lt<(fcUao>=~ut}cQ&q{ zP;+4BlV=Yd-tV`lJ91)wVYXt|hx z=KlYK4;eb9wVKucKYC#Lm-@YT&13)nU+?rxfMKfDDuLarVC~f>wRJyc@E*9X!?4gL zs&-KtUTAli$y0Z8 zo$qyrxr_@=*!k{e>z%>caP>c@V#^iR*v*VBcH9oPHKJIpa__6ft=9TA`CI-i(*W7rruH<7ln5MXB4_|}UG27l+Ck{ou4KGDbe6}h1`DZD! z0^ir*`q>NXUimlw+P(BY=ixHJeX9hP_x`+oJ+==#F2+{ zE-<(ay1<|}cNW)I{;bn7ObfP$dwO5#Us*4gupTsPB#^`%yex96Ps18ThN|6fpRb?C z2s#>b!nfozkjW@p{)MifP4p$)_a!q*ZR@8`pKkutYN6PsJ$v?a{#tqed*zQN(8m4U zYwe3U8oVxw9MQV5V&9xC_KGb6liY5-F$zsvywYjzdGN_nTc*36VM=H^%)+7Aa$}K~ zRsBrOe>LZsK3qJX?C**@e9_@SY&s_|8MF1ZvGYXe@(u%a)0%&U}J_=pvFO^ zrZNBKOxw3p1)MknIbZ&K{P?FghhocwZ<8%~7rfOvl4``@Gf~V9bfVymWm$^1jFSI0 z{+hpE{_me>%htKrm8b|g{k`)3cT9}~Xy1GN^smd)7?y}CP5Qd$tt)3C59k8fZ^BNz zQuCqC6;Eb!hhODH{X0J-B1hx)b{os^qSH{qxkMp+&&S&#oB;+1c4Uw=J%Fm9Koo|8M1jfA3zH^D%;^M5k3nsGOQ_T3>YU zTWDeN@j0)-nTC=%Uo8Sf8RVj(?}fPX{3^VLeoe$=4@?zt97Ju<3!E_iJ)`6 zJ_VW0y&uXpYyI=}r@nkC$*IlCI(4dWHsoZfcF@UEvnSpO_^K{hk)54=ZSf7?OY66V z@8#Q-CmVl#VaH_U*S{xN@h-^ZNxYf*cgC;hj8C%|IKYQ$U1>Zm#qg_o-Msx(ybOCc zC%j@=Id}fUhYyeB)@k3q2c0Yc7_u7{R-C~UU?)GCm;WRt+bSsmkG=5MWEdPjGDz<#`Fl)}A(OSGYnhVR%&rsPZ+L??=pHNAm5M(fFS?c4#MD&u^_SGr zKBom)$Jf04wOVM=zq-eSyuWoneKIv5na4kQJ@%lWwgJFcWS7*QOx{-FRnhIZ-e9 zs^;IS6?5mUT)DFG-2GRtvQlzZ&kYWKa@2Kq*V~Ss+`smJk8NM{w0eHtmi+pv6#=_l zeyje{KYd)TCdpLv#G0nYwwJsoPvgG*OErm~VaNJA&vUDdZmXV>mwo*2?AQIrZ?Cw?u&s_c=k@;L zM{!&B80}V#lC%6`u(|em>stl^<3HtbH~3<1Sj-1CyHm@!pQ(uz79`&JwSnU<=Ym)N z&uQQ9zNKo#uh4boc7}cZFN<^Y?fND^*E_c8&mn8$>x>BszvjDudh94m2lAyBx;sq@ z`k(tK`u|4u`7X!yFR+m0XkeQEu&vsnptlB`pYizSpH*91E}7e!^mntUfL% zn}NfOTX5$J#sf_CpmXt(R#`q?#pZCyY}@?(nWhY%dj5xev=xgm{qr=YB)y^6aKXN> zuQfkRV%+ferQ)66CAkMI6RLUE=>J_5-BW#%N8oXMe7vPK{{makJd*rrwF7Rl436e? zv)9GD%Kqj*vYDCT$fEzt3@=zMd}Y4$ueue3TsnV`>|dR%tX*{&Q%O@;6}~d$y!0=w z`GGQ(WM3-fu;8E8k%cbc5`1!i(FLa&tPbieALn~sxys0$_-DPu->hHz7wk{B=6d10 zKkvt?!%Qr91r@fq)=&F5|H?BZZj~$l|AXcl|EltGZ)o)Y>-|}Ojr8A7%axtkx4rG; zH{IZ|;=8=Va;5(aLcR5+KODqXnlfBkC2)Gj;XQ9%H%{d|AO)IH?wSx5ylkBmgWdf7 zRzg1~oV)+!OUa`M5BV#JKiXG-W_^Cm|1yu)>Eilj|6gbR-}TVGD9z{;(+V$*U(@^d z1ZGG}Y6VOU_`?0@iih#1tCEbXgl5%Uz0g0=P-TJ6!I$BXr8}<4yp5}#$i54hUib6e z>X-k7K2Cqg0P4{FynNjFwLEAk%mH}gVgI{G29~`XAsJT;pV?|)RJHJ;M!w};HS^a`<~|uWt~bt@pAq;4)BRn1xzhVK+{E=Sy%8eMqm6t z;mdKQU$1-^Idiuy`mgv>eA1#>i`}a(_Ar_){;&8VytGI5Z_eQ#;^_^qL~d+TFZ@#< z>2yKALrn5EVzOw{Tcrt?FJHd5t{KOK5u=wbQ-+u7ncX>7%U*u=dGT_xj(`V;;t|o% z_W4hHe=N7Hs)ZhNX+$_bmjOEIPSeB3vKcyE){W8zfb=k^rL{s7IuaJga z{~lnOCSuvE{ABm%`7PS)3QTZfF`TxIXV&JeI?Mdq#d z^~;zKl!-nvl4O7k`+%$Y314kW{{A`pm?48H>4W#uFLC*{S{aL(Wt2Sq7!HDLP`zmK zH!IK~;V|cgXTRq^V+rW3k7i3)(J^h|!i5om4!8Ll1UfAl<%hp=kImTQyk#6 zxo@w|zs@<~>+kuNEDrhWCrw)+AN=3uK-^c*u*VUxWj>p={F-15GJp04mxvsfpjj^% zR;;>T%5uhZ-IxA3Gml)8ujN{>_q+Z1z3jJE$Gcy@%KBGL*E@V$(*ODW3>n+&M1TI> z|Ko@ZLq##eleN4Hvh{Y)SNOI6^QrF)4Eg)N)-v4u)jS*8Q2lY_dk$zFm4S`To0M65 zCvbT$72Nc1%ehq&4xyQ|>TH!4Xf1r@zx#XTGOjb;D}I$$?VY#v{T{Cqzb5Zv_72}R z&2G7h)3tc^bzj;KJ$vJ8Ho>y3u_EQ*l{nVunGE1jr6nAdtN!kmlUoq{{B!tmlVx@$ zF|He&uIg7k{j=^*mF>dME1sUu?E3dyPMrDAg-_30R>c2ZclzhwC%f%q@_!t7m)^R< zo>P;l;^l*XJ-Xe3tLpy#?$5nn^XHsW$0#qeJ#<2E}h%=MbmfDZ*T1 zef^rIBtzD7CW#)sC%NIbyP&!Mk@YA2A9ep`WmLPUa6`5LtOJdz=a(=rfO@#w_O#gW zgLXN56T0;I`0>xzUH0GaKX$!b5UTc4>EHX6PpdEfsEYRS_pg@!H^Gtxlyr1Jb3zIe zKnrz1b3&%V3*J3n&wHvQ>(iUb%Oj?USbmaR`f(+6Ug(ER&ck5%ypZ_e<#l`NH-AXK zeQf!^@aOjI40|Ge@Vt5W_22#JzwLt{ich_nc4+=xwY)n~8e3|!*c_r17a6VWaMdto zyS!!ZFYs{dMDsu1uLKDX>Za zc}y!H>->+pf3J9Wc}>&$Qp7>0LD_#TpSte#{q5CCS{tsbftoB=0$Xo{!sad(pPBK0 zR~qvNOKyia&}FQyS)W|IYs!OaU*G>%vidz=iKI%eU)%6oC-QQt7VCx)06wp zKZmQgJgNfC%z!8K_IE$gT^D}s=fl&PlcHDzK7_YgOxZZ2MZjs7W5(9>Sr*(4ooh1t zb9}nDY}+PQ8s@y1@9+J8Khl4EJ8r+ageUyU!vD8Uzkhh^^!re&Htkmz?5n;pxSf|< z0g(c&leLq(_I_SK`@fAR%MHzb<}FR$v54=lwSLa=bKJMJWe=;Rnohm@9yA+d%*bNS z?QqZVkLzmKI$1^AdXNh^w;E5rvhifq{+9vvU)6P=*TNa|cPQ?NWN_*R&kc#~GrnNK z?GW+ji}d|N{`)`b-e3N(7u2#l@O1A!|HIO!)7K^bJYxzH4es`~*ecOxe39MzvwlM6 zKUR<+bfp7GX=*y7hV|9>*kG1r*BCCW+{{&oSkPRl^uILpzxXTz?uPtbZ`3JjzIlS>#H%&ni1JeL|I^LQJN~}RdwqXn_TJS$ z68my*uaH0gKC=F=LiJ@c*c_3}?QMbJ?D0dR__En6dCM;awTWugx6NMZN5!ny1g*-^ zoYfT3HsKj~LyxRe-M=dz|E%@8XV{mz?d0W+Z@yN&_*ymd_RgkXpqcRCda*0_bN;$| zX#JSJEm!Vd-Rs{Kzt_HhZrA=Gjgceu4QT&=-lN@OzO&O98M<8aPq|cug@`1|$iNRg}ZcXXh&@|Di_C-(bYPGdY_ ze7sop_(s@d%$KgqxmWbp%$X|>Isgc?Gv$8pddKLm+<)urzy4o%_Sf;gI`ukL?2d;i|&rMD(rof*K*B^rATG%gZgd+3k{X!HWS5OgL(=J~@` z^Y+i4_rqe_I`=IqlN9FGbuyT3EN0~?yHSdpyl{%EUvszB=ww2ov&U;L)S?jZL9bQ;*BE-km(i6!-yKW|v~JU@=TVP2HPB4&sFDJrf# zT_p^Yd+KNMKDf91_50Q*EF0F|-?G<&agq^JO&s%e&_Q4vM>!uj?LWylVe5SUnAhwL zOSPlT(^z)Y8&4B`@LTH2Li_V}`wD*kp3feCT~XWFVK3jVWe?Jj^M|tk*8jV*FPJ2X zF~qBsHl6RiuK-&MkmKvVRQms`P4h$_aEt%?dhSu<>FUA zGhbU&zDwGo^EzwyF8|3Kd14HTTfn9HEnkhT={5HCvs>)Kw@7`|xXn4`T#OuZ{5H;m zKQ^zGkLdrgx^15b%arTP)4v~UW4lr-8}nM;;GgicS}Vqw7yijVv*f{j-A#5fC(2oF zO#OG{_m1523DMW=`U-3RgXSEf=pq-zUSR_hoh4-@CW_l@`l8OyQicWi5Zq z3v;ReoVm*snMxB0Ov`9EGfrEV<9lbjU(Ac}-ulwYCl?OSPS0~2` zEPANF(6FfTyM=%6MXzHzvT5__vh3YeVcBgb3k41i~iaD8}eS9p5OOKq@#_a z;!x|qsW)|*gBjMH$vb$C`}W4qhrXTQ6kuAn|No>{{0#5U#B8st0r$HCmL8Dj{wL_f zQP|k~>c3NPI^ze=AjU&m_~N4kRb(%&=VP=0EoPKuxXP*U-qY}a)jI#VzZ$=u*eCIC z8BYUfEn^qcu04FEuf?aGkMX{f{^d<$#7?eEFY^^)$t>uh`*Z?#9>VefzD zOxB9Y>zr3AHDrpmbT3nyrs;D8(NR42eEqo#7X;ec+M3q<mBRQ zFE2hawa$^J-u7*Lt^bEY*_apC*E>#HxPJTBs=Z+^#N*`U53l|_|M)+rBU3@Gg3G=2 zl2Qf#({iE?>?&Lu?|PnvgY){Y$vyRwQWsL#>dyv)78+bz8Nki2x^?=(QzG@?d9x)t zsmK1@KEZOrSr#TdRDq)t}$*}76*AIXAP_RhZ={?IX59Yn!QTNfP`TyQ-|1WZj z%&dP?H-C;`T$I%Ch-1gMPt^>M9{->J``~rPCF$HMTTgFnDEP8Gm1V_=AOBOg`W^T! zef81A#j&MoKd#xEn3yc6{Nka1ietiy|0|C!oe})t>`C>-PZzy?CDN?;Qr`Mkq>XUG z?RmY|kNYuR{Q;S1G1#*D_=mOjoz38-ojubg&#?XTI3u!DVudrS&s)%Ts%g#_^yjZ$ z_S^eNd>iu|n{vVD{_^ho@5`SMf^VpC`|I#W>d8e=!M_kPb|HLUGGh*_g8k*om;HMF zF3~VBh+uzkDfoQVT)qoS>li@0XzpfQU;J-Vubpm!?2b$8m99IPUP`@L{?T6i*U=vn zv;GHtw0DWA|KGc!?N4&e6~=nZWHods)F!NT!qhyD#e~(?XY^kf7kjeOakl;>)$tc|6TUhPgk!}`uFwo2E2s*6%XJ7VZ z^9$SftpYJG!(THnJkzL~`}lw8s~`WoL*f^G*rX#^RocdI;Qjmgn#@YR&`#iDiM#rbsn!oMVxyF8v7jSP_z|Z_4lIPO4e`;0?K2ypVe!wO%TKal@ z8ZM+WW(ZZeYpgVw!pm%Mew$svTKmlZ6V@?)-F;bpQ{KXN|Nq($&kldf zzL@)NKk)nf{rx6B&I>kWoZ5QAmN!Aj)mk=GBe?b^yq}Y9)?l>xV#vaC&-Eo;x|n8l zF_$&2i)ViIep%ka)8F@Z-zq)3Z2vN$J30(a@3qc=+W4*-n=kyR`X}aFog-9UKbB0aemJVpliX+p0rZYoMxZ*jbDIA~+Hu8kL*k-+c{{GXaPs^OtuP9BMKHWUf z{_Af${&-NQseb#{C0_BtWOv|#(~x_^09j~+A5b#`{%x!!X! z=H8n9hRh0Un+h(598qfReARWJ0A#P#^_}y!&HHla;`P-j3ub=!`X@L0Is1v_y}wrO zUmIZm_4bWrD+B&7*yePZK`eK@5w8Mx!0m@y{k)J$y_X_0**0WU&jZbGU3kVPFhQ0z zYf)Wn@T|6NFZCDMW>(9-%AZ!}SW@b1|GwV5{{N{o;}^drjTx34UQjO6wQY5->xr%f zPu}KyoVvc|sdoI&sBc@(o!I~2_fKwy%a<>&tj}!+&4JBcTA!P%yS;FV>x*BLOKx4_ zGjZPkQtHMYK8B`?JR-Y~N8Vm`jX@wsOsOc4+2O)<(806fBDH=m{g@Qu-R7Kro*%{L z5Z0camBqziU}H09*IfOu@No89T`mo?7VN+Oo|!?<_NxxVgB#8M3^SA?bCI{*FuZYP zn7yd}=NVIm121keGwfO%KjC#jRw3v5{`LRw@$&Mjrm=2u-JikMu=J-m14HuU_inkl z|Net+VH`@@W$>mW@y=!&R8JZ_)t6k=c(|0pSHfrmfg(wV^*tsYF3ukx9cJ~rDq_U zZ{C0Z&CKQ?WVX0L@#S|%;jgQ|?|b!K!34Bm=hU?$sj~$?T=>FWEZjZuqkYkf#4pRY zO)hDg&FZjWCg%ZIqC5N5XuZ|KvrH3A*@AkxR?c-a+;;WcO3hD?7R9=n{`soYuqxT% z^sE2B@7sv#=GRv({(EnE_R`y`xAZU8@3r;p*8l6g-q+={by;rTwD zHvRr>afbBYziSLPSgivuMU-Z*glxI--rgTq%iFMT3X*5dKM-v4=y{PbU_(U;K3H%`_)aZOGm*1^E+hbfT%kbgS!@tb4iqaW=ta8#(arHZu#ste9dBwJ8 zPnhq0cJ%va^X>^sj#i#4|8M)a^8bxyeo#iOt z_!W9(<9`PmYX*lW3=D5l7=B#I_X2GZw>ifpVf`!e%9WD8O)>4DIW|Ux(+nGocC)gJ zA6u-`u*-;np?)jF6&^-K&0qW`31SUvIK1wi6TQ9@;?8x~H*fQ7r>@Yi$E zI=&hHf2%Iq+y1qGX?r`+K^}C(m#mn=i#~>hy1zE}{9j@w9;vYK-~auVKU3cL%P?>I zxUxR;Uu)^&_Lj9F@W=dZ#Z~FUeQ%>5l6h`&yao?Ch2I|DQcpF86pf*T#}* zm*Q&6pFv%8x9;kE_ssKa7qu90H+XeioPYJ|RX>;in=i~|(BtsjKkrliDTWQ69=~b@ zX8pT<{P>0%(|M8$#dx>$=KoEe{63F|(c?6OLk&lRZp~}hyohaFxBEZk^aHHc^F@QM z#Jl&@C+t;N5GAb8eI=9O@-cyFo~~yOpD6xs_C1?puKm7GpwczFJpSwRzG^k%FZ?aOz!Sm*Xi=# zf8K71rN2NE#qZC=9Je;UuKc+E{=3c$%Uq6tg3SzTYJU9Z-Q)G#EB-mOs53nhe)+Qw zzv4M2fuJwP*%e<*Kes0Cg?{Ijmkg&L1n*y!`1DPUiE@L)pI;%3t3KX0`|GCOaPUWd zbDgYV){UtExA^Nb_l;&Qe%O+Ej^Uo&iJJ$rL@ z&WAG}ZXDQgc*{4fAMf^j1ci@w{LieOFtRRDa+E)d}xvcZs?FOXYBoV|Tm^ z8eVYSd?l0d{k^&=5+BPAS?B17{9PO#e_P`9`c$SJ2LGH@PkxyGqJI7L&t`geML)?- z&}2+L%=qqpkFV$rx4#DxTws$$z7V)UTRa{?ZYf#6vXVHwe)+{>$b+TnGDWOR~L%^3GS_*efdCZ%Zq?+ zCZ>)#47vOMuVUM)u^{V@x}-xcXx{3oVNq70=2gZEVt-fhtpuID?O?tCztyf~J3C!v z{y$P^`1b0&^=9{%nGB7Nk8Sc_CEhaJBvJY9zEr&Gq(hLR=XuqM*O}`pKYaXCdhpAa zl7Pn^uaC9XZA}KPoZV>e*u!vt$Cdp>Wp>}#6AoxTkiPxv>qCZD^94@rWxw_kMdmW{{KI&cE4Bhe_)t{!j6BSy*5tkuRE{5e)!IjMVrraa{hdGdj1Rh%6CeB z^L9(DO`kZ6Q9-5D;L@)B5)`irfFNW_WvT{h|N=OOO1x|Npr%!>#r4 z6F%<&&EiCyW>~RmzuprYlo_00V+NMezC?w+4uO}xrFwKP_|f+URvXMqkFC{h?ti}i z+=mYZxxsHk6*aXonghPR7F;mD?s4>wm*)FY{}#_$6hEKyfSBSd|1Ji-`8(Nc^6T|} z2z*s9%qrwuE;fCWxsJz*UtfRgZ)Uim7V_6hxm_;LhcRQ~vKRNaS2O)C^M7*BE}X5w zYu-8cdw)OYfL79PnYie@^X>EZ_ee5WRVpi$x;H%8<1=ygCP9U{0bfC@(#zky2sdYZ zF%PjOotxqAWyFef5r^+zVC&Iq^S-QmiCB&f(gj-E+-&+o_lU}rcGKj()q z!!#p?S4TuF;q4LL^}dVmzjuGRwNx?L-Fbx~crf!yy!sXSzJKrceA=tAcb~7wIR=JL zq0;Ae>l=d>HI_^?I`2Fi?E1*$Y+sR1hNmx|%=}hv%eF)FSN#8<>lql9*Zt1!e15a+ z@BG^DnGD~u8BARN``3N{&%p57ltF6o|NQ^@3DVsvU#uk%(pt%OtMmMjgw!@b4aSW9kJd?_I&*HAAK)=fUXER9bo@e z`2Uns3<8%{@ut5Ge)bu(+G$(B)5?s;Kch>;8Wx34%q{zJX_0$rC?kLD{?$AU%U+aE z1zTpsAhq~@Ae%$*m))lrGP>%*B^e@sUrHw(XXMuUfoZf&r0oAerD`&YAd z;Rla?zAm=ySgxP1uWNl}fho5`V9m?>|Go%pv%eb)o~-H8|65VlKRJ@2>ybBTmF}an zC)Ks<^FsIBt#{wdAaZniUHE1O6V~UO8A4hmLER{izvrE&OEPS;s!1)dFLn2}*~||v6Jz+lF35>Pv4y3( zM8@Z`($trKcFqzy#ajP=_HqV>;X_Il>t zfAzaq86@iFM6JJE_P6}sM{b4#|Lpzi_P_m=?)H!MfLqpHzuRB$@7Vg_x5yj^&^0dt zPIDgbx;*{q3ecJ2-|cE;9{zlFeNLTUWX$XR)xQN95>{XK*X%D|{QU3iy#9E>>%ZdX z{+D6+um~jj`FDN%yZ;@x&ueyO|JB|cJxA`o*s8-RRaLicm1@3PAyL3EW0^#e>)rY8 z9EvS19>Me5gM))RgY8O}-ZBQCs#hNt9sP3Gzh#GbKpP%fBu)lLtoPKM>k6XJ{#v@> zUc2V)Es^yh5$~BltL0wPZ~dybH0N^KU+LDbc1C%Z(*6b}?v?y(29~+Et;FW4<)hEL zE<%>`9TPsp!(hjm$i^^#&EAdkn0A|9P*hlV^W*vSYvQ|B{qHYZuU``K|D1i?{2OZ* ze=4kaTF%g7&2q-nRaUeFe4qsA1PDb{4WlQDx}V~&U+83HXIRdt(jwq==W(^vfjvJo z)`>fHEqSnbN1Vs6T9NDAp8NYBRVlvw9vGvq^4G5Gb?cJ*?uC0Nd?|*U{2*|A%GyaX zzjPQXHCNkziDdY6=(qphS4Llh>*Z=*ZhyLDMb(@|eO>CG@_wAYx-{lu^PbzG2fs+> zq+8#}Z;+dyoo*7Mdj@agT z?|ea}p0l%a7}uMplD8!uT?xEBUv5)S!upkuFc;F$i+!iE+Gl)eRF<2%YXkfDEGo~g;`cY+qP*N zJu`f5>~lN~;=0Xy&waB=dHed;&)qLO`;%Yqny9~W%IejtJ6}iMcy%UqsoSg|yRV<8 zhy)6&{SB|%d(YSE*Hz!n7?rKaMjrfELz*x&M*(>Fm>74LUO8 ztqyqcna_+xyqUK{70;({nEMtSs&Y=%p-xr*rX4+YUOTst zcV|E6o&7g&zW;yf&9qC#j9VQ4_eOK~&fr|I;@=%thR{n^it8>|TP{1t4^9l5=f1VO zb=53<#qss8e@0I=ntMP?)_u0@!MgqVHRqQK3Ut*aK7I4YAv*j^e_ZX^AM>=;9Xx)q z8a8xaVgsEAB%Sg4)c5^bvDf3xE59zk#&F`*l_h)UnbaKSzC2>a|dcVOgJZ&~dewwY9UAX7oMZ zZG60J+h$PwRo^SS`{Gsjog2CedKTXYQO%`qwn{hZQEw} z@@%oJQ9FYNyHniK;)Uf-U%p@6wn(LP#wNRdSOS=Et96wh!`~lz^Y^RtW&Ppy^lR9* zusn-D=3Y1R^zUw$8BbN+vh#JjDrEd`t0tp!3DXTH@DXXcb|MQhJN_;GDZc20{k!`; z_rh=AE_;9d+BLSe>N}Q&{KdlEGiFEhdckn(8vmN*e^Q$Fy3@LzlLs#kR-rH7q&TdTZL(I>9e~Q{+-F z_Me9(B9FJ1(heJ}|NL{dxF(;dP^nA3li8nG(1dmInHSe(nfARb`odqZ{y*&Gw3HsbFG-3!# z+y3CeA&Yl&c8KdTzy105@^(;5|J&Pn5;y)WJ^Y*T%5VD#OX9ER?GkHEV>BpLnPg=j z`CeT0{Kp&TAYsVA_U@J#mig!7WoNP~TzUCF$Gv`uvPRoptEcl9?0V^~3qc9D+TP44Hu}irKTZCQjTlf4RHejx(v?+V9)mIqmW5 z)a?1MmR_Dh8Zd@sMKcWi2%U8S|HtCGX7ZB=3o??gb0u{X|S zy5iHg=xzPCsqCH4A3B4F9(L6~_!I{U*CLjf>Ysa$$MR>&mYAir z$K~Jp*`&w+|G&|vA-CErPJXe}f6vXVN9M3wg1719Txs%M|FbH4j46v51I)2D3 zbFSpy8d&^%zxj^qZ}ICNXC@Si{`Kr)Vs0)44XLiV{*oc~6}y1kATV4*dA; z8@tv1$Pae2o`1_GM=?9R4O!x<_G#P8x4)`0pIFMl61Uwg-`mS3f(PYin=>o8sBk59 z{ZrW*-pkOvP~PcQz3i{l-wPKm%!ur%>HPP-%2wZ0>D8Y9ul7x1Rlso;ThL$7Uek-! z`?3^wTw0&T$nY$q7Rw}(!k#Dwt6l$`Tp40>rf*+7Wt%?SNw>d#{|sI0Sj52a(2DU3 z--6C{77RW&%o--kFxY)qY9GSh(7B3j!K*)M%G7{6Z6W>}v4oH_pg|H^MGnXms^{qOvaWqRx$V*A!JKRCh2kW(VF z$;u+~cIaD!rL}A@PbhAAzHmih{_mf++1MQfZ@pm9aFPb!FeCfd_p9|zcE$W(ukFO;L94&E2G@W8lF!Cw&D1jC-NmEw z4x+E#Z>t0KgeK@6=o3(sU9R0WzfYp+%KhBGvCCh7pAXuM13q?5e!)Mz*Z1H2RsnSu zC(M{Rv$K?oPu|YvfzE{7OZSZ5&j0>*NiygZ(Uy?Lx!g`xKH+=kw=kTHWY|@xx8m52 zUB1%_*XM^n`LRxx;m?0{(CisIsBdi8*49=tze)JowQEJW?tkxIou6{U`}UbLXQsFF z%il{edhq$p`n+tbIz~^w7oYw@*4*_5*nfS#(EFDquY#h=qP6yQ4tBX92RCJPOgB8W z`I~>W{n>*LX05wl|7!WaZBBo$e*upxrKw+1c6G zcps>}48OfPjy-mJ-}=`RuK$>xdwCtRET}iz;d&~CeahWys-ANfynOjG;<S$tr^e3fUn(Y3=y;H85s=B ze(Evo_G=LMinN(U#-TP+vGkSrTlMp?EDUdM+cGljD9mR3UFQGb*xJ?=e~+zYonv2X z^Yu4mv3-YXm&E^NGg;Hp()LZ+zvb~Rm$!d>eSMEw)ka>ND6ry|VfLWik~*2}Z5IKG{*DD+wE;rmi`4zYvs87PrX?t z<`DT;XeN8Z?(Oofr>)C!_3qxQ-zool4)gTykKVm?zp*jf7_{*_mGQ=sHFce5m>KTv z-?{P6A35!}i~n9P(pupb3Em6;6t;=wQWR(t%XuNlRLyiXUGdbf-YIen1@SxQ-~Kiw zZ@NZ($(!Hi*Z<4qFHU2=@rzeuHE1_Bc$Ovuw8rKVY^rAe=R;)=4juamYMW|rjs`7F zYUY0I*XFun1$b@sqOwltoXz?9c730h_dkBJv-{$%Q=ly@3R(V_^K_XLN=m+Xe?5P# z@TAqAeMi)N4eJ=qpL_j1+P5+<{#nhcH~G%>S5H)jaqWNk1C+0p{C{e@O#2F7JjcZO zIyv?KoiEqFIjBDIqy7p%rh{%GG92#zw@$y$%+NCjbxLRI#nrEm?SGXrA@w%j1n}u; zTjh=a<@T|DUkN%s4LY&Ic=U(6jda5E*0+Ys9dA#3$Z(+T$jqi)Z7%uCfxe+Ud_3x47FIRJ>1)WQ$nOykODm%w2V& zrEly7&hs${bpPvKw~W~RF{fUMKe1Y}^xtokEi(Iq*c$FtT6~1n*SG8$tGO_(aTipt;0isDZvZ^U2P(4f?^0zP$=^5VcY+mDJIsvLp!VzFI+ zn4yzKQ`Rvqkea8TBXww=dW7EsJ-?c#l6U?*61?}f=x=*1DEXu@O_*RH`F{Toh1&h= z|L1)w%t>KwIq*LA&dgu`{ulr8^Gy$$r{cW&L`e0%@8_LoImUh4Yjj)m?lsX%4=4LA zjN_F0FkLm+{fVl}-&G&=FaBThJx8Fc&h&4*|L^O(plwruVxUQ;{WVW|oo=59Xk68@ zC|+Q>+pLNDUm0`yn)zM#XT3OnLP98D+Ji6JGBFaz@4Q*GvCSd&%F_6Kzq5A1kIFSp zXJGJ3V_0#_Uisg@fc3V&Y%c}i)PS^hb`XLcF_rJ}z^w|@> z^!x71Hv8AuyQtxNzS?yj4M&e(p`eERxz6ex#gFbeJc&<1ZHa%njU7t4a&Awy__tq2@rAfcch6hU(xdKg-hmFKH=pt_lqz4WpULPj z?f%T2edTHE+$F&W=$_7WZs1xjHY@1=QN|6=JJ0U;FgxDu_Uxd`Upq=KihNu5W_^0} zitCaNIT{W#KP%il7AAOL$%_4*Ce8Nm?IN9-YQ!10GB|g!FgCmXj}d45vNGOe#nJyEDH;tT%q4*==iBT4-btP1IFl`4!cC?YvseAR ztP?Vsc#qXbe_7qZSG&qyQYfK_!6bJ7ocbK81G&{~o0wF7-8cJtU_o1>CZp-8y8oN! z$lBF2hdBQSRZe0J7OYER=W_*IkH5wE;n1>P?xj+TH_ds*Vz6}e^S3>vssEQO6nkYF zwps58=Y_9)x|(gg>Blgik0Ctpw~`;j^ySIM_Gg{keLfbutVN6~97xcL{^8kjY|G5SaFF$>-_27(504uVNDr zSXUQp%P{%l`uFo)Ui@O6+!x&KJ@MoH*I%Rl|KDGdnyn-4(EDe5=>7>ym#(S{=VOre zo2t}md%5nvxBJ>q_J(uw)Xg4Gefght_y5biS03N}b-R{f!p_+1-}isr{`Si%!3%i!8mzhD-#LT3GB;RDl|75YEs8$DV5X7%ArhP##wG5*XEC9@7_Jt<@` zVcdB?NOLdvFuE5E?H-ea9SkJ@|Elve@!cOKB5i?HcGGgkhFBEXF_|?rf_rhGps*6YW@HMQt|38SY!Q^rL z5BvK+;u&Z3-v0l)zMhZa!`6E9rue_#MgRQ%|0!`|@XIs*9;y83U;nrNOtKMogUHck z%nJS!_pjn(_$#4k$#9A1$0E%?q5V&*Ia;cCvE3% zvzKSQkoQ;X)9?C^ot6w7rx*ja;LZl z5;r(oVEbSGA1}j$C-r~lyZS8-{@tzoH2i<9>bAL3AFnTDZg{e^i+RC{kNh_Z8D1Rs zEMmHn%W&J|U#`sM?aT^S{^nXT_^{Pos!Nq&aqWw5)nRuCRqqQhWt!Bg&F=8xmv7as zRq;klIhG7g*YYoIs(He@=f}bOjG$dr|NgJvT#~BI5K+v~vM#>hDZ`3I_gxRV+~#Ze zab%?=r~_bR$$0N?k#1KOchVpA-bnT{ZR>N^Pnz;?-tiA%{h{BVFRv||cYgoRqso7d zU;pR+ghim;F=`uQZ+#GZYES*mZ9akrwmiP`b9Mb$9j+Hb;>Y&C$+r6TKK4%J%Nwqe za&qfJmYo0o&sEck&AvZz-Ln7Z&zz~5IgN3`*SfnmUUBR?7RsU>@^{zacc<20->>Bk zKY=e{W6hZyrz`90u0Q=#_vx$iH&}zP@$|{(ts{ zZC>Et%wzjcc5HuWFadlN-@Xqf4X00^p8dtX(%;ml^l|*Or~c}1ZU6qYIqq@m*th4e zZ%_Z5Q)^utd3I62`odd#6_)UINxaSceZ$J{?hd1WpJf)m(`5QAaV_)qk9+&Mb_v=U z8-HHcKhFKJXQ1UF^fER7M7GsMi8;*6*q1YT`HY0SFIH|%^>}^f$Gmte&}K03A%dqbrTneRstxRt z_!b--yfv0TJ8VN{MT1qYtY+KFSN6U2LQLC^{SCLQyETF7`i!0;@s&T-)E(wJGk#jK zf9<+;QnpX_eb?_ksc^lw>MmzMiI+p^e;ZeplL|)1E{3Xkyf|Uf@Z;6>Kl3`&-`$IQ zZ=CzJM($q4|7GuuKb%_qc|J2k+?N`;dpq>M*B^PE`6DT{to!oq9 zXj?i%i1xbcDSz4Z-m@~){FuI9qqaxwHTW#(3%@VUpJwMa`Ri>BkP7e5`V0);{wMx? zRONfE{`nDGh64*{+JlwrN5$}Ktlxa@^<&d5F{;K3-s~%VKfR=9%}quQPFY*qc>lxO zZI9W%{bAR}*kYo#|D7abiw?8x>4NgT^^#w_{{E`Ys-8EK>5JLzhAjfx!>z=KmMIxDb}#+ z%F_E+br^gP{?uUzO|<&G>!N$c;3B7@c|iPuJt*VewD z?>2GD-z{eG$3cB&58D?n|Le)fT)uqyS~v5hTRa?6|CeMgk$e>Y|L&`q4D0&+yC$a; z@bmLO?>qAQo;`o+v9H&E*4^E(L$3QZqt3ePp%F3tN5Ag!x%jARTj5m4hesdX`Ftd8 z**;+>kHa3u44)+)?)$F(wylrLN{hitYZsTSXh}%P%td`%ZF4~D3R!EtZZ8U6|LY!T zLzChWm$#-2EX#HaJ9%vO=-%+WbKmcK^QR$=4Dx1#R6TjSoQEsmh6UN?x?jcx9l8^U|cUVwHdEj|$EcxwS@t;~u8akD=> zV^Ao3A)x51lFPHN_v259Co{wi_tn0i-|BK--}bAHp6%D1HEU1&0}Z?{^Vi-C>J?x7 zlI;5Jy-}!)Xzqfp#fx}D3PZLuarJeX1sn~$$faeWb<082-Bt98;ZM8emO8IiEMRUB z2~$bu*<5v9+x4 zjTv_=IJ`5cWx}F2e_r1|1nPMN{hoGLU*G!wukCZ>=09;ay>mL``TAY;fs=YZzxC(m zm8cD@+7~p%|CZZ1pQNX6YPP)Xi=AEg{oCRQuT42%{|6=7oBoZDuq)uK)6;@oQ_}tNPj+9*6Sxppj>WU90Oshc^6q>0cZ* zrSpz>X_H84P{nD62PO?I6Sh4xa=oAat6s2KD>M53@97WkEzfQ}9t>JisPr%E$d?08 z_exkAXWm)g>&!99NE+y6k@(zbi=_Y2f@T^}!L!J;70W#KaG5~Ipn zwft9m=k1&S{roe_9S5dVy+0ISF1z{Mm;W|xrMJxXeK%jY{O{_Q|C|2Jd$ahzn7XKf zuzSP5c@m$Qga2t>tPhU9+t;w@=+~UGJ5DcH#B|qPH~Z__{d%i!{l!OBxtHU&?1;M! zI@uF0d^M7x>l*ut!#~(%EE)9vJe#h^ce@hQ(Yn9fp<`;u@Ad!l{^++9Z|d|_chG44 zzpEkZ&ueFfNixh$Qg^Q(_kP_L9He&O(?s({_QI9hI2nW6b^Y0oZSlS5m$3f&=Xw9U z{$82S@--N=|E}h*r$}K^NMXgEzt_Hh{1$!x{NBBx>JFEC{%^^xeIGA!^~3qc zECJpB86{3I1jsV|+Qwi|^>u6S)FoMuy`pn&&(g5wW(d$L-I%j$@#?+@+wK2J?)&@k z{Dt@5FTVfI9oujALR>v89CPN@6;d!5aXWFPUxvWxJ_6=%#w!mqx+C786AS+TYA z(y`T-87G{Xr@pECjYn;(fOY+n$``FimRy(~=X3T;@*@AY(?j>@pAHNtsh`YzpyXd< z-0wxdNEGTT!K_qoe?@b`jpe&OipT#?`rGy8-n}P3KDTWDar2D*+p=!{n7`{^j?CVIjCcE9 zCro|+f7S81R;5|~kK)SX>ZMu#D}6f_ymyVZ!$H~mKOTfAAJ8oiTmK+F{OXha{{M^K zJZ%32XcwYZ~b;Yk*`B_>F7WzNWZ(sWR zgz(b8{jxJvyfzDc|MQXG=J(W{oo@2I`(GDq4BvAi>Z{T!ouB8WTj$l?Z&!QYy*hmT zx4OPrefCN3U$md@pRb>LL(0kMM%kZK-ePP1y6jIk-~6_{;Pz+plli^-9{b(us@M8i z|7_XH+nFH;1kO3m{wUw;T)mC|q4E6e&%gKgZ90B-yJ?`h;`SAViTD0WF&ZeiFdv52>c(_NiZ$T*4A+zO@yZO7mT7;#?)&d{PyMx^>d^Cn;>WMcGEUHmidui_`@1#2 zBc9%{`X1AJX+e(vC@jc`h4E;M)vJq{jZ~Le_S5- zzvAoN>wmWGUVpc)JoA8w)PnV&FC2Ytuk`5Ykd!; z`9D+hfOz~agROs`EIz)h?|sl*wuYoXT?|ax+GT^KIZ4Y@T*^c*Jqr1#T&BLaRDPk#t~4Gns=z-bJg$9^No%^ z?*49H0!mX;Cw&j?Pq(aGG?(jZ>CBqv{Wp(4o*tPh+OSSUzhTxif6kNn{I1d;pLf<4 zTAto>Ahqzm-4?fDJD*HN`o#@@HYKmhS^a58soegGGi&Bsoll;o<`!+D`~Gd_>gVYlo%NT>{v3U( z)6L6N`})yW`Ayp%|35eJyWig>ZH)^vl>d1@w=dMa=x^^Dx%fcJEC>Hv9s6^B?mwNL z*JJH>U#@)4zS>J11}wSz|I61sT6yxVyxsSj$KUU>yD~H$TiUWWLVCfwqQ=*AeoU9l zN{T-|RXaRv)x-G|q6j%Hm1J2ue&-x`+Mi^kOj*ZP&k zqOtFqy5F1`F(0-TKAhg=*K!YD?RIDIML0ttx|4RPyMPN z&yLsaDVU#f_p$!Bf73nh_hzO}SXc7*XMe}uz5h@4XfnS3_v`t$`~TgJynK28{Jfnz zma{n=Nvr+8bgG`8T~$sH_ktNeK;f;t==OPs$=9u<7#glU;jGzcxZso_Pw>1J{m=42 z_m5wns(xd$(2JE%>fN@!&^qpRWZurHFOycUQB=tFwiVgTCA##sy1Z@JZb1d!e4Xr? zm$&zH?@MiOTGqWk)XUJWv zdGPUf(({ck-5Qwt@0)+EmVe)9_Q-#pO-aG|4gcTERKEB6eshc2WBZCHt#{vls#IoB zQoH{#Hrx2$ou|g;e5;Pzzf}77r9MmV@BUrqOc)HTmhb<*_|m;ikK=z$QHs``bR&P7 zYv=bxEPLNRo<8yWKk=un>(gY-Lv^iI3(f6kn=!rSd1rmy#?o(Rw!Tf>s~4+1@4JoX ztWeioE8j*(M;A}8^%1YB><^eMesKMsveNS!4Np$S|0~N{+E6n;bld-r`~SawQWpO) zzV1&=OyAw93|-m`PZqP-{R!Xha^%fLrqb=tSM!v})<-%!%ryC5ufAS*!df8#?VR({ zlDoS0-(V0CEPQZqt?g=1nWb6`a6k@^pp*ZbG6f3D3S62{n} z5oxH9wCm%7XW#zp`~Byib=AWQhyJ&pv-$9S^6j5j8vp#?wCnbJi`w5$%BFu@e%=24 zSAW}oD>wdqs6MZ2a=%>n+qZ8|YBZ|kD`aH-=HK`GtJ^eNzp9mt0jWE!Gj6?n|M$h* z$dmO`_xIntQ1$m&=Hs1R`|s60`>p@ECd_i*%OBmL>$P=dcPxl1DRgl>lJ{x<|9|F> zUbH$-uAKHW=JXc-x|%Axl@b@b^TWWuQn<`eOrS{){ z|9j`DVcZ$f(jVP@_}}(-Tnc2!oL{rGVcph97Nfb{Hj@6^f9w65z9o=FulD`ftRFIy zw%z-0{FL+e?q6$|FI@Zm|I^4~VYciKrH}pE z|28fBQhD`tOr7AIzS*X4>hBd?tXc8m+_`h5mCOhJ{k}irOU06u{S^nN*3CEPRY>}- zv~&&o;x|9t-|T%M`{}pB-epHq7o_EOuh`F5@o~qL7vF8)*e{y>aW#R#!j)j|6B0x-hb6$EGJg|e}2EWIbIF6 z7T{^^Ri_N?e_QhJ8O(jsS^w*Q#^gu-zn8}SePpt$W>)NxcmKE6UT5+sJfFp&dF*~h zculZq+d|FI_TSUrmj9}@uS)0XWjGL5{cM+57t=GbZ-0D3yXVPWi&QgZb2)OR$$RIO z2?0wJ0_Prkta$FH{;Y78RlV~TEX*|ix5_*E>r9@izq9-8^FL_L<}*;q-T$w8IrEVx zWt&dE|No`lU+Q1{oqO9i*8e-5rEn&TK{Lx_=gM2Z|s=Rbj#*Mr82{(=RwKD>fMD@*hBMj`A{)1}9Z)l62-$d&mmxH# zh$*J^$L!AfyN~?#{M+~c*-TT`tcj(T;%ogF8x*6|D((L*^^Z0G*3=NIv)(M_k-Yz( zN~L*ILig&K?R$3o>RPebYZGRR$8^nzE6OxddQhShq7yO2oN2?H(w!%=FW)%d_~WR3 zSaMJ8<>Ys%q6`_q(w)B}q?p@muIGn_CJFzycQ}mms9R|D*B~RB=wez4_JGp08imZ$7_EX#bDd zH_~G}|*!D-+X*6a<;|Y{`334@BeZ1 z{`caS%|DLH8@~JD%b1XCI&n>Y)FJ*Uw{PCuxo$>6_{+cg_uO~YemKEBwff(4+xE)a zukU;}`pv{J`#1-K#LkUdTc(K?bqoBDcszgm`Bln{uWX)a{noGcsk;*qU;nY&-s#UH z@p}97E21CW?~P|>V3-nNx{{4SLW-BcpeQ->r_ZOI_kO-lR{Zz!$ML*3>p|{$Z~SW7 zkE8O6bstwV9$?E}r0_r@oRNWn!7O;Ot-{LGhd-+C?Ju}roWK9-6X*4Ncdw7Pk$C*& zeZ81Nnzs|n#J3cLV{~x7QN7eon9=9)> zUDLPc|IzzL>|T5Qd6b_tzf|?#-i?xs2aZLji5!Ti{$C900^IjsC>XvjCi2yejmQ6g z3a`KW!T9BI+50~d?)x)F1;4p@^GWxOMTW=a6#uErZQpqG{a5+tmKj+l&dIzC&xDLr zj_?1n^7SVbySs5$1Olf0=wJHXe!J$${3BZh`R?`q#{N<3qjL$ebW1nT@x(_q{kB{O9_k`%Bmw zI_?HC9Q|?pU#!*Z&+|p}>n8nC%2+Xf>L$1KtKR=ldGRqnKi}Qu$mRFH16*8c{*~|V z>bNPsf3`?{*WZ2b>ur>j>i+Dk@9X#}{%^L#|8vRqeH|Ul{rANMA9vb&K7Tl|d-|i3 z&g{yMqGVD#A1pgEKdG-{N69~7LBYZgC%7l)&i?cBd~$z>MfHzISKhnp%gp*!J2~#Z z$nV4cHll)p-|v)KC@IC&ymY!#eEng?$EDnp-|FuxQS`H`(orgVtaWTX+ksh!1E}lNrro>pm7gI?}nadtRitoAahutv7ca-@i)n*#5nH_o_2$6n}kn zb!vAVpW#2fwZ(saX77LVJ7L8~f7Soz?}SwC?rhrb+-q-H{o|3!&-?ZNr6r{-mwm7H)OFVHOnpA7eC=AXn;Vb+m$zTP@4arhUj3U@$Hn_oO@FqY?-W-5 z7V{@&XHn{>d8hUL(!cK6veEYXm*4yS{`mj#r&K{^^sref8>zZ*R}v{q5hW>i_j0H*bHWEWcOr-S2~o&z|00zeBhBZk+A)6W^|!o?rF%@zvt% zb@%S7%ky5iQtmJp)RTUb@*7lD-`Q95?3mlO`-Q*0b#$ye^1tu$cK12=YVGvp>s9sU z?=%E;irn>OqJG+3>HNPr{@<;X>Q{67J3PGVb4#_Vc##f*Y5A z-`^w1&~xmge(kGWl_&o$+TVY3m&$&@w>H-|9*;L~JpX-t^_x}4^*>jfnHK+Hjq`c; z%kTd$Jo^9V{l8h0{-{?fJ6>|puQ*ch$lv+Tcl)xGtlyyaZ%DJ_zVm8F=Uabxv-OER z@Bj1p=64y4j{JCjv$CvO>u0$ScE4Kn`P6KU8)mP6>PIst%#~tL z3hUqhBWkMZ)s@R@Z?^0{`?z~C!xaXN?K!*+9ea;X_%)AvPQBaZV$Hg|*|V6XF2?^k zs`w$3<-l9>_ub)fyLT-wT+nN|LGbSJ`H%cg)=EiEnDqbA`|0xb@`AI4>yO;KZkNx{ zabqp>1d+#)-+vVUyz{@~hmTv-wIc20Wf``ey$Y#s_yVGPB$N{grZg*YWvF zJ{NrZaMY98VV7)S?vn!-x!ctiGX&W-q(1WB81DP+_bS#2{Q@3#pAYO6-1y^jJ`2Nz zS=0CbdVSJ=_VEjAZ4Snzov6KkJoxN{Dl4(f$4ogHAH}=>Jw5z?@}v70{v17G zK3{Bth&0nA-_H8LPx0GwuLdeOckb^3^(U_Q-`vu$t4-|x($DjEO#8BY{vwmo2ita+ zFXvp7knvIfQrh|d0#jB0|NPSEdWC^O%3QlL{>trWpJ$i;@)SyLpa19JyQAOL|Gy3| z{&r{0aXs%H`8zjP`Pbb)9_;^7sUU29K>XsATkk3*v;I_mnqT$*^uKMZ6pzm5|F6&Y zTVL(pZ+UAYN!ATribwZ-8j+-=pJ;#PF#bfi;e+7&Aa7G+c zczj-FZ-K_Sc{?pOw>gCG>aySa{qw_rE05dzvRC}*T>j|E;^Si1>C`1ob> zi8ZzDW>O0N2Ts@BzjA!u_1kev)%U%Byzm>sY3p_p_ zYge4Sr{>Spil0BUtE>K9?qbN5xK-(Svfky5CdZBrwuZ(4;l|Q%nQif=FXu0<_;X=7 zYstip@|ka(j}&w}l%~G;_`I|Jaxa76;jaBR7RSfvD&z}fEc$Mr6!Fpj>x<{wtW$Eb zAzby;y|dWm?#x;vaUtY={Skvl=O0`AczL~i^8YIm6U@2pl$F2y^FNhW@t?}kNzcB; z+Z8_!$*(9!E2^Y`Xto&}jmSIBP{|`H#KW;y7YcI)Ia6YzOefx7?#vqmg zhi9TpAuJm#yBOYOt>Q?Sx~OpVjL<8&)3$Xpobx_%TWV7LbHm#@Octw`*G^UZ<14?O zBf<2v7^BRsI~S5J?%&qU@J+J5OHu#dbnjrsAj1P@T@Str)_>f2|NGy5cNd>&j6RpL z7*DDlvo{r0&=s~&{&|1Ok7CWfM?v+OUI*5&HI(+-+x}mf%81wW#$)SkU<7su>i+AYPP^ev9%YwgGQWoy|QIvyUKe>@2Kflgrvz(axf0@vNgU%lmPvxb_>VI}_Vp-vzdnEqa z>;3;XDht+szICeq;+AEP&d2|b{QM`F$wI}q{@>|ITWx;I&)R19|7cSE^m#iq-~By3 zRen)nZtef~x3}kCV)%3AYr6E__hxI0?7H6n75SgO|IgNrjvoih_sa=(FMp@S#G$h^ z;=_9FqlX%5XHV}~AG9U^#L53kmC9$0c+FPJ`5Wi${d&!9+D+*o#g$fVGXMA7TeVDR zL237X3#Eim=id5z+3~3>53FjFs=s*cT9~%y{|S%vtNCN9o-K2GXDwOuCpJUt-@;3G zd9GXw|1{q|&5I}g->dcFVyrpQmz5dYrv15J^gPS;*Dt$ySI^7}wl~S;eJGRl=cuOl z`9IgUO3Kd9`%!pJ^&kKKXr2%ErRwG=9-F`a`sL5RPm2HlzLl5v|9qPxKTa=EYVeoe zTYhlfS02-Uw!vcgzw_V!d-~t>mxXih{)|mu?iZiEmmmJ|`C_KDyYtjVng2K*J#NgD za8~4bj??^KGHte76d(BZvPeXInjhl1=K0&p-#@O&yIk!eCb%Ee#n>qH|J6tF>AzQR z`V${5mCxXD^i@XK)kj@AVPS9bdsrE!o3Z`Sboi zds^{t_H?k8SHCuAo9^G2Isfmc>uR50hlgJ4p1*CbMPbszw`!(ltO@Dq&#m@eHjvBZ zKe?{xzq2-%#Ku1*4GY!(uihtDD}Vl(ck9Mux4frLW9+g2aky^2hkxD2&DpHWX3yo^ z>sD8_?tS`&)$Ql@O9~3kfAh2Q%d@Lre!YMHHS^2UPcx-|*Ktj~B6%(Tc(;D+imm&l z7#Mh0-{#oBd9r?Az9HxP-xa$8OEn%Z`g^T(DZ|I^{hfc0=>ID<`S#s z$*lOT0rxlc8@41h>|E8$;Ozfz`b71Ir>|s1e65%jx^zd}$ji$c${1w|GWHL`9-zk|K}>d@9pSV{`}46lOK=gzTfw+xa)Op(VtK-`MK+L zXzrO6RduW0-9Pg4A_Kz}vn5M%PT212mSPl>wNB|4`r`V2;YMDQ zO)bBtS%+(L-F*7-O8C|taW`4LHmavwbM=Y$Rtyc?y5rumWAXUO?K|%6GGJm@z#J;{ zyVU33YS3W)=9}x{_F84Gkys3B2HlP@blU&pX7uD^U4g$|?hIVd^5McybM;=)#a~|S zT(dE$MsNPkLIZ6^lZy)izuSTO25XOX`AIW;`Nzz_;9%dw!0^IhswP9|a?UTQ_Y8h( zIe++VH}5H^aJ$0#EpLD2{T1AAYM(4N_u+ced+GSQd%7FUo$gzHy64%rH*nY6cWVyZ z`|)P~|K7Vy)ygHHT8#DEs;s1ab+=1J-Tw62`8>Z@r~Pik*B7*KNnNdX z#dF_pyLs;|FUo6a=8BS`$nz< z6RyOvF4*i-eC@-3o11r^Xfl;Py28OQJ9%c@-_k6W2(_Q@epmcj7_$_~kjPRx@l_+S@(fYSlWX3)A*JUcZaWA^KO}{`ZHfYLCa4 ziZ*QfB3pkol;PH;@;|lW*Irxyt8D$+#aVlKv0U2Sdg14brm{6`l6$ku_Ezb){8iic zy!v}3b56#qLQ^3Fmhzg%%^yF7*YCZu@w8m{RZz!x_4oMjsZZ?h#$9)7i2UR>GgYcP z%y!;C(^ADL_x>NbssmszOpj-?)46h44rJ-f(Hkzf4+aV(R}%h-}Mg;G?tf@m92aI@}+}I z$orYOo4=QJoQ%8s#Qy*73GPfGm3yD>1(i0RUWr|Qoy_~h?5gg%?U^616D0MdPG0!@ z{<51_wzleHc63OR4VTbiw^URtx;EIvyWd_@Z7om~mR~{;=)e_pp5aQGHMTzU{~Q z$9Hb$A3dpGqoAbZSND+lRzvJ=y%KAC~{^onVzkGsW*Mxqi7mGcj=l$Vd_@s9$NkSAZA{-U zbX&E4Rym)UD!8fM@k=?!O!Z$#On&w6uPe_cSN_+Jj>!*xv_3i}f8L&Q`}@=WNAHOZ zz563uwQD8ggCEuR;(vWldL$+IQN2FeF8huA|2KyP1wXd`Pp<#-ZNA?5JNeT#%FO?E zTADM>*VLnzNs2+t`tM$OU(+<%|Cg4#Ri5kWt$Jy{<&pS_HP?>TXgP%UGK5b0A9>zh z)oAmTUi(EqpX~4a9cTLQkw|}P+-=v>KekrZY-Ci}>)-XU|L^XiYtQXH-!Fb!F1&vV z!i7Dh*K4=?C9N+yqjpSQ@Z&G>{Jzuu^W!VdEISte)z)Cz zk95_2v!(ujzQ0F?-TrUYO~u6(|Lsfv-t4#`n`FQ2{r`@R^L0P71vgqB+1GiItyU*D zqG$g^wuVjf-c64Gap2Gr3*L19&$nJno&Na$N4mV8Y-&Ib#|4Ifxoi#DvvL?ZGWH85 zge8Sv31xPeee%Cn|3z13hxwfU7Ckm)2)SIseBi>>y$ol**Z-=$Jpb_d|9Rgxhi~^t ziVyQ;0CnqMyGJWo%wuS>a&f`TfF{&n$VJ*8}je(J6XHd`HrVB zufp_m^-4-gd~%hE&kCKldNG4~zt7(7<_r7zZ+-Ed0MW;ht3KU*dH;XfC5{Q=2Tn^f zeR{ILV&YUitFF%ctKAzx@$-A~{=eIQ94XzMzoBH^XQl4V%UB#{cd;_wp2HVl`t!Y) z_{sWR>lJ<3J7(1BPOCio+iqUfwTDd0U&bxo^u&HG+lN!x;g4T_ug$o`QSkNE)qCdy zrT3;Cop=9q|NQwTOiz@V+9vR9ldU)W>3=&&+2Q{C#%)!;r=Jir+ka|b_c`aQmH$rL zy-$6#{_gLIlP*n}@NDmem5dKs%gg-ltXZ<-#^-?T>S;R1pZ^ZEe4hON;;K^|4cTF` zj1#ul3N6U{bpIPi!@5cTQ{^|G6=c}_?0R_Z+1(cRI2y86^)YCi)0Mf!(Xfhj1#IH~ z)2Zz1|K>?CwoUn?>U(7Vo$r79{qz1mO3(M5#`fpq^M94kYD<>B{jq$~|1ByH{LlNE zeYF)@(EH=_U-_LCno4Eaj4m#3YQE7{tN#8@pw_q&HJB{hg~j3KzS<1se=|?kpWL=dlPNX}Ws!h0@*;uky?-m; z*Z+IJ`uVbDY+p+4ZgtrE|NDIV_xt+fRf-Ds%k+JZ&6nB=nzddv{l78)9qIX%5AJ#P z|FtYwhp0{JJpnD_#PKl-=*J3q;kE5^z`R$@cG?~(Xzf9^juHur1$ z|2&wfZQ0B3{}21yn0kJz|8eJY@4fF@VV%V<^WW_eR7fi4+tt=kHkJ4L^gq|j|9H>e zrFWH4;`N{ZW=mW4T1?;buA^b0>c5?K)sKJIyxF9($M4hK?;lMTf84rFd(|eEP?uuy zhWY>Ru3z?w;mO|rKkqB;vQ&>fHedX2cgId;hEIzca_6<{JwE^Z+xwa~$GN|IUbFp| z^uB)Plt0U-I_|pk^|`&$w{QQ{jhC^k`TctS?fU=Hg3FiJ+gCq(G*5~_aQ4@~)5CUI z{Cu&|Q@0`M(f^t3?|M7(?3sPd_UOklDvu#*9l!- zeCF2rw~sU3Hwr4u|82Rtt0egL_HEmq`PqBywR`#K{gsu$z28`tX)>gmGJrab%a^e~ z`E9cG??n4whvOdY|MTPL4w>EBj3VI-C)_z}eyq>y?6|p@HSA^`sD+dH=jf^jo0t6j zZ(TXJA!lksM#gXT`hVBw9+_WdU(7Cd=hkMa1Ipt1zNTDHF0-PJGh`U75lvR_@^R_yle+mi#CJnuPNxbjWD=KJejzx~mfEDjg7LO;$t zzjLX7E${bOH|g`GZ$4h1yKCb6{XU8(Li@H$9k4KsWm&sM+#)HT@xb!?-xqqVX5F&3 z_lNSb_HXVn-siga{>wy~n%{HW{_eZoKjJUB3GN7aYajYYeiLkJUhEoAMqu>&|DZWZ z5eA#8hZho_|KA?VVy60EaQEqdyazX}W_NM%P?OX(Wk_4kyw8s_Uv%ZsEw8LLB=_%+ z*qwNH=|%OUKaSs90Gj3D`B^;S-sPjI6Og9V`4ex{i+^1H_p{u^DNmN)`@eSW+8g)o z&Aao?Yx?^7x;N*NuV*nXVQbiTk2&|4efEC$O^51h{YziG`g(t}l0(?v{|}bO&;PS_ zeH2IT(fQweL;t0JJ++*z(YMR~?ewaz!TL43=2pGcT`_y7FynQi{2mkiFAzP;XGZsp*Ur#}17KYz|T z@T|D`{QplM%a^`qf1JkBAjshQ$eby`N3542LOoQkZq^INb(`|#Z}she^Fwhi^T+Mt z|0g{5+x72HxaBhM+@t$nr7zR=Js!XP;_COGH~5_YU=hKv-*t3u#oyZh zXN|MDt|+}O((_?t(288Hy8o8Jw;yY7G3>tfRB63$vOW7F@&6IMhTnYqcU}x-X!vUO zWM|##RYJ3W9GCrnBlnZ#r+?!0!oSyOgT@%7jx&%eSO6K-H+dOe6`Y_EkFDd2Tibk|NNr+$IRZa(GVC7 ifzc2cDIu_-@}GT7&%&T{S(Cni{OsxK=d#Wzp$Py3Q!RY} literal 0 HcmV?d00001 diff --git a/src/main.zig b/src/client.zig similarity index 100% rename from src/main.zig rename to src/client.zig diff --git a/src/entity.zig b/src/entity.zig index 073d339..e69de29 100644 --- a/src/entity.zig +++ b/src/entity.zig @@ -1,123 +0,0 @@ -const std = @import("std"); -const sdl = @import("sdl"); -const Game = @import("game.zig"); -const Graphics = @import("graphics.zig"); -const Time = @import("time.zig"); -const World = @import("world.zig"); -const math = @import("math.zig"); - -position: @Vector(2, i32), -player: bool = false, -enemy: bool = false, -controller: Controller = .{}, -next_update: Time = Time.ZERO, -sway_x: math.Sway(f32) = .{ .amplitude = 1, .frequency = 0.11 }, -sway_y: math.Sway(f32) = .{ .amplitude = 1, .frequency = 0.13 }, - -const Controller = struct { - const Action = union(enum) { - move: @Vector(2, i32), - }; - wanted_action: ?Action = null, - move_units: f32 = 0.125, -}; - -const Self = @This(); -pub fn update(self: *Self) void { - if (!World.time.past(self.next_update)) return; - - if (self.player) self.updatePlayer(); - if (self.enemy) self.updateEnemy(); - self.updateController(); -} - -pub fn updatePlayer(self: *Self) void { - var delta: @Vector(2, i32) = .{ 0, 0 }; - if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_UP)) { - delta[1] += 1; - } - if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_DOWN)) { - delta[1] -= 1; - } - if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_RIGHT)) { - delta[0] += 1; - } - if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_LEFT)) { - delta[0] -= 1; - } - if (@reduce(.Or, delta != @Vector(2, i32){ 0, 0 })) - self.controller.wanted_action = .{ .move = delta } - else - self.controller.wanted_action = null; -} - -fn updateEnemy(self: *Self) void { - if (World.getPlayer()) |player| { - var delta = player.position - self.position; - if (@reduce(.And, @abs(delta) <= @Vector(2, i64){ 1, 1 })) { - self.controller.wanted_action = null; - } else { - delta[0] = @max(-1, @min(1, delta[0])); - delta[1] = @max(-1, @min(1, delta[1])); - self.controller.wanted_action = .{ .move = delta }; - } - } -} - -fn updateController(self: *Self) void { - if (self.controller.wanted_action) |action| { - switch (action) { - .move => |delta| { - const target = self.position + delta; - if (World.isFree(target)) { - self.next_update = World.time.offset(self.controller.move_units * math.lengthInt(delta)); - self.position[0] += delta[0]; - self.position[1] += delta[1]; - } - }, - } - } -} - -pub fn draw(self: *Self, delta: f32) void { - self.sway_x.update(delta); - self.sway_y.update(delta); - const transform = Graphics.Transform{ - .rotation = Graphics.Transform.rotationByAxis(.{ self.sway_x.value, self.sway_y.value, 0 }, 0.05), - .position = .{ - @floatFromInt(self.position[0]), - @floatFromInt(self.position[1]), - 0.5, - }, - }; - Graphics.drawMesh(World.plane_mesh, World.texture, transform.matrix()); - - if (!self.player) return; - - Graphics.camera.transform.position = math.lerpTimeLn( - Graphics.camera.transform.position, - transform.position + @Vector(3, f32){ 0.0, -1.0, 4.0 }, - delta, - -25, - ); - - const ORIGIN_DIR = @Vector(3, f32){ 0.0, 0.0, -1.0 }; - const INIT_ROTATION = Graphics.Transform.rotationByAxis(.{ 1.0, 0.0, 0.0 }, std.math.pi * 0.5); - - const ROTATED_DIR = Graphics.Transform.rotateVector(ORIGIN_DIR, INIT_ROTATION); - - const target_rotation = Graphics.Transform.combineRotations( - INIT_ROTATION, - Graphics.Transform.rotationToward( - ROTATED_DIR, - transform.position - Graphics.camera.transform.position, - .{ .normalize_to = true }, - ), - ); - Graphics.camera.transform.rotation = Graphics.Transform.normalizeRotation(math.slerpTimeLn( - Graphics.camera.transform.rotation, - target_rotation, - delta, - -2, - )); -} diff --git a/src/game.zig b/src/game.zig index 9cae0f6..0b084a0 100644 --- a/src/game.zig +++ b/src/game.zig @@ -27,7 +27,16 @@ pub fn init(game_alloc: std.mem.Allocator) void { Game.running = false; Game.time = Time{ .now = 0, .delta = 0 }; Game.keyboard = .{}; - Game.mouse = .{ .x = 0, .y = 0, .dx = 0, .dy = 0 }; + Game.mouse = .{ + .buttons = .{}, + .x_screen = 0, + .y_screen = 0, + .x_norm = 0, + .y_norm = 0, + .dx = 0, + .dy = 0, + .wheel = 0, + }; Graphics.create(); Assets.init(); World.initDebug(); @@ -46,9 +55,11 @@ pub fn run() void { } else err.sdl(); Game.processEvents(); - World.updateReal(Game.time.delta); + Game.mouse.x_norm = (Game.mouse.x_screen / @as(f32, @floatFromInt(Graphics.getWidth()))) * 2 - 1; + Game.mouse.y_norm = (Game.mouse.y_screen / @as(f32, @floatFromInt(Graphics.getHeight()))) * -2 + 1; + World.update(Game.time.delta); if (Game.beginDraw()) { - World.draw(Game.time.delta); + World.draw(); Game.endDraw(); } } @@ -66,6 +77,7 @@ fn processEvents() void { Game.mouse.dx = 0.0; Game.mouse.dy = 0.0; Game.keyboard.keys.reset(); + Game.mouse.reset(); sdl.PumpEvents(); while (true) { @@ -83,8 +95,8 @@ fn processEvents() void { }, sdl.EVENT_MOUSE_MOTION => { if (event.motion.windowID != Graphics.windowId()) continue; - Game.mouse.x = event.motion.x; - Game.mouse.y = event.motion.y; + Game.mouse.x_screen = event.motion.x; + Game.mouse.y_screen = event.motion.y; Game.mouse.dx += event.motion.xrel; Game.mouse.dy += event.motion.yrel; }, @@ -104,6 +116,9 @@ fn processEvents() void { if (event.button.windowID != Graphics.windowId()) continue; Game.mouse.buttons.release(event.button.button); }, + sdl.EVENT_MOUSE_WHEEL => { + Game.mouse.wheel += event.wheel.integer_y; + }, else => {}, } } diff --git a/src/graphics.zig b/src/graphics.zig index 141712c..b1b9602 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -13,11 +13,11 @@ pub const Mesh = struct { }; var window: *sdl.Window = undefined; -var renderer: *sdl.Renderer = undefined; var device: *sdl.GPUDevice = undefined; /// Only available while drawing var command_buffer: ?*sdl.GPUCommandBuffer = null; var render_pass: ?*sdl.GPURenderPass = null; +var render_target: ?*sdl.GPUTexture = null; var shader_vert: *sdl.GPUShader = undefined; var shader_frag: *sdl.GPUShader = undefined; @@ -30,10 +30,12 @@ var transfer_buffer: *sdl.GPUTransferBuffer = undefined; var transfer_buffer_capacity: usize = undefined; var depth_texture: *sdl.GPUTexture = undefined; -var msaa_resolve: *sdl.GPUTexture = undefined; +var fsaa_target: *sdl.GPUTexture = undefined; var pipeline: *sdl.GPUGraphicsPipeline = undefined; var window_size: [2]u32 = undefined; +var fsaa_scale: u32 = 4; +var fsaa_level: u32 = 3; pub var camera: Camera = undefined; @@ -41,27 +43,27 @@ var to_resize: ?[2]u32 = null; const VERTEX_BUFFER_DEFAULT_CAPACITY = 1024; const VERTEX_BUFFER_GROWTH_MULTIPLIER = 2; -const TRANSFER_BUFFER_DEFAULT_CAPACITY = 4096; +const TRANSFER_BUFFER_DEFAULT_CAPACITY = 4096 * 1024; const BYTES_PER_VERTEX = 5 * 4; +const DEPTH_FORMAT = sdl.GPU_TEXTUREFORMAT_D32_FLOAT; +const MIP_LEVEL = 4; const Graphics = @This(); pub fn create() void { // Init if (!sdl.Init(sdl.INIT_VIDEO | sdl.INIT_EVENTS)) err.sdl(); + if (!sdl.SetHint(sdl.HINT_LOGGING, "*=info")) err.sdl(); + if (!sdl.SetHint(sdl.HINT_GPU_DRIVER, "vulkan")) err.sdl(); // Window and Renderer - if (!sdl.CreateWindowAndRenderer( + Graphics.window = sdl.CreateWindow( "", 1600, 900, sdl.WINDOW_VULKAN | sdl.WINDOW_RESIZABLE, - @ptrCast(&Graphics.window), - @ptrCast(&Graphics.renderer), - )) err.sdl(); + ) orelse err.sdl(); Graphics.window_size = .{ 1600, 900 }; - if (!sdl.SetRenderVSync(renderer, sdl.RENDERER_VSYNC_ADAPTIVE)) err.sdl(); - // Device Graphics.device = sdl.CreateGPUDevice( sdl.GPU_SHADERFORMAT_SPIRV, @@ -113,8 +115,20 @@ pub fn create() void { var window_height: c_int = 1; if (!sdl.GetWindowSizeInPixels(Graphics.window, &window_width, &window_height)) err.sdl(); - Graphics.depth_texture = createDepthTexture(@intCast(window_width), @intCast(window_height)); - Graphics.msaa_resolve = createTexture(@intCast(window_width), @intCast(window_height), target_format); + Graphics.depth_texture = createTexture( + @as(u32, @intCast(window_width)) * Graphics.fsaa_scale, + @as(u32, @intCast(window_height)) * Graphics.fsaa_scale, + DEPTH_FORMAT, + sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, + 1, + ); + Graphics.fsaa_target = createTexture( + @as(u32, @intCast(window_width)) * Graphics.fsaa_scale, + @as(u32, @intCast(window_height)) * Graphics.fsaa_scale, + target_format, + sdl.GPU_TEXTUREUSAGE_COLOR_TARGET | sdl.GPU_TEXTUREUSAGE_SAMPLER, + fsaa_level, + ); Graphics.pipeline = sdl.CreateGPUGraphicsPipeline(Graphics.device, &.{ .vertex_shader = Graphics.shader_vert, @@ -144,12 +158,9 @@ pub fn create() void { }, .primitive_type = sdl.GPU_PRIMITIVETYPE_TRIANGLELIST, .rasterizer_state = presets.RASTERIZER_CULL, - .multisample_state = .{ - .sample_count = sdl.GPU_SAMPLECOUNT_4, - }, .depth_stencil_state = presets.DEPTH_ENABLED, .target_info = .{ - .depth_stencil_format = sdl.GPU_TEXTUREFORMAT_D16_UNORM, + .depth_stencil_format = DEPTH_FORMAT, .color_target_descriptions = &sdl.GPUColorTargetDescription{ .format = target_format, .blend_state = presets.BLEND_NORMAL, @@ -161,20 +172,20 @@ pub fn create() void { Graphics.camera = Camera{ .transform = .{}, - .near = 1.0, - .far = 1024.0, + .near = 1.0 / 16.0, + .far = 128.0, .lens = 1.5, .aspect = 16.0 / 9.0, + .matrix = undefined, }; } pub fn destroy() void { sdl.ReleaseWindowFromGPUDevice(Graphics.device, Graphics.window); - sdl.DestroyRenderer(Graphics.renderer); sdl.DestroyWindow(Graphics.window); sdl.ReleaseGPUGraphicsPipeline(Graphics.device, Graphics.pipeline); - sdl.ReleaseGPUTexture(Graphics.device, Graphics.msaa_resolve); + sdl.ReleaseGPUTexture(Graphics.device, Graphics.fsaa_target); sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture); sdl.ReleaseGPUBuffer(Graphics.device, Graphics.vertex_buffer); sdl.ReleaseGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer); @@ -190,25 +201,22 @@ pub fn destroy() void { } pub fn loadTexture(width: u32, height: u32, texture_bytes: []const u8) struct { *sdl.GPUTexture, *sdl.GPUSampler } { - // const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window); const target_format = sdl.GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; - const texture = sdl.CreateGPUTexture(Graphics.device, &sdl.GPUTextureCreateInfo{ - .format = target_format, - .layer_count_or_depth = 1, - .width = width, - .height = height, - .num_levels = 1, - .sample_count = sdl.GPU_SAMPLECOUNT_1, - .usage = sdl.GPU_TEXTUREUSAGE_SAMPLER, - }) orelse err.sdl(); + const texture = Graphics.createTexture( + width, + height, + target_format, + sdl.GPU_TEXTUREUSAGE_SAMPLER | sdl.GPU_TEXTUREUSAGE_COLOR_TARGET, + MIP_LEVEL, + ); const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl(); { const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer) orelse err.sdl(); defer sdl.EndGPUCopyPass(copy_pass); - const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer, false) orelse err.sdl()); + const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer, true) orelse err.sdl()); @memcpy(map, texture_bytes); sdl.UnmapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer); @@ -229,6 +237,7 @@ pub fn loadTexture(width: u32, height: u32, texture_bytes: []const u8) struct { .d = 1, }, false); } + sdl.GenerateMipmapsForGPUTexture(temp_command_buffer, texture); if (!sdl.SubmitGPUCommandBuffer(temp_command_buffer)) err.sdl(); const sampler = sdl.CreateGPUSampler(Graphics.device, &sdl.GPUSamplerCreateInfo{ @@ -237,6 +246,10 @@ pub fn loadTexture(width: u32, height: u32, texture_bytes: []const u8) struct { .address_mode_w = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, .mag_filter = sdl.GPU_FILTER_NEAREST, .min_filter = sdl.GPU_FILTER_LINEAR, + .mipmap_mode = sdl.GPU_SAMPLERMIPMAPMODE_LINEAR, + .min_lod = 0, + .max_lod = 16, + .mip_lod_bias = -2, }) orelse err.sdl(); return .{ @@ -261,7 +274,7 @@ pub fn loadMesh(mesh_bytes: []const u8) Mesh { Graphics.growVertexBuffer(Graphics.vertex_buffer_capacity * size_mult); } - const map = sdl.MapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer, false) orelse err.sdl(); + const map = sdl.MapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer, true) orelse err.sdl(); @memcpy(@as([*]u8, @ptrCast(map)), mesh_bytes); sdl.UnmapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer); @@ -353,22 +366,19 @@ pub fn beginDraw() bool { Graphics.to_resize = null; } - var render_target: ?*sdl.GPUTexture = null; var width: u32 = 0; var height: u32 = 0; - if (!sdl.WaitAndAcquireGPUSwapchainTexture(Graphics.command_buffer, Graphics.window, &render_target, &width, &height)) err.sdl(); - // Hidden - if (render_target == null) return false; + if (!sdl.WaitAndAcquireGPUSwapchainTexture(Graphics.command_buffer, Graphics.window, &Graphics.render_target, &width, &height)) err.sdl(); + // Window is probably hidden + if (Graphics.render_target == null) return false; Graphics.render_pass = sdl.BeginGPURenderPass(Graphics.command_buffer, &.{ .clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 }, .cycle = false, .load_op = sdl.GPU_LOADOP_CLEAR, - .store_op = sdl.GPU_STOREOP_RESOLVE, - // .store_op = sdl.GPU_STOREOP_STORE, - .resolve_texture = render_target, + .store_op = sdl.GPU_STOREOP_STORE, .mip_level = 0, - .texture = Graphics.msaa_resolve, + .texture = Graphics.fsaa_target, }, 1, &.{ .clear_depth = 1.0, .load_op = sdl.GPU_LOADOP_CLEAR, @@ -380,7 +390,8 @@ pub fn beginDraw() bool { sdl.BindGPUGraphicsPipeline(Graphics.render_pass, Graphics.pipeline); sdl.BindGPUVertexBuffers(Graphics.render_pass, 0, &.{ .offset = 0, .buffer = Graphics.vertex_buffer }, 1); - sdl.PushGPUVertexUniformData(Graphics.command_buffer, 0, &Graphics.camera.matrix(), 16 * 4); + Graphics.camera.computeMatrix(); + sdl.PushGPUVertexUniformData(Graphics.command_buffer, 0, &Graphics.camera.matrix, 16 * 4); return true; } @@ -402,6 +413,23 @@ pub fn endDraw() void { defer Graphics.render_pass = null; if (Graphics.render_pass) |pass| { sdl.EndGPURenderPass(pass); + + if (Graphics.fsaa_level > 1) sdl.GenerateMipmapsForGPUTexture(Graphics.command_buffer, Graphics.fsaa_target); + sdl.BlitGPUTexture(Graphics.command_buffer, &.{ + .source = .{ + .texture = Graphics.fsaa_target, + .w = Graphics.window_size[0], + .h = Graphics.window_size[1], + .mip_level = fsaa_level - 1, + }, + .destination = .{ + .texture = Graphics.render_target, + .w = Graphics.window_size[0], + .h = Graphics.window_size[1], + }, + .load_op = sdl.GPU_LOADOP_DONT_CARE, + .filter = sdl.GPU_FILTER_NEAREST, + }); } if (!sdl.SubmitGPUCommandBuffer(Graphics.command_buffer)) err.sdl(); } @@ -410,7 +438,7 @@ fn loadShader(path: []const u8, info: sdl.GPUShaderCreateInfo) *sdl.GPUShader { const file = std.fs.cwd().openFile(path, .{}) catch |e| err.file(e, path); defer file.close(); - const code = file.readToEndAllocOptions(std.heap.c_allocator, std.math.maxInt(usize), null, .@"1", 0) catch |e| err.file(e, path); + const code = file.readToEndAllocOptions(std.heap.c_allocator, std.math.maxInt(usize), null, 1, 0) catch |e| err.file(e, path); defer std.heap.c_allocator.free(code); var updated_info = info; @@ -419,38 +447,38 @@ fn loadShader(path: []const u8, info: sdl.GPUShaderCreateInfo) *sdl.GPUShader { return sdl.CreateGPUShader(device, &updated_info) orelse err.sdl(); } -fn createDepthTexture(width: u32, height: u32) *sdl.GPUTexture { - return sdl.CreateGPUTexture(device, &.{ - .format = sdl.GPU_TEXTUREFORMAT_D16_UNORM, - .layer_count_or_depth = 1, - .width = width, - .height = height, - .num_levels = 1, - .sample_count = sdl.GPU_SAMPLECOUNT_4, - .usage = sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, - }) orelse err.sdl(); -} - -fn createTexture(width: u32, height: u32, format: c_uint) *sdl.GPUTexture { +fn createTexture(width: u32, height: u32, format: c_uint, usage: c_uint, mip_level: u32) *sdl.GPUTexture { return sdl.CreateGPUTexture(device, &.{ .format = format, .layer_count_or_depth = 1, .width = width, .height = height, - .num_levels = 1, - .sample_count = sdl.GPU_SAMPLECOUNT_4, - .usage = sdl.GPU_TEXTUREUSAGE_COLOR_TARGET, + .num_levels = mip_level, + .sample_count = sdl.GPU_SAMPLECOUNT_1, + .usage = usage, }) orelse err.sdl(); } fn resetTextures(width: u32, height: u32) void { sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture); - Graphics.depth_texture = createDepthTexture(width, height); + Graphics.depth_texture = createTexture( + width * Graphics.fsaa_scale, + height * Graphics.fsaa_scale, + DEPTH_FORMAT, + sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, + 1, + ); const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window); - sdl.ReleaseGPUTexture(Graphics.device, Graphics.msaa_resolve); - Graphics.msaa_resolve = createTexture(width, height, target_format); + sdl.ReleaseGPUTexture(Graphics.device, Graphics.fsaa_target); + Graphics.fsaa_target = createTexture( + width * Graphics.fsaa_scale, + height * Graphics.fsaa_scale, + target_format, + sdl.GPU_TEXTUREUSAGE_COLOR_TARGET | sdl.GPU_TEXTUREUSAGE_SAMPLER, + fsaa_level, + ); } pub fn resize(width: u32, height: u32) void { @@ -460,3 +488,21 @@ pub fn resize(width: u32, height: u32) void { pub fn windowId() sdl.WindowID { return sdl.GetWindowID(Graphics.window); } + +pub fn getWidth() u32 { + return @max(1, Graphics.window_size[0]); +} +pub fn getHeight() u32 { + return @max(1, Graphics.window_size[1]); +} + +pub fn generatePlane(x0: f32, y0: f32, x1: f32, y1: f32) [30]f32 { + return .{ + -0.5, -0.5, 0, x0, y1, + 0.5, 0.5, 0, x1, y0, + -0.5, 0.5, 0, x0, y0, + 0.5, 0.5, 0, x1, y0, + -0.5, -0.5, 0, x0, y1, + 0.5, -0.5, 0, x1, y1, + }; +} diff --git a/src/graphics/camera.zig b/src/graphics/camera.zig index 20502fc..4b37b4b 100644 --- a/src/graphics/camera.zig +++ b/src/graphics/camera.zig @@ -1,5 +1,6 @@ const std = @import("std"); const sdl = @import("sdl"); +const math = @import("../math.zig"); const Transform = @import("transform.zig"); const Camera = @This(); @@ -11,7 +12,11 @@ far: f32, /// width = height * aspect aspect: f32, -pub fn matrix(camera: Camera) @Vector(16, f32) { +matrix: Transform.TMatrix, + +pub fn computeMatrix(camera: *Camera) void { + @setFloatMode(.optimized); + const xx = 1.0 / (camera.lens * camera.aspect); const yy = 1.0 / camera.lens; const fnmod = 1.0 / (camera.far - camera.near); @@ -23,5 +28,79 @@ pub fn matrix(camera: Camera) @Vector(16, f32) { 0, 0, -zz, wz, 0, 0, -1, 0, }; - return Transform.multiplyMatrix(projection, camera.transform.inverseMatrix()); + camera.matrix = Transform.multiplyMatrix(projection, camera.transform.inverseMatrix()); +} + +pub fn to_screen(camera: Camera, position: Transform.Position) @Vector(2, f32) { + @setFloatMode(.optimized); + + var x: f32 = camera.matrix[3]; + var y: f32 = camera.matrix[7]; + var w: f32 = camera.matrix[15]; + + for (0..3) |i| { + x += camera.matrix[i] * position[i]; + } + for (0..3) |i| { + y += camera.matrix[i + 4] * position[i]; + } + for (0..3) |i| { + w += camera.matrix[i + 12] * position[i]; + } + @setRuntimeSafety(false); + const wmod = 1 / w; + return .{ x * wmod, y * wmod }; +} + +pub fn mouse_in_quad(camera: Camera, mouse: @Vector(2, f32), quad_transform: Transform) bool { + @setFloatMode(.optimized); + + const matrix = Transform.multiplyMatrix(camera.matrix, quad_transform.matrix()); + + const pi: [4]@Vector(2, f32) = .{ + .{ -0.5, -0.5 }, + .{ -0.5, 0.5 }, + .{ 0.5, 0.5 }, + .{ 0.5, -0.5 }, + }; + var po: [4]@Vector(2, f32) = undefined; + for (0..4) |i| { + const x = matrix[0] * pi[i][0] + matrix[1] * pi[i][1] + matrix[3]; + const y = matrix[4] * pi[i][0] + matrix[5] * pi[i][1] + matrix[7]; + const w = matrix[12] * pi[i][0] + matrix[13] * pi[i][1] + matrix[15]; + @setRuntimeSafety(false); + po[i] = .{ x / w, y / w }; + } + inline for (0..4) |i| { + const a = po[i]; + const b = po[(i + 1) % 4]; + const c = mouse; + if ((c[0] - a[0]) * (b[1] - a[1]) - (c[1] - a[1]) * (b[0] - a[0]) < 0.0) { + return false; + } + } + + return true; +} + +pub fn raycast(camera: Camera, mouse: @Vector(2, f32), plane: @Vector(4, f32)) @Vector(3, f32) { + const matrix = camera.transform.matrix(); + + const local = @Vector(3, f32){ + mouse[0] * camera.lens * camera.aspect, + mouse[1] * camera.lens, + -1, + }; + var global = @Vector(3, f32){ + matrix[3], + matrix[7], + matrix[11], + }; + for (0..3) |i| { + for (0..3) |j| { + global[i] += local[j] * matrix[4 * i + j]; + } + } + + return math.raycast(camera.transform.position, global, plane); } diff --git a/src/math.zig b/src/math.zig index 07ec24b..731f22a 100644 --- a/src/math.zig +++ b/src/math.zig @@ -123,7 +123,7 @@ pub fn Sway(comptime T: type) type { const sin = std.math.sin(dist); const cos = std.math.cos(dist); var len = length(@Vector(2, T){ self.value, self.velocity }); - if(len < 0.001) { + if (len < 0.001) { self.value = 0.001; self.velocity = 0; len = 0.001; @@ -137,3 +137,31 @@ pub fn Sway(comptime T: type) type { } }; } + +pub fn raycast( + origin: @Vector(3, f32), + target: @Vector(3, f32), + plane: @Vector(4, f32), +) @Vector(3, f32) { + @setFloatMode(.optimized); + + const offset = target - origin; + const plane_dir = @Vector(3, f32){ plane[0], plane[1], plane[2] }; + const dist = plane[3]; + const dist_mod = dist / dot(plane_dir, plane_dir); + const num = dot(plane_dir, plane_dir * @as(@Vector(3, f32), @splat(dist_mod)) - origin); + var den = dot(offset, plane_dir); + if (@abs(den) < 0.0001) { + den = 0.0001; + } + + return origin + offset * @as(@Vector(3, f32), @splat(num / den)); +} + +pub fn limit(vector: anytype, value: f32) @TypeOf(vector) { + const max = @reduce(.Max, vector); + if (max > value) + return vector * @as(@TypeOf(vector), @splat(value / max)) + else + return vector; +} diff --git a/src/mouse.zig b/src/mouse.zig index ce870a2..3734c99 100644 --- a/src/mouse.zig +++ b/src/mouse.zig @@ -3,7 +3,15 @@ const key_store = @import("data/keystore.zig"); buttons: key_store.KeyStore(@TypeOf(sdl.BUTTON_LEFT), 4, 0) = .{}, -x: f32 = 0, -y: f32 = 0, +x_screen: f32 = 0, +y_screen: f32 = 0, +x_norm: f32 = 0, +y_norm: f32 = 0, dx: f32 = 0, dy: f32 = 0, +wheel: i32 = 0, + +pub fn reset(mouse: *@This()) void { + mouse.buttons.reset(); + mouse.wheel = 0; +} diff --git a/src/offline.zig b/src/offline.zig new file mode 100644 index 0000000..26a2566 --- /dev/null +++ b/src/offline.zig @@ -0,0 +1,5 @@ +const client = @import("client.zig"); + +pub fn main() void { + client.main(); +} diff --git a/src/server.zig b/src/server.zig new file mode 100644 index 0000000..3995b10 --- /dev/null +++ b/src/server.zig @@ -0,0 +1,5 @@ +const std = @import("std"); + +pub fn main() void { + std.debug.print("Server started!\n", .{}); +} diff --git a/src/time.zig b/src/time.zig deleted file mode 100644 index ff505d5..0000000 --- a/src/time.zig +++ /dev/null @@ -1,51 +0,0 @@ -const TimeType = u64; -const TIME_UNIT: TimeType = 1 << 32; -const TIME_MULT = 1.0 / @as(f32, @floatFromInt(TIME_UNIT)); -const Time = @This(); - -pub const ZERO = Time{ .clock = 0 }; - -clock: TimeType, - -pub fn tick(self: *Time, units: f32) void { - self.clock += durationFromUnits(units); -} - -pub fn past(self: *Time, goal: Time) bool { - return self.clock >= goal.clock; -} - -pub fn offset(self: Time, units: f32) Time { - return Time{ - .clock = self.clock + durationFromUnits(units), - }; -} - -pub fn unitsSince(self: *Time, from: Time) f32 { - if (from.clock > self.clock) return 0; - return @as(f32, @floatFromInt(self.clock - from.clock)) * TIME_MULT; -} - -pub fn progress(self: *Time, from: Time, to: Time) f32 { - if (from.clock > to.clock) return 1.0; - if (self.clock > to.clock) return 1.0; - - const duration = to.clock - from.clock; - return @as(f32, @floatFromInt(self.clock - from.clock)) / @as(f32, @floatFromInt(duration)); -} - -pub fn unitsFromDuration(duration: TimeType) f32 { - return @as(f32, @floatFromInt(duration)) * TIME_MULT; -} - -pub fn durationFromUnits(units: f32) TimeType { - return @intFromFloat(@as(f32, @floatFromInt(TIME_UNIT)) * units); -} - -pub fn earliest(a: Time, b: Time) Time { - return .{ .clock = @min(a.clock, b.clock) }; -} - -pub fn plus(time: Time, ticks: TimeType) Time { - return .{ .clock = time.clock + ticks }; -} diff --git a/src/world.zig b/src/world.zig index d7b394f..35eecf6 100644 --- a/src/world.zig +++ b/src/world.zig @@ -1,82 +1,261 @@ +const std = @import("std"); +const sdl = @import("sdl"); +const math = @import("math.zig"); +const err = @import("error.zig"); +const Game = @import("game.zig"); const Graphics = @import("graphics.zig"); const Assets = @import("assets.zig"); -const Entity = @import("entity.zig"); -const Time = @import("time.zig"); -const comp = @import("components.zig"); -pub var time: Time = undefined; -var next_stop: Time = undefined; -pub var entities: comp.Storage(Entity, .{}) = undefined; +const Id = u32; +const Order = i32; +pub var object_map: std.AutoHashMapUnmanaged(Id, usize) = .{}; +pub var objects: std.ArrayListUnmanaged(Object) = .{}; pub var plane_mesh: Graphics.Mesh = undefined; pub var cube_mesh: Graphics.Mesh = undefined; +pub var table_mesh: Graphics.Mesh = undefined; pub var texture: Assets.Texture = undefined; +pub var hand_texture: Assets.Texture = undefined; +pub var camera_position: @Vector(2, f32) = @splat(0); +pub var hover: ?Id = null; +pub var hand_transform: Graphics.Transform = .{}; +pub var panning = false; +pub var zoom: i32 = 0; +pub var hand_objects: u32 = 0; +pub var min_order: Order = undefined; +pub var max_order: Order = undefined; + +const Object = struct { + transform: Graphics.Transform = .{}, + scale: Graphics.Transform.Scale, + mesh: Graphics.Mesh, + texture: Assets.Texture, + order: Order, + id: Id, + parent: enum { + none, + hand, + } = .none, + hand_index: u32 = 0, + parent_infl: f32 = 0, +}; const World = @This(); pub fn initDebug() void { - entities = comp.Storage(Entity, .{}).init(); - _ = entities.add(.{ - .position = .{ 0, 0 }, - .player = true, - }); - time = Time.ZERO; + for (0..10) |i| { + (World.objects.addOne(Game.alloc) catch err.oom()).* = .{ + .scale = @splat(0.5), + .mesh = Graphics.loadMesh(@ptrCast(&Graphics.generatePlane( + 15.0 / 16.0, + @as(f32, @floatFromInt(i)) / 16.0, + 16.0 / 16.0, + @as(f32, @floatFromInt(i + 1)) / 16.0, + ))), + .texture = Assets.load(.texture, "data/yakuza.png"), + .order = @intCast(i), + .id = @intCast(i), + }; + World.object_map.put(Game.alloc, @intCast(i), i) catch err.oom(); + } World.plane_mesh = Graphics.loadMesh(@ptrCast(&PLANE_MESH_DATA)); World.cube_mesh = Graphics.loadMesh(@ptrCast(&CUBE_MESH_DATA)); - World.texture = Assets.load(.texture, "data/wawa.png"); + World.table_mesh = Graphics.loadMesh(@ptrCast(&Graphics.generatePlane(0, 0, 0.5, 0.5))); + World.texture = Assets.load(.texture, "data/yakuza.png"); + World.hand_texture = Assets.load(.texture, "data/hand.png"); + World.camera_position = @splat(0); + World.hover = null; + World.hand_transform = .{ + .scale = @splat(0.5), + }; + World.panning = false; + World.zoom = 0; + World.min_order = 0; + World.max_order = 9; } pub fn deinit() void { Graphics.unloadMesh(World.plane_mesh); Graphics.unloadMesh(World.cube_mesh); + Graphics.unloadMesh(World.table_mesh); Assets.free(World.texture); - World.entities.deinit(); + Assets.free(World.hand_texture); + for (World.objects.items) |*object| { + Assets.free(object.texture); + Graphics.unloadMesh(object.mesh); + } + World.objects.clearAndFree(Game.alloc); + World.object_map.clearAndFree(Game.alloc); } -pub fn updateReal(delta: f32) void { - const update_until = World.time.plus(Time.durationFromUnits(delta)); - while (!World.time.past(update_until)) { - const current = Time.earliest(World.next_stop, update_until); - defer World.time = current; +pub fn update(delta: f32) void { + const hand_target = Graphics.camera.raycast(.{ Game.mouse.x_norm, Game.mouse.y_norm }, .{ 0, 0, 1, 0 }); + World.hand_transform.position = math.lerpTimeLn( + World.hand_transform.position, + hand_target + @Vector(3, f32){ World.hand_transform.scale[0] * 0.5, -World.hand_transform.scale[1] * 0.5, 0.25 }, + delta, + -16, + ); + World.hover = null; + World.hand_objects = 0; + for (World.objects.items) |*object| { + updateHover(object); + } + for (World.objects.items) |*object| { + updateObject(object, delta); + } + if (Game.mouse.buttons.is_just_pressed(sdl.BUTTON_LEFT)) { + World.panning = !World.tryPick(); + } + if (Game.mouse.buttons.is_just_pressed(sdl.BUTTON_RIGHT)) { + _ = World.tryRelease(); + } + World.updateCamera(delta); +} - var iter = World.entities.iter(); - while (iter.next()) |entity| { - entity.update(); +pub fn tryPick() bool { + if (World.hover) |hover_id| { + World.panning = false; + World.getObject(hover_id).?.parent = .hand; + return true; + } else return false; +} +pub fn tryRelease() bool { + var last: ?*Object = null; + for (World.objects.items) |*object| { + if (object.parent != .hand) continue; + last = object; + } + if (last) |object| { + object.parent = .none; + return true; + } + return false; +} + +pub fn updateHover(object: *Object) void { + if (object.parent == .hand) { + object.hand_index = World.hand_objects; + World.hand_objects += 1; + return; + } + if (Graphics.camera.mouse_in_quad(.{ Game.mouse.x_norm, Game.mouse.y_norm }, object.transform)) { + if (World.hover == null or World.getObject(World.hover.?).?.transform.position[2] < object.transform.position[2]) { + World.hover = object.id; } } } -pub fn draw(delta: f32) void { - Graphics.drawMesh(World.plane_mesh, World.texture, Graphics.Transform.matrix(.{ .scale = @splat(5) })); - var iter = World.entities.iter(); - while (iter.next()) |entity| { - entity.draw(delta); +pub fn updateObject(object: *Object, delta: f32) void { + switch (object.parent) { + .none => { + object.transform.position[2] = math.lerpTimeLn( + object.transform.position[2], + if (World.hover == object.id) @as(f32, 0.125) else @as(f32, 0.0625), + delta, + -8, + ); + object.transform.scale = math.lerpTimeLn( + object.transform.scale, + if (World.hover == object.id) object.scale * @as(@Vector(3, f32), @splat(1.25)) else object.scale, + delta, + -4, + ); + }, + .hand => { + var target_position = World.hand_transform.position; + var target_scale = object.scale; + target_position[2] *= 0.5; + const hand_order = hand_objects - object.hand_index - 1; + switch (hand_order) { + 0 => { + target_position[0] -= World.hand_transform.scale[0] * 0.5; + target_position[1] += World.hand_transform.scale[1] * 0.5; + }, + else => |i| { + target_position[0] += World.hand_transform.scale[0] * if ((i - 1) & 1 == 0) @as(f32, 0.5) else @as(f32, 1); + target_position[1] += World.hand_transform.scale[1] * if ((i - 1) & 2 == 0) @as(f32, 0.25) else @as(f32, -0.25); + target_position[2] -= @as(f32, @floatFromInt((hand_order - 1) / 4)) * 0.01; + target_scale = math.limit(target_scale, World.hand_transform.scale[1] * 0.5); + }, + } + object.transform.position = math.lerpTimeLn( + object.transform.position, + target_position, + delta, + -16, + ); + object.transform.scale = math.lerpTimeLn( + object.transform.scale, + target_scale, + delta, + -4, + ); + }, } } -pub fn requestUpdate(at: Time) void { - World.next_stop = Time.earliest(at, World.next_stop); -} - -pub fn entityAt(position: @Vector(2, i32)) ?*Entity { - var iter = World.entities.iter(); - while (iter.next()) |entity| { - if (@reduce(.And, entity.position == position)) - return entity; +pub fn draw() void { + Graphics.drawMesh(World.table_mesh, World.texture, Graphics.Transform.matrix(.{ .scale = @splat(8) })); + for (World.objects.items) |*object| { + Graphics.drawMesh(object.mesh, object.texture, object.transform.matrix()); } - return null; + Graphics.drawMesh(World.plane_mesh, World.hand_texture, World.hand_transform.matrix()); } -pub fn isFree(position: @Vector(2, i32)) bool { - return World.entityAt(position) == null; -} +pub fn updateCamera(delta: f32) void { + World.zoom = std.math.clamp(World.zoom + Game.mouse.wheel, -4, 8); + const zoom_factor = std.math.exp(@as(f32, @floatFromInt(zoom)) * @log(2.0) * -0.5); -pub fn getPlayer() ?*Entity { - var iter = World.entities.iter(); - while (iter.next()) |entity| { - if (entity.player) - return entity; + if (Game.mouse.buttons.is_pressed(sdl.BUTTON_LEFT)) { + if (World.panning) { + World.camera_position[0] += zoom_factor * Game.mouse.dx / @as(f32, @floatFromInt(Graphics.getWidth())) * -15; + World.camera_position[1] += zoom_factor * Game.mouse.dy / @as(f32, @floatFromInt(Graphics.getWidth())) * 15; + } } - return null; + + const offset = @Vector(3, f32){ 0.0, -1.0 * zoom_factor, 4.0 * zoom_factor }; + const target_position = @Vector(3, f32){ World.camera_position[0], World.camera_position[1], 0.0 }; + Graphics.camera.transform.position = math.lerpTimeLn( + Graphics.camera.transform.position, + target_position + offset, + delta, + -32, + ); + + const ORIGIN_DIR = @Vector(3, f32){ 0.0, 0.0, -1.0 }; + const INIT_ROTATION = Graphics.Transform.rotationByAxis(.{ 1.0, 0.0, 0.0 }, std.math.pi * 0.5); + + const ROTATED_DIR = Graphics.Transform.rotateVector(ORIGIN_DIR, INIT_ROTATION); + + const target_rotation = Graphics.Transform.combineRotations( + INIT_ROTATION, + Graphics.Transform.rotationToward( + ROTATED_DIR, + math.lerp(-offset, target_position - Graphics.camera.transform.position, 0.125), + .{ .normalize_to = true }, + ), + ); + Graphics.camera.transform.rotation = Graphics.Transform.normalizeRotation(math.slerpTimeLn( + Graphics.camera.transform.rotation, + target_rotation, + delta, + -16, + )); +} + +fn getObject(id: Id) ?*Object { + const index = World.object_map.get(id) orelse return null; + if (index >= World.objects.items.len) return null; + return &World.objects.items[index]; +} + +fn bringToTop(object: *Object) void { + World.max_order += 1; + object.order = World.max_order; +} +fn bringToBottom(object: *Object) void { + World.min_order -= 1; + object.order = World.min_order; } const CUBE_MESH_DATA = [_]f32{ @@ -130,9 +309,3 @@ const PLANE_MESH_DATA = [_]f32{ -0.5, -0.5, 0, 0.0, 1.0, 0.5, -0.5, 0, 1.0, 1.0, }; -const TEXTURE_DATA = [_]u8{ - 255, 64, 64, 255, - 64, 255, 64, 255, - 64, 64, 255, 255, - 64, 64, 64, 255, -};