From 46a578fd86ae31863283516814f6513520b426dd Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 19 Apr 2026 19:35:21 +0200 Subject: [PATCH] =?UTF-8?q?Tilemap=20YAML:=20sala=20pir=C3=A0mide=20render?= =?UTF-8?q?itzada=20amb=20tiles=20reals=20de=20tomb.gif=20i=20caixa=20de?= =?UTF-8?q?=20debug=20llevada=20(v0.13.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 1 + data/maps/pyramid.yaml | 49 ++++++ data/palettes/srb2.pal | 259 ++++++++++++++++++++++++++++++ data/tilesets/tomb.gif | Bin 0 -> 23092 bytes source/game/entities/player.cpp | 18 --- source/game/entities/player.hpp | 1 - source/game/map/map.hpp | 6 +- source/game/map/tilemap.cpp | 138 ++++++++++++++++ source/game/map/tilemap.hpp | 36 +++++ source/game/scenes/game_scene.cpp | 33 ++-- source/game/scenes/game_scene.hpp | 3 + source/utils/defines.hpp | 2 +- 12 files changed, 501 insertions(+), 45 deletions(-) create mode 100644 data/maps/pyramid.yaml create mode 100644 data/palettes/srb2.pal create mode 100644 data/tilesets/tomb.gif create mode 100644 source/game/map/tilemap.cpp create mode 100644 source/game/map/tilemap.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 70c30c6..c1c1a9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ set(APP_SOURCES source/game/scenes/game_scene.cpp source/game/entities/player.cpp source/game/map/map.cpp + source/game/map/tilemap.cpp ) find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3) diff --git a/data/maps/pyramid.yaml b/data/maps/pyramid.yaml new file mode 100644 index 0000000..6fc0a90 --- /dev/null +++ b/data/maps/pyramid.yaml @@ -0,0 +1,49 @@ +# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif. +# Overscan horitzontal: 8 px per banda (les parets laterals ocupen 2 tiles +# en el tilemap però només 1.5 visibles al canvas, així que la textura es +# dibuixa amb un origen de x=-8). Vertical no cal overscan: 15·16 = 240. +# +# Símbols del grid: +# W = paret (tile 99 del tileset, o (3,3) en 0-indexed) +# F = sòl (llista = un tile aleatori entre aquests per cada casella) +# . = cap tile (els sarcòfags seran actors, ací deixem transparent) +# +# Índexs lineals: els tiles van d'esquerra a dreta, de dalt a baix. Amb un +# tileset de 512×256 i tiles de 16×16 hi ha 32 tiles per fila. + +tileset: data/tilesets/tomb.gif +tile_size: 16 +origin: [-8, 0] + +legend: + W: 292 + X: 169 + Y: 1 + Z: 202 + T: 200 + R: 262 + G: 263 + H: 230 + I: 231 + J: 233 + A: 2 + B: 34 + C: 33 + D: 32 + +data: | + YYYYYYYYYYYYYYYYYYYYY + YHJJJJJJJJJJJJJJJJJIY + YZWWWWWWWWWWWWWWWWWTY + YZDDADDBBDDADDDDBDDTY + YZD...D...D...B...DTY + YZDDDCCDDDDDADDDBDDTY + YZD...D...A...D...DTY + YZDDCCDDDDDADDBDDDDTY + YZD...D...D...D...DTY + YZDDDDCCDDDDADDDDDDTY + YZD...D...D...D...DTY + YZDDDABDDDDADCDDBDDTY + YZD...D...D...D...DTY + YZDDBBDDADDDBCDDDDDTY + YRXXXXXXXXXXXXXXXXXGY diff --git a/data/palettes/srb2.pal b/data/palettes/srb2.pal new file mode 100644 index 0000000..110eace --- /dev/null +++ b/data/palettes/srb2.pal @@ -0,0 +1,259 @@ +JASC-PAL +0100 +256 +255 255 255 +247 247 247 +239 239 239 +231 231 231 +223 223 223 +215 215 215 +207 207 207 +199 199 199 +191 191 191 +183 183 183 +175 175 175 +167 167 167 +159 159 159 +151 151 151 +143 143 143 +135 135 135 +127 127 127 +119 119 119 +111 111 111 +103 103 103 +95 95 95 +87 87 87 +79 79 79 +71 71 71 +63 63 63 +55 55 55 +47 47 47 +39 39 39 +31 31 31 +23 23 23 +15 15 15 +7 7 7 +0 0 0 +191 167 143 +183 160 136 +175 152 128 +167 144 120 +159 137 113 +150 129 105 +142 121 97 +134 114 90 +126 106 82 +117 98 74 +109 90 66 +101 83 59 +93 75 51 +84 67 43 +76 60 36 +67 51 27 +191 123 75 +179 115 71 +171 111 67 +163 107 63 +155 99 59 +143 95 55 +135 87 51 +127 83 47 +119 79 43 +107 71 39 +95 67 35 +83 63 31 +75 55 27 +63 47 23 +51 43 19 +43 35 15 +255 235 223 +255 227 211 +255 219 199 +255 211 187 +255 207 179 +255 199 167 +255 191 155 +255 187 147 +255 179 131 +247 171 123 +239 163 115 +231 155 107 +223 147 99 +215 139 91 +207 131 83 +203 127 79 +255 238 220 +255 220 185 +255 203 151 +255 185 117 +255 168 85 +255 151 54 +255 134 25 +255 117 0 +243 109 0 +229 101 0 +216 93 0 +203 85 0 +190 77 0 +177 69 0 +164 61 0 +151 54 0 +255 255 239 +255 255 207 +255 255 175 +255 255 143 +255 255 111 +255 255 79 +255 255 47 +255 255 15 +255 255 0 +207 207 0 +175 175 0 +143 143 0 +111 111 0 +79 79 0 +47 47 0 +15 15 0 +255 255 115 +235 219 87 +215 187 67 +195 155 47 +175 123 31 +155 91 19 +135 67 7 +115 43 0 +255 223 223 +255 191 191 +255 159 159 +255 127 127 +255 95 95 +255 63 63 +255 31 31 +255 0 0 +239 0 0 +223 0 0 +207 0 0 +191 0 0 +175 0 0 +159 0 0 +143 0 0 +127 0 0 +111 0 0 +95 0 0 +79 0 0 +63 0 0 +47 0 0 +31 0 0 +15 0 0 +255 183 183 +243 163 163 +231 143 143 +219 123 123 +203 107 107 +191 91 91 +179 79 79 +167 63 63 +142 46 0 +134 39 0 +126 32 0 +117 25 0 +109 18 0 +101 11 0 +93 5 0 +85 0 0 +119 255 79 +112 240 75 +105 224 70 +97 208 65 +90 192 60 +82 176 55 +75 160 50 +67 144 45 +60 128 40 +53 112 35 +45 96 30 +38 80 25 +30 64 20 +23 48 15 +15 32 10 +7 15 4 +222 255 168 +199 228 148 +173 200 128 +149 173 107 +124 146 88 +100 119 68 +74 90 48 +50 63 29 +0 255 0 +0 223 0 +0 191 0 +0 159 0 +0 127 0 +0 95 0 +0 63 0 +0 31 0 +255 111 255 +255 0 255 +223 0 223 +191 0 191 +159 0 159 +127 0 127 +95 0 95 +63 0 63 +233 233 243 +196 196 225 +157 157 206 +119 119 187 +84 84 167 +65 65 131 +46 46 92 +27 27 52 +213 241 255 +191 235 255 +170 227 255 +149 221 255 +128 214 255 +106 207 255 +85 200 255 +63 191 255 +55 157 223 +47 143 191 +39 119 159 +31 95 127 +0 191 191 +0 127 127 +0 95 95 +0 63 63 +231 231 255 +198 198 255 +173 173 255 +140 140 255 +115 115 255 +82 82 255 +49 49 255 +24 24 255 +0 0 255 +0 0 231 +0 0 206 +0 0 181 +0 0 156 +0 0 132 +0 0 107 +0 0 82 +0 0 79 +0 0 63 +0 0 55 +0 0 39 +0 0 31 +0 0 15 +0 0 7 +0 255 255 +207 127 207 +183 111 183 +159 95 159 +135 79 135 +111 63 111 +87 47 87 +63 31 63 +39 15 39 diff --git a/data/tilesets/tomb.gif b/data/tilesets/tomb.gif new file mode 100644 index 0000000000000000000000000000000000000000..86efffc61137e238ce3e34be514792c47df2b1fd GIT binary patch literal 23092 zcmd3tRaYEbuwa{pAdS1bySoGn5;R!w;O-Ed;OTTC9S$P=&eq#U-0CNKPKaUU(kAj5cGZP9S1vecfB^@;tGZhsxEfp&b4F>}) zCo}zLb~AoKE{b@H5+aI{q819Gv>b}e zeBW6FbT~wfxx_6*Wi15cTy&IVT{NWqEp%j6EEIM8bxi#I9W2c4{J|Cgu$?FvAU*}q zYD@u4O#z@#Kx4QLIK;a#({`%DThBbm#4g6vKE~Dq3^1Dl{DK|>!l8;SUV-V}fnc|Q z61%AuKm`=A1pV)4TzX?%Mq_sAU}4QvQOy(-yxcZ;424cju0#KCum68V%2zlf(0?Vd z{BK$R%>;k|4FCiMhlB!v5s^{RK-9SSghW&TRccCVMtVkC7C0w6FEgyDxFjSuCo`+E zydsaPu(YwMxuvzOy`!_MyQjAg(m&7{GaMO5Lqj}4JUuZUFQXt&CBL}3yt1LNzPhr# zxwp0=LpB&fwy?ChD}S|hvvYTQaIJ8BT0-*P^#1P^01G0KtIzK@8j0aGTB$A=G>^+v z#BL}q{1S!7Zn4y!s3+^GkeLB{CPCE_N5Ee?)t*%J)s_m?<&FXZi=fVJyVC7dUpAX3 z9t>hlsb3oim!srM7^#WGEK|yi@chbqp;4|98>C&J*?cceU@L807KuhBig$_5xCGI+?p|dskQ#Mx5lt^F%RH|GJ=wW8CF=zYB(K zT~V+RG}xFcQpjT)6DS<$Af+ljA+~Ta&Z^KM|1tIF*>~~>vdc_%=lk=Wf81zp521-e zFLN#$TO#o*G}5R!PyC#hKi%e@wtO!LQAhTNUt9{jf=a3!odR7$Mxiz>z@0%n^fX6( zkKwA~dqQYeLMfGLOSRkv1H;8ZVN~ms9GH^x#N^P@Z~FTWscxmEZ9RNtDJ6dFG^%W< zCs~{ap%YNad?-bRMkgXgL)rRRf(!s79ns`=ew<;JCPo zEH(Dtkax+50$i|`M7@ny5LHGYRb*#wSY|eKb|O{%7JMo4Q$S58Pb_7qEN2;bJuP=0 zBV3qh%!ebMV90$7v4;6H7R6MyWKnh9x}mF3jffk1XD0>E(oi$@8%drPItH1$*zWq6k55f{U3gA#H$ru{q^S**QC!k5L zsGFrOJ8P2aea-(&cfe)eigd{cY-4H4cFCkN!eE2v`Y!%3^$rTMo+ju{ zdl!+j{<5bhz3`c(#%(6ndBP1-W|3)NNt-$6=b|QZvy)%YlZ9%dld@m~=0&OC%J67l zD!9T>OYk_urHHgGCHpM}+mLp#<9Dlp>|c+`kfQ9TdFF71nuAXaxewzp2xwlPqSbfW zk7Hziwa+k92g0@F2LF3KT*JfgZpk7ycssB5cj&we6}>P@bN4#lyNzapzQmf&vb};$ zYyTa{_{6if+~VK$zGVLc^!@#v#SZ*;|2zwY-X(8ySkXrUcVx{5G3K)LrA15$*(pIw zYjGAK;>IcE-odWx`;Bd)*RRz)H&p2zbGQQ_-vk3%MdL29_P(G3z>jAl<37U-b^Y9aI()zUxjSMnliU8zrdn+jX8KXB?Ns~Ad#I$fUA6=CSl8y*ZQGjTNOtBaV z1G|lYFrJlWJ04Gb>Ags_X#tysk9Oi7ZG}Gc7b0$Y^<_yQfFQyffflA$-a57qvli1{0TupW>w;^`Z(4+!O!>IiAJGgc! zkNd0}v5D!%^f6uwY4VM& z;-CqiRg`VloJhDM3lH$x2OIA?D4@O2t+vLz!s~KES?S4vVE?@82h1gmAMiOhG>0t6 zikdQxj=y8YgzabG`mTub_s-=KhgY62^NEHyV2zq`sGJnjJ_tdzB~+VTarJ#nZmwl8 z;Y{UIFs4LOglAj#&$5{h%q6Bq&-ORo3p*E_DcyhR`Hh(rv6QBJRc~vff-4P%4`&0U zcpc#eN{xw*0?ZOto~`ANOS@mG|=oWyHc-G}pi@=qzAmu1_`cCU*v(2-R24(;LAtuPt}-;Cl`e@93C% zthBdU)Y?u@>D8yzGMT&8TQbl5wBS;^{S-gk4n@&M^JGNGVnaj7cs`GRv7snCNvmiw zXxE@8SV6RZto+eqoc-0`YPXgo zp1F>u@|$ax?xtA#OXZB+DHK>|yGg)3FUsoD=bb@4o8{cP_K)X+HY)ncQe~2dcep zDO7w;Dc-w4bo1zUFJ1{Fe}|o>p|Wq=2ma^f7zZ%wUB{PAzL{J#=l6bozoU}t;1{m} z|2Qswf1fQd_Vf0$vV+A<=E{0cOD)3eC3N7k>k-|y1=XaEoGZxX{=)8q3SUK8L9DQ6 zOpDV$>ifBcy58Qbp|CPqaV|Hq#mL zTkff|$Ujc;KObb1i=TDin zuyTT^i9Owclb?N(;VNd~Ic-qK6mfn}SAN&vI4f>OCv5%Gv$%7__{%Y(Y`hqaGkYDx zVC|Qn*B8-78P5Pq+i|!A5rPCW0=a{uu;Pd)+lZ)~3`5x$4pzLlTJ0dh#W=$Qk!e^_ zLL|}9pv3HfM9NnY(O{EVgmneU+S$wMlE>^}%_+;Kq2)=6cS4+uvsHyk)UEU=|t6m@Z9@1V>0)e{(Dfc6L&%weIERJFx-WpqxH{iPi3ifrM$7& z^Plpp`eqBo zMVL4^i+lo$G^*sgHzx4}Dv>S@CQ$(vs8IK7DZVpLoJ1l0TaMjCj`TBu^#K7ZQIXq4 zVPPN@eFq|Ag;RuUyc1>`ZHPbs$jm7*GQ+c+FkAq)tvq3@95`5v_?F)o8nsSf=UNfV zrHOY8iO#C5pxv*?eyh;qOdL)?Q|9C>6C$AHtSs25%zvva^5$_UDz)z{b^E#)M$R&Rkd~9%nAR@o27Ut$2nZdDj@U;kc=z z-CObPUO;QyldTU3`42W~iYE|{7<12cGY&zux8AiUy0u3{wSSoOdmm|=Z3v)N(eTAxZ!6XuIHXKEHIsyuxbP_nY^YJzfmYD*Rhdl8F0-4Rdh$8|M zjy%nQB2?Cmw0b`neHxh*=w38%`mjYj3oDuRn%FiQO@kWwCmUspRNif>7>Jq#OnwNc zHp}=l%j(s?q=FHBoCLV?tl|vWrXajCfLgNUn-MU zv#eI1WdfF`rk1z=W?*7HU;+$icDHR7Za{}bxqn8WRz)^Cm0sXJl_!+X<@n;+mjaHoAV)7ZFYb++kQ>jN4Qr{UI-U=RJ(>* z%?i~_3D=+_b`VVlQVV-gAvfZ`G!V7pJTuij64mYN^`6Uhj=sw0HfLX2_a52S-M#m^ z5Bh>fU|NuK*L7fW;Nd#;`ha^~#0B^k{D^l^wf9!tqY5_n7A0X@Kni_SZ7Feu;$_W>Y|N;hWUr7)fACU^}IujEABKf zEx8^*o!Xi(dZ{+p~ zU$OeTVn@2_u)X4fx@wzKrlhvwZm{b1cf}8N&0loYL21o~)jOa8x`exj3~vPJnOjRB zT~8uiZ{=7|LtQVLTZ{Kw&o)@k7F`EluXW$8r%x|85YDw(r%Qtu|6R%XUc-tRHP;)g zG#E5Dp>8(Xue955b{cF7IBmAntdjlS=tbS?P}&^r+3efi94FnnlbARdSc=BQ9kgu>kl|;0f({BZR(waHt|Ew+(SMG5PR++ zqu3#R?_o>8p;+&MjH8VR9J3mK<$p)2H%Fql#~Oym%C#*1<>BcG(mFfGKH3aX zth-ckh%^qnSE46J-%i+S0a~<&A>xPog(s)eCm8gnBDDa25e^42L>@7AcV%S1n^E#` zpnu$v)>K&dOsrwe2K0J0G=qEb$i{IvRx~>smb9d4lqJF56mOS~S^UbelG) z;f1qMIl5O4(b(Ytl#du{2;$kHY%oM*|E1%D2H=_DZpmGzs-5haIR(wRbtoUIV_&th z9rVXswHt+PW}fNLhmb%v$CR!o?6+17x2Ei`)Bi!q7kjss(Qa0IuMcXk_kV5r%*XvS zjoXdlzZ$!_eT%zvpso3JXoYqui?;iWMlts90EodB0l0%2!T{?}o5b$Sa&O1~T_En> z0ikzrKkh&(J9RgL5k~hwqkH)H6N1^R&Y62Tr+do0d+PXm($DwPHw`40o=iUiS^65- zZV`8T_le^6u?-&t9QXOj_tbOug;X9sJ3ffl?STyODJl|a8I$Coi8LLFO5~yd)my3F z+ZTru^4U8jtgC-$_b@7V4rr$a<6Fco?M|% zrtzn4dC!hNo*j0d$;joBiC+?ojzfODs2f6GBFK-ihK9e5znp!TKE*u_&s`$;v!tk; zGSVD68A_(8JOz8d+P%VNvLBqTzJdalzblmMg@m(P$4#fCo0;6MYYp98o&{ooL)LwEVhsFa0m! z%hTle4Be+NT5YlDVX%d@8wH5m3SAGBRdu!ki@b&(1apDp1IF3!=v@3445WomGXCxCDjzKM!J)d7N6iv&Z zJ4Eo!6b+q9S`mwJKnhJUUMyF-0*f{wo>Df5p#v+TI#d81%yW)aItJ$UYJ_`DE>=}! z{esE#xB$zfIRl5-BhO@BQ(jYwWphzbwOXg>s;p;G^`pQ=d$Cti054n5{;<0S*)mO4 z(EaSBcX;yYs-FC_HVht#Ky45N2v1)tTux##oItKXr|8un+z`&^lC3?zZ!v?%A3>%h zWcsDPT)oP0x|8{26`(tr3uW~=UoBhB3|xNItXZmSwKbYk(qw6AL7M&dFx~BF`e~(5 zA%16e(`{rsMQL}Y$H(Jz8?8v(`EbM&a_Fe>UG?7w`qlAP8Gk%b)a&hOa}~aA z>Wla73s6$p2#JQ~KWK}t*&Kq2?p;3=jKjr68A9MN5J`)##b6wY$@Y!J4q_!R9!?O3 zSQyG&c(xx#G7(g$UU4)sMTL#WU*sqd)vggc6O*y%JWFG_>N3Uaq2o!Q9W0TID{@|} zGaw!b<2#{UQIf1DvtE*7sN7M^@dcnI4gQ>MO_vJF*MUp3&pbcLc3A<_<#^O~&;;a4 z&}2#zJ-*K6k^T&$fFj}3TIs^}+(||kWkSv-+2VMZXd;C)xfmHz#WomAGNe2Askp)< zsWl`P3X>~DDuoaoz8SyiCyr5oi<1^qlko+9>L8REcELyE3;TUD#Srgdg2X4rvU;#c z%!x5yOZY_L8c7o~FwF=4qHFJOU9}-jY{jMgnWD+7gNv=o^XTTFPCg#BNGDT*T-WtP zXMWT9IMKdP!G|@d(H*9^Y@;3mxrpiB|Lc7@Xm>7j#Ww|lJst9aiM|)ag!1Z`glbj; zvdLw&z~dBoA8Y;s2Eye*A$(^pNb3V4M(q&`AJ+w3izg4WwVe7?bJD9aAOtyBl2ima z*L>5(?+sWB&?WV*0k;Jt*ba_Cc#bSm9rWEO)m5`}d-v5bPrq~zySDA8HOtXhK0zC1 z>fK)+n`Qj|p3gmP{>yAU4?zcMkm~{zqKF5njikvybP^Kh5cFgfGOyzlDK@W@G{?Bt z<7`_)K?F(I2Z6IZpIku%(&ig)gz~yt@6#ge0hka4r+g=F5`DQ&$~KiT31$Ht$B+A( zKmYb_0as*CgVJK`tuV$~=qHaq9CT>YxAgM-p4WmM6?2cObi{H`s^m*^&Wmov-p@=C zrlul(xCtBz?#B2R3i3O3qKk2m0m$(o2dEeUzz`B3YB>aWuov*j%dcrCZ#-M?#qO1J|TldXp?`HLaSiTaGZ|#W`FXl zZivuY`mnup{dZdXR0*4h*CZcVP&x#qlq1nXSWo6RUJqO@t4@PlI?qBHH;OiGk>t2F zlus($Ve-Vix@g+LVv)daQRUa&Ais0kqR4>2>Gsrmo>Nj&&Z{vkRqa@!IPO?(2z9o# zc3J;=B%5195ve%$TIU#UV{D=xsf6+AY{0(?7lm&yU(rZ3u%RaN`4Uic;Ay<@}V{1|rK|@(U$Qzig zw>B7~c@HQWp)vLa$E6pJ3?c6VQtaYX0qbjnsG>dpy?hQav^$47jK82U^RXKE!XyW) zqh_JeR5NyNMz`BI-m#GyE-g}MSU^xid-E6>pj0Try@(NxI8je@9%C$=lsJUP2c?e5 z36#ny&FS`91{I{vj!JYrhy#KKR;hNWfH?AS^MCv}wLn7bkT2S~J^JJ+Ub-POUm2f-4DRTtji|jq9e%IFRAm&F@JtsJ6m{tX&+aa zaVss!4q{?9g3CvHER$ljQA^)gGLZS1qpoVLLzurfA^updBxM$Bp~s=I-A(4=Ci^&%tN2V_A|I z&=78XCB^WuI^-Y_lSpdyVe-&tf5|?=NoMr3(>3R|8y$qk@;g4uH=E)IeVaUvu~TrD zZGD={dZ#mAX4doxcRVqVO#P+r=!YFJLT@gR%`NAWXdlJLa|pY@EkfagHvrE~M|r}O zG@N}@bM`H^?({H|XuVXDt#PgArZiPWgmu8_wRCvf+z{Jj>4mJWca+tto!Y=q2_LfP z({oqik#(RJZ(EpUe`bvF?{1topqTRSsw{uzeGI?w5`Bfc(%{yj1NqR558HFb>fbYm z+4miUM2|3Xz2fn(%SOyP`9azDN1RD_H~Ck6yG_GQx?~XxU8vzJ{g2*jFgfq3#Jf$W z9?8<6N5dI-$72yH%A{7?&Z@S%65Us0J2zfB!hf?H$NeI@_T4F1-q7ei9r_Np(xdIU z>w}QMItX6vPH|p1z+;AcUtm8UI|(~IrY`*B?cqIx;b)cZKOyr|58qGse-W!NAMN7P zPsboXQ+#(bk3jJ>x{Gw|t5u(aQk45PSLbxZycG*-SW7&2e`||?RGJ_Udgco}7%l=B zMo&aefnYA}U`zf0x(vL1OwZ<^@1j=U_xru)N;$XcJ^$pJ-{ptw-+QVOSQC#0CWlz3 zj#_8n`{tKnH1Tm2wqguhGEcOIdb-;R$(dBThjm#(<#A7fKQsJ14ESy@7xG2Gv;I5k zp{hMWm{0sYN#k=kJ$I-^g!}cWnO#{Jn}rARe5e6p*!xIOQgK9Bz6n8^3B^b#Wf&qk zyg$o#HFuaufhF{*k2YeE?wG4(spB!9 zc@JV(uBEeCxfjtLKuarTtlWIWDn^mOKXlI0#{$v*82`~658X3%{}3O|>L+I^2_ZwE z+X24mbFApGdy8jiI6=^JYcvK$usR+LMtyX=w#TKM39?f>dOiUC4)EL#cx{jW`x5^V z5y8%9*Z1VMC>L94N#jAlVrm@Xp^5)hAn_9?6z`jW^&>@uZG^jmMW|}4e|mVdLz~wX zBFv2?01W_hj~J8tIIC15%p!qrqinSnhl|@V?ZMzFiE)EbHcfUXn8LQw?8#39tLY$RUb(;ctCWO@;t4*W{&Ex%Xj2Gw{tXr?N#yZxbmt3phqo~g&b*N` ze_zS0dd;j71e!GkQEFRQ^4WY3GuNnqf|<{foGk(W#xgMB6AWQve_6!kGGr#6segIJ z>re1=j|dU_l9sC>#A z!-!dy3mfVHAkAeYOD7{kSl~OHiV$>@*YOspRp!0Wo)JiwU-5nqrOdVDV=xiyyyhJI z2OT?dE%0&)nQ~7nGS3NoAICx;TODrK2_8Is5iIe|VN*Y3y-m)2*|qZFL7bK=X`3)% z>!Z$=X;F7H(SHkyY6XgqGzzwz3h>|JJ8K}LgV4r5>BBs?u~4rOd9mG{B;hRB_wf7(1)qXwnJ}cLnNN4ZF5D+5GfT`WD z#BjF$b-_e_Y*YIRUup4F;2u@|+ot;EGzQU?#bqx zKVIDU8FDEmIdCapd6;2Qgq!%@nL+^#0zOT=Sxv(4O&D2-0t!u1(G7B0&0lRxh@uL7 zY|2O{ij|Q|iZgwC-Ac+QykPe#nWAxF_wv|;^ZY@&+*zOsae&1n5H6rqq^i}*w$(8U zBxs8ojL~N4(_ClLW+VJV;XjU=)l8aI9C%p}Y+Dy!9>@GvJ0e>i`Cd17Mm!-HnRS*< zt<`Gn)8S%^$`#O&VcR6208nx6;1UOfiUYEuJ8+FU#oybiH#8^0VhNIEuPjr#8E7FFPhLyJwJlESY=2?@VRP zJ&s+Swg2HLJ)wHxuEwt7=E+ zv8v-^vwP{ij|I?d&J5Xl@A;3cjuhlJRb)2E^VOb1s*(DeymDLQ`P#CgwYuuMgu_bS zTAm5|rt4eYbozW(Jc>N(QTdyolihQh1G8D3Jnwzn`fc1(eY_$Pwpob6)etd}#uK6; z@A2x0u6}F{U)_pA-S>7_za zyA@Xz)>aQzQ-3T(e+((?GvDjRh#50H*+$C?J_AM46y6LZrGw z-1&o8#xhzP!+5j~;qvUqLCM0(&TX)pZ2ExE5I2<^265tLmkR}fsxAA40p61x%T-eu z3IHr87zf+o`AHzY(>UO6YTmACQGc51&ah*7A|zo#95Vb_Z-#GjqAq);4zu@2xK~cM z_g18m$gXBFq~%$sQjBGwEnsS%1!B87`%%@3B@T;^(N4`v4Fqg6U8TJu?^f!3*b+y_#N5{oU=? zRlUCvOj6rTroTptTpQbz&$dElt+HFC{?1CFwpm`b;&Z^@&n+omFDWW@0s!-hf0tB9 zmr3T}0L9Bzd%+EL70qek&KFw1F6t0r zYr&l}6eAo2!bo@AN!A^F5J?T!MYpC)P$o(g zC&;H^KGh#sbsw-90$6(w9+YE=jXq{t3yKAs#6frOqWlGG7Ktq) z1#(V^WoFyv!Zta$s9TI(9Zt#JPkk~@$B>3aAD7!lu?4wz=~wu)5%Kc z3QAkEp3?Ax;e;Wo8B!EU8uEuIXxOb-?+MJP004Blf zCBtip@a$-|z9YLCjqd9$WytO?$c{sE>*2CN&+?C+WrpH|PaHQV`Y>5D=hjM_-92T_ zHHVHpo60`66?C@NFSq~LY@5U!3WACMS4M3LP7Mq3ag ztHgA=#dJE%|MwuWvr;^B`>}J2M1Ci)Vhev8%0qhkweL=W{ZRuN8vBh;CGkoZ{eD`^ zb#bncurHi{b*-QHH*ZpFJLhi#v>v|64$(~!p2;azN4bmG9q;0u%b5cj^frOKhjYbS zb>v((zfID&^LD?ozZMo}bdGgtFR`;P(NKqY_E$)YLoedi^qoz@?MtHLYtC+TUfoNa zBYsHkBSzn2Wx^S*)EN*^w_2M)+Sf?w*et2={ttO@ju_fmF5I?m+e3>E$hAAbHaVb= zJLmX!WV`wN>vkeQWr2FTY_@LUt~}{V@OoM0FR{?y>x{oEvwzq8&uWbTo6xv>?bmJp z+*9`8^SZyQpZ{Gz|34e)U=Bk1dV2c$ z2Z#EpNB<+A6C*tsD=TR5Xsc@*TNvw`Tl>35hw#Aji%TH<_729`)z!`E!RpD&-P`l_ z-#z%RgmU#pfq|d|4jTP2Ny=ytJctev`eI`w;kL_VXiJ|+6#(-f2_g_bOBhe4BOQl? zjAOt^B-0>K0rrl+rtw+3gN7Fu%MJcBZArg2m(T0YCNUXIG*>K^sTPUhR?ILiyQwwV zh|)O)tOlcyMuo!7UjpI{`apB#4LLgT6m^xZQbB{)NqsRqW{HCgPNpgRwFugZk@}G(aQOM**vR$(?g z>S?q_E-vBlrCAz=Fc$yZ4H0~wl2;LxP`DnYnDnCT>$$Vt4@E1k{yjk-pQdNu+0jkx z-kUIXqgKBjxjDtYc;!0lq{M&ggcjII(p!v^E|H|jb$;adg-M)83WO7LH-Mz)=QL;- zBnX3w?MuqHW}th;yJiw?@H9dae&9UjG^zw{nRN59%=6e_McrXMT=xj#n_)1GP+y5K z-De#r^^|)=ArpY^2NB=A?DvR@AI$;r{b=Il{QnduDShV0O8R1=>^P6My79B7c?FJc z1gq%#gm?X@GBzMwFm27W>lUqS3xYttG&Wi63(29~UFZMJAiUT1ki9HbA)2bC(j&^p zUfcdB*`59TIc=b%@_aB**T$vt#`gCaI8BVV%+Fd-KJEAOSJ2<@^uIy>26y6o{NDTH zf4w{i?-Skyz$MAFX}EWr(>M|!y9Mkh^Tiy?n|j?$7_m2x_Pblv8@zHMf0cC#pjbw*itv>YE`OO!|)H7$}nN7y&E zh!8Vs-q*1pyM^SOm|_>E@liZwRynWkPn1uE=^6(nsXor;jF$2WfVt^Nk4de2KqRX$ zFDKAHxUf|Ec-+{#QDaE@gmW`p(ZYKnDZTh>xnzr?DcNG?aPbLm3s}jd_gjRs;#k?) z8KOv<8GaSbK84NrjLzG=2Ko{nHtkCdtkIp}%T$@z^SDZrG&bK~lyY%c5b`Pjq6rcx zufnl`A_r4x6x5iUF%KYpc6l3CHW#%d-j;Kr zxIgk;3%IobJ+?Ro1prkhNMiW0nF+qp@^>7q#OP2Klkg?&D7@)HgKH+8AFueY_Dj|I zp?ZNS{rQOC(q<&0n!M6Pie4ettm&arE@QoE>kG}y?P`0w4#CZ>0X~~a4RP*JVto@R zUm8yQBaL%>hyF3qb7k3N<(KJ>b-gud1#YAocZsKdcho;!dyl8~Qdm2`K{!T)A~zC? zo01o~A=%YK|M7ib26)M->jhWGV|17>#p2LXWHZ*^>md(s-VTBw!#IVg2!drfg@LDt z)+ezeY36z9UpK`MQ!pmIvXUiCb7Dssmm-7i&*R=k*7^RPH2EtQ(SpW>5u)a^K}cTI z2a?H-qb`djaU2^+w#Gbw7cnE3a*zf}U}=*jHBrI{Qi)956=my#n7$}~13c3kv0SLN;%-v4S-+taa>p_pyAn#qNUE|$w&9n%y?<;W-c6!A!z)Xq@Wp>+}R zlZTGHNBf#Bzd=Lqo#FOFU8Igz1uFK0! zi?tW5;y~qoCFNDpjJYYQZG%v&EVD#cb3v?KV^`}yIM)KwlS>PxlV^Lt{iG3}vJ1g& z1Ew)g2ihay=Go4DBKb2F75&Eeoke1j&$XF+T{aG5SyXS!*6qtMhG;zW?uzb!|GF{o z=f3!;;?A!Qd!MffUhNt?ZLiL!4mjR(O8iTsv4yA-o3GxApGOyAX%l2evkgekABzln z&#L%z*)sX)%PuYJFAW*H*!O?b9gzL$JPh)bDWj*vZEu8E!^oIhB!;*5yPU+xT|xad zxk~t)X4q#=nr?`X(`Vwo79iLgo=*w$cJL&>=E1>l9BFi={04X9lHd<5ns`EcDc)Ty z6O0wTvivdZNMs%(lEVn=KI;Sj=g+e)OOi`|>r0k0y`Zc*f?hRe`r#nS?)QDP$bRbF z^Kkm%YJJY9<-yzqpT5VsFMc>y3K*->cFX@6ata5l z-b}Q5b;o*CYkEEAK3_PMx?1!Q0{~s4L5L7NO6{OKHKzBr@0F5F*U4xfNoX5KVv6no zIZw3B143<*HqaAh2$xOgg58<34epEIdMo`Vyvj7J?X#JocDV{PF;w45QXZR~R{$US z8}wWlyfF}LYK1f>i>o`xfn3D#J(vSc%3idLb3sdZ`6;Ai)g}PwY+XXXgQo~P=L1Ri z30qSOmh3 z1uINMPH4%ry-X+j1D&C=ksHZwjI^Jt^1qS)@=M z0_Vaf^Gzq2DF(T&*Gjlf;u(z-e2M}f$WFDkw-tp%re9l7=(Oz6K0=- zi^kjys1su;JnY-Bc%88owG)#IJ`1@0IAqWbPscUTPNI{;p<557q+zFnQMGPQnr}}~ zItZn7M@%d80?SHe<9o9fe8!E$E~3Y&S>V7if|0R?!r;ttVB#hz$`g_SfGX!OWa7x* zkW$qJk=2k=RdqaI^8p6uxXpK|hSoub`>BQ$B?aviiL;hQL{2UfC&x^afsOAti=Y#)R2Oq|&J*jK4`%&$ZXjNiqe6 zCA8_*&#c=ULu5T|js;mt(?V((W9!DLZNbnqI0$v1P8P*6%xFi(i#6Oh5`Y4bohlFN zA;h7m&z?fcpYY1>*8#s*~QrpkfWl;1?z zQSz0bi<*tVatiO#6%1qyNM%U^V1=Syz6gdf0dRLvLbsMnxsl3coo={arN~u-BbXWB zER1(f`(Bi5|GmH?5}c!>X;zMg%8gDx8N4RMDr-|&yH~sEO(=*_TPt3Xp$mc~s(Y-= zdL+sg*=)+Tp_$<Q%UL)pkebQ-~J}zqg9NH$4);DH2uLd_}I!Y;(q{|D;(8 z+AFCgY{x`y;Q|9KnUH^Z$^Mis4b^J|?xphyVwp3uo=mj11$0m}<;j7Xelr#4GFRp! zw-x%7z+JYr1$0tiSB)i>gkRzY5ew;GbRx?)XSbO9!r(M8+OXRcaq1Rv64jj7chwX( zrV^{g5|xay`~HxI5?>cdqGh0wZPdR8Eu@ zP$l;;C8Le&*gZpa+Vp;8rSrtVl?Q$5xVQF$GwL3lRNkn}6MOG|tw849tfdsMP$sHa zj)s6LA^DlG#Xi}Wojov(b$16OfrO<$Dv{+B5^6Vi>ME2}Hv5{E8qYy(-$6}sLO2L^JyYO}qzjlYA|;Wpkagnh zFH=1Xg3U%u?f*_oT8VL{y?Ygan$<*1(?o@;KL>DxfqLBo9HYtNudWRVuI7bE2%j>& z!Ra}#Edu)G!c&93Lme3tkkYsEL>rthD8(VwkfX|>V3djko3XX%AqnxZ1l=K=?ohqV zHuCQN`g~|*oehp&bxAJC01$bEnx)hPG|_W0%=2{u$!FvUN9xWNIsJ|{m7^tAGEAp@ zw8gFC>?`P02zif*5CNrjK&198YOG&mYG`XH37j$J3ru1emUixhR{(tyMuvLhOlM-M z9`~<*pc?v4sHS4-*$viLj}(rM=-K`#3{PxEA&rclywgWc7oO^}n|ffGlG_@aiuq5` zwJ)1QU1XVn`l9YCj>x^E9>$EM1$1>}A^&Qac}p(geCxvYE4ufc579}5v6-FDo>dlU z`XCujV@#VDn%TG-hG(6r?rwh-_M=;AFQG{HXfW}wl%I*1XYT5bsaQ-p&)}b&AG?~L zM1d|0Zq0hb^)}vRH#mVa#@m2?3#L;``!REtirt=xgAd6gjf9Jp6_<83hk z1MfDZ>3^@aK$L7n-F$?o3UQ@bxsUm2yCv7(+01tRZBhMO>%;#E*E=x_YJPKD33GyF zu7gau$lC*4oPA%<8kELzbHL!_*z`@E6$$zhwW*Go%MO4@$q?b{i^!^!!77#CDwYj$ zwO;cKXI0;Xh6?H0x4+X&H3Q+X27H!>MQQsni{0Mxl4rA-a{&66#x4y7{3Gw4Va z(x{J(69%>L!1U^{y?C1!vSki(K=)*`D&#-^{l$Aojd7e*bX!h;JK2s#E@$b$2RTz@ zx_Pd@BD%Bdt-e8Nr|EjQrG_yAA~Y!zlJ$&lriCQ38q29z#Qk-iS*!@;x9hx+zoVNC zJC(KbHaT3eD9O1ug_H_UwuIO_`^9eq->ynNXG0Mgd39=UNVM1dYKBmG?s$6t^m_k% z8qs#t9UV1=fY2jhI`PS}&+XG;|KYlZe6iF=quq23j4zH5bDfys5JK(L66wNSD{G2Foa-h}rm3!r+~}=j1~C`PocH9>wz`Md%6T;MOE+ z#lOFUHh;UtuTI2m+vano-V~Px>{elIo4va#6{auRGEZeN&mx4+VD!#H{7{j$7ihHn z?9TruR~e}0mhdstaru_n*L|1{QoU>oI35c&E5(|=q&D8E5!}E9F6Tf_=Lv4-3+@Wl zYzs@C$xG|quzsp$S_`x*jk7!6QoiAauIp!v)eHmgy>glon3~~fD-(F^-lw^_$)w3H zplbcURO#HzxxJ+>)O9}OcOD?B8=bGv?Olt>5l@y+?i`w8>jgd79Xh=>($4j!s1KZ6S?g~`S^!Ia$P#>o!8sD$jimE8zYoqmg2)(wT z%QgG&v*5^(Ji*FM3T8jrups0)|1sp--Fgf0M$h@OfXbdMrgZNLb}!|y(C*gI^m~tu z9q-lK_&VV@zb791=xq2w2jBx@`QS?Sj@r?(fa<#+G88@_s1E_E->TaD`Qn_lI$D|u z=;a$pchG-?bZ7h44*ZJ7?9qRAbI^| z-n_UFl`MOk&1t9ak`S?M;MTds z1`fnAnbXQuvljDEGng=u4SESQs7#{TKp<*Ha~nH<1QkB@2k)K#)IkXK?TdOyK|iPj z6=3}emMftFU-^mcI?${jS0m%Xoh!GaDH<1CJ=BZp0u7LO`3m-HvZdj}h!dkk8dPba zpGAAlbarzo&0~%S*^CTRDPzt~0YC+yRn~$OyoM?k z?QW_HZG*iEV1R0PgW&F+xSXP1zNnfLxG>>@_3PKG696#1I(F>Z6^kEFzC5ALoHL8& z+<9iv&_g+&`+S<*WzgbG<99EAknY{M{r4MgzJUdhBCjb_;BWOTxZ8ooy>?)1WDWOL zbQmrdopTy$IG0Bg&@e%WBZ?>zcOZ&LB8exO=i-Yn3gbcl5bUWpBYQ>!;nZkAp*Bqj z`>ls#HXxukq-Xc_m|v1c#g+nqXFa)F3I_^jAb|-;Xpn%vjnGJ!{`6L%fd)BP7C>~> z^`V+}q4*tks3hmno7Tk{_>x0H~b8BNmqfvI?cEWK!6rSkPH?QLVPxNl}NP zXd>9HdIl@(ogf6-XEug9=0Tt#oIs5BqK{Rkd!{NEYI7rOt|0E9R9H~ zT{Erp)$V+0;L`p;(C@#gI=$)R5ct#Sy9_p{HNl$y_H}VkvrC-XtherZBFloq_hrcd z&fd)cFspJ9AgBCn+a(Kn^RmWT%lkEg7w@P4p(*27DU;cRz<8GLJWkdl)K3Y}rBi(xmO1&~_TuwbC3C#9y9<#VIUoI$w9IrYVcUX-I63fBiO z>#1)MBOsv*+i|%NM(tk3;Ya&EWxxFS@P~SOnU3y;J7baTSRe~cv|xfjLPYBXi_($F z1lYj0Auo5v=ok|dXtzRrtasBu9`8ODLFoyiYE%lL)YMh4O%-iID1=JW2!X=!5zTQL zIHL@eroKE<4_S`2vz#6~(&PYBF`w(#bZCr;3SgdiE-FUL%tPj3go!hzUz#(h#GZ zq9{#yyi@|pn-2^lEIqkFLM$zsyF_L#mxhQHV#1htJcKd%SO|EjYgEm|$~F1;LPSEZ!Qhq;-&(evlxvm6H>$@8j^?th1WmxTTN;n)GsN?wI838 zlK6V5y4wObuzm$DFMNytUsVKmxBxy3kEXjMN|E&?&Pc9iARE)45eb7#ESXVaCp(i7 z7%ADMi5bxwKUqm=slK7dc{zNWLNJUsIh144QkylZ@DVr*65m&_c?jHA;2}4LNRDm1 z3;tS!zX1;NN*rviZ`mkG3T_!`h623_)2*c^xbWSsMGVE|Xv1JDRhGrJUKSrDL0x9= zf~1Kw7~2qFi_k#oqDEsw*x0|U;IDplVdGr!Sjc#m#?N$CWgO|uXV5(|XEyBM&e)`H zE&WE{YB$Nkg`*oe&|p2swzYn#*>u54FLJ_6Ki7hZt|O?|Kl&Kz&S9^6o_irRcgV;Z z0~gM_h4X)Lf!kdFJ!H;#&NX8RM<{G%&}HHx6rmBNnPzCPNRv7eSQFe*Z5V;k9fO%G z?E^|_>+T#~`ye)D^CUI4`ovx`)ult#h%GCut|BJ&Ku$e0c9A*F!?apN?Tg?1&N>sb z$dxL#qw9X_f+@ic6q0}!f?vl>mKZ$TpRhGq<8jsqIP-J3rkm)_5D%j!dmyB<`%-au zW!#?ol$e;703AjxncD_Aryvm_lZTvo0`nRpHr2`x7Cq2DQHi6eo=_RRsGQM3q%j%Oi z^&Wx8`Cd)`Oi)X~wp}c5tf6Jia*sJx)VHs!MN{YSrY%H$4gma$f)t-10DcMKS6Kwz z7JePi#0)Wcr4wtq8wHmKqCDCDwWk}B#@4Y2=(ld*XoLH^9Ro4$Rlz~o?6MS)+pqSy z&JhG@V;VWD+grKEc@Eyc;o;Lv$HMojn&(O7A<%1K>xt+#xXYJ8NHijslF;JbEqM;* z_hNZG8HwZA=z7yn@9PXAQFjTy^4p#ze)Dfj*j-oNpqO{@bhec295>|#xisH?)bH+H zl5&aka-Whz@&Zr?7gmcDM5Y*&$5oKe3l_qt83W23x{KZ!i5EvJ6 z5ronISN??<^;U1)7b9MWd7Rg4pvQIM5LWaQMxGQD1c!f{B}=B!RRsib7bj90M}uPJ z0oj&XqQ++MmO6PS2zHfx6cS9SMRI{>fj(y(e>YctCs(=@c>CmYb;bfYrW_f9A!N8? z_vK#`K^=5r9oi8QKxP=x7j)RSg5$Au$Wm~;14WPZYfk4ZfkFtt6Ja4Bh$aIwPgIBi z@Oo~8et@Gz*)TWVk~^%IahODeH3)$FQgY`rJ~@2y+t7)~qM zipf_^g^-IpbqFMf3F5 zMP3Jy4@>}>TnUYd_l%}#3c>hWwh#-esavht3zbI*lP8(9$$getnWjf^rST0+5Cht< zSm|I6%P^dd^*X4vcf^5OzxfaLgAkBVUeGz2&8HF7xfj!?CM6LIBC&xVA)B~)o4OL7 zazdH8X@>(gUFrcDzX=2CIS$`|JY)rE@u^_k8KrUm8uQsYT%t56 zW^Nc^s_ai_EbhG^(I;!G>)Z9^KJ~Z0M1haG1|0q`6h07K&#`;Yyr$ z6G*`X9{K_?@S&2SWJ26;8?oP|5_|H8on|UAtirH|ik<+976WoyS-r zZh9idxFX5eBD#Q@&=?A)2?|GQdEZxYL}DaS8l^A*oDU{lQ`%&eai3F2YGJ}9f$F3& zfMRA6D=Efa1o|)rimBUSk#s_tocbo_nF~>Am7`jWd#ZGO+H8Lc6Hq#+9oim+_B=;| zKm1p7yul@{%AzO+ZnROUP*SQB0jw7hF$$^?zEZ5SDXLw$tX_Hlsu{xsx00O@`RpS5xL@)#nD^*I+0}h*d+5mRL zqcSV7t>20_&|qcanrt@XS&pC;2cZDJiL&Aqu`GKQ3Q(~qJAAiibLF!s9qX;BW)Eu? zZuI&=0IE#{iz4G`utR&WE5R7{i9zj|sPGxFLg2LAkOLLV15!&o&ci@iD|!-_I9*Go zVR{f6z@H;VAyC_^Vq3LnJ4ogqIJbbF^F6DJK8KVk3?e_lSGPYzp}4zvd&?4s24y3A9!AimU;4Y6VFW~Q zxH~Weh)Y&Bp#+ZW0zL4!nNfe2rEy}XQY_PG$P!sGI4F}gHe_aMhM*h58>K$m8Y6JL zM3B5haK8RA0?w-h(5nRJD}g1XYDC3Q_wp|7GfghHiEUOb_$W!N)rUFq~x5lr3jEuM!87TWg)qt-MZF!oBw?k-W23?~tQ(Q6vrDBLmW;_d@X905 z$)DT;HLR3;d`1X)7CYQW^5cs0hQ!R8%!|DLHZ;;~f94W2lC$6X%tJvVI$+6~OwC@~ z0-YSn+AIiasZj*P4Q3oqeW;Qs>?CX)HOu)V(#+25Is(_6$?&WQPr}WhT+eekn1cB@ ziZINAjC?3p$cC)9L_4qseYDH`3wW3#e``DsT{yi7(NR@ZM9`ToK@CcP(Wu;ds+`8@ zY=CGcw&?S@DknAUP||Ic4@5wkXORy|;L`TX!w@3S$9!A33s@?M3&|QW0=s=h3#1CY z(7mv6XFRpn`nO5ln@bI!#SpQ<>C{G0u~A*d@Wj2^$jU@qbIYm11L{y*Ew*0W)$I_m z&MDULo5KS-(|`xogxo^~yj#6RhxnHN(72hmK@HSEO4JN}t-|To){tm_E!dBY4N&`@ z3uX*ei`bjzgP=qX1+;_B!nrE^oRz&0m%R^RZDRd!wqem4{h+p=T^uNPzeuwP(!@C= z^m7#1e9cH}MfaGBcL}*$+qTVDe0>t;K%B6%+sEnKzwO(P3=^b_xDS?WsQb8uY6PZK zy-@~1bF+ibtq2JboeNRj5{2E_jomEl55=9LT$0?98ft!AO>t}-OQYJd$lBHU+L+*5 z`ZnA46?AbH*ZyVS?w#Ab0NEu&4(Q-E{*6_e*Wbc9;3N~^ifat&%f1&z6Y)F07q&O{ z$9WLM-1*lmlZX}0lr+{&-AdE{;V=E+&$;3C(%}oT;L5wD{_x<@3uEr^m)2u@FcfMQ zDC72(m`3VmtzCUAkl$~?#HEnLq_D(Hyx01j3&ddHb@R7Mp5*Y^a-<3wAXS!rv7o-0Ov+>bu4*)YdgysFu7$6ib%si%@4 zyLIODF<7P>W{%6FKFe|Z;iQi0tidIeE(Es<6`0Ngyg{=;$u2wRUN5GEH~uhyZZU`% z7h8!4$vo`Lis-(e=m5_D=*jNr%MR(y?zP5H%R-RJUY-rQ%mbiYdP1a(>I&U+!7Y?V7|Z-5AoBhGV`p-ovt$Y9LgOZNsYKb_TfoZ zq)Frc;wy&BrIv)I-txBh@+exrTC?#xOS8G$%{RY7LAgR{7EBD)OO{}GKwbli7g&tf zSB}S;uMm0SDbxy|@OeF%hu-k8V?m{X_2Z!R+`#qJ@^GM`wShx3A|Fc#RPxO&bx?$D z?bFU5hf34|8m6?^Anw*!C6_S&k zm=_Zls;jI2shpLh6#%3%wYImlqqw`ay1cux0K&t>#m2|T$;!*j&Cbuz%pTL!%GA}# z*VES8#v$J!C*tEd)-6tIpXu=_9r18#fAR^lmRRxa9}}z#u6o3 zw5U-dVG0#79OMwd0EvwlE*w`p8Do>q6 z0U%JLr_Y~13Gf6;w5QObMF~V(%CxD|r%;S}m9LsyukY^l6Q^Z>m{m zA16K>mX_gK02m!1!YIt6M39JPjofQesNcUrF|8CO$<#|0tP$_?bPHb2V@1l*w=DKwxjPR&Yk}DJV z{d*FDT95;8qWrkzPQcKkOP@|%sO#(4vuigCS{$ #include -#include -#include "core/rendering/screen.hpp" #include "core/resources/resource_cache.hpp" #include "game/map/map.hpp" @@ -16,10 +14,6 @@ namespace { // es pot convertir en un membre modificable. constexpr float SPEED_PX_PER_S = 60.0F; - // Color de debug per a la caixa de col·lisió (ARGB opac). S'elimina - // quan el joc estiga afinat. - constexpr std::uint32_t DEBUG_COLLISION_ARGB = 0xFF00FFFFU; - } // namespace Player::Player(float x, float y, const Controls& controls) @@ -94,18 +88,6 @@ void Player::update(float delta_time) { setPosY(std::clamp(getPosY(), MIN_Y, MAX_Y)); } -void Player::render() { - // Debug: pinta la caixa de col·lisió (16×16) per sota del sprite perquè - // es vegen els límits del mapa de colisions alineats amb els passadissos. - Screen::get().fillRect( - static_cast(getPosX()) + COLLISION_X_OFFSET, - static_cast(getPosY()) + COLLISION_Y_OFFSET, - COLLISION_SIZE, - COLLISION_SIZE, - DEBUG_COLLISION_ARGB); - AnimatedSprite::render(); -} - auto Player::baseBox() const -> SDL_FRect { return { .x = getPosX() + static_cast(COLLISION_X_OFFSET), diff --git a/source/game/entities/player.hpp b/source/game/entities/player.hpp index 1d9755c..af21ad9 100644 --- a/source/game/entities/player.hpp +++ b/source/game/entities/player.hpp @@ -37,7 +37,6 @@ class Player : public AnimatedSprite { Player(float x, float y, const Controls& controls); void update(float delta_time) override; - void render() override; // Y de la base del jugador (cara inferior de la caixa de colisió). Usada // per al Z-order contra els sarcs: peus per sobre d'un sarc → darrere; diff --git a/source/game/map/map.hpp b/source/game/map/map.hpp index 39b9179..d0e00fe 100644 --- a/source/game/map/map.hpp +++ b/source/game/map/map.hpp @@ -18,9 +18,9 @@ class Map { // Graella portada tal qual de l'AEE (4×4 sarcòfags, pitch 65×35). static constexpr int GRID_COLS = 4; static constexpr int GRID_ROWS = 5; - static constexpr int SARC_W = 50; + static constexpr int SARC_W = 48; static constexpr int SARC_H = 16; - static constexpr int PITCH_X = 66; + static constexpr int PITCH_X = 64; static constexpr int PITCH_Y = 32; // El pas entre sarcòfags (15×15) és exactament el que ha de cabre el @@ -39,7 +39,7 @@ class Map { // pas de 320×200 → 320×240 i reforçar la falsa perspectiva. static constexpr int HUD_H = 24; static constexpr int BACK_WALL_H = 24; - static constexpr int SIDE_WALL_W = 20; + static constexpr int SIDE_WALL_W = 24; // Origen del tauler dins del canvas (cantó superior-esquerra del primer // sarcòfag). Derivat: mur + passadís en cada eix. diff --git a/source/game/map/tilemap.cpp b/source/game/map/tilemap.cpp new file mode 100644 index 0000000..f6bf7b7 --- /dev/null +++ b/source/game/map/tilemap.cpp @@ -0,0 +1,138 @@ +#include "game/map/tilemap.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/rendering/screen.hpp" +#include "core/rendering/surface.hpp" +#include "core/resources/resource_cache.hpp" +#include "external/fkyaml_node.hpp" + +namespace { + + // Cada símbol del grid es mapeja a (col, fila) dins del tileset. + using TileCoord = std::pair; + using Legend = std::unordered_map; + + auto splitLines(const std::string& text) -> std::vector { + std::vector lines; + std::istringstream iss(text); + std::string line; + while (std::getline(iss, line)) { + if (!line.empty()) { lines.push_back(line); } + } + return lines; + } + + // La llegenda usa índexs lineals del tileset (esq→dre, dalt→baix). + // Els convertim a (col, fila) amb el nombre de tiles per fila del tileset. + auto parseLegend(const fkyaml::node& node, int tileset_cols) -> Legend { + Legend legend; + for (auto it = node.begin(); it != node.end(); ++it) { + const auto KEY_STR = it.key().get_value(); + if (KEY_STR.empty()) { continue; } + const auto INDEX = it.value().get_value(); + const int COL = INDEX % tileset_cols; + const int ROW = INDEX / tileset_cols; + legend[KEY_STR.front()] = {COL, ROW}; + } + return legend; + } + + // Copia un tile (col, fila) del tileset al buffer ARGB a la posició de + // destí (dest_x, dest_y). Els píxels amb l'índex de transparència del + // tileset es deixen com estan al buffer (alpha=0 per defecte). + void blitTile(const Surface& tileset, int tile_size, int src_col, int src_row, std::vector& dest, int dest_w, int dest_x, int dest_y) { + const int SRC_X0 = src_col * tile_size; + const int SRC_Y0 = src_row * tile_size; + const auto TRANSPARENT = tileset.getTransparentColor(); + for (int y = 0; y < tile_size; ++y) { + for (int x = 0; x < tile_size; ++x) { + const auto IDX = tileset.getPixel(SRC_X0 + x, SRC_Y0 + y); + if (IDX == TRANSPARENT) { continue; } + const auto ARGB = tileset.getPaletteColor(IDX); + const auto DST_OFFSET = + (static_cast(dest_y + y) * static_cast(dest_w)) + static_cast(dest_x + x); + dest[DST_OFFSET] = ARGB; + } + } + } + +} // namespace + +Tilemap::Tilemap(const std::string& yaml_path) { + std::ifstream file(yaml_path); + if (!file.is_open()) { + throw std::runtime_error("Tilemap: no s'ha pogut obrir " + yaml_path); + } + + auto yaml = fkyaml::node::deserialize(file); + + const auto TILESET_PATH = yaml["tileset"].get_value(); + const auto TILE_SIZE = yaml["tile_size"].get_value(); + if (yaml.contains("origin")) { + origin_x_ = yaml["origin"][0].get_value(); + origin_y_ = yaml["origin"][1].get_value(); + } + + // Carreguem el tileset al cache (idempotent) i el pillem per accedir + // als seus píxels i paleta. La paleta del tileset ve del GIF mateix. + // Ens cal abans de la llegenda per saber quants tiles per fila té. + const auto& cache_name = TILESET_PATH; + Resource::Cache::get().loadSurface(cache_name, TILESET_PATH); + const auto TILESET = Resource::Cache::get().getSurface(cache_name); + const int TILESET_COLS = TILESET->getWidth() / TILE_SIZE; + + const auto LEGEND = parseLegend(yaml["legend"], TILESET_COLS); + const auto LINES = splitLines(yaml["data"].get_value()); + + const int ROWS = static_cast(LINES.size()); + const int COLS = ROWS > 0 ? static_cast(LINES.front().size()) : 0; + width_px_ = COLS * TILE_SIZE; + height_px_ = ROWS * TILE_SIZE; + pixels_.assign( + static_cast(width_px_) * static_cast(height_px_), + 0U); + + for (int row = 0; row < ROWS; ++row) { + const auto& line = LINES[row]; + for (int col = 0; col < COLS && col < static_cast(line.size()); ++col) { + const char SYMBOL = line[col]; + const auto IT = LEGEND.find(SYMBOL); + if (IT == LEGEND.end()) { continue; } + blitTile(*TILESET, TILE_SIZE, IT->second.first, IT->second.second, pixels_, width_px_, col * TILE_SIZE, row * TILE_SIZE); + } + } + + std::cout << "Tilemap: " << COLS << "x" << ROWS << " tiles (" + << width_px_ << "x" << height_px_ << " px) des de " << yaml_path << '\n'; +} + +void Tilemap::render() const { + auto& screen = Screen::get(); + auto* canvas = screen.getCanvasArgb(); + const int CANVAS_W = screen.getCanvasWidth(); + const int CANVAS_H = screen.getCanvasHeight(); + + // Clipping de files: només pintem les que cauen dins del canvas. + const int Y_START = std::max(0, -origin_y_); + const int Y_END = std::min(height_px_, CANVAS_H - origin_y_); + const int X_START = std::max(0, -origin_x_); + const int X_END = std::min(width_px_, CANVAS_W - origin_x_); + + for (int y = Y_START; y < Y_END; ++y) { + const auto* src_row = &pixels_[static_cast(y) * static_cast(width_px_)]; + auto* dst_row = + &canvas[(static_cast(origin_y_ + y) * static_cast(CANVAS_W)) + static_cast(origin_x_)]; + for (int x = X_START; x < X_END; ++x) { + const auto PIXEL = src_row[x]; + if ((PIXEL & 0xFF000000U) != 0U) { dst_row[x] = PIXEL; } + } + } +} diff --git a/source/game/map/tilemap.hpp b/source/game/map/tilemap.hpp new file mode 100644 index 0000000..75b7aaf --- /dev/null +++ b/source/game/map/tilemap.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +// Tilemap basat en tiles 16×16 (o la mida que indique el yaml). El mapa es +// prerenderitza una sola vegada a un buffer ARGB propi a la construcció; a +// partir d'aquí, cada render és una simple còpia de píxels clipada sobre el +// canvas del joc. Això evita recórrer la graella de tiles cada frame. +// +// Les cel·les sense tile (p.ex. les caselles dels sarcòfags, que es faran +// actors independents) es deixen transparents (alpha=0) i el canvas +// subjacent hi queda visible. +class Tilemap { + public: + // Carrega el yaml (camí relatiu o absolut), el tileset referenciat i + // prerenderitza la graella a memòria. + explicit Tilemap(const std::string& yaml_path); + + // Dibuixa la textura prerenderitzada al canvas a l'origen del yaml. + // Offsets negatius permeten overscan (textura més gran que el canvas). + void render() const; + + [[nodiscard]] auto getWidth() const -> int { return width_px_; } + [[nodiscard]] auto getHeight() const -> int { return height_px_; } + [[nodiscard]] auto getOriginX() const -> int { return origin_x_; } + [[nodiscard]] auto getOriginY() const -> int { return origin_y_; } + + private: + int width_px_{0}; + int height_px_{0}; + int origin_x_{0}; + int origin_y_{0}; + std::vector pixels_; +}; diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 8d8f990..a1a5f63 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -18,7 +18,8 @@ #include "utils/defaults.hpp" #include "utils/defines.hpp" -GameScene::GameScene() { +GameScene::GameScene() + : tilemap_("data/maps/pyramid.yaml") { Resource::Cache::get().loadSurface("player.gif", "data/player/player.gif"); Resource::Cache::get().loadAnimation("player.yaml", "data/player/player.yaml"); @@ -59,9 +60,11 @@ void GameScene::render() { const auto BG_ARGB = Resource::Cache::get().getPalette("ui")[UiColor::CANVAS_BG]; Screen::get().fillRect(0, 0, GameCanvas::WIDTH, GameCanvas::HEIGHT, BG_ARGB); - // Fons del mapa (marcador, muros, sòl). Els sarcòfags es pinten després - // fila a fila entrellaçats amb els jugadors per al Z-order. - Map::renderBackground(); + // Mapa visual: textura prerenderitzada del tilemap. Els sarcòfags + // queden "foradats" (cel·les transparents) perquè passaran a ser + // actors independents; Map::collides encara els bloqueja per a les + // col·lisions del jugador. + tilemap_.render(); // Ordenem punters a jugadors per Y dels peus (ascendent = més al fons // primer). Usem un vector auxiliar per no tocar l'ordre d'entrada de @@ -73,24 +76,10 @@ void GameScene::render() { return a->getFeetY() < b->getFeetY(); }); - // Interleaving: per cada fila de sarcòfags, primer pintem els jugadors - // que hi queden "darrere" (peus per sobre de la cara inferior de la fila) - // i després la fila. Un jugador amb els peus davall d'una fila es pinta - // sobre d'ella (el seu cap tapa la fila superior si s'hi encavalca). - std::size_t p = 0; - for (int row = 0; row < Map::GRID_ROWS; ++row) { - const auto ROW_BOTTOM = static_cast(Map::getSarcRowBottomY(row)); - while (p < sorted.size() && sorted[p]->getFeetY() <= ROW_BOTTOM) { - sorted[p]->render(); - ++p; - } - Map::renderSarcsRow(row); - } - // Jugadors que queden per davall de l'última fila de sarcòfags. - while (p < sorted.size()) { - sorted[p]->render(); - ++p; - } + // De moment els sarcòfags no es pinten (seran actors independents); + // dibuixem tots els jugadors sobre el tilemap ja ordenats per Y. Quan + // els sarcs siguen sprites, tornarem a interleave per al Z-order. + for (auto* player : sorted) { player->render(); } Notifier::get().render(); RenderInfo::get().render(); diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index 3dab755..5a3fbb2 100644 --- a/source/game/scenes/game_scene.hpp +++ b/source/game/scenes/game_scene.hpp @@ -4,6 +4,7 @@ #include #include "core/scene/scene.hpp" +#include "game/map/tilemap.hpp" class Player; @@ -16,6 +17,8 @@ class GameScene : public Scene { void render() override; private: + Tilemap tilemap_; + // Vector des del principi: AEE suportarà multijugador local; cada Player // té els seus propis Controls i entra al vector amb una crida extra. std::vector> players_; diff --git a/source/utils/defines.hpp b/source/utils/defines.hpp index c06c8a1..e4c3e7d 100644 --- a/source/utils/defines.hpp +++ b/source/utils/defines.hpp @@ -3,7 +3,7 @@ namespace Texts { constexpr const char* WINDOW_TITLE = "© 2026 Aventures en Egipte — JailDesigner"; constexpr const char* COPYRIGHT = "© 2026 JailDesigner"; - constexpr const char* VERSION = "0.12.0"; // Single source of truth — Makefile el grep + constexpr const char* VERSION = "0.13.0"; // Single source of truth — Makefile el grep } // namespace Texts namespace GameCanvas {