From 7443c8ab54d78cce0c8e7dd13d3c3db9a34e4cf1 Mon Sep 17 00:00:00 2001 From: juraj Date: Thu, 20 Jun 2013 03:28:13 -0400 Subject: [PATCH] Implemented 'move', 'attack' and 'pass' actions. Implemented 'BattleService.last_state()' periodic calls. Implemented fast user switching on 'Frontpage view'. 'Battle view' now playable ... --- res/css/main.css | 106 ++++++-- res/graphics/battle/ofzza.gif | Bin 17691 -> 0 bytes res/graphics/battle/sprite.png | Bin 5468 -> 0 bytes res/graphics/battle/tile.psd | Bin 50263 -> 0 bytes .../{tile_move_near.png => tile_move.png} | Bin .../{tile_move_far.png => tile_range.png} | Bin res/graphics/battle/unit_enemy.gif | Bin 0 -> 5848 bytes res/graphics/battle/unit_player.gif | Bin 0 -> 5946 bytes res/js/bt_config.js | 60 ++++- res/js/bt_game_battle.js | 251 +++++++++++++++--- res/js/bt_init.js | 37 ++- res/js/libs/angular_interval.js | 5 +- res/js/model/bt_model.js | 65 ++++- res/js/model/bt_model_battle.js | 137 +++++++--- res/partials/views/battle.html | 20 +- res/partials/views/frontpage.html | 5 + 16 files changed, 567 insertions(+), 119 deletions(-) delete mode 100644 res/graphics/battle/ofzza.gif delete mode 100644 res/graphics/battle/sprite.png delete mode 100644 res/graphics/battle/tile.psd rename res/graphics/battle/{tile_move_near.png => tile_move.png} (100%) rename res/graphics/battle/{tile_move_far.png => tile_range.png} (100%) create mode 100644 res/graphics/battle/unit_enemy.gif create mode 100644 res/graphics/battle/unit_player.gif diff --git a/res/css/main.css b/res/css/main.css index f71bab5..d94738e 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -94,28 +94,63 @@ div.menu { /* Battle view markup (TEMP) * ---------------------------------------------------------------------------------------------------------------------- */ -div.toolbox { - float: right; +div.game_over { + position: fixed; + width: 100%; + height: 40%; + margin: 30% 0px 30% 0px; + + opacity: 0.6; + background: #444444; + border-top: 1px solid #000000; + border-bottom: 1px solid #000000; + + font-size: 18px; + font-weight: bold; } -div.toolbox div.timers { - clear: right; +div.toolbox { float: right; - margin: 4px 10px 4px 10px; - padding: 4px; - border: 1px solid #444444; } - div.toolbox div.timers div.timer { - color: #990000; + + div.toolbox div.player { + clear: right; + float: right; + margin: 4px 10px 4px 10px; + padding: 4px; + border: 1px solid #444444; + + font-size: 13px; } -div.toolbox div.selected { - clear: right; - float: right; - margin: 4px 10px 4px 10px; - padding: 4px; - border: 1px solid #444444; -} + div.toolbox div.timers { + clear: right; + float: right; + margin: 4px 10px 4px 10px; + padding: 4px; + border: 1px solid #444444; + } + div.toolbox div.timers div.timer { + color: #990000; + } + div.toolbox div.timers div.action { + margin: 4px; + color: #0000ff; + font-size: 11px; + cursor: pointer; + } + div.toolbox div.timers div.action:hover { + font-weight: bold; + text-decoration: underline; + } + + div.toolbox div.selected { + clear: right; + float: right; + margin: 4px 10px 4px 10px; + padding: 4px; + border: 1px solid #444444; + } div.grid { @@ -149,28 +184,47 @@ div.grid { width: 44px; height: 44px; } + div.grid div.grid_row div.tile_selected { background: url('../graphics/battle/tile_selected.png') top left no-repeat; } - div.grid div.grid_row div.tile_move_near { - background: url('../graphics/battle/tile_move_near.png') top left no-repeat; + div.grid div.grid_row div.tile_move { + background: url('../graphics/battle/tile_move.png') top left no-repeat; } - div.grid div.grid_row div.tile_move_far { - background: url('../graphics/battle/tile_move_far.png') top left no-repeat; + div.grid div.grid_row div.tile_range { + background: url('../graphics/battle/tile_range.png') top left no-repeat; } div.grid div.grid_row div.tile_attack { background: url('../graphics/battle/tile_attack.png') top left no-repeat; } div.grid div.grid_row div.tile div.unit { - float: left; - width: 32px; - height: 38px; + position: relative; + width: 40px; + height: 40px; + margin: 0px -40px -40px 4px; - margin-left: 6px; - margin-top: -8px; + background-position: center center; + background-repeat: no-repeat; + } + div.grid div.grid_row div.tile div.unit_player { + background-image: url('../graphics/battle/unit_player.gif'); + } + div.grid div.grid_row div.tile div.unit_enemy { + background-image: url('../graphics/battle/unit_enemy.gif'); + } - background: url('../graphics/battle/ofzza.gif') top left no-repeat; + div.grid div.grid_row div.tile div.distance { + position: relative; + width: 44px; + margin: 32px 0px 0px 0px; + + text-align: center; + font-size: 9px; + color: #ffffff; + } + div.grid div.grid_row div.tile:hover div.distance { + color: #000000; } div.grid div.grid_row div.tile div.info { diff --git a/res/graphics/battle/ofzza.gif b/res/graphics/battle/ofzza.gif deleted file mode 100644 index 33b07ce93be4c6b911a535903c15af62d47c3fad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17691 zcmdsfc|4SR-~V7RW1q2R#x};1UBrkOV`3%pWpM`uiyXIfA81l^LcOI%gEReqvaqDk_Juw0D+i5 zj9?CSFrxr7h?R|n8^XfO&cy-c0&{}cI3OSx1B464&cz1jWar`H<>cXjz$Ca~Trgf9 zPF_(yIKMQETR@OsNKhCiyjfI8kVA}BNLUbt+=3KAY!cb5yonDfDh@-##3V#fXbCx# zxQ?hGLVCN5w4Ahz6hcuLrphb3Sq34$Q%?pZuPBGmLQ1JwZBbJd))TZ=L}N6zX=`d= zv^4jr%c<+_mNAhMGZVw=>#5-nIqGT{5_Tx;lMCLVrDJgtPdsX2Ni?$dK4M0&-Dk6R z-+^>%w`-A)UlN`-Su_g`)T0R(Kr*jTYBwLqLPA-lnM~v+~4mi059CzC9e#q^N z<6cj%!>1JmSPXHFjp z35yB~KYcXT_vUdgzsSmn$Z)?z$Dr720r6F5<6`fG1?;|Tc{VOFDE^T{E~z~Fq(k9> z#;BlEnN8`L>F0CKwWUPI=C&WMJnUEQbF4I|H!Ujpazj&Ae0N4`hqR>HM;e;HDF;*Yc|>l2h+KEUPFTEXqlG zl#+ex_4Ufu>7vZa+ZAV<&Sc-2&8;5Gs=s#gVM=pV`NH+AyZ5VSZ(N)z%g=wfSnz1J zrlzH!wV?g}o#NWJ{nh1nA3Z(uJa)0NAoEE^ZT+L$Ew`>Vf4bH5=6+kv%!BgzdpBRy zKWu7gtbF{btF685ackme;+IF2=SQ-td%o86EI#S#DSuhM)O4r0XSA!Q^Ha;CS3TWN zd!M}RYB@iXnfLbM*t4$ucUL+G<_89b&d+BIyzIOA?t1>hrGcUMGlP9s7m8jD4=ucW z@p5GP@l^Yh$)3^S@sh>T$&umT$0x@oMyozoJY1-0oojjU`QGg>_ussknVfn(JvIFD zWB=QCA7&~Tl@qJj<949t&Rn$%1D3>^Cc(J_X(}GI@4@l8 z{;hAFB%-zio)ixBkt^J!?LAncwTESgnNILTi6Yw0+F0u}Y0RGMA9oIrB4trRL&L?Q z{e3gdVFKS4ee2ncj6O^imgHv^gb>CKo$uoj-O?4s`CJ;T67|LiC!IQd253yu4%A5u zye+QA$30HGr$7ybs}347gcRo+baRt_NiLZ6!)df2@DG64+IO5_B)Va zjGYGNTI?4p8d%_I0elP~!Z1$%yjDRJQai4!o=vh}JPIyY?v!*v)GV0 zE?EQNGuZQ3jypkfuged=Xu)x~%zROw`Cx6L7C2aK8(hM1CMRgI9oTif#VLN@>zf-C z1bnvFv030T_v$Qa%E|`f*|!TTjgtkF5{94cwABiBaH(|KnEJz|W}kYePzI(52C3vNTIabuwHM3giYE@G^F;F~9|+pTllR){ z3LydGcV=r}&h>^*Tw|Z$_n_O+JnkA!Op&n4lm0 zqL+yMtEh5!JyDk(#cjWz^bY5uDk0}A?iPT5vt7P4|>9z|kitHdp?OYBIS)2!>>fmT+cFRp$F?Q9o^0&{*46HYI_WO>xDpqWf z^&1_Kck>#tC@^VXLLki5Z0i;v@_3lU6pPV9bGxgix4*~T6kb>2ws3Q0mx-D9!p;Qy z?SbI~kvrY%7+5&CwzYbsy|GgEUi`Ww-(q&~f#2Kux=lOZFc_vvs|d)(rnOlFkcMt2 zUNBPJd!39sf6HTz90A#S$r&Ci3Cyc$pcCD^sx}KP&nx)NPxA_ig|kcT!iJ_nY|&4n zC6jym*<+d^jHcUUP~)7s<473R(kfQpNt&?8xJx8F_h_Q_Q?z;l^5jzFI#g9|;aK6h zg)5_aVg0D8P)1c#>la5ArLPyIAli%e%#?`-v%UjHRiHI2E>MCuYI+=NRcUvR0%~WB zB)*3ZR0q^5sEz+?pypx284<|mh@II()lUj1Ow6jO5E7`1xTd^{vWU%8fJLmF1$hdN zVx?cibt{_~YO3z>teO2RWoDmp?fUwuoI10_)>KueYSwUJ8nW@nXvlKl0*N>%FA_Z* zj$q#A7bAHIq?V|+G+Y_0FkKvBid&ku+5sZOt}NmBnmFnb&UeI}tPTqYKy_FLsw^4@ zJ|z{p9@O!F0je++)UCe=>he(?Z1qXFVjYLMf{XI(Yu3M?p{RBj_WY)OG3L9Pi-|Y{ zhWuaNzl88`CMIZF+MJ+B_CKQSpP2zJnH&A;qr^ zsq8I%aEKJ?XFRhaSB5lu{g6`b>4h!Z|0bU0`7MbEqnKKusAQS#XaT$pl@h6FVtdIP z=Ptsbe$%_K*dpfW6M>4)np_RTz})Em*80h$Y$2#du21#Oy%LI2Y(gI_c3#>WJU0je z^;Ag6>DhRzi;Hi#r+%@H^|co3H*doS^Bh%E3}4dII1x?n7jp6uD4B~jelG-mqmGp1 z#Mk5TlS|i!PmuZR-Qh9`fz-a}K@mQIvUrMcY=%ihiEA+|zZj@6}BIb0?WB z`)V?$=huqYlHoaqvAT~IMqRrvJ@T8GZ<4>Glv7Sdp!)kIBs5uKIMwUKZ{fh&cZZw96Of~=GPNCapb}4NuMKGy zbx5B%;zVZBQY{_(*WFXLCK+eO7!d{*p}j&5Wb9mz6_;<4FiROmH7D18yspa zZ-B^`sDyJm%KE~zSw^660|th_dQ9nf?_etpQVa{MXuAxmIIfJsasf!k$IoVo@UB7{ zd;k{wJgR9Uq|?tYNn_YQ7Z;XZuQ~1w0=W+)Ss|rWkm#fhy!Qs*5>dQJ$b0s6r&C`^ zVx&-e|ANCCH?C~77ze?lIRT-0I4xa2nYa6Z)?xOAj&r=OQOSPa?=`=j+3HhM$9BZM zF2d+Bhta~lX;)39h3Px#ysolvMd1wYkQ1Bb-Ad(;1kOyCvv(-%uV>yGXE6uMGt0+c zI#J_WIoW+sEAY!``aZ!fn|e-m&zYnol5xSVgjyzpgAd>89J+feC5fFgCOwsIPR9di zj0tjsr_M~NIxqJpvV_L}ZiaGBCWs~A9i5b=ge4*BQ)O91R_%#`G@-5Zyf+?)@Dv(% zFbZX8<~J0Q=+T)^Ye-@jA&_#-mf2C*dUmuQU`NNo8~-{xS_h~KmyB5o#_!|A;pffU zugCD;=Rf3Byzh>+?^i*S*LpA%=At`K-ei=J02(r+;!7se*wMc}q^JHqJIZ_VH`tMc zegq>F`!hRg+Vt$(NOK%FX9m7~ym(vL``=o+@-wdQt`{|~Co@C9F&iYe2=;mDT|jy6eHe}k5b&=fgSEcPn%Swxy{5=oN%l7Y7)x<8chaty1< z$=;_%FJ9?rfZWYXMEgoz0Y_P$C^9OJG&6a8!PvA{e~+rPEVUN(s=c{BH}Ioo)}d4Q_h6xA4^4U|`=sc=;LOt8-h>MegC^>mZ6DwMM??H3 zF<}Kj&m;$sUfoG8WcB2|;Q$FnLwQ*U%sguZ>9?sdZrvNLjtN@b$bx9GqaArWpy2A^ zsL<7esyEle7!Rpp~&Tv5;a(IHyh-+pl8@ zx*tN8$F}xh?8<#@#LlP`^=#Jm6xX-gNxiQn>-+~*fG=Ib{HmyNY|xA0u~q*(DNw#U zMFT#LFUcw;qF7DtO&sQLPh&K=dIAcFnrsYb0*}K{G8ux^Y%CMcdlMC_%PVVtKD5@7 z9j>WxQoPPkKsS1@kx<6KjE8Y&d;zX(3H8eIO#9$%3gKTmt{WW_y`unv%!FHMh_U*Rz+}2pP(wy6o#_4jKCrdqbx?G`sY9%~7 z$c2EYk*A27r$tjY*)vs`p23!HYKa%+rb_NNv&FGPh4}~|9QCHIp46tu!!OQcD3CYl z4ra~fQE8F`E6c;+fq)y%`VosWq(rm>nE}r%zDAXNip=$9006aKlh$oz)|IVn?(w;2 z3&*xMo__-GeE;Lv<`Uly-4CG=EAZ~zEya>}4iCWV2WjNm$~I96I1!*LG&+o>nh?1H zR@5`gzit;(M%K;j7e)W7B#^3 z#hScPE4}#g_+SM{yo{+mDGC)Q&C{Ux>cR1u^9$uX2Al5da%!umFY{mF75+OSz$JC9 zAm>r(tU|=XzC=+?z%Aqh+`?CVfLnNyD?ZU)o|w+#sHk8 zukKmoGb>)$=E5J6F^^;{ewpGw~**c(K9zpN8ACc{{G_(OA>Wme8@!_eMu z;~zQoK836U7P10tx7&BTUyKJtl@W??5Vr_l^9>Vfa{6}(b4*>1*VqFg_F6f<$!bff z0ZB$1ER_Im;RzVcE##pZg-I^U!e+-t4*X;ZhX40C@Gu70vZ<6UTkFSEry9efs&Fp% zN+%fXX~)wam8yI&Pr$ou2^$`-5W;mgHkrf}dpgFOU;c_oUJR=Suya z^cuv7bYt^6S+hIjRmBOlu=O0+R_$7PJr|ySOryI6iVyc)EKDa^0VeSNv8Kwttsm|= z^*k+X2;|uT&IaantUY@O(|hgrI^AOlRdpPiR5uuT_~b*136UU*@qa&PukF5kYil;J zD4&V;C;OsR8ln+uy6QIY&8hhxYi|`DI5Z;UWQyC4h8YTBtwC#6gz1U>OZgqR6Q5OE zkalSlKNwH*mx`Bo+i*XTr%@+m1&w?bgM04z@%)Va&S^s1T}J^IxOId|nXm_=S9UKL zPi0FELwG7%N~U)R|2+=8B5KwHifss|0~F$(rFFD>J)jM-rz<)NcSf6T02Cu}c;B{w z-LQmOJ1g9!;|rr*V;Rh#Fp-`2uxfWYr#shQ)D{^|>0NBvJS%g$tWzJ_oZfRIrC?9E zsno<*-+0091ATR~vc4$8UC;ITV4mJS)`{1T1ddr(CPbgLDtD~nbgkB+dWQSoGIdcr z!;cjs?N`3#MuIunYSAExB8$uBHPr)ilch-Qle6vVlA1@5^&Dc}9iMxs>L!P^BY5UU zv#Ht(bguz?;~Fk$N!M#vpgXY5@uxN%SihLU>^qmRevOyy$Cl z&;R8$FLIz|j!ArXl~)7He_%kvu_2%-^&$(v)n$`FWQx(S3}EzNbN+P9L$b`zi0>`Z!PRnHp>U^ZjpGmm=?pNQbU924pvk$=L` zsS+V`-hZ$as)iP+E6S|!TWsudZMi9WzqT4?N9f7LGV-hsXUv1|9=xkpyUK}7S*t|u z_aA>@V`0)SX`H2kl?z=>+&Tjsmm1h4N=r~}`g9w1HF3KrpADX%pJh8;l znGY9izL()z<*yU`0P~@&j^#9xc9K#ZCHc{1@FMRGo2!f-r}A$6%G4 zFtmM3rjkfi5>teimD_u?NmzH;?qEtRlMOeDDW=n=4UMbXH1_{Wn-2Em)o*E zf!`=bucnP=J%?Hf`kemd^$O;}N##%S4)a<^Z>13dA|yFeTYF4KyIWY^1e)p&>F!wH%Fw zER3+Q?X-|;UN@wdlOQ+0t=>=S)>WY-G6lC+9Q=#x9ehe6a{=>r6B(5r756#}PR-5x zWwXiX(?Z_8USn#$qh-Ed_uiP$<3V8(a_N@t|F}CC!BD_muu+jp3iDwp3Z{G(lght8 zu?z>_)pS5yr%o#Phhaae4XNP}+UmO!qCTLe zs5&1fPDzz`x52YFnV?)oze8<~&pJ)oI_-c~^B@0aqe`F1jKr^kx|9SYG7t)=LKIMY zHM>2&@PtED9~P$&iBKVx^Q+nE77u*!0qRJgJF{+P|B~1R1@kG*OaWOJtC)UHc0Knc2|&cT+*;>|FBpJCAWDC4iYl z=_2qEpO?3d{hh@Z=o1-rqRS})W@93AS^pW%Z9^8`(9@07uropbl*mjUN4iGA{k}hF zp8LAnXL~){5%jrb`~CsdzCTo*vCh8lUbgRfk;oB7L~}7`EOD9N-lq}Vj?90g`xt-` z+GsRYvHVfnmHKHLbx`k$*V$;tuL)7Li+fjAZ+H(I>_ZxWr#Nj$udWQK3GO2h?b8|# zILdscjGlo6?%e+2eKp$>X*DyCiol zjad%Z&}(pR2-u?jNx+sVrikICPh(Wl#S}D!g*K)!zlR)89xh(5L>(|5mf~u1TtB5T z^2}^_qv#Z$wKV2J4h&sL>Qhjzyng-HCGXvlg*A_Rf*AV>gc^Djxsn31z@_ z7VTQjN1bI>b&IvJR2Uc+&B2;8+i2~* znF&HZWQ1B%AO-42tGZ=`V*1So{E2S)XOxIyE(H7is%{yr)jGvttBx^BRdKke0e8N| zK2TwcVokwUvF~mVyF$DhLZw8o|2#?*XE%%uUp93UJ*lQHq69G#35JRCZFCYIG-dwd z_P(Y>F*#AnXg&!b{eCxfc{KH^#@i~Tz-#rR&Oql?@se&0Cy4vE`qVT+Iv(%Fcx* zv?%@)VC$@$3O4|onC=z_moS5z_H0Xa+XXun%dqo4@OZGgcVvS-w0gvKvZVxNo|B%`%i}(-d zm90xnXWFF(iMh35Op;Uq^U8)QU}UleOUZnqWXS9=wW+e|AJsR>561GK$P#{@1D6e` z5sV9~z$@)3vzR?M*aCXmHeo}v@)+r=4MS7Ux^>7tRG)~pjlns?QM9uz=VOcd zvy$+htu43wqS#nhT|k{hM@Kb}#_;ZKZQ|nz+h>Rjp-kQb;>`&w5zH+!H)ygID};{! z;^`ZMYo2}Tas~9&FH}J3&A|VtwstjrTl>>H45h6tOC$l_5ee{)SnY1r94m*s(+`rs z#qdDPR%+Xo6rn1s2lp zFNLNHCL;T;c|05R>k&B4M~LP^t&t@%#UN}jRuA-i%PG~Bl;3r70kdAh zS86;#_dAgROP{~w_Qwfx&Wu}@>?2B+Sq&RuYe^Pf2NB?Y*u#~Yk_M2k#kz7d-; zvUmx*T-Cl$t!jU9{C0fd3~xQJG%u29*(KH&G&>U@f=q8^S0ArugYgklR}L)Sz}fiD;wL@C)0?DNl9$I*2T-P`rIOY`N)F(q*6x3T z^xtoktq)(;r)fgbsipfirfFwR$As|D6>dz^V#3}Cs9u8_k+u@C^uDgiwURGad)m19 zx!KUGA?c>;arR=WX9T25z>uU({Hz(vUPC7({VwX>oo9bvN1}vJmPbUyq<(2kSovbj z&8Xj}Od>r8N+R^SAaML-wB(8Xsg(%L`+*OJ9*2l=s4^p~w``^?}n zbxz6aH-8a-;+WuV3C?MClliNHn!(?{raKiWAGqr~)=|lYLtsb^Tzu#|;giBcYs?w`$}Y8i>qf zg<3d*&$Xoj{I|sbQBpl-vr;RAC9{h-Lu&s??((-KX@?N&6T`6JJ!?_-`1p03_V^U* zAxq$43?%MY)b{J@;j8TwrbdU?9lI@k#lxNzfiK$m*cjX0I~?kdmi<7No3cf}ylJ{c zG9SwT824?<#=Smb57Vt3tl>=5RzuKgt6_wl6MFuq6kHi8n=~A@?BGY)(HwlA#)u-I zh@Xm3Ufln9K}8TN?m=0u|66nM}Px zzKWf47b!B;TnZ?f3DJ^o?pj^*Xfr}3t#S0oUHU3E&B$Tg>L|}@D#-?oTgL>-G@|W0ke?Q zAT7@GBD|5)VdF!KGT@=b%jJg_H@6{u1xqLoEjC}IduZXg!CV3!TG*{Tw75-sXdy9D z2H^V=VXkqoa{;r4@5^O;`LCN*Ypmk?>HvUm+jFO){|?`f+ms=tF5f$SIxngnje&|U zNXUj-@qM8Myr7wXH(|axawg@B4A2J+vr(${rA_KghPgzNctnRnudVcop@dz8=Whwh zt>}n{o^u_rbNre`8P_#v-E0|iSKGZGd)258gapWOx!r4>y{_F`4zzn2?)CmcyO+C) zQZ1{;Tn`zdRLhzjg|*=v>c%e~?R{u@4ZayLfRDbe`nIp|kkn&p+AT9DT}OIJa-6d* z6RtXhPnEN8Pn&|k%%t#H_b63h8%=-UMB(N9xq zy$jU0Q89FHqd?g|y^ZqOvwU#1Z_wUG$$q{Lyp8(U9C1VN==M%FBO4)AR6X#D=A)dO zrqU_s@Zeb|U$LFV2k!W*^5^-6HXMkpd zvRnkK+~eLX9*?v_DZ|9&YFXJxoX$|eW#Z{Hso??ID=4)2_A!d_%fko~Sbo}8x!(BI zrBusIf5G_WrY3DT>~hj3p-xTO2r6A-_#ayiH5yuykY*11~4ebjT>*f#yku`T=G9NQLPY_&x1 zl3|{*V;bo7nx_ddIk>x?bLySAJ|h+0S%%b0CxbiZ&Ptufh64u{#PWh7cwz%6Poz$E z*p5c4+|Q-J8w0>Ayt`RXB*wS0jFFK}hmgd>dPh7|H8aaU}sGKBP9Q7%;3s`)W) zEDi^<%K*8rtYy_6fA+gQ`jMa*suj_bLf>Bb3T))lx(mOUxvd1_7~{2IT+0*1syheS zGJ*B%$8C#ih)qRjGHNY-3}Bypw6F2Z5L={L?~Q%eLcX)7&jXI4t~ntF8$fx`SmB*T zaTH5QygZ=8KxSUHF*COTPVdz6>D?oeQ1*}A6mA9YUM{v!GIN;5&zbp+pDwP74d5tF z#R~=>d3_?AKcP&;5l;rHqEq%=-Z|c{Vh56U7O!pOi?3eK^tV5E68vMR`XE%YpV;h0 z0MkE{nmXIkl~ss4FMccRgmw^hEu+L#z6=%hX=i*cf2>rD1jhIDN$U7IQpVTmlJ55o zr&yU*h#EUi9?z4(?(I)A`|m9{#YzuhDp9xtk+d+5uo&*7cId;?M4c|QwVlhMSYKyp z8%lV?c*7tY$FwJ-mUeb2e2N6Y+Wbk61&GM`iYp@Znkp^*){OTX)=Cg+9%n|O5jm1-`h)Bv>J zQJ<00|3&Dm_J4c8U?$V14J}a)Q?V7(zhLDFhBv#BjyoljtDd!cT&N=ySPv60jQc!Q zd|+^Frg^B#>qw52UKCH@+DaDGq^{(;Ciy1!OciU^KSkH5GeyR}wZpH6WqNgbKQiS1 ze$uywX{TJ5f@H3~5`iRWMZLVel*_x=^qgDyL91k<8s+j@DOJzXF7F_~k)!#p^n{tw z+~VY#aiB@a88FOeWgqL{ENx8zmK*ljneqI}y~X2|{BtEhX4F$_z0PH##Bjg<(3tkn z?N1A5+7%lMXTE<@IBWb@g);&X=$Ft}xD}=t-xc9RRaB5Z^6Q?FhxF{fm*!)RP}WzW z<#F4n4N={_k-)Qda*tWgV}*pYxt6U5bb{@QO3!iW?70}{dgjVlYCr*G<@$50Bdp7j}D4{(9UX+40ffrLTD_Yhd8_=@7 z!=|){7|zPusKRH^I@Q9Fk4lm$!l(9)vlE^03HqnP=WiFzRL z;>NkzazlwwRCizQP2-3w>q>-g6ZCgxa=lzvB5Vm3MErL5=u0lH+v`h&^AC;x5%|Z; zIu0nuBOyZ12E7w0&w3MC<3T23BV3eY0o)fjTeXkCUsLUm9g^Y+d3ORwp!sy(@ z8H##kr@kr=s>3lVRXfnevltjp4r9gKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000VyNklsIIK2k^{a_NYP{uK(SCNo_hbx{OIV7Y<+#r zbaPX~SanV9g{CF#Mez+$)8{YkK5-#OR`Sxgps-pC z)46&DyFa&=dzLTZ<5ZSc`mQs2?K(5VlcZ*5i4Wf-lNjOK)v;yZ)_|1wFFAhY%~xOQ zy*43B!h=1(K)f`@i1#RgwB0C6OH8a{21Vd;4#esSlUfwc0^j?;)3S zv1O=u^_1(!w*?T#7@Gd?$v4vHe zyZQ1%2RQW2gCvt#)^{&s&H8rw1_o(qh~)tkNfNp&MA_q!Id{6mxwAPwzrT*(-c-xc zkIvBBljMoR7T?{|i{j2ADK`7JcJcPV_3^?JU&SZc^z~n-tFx8vn=M$jTLch_A{mM* z>$U{=9Rf9enrmZBFW8(LnxV42p2PcWO6C-!Nt2T9XVxq-Ha3D!_0n8l$zzW{L-&eK zx>t5GpULR}0#}ldkaX8Z4Jz#3vXr)1kfZ0X;R+v1n_^`1CY3^`v&PRGLq2}>;Z3%! zZKrujHT&<`h31iY=#lSoZDO8^a4^3J1_6?=u<-f??F|(aO`XB1Bzv|lLy5>-o~WnK z*~0VVYk6&`l8U0jT+ZUTpZ|)s<^~@B&Lf=q*CSjS9;T`yT)I6VLKc+EWon`xrm}h7 zK0Ct3b;}u;nPNdmyl`VH?NSW`Lql9Vew9Do`J3Fo`!3e6{X9#mB7EikeVn@VhfL+h zk&tZwA%x`0vT$YL(A*f}tqVgWQ$@zpNn}}~y`u@LT;fynjNX``Ff+?N+wNrDnl*gm z(Xa7GfBJnikB?<-OA)fbqp4*8AqiC!%DRXIT;`HFQrQxb`Up9*Ot>OQO)N@P*iXSJ zVwJNTdHM-14UUnR9_QIV{|^3;%6$(UqUYj=G}hIiD9U2-3`>^_rGnB?8zGakz^CA9 z60#<)e)(!sgY^D3?2cRpgvYXUbe`XItx~bU3GQ5`1=RA@$o1pF22iO{^E5;u8*>L z-D;L}RDzi&>i6N%v?72K@_3~Re?S_XP2$=*nm>%{6?ptAjshkoS=**c4iJr1;t%=Q zw__v!{KLQDuYYlZj=C^|@0~>1)Q%*9kkv&niWkdu7*8d@t6%`g0*_zA>sQEGMedGv z&^xK~;`@VqeR~_vKKu>Nj$h%q6R#0ku%WXSBo|dkIF3}31qiU1NN1$$i3xmOA8T9M z@q0AN!sTi@!H%j{{$l+ue!OQbS7&B9dSQsR+9?12)N`y{*F?@U2S_wm1!;;#D4UkK zI3SKC3gwc>CuceQg)edG<$q%ThCAp_POzb-kwYtfgQ2-3iehtk$10AU>m^etGCP&y z+Ko6;Z5U*SNI=1L99L6R13+{{E(w)PlLy?BiNu`Ag20={5?QiYefe1>>3WvHsQ*lxLEL7K~?q&quSFq_VB z;F%|R^}=~pR=1MROQ;@+Kj@C}5B+hjk4*C5&bxS{_Y5aT&r{i4jbWJt0~!sLehTH1 z1y}&0Y?R%SUZia3)JLn>zv~VPa-PBT49~oKj@TAgTVpz+|kVg!L1mU z!BBphlSA{ob#;Q@*|3yCDa+tO97&bXT$%ZzNu$rZC?H&cZk28;A>p{L@G6>6JPv<* z;$P_LzlJGUxB}KzxA5q;4Wvzz-guJLO%ZFRb719K4~|{6%v@1O^G~?=5oCMJcF}y{O;z>Z0c&ECEy{UCwSwc$&KVNeVH*L zULVx~FL}Gna3KXIcx3OVV960^lIkuPc|EE`;xkQTU|V?|bE!@)6h(;(;<#OG(|s%zn| z?)@UJQRMy0H#n^sR8q@Ok8mX%7hECTR-8&lZDVK2 zr@vJS)DaH*ICAC;6;hO$g*-pIbdJ4;?!~A0NT=qwuc?EP(G0q!bMEFSrfX2UB*^ry zT>^?{`W9Gz^2sON_J+*@by;LQ$7W|t_u=KpD6rXd4BnHYIJ+4oasm`q?#7=T(E4FRlg?`Z;G~Fst+%@6sv5x zpvZ~^iwlSS{{i2sfo>P;i+Zlvu=QX`FDgM_WxP62I|w+7S6tjrd_D{Q&jA3qO5a5O SumiOK00006LF9->HnHhFSfDi(N>;VZ0NeH7X$957E#}2kLfU;MC7AS3*W$!{w z3uTWmODB7jQYfReK$&GjV*k(4z1O~WjG^CKzP|tOXNNdf(z!>{Bk9q(x;i2>Dm9%5 z2=j4*KdtDm8xb*4y~9JJ5|f>p&`@P2&iqZ&8e=&0WS4Fw1rlkVTqn+w=O|QN8{XSh z+EA>Jb#2(ELvq{Xf@pcJB4LC^o<5>yhIB-pw3Do1w{VXxC1EA`1^IHFL|l@uRB6LX zx;B)^)LHT{xW?$fhGHtB%j?=O5);IIlT*dfYK>gnp-sEi(zZcC;*gLwLG3$)gaow` z2e%Ch4r~kmLRts459<&T7Ti(Hej0{*z*QHGEITYMCXUSxLR}l?>U0HRfq})v#chh) zwNYzw0)skr>V#^7gIj|{Ywd8APEyiZrESD0;q=7FwNj0uK&Mcv#Hd%2r7qNUZP*Yq zGJV*3$u7BY!WVoOVKSbGaC7P8iQ)G|f(@Hf(8mSlBw2}F>2pwkvbysYA1*BCOX zsVxc$HA>p#GHIY(DbJUybXsVSAj|fUNyD<$ntX{aTvAY=R7fS*iVzrG{2cPBsFV|al)Fioyi7iy&P*doBe+}YVK znVcM!sM6{rDycj%CLD^>Mxl^}wU3Hx->y?|WN7=C_OZc1L7hT^+s1YXiH!@2jf@J8 zj-x%x@>a_}0ckO6X(2+-s+FPI$Tw5VyS0Ma6paF?NTTG)V5$F(dK)S#WRsxK!ayC) z^+smo(>|FlA7&ZLbylT<_R#`~Mk`0;yEf#Y=koxg3GbmT7$%WozYCWlGstCK0xe?A z#j?IlVWZJri!P&^ck8S57w5`V_LzlHMWtS?M61r$6-zYo$QVV4%5c@#~T=wy?N_D_x1&IBi11|gcSfx7PvI4|@&;gfye5_I(a9IIjKj?tVK0a2d z4!EoUu^)86Wgj1_R0mvEfY=W@;Ifa8RjLCnD?scA9dOyl$12qUmlYuPgATat<71WT zfXfOH`#}d>_VKYwb--l>i2a}gF8lacr8?lU0>pmME?gdF_eHr1d=`trIgxrdhEZ?F z>+p@5L+&T3vb;xav`fM3qi1R~6=C0$$_aXr%_jmFT_c ziV~EFQt0p=OlEY-H9aNy@{HKN84$%}#@qlt0qX2@xvsDvC2Ob@61_+&(GWHKWD_w- zCvu`Ag`@yg5sw0sG=!*{D5XxtmdGosP@&W*R8%FTyHn}t_bvkvv zQmx8iZ}H+%un4?(Z!SJfk(0|$@Z=JpY$kc^IifpE8At;B6%Zl!m-ecrG|h+26#jG@ z3tq!`*IgRt?UkXCsI=e^Cs#>_Gd-~?O{oXp-XsHhyM(BS7W%jZuH|qoCBqpVP93PE zFMLCHFdAuy5Sf6{QM!?1_o4>#m6HJx$hi^^>%Pk-{*4P-T^BIH*j%B0GkLU&_+e|Y4 zCutN5R1I|_?ldH%A%FU9^+nU$(nt@1=|f|&K3F3hnr<}4jmFf1Z-^5{Fu#xs5$+@S zC7@E+(uA1X3FV0hF=EicAm^dZT(g6p39#S%IL4dZBAFJ!Duz#WL{+FnM#ieUfIqiN zrnO=ksV)f8Xfbhkn#KS`T8$wRWgcr-6q05hN*H$0f-kXLo2g7fG7wng<4n^ax(fWd z(m-MiFE7^|jk>VFBFaTg{arbpk4;DIi~}p^@Rk(n)bVnaT!YS|5JAxKloj@1gfRwF z67zG!@aJvUUj)wA7Ly$X-!djyn`1d4x=56|3`vfKl#f)dRA$Iabcx!8jN~Maa^1LS z3t{(MwPr-5Qjx>Wh?SViCveeFb61%>TT)1;0QVxfMrT7jlZ&<#_sq(no_{=WeHr~x z@umd0h4fUb5Nmgxx&TZGt(?!X9!enN3b9^Ul)Nj%dqT74T8q;W)eJKNTLHRrxBK*( zVZcf>8hF|(AxWW93xV76VSYu2!@m9ci(QW*1Hh<*jxLdE3zF00&>$h;j3`F0L`D<+ zhSB!qEFJ0ORO^IPv6z?kCYQd_0u4;Gf>aQ0Cxi18;CdolD~ojn7>}j)&&tDVAr3iz z4HOWrYv6SbbB#mEKa07Jz-w8)3OcwD%UmGKm*MpxxPGsw5Iepo39cs>Ddfd)eHyMC zD+}`#aE-j{pD&ldV01#v8tdfJT)1uv*It^8G?=NKx&Wnlx z+m=ax*o(HApxO`SIP;z6IQ4RvGhvPs%`(Sj%_n5rBtmNZY>sQZ6inNRgls*=t4}rT zFTBGw6mn@B1l06-t$<1L)fc?{+F*VrZ&`5+Gla$1tEJ$KTc{C(eIb>LTU&SH|M88y zV)4q+Dovg(M>|F?&V*hDW*9WLN~WOageX)NKD|5*|HIpO8t{_o*Kpyt(m?zNw;`2} z`4iEdABdBmClL);3@HLryCu12!gB;6ed_A(j?u ziWm_m{NqeKNG0M=YLa@S5orcCO)%+5I+F+zOS+R3u;2QTL13}vl6g8G6c zf|i0{L8u^H5GzO$qzU>7h6r*5DnX&(UBLvwbir)F0>LuDI>DEM9fJLWZv`g==LJ^; zHwAwQUI;}(FQLD%uCS>vP#7wV6!s8i2nPytgu{d*gcF4`g!6>Ug&Txhh5Loy3(p8I z3;z^877>xBsG6vOsI@3m6fH^-^%rG}G@{X>X`(rz(;=r*PFI}nI~kn4o$ENabPjV)bnfe%<6P`K z*?ErhYUeG^hn>$jUw3}u;^I=xCBUVlOM**3m!U2rUEX(Db>vh*>Zk}%S-GbfX-1@tz+{U}jar?||uiHttKipor zS9WjW9_F6nE^{C5KGS`r`%d>`?$_O4diZ(-cy#s1@EGbb&SRd3-s7;xFCGs)Jv|$G zcJl1ysqh@@InVP8&u=`hcs}>4;?=?{+G~JUq1OzrHD3F?&U-!d_V#Y-9pT;ITjxE) zd!6?|?@Qj#eEfXc_$2zse8%`J^x5ij(&ug^_exDFMOGSAX+)(tl{Qy8R_S(S_sUHx zM^~0q9$k51ID;##F?mDnn>Dif=$sB*B%H9wJG zBfn@rsow;@m41i)uKT_Ktv#*wrrJN(5!Pu@C#}xNIxFh@Q0H;odUfOL z7Sx?z_h8-I^{Um2s5i9U$MyEsyIJ3_et3OF{g3OH*1sjLDvlH@#q-36#P=K2X^_yM zu)(qhM;p9s7|^hH!|@F_HoVx#qfuz1>_#6qI?(8TK!j>T|6)hLFJkiRzRi{>}R?AzRYwguKvUO4G^{ub8sn#Z`&GLUwSs_sp`2cg`^~=e3WuAH6V3Z>aJccy;6HE?DczEP};j`N7DV$2d96Q{v;zR zV`j#M-pzU!_CAnVDRV&Py3D72V*1SPbGdI|-%)+P?^mmzqTlxZuKhFnukQc&o!ECi zdFT3o4g)3+I6E+4V9CI52h|#+9JFU}rNNTHn};|L=`-ZBA!U*j$#Ti#ti-HOv+hfy zrE{f!$|7VR%WlfM%4f^}$nKgwJNrgX*PM@XZsvB&os)Z85v^FDcrY|!=#rsN^HTEG z?f!ggle=w$g zC~Q_Zsqjisc+tY5XT_Ptn@fC4R3$$QZ!vu8@IOYxjaW6(X{2=Ifp;6cJMP^pqasHw z8!Z@}HTuAqMq?(9xjr_2?Amc21FRr-~aA|pbzGKV4RUXxd`(d{a*Ut8xJ!1B?kCH#y`my-qX&*oOWbh~7&S^hq$y|@Qg>x^@OPaTRe&hMG z=9ewVU2tY$)WVIQ*74?(D`wI5m-k-Do+JV6bE*$K8@bsZx zhmIcZarm3B3QbF+1_V=Iyd0l<)36f-8ip2|KR7MpI=@W z`-}50(=JxNIOkHGOUr+4{_DoeA(!`DiMjH_)%2?uu1T-``J48)vg;Fn_x^p(ANBuO zcO&@5?wfHpkKcOd*7ZLN{(O0R@*UqhpWbbD_p5sm_kOtF_x|+E8tJo!KeYzX z81SNDw4e!HnL&F3ZFKwv8?o9K5MPtjAOho0#yiA|9&R=`IvK)ui4P4C*;tSj7%zal zmz$fLyPKE0yO*!0ho^7VN?u--s@CxHtLo=h!`F*`*gs6b`di@b>FMp`UD?N{vcHdy zk3W9-_%pZozKH_kKH}>Jg>NMiG$cY_fyh^2ya><#BI6-JHz=huv-$xA#*c^ygig*b zu5Rugo&xJcSQQg-iIs`KNgxtB37uVC+?+*T?I6)t|D|8{%Pxihl|7-KagQp6w zJ&X(Zc;%$8J~?#yw@0hC96odXab|YWVGaG9gU5a7k0scjcH(}!##vW1<7Y>wSCwkpKWO5D1#oTJ z;lN2K#ER;&*xn&J`BhU9UR{C!a}kU`6E6|X)0cE3kE0@-J|dbvtartYPGslVd`+zNr7~;H}Wk zxwk&=^wYJJjEEg8Qf?0}FFiTtV5{k0?*63nuxp#TkD9WoP(T07&BtE^<}V!;Qt;&1 zi|!ZiW$oVGX3cL`2mJE>JN09KcoCA>V?ejB&fFZlZN!qY1La@dnz`+^p*SyVlsdY2 zO}!<@4N=|47>{4N^}f%GBd1IM?DqWTw9h|Ik!#l)$<>e6jX68!;VJ(F=O&#(t_^R| zM>@Rq)6*X7Ro!NHAAVuj?zxg}Mlx{5ufG||-R}|{ zbLQvGfuplKtf?Jz*>%^=v*j!DVz>CcEM1bmzHiOm*N23*TYKh)P_k{p&bjq}yLjiP zKgO)sb@I!iTh&K|PcI*}cgCq(>JN5FFP0W>8`{0(P|FSL7WTh0=wa2f#|J#Ee^I~n z`%Cw3tvbE;;Gf%`xURc#w>Wc&OV+iSaO>Xfy84$+<(t(v%g5Xd zTJvyW`K2XgDYt&TaWZ}Wl*iJJ?Wg?iH)+P0{afpfobuPqy&J}qRXg@<)p5f&H;rWT z82O9A>%RUqMZ0E6`tu(zt-fO@zi{UC>~!OKW|g7=9ZCHipkFv3Zz#209*Y1Ii1W#!U zYa0?4)Lx9IG~!`iKrBlI=CUmnn)J6?Dl%U~Uifhlzyk~X72wLLxgLUg=6VR4^6McK za5@BSgl64T8GOM?5hB9;MRu>W6heeN%a^DWiD_~8;^Q{PN2+3QDf0*Ttqi|-4}91q zRV)#Sj7#UQbdfksqRQq1D0dgt0aA4FGjZ+yk*Ecf*j_f}?xGK+aaxP@QOH}vfAvA) zv~e0PZ(LgCv?oQYcxA+TrK#znup8!$ABw>;dRZqwTLY^{wC#Cy=IZ_q%zEz-;}a^& zNZ~x#f;i!O3c(o~zlln3qB=IAZWWw1X@o~=kZ8-^*^GI!fp^*eb&Hp);pkKiEH2S< zDhpLuKJ1rnBpwEj8lJ)NeW8X7$Gnuo6*5Re#OON>sWMB6VFVu26{_qKHl2p^RC#KC zSg9?@wg^j=8VFlprOnO9JZL{cozg-rT`7aO_V8=Q%?%HI#e+s677O66lnj?^G8Fl8 ze4`h6S~cf%+j}EvIkHPM1$b|SuxU1YxEERwW%Z#*Q*04`)GDMQT3ZI`8W8#Sk@o2l zn1sa>)3x|;KH6eX=tH`YK#~OStA+#K8X_h+@U5orY3bW#IPeyca#F(TuT@L9(y&&F zC}GQpnDGBL()%rthH}{Y01r~zj_cE>QOLOmBJG@vNIQ4O(lbb7w-De6z|R}^Y1cS~ zQV9!b6a{mCb|rB{L6q>LfcJsW18BOrKU38H`}^|?3zT2cpKYQHD`wcAD=-J5?1G~W zKW~n5Y<4!hBi2$BvrB@Z+vPGUcq7z;VPEkc+9zM4)yXw+ebb8-I%zKVK87S}(sM1| za?u#b!V9yJt0Y+_(PVgu&%G^=QmT?sAX%xy5s!N*#FlDoz5Yi@?!&$Q=fe1xd;QOk zODT}Duk+!(dfGlyQelpgzDq~2iBgiIq%Y+oRXGuZOfn=!t5b1%0l3#Km>QjzZ`vbZ zmIkXrS*YRUBX|it)mk|duaWYiNu){_$-U4g85&i(B?jra1<@dm{e<+)7mL}KUar7E zI6CC?y*bqvttk?-^PbRBN3% z5=$)@1KGJ^mKI$iT{m3GbqTa0Pyh}jj>EsHDqV&tO%q4c(xIPVnys$X6D$EtvuN;s z7{h$hM~K-C79~oHwtS|E2k=bi77Y(DP74 zrj@~KE7A^rX(M40!RguJgOd4$N-+_Xn&UB2!M~kTfVb0*&B=1N3#@Nf=O{6EG?uxW z;S#qZ?A^nZ4-Q0E5y+Gur(o`C`MSHH4E)=&HqaJ(EUahGZ2>B?&nP{sv4}8H=lvn_wE!`KI$?+JR2CS5axPdC@a)mXqd5< z(3YgFS|HKlvj`*mzip%5@BVfWZToi-eTz0K-6N#@w{6t>g>Qm1(p4+?{qM6;J+bfp z_cO#w3zYwkjVdZfDt5VwQ-;e`tS#?Jp#9(N&+h5)-!#y3@Y#{gumm!VJKYDXWVu-Z zPg<%9OhMPC|NGJY(E@E2BkMml`?TYUfPu;JL_mn+iGbaN504bo+W!}y2y*vzJ0fPf zrs;`5eVK`XZ|;e}6u~l=rG}Lb8mfvCjz?t+n>GV zjh_gdbJef`*J7?m3*VW(^eKY5o=Y>Y`q4e-tX=xw@ig$Dx_vYSV7qvMmxGtZj~l$b z`8z)z7I&jA_j^QC|GOh~?~w;Y;JdWkDEPF}1M-jvT&BJ-3JsH-9+F2ypnUSwD0rCj z2sYUXGH*OI3afg43O!krzbJ;=rFq!R{t6NEy6O(*I!;tf^K;3`F2D z@0w9C%f L@@Hj6{F}0JaY+pJ-cWWJc=dTFBr*UvJJi${R`hr?%9xgUm@{c@T*Zc z+a1eXXCst(ZFK|ICGZ=l%YhbUSjWhpppGXyzrea@-hjGa>-h{Z8TJ$~d6f5rJcjrA z?>#mO#)}>!h(1f71E5|H5!?nl0NgG09uPph{e|;Eu>1orw%^Xr`Ixu!<0!GYz1|MO z=VMHa$qaeR0q+S*eSc@byTBVlPJ<77g+spOxl;jj$k*Hmz4EgfbjW+phu-=L^i~-0 zBg5>@Utp( z#J4&O*zRveM|`*I*E-V?Uk$c3)g-k^9oPmfCJkUKbYs#KUcfZ3@Y4+98$)^nkgEss zwc)-Rq#C@YtwMZZuLIYfw0*10o-uXem=T4;lsVGD{WH^g_DGDo9pi5!HZJyVOjYan zq#oT9;^XecROclO>Yth3E462GkHmzy*atB+%t={;-s#t;cY3dsWP00QF}1j~yd0@y z@WB3kGkd2ocgDv)imAh<7AS}2%B7M)1Ny_wX(>IEx+liR#XgCt%cK`+hpCi9bL3e= z1`RN&PmGU!8dDzxCX9J^L`k8xK$Vx9Eo1V?VDpHJeI6s8{lU~p6UL2xcX)B3MvZEu zL!gv>p_Hk~G@}#khwR5eg|ROA2pT{F*{8GF2dr4O zWYNO;b3UFuljc7bstx%gda|q`rV91w9=AGf~FnzNM8%`fYP!pMUk4u^}fIKHLxyR zDM3B7I5rO4{6JqFHLqcsgXv?mImV8@r?1MC${PN=dVjdbtdOhrZG9D5Q_w~G%O@1u zTl&ge(=4TxgRXf)@540$?Smi9_(Si_p|ouA!nw0&T-SRt4X~UBf75$lIae{Et9rK$ zYyW|3Fh%%OS|8rkG$Kv@HkJC>qS6nLN{b4BN(c4totDxgfl_IOSRgN?Qmc4|6Ol^! z3072UN-|R^(oY;xDU&uV4@qa>I}GVC1o0@QHj`RtL8U;_z;Fq1k7MewH^X3HxPV90 z$hFUaN`~%ohkjq)yg<1W z#=WUG+v8F?{&3*%hP@S+#sQb|xy;O^ERQ2U+u+j4K;RB0qt^Jid=3Q zmXynLU(cl+3s}rZ0emi_T#9T!xzqy86yQ=Eyi1oXTEORWN(;!PFcTt|XQaKFOG{vW z!WofmMx(w7H(=Qbf+ICsT*_w8%n!`mI38Gu<MJpG13UN4naz?ZoxH5bbj6Ydz^yEqt~QbB`gQ-nH6_#PZ_h3r zS%h{e7?eP!XqWm?i_aVj)@pp*9o8)6MN_jhmNQG)gnlMF$ZVFXwVEEV8PeaET#4l4*y|b%2BhF zTDNG*q5aD2+rJPyHQaMXyT>daWTAHOWK+Oh~rEzaXr7-MR zTia}w;4nqkz`r=~WT`V5uYFiUyuX+foowK$LC6vo8XGT9=PqRq(>3>s^!S&FTQ^Eyx| zo0%1rj)sZQ!Yp0I+486ZACByrrL?7r3pultwP`u4mo|>cEJe*YUDMK0%}Axx)}@v? z^eK4Mpif#hsFY4zucT5A$t9eDk7g--G-0i5hDoVqjx+2+pjI$T83tZXgC-`WAuvnV zuU*Zg{3qAIq_|LP`Quo%`3Kg0{?%0*p?Tf!pS*f&Ylx1OWmAj2HH=LQwd{ps$r~fTGOj9$9k<}y_R|RbH@7ZJ6OG*uJ zup8O?xYR0{>^AIqX*#hP zFJD$_{HmPFqaMRfPTo*jZj$Hc3r^mf%i#_KXfoeXw#T^FV2WpQU^J1vWK;QO11}G* ztVW2hn1^vs`6k2WGByXU&W7D&5BahTQmpef?lyc`Zpy)2JF>eREvo?(4!nv3#L@YZ3g`d8izGzYCyJ?K^#*a4xN{#7?djCLbg7qF=j-bu)51Pm4O7= z@>u^8bZ~i*EstmzkupHrku86j!i0t&Kp4wr*u;eI(R70;d>8U7|H5br--dJpFMLa1 zZV|qzH`omma^TS6Bj0}i!_ng>PoF(^{=&sem#_YI9Y4P_pFfz-jf!$tu3kgQ*U4SF z41yqOPwwZRFZ^=x(yu7V3EIr}r}LcP6;MevR+Kw+`pnsLW=Y5#^R|*ZcKpOi5IjQ# zq1NWiE$aK@(W4fUrc(aKDnwkq`S!c-fB5m}F-G$2Ig8ApKB$r62ZD?wllj@Rw3NRv zc|(1u+`&VKzdrKKx8HvM{g0rNm8_uh#{L5!NF^;)nguUkrC^rsr;@1hyYFc+ZL8c{ zx(@^o+E#h~!Y{v2h^}4R1A-`tr95I)A9LncuI}EwmsQEshgVAIXg{4lf8hdF{PNCS zFfEuRzu~~-N_mcL@*P%!SfB3@_2Va?CC{AYntVG5qRLWQAB3oaN{ae6oRoI%-VJ%P zlC~kj`3nTOQtsza=de7%b~l*naH0c2OO@93`RXeWWF?u3??1>kIS-;UXE$$#>1G?v z9P7h^30Pt=d3|FO2!hJ3+qZAWQex)NJO_9U`6EJf>dQ?iX(}bEk_ z#x|s050ae9Ef)3J%l7rdID9~ks{8`-wv{z>Oe#0C5YZ+_ESZjps55e`Bvon7 z+>H96!<-ziloWM~UIfi!k~3uvr8KLg?X%BP&Qu?!l+fRpj#+9gXDTHXgkFSweb*iv zav;fI$wBl=Ij%kwqOIFrEoUwz_H;XPSW3tptNj`|TFU>S93kKfQKBnQD&&%Z{d%YM z>>e8x-Z?Zhv?F{%LP9#gr+xeO?b@}ol1q$@iU{u#_8Pff$=%~)qa(X@?cB+(zFz1V z63?oHY+sRYX76cX8=~E*#`tN=TxHH z*wNq+0Dcv?KMQwzar>olGjd#PbVRqVVWA=I+fAA{4qP=xloaV`=2%L)!yBDtxcUI< z9p0NV5!^4pONFY;A4>ODQ)db4bA$Ckm7Ty%1UAH3szl!yW~U#gvTGOEf<1LIxM`SG zB1Blq{z+sPlbF5cQ96-`2GEV2d57gbih|0US3#GWDS< z0d?p_;9UT_%BD|cRl;sOrs9^UcTb!Ly9-ez)CX0<4m3cW?y1D>+NMqLNw6oFRSBiU zt!nRb5TR2Ltxq4=XrHoZ;R04AWR5D$^~oEW!|ba^#~au^29k4Ntpw}CWNtzo>cgn) zn+bd97Qs$7rj%HpN!AcC8#ZBEn@J_D4-1j0luXAQxC-{o!3O3}7r<^}*!ON5b;x`T z>^@wMnKPxNsB@(R-yrC24C>%Lvw9_FPE|4xLHjTb$z-m=o!YQT5>&3_RPs=#_2Ez- z3cKs5BwHUu9X(%Im0T}U<-?oc&u||itq+!xsm~1BFQ$ zID?>j3a1k5GmmR>t{1`ZVP+au7>0{~xSfYQBsTaYhm@&;-4r1h~o$=W=)^x~bi7)-gr8@%@X;D48` zP3nW!UjS)ITEFtA6{I!?2Wv}b>RNEOA6?}2rQhDLZQC8r*Z5EM)_zNRZI>6fnVTK_ znUDv>y!nsiMs*Ci-!X~~Cv^Yrj`5z+^_MDVOLSCtlqq~L2uXtj7W~_N2t!sFXK7+s(1y2snD?B>9TXN|$;|pWm)BU?eqzsZX<|^MU zJTg2Y{p<^4nX$pGoXE&@X};Q)Yj{jVc+}uKWk!Rs$@5XsQJJ|qu+wcjh(nQLh8YZS zno#o3;#TI1?TbjAy^EfJYX*8KZlNbU?1CZAWx8> zZ1e1#1w2PK@Yb>EMBbed&dFW$29D%OSyXe*&?R7Uyv~`tr<4lk<}EY1_CZBo;ZUB+ z3M*H8ZJE01^?Mm-nA?7d_Q!sYdt7zElSEBRbSqGV`w91&T6t zNdIqN0AY!?rVX8UPcND?G=2Ls;5O099;%fO^`aF8No$`N9~(vGAH-^(=$$s`VrLm1 z8y^})4@V^xKi7+jdyISZ5O`E{BR^woxn4B3_psX!kYrCu226c#p!D25BT(;w)YQXw z@ZulfV007*&CT@p+i_A_RU1=fx;14u=0CgZe?T3k$HSfrn-A zIrv?cgJ0oU9#fHnt(^Gy4(3)I{5n5#D-K4_VtdZ!gR!qQM-HZ5lE8bfb~vxd!SL`s z<`quomK^*V$MaWnunp&Pj)N5(2gB+D=R#e<0o}yGi*XQfUe6ye^8t8{;voWbnF!aD zUdO=}j_5Wym`8l(i4V4M@j3Xl9_f4zW}NwO{J+96{Z$+cgv1`4!W>+Jt9BJRm}3^6 zk1Q-bfOn-W4z}{L#Fp1-Y;dqm$8~ECHqoMm1G@zWzrv9{$C88DDUEAd>d^iw4#sJW zI<{MLuzBiYoKY)qu!W<$1qU-N#5uh4Iry&LhnbY9=erFO#@Y41jf6w34B<#iLpa{H zA)JK%+m?oKTAx>ua8jRvj3JDtQ=mt;O+(ny5#8DlR^qB)1!r`dBy8Iy-G(7-$2HxG zgz;pAF?KxEtw@-jzhK8r-HL=wZq<0&4=nlOTDJ*H6A6Fx2JY&XBy1)G)(~da$GOSQ z774>Dqiwf!nDGp@JzsV{3DX5rut9l- zFs}o^nsY@GX6tFgwcV10={hjayt8m|w;*A*5LP72xVqaSVeW~8@A7U&{!RewPKB4pQNBkViE zQwx_F;G{ysLV9Z9#4}gaE{-@KJ?VW=sR$}->p!3YGI=*aPHsFHJNit zzx@?X>l5NRrDVboi^y&+!8~ktYaQMV0@u*R zA@%hThDIoTvoJurJ_-Xc=+(!Wg(@|pamLyv1X&9=Pt6Rh2|fYvGyqbR0hsZG=7j;r zOiYdK4B6>`pI{bFDS%f;k&#Ata|e_^MK3N@USfw1N(TIq3E0X3tT^E8ZH#CHV`nll zBOCC-#N3jM_Q(W$v@o;$60nZ6I$~r&j0ZkG4|vDY>cLt%_~|s^Ev@4~qiAc}bAZEW zjqi9Wx6aA!L}|8%LmR^&BW6}MCt+nFTHi!!er;-P7Y<(jO7_rCKhMhAGFJT|(bArZ zNoOJjN#H?$WE~4}1x5-xiuZ8CopLcwK8`G88lIsUaV!a>FHATdxCBpZN~m6$Dbdxz z!p9rOb+n{f5p4x>hn~hK$!0;eX4!7|kX*$>eY+q6$;QEwO(YR)iS`YE!=u(@k}dHF z(HY_xYh~wRX78=!axTfBz{DZY?ue~}y<@0xwxM$j;%IV&QIn~2WT<&6@@R&OldF@n zV=25#jwXbX`AOI=dsiCGktPDq`X2F)!B?|v(-|%db@xVfcM+NL1;DGv`wMS?&j^+7 z7U_^uZZzQL&hV%D0|I86u#+O}LMV;x@5Mqh8^Y*ZHX{@s@WTzQa3xImbC)dFLjxGLwVT?%J(q1Ybx#aW*|P zCo?qbs}n`%BL&>Vd*r!uYG@iS23Gxiv{` z)hS|W(C?yC*P2gXt4r%H_)<`lb-69GyDRF$N4)5K$ zeR%bcnW_8lh7aFQ{}QkbtZuqkMi*J4FI73kXHr)4gnU5pD*-3X9oa0030eK8GWL$0-%;zSaw&` zcoA61$s)s;L%SF^&30ihQIo}bMzJ^c-$gU>upS2)z(vdqpL_J%5z`ObJ_20uDhol3 z^=-C_@@rXAiuP#1oU3hj1D((i<4P!Z~SR0$Hm%mp(NFdK8tdoHNbvVZC?J4V2%2{9hAxqYZ(?Gx)9#1J99JhN(Pjv;)Du4!p8Jf90zmVySxY=EpXZ#hkg5Im5WyZ-jEj9t5 z3w7ZnTtv8;+DWOTX(v3dVz@!Uf5Swf z-tj|a)V*S*hqZXQ{>@PmTtBq1fw|?Lr%4D5YuLjwsx|epY)b|@XDYSAr(5C!#j}^} zs81#PdtLlyRlIR>NUYzbblvF%G~PDqy0>^bz%)Hp{oCv{KhsG2XvO(_^-aj1S^Se$ za@#8tT1t_+-vB1NW3DJaYV9{jdaJw}EHE(iKfnzI{e;E2DSeon7rFOFoD9AD?~U}A zmb$R7H9UQF<<&y%Zp~H{g7>{XQnh)=^@^Zrfk$QC8b|@l}3<&{g7x`6>CtGOipA&pGY~3*Iraa zg>NwF3ziP@@HqLXJOMCtnXZEIqR@5NF6d3LMKVuOj|oQ8!E&C2Xf;FqY>~T#pxa%? z(3o}~>Q80lIyU&$ArK(bMxnvmC&QU zPH(z*3@IjqZqvv!zwNxdoU8e_WIw0PbhnJk2kX_Jg;%wYlelK;%e%g7pnNS-B5S>( z)%1+6VuVZ3UYTOn&MS0t-rlom0Rkq8zFIo%Rjg%q@S5WP=;38uDeIe;A50enaNoJ{ zEY76lGQ}JdeRb?dlv8kC1a!y-;G8xRDPCGy>Ch%+_n*B8CSCreHzavm0X!9) ze*=H)qIdyC&EjnkV^r2B2ydQ0STZjCW#Z84)=);=LxUstixSoI6_0?-LhRfToV*m_ zeRem0|A#4zT6q^GJN_=DD21lIR-W zbK@4a`_@aCsx^W$S#-Y;k2 z&I&I+sa<^eRx(p~=~Z1bKlhSF){>;B(_{W3811R8zOU_%B*hh6%X0!x>f@=J9rWu< zTyzs@V6Ff9>yH>19J?kiecZVLlGn{IGQ00vZx^eE8Zy6XtRXqh6_llunQ z!;Uq${rb%&cLVp*)~^_-otNAlK}9I!8lbNLgp~hz(0C}|Z=tj~*7UXCBXQwQEvBhx z+C``(2M7F2hbW7+THAlv;mJ2XlI8NC`Z>AE!n(TbN6jrSfh#8;hv)}cD_KOPEQ3$4 zeAi-fCZ#2hRm;k^uW0M76vOp>PU{66pz} zn;!I1YT{Ltc8FY0;R1yx$ZHBZ<$kFoe57qh7B(l;TidEa8;lmw?4;Z=SrcB%s&i_;zM$B zcGqSjQPad=Q1JOll_Cr|H`rZUQ$nbDmg6|z_tFIv(~R|_n}192grB5#D4@*HZR2TU z$G|tU)WEl^{WUk!U($i}cpR-<_!LjS0qGl#&x(ed4j z4G^9c5xGv>slKQ~)fPNmj@~eepV#biSul@&V)};4jx@(sJhD{)K4u3AQ{-OgCrw8p z7P*SwO4FEm0f=uIa%!?PP!vV>qD?hjRTE8CA<0En7e|f%P@=XcJJ`9Gs9&08r#1gq$nY>Y*SRT7_<}JTmKxX@LOGP0h=TI08_X8sB^+ zUl8Hv#`ARAZUNIklqu5%R1Ii-yCJ4N;RWpE2Q0KsYdfc9rMBPWp6}kRBDa}=V$&QGP_7fR{7IAcxSs?9WAoVGAgd zH4SQR?gZFao)8B1Lk#j|fdSG$utjd4oQ5qeszrfw9wPCkG^9~Mk-KM*V1FNpyN*LT z+5qwkljp8;k{!7Qb3ML1F6$ZAzv_$74KrM3K54NIO6XS+TutLN_>`2WYG z{E*QCWJWcXaLq6yrpzr$g+AJ^Hha&|U4Zhqc0Su!>{YFM^6o5Ky6s($?`gSJQjNo> zu;yC;A^h0!`hv}g*95trS~{@$ZBlZjzN=Dm!-V;wpp(hhU2|MwRqFQg)*|Z);q?u& z7hW`oqfJ!JC}v`x#!ZYkvJ&*Tw(DH+sZc$?%qJ<5GmfVW-f;{b9D%2A$*Khp^zG!> z$RD!|P=X{Uax2+7`U!kS&m3Uh6b5HC+-}#Zd_3tGZ>4xvMz%D`A$>U6Bl_YaP|l!* z7f7>EK}{Qj!zjUIUdhcEe`t|TGylv?Q&2evl$7C328jYwi`Fqkuy(+nKY|y-nlu_F zLkkspOQM9u8sW5}L~7r96`2c#$d?i3SLo)F!Q9R=gI02-6=~339zr_E$PX-U8B@^l zJRni+)5e9+x1Ec^8e6@z?qgtwqnP<61XKs;_zQq+Kz2i`~I6#B(U#A31bmG zQD?#A7oHu!(wdOw!_c!G#f(;G6U^xGQm;lWYwm$FNvV9PfLrlZw8ssl%ZaY6pO?AT z5iZH%1qM2sKW&rsR)FZ{&#|uGfX)b3Syl$lIqH3IWw(G!&H)eJ&h7K(TIIyD9*ZTH ztit(dDjT#6dq*Q)h=*TYBKJok7iO{4Ro`}QkF%`HfpU*r@`|T_Z9=LFnpUh6VtFfZ+>bOQMUPCNr=S<$)ey`&9Q{* zPb_}|gcOgWIQ-NAoNSQnR4}7N^9%=d`>=eXKrtr%)A&7A;i+{2P$JDGJE_==SIEht zb4LNmdubD34o+KMkH(|WF)4j0AL0)}%#7_{u;&06&4;i+!wit+5T`$3Rjf&%d?3M+ zh-0c*_sJBJ<8PpaidsjPCp7n-qzYvW>;Kq0NI)c z?5Uv(wWAcbD(rRp#uW6T4#rh=b)WyYxGS2)Q~y?*3T@1WzKt;w7vt38uv<1!1l5li z{H)E%3Yepc#Rtf9_X-o=Hrx=CGfCv__ql`wZh^x0?3wM2)tkB5OSZeo3H?6fB-X2& z98b;IrBdkQl@mEHXx9x?R$3PVu6Z-7lrtZgk*M{~N;hLZj=uPp#~dGWWMaw5gF3=H zRi5<*{kr;&LEEP%>6b2!mf5++@}NI#hu%#3@Fu}*A!#)?as`Zg)Cd4S-)ZcZus2s%sbR=9^|OxUXczp%SB_ciZ1^fgxwMqB~w_Bf-|9# zFZVV8>j=u=I)JUEHr1vZffkoKyJZeuR?rqmiz3PwYUZ8f8dzs(+~3CU z^KQ=Q^0`+GL|7Fv(jv$CFmtb2wy@nJ&p3=b8Rj@v`-M?+R&E;Nm&_BCw6` zy#QUPe%xuyE_9vtaq{pV6KO5Q^nZDMPs{aa+b2(_$BEYCRf72k!$p+_{UN{Rlp%qW zV3(XpS}Txu@$@9#dpH2`+f-?SS&f2K6^h4Fu||)G+I)=Es)Cv#{P+5CAHTN>@;Ub- zQ*qAo85eGsu%rma(ml?<18%;k$RwInAPt8mc;Kzjk#pzxaAcOS+1jVHeV`Siy5zM& zdb2oYtN9z^*%{#C1hzrny!%LyJE0+!eu3pMSeP2=Q+x7)>(ssbUz`9#CUVq&vmM9% z3c>{FpxOx3b5OiU%|#4mO`^DJHa{v4z$Gx3-u|2<=(6mH#y&*+MtEZ2ACfbj1z_p2e!^$^B;pJ}4Wk$WuVexs>or1Ct?VX4bE z&jj&rVnJ{>q6%*RHpcCY5}*xH>%fuD|5@G$gP_JBtvseEv+F(^e728!rThK6>r zt`kMPgjAh(QX%@-a6D{y(%?>D_}Y~dSG^{>G&rKj$R+v?f;$%a!cs=53kC!K5+P;;M8xpIM zpYzKfS%ePheJk`EVasMtP_e=p@T<8^QY(8!4*B()Unf;>6dm`i4AW@a`4Zr~Ov5|vYmX13Va z<~G%!V!4&tzG!2njaF~lG*dlKPrkj+yx(WO-=FUDcbwO8T|vPdpY>^WAUn`+YajrH zH83Yx;w((9NM?8&9G+-tZSO?Dnb=SXBpWNTBjCuewzW5N@UXSBF?RAL+dJAiQY@Xl z$xd!WinoiSgFVHC;Ns!nLbY@8a(1B@yQg@&kSVV2wr&CbE;i0?9$iS`@A`Ec3c`Jyv{*6>t;%Bqiyq*|_r^v0Ij{rQ z$NM?ya}Misj??IYppa9UzLrS=_I!UbDD?8CK$}pePY5$$rypj^23r=J!QgNU11z@E zNbvCCZGq%)4y`bd2;xoT(TTeQ9V#~%3ImNn(f6Z5ScE8L35!t0M2AF19i$ry*;cU; z!P`TfN`grVJaz@wLK=iC3inKj;ns(kl|@jRf-R164OKzFmRNRki1Ci-^c-I6 zgyh7;xF18!l;PO)#E9%fo+{ELBPFUU0xQ_GF>6!U;TY?KarS4UjCO2}yudTwof>*M z#&C$IdnMZFT8z%XM(vY{q#YUY#e%ruc!P|b+>>c$Qwchy*-5wK^>=Se%H5tR$_d?{ zv#~BaVlEMQkPKZ&1~qL9EzQrU$WK0(ZMTvFyO?9WlnSXR+)}+WwV*`!HVyPAA5yn_ z%a?S}M7~MQ-fdqqK$@MTZ<(OAEYQ(?sg3)yoA+m|JtwOWGi7uCG2>Q*iXazO(f^IuGnR-?&qLxMiW<=bU11uVVMZrihD2 zg+CuH`=zDiSo;Z8N83nS*|4ho#tGrcvp-FCmH%-fcKWR3?itC0?wUJ2b@$I#&0mnr z^*255t5>V9e(Eb-yxRQk=c<=iaP`}LMjW6FO`9$mYo z{5F03_QS_(vu)q*wXe;0tL5UCASc-O7r5{2fPinHH62TCPwP-Q zVC0Y@>8W5=Ba9Wmd#Z|2V?+rl!!r1sdg%I64RHJU2zt%Nw?jWc?PkWs&}+aSNGH>R3owYH@Pc?H;Gzz_)4Jd)ha`2yF9bk^qiUb z9*yed*V4w&!QD@P_;RK-*WpPuuHj1U?px~+Tgd@pZW57QGhAh45z!AYt0;YCaq8Va zU09x6eB(;x;uB8j?8uegTB@EQFuI~#8ZQFkD^S?Jx#vcUla$&1S6GjphN*ycG}vH& zFNIZQe&Fuh#tJH3TMK;#F+p9qPtg%+8yr)LSv9X8r&TTRA+VZihqw!V7|#iaj($zq ze4{4CM)Ks6o1h1_)Z0xrZ^n!E_X&6mI9^G3Y+=4jR=ul_s=O`Qsw@z3@M=)G*|oAm z(Xl3HN0G6ZU9=$@O4Dv?YM!W`Lao!6>uu(#OHo_J$FF-YMtexA&! zHYX3Z*v~Fb9ktpaCVk0IpHLw4lQ3os4gjpu`akO!wT0@LD9I8~!&5I)vWE|Fek}Y% zxtnV-&qVmh1GKbqH8j4p=^A=bGP`A=hzP&@;oN5?bLkjnKjd+nx;4yd(@7Iwe?8lu zVt+~O{Hk!@I>kuYTa7~3QcvIfdGE-Qlgg`G?U{48L8kK&sdcf$>!omkxC1v3DOl`& zvhU{b2=ikD)f)5K{J0gii#5S--Sd03?wf<<*K?z^vyR-?KVK;z>t24}e`_x0$8GX6 z+Ky{vs+vEEX56)^XB^**2b@a$3&xr%SpV6N7tRc0H-`4J9>n+@c@!bMt!wiX{|8HS)qZ!f0yuI>{TIfRd(R0XPRnb ziUOd|K-L!^-E@m2@7F>1R4 zwh@o%PBGMy{kmR0yzVqNziDILOw|)ES>40lgZ$$V1VKg0W3=%!48yRH&$~?{^tXj= zLJaiW3>aeZc{<1i>uFCd>e5KqhovkYAXu;t@`XD=8Tcid8o?ltkPt09s_n8oc4Kx% z-akoctfy_H+Ax&s1b)w#B268wG_JS zHhMg#Q(!w?WkboY;K!U8RuZC%fL@ylSOI~1?h=YoMLJT86O9C<=!pbLEAKV#aH@V5{C z)sQwA_IUF0dcpH%0!b`XdC^#~-tr=v;jyAFcaowwIVGxdi-p zcJtMop%Oq*x}gVPiGUx*EzW_$Bw8C@|j%>u?g+MGqW zr8aSVGMLuZd`(MIX-4BMSwFjP>2P2!dSo971%&{8YSFPDU=Fbm7)~H#mm9K4V3JX^ z50ZuRfs{L#=V2MfeRHZ1eZn`bC8%_ zwIqUA4nW{_XuK~c#Y}YcVnD8qW~v`a9y6$T(biNsE@d8fq-T|CM{aJda%{S;zFZem zQDMTwQ&2;hQ(VeLYk8$e7o$hlxh^-p%xJ<&^871~{|dM06pojXiPK1{UFX-966 z&^~o-Khycf=)CrR_r$@=m1#Pd2lDQp?@y(Q5vOydy~|xaY2GRK$9LW^JkT7~zN5J~ zOWi<7_;`PI687YNw@ad>sy|a=0!x28OaQupik(&1B|e8X83*$g*|U)ag8P zm}r`wT>i^B^YfuwYB$>X&2$X}KWuxwyxoL0cf<$+!_W3 zf67HZUC$~Tae*WOvUGV4m8v}}Jk4D3`^<^usA`3qtM zECX3-q-yz8MV2kO)s+F=`l z!JqYtf~TrQ>SBvsND=Hq|7BFppP=7o0|jIJ2?f8({)JKt2K}32{O%ed3wO#t<^mdStPC zOXtm0Ncwguc#@eH!fU{Nc6h-i6b5RTd&!14Md5v6zTwvo^{n5NgzD`*8DF#IBUPBB8p=m4IlQ5JX;bzvmb z^IHs4pPWxHZjRH%K)?@p50;ezMfQus|2#T?DOX}4sv#+svRRYBcz#`KrKgbjeE5~y zfLnKrHGW~12Mms&`9I)Du01c(e`odc(i?4hhEBB)^JJ%!LTx~xT zaf=`#5zZzDxl{&jMysn?!1mAWPaQDo+hx$woRoDt3 z<1loGJ7j<FG#XpLU4*`}LrM!Rvrr?H-hSdx;P8AS zB>UAB>oyuHtUVb2;N9&Sr~_#h4CJU~^pJ>$A7H3dRI3nzLA#HlOjZi9de$uBP&HoX zV}&`6%IEDz0u>Bhh&`m-(Nw;~gJ`cur{bn6Nirkg^aol4Au79;!b{3mu#+A`B#;kd zcrxxM%uc>)j2xN)1AvGTjrRxqZt^Rgg2zU4=zOO*`Q`?wg0@17Nb5F&n z6l1(uqnusfG~o2|5U%gi#~Llx_CZ&9i(}2W_#Y6@nDF2lH}7H@(@+6ZtT7TWNA3_S zvOfX~mPy2lo1IL@`B$J=@?A9Egf2n4fU81KM>Y;us}|Xa54~wKni`RRxCtXmZ0rVo zyz3*re<36?LepSEZSBby%CE$Io8hZ};~2qf;D}r;4*zkFU&*^iBgOg_5+>&RW*tL3 zDk^$td2dDb3OC>$=R47b5r_7&{bT2AVw^-RzUSR1X8%>6L`Iiq&4)n4H!By+PIi~~ zAX)GvGg<=F>NX7`H!N$JAFO1J-E1O{Jh@OAP+swAWo^?mXiRZNTa{(64PF#T5wtpD z4fMM_kAG*YQ3@p_YSd7(1i6Ux$D_tzxIP1K=oFjQtR*%{e~1FVKD*P<6jBKi8>tWp z&2(ss*0lSizm0qs>KKbox%!bfwFTQz@f6C6z{`9D`Ln5I@a@*q9${7(vHt;fHc~4i z@zk;+rFeCj`S;VW$}Z~vHgKqgTLt(1ufvtb_RdZRQHz|0HEhjmA08>F32ooFepnYW zamd<8g+J5~sjWn~CKXALuz=Da+QQvn6wI^q&(8_!^{&=tdtRCB(|n<7t^=RKMEPmW zj!aW4wzGym9Sh0H!_Pcd4Y8?>gt43>CeOIyy{luIo64r#6Gib~+X#h`2HZyMn*w67 z%dZaMtDQ1S?C^94P%WQtIy5#W5aIG34Q7xNw2`|`mwhu$l6S|~uUs{X$^7FN-<_xD zN-J+1Fi*wh9^#@PvhLY9+C!VR3aQM<){FY=%K%ggSQlr-9|X_5M4IJE<{@tWBk!G> zLW}Z=kMG??oZE5g#gn$lf>4H8aXx7?gMB8#(pwkLkw6)6#lyvQY<)BOWY*=%d?O&W zivdnW`@XL-U-{Ci11Af6_-wKhY*-4x6&RX@9%Owte@ps&VC~rN;Sf?BaXR2Ewj&tY zw8^^Mr`Jp%^Z{f3dmCBpX#D$Z{(r{(WhUQy9_#MwG~~+CskQX`;Vib%`nrq|p}?#Z z2s8>py6U5{hZQFFV!v=&z0D62`Ii@mCB}3`RdMRk-H+U|d6i^Z*(opsZi*y034zDx zwi^c5Vt3)2Zov+2PYL0x-x)ao$hf@u3yerBXhInyHi;EO0I;WfefE$~LhOoP#R?OO z4A<2hgEP6!en9M%wB6>jTJh)5J?S>6Lg@Kr1tn_qY2{8C1MXP*>R5(2Y(^j6-|2mi zfBB6CPcNvSU%*c0Q};BJl)cN`aN(|bxWU7%krf9kP%Ez6`2X6dFS1|p8A5kSkJVK^ zK(p))(&L&%s5kX%Cu=R?wS&Xs!r}u{Z0ApwuCNVxUrif)<`J#4VuaTUp7s4N{r(pw C1bJBi literal 0 HcmV?d00001 diff --git a/res/js/bt_config.js b/res/js/bt_config.js index 391d4f7..2189960 100644 --- a/res/js/bt_config.js +++ b/res/js/bt_config.js @@ -9,23 +9,55 @@ // Configuration namespace // --------------------------------------------------------------------------------------------------------------------- -// Holds root path of URL where application is deployed -bt.config.urls = { - clientUrl : '/battle/static/btjs2/', - servicesUrl : '/battle/' -} + // Configure root path of URL where application is deployed + // ----------------------------------------------------------------------------------------------------------------- + bt.config.urls = { + // Set client-side URL path + clientUrl : '/battle/static/btjs2/', + // Set server-side URL path + servicesUrl : '/battle/' + } -// Set poling -bt.config.poling.minimalIntervalBetweenPolls = 2000; + // Set poling + // ----------------------------------------------------------------------------------------------------------------- -// Set debugging options -bt.debugging.events.publishToConsole = false; -bt.debugging.model.verifyModelConstructors = true; + // Set value for minimal time interval between polling same service in [ms] + bt.config.poling.minimalIntervalBetweenPolls = 2000; + + // Set debugging options + // ----------------------------------------------------------------------------------------------------------------- + + // Set if events are pushed to console + bt.debugging.events.publishToConsole = true; + // Set if model constructors will test received properties + bt.debugging.model.verifyModelConstructors = true; // BattleField configuration namespace // --------------------------------------------------------------------------------------------------------------------- -bt.config.game.battle.styles.selected = 'tile_selected'; -bt.config.game.battle.styles.move_near = 'tile_move_near'; -bt.config.game.battle.styles.move_far = 'tile_move_far'; -bt.config.game.battle.styles.attack = 'tile_attack'; + // Set CSS Styles + // ----------------------------------------------------------------------------------------------------------------- + + // Selected tile CSS class name + bt.config.game.battle.styles.selected = 'tile_selected'; + // Selected tile CSS class name + bt.config.game.battle.styles.move = 'tile_move'; + // Selected tile CSS class name + bt.config.game.battle.styles.range = 'tile_range'; + // Selected tile CSS class name + bt.config.game.battle.styles.attack = 'tile_attack'; + + // Player's unit CSS class name + bt.config.game.battle.styles.player = 'unit_player'; + // Enemy unit CSS class name + bt.config.game.battle.styles.enemy = 'unit_enemy'; + + // Configure actions + // ----------------------------------------------------------------------------------------------------------------- + + // Set unit's move radius + bt.config.game.battle.actions.moveRadius = 2; + // Set if actions and movement can pass through other units + bt.config.game.battle.actions.jumpUnits = true; + // Set if player can attack his own units + bt.config.game.battle.actions.friendlyFire = true; \ No newline at end of file diff --git a/res/js/bt_game_battle.js b/res/js/bt_game_battle.js index 8bcfc1b..53e9050 100644 --- a/res/js/bt_game_battle.js +++ b/res/js/bt_game_battle.js @@ -58,17 +58,23 @@ bt.services.battleService = app.factory('BattleService', function ($jsonRpc, $in // Map service monitoring methods battleService.monitoring = { + // Starts continuous monitoring of service monitorTimeLeft : function() { $interval.set('battleService.timers.update', function() { bt.game.battle.timers.update(battleService); }, 1000); $interval.start('battleService.timers.update'); + $interval.set('battleService.states.lastState', function() { bt.game.battle.battleField.update(battleService); }, 4000); + $interval.start('battleService.states.lastState'); }, + // Stops continuous monitoring of service stopMonitoring : function() { $interval.clear('battleService.timers.update'); + $interval.clear('battleService.states.lastState'); } }; // Map service initialization methods battleService.initialization = { + // Gets initial state from service initialize : function() { battleService.calls.initialState( // Parameters @@ -81,6 +87,8 @@ bt.services.battleService = app.factory('BattleService', function ($jsonRpc, $in bt.services.battleService.BattleFieldUpdated.dispatch({ message: 'Response from "BattleService.init_state().', data : data }); // Initialize view-model's battleField from response bt.game.battle.model.battleField = new bt.model.definitions.battle.battleField(data); + // Get last state + bt.game.battle.battleField.update(battleService); }, // On error callback function(data) { @@ -89,11 +97,86 @@ bt.services.battleService = app.factory('BattleService', function ($jsonRpc, $in } ); }, + // Destroys model gotten from service destroy : function() { delete bt.game.battle.model.battleField; } }; + // Map service unit actions methods + battleService.actions = { + // Passes turn to other player + pass : function() { + battleService.calls.processAction( + // Parameters + [[ 0, 'pass', [] ]], + // On successfull load callback + function(data) { + // Fire events + bt.services.battleService.Called.dispatch({ message: 'Response from "BattleService.process_action().', data : data }); + bt.services.battleService.BattleFieldAction_Pass.dispatch({ message: 'Response from "BattleService.process_action().', data : data }); + // Refresh timers + bt.game.battle.timers.query(battleService); + }, + // On error callback + function(data) { + // Fire events + bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.process_action()"!', data : data }); + } + ) + }, + // Moves unit ( unit object | unit id) to new location ( {x, y} ) + move : function(unit, targetLocation) { + var unitId = (angular.isNumber(unit) ? unit : unit.id); + battleService.calls.processAction( + // Parameters + [[ unitId, 'move', [targetLocation.x, targetLocation.y] ]], + // On successfull load callback + function(data) { + // Fire events + bt.services.battleService.Called.dispatch({ message: 'Response from "BattleService.process_action().', data : data }); + bt.services.battleService.BattleFieldAction_Move.dispatch({ message: 'Response from "BattleService.process_action().', data : data }); + // Refresh timers + bt.game.battle.timers.query(battleService); + // Process move response + bt.game.battle.battleField.actions._processMove(data); + }, + // On error callback + function(data) { + // Fire events + bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.process_action()"!', data : data }); + // TODO: Remove alert! + alert(data); + } + ) + }, + // Attacks unit ( unit object | unit id) at location ( {x, y} ) + attack : function(unit, targetLocation) { + var unitId = (angular.isNumber(unit) ? unit : unit.id); + battleService.calls.processAction( + // Parameters + [[ unitId, 'attack', [targetLocation.x, targetLocation.y] ]], + // On successfull load callback + function(data) { + // Fire events + bt.services.battleService.Called.dispatch({ message: 'Response from "BattleService.process_action().', data : data }); + bt.services.battleService.BattleFieldAction_Attack.dispatch({ message: 'Response from "BattleService.process_action().', data : data }); + // Refresh timers + bt.game.battle.timers.query(battleService); + // Process move response + bt.game.battle.battleField.actions._processAttack(data); + }, + // On error callback + function(data) { + // Fire events + bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.process_action()"!', data : data }); + // TODO: Remove alert! + alert(data); + } + ) + } + } + // Return service mapping return battleService; }); @@ -111,10 +194,19 @@ bt.events.define(bt.services.battleService, 'Error'); // "Service call successfull" event bt.events.define(bt.services.battleService, 'Updated'); +// "Battle field new turn" event +bt.events.define(bt.services.battleService, 'BattleField_NewTurn'); + // "Battle field initialized" event bt.events.define(bt.services.battleService, 'BattleFieldInitialized'); // "Battle field updated" event bt.events.define(bt.services.battleService, 'BattleFieldUpdated'); +// "Battle field pass action" event +bt.events.define(bt.services.battleService, 'BattleFieldAction_Pass'); +// "Battle field move action" event +bt.events.define(bt.services.battleService, 'BattleFieldAction_Move'); +// "Battle field attack action" event +bt.events.define(bt.services.battleService, 'BattleFieldAction_Attack'); // Initialize 'battle view' game functionality // --------------------------------------------------------------------------------------------------------------------- @@ -133,6 +225,9 @@ bt.game.battle = { $scope.services = { battle : BattleService }; + // Set battle field actions + $scope.processAction = function(tile) { bt.game.battle.model.battleField.grid.processTileClick (BattleService, tile); }; + $scope.passTurn = function() { BattleService.actions.pass(); }; }).controller }, @@ -175,10 +270,12 @@ bt.game.battle = { _playTime : null, // Holds remaining battle time + battleTimeFormated : null, battleTime : null, // Holds remaining play (turn) time + playTimeFormated : null, playTime : null, - + // Parses server timer format to milliseconds _parseServerTime : function(time) { var parsed = time.split(':'); @@ -195,13 +292,18 @@ bt.game.battle = { // Recalculate values var diff = (new Date()) - bt.game.battle.timers._lastQueryTime; var battleTime = (bt.game.battle.timers._battleTime - diff) / 1000; - bt.game.battle.timers.battleTime = Math.floor(battleTime / 3600) + ' : ' + Math.floor((battleTime % 3600) / 60) + ' : ' + Math.floor(battleTime % 60); + if (battleTime < 0) battleTime = 0; + bt.game.battle.timers.battleTime = battleTime; + bt.game.battle.timers.battleTimeFormated = Math.floor(battleTime / 3600) + ' : ' + Math.floor((battleTime % 3600) / 60) + ' : ' + Math.floor(battleTime % 60); var playTime = (bt.game.battle.timers._playTime - diff) / 1000; - bt.game.battle.timers.playTime = Math.floor(playTime / 3600) + ' : ' + Math.floor((playTime % 3600) / 60) + ' : ' + Math.floor(playTime % 60); - // Fire event - bt.services.battleService.Updated.dispatch({ - message: 'Updated "BattleService" timers' - }); + if (playTime < 0) playTime = 0; + bt.game.battle.timers.playTime = playTime; + bt.game.battle.timers.playTimeFormated = Math.floor(playTime / 3600) + ' : ' + Math.floor((playTime % 3600) / 60) + ' : ' + Math.floor(playTime % 60); + // Check values + if ((battleTime <= 0) || (playTime <= 0)) { + // Query values + bt.game.battle.timers.query(battleService); + } } }, @@ -210,31 +312,115 @@ bt.game.battle = { // Check time since last query if ((new Date() - bt.game.battle.timers._lastQueryTime) > bt.config.poling.minimalIntervalBetweenPolls) { // Query timers from service - battleService.calls.timeLeft( // Parameters - [], - // Success callback - function(data) { - // Set data - if ((data) && (data.battle) && (data.ply)) { - bt.game.battle.timers._battleTime = bt.game.battle.timers._parseServerTime(data.battle); - bt.game.battle.timers._playTime = bt.game.battle.timers._parseServerTime(data.ply); - bt.game.battle.timers._lastQueryTime = new Date(); - // Fire event - bt.services.battleService.Called.dispatch({ message: 'Response from "BattleService.timeLeft()".', data : data }); - // Update timers - bt.game.battle.timers.update(); - } else { + battleService.calls.timeLeft( // Parameters + [], + // Success callback + function(data) { + // Set data + if ((data) && (data.battle) && (data.ply)) { + bt.game.battle.timers._battleTime = bt.game.battle.timers._parseServerTime(data.battle); + if ((bt.game.battle.timers._parseServerTime(data.ply) > bt.game.battle.timers._playTime) && (bt.game.battle.timers._playTime !== null)) { + bt.services.battleService.BattleField_NewTurn.dispatch({ message: 'New turn!', data : data }); + } + bt.game.battle.timers._playTime = bt.game.battle.timers._parseServerTime(data.ply); + bt.game.battle.timers._lastQueryTime = new Date(); + // Fire events + bt.services.battleService.Called.dispatch({ message: 'Response from "BattleService.timeLeft()".', data : data }); + bt.services.battleService.Updated.dispatch({ message: 'Updated "BattleService" timers' }); + // Update timers + bt.game.battle.timers.update(); + } else { + // Fire event + bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.timeLeft()"!', data : data }); + } + }, + // Fail callback + function(data) { + // Fire event + bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.timeLeft()"!', data : data }); + } + ); + } + } + + }, + + // Battle field namespace + battleField : { + + // Actions namespace + actions : { + // Performs pass action + pass : function(battleService) { + battleService.actions.pass(); + }, + + // Performs move action + move : function(battleService, unit, tile) { + battleService.actions.move(unit, tile.location); + }, + // Processes successfull move action's response + _processMove : function(data) { + var units = data.response.result; + for (var i in units) { + var unit = bt.game.battle.model.battleField.units.unitsById[units[i][0]]; + var location = units[i][1]; + if (unit) bt.game.battle.model.battleField.grid.moveContent(unit, { x : location[0], y : location[1] }); + } + }, + + // Performs attack action + attack : function(battleService, unit, tile) { + battleService.actions.attack(unit, tile.location); + }, + // Processes successfull attack action's response + _processAttack : function(data) { + var units = data.response.result; + for (var i in units) { + var unit = bt.game.battle.model.battleField.units.unitsById[units[i][0]]; + var hp = unit.hp - units[i][1]; + if (unit) unit.updateHp(hp); + } + } + }, + + // Updates battle field state + update : function(battleService) { + // Call 'BattleField.last_state()' + battleService.calls.getLastState( // Parameters + [], + // Success callback + function(data) { + if ((data) && (bt.game.battle.model.battleField)) { + // Process update response + bt.game.battle.battleField._processUpdate(data); + // Fire events + bt.services.battleService.Called.dispatch({ message: 'Response from "BattleService.last_state()".', data : data }); + bt.services.battleService.Updated.dispatch({ message: 'Updated "BattleService" state', data : data }); + } + }, + // Fail callback + function(data) { // Fire event - bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.timeLeft()"!', data : data }); + bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.last_state()"!', data : data }); } - }, - // Fail callback - function(data) { - // Fire event - bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.timeLeft()"!', data : data }); - } - ); + ); + }, + _processUpdate : function(data) { + // Update HP + if (data.HPs) for (var id in data.HPs) { + var unit = bt.game.battle.model.battleField.units.unitsById[id]; + if (unit) unit.updateHp(data.HPs[id]); + } + // Update location + if (data.locs) for (var id in data.locs) { + var unit = bt.game.battle.model.battleField.units.unitsById[id]; + if (unit) bt.game.battle.model.battleField.grid.moveContent(unit, { x : data.locs[id][0], y : data.locs[id][1] }); } + // Update game status + if (data.game_over) bt.game.battle.model.battleField.gameOver = data.game_over; + if (data.whose_action) bt.game.battle.model.battleField.activePlayer = data.whose_action; + } } @@ -248,4 +434,9 @@ bt.game.battle.model = { // Holds reference to battle view's battleField model battleField : null -} \ No newline at end of file +} + +// TODO: Debugging: Hooked events +// --------------------------------------------------------------------------------------------------------------------- +bt.services.battleService.BattleField_NewTurn.subscribe( function(event) { alert("New turn!"); } ); +bt.services.battleService.BattleFieldAction_Attack.subscribe( function(event) { for (var i in event.data.response.result) alert('Attack does ' + event.data.response.result[i][1] + ' damage!'); } ); diff --git a/res/js/bt_init.js b/res/js/bt_init.js index 2651d6a..0c0083f 100644 --- a/res/js/bt_init.js +++ b/res/js/bt_init.js @@ -57,6 +57,16 @@ var bt = { // Configuration namespace config : { + + // Web URLs namespace + urls : { + + // Holds client-side URL path + clientUrl : '/', + // Holds server-side URL path + servicesUrl : '/' + + }, // Views configuration namespace views : { @@ -110,12 +120,31 @@ var bt = { // Battle view namespace battle : { - // Styling namespace + // CSS Styles configuration namespace styles : { + // Selected tile CSS class name selected : 'tile_selected', - move_near : 'tile_move_near', - move_far : 'tile_move_far', - attack : 'tile_attack' + // Selected tile CSS class name + move : 'tile_move', + // Selected tile CSS class name + range : 'tile_range', + // Selected tile CSS class name + attack : 'tile_attack', + + // Player's unit CSS class name + player : 'player', + // Enemy unit CSS class name + enemy : 'enemy' + }, + + // Actions configuration namespace + actions : { + // Holds unit's move radius + moveRadius : 1, + // Toggles if actions and movement can pass through other units + jumpUnits : true, + // Toggles if player can attack his own units + friendlyFire : true } } diff --git a/res/js/libs/angular_interval.js b/res/js/libs/angular_interval.js index 617c08f..e87ff24 100644 --- a/res/js/libs/angular_interval.js +++ b/res/js/libs/angular_interval.js @@ -23,7 +23,10 @@ var interval = angular.module('angular-interval', []).factory("$interval", funct // Starts an interval action this.start = function(name, starting) { if (intervalBase._intervals[name]) { - if (!starting) intervalBase._intervals[name].run = true; + if (!starting) { + intervalBase._intervals[name].run = true; + intervalBase._intervals[name].fn(); + } if (intervalBase._intervals[name].run) { $timeout(intervalBase._intervals[name].fn, intervalBase._intervals[name].interval) .then( function() { intervalBase.start(name, true); } ); diff --git a/res/js/model/bt_model.js b/res/js/model/bt_model.js index 080f558..1e0b720 100644 --- a/res/js/model/bt_model.js +++ b/res/js/model/bt_model.js @@ -17,6 +17,11 @@ bt.model.common = { F : 'Fire', I : 'Ice', W : 'Wind' + }, + // Gets element definition from name + getDefinition : function(element) { + for (var i in bt.model.common.elements.definitions) if (element == bt.model.common.elements.definitions[i]) return bt.model.common.elements.definitions[i]; + return null; } }, @@ -24,12 +29,54 @@ bt.model.common = { // Basic weapons namespace weapons : { - // Holds basic weapons definitions / types + // Holds basic weapons definitions definitions : { - sword : 'sword', - bow : 'bow', - wand : 'wand', - glove : 'glove' + bow : 'bow', + glove : 'glove', + wand : 'wand', + sword : 'sword', + + magma : 'magma', + firestorm : 'firestorm', + forestfire : 'forestfire', + pyrocumulus : 'pyrocumulus', + icestorm : 'icestorm', + blizzard : 'blizzard', + avalanche : 'avalanche', + permafrost : 'permafrost' + }, + // Gets weapon definition from name + getDefinition : function(weapon) { + for (var i in bt.model.common.weapons.definitions) if (weapon == bt.model.common.weapons.definitions[i]) return bt.model.common.weapons.definitions[i]; + return null; + }, + + // Holds basic weapons by types + types : { + ranged : [ 'bow', 'magma', 'firestorm', 'forestfire', 'pyrocumulus' ], + dot : [ 'glove', 'firestorm', 'icestorm', 'blizzard', 'pyrocumulus' ], + aoe : [ 'wand', 'avalanche', 'icestorm', 'blizzard', 'permafrost' ], + full : [ 'sword', 'magma', 'avalanche', 'forestfire', 'permafrost' ] + }, + // Gets weapon type from name + getType : function(weapon) { + for (var i in bt.model.common.weapons.types) { + for (var j in bt.model.common.weapons.types[i]) if (weapon == bt.model.common.weapons.types[i][j]) return i; + } + return null; + }, + + ranges : { + ranged : { min : 4, max : 8 }, + dot : { min : 1, max : 1 }, + aoe : { min : 1, max : Number.MAX_VALUE }, + full : { min : 1, max : 1 } + }, + // Gets weapon range from name + getRange : function(weapon) { + var type = bt.model.common.weapons.getType(weapon); + if (bt.model.common.weapons.ranges[type]) return bt.model.common.weapons.ranges[type]; + return null; } }, @@ -40,7 +87,13 @@ bt.model.common = { // Holds basic unit definitions / types definitions : { scient : 'scient', - nescient : 'nescient' + nescient : 'nescient', + part : 'part' + }, + // Gets unit definition from name + getDefinition : function(unit) { + for (var i in bt.model.common.units.definitions) if (unit == bt.model.common.units.definitions[i]) return bt.model.common.units.definitions[i]; + return null; } } diff --git a/res/js/model/bt_model_battle.js b/res/js/model/bt_model_battle.js index e119528..f37ecc6 100644 --- a/res/js/model/bt_model_battle.js +++ b/res/js/model/bt_model_battle.js @@ -59,6 +59,17 @@ bt.model.definitions.battle = { if (bt.debugging.model.verifyModelConstructors) { if ((!angular.isDefined(this.location)) || (!angular.isDefined(this.location.x)) || (!angular.isDefined(this.location.y))) console.error(obj, 'Localized object definition incomplete: Missing location!'); } + + // Update functionality + this.updateLocation = function(location) { + if ((angular.isArray(location)) && (location.length == 2)) { + this.location.x = location[0]; + this.location.y = location[1]; + } else if (angular.isObject(location)) { + this.location.x = location.x; + this.location.y = location.y; + } + } }, // Weapon definition @@ -111,6 +122,11 @@ bt.model.definitions.battle = { this.hp = (4 * (this.physical.defense + this.magic.defense + this.value)); } this.updateStats(); + + // Update functionality + this.updateHp = function(hp) { + this.hp = hp; + } }, // Grid definition @@ -125,8 +141,7 @@ bt.model.definitions.battle = { this.addUnit = function(unit) { this.units.push(unit); if (unit.id) { - if (!angular.isDefined(this.unitsById[unit.id])) this.unitsById[unit.id] = [ ]; - this.unitsById[unit.id].push(unit); + this.unitsById[unit.id] = unit; } if (unit.owner) { if (!angular.isDefined(this.unitsByOwner[unit.owner])) this.unitsByOwner[unit.owner] = [ ]; @@ -153,6 +168,7 @@ bt.model.definitions.battle = { } // Initialize children this._type = bt.model.common.units.definitions.scient; + this.style = (this.owner == bt.game.authentication.username ? bt.config.game.battle.styles.player : bt.config.game.battle.styles.enemy); this.weapon = new bt.model.definitions.battle.weapon(this.weapon); if (angular.isDefined(this.weaponBonus)) { this.weaponBonus = new bt.model.definitions.battle.stone(this.weaponBonus); @@ -169,23 +185,36 @@ bt.model.definitions.battle = { if (obj) bt.model.extend(this, [new bt.model.definitions.battle.localized(obj), new bt.model.definitions.battle.cStone(obj)]); // Initialize children this.contents = [ ]; + this.units = { } + for (var type in bt.model.common.units.definitions) { + this.units[bt.model.common.units.definitions[type]] = [ ]; + } // Adds content to tile this.addContent = function(content) { this.contents.push(content); + var type = bt.model.common.units.getDefinition(content._type); + if (type) this.units[type].push(content); }; // Removes content from tile - this.removedContent = function(content) { - for (var i in this.contents) if (this.contents[i] == content) this.contents[i] = null; + this.removeContent = function(content) { + for (var i in this.contents) if (this.contents[i] == content) this.contents.splice(i, 1); + var type = bt.model.common.units.getDefinition(content._type); + if (type) for (var i in this.units[type]) if (this.units[type][i] == content) this.units[type].splice(i, 1); }; // Clears all title's content this.clearContent = function(content) { this.contents = [ ]; + var type = bt.model.common.units.getDefinition(content._type); + if (type) this.units[type] = [ ]; }; this.isOwnedByPlayer = function() { - return (this.contents.length == 0 ? false : (this.contents[0].owner == bt.game.authentication.username)); - } + for (var i in this.contents) { + if (this.contents[i].owner == bt.game.authentication.username) return true; + } + return false; + } }, // Grid definition @@ -218,7 +247,7 @@ bt.model.definitions.battle = { this.tilesByY[tile.location.y][tile.location.x] = tile; } - // Tiles selection + // Tiles manipulation // --------------------------------------------------------- // Gets all neighbouring tiles with distances from source less than radius and with no units in the way @@ -244,13 +273,30 @@ bt.model.definitions.battle = { if ((base.tilesByX[neighbouringTile.x]) && (base.tilesByX[neighbouringTile.x][neighbouringTile.y]) && ((!result[neighbouringTileId]) || (result[neighbouringTileId].radius > (sourceRadius + 1)))) { // Add tile to result var resultTile = base.tilesByX[neighbouringTile.x][neighbouringTile.y]; - result[neighbouringTileId] = { radius : sourceRadius + 1, tile : resultTile }; + resultTile.distance = sourceRadius + 1; + result[neighbouringTileId] = { radius : resultTile.distance, tile : resultTile }; // Process further neighbours - if ((radius > 1) && (resultTile.contents.length == 0)) base.getNeighourTiles(resultTile, (radius - 1), result); + if ((radius > 1) && ((bt.config.game.battle.actions.jumpUnits) || (resultTile.contents.length == 0))) base.getNeighourTiles(resultTile, (radius - 1), result); } } // Return result return result; + }, + + // Moves content to new tile + this.moveContent = function(content, targetLocation) { + // Get content location + var sourceLocation = content.location; + // Get source and target tiles + var sourceTile = this.tilesByX[sourceLocation.x][sourceLocation.y]; + var targetTile = this.tilesByX[targetLocation.x][targetLocation.y]; + // Move content + if (sourceTile != targetTile) { + sourceTile.removeContent(content); + targetTile.addContent(content); + if (this.selectedTile == sourceTile) this._selectTile(targetTile); + content.updateLocation(targetLocation); + } } // UI interpretation @@ -258,14 +304,19 @@ bt.model.definitions.battle = { // Holds reference to selected tile this.selectedTile = null; - this.processTileClick = function(tile) { + this.processTileClick = function(battleService, tile) { // Check if tile is selectable - if (tile.isOwnedByPlayer()) { - // Select tile - this._selectTile(tile); - } else if (tile.avaliableAction) { + if (tile.avaliableAction) { // Execute tile action - this._executeActionOnTile(tile); + this._executeActionOnTile(battleService, tile); + } else if (tile.isOwnedByPlayer()) { + if (tile != this.selectedTile) { + // Select tile + this._selectTile(tile); + } else { + // Deselect tile + this._selectTile(null); + } } else { // Deselect tile this._selectTile(null); @@ -273,37 +324,57 @@ bt.model.definitions.battle = { } // Sets tile as selected this._selectTile = function(tile) { - // Set selected tile - this.selectedTile = tile; // Clear tiles' styles and actions for (var i in this.tiles) { this.tiles[i].style = null; this.tiles[i].avaliableAction = null; + this.tiles[i].distance = null; } + // Set selected tile + this.selectedTile = tile; if (tile) { - // Set tiles' styles and actions - var nearTiles = base.getNeighourTiles(this.selectedTile, 4); - for (var i in nearTiles) { - if ((nearTiles[i].tile.contents.length > 0) && (!nearTiles[i].tile.isOwnedByPlayer())) { - nearTiles[i].tile.style = bt.config.game.battle.styles.attack; - nearTiles[i].tile.avaliableAction = 'attack'; - } else if (nearTiles[i].tile.contents.length == 0) { - if (nearTiles[i].radius <= 1) { - nearTiles[i].tile.style = bt.config.game.battle.styles.move_near; - nearTiles[i].tile.avaliableAction = 'move'; - } else if (nearTiles[i] != this.selectedTile) { - nearTiles[i].tile.style = bt.config.game.battle.styles.move_far; - nearTiles[i].tile.avaliableAction = 'move'; + // Calculate weapon attack radius + var units = this.selectedTile.units[bt.model.common.units.definitions.scient]; + if (units.length > 0) { + var unit = units[0]; + var weapon = unit.weapon; + if (weapon) { + var attackRadius = bt.model.common.weapons.getRange(weapon._type); + if (attackRadius) { + var minAttackRadius = attackRadius.min, maxAttackRadius = attackRadius.max; + + // Set tiles' styles and actions + var nearTiles = base.getNeighourTiles(this.selectedTile, (maxAttackRadius > bt.config.game.battle.actions.moveRadius ? maxAttackRadius : bt.config.game.battle.actions.moveRadius)); + for (var i in nearTiles) { + if ((nearTiles[i].tile != this.selectedTile) + &&((nearTiles[i].tile.units[bt.model.common.units.definitions.scient].length > 0) + && ((bt.config.game.battle.actions.friendlyFire) || (!nearTiles[i].tile.isOwnedByPlayer()))) + && ((nearTiles[i].radius >= minAttackRadius) && (nearTiles[i].radius <= maxAttackRadius))) { + nearTiles[i].tile.style = bt.config.game.battle.styles.attack; + nearTiles[i].tile.avaliableAction = 'attack'; + } else if (nearTiles[i].tile.units[bt.model.common.units.definitions.scient].length == 0) { + if (nearTiles[i].radius <= bt.config.game.battle.actions.moveRadius) { + nearTiles[i].tile.style = bt.config.game.battle.styles.move; + nearTiles[i].tile.avaliableAction = 'move'; + } else if ((nearTiles[i] != this.selectedTile) && (nearTiles[i].radius >= minAttackRadius) && ((nearTiles[i].radius <= maxAttackRadius))) { + nearTiles[i].tile.style = bt.config.game.battle.styles.range; + } + } + } + this.selectedTile.style = bt.config.game.battle.styles.selected; } } } - this.selectedTile.style = bt.config.game.battle.styles.selected; } }; // Sets tile as selected - this._executeActionOnTile = function(tile) { + this._executeActionOnTile = function(battleService, tile) { if (tile.avaliableAction) { - alert('Processing action "' + tile.avaliableAction + '"!'); + if (tile.avaliableAction == 'move') { + bt.game.battle.battleField.actions.move(battleService, this.selectedTile.units[bt.model.common.units.definitions.scient][0], tile); + } else if (tile.avaliableAction == 'attack') { + bt.game.battle.battleField.actions.attack(battleService, this.selectedTile.units[bt.model.common.units.definitions.scient][0], tile); + } } }; diff --git a/res/partials/views/battle.html b/res/partials/views/battle.html index 7f0b0c8..78cbda0 100644 --- a/res/partials/views/battle.html +++ b/res/partials/views/battle.html @@ -4,10 +4,15 @@

BATTLE VIEW !!!



+
+ Last action by player: {{ bt.game.battle.model.battleField.activePlayer }} +
+

Timers:

-
{{ bt.game.battle.timers.battleTime }}
-
{{ bt.game.battle.timers.playTime }}
+
{{ bt.game.battle.timers.battleTimeFormated }}
+
{{ bt.game.battle.timers.playTimeFormated }}
+
Pass turn ...
@@ -21,11 +26,15 @@

Timers:

-
+
-
-
+
+
+
+ +
+ {{ tile.distance }}
@@ -48,6 +57,7 @@

Timers:

Element: {{ unit.element }}
Stats:
+
HP: {{ unit.hp }}
Value: {{ unit.general.value }}
Attack: {{ unit.general.attack }}
Defense: {{ unit.general.defense }}
diff --git a/res/partials/views/frontpage.html b/res/partials/views/frontpage.html index 485ece4..6e4199c 100644 --- a/res/partials/views/frontpage.html +++ b/res/partials/views/frontpage.html @@ -6,6 +6,11 @@

FRONTPAGE VIEW !!!

+ +
+ + +