From 565f395da2a06b099f1b8fe6e9330fad2be51166 Mon Sep 17 00:00:00 2001 From: rexim Date: Sun, 6 Oct 2024 03:59:31 +0700 Subject: [PATCH] Tuck items under the WASM --- client.c3 | 12 ++++++------ client.mjs | 4 ++-- client.mts | 10 +++++----- client.wasm | Bin 71486 -> 71478 bytes common.c3 | 4 ---- common.mjs | 4 +--- common.mts | 6 +----- server.c3 | 6 +++--- server.mjs | 2 +- server.mts | 6 +++--- server.wasm | Bin 87311 -> 87276 bytes 11 files changed, 22 insertions(+), 32 deletions(-) diff --git a/client.c3 b/client.c3 index 179df68..bb29582 100644 --- a/client.c3 +++ b/client.c3 @@ -916,9 +916,9 @@ fn void key_up(uint key_code) @extern("key_up") @wasm { } } -fn void render_game(Image *display, float *zbuffer, SpritePool *sprite_pool, ParticlePool *particle_pool, Scene *scene, Item[] *items, Bombs *bombs, Image *key_image, Image *bomb_image, Image *particle_image, Image *wall_image, Image *player_image, float delta_time, float time) @extern("render_game") @wasm { +fn void render_game(Image *display, float *zbuffer, SpritePool *sprite_pool, ParticlePool *particle_pool, Scene *scene, Bombs *bombs, Image *key_image, Image *bomb_image, Image *particle_image, Image *wall_image, Image *player_image, float delta_time, float time) @extern("render_game") @wasm { update_all_players(scene, delta_time); - update_items(sprite_pool, time, items, key_image, bomb_image); + update_items(sprite_pool, time, &common::items, key_image, bomb_image); update_bombs_on_client_side(sprite_pool, particle_pool, bomb_image, scene, delta_time, bombs); update_particles(particle_image, sprite_pool, delta_time, scene, particle_pool); @@ -962,10 +962,10 @@ fn uint ping_msecs() @extern("ping_msecs") @wasm { return ping; } -fn bool process_message(Message *message, Item[] *items, Bombs *bombs, ParticlePool *particle_pool) @extern("process_message") @wasm { +fn bool process_message(Message *message, Bombs *bombs, ParticlePool *particle_pool) @extern("process_message") @wasm { // TODO: verify_* functions should return the typed message with exception, so we can turn this into sequence of if-try-s if (verify_hello_message(message)) { - apply_hello_message_to_me((HelloMessage*)message, items); + apply_hello_message_to_me((HelloMessage*)message, &common::items); } else if (verify_players_joined_batch_message(message)) { apply_players_joined_batch_message((PlayersJoinedBatchMessage*)message); } else if (verify_players_left_batch_message(message)) { @@ -977,11 +977,11 @@ fn bool process_message(Message *message, Item[] *items, Bombs *bombs, ParticleP } else if (verify_pong_message(message)) { process_pong_message((PongMessage*)message); } else if (verify_items_collected_batch_message(message)) { - if (!apply_items_collected_batch_message_to_level_items((ItemsCollectedBatchMessage*)message, items)) { + if (!apply_items_collected_batch_message_to_level_items((ItemsCollectedBatchMessage*)message, &common::items)) { return false; } } else if (verify_items_spawned_batch_message(message)) { - if (!apply_items_spawned_batch_message_to_level_items((ItemsSpawnedBatchMessage*)message, items)) { + if (!apply_items_spawned_batch_message_to_level_items((ItemsSpawnedBatchMessage*)message, &common::items)) { return false; } } else if (verify_bombs_spawned_batch_message(message)) { diff --git a/client.mjs b/client.mjs index 0976ae5..7b8251b 100644 --- a/client.mjs +++ b/client.mjs @@ -231,7 +231,7 @@ async function createGame() { return; } const eventDataPtr = common.arrayBufferAsMessageInWasm(wasmClient, event.data); - if (!game.wasmClient.process_message(eventDataPtr, game.level.itemsPtr, game.level.bombsPtr, game.particlesPtr)) { + if (!game.wasmClient.process_message(eventDataPtr, game.level.bombsPtr, game.particlesPtr)) { ws?.close(); return; } @@ -242,7 +242,7 @@ async function createGame() { return game; } function renderGame(display, deltaTime, time, game) { - game.wasmClient.render_game(display.backImagePtr, display.zBufferPtr, game.spritePoolPtr, game.particlesPtr, game.level.scenePtr, game.level.itemsPtr, game.level.bombsPtr, game.assets.keyImagePtr, game.assets.bombImagePtr, game.assets.particleImagePtr, game.assets.wallImagePtr, game.assets.playerImagePtr, deltaTime, time); + game.wasmClient.render_game(display.backImagePtr, display.zBufferPtr, game.spritePoolPtr, game.particlesPtr, game.level.scenePtr, game.level.bombsPtr, game.assets.keyImagePtr, game.assets.bombImagePtr, game.assets.particleImagePtr, game.assets.wallImagePtr, game.assets.playerImagePtr, deltaTime, time); displaySwapBackImageData(display, game.wasmClient); if (MINIMAP) game.wasmClient.render_minimap(display.minimapPtr, game.level.scenePtr, game.spritePoolPtr); diff --git a/client.mts b/client.mts index e2a91bf..8185ec3 100644 --- a/client.mts +++ b/client.mts @@ -70,10 +70,10 @@ interface WasmClient extends common.WasmCommon { unregister_all_other_players: () => void, key_down: (key_code: number, bombs: number) => void, key_up: (key_code: number) => void, - render_game: (display: number, zbuffer: number, sprite_pool: number, particle_pool: number, scene: number, items: number, bombs: number, key_image: number, bomb_image: number, particle_image: number, wall_image: number, player_image: number, delta_time: number, time: number) => void, + render_game: (display: number, zbuffer: number, sprite_pool: number, particle_pool: number, scene: number, bombs: number, key_image: number, bomb_image: number, particle_image: number, wall_image: number, player_image: number, delta_time: number, time: number) => void, ping_server_if_needed: () => void, ping_msecs: () => number, - process_message: (message: number, items: number, bombs: number, particle_pool: number) => boolean, + process_message: (message: number, bombs: number, particle_pool: number) => boolean, } function createDisplay(wasmClient: WasmClient, backImageWidth: number, backImageHeight: number): Display { @@ -236,7 +236,7 @@ async function instantiateWasmClient(url: string): Promise { unregister_all_other_players: wasm.instance.exports.unregister_all_other_players as () => void, key_down: wasm.instance.exports.key_down as (key_code: number) => void, key_up: wasm.instance.exports.key_up as (key_code: number) => void, - render_game: wasm.instance.exports.render_game as (display: number, zbuffer: number, sprite_pool: number, particle_pool: number, scene: number, items: number, bombs: number, key_image: number, bomb_image: number, particle_image: number, wall_image: number, player_image: number, delta_time: number, time: number) => void, + render_game: wasm.instance.exports.render_game as (display: number, zbuffer: number, sprite_pool: number, particle_pool: number, scene: number, bombs: number, key_image: number, bomb_image: number, particle_image: number, wall_image: number, player_image: number, delta_time: number, time: number) => void, ping_server_if_needed: wasm.instance.exports.ping_server_if_needed as () => void, ping_msecs: wasm.instance.exports.ping_msecs as () => number, process_message: wasm.instance.exports.process_message as (message: number) => boolean, @@ -315,7 +315,7 @@ async function createGame(): Promise { } const eventDataPtr = common.arrayBufferAsMessageInWasm(wasmClient, event.data); // console.log(`Received message from server`, new Uint8ClampedArray(event.data)); - if (!game.wasmClient.process_message(eventDataPtr, game.level.itemsPtr, game.level.bombsPtr, game.particlesPtr)) { + if (!game.wasmClient.process_message(eventDataPtr, game.level.bombsPtr, game.particlesPtr)) { ws?.close(); return; } @@ -328,7 +328,7 @@ async function createGame(): Promise { } function renderGame(display: Display, deltaTime: number, time: number, game: Game) { - game.wasmClient.render_game(display.backImagePtr, display.zBufferPtr, game.spritePoolPtr, game.particlesPtr, game.level.scenePtr, game.level.itemsPtr, game.level.bombsPtr, game.assets.keyImagePtr, game.assets.bombImagePtr, game.assets.particleImagePtr, game.assets.wallImagePtr, game.assets.playerImagePtr, deltaTime, time); + game.wasmClient.render_game(display.backImagePtr, display.zBufferPtr, game.spritePoolPtr, game.particlesPtr, game.level.scenePtr, game.level.bombsPtr, game.assets.keyImagePtr, game.assets.bombImagePtr, game.assets.particleImagePtr, game.assets.wallImagePtr, game.assets.playerImagePtr, deltaTime, time); displaySwapBackImageData(display, game.wasmClient); if (MINIMAP) game.wasmClient.render_minimap(display.minimapPtr, game.level.scenePtr, game.spritePoolPtr); diff --git a/client.wasm b/client.wasm index 7274e96fb9cf127a66434172be252ead830161f0..0bb17ac4ceebc20873d51e2ebee5407124ecb39d 100755 GIT binary patch delta 3443 zcmZ`+3vd(18NUDS^spt_XJb3I32;&z*Tx`BA$~wYeP9eE1PBiY9N;3$CnH%eOJ~45 zBAXBj8JL!CTcx3sn374;1}Jbm(g4N~!ppoS?X=WDN-`-SP&$SrZPU(>{(F)E6DrO8 zyZim$|JZ+b@Af*k^Lw}Rsf;Tc0ES`Ui;b!4!M5|YLBO`CWFHh8z?7N_Z43WdPIpTs*K989B@UNn997n6bgnS zu1Kp~-{SEo5oS#MW;)~x%vg);4SH3t%kN#LFm5uKFDUUoQMG)=b2Depsh!)PG&cDI z!BDt4s+*Roa+T*I?lg&D3F-Xk)|_(I{Nk-YvR3 zjQ<>fC(qX8#cZgMm?F084LYGTiLs}1Rcw`*CZMLRyhkz8WN)#lsPvk|5@Eht; z6pC2UA=ns;1)1P;q>HvmbR@k)J>5rSMXS_HC}tE*LWge5ikxVooKmdOW*st%tcYog zobY;*k}CXG?=b>eqd6mjm_laMZi#I(aa@dD$;KW-MH?fXXPO*bf!=2^dHNTbB8GAB zQVu31GQ%0(25eO#k0O?L@U$Mh#5LOir_3Usm-s0$L$T!bBW2Oq$np_NH1#6Zdv0Zt|FxkfF6JG9fn5E3H z!;mbI1bVTAxAdYW_&PD>!%8_)`e-z3krCeRM?>OWKO7Rs+jFyQyp*DIvHlJNIX-t# zjJFysVu}s)HL)A;j7Z-?bCQ-=A7K-9JkiGCK1Q!*$rkR5Y}UOcX>azU@zOw_G^DwY znW{5I5Po%!h1HK)d{TOIGQ4D&M0+BLtgt0PV=a1}@HBDK@G`a(n-A7vHf8KE>_Qn^ zgw3U&=*W|}iBT~c8?KlgR?g+po>bv0V+#^kK{+Sr*l5HmP}h~i_^`>PLhuRi(4e@_ zc+xvBx02C~xT+{$v;99@yc4LnTaIILJi zUg`ymEO8=V!kq`<2$`{J1aY*DLAwQQ)em8!O$pc4$Go943)?5cVd8JkgM|H_c52_- zKJg*;EVb`Q*n5%n{;E>=fZSL$29A=#mp7o?{qoNde`a+AJ|vy1e+kXxuvYWxY8K9t?)86!bEIQ~9gb;7 zHVhez&Od#(9^Fpd`U1M$ymb(|J+!p}-4<+fpvy_y=I80f!O~!LSy~$Gm2j`ISegUd z3b2BoY!d(uXt%fj6wu;_I|@;5-(g32e#c0Z?9LLDPF>dNa?Q>)h}d??C1}IgswL}c?941*!UQp#d4o; zg|r>p2%nP7IJPAtQsWu1Iu;4z)ai+nLBIBcS&m}4AiqIEnY z94BW^Hen$vPwj*&Taef$!xih6nz{8baqi~#t9R&_QG@MrD9 zxwnk)v9|J0PqEnSzpxhIV^ZC74F|(7{w%e$hsMpPN4SjDUwnYCzN>mNKwn<0$QH3? zAI=b;xxCP$-TqrG3s=dMYlq=;&GN-mHm8@e9Y%Z<7?A2P;)}?P#K4VSfZJr@&FLt& z-yDzf*3EHnTPyuX8Ha<$TVa$JZq0-qQgZu0*f0Fj4n5lBFE?@n|E>Ep*;wk+iXKy? zZ}Dep7JA56{|x8#>FMK#Mn#4Z)5WkW|8*=gZ+{*ibOjW~YH|C$N>Ft!p|`O+xhwX~ zPla;B)BUbn6%X9DDj&RTjrsp~t+D!53!aRd1$Z2uic49L8-FhgG7O%GqKu2r7vRyF zALK^W2B$aVtO-Q|E>%?`&c)$~H>i55GLxE6iz)|O{C?RJl(LhU#~*U3vL_M>$gW`P z_;{xPgJ44Zk^uPwtH0}#x`?WgiSdjK$jYfnk>!>K?_$+EYLZ!&11)~+O^%PvfI+b- z7FpJvtKJ6B)3)TGB}j+Q0R0qj5c^w@3VJDQ5;Q6r)D_0MNZ6E zSsI>BgN7O~=nSFHqt~xEql!}3^jyyU(2@vVdgc07RhgM_KkoL2qRK3zM{j5M^Ab^9 z4RiXDbtkp)k(rPad*0w_R8;reegu!dCAw%{meV~-ZfM2L;dRRqrO_M3)u=NDlz=sNxx;CSr-7!02H zr~zPy#`v58P>{dqk%!I0Sw7kuCj+36^$m@0&VoncRXLEsDQXklmxU=VcXNw3qWDV^ z$~yxf&4k|-_;C=RgyC#(1l0^R`W07H3B^0IU<8EY=d+*`n&XfSqu9vEgl$_kWSM3C z6C`>M$_JIwhNWN->Y}CiZW?I@`q{ELaX>IF!K5>mBy&L|3zpv1rrDFZwmy8K6_v~4 Pzs$jUERT2QK->QS615}G delta 3335 zcmZu!32+ou8t(sf_snFDq(csJaP$mUfG{9P5)d)XjmwP!;xSAnJut%@$s9lsop8#s zfXhDCrnE|Nu@skMfySWAxuD!AURx-zz`B+wiwndH_Hh5#GXX-GRQ>NizW?n1!AsLt zVc%B4%6a2q;5ZJxSz}#EvQyi&_CV$&X%loeNoK1R%=91dB>z2Mst+UMVMw+&6pH%1 z3DpxwsNpzQY4=4#A=Q^KWUlIty(Sv2iE{&TVrpDXco1CY343F+Iqg^uloIX%dWkR1 z8D#SWB7sD}8w$)&>XVQ}&J#>Pf&rBkr;Hd!22)N9xy|90Nozg{{|`-oi;GgTa1 zWMEn#A36J_7JSy`0E281yTQ2)pNar~qr0;@(VHe`_j4>@1}wFB5)A&L)M5gZ(0iyk zZcYO@dO;|lADO$rdHNI5bF_n{5NUr)HEQNqNxbH>2O*ondVMes)#)^aB~&0DBu*0wO7PZ4$f%q>7Xl1X;bGiWk#WZF%;=FV|Z!20ZavP zDlTgajHF?&w}K(?b{y7{Q|6>M7{URVHZy!t$ufL#?k*EOJ*|C8w3}?Q)d9x0a3XI~ zeunxCOa|#C7Gk)A$6dw@S&E~sr3#A?2_dhvf{03KQ0Z9Tg0iMjMEb)W6n;=!P|2ys ztSPnviftq-02hsyHl$(1X3PsOprG`sAbs#Sv>Yp`AmzwH1u4Vh-0uv{Alyzg+>eLQ zos?JdMT}Fbf)%85TGzRfmkizgsH;L7E&$S^pqwozQc&8+lHzWO6s8xYh~dOnSMqoh zl#>WW4ZC2FyO05Kq72J!=6CEqxnOukV`aLF#vOy)MfYG0y@bX_t^}vaLa^>mbHVft zkwsfJyZrjP={bVM!YSAAdU5*ePu1tbK3Y`Q5%%d-b^o)#ep=bE2KMX68VHZa$p!g5 z9Hf_0#c+tOUf7%VXdH+bwQ*SKL6*#A$8RAh*8m$(kn71G04a@ZHjwUx!+!w@47n|h z3}C#U77oWOgBP`Ad2SKQa&S=}c$YRK{~mQOUWIhe;ulaqc}WZo)5A-C3rFbGr7k#1 z=PwPxF{8f1Wz2KpvgPoe{`s==gxXhF=>A`ig%i}Vq8bkB<5nyo@B#gJ*v zes@b}oYvP{B!Ip8t*vVTJ)Ymz6=~BpC(<+9?n7$b-VE40PpKJ_AN9Q{GEM8{HI3>4oMA3 z;pM3eN|&hpXo!AyxC?wiOOBi`xYQaWv|!ev{nexMacJ$1y^KTKeXJOV_UW zcDO{FkC!3lzbE?8F(;Ox{PBtIhWspkfdwaDf{S|5sonse=%e1(0M6(aKX}aq7xbln zeURXB=*)6}3v|rqmr>|{_V?Dpv&MqUAd?DW{11qWBz;9Mepw7>^;=&~A@CKQc=<1I zSL99fI`c)xprLSAAQ-qY%AbhxFq0NpP0-yKxJ7 zce4}yCN!_%OK%!`Zgvn^EF*Tju2$t|{4v){XX$@_sDqm{{A1fJqcSYYMnAlnr?3BU zQ=8%w#X`gzRyn)P7YeA6gnKS~X{|1PW>>8hD#eHHJd})wR8^~Y)POry;~6#Z-dEHE z+VLDHfkF6)DuD-Ike~}0T&{TwpsltoA979pn5sThs$G+y(3Gr+Ct}rFy#&PtN}G71 z)*XntqsfFPk_?4B{>TvR6@$67h~L&^B8>G^J7KTmA~u0X)&=@e6E2I4r;$4y~1?5k^d!tC+H1Cd() zlgRj@*yU5&iZOQlKPDKVm=g+SYKx8 zY4-Z+lYyA3c?zJH*<<{HkUp`0`r bx-QO4xcR@_WmVJ!UC= items.len) return false; Item *item = &(*items)[item_index]; diff --git a/common.mjs b/common.mjs index 2f2c916..32515d0 100644 --- a/common.mjs +++ b/common.mjs @@ -5,7 +5,6 @@ export function makeWasmCommon(wasm) { wasm, memory: wasm.instance.exports.memory, _initialize: wasm.instance.exports._initialize, - allocate_items: wasm.instance.exports.allocate_items, reset_temp_mark: wasm.instance.exports.reset_temp_mark, allocate_temporary_buffer: wasm.instance.exports.allocate_temporary_buffer, allocate_bombs: wasm.instance.exports.allocate_bombs, @@ -14,9 +13,8 @@ export function makeWasmCommon(wasm) { } export function createLevel(wasmCommon) { const scenePtr = wasmCommon.allocate_default_scene(); - const itemsPtr = wasmCommon.allocate_items(); const bombsPtr = wasmCommon.allocate_bombs(); - return { scenePtr, itemsPtr, bombsPtr }; + return { scenePtr, bombsPtr }; } export function arrayBufferAsMessageInWasm(wasmCommon, buffer) { const wasmBufferSize = buffer.byteLength + UINT32_SIZE; diff --git a/common.mts b/common.mts index 1182bd2..1e9389c 100644 --- a/common.mts +++ b/common.mts @@ -5,7 +5,6 @@ export interface WasmCommon { wasm: WebAssembly.WebAssemblyInstantiatedSource, memory: WebAssembly.Memory, _initialize: () => void, - allocate_items: () => number, reset_temp_mark: () => void, allocate_temporary_buffer: (size: number) => number, allocate_bombs: () => number, @@ -17,7 +16,6 @@ export function makeWasmCommon(wasm: WebAssembly.WebAssemblyInstantiatedSource): wasm, memory: wasm.instance.exports.memory as WebAssembly.Memory, _initialize: wasm.instance.exports._initialize as () => void, - allocate_items: wasm.instance.exports.allocate_items as () => number, reset_temp_mark: wasm.instance.exports.reset_temp_mark as () => void, allocate_temporary_buffer: wasm.instance.exports.allocate_temporary_buffer as (size: number) => number, allocate_bombs: wasm.instance.exports.allocate_bombs as () => number, @@ -29,15 +27,13 @@ export function makeWasmCommon(wasm: WebAssembly.WebAssemblyInstantiatedSource): // between Client and Server and constantly synced over the network. export interface Level { scenePtr: number, - itemsPtr: number, bombsPtr: number, } export function createLevel(wasmCommon: WasmCommon): Level { const scenePtr = wasmCommon.allocate_default_scene(); - const itemsPtr = wasmCommon.allocate_items(); const bombsPtr = wasmCommon.allocate_bombs(); - return {scenePtr, itemsPtr, bombsPtr}; + return {scenePtr, bombsPtr}; } export function arrayBufferAsMessageInWasm(wasmCommon: WasmCommon, buffer: ArrayBuffer): number { diff --git a/server.c3 b/server.c3 index 26500f7..aabde9f 100644 --- a/server.c3 +++ b/server.c3 @@ -598,16 +598,16 @@ fn bool process_message_on_server(uint id, Message *message, Bombs *bombs) @exte } uint previous_timestamp = 0; -fn uint tick(Item[]* items, Bombs *bombs, Scene *scene) @extern("tick") @wasm { +fn uint tick(Bombs *bombs, Scene *scene) @extern("tick") @wasm { uint timestamp = platform_now_msecs(); float delta_time = (float)(timestamp - previous_timestamp)/1000.0f; previous_timestamp = timestamp; - process_joined_players(items); + process_joined_players(&common::items); process_left_players(); process_moving_players(); process_thrown_bombs(bombs); - process_world_simulation(items, scene, bombs, delta_time); + process_world_simulation(&common::items, scene, bombs, delta_time); process_pings(); uint tickTime = platform_now_msecs() - timestamp; diff --git a/server.mjs b/server.mjs index 3ebcd08..9d1cdf8 100644 --- a/server.mjs +++ b/server.mjs @@ -85,7 +85,7 @@ wss.on("connection", (ws, req) => { }); }); function tick() { - const tickTime = wasmServer.tick(level.itemsPtr, level.bombsPtr, level.scenePtr); + const tickTime = wasmServer.tick(level.bombsPtr, level.scenePtr); setTimeout(tick, Math.max(0, 1000 / SERVER_FPS - tickTime)); } function platform_now_secs() { diff --git a/server.mts b/server.mts index 6762824..61849f8 100644 --- a/server.mts +++ b/server.mts @@ -104,7 +104,7 @@ wss.on("connection", (ws, req) => { }) function tick() { - const tickTime = wasmServer.tick(level.itemsPtr, level.bombsPtr, level.scenePtr); + const tickTime = wasmServer.tick(level.bombsPtr, level.scenePtr); setTimeout(tick, Math.max(0, 1000/SERVER_FPS - tickTime)); } @@ -113,7 +113,7 @@ interface WasmServer extends common.WasmCommon { register_new_player: (id: number, x: number, y: number, hue: number) => void, unregister_player: (id: number) => void, process_message_on_server: (id: number, message: number, bombs: number) => boolean, - tick: (items: number, bombs: number, scene: number) => number, + tick: (bombs: number, scene: number) => number, } function platform_now_secs(): number { @@ -155,7 +155,7 @@ async function instantiateWasmServer(path: string): Promise { register_new_player: wasm.instance.exports.register_new_player as (id: number, x: number, y: number, hue: number) => void, unregister_player: wasm.instance.exports.unregister_player as (id: number) => void, process_message_on_server: wasm.instance.exports.process_message_on_server as (id: number, message: number, bombs: number) => boolean, - tick: wasm.instance.exports.tick as (items: number, bombs: number, scene: number) => number, + tick: wasm.instance.exports.tick as (bombs: number, scene: number) => number, }; } diff --git a/server.wasm b/server.wasm index bea6472636f78832d7ec9bbc03708ebf8e13a573..ad129c53cb949f989c5c6eb62a6099ffc7be7828 100755 GIT binary patch delta 3631 zcmdT{X>=3U6`nhz#YVOngE!e=kHiugV%Zo%fF_P*7PHyd;E;wTgDj7*WG%KdV#!I5 zIVq)Z>NJ>VGC)WQ2Tps^o*s;cVkn!<>IQ<%4kzV=u*Ebfg_IBooVIsH@|MEC{;7Vv z_wIe)z2A55JUxxx|GoB`Pqd8_fUc<~uK=fG^m;lzo;bkHY@p-F30Tj?IQ$-u;1GG2 zD0rzCQ1i$+dT2Ip8C5fR zO5wEWGm7RfSZFVIR0z(+E_an@k=GXp_S<`1QyoXK#q3+GpB33^c{syIvHMo6SV5V! zlVd3^n$xk@nF)y;&?SB>`Bzda|X7Z^fw;tg-g_GF}!_U z>MdxOZ>QE5l^%{}=vw%hww8TY;V{uQ%0jQ$sVo^A#nEHKhV9yRbg`Qi3`cWJm2A?` z`L?!3v@JA?!q^Yhcr9t1n>1aA4kgiAW#6Q?QKl#+pU&s>T$GI#-4>cMGYCX_Q z`;pBMN!_TV4y(X6pHkXvj#lMFHz^ObTSJTP85$xI+O$0qR1&h=lVCg=iBMJchLDn~ zlrg0aNiJvBU^X{DhL)8NGJht_B3hNl=9IoCTy107^}O`h?La+vB&ni$5wX(y#* zO)7h0dVFXLdxmt>3`flOHF+s2rVDb`G`jYkD+5{-NKxPg=-3tt0GuWBmkc2rgQm<5 zC1C{2c16;dd=uNX1!!UPjbKn822iB7CXochVQ_|Qz_EiI6q9hgAm*bdDa0AWp;Qbe zqV`Sd(a<5!3K_!m6C>1=Qi>)_U1w55G!bel2B~pNKCxsL$QjEjS!g1wSFeO7Id;uc zTHmB-Pc@MGFS6y=>aNisldac{u!|Jm%qD-kJ^|6gZcIv(6__~&Z%mEt9=JKc+e|Z( zX#qBjly4}=yEi630|^SUF^qH}YR}Ed$nvk7T`YySlvr4o1Aihiu6L8Wx8B3?o!cLv zf8bXEXeO(^S_^wfa;F*gk_nw2__M0-Vy6-re`htKH{IDGw|#JuhW(^rV;LMEcQ)pO zERWc$L8~Jl&t*71bgJ)ew2}p0mt{1W8%#H^E37+#c_=7GUeu&Os3Kt%jq*2 z${bhFuYh9rl5P7dkTB|i8OcT-SYT9}fvsR{9e8&rtzbst4;r>&%YmWT*kcD+fP?bI zgAITYRV~AZs9iSeVux{f={*G#-ubpiY8%h~3b}1G+eCJ?j6(G7mXSCPXjR5Mb#%9` z!u9=D9>>zd6H)p4!}H)Exp#ObdR{$ZhC^i0kuB(lqo3i}ezY0=zdV+Y>$YPjacnp~ z9zCaz&%x1r;&tS$-Z2J0Y z;)T8#O&6vCF3G09H)!E7+5K$>ju*fE9FI}cKR?FvS^B_;=d2)1+qM4@$5)19kXA@v1lhdwfOvN8ce6LWy=lD%yuc*QSFIO|o(fBst#yUAI z?zBtADR2y~NC`%0g{#dMjWCl=x~<^xXac82uvv%iBT;O7^SD8951>=ZOov%GEl-C6 zoc5l0$j^NzF>t@blJMCAX)`dYcXWiPVBD;!Qv~es6$v*JxO8qS0M9bwyPwU zN3aG3p=wG}Z_m;IzQpt8%SE9uzPI1u@dt&eI;T2M>ysoP*ej;@29LL@S!Q%Kb&5iz zpwzrGwa&$Y=qTzPVRd>!!OG$U>DnAfNSmee3SLL`@)AAI2VK4jXDKcmeyn%4bYT{l zis$Hp4!ft^SElqfT-WQk^aLvdSmIn<+QZ8?!b_#)KUK?g_ybtAi05IkHT)Fcd`bP;2rx%ToYDytl zvX;U?uq%%sMVeO%iBK+8m%@wS=yt_L2Lz8`4+<68;gBoE5Z~n!tUbP{fDpugNY36c zyQ3!L3J8mH!?6C?s;G#sQmUT~xzgI%kR{!l4LaK014^T-GQ0J~X}$J9l}Fk+2Zkeu zs>Q3QOi|RwYmn2YY-WK!T;!OSs-?$s@HVKC(#l{Y1f;?;7(oZKq^V^v#NZQ_^5J%h H3f}i0F!!g# delta 3661 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 i$EV=U5mE0?fjl