From 162a0dfb5e4d0db1119f851e4a3c7464255adec3 Mon Sep 17 00:00:00 2001 From: rexim Date: Sat, 28 Sep 2024 05:26:37 +0700 Subject: [PATCH] Turn Scene into an opaque pointer --- client.c3 | 20 +++++++++---------- client.mjs | 12 ++++++------ client.mts | 28 +++++++++++++------------- client.wasm | Bin 64288 -> 64596 bytes common.c3 | 55 +++++++++++++++++++++++++++++++++++++++------------- common.mjs | 38 ++++++------------------------------ common.mts | 54 ++++++++++----------------------------------------- server.c3 | 4 ++-- server.mjs | 4 ++-- server.mts | 8 ++++---- server.wasm | Bin 73075 -> 73311 bytes 11 files changed, 96 insertions(+), 127 deletions(-) diff --git a/client.c3 b/client.c3 index 0be1565..2551ed5 100644 --- a/client.c3 +++ b/client.c3 @@ -272,11 +272,11 @@ fn Vector2 ray_step(Vector2 p1, Vector2 p2) { return p3; } -fn Vector2 cast_ray(bool *scene, int scene_width, int scene_height, Vector2 p1, Vector2 p2) { +fn Vector2 cast_ray(Scene *scene, Vector2 p1, Vector2 p2) { Vector2 start = p1; while (start.distance(p1) < FAR_CLIPPING_PLANE) { Vector2 c = hitting_cell(p1, p2); - if (common::scene_get_tile(scene, scene_width, scene_height, c)) break; + if (common::scene_get_tile(scene, c)) break; Vector2 p3 = ray_step(p1, p2); p1 = p2; p2 = p3; @@ -287,17 +287,17 @@ fn Vector2 cast_ray(bool *scene, int scene_width, int scene_height, Vector2 p1, fn void render_walls(Color *display, int display_width, int display_height, float *zbuffer, Color *wall, int wall_width, int wall_height, float position_x, float position_y, float direction, - bool *scene, int scene_width, int scene_height) @extern("render_walls") @wasm { + Scene *scene) @extern("render_walls") @wasm { Camera camera = { .position = {position_x, position_y}, .direction = direction }; camera.update(); Vector2 d = from_polar(direction, 1.0f); for (int x = 0; x < display_width; ++x) { - Vector2 p = cast_ray(scene, scene_width, scene_height, camera.position, camera.fovLeft.lerp(camera.fovRight, (float)x/display_width)); + Vector2 p = cast_ray(scene, camera.position, camera.fovLeft.lerp(camera.fovRight, (float)x/display_width)); Vector2 c = hitting_cell(camera.position, p); Vector2 v = p - camera.position; zbuffer[x] = v.dot(d); - if (common::scene_get_tile(scene, scene_width, scene_height, c)) { + if (common::scene_get_tile(scene, c)) { render_column_of_wall(display, display_width, display_height, zbuffer, wall, wall_width, wall_height, x, p.x, p.y, c.x, c.y); } } @@ -308,7 +308,7 @@ fn void render_walls(Color *display, int display_width, int display_height, floa fn void render_minimap(Color *display, int display_width, int display_height, float camera_position_x, float camera_position_y, float camera_direction, float player_position_x, float player_position_y, - bool *scene, int scene_width, int scene_height, + Scene *scene, SpritePool *sprite_pool) @extern("render_minimap") @wasm { // ctx.save(); @@ -499,7 +499,7 @@ fn void emit_particle(float source_x, float source_y, float source_z, ParticlePo fn void update_particles(Color *image_pixels, int image_width, int image_height, SpritePool *sprite_pool, float deltaTime, - bool *scene, int scene_width, int scene_height, + Scene *scene, ParticlePool *particle_pool) @extern("update_particles") @wasm { foreach (&particle: particle_pool.items) { if (particle.lifetime > 0) { @@ -507,7 +507,7 @@ fn void update_particles(Color *image_pixels, int image_width, int image_height, particle.velocity_z -= common::BOMB_GRAVITY*deltaTime; Vector2 new_position = particle.position + particle.velocity*deltaTime; - if (common::scene_get_tile(scene, scene_width, scene_height, new_position)) { + if (common::scene_get_tile(scene, new_position)) { float dx = math::abs(math::floor(particle.position.x) - math::floor(new_position.x)); float dy = math::abs(math::floor(particle.position.y) - math::floor(new_position.y)); @@ -640,12 +640,12 @@ fn void explode_bomb(float bomb_position_x, float bomb_position_y, float bomb_po } } -fn void update_bombs_on_client_side(SpritePool *sprite_pool, ParticlePool *particle_pool, Color *bomb_image_pixels, int bomb_image_width, int bomb_image_height, bool *scene, int scene_width, int scene_height, float player_position_x, float player_position_y, float delta_time, Bombs *bombs) @extern("update_bombs_on_client_side") @wasm { +fn void update_bombs_on_client_side(SpritePool *sprite_pool, ParticlePool *particle_pool, Color *bomb_image_pixels, int bomb_image_width, int bomb_image_height, Scene *scene, float player_position_x, float player_position_y, float delta_time, Bombs *bombs) @extern("update_bombs_on_client_side") @wasm { foreach (&bomb: *bombs) { if (bomb.lifetime > 0) { push_sprite(sprite_pool, bomb_image_pixels, bomb_image_width, bomb_image_height, bomb.position.x, bomb.position.y, bomb.position_z, common::BOMB_SCALE, 0, 0, bomb_image_width, bomb_image_height); - if (common::update_bomb(bomb, scene, scene_width, scene_height, delta_time)) { + if (common::update_bomb(bomb, scene, delta_time)) { play_sound(BOMB_RICOCHET, player_position_x, player_position_y, bomb.position.x, bomb.position.y); } diff --git a/client.mjs b/client.mjs index 69d1778..1c8f261 100644 --- a/client.mjs +++ b/client.mjs @@ -393,13 +393,13 @@ function renderGame(display, deltaTime, time, game) { game.wasmClient.reset_sprite_pool(game.spritePoolPtr); game.players.forEach((player) => { if (player !== game.me) - updatePlayer(game.wasmClient, player, game.level.scene, deltaTime); + updatePlayer(game.wasmClient, player, game.level.scenePtr, deltaTime); }); - updatePlayer(game.wasmClient, game.me, game.level.scene, deltaTime); + updatePlayer(game.wasmClient, game.me, game.level.scenePtr, deltaTime); updateCamera(game.me, game.camera); updateItems(game.wasmClient, game.ws, game.spritePoolPtr, time, game.me, game.level.itemsPtr, game.assets); - game.wasmClient.update_bombs_on_client_side(game.spritePoolPtr, game.particlesPtr, game.assets.bombImage.ptr, game.assets.bombImage.width, game.assets.bombImage.height, game.level.scene.wallsPtr, game.level.scene.width, game.level.scene.height, game.me.position.x, game.me.position.y, deltaTime, game.level.bombsPtr); - game.wasmClient.update_particles(game.assets.particleImage.ptr, game.assets.particleImage.width, game.assets.particleImage.height, game.spritePoolPtr, deltaTime, game.level.scene.wallsPtr, game.level.scene.width, game.level.scene.height, game.particlesPtr); + game.wasmClient.update_bombs_on_client_side(game.spritePoolPtr, game.particlesPtr, game.assets.bombImage.ptr, game.assets.bombImage.width, game.assets.bombImage.height, game.level.scenePtr, game.me.position.x, game.me.position.y, deltaTime, game.level.bombsPtr); + game.wasmClient.update_particles(game.assets.particleImage.ptr, game.assets.particleImage.width, game.assets.particleImage.height, game.spritePoolPtr, deltaTime, game.level.scenePtr, game.particlesPtr); game.players.forEach((player) => { if (player !== game.me) { const index = spriteAngleIndex(game.camera.position, player); @@ -407,12 +407,12 @@ function renderGame(display, deltaTime, time, game) { } }); game.wasmClient.render_floor_and_ceiling(display.backImage.ptr, display.backImage.width, display.backImage.height, game.camera.position.x, game.camera.position.y, game.camera.direction); - game.wasmClient.render_walls(display.backImage.ptr, display.backImage.width, display.backImage.height, display.zBufferPtr, game.assets.wallImage.ptr, game.assets.wallImage.width, game.assets.wallImage.height, game.camera.position.x, game.camera.position.y, game.camera.direction, game.level.scene.wallsPtr, game.level.scene.width, game.level.scene.height); + game.wasmClient.render_walls(display.backImage.ptr, display.backImage.width, display.backImage.height, display.zBufferPtr, game.assets.wallImage.ptr, game.assets.wallImage.width, game.assets.wallImage.height, game.camera.position.x, game.camera.position.y, game.camera.direction, game.level.scenePtr); game.wasmClient.cull_and_sort_sprites(game.camera.position.x, game.camera.position.y, game.camera.direction, game.spritePoolPtr); game.wasmClient.render_sprites(display.backImage.ptr, display.backImage.width, display.backImage.height, display.zBufferPtr, game.spritePoolPtr); displaySwapBackImageData(display, game.wasmClient); if (MINIMAP) - game.wasmClient.render_minimap(display.minimap.ptr, display.minimap.width, display.minimap.height, game.camera.position.x, game.camera.position.y, game.camera.direction, game.me.position.x, game.me.position.y, game.level.scene.wallsPtr, game.level.scene.width, game.level.scene.height, game.spritePoolPtr); + game.wasmClient.render_minimap(display.minimap.ptr, display.minimap.width, display.minimap.height, game.camera.position.x, game.camera.position.y, game.camera.direction, game.me.position.x, game.me.position.y, game.level.scenePtr, game.spritePoolPtr); renderDebugInfo(display.ctx, deltaTime, game); } (async () => { diff --git a/client.mts b/client.mts index ea593c4..be46c88 100644 --- a/client.mts +++ b/client.mts @@ -85,14 +85,14 @@ interface WasmClient extends common.WasmCommon { reset_sprite_pool: (sprite_pool: number) => void, render_floor_and_ceiling: (pixels: number, pixels_width: number, pixels_height: number, position_x: number, position_y: number, direction: number) => void, render_column_of_wall: (display: number, display_width: number, display_height: number, zbuffer: number, cell: number, cell_width: number, cell_height: number, x: number, px: number, py: number, cx: number, cy: number) => void, - render_walls: (display: number, display_width: number, display_height: number, zbuffer: number, wall: number, wall_width: number, wall_height: number, position_x: number, position_y: number, direction: number, scene: number, scene_width: number, scene_height: number) => void; - render_minimap: (display: number, display_width: number, display_height: number, camera_position_x: number, camera_position_y: number, camera_direction: number, player_position_x: number, player_position_y: number, scene: number, scene_width: number, scene_height: number, sprite_pool: number) => void; + render_walls: (display: number, display_width: number, display_height: number, zbuffer: number, wall: number, wall_width: number, wall_height: number, position_x: number, position_y: number, direction: number, scene: number) => void; + render_minimap: (display: number, display_width: number, display_height: number, camera_position_x: number, camera_position_y: number, camera_direction: number, player_position_x: number, player_position_y: number, scene: number, sprite_pool: number) => void; cull_and_sort_sprites: (camera_position_x: number, camera_position_y: number, camera_direction: number, sprite_pool: number) => void; push_sprite: (sprite_pool: number, image_pixels: number, image_width: number, image_height: number, x: number, y: number, z: number, scale: number, crop_position_x: number, crop_position_y: number, crop_size_x: number, crop_size_y: number) => void; render_sprites: (display: number, display_width: number, display_height: number, zbuffer: number, sprite_pool: number) => void, allocate_particle_pool: () => number, emit_particle: (source_x: number, source_y: number, source_z: number, particle_pool: number) => void, - update_particles: (image_pixels: number, image_width: number, image_height: number, sprite_pool: number, deltaTime: number, scene: number, scene_width: number, scene_height: number, particle_pool: number) => void + update_particles: (image_pixels: number, image_width: number, image_height: number, sprite_pool: number, deltaTime: number, scene: number, particle_pool: number) => void kill_all_items: (items: number) => void, verify_items_collected_batch_message: (message: number) => boolean, apply_items_collected_batch_message_to_level_items: (message: number, items: number, player_position_x: number, player_position_y: number) => boolean, @@ -104,7 +104,7 @@ interface WasmClient extends common.WasmCommon { apply_bombs_spawned_batch_message_to_level_items: (message: number, bombs: number) => boolean, verify_bombs_exploded_batch_message: (message: number) => boolean, apply_bombs_exploded_batch_message_to_level_items: (message: number, bombs: number, player_position_x: number, player_position_y: number, particle_pool: number) => boolean, - update_bombs_on_client_side: (sprite_pool: number, particle_pool: number, bomb_image_pixels: number, bomb_image_width: number, bomb_image_height: number, scene: number, scene_width: number, scene_height: number, player_position_x: number, player_position_y: number, delta_time: number, bombs: number) => void + update_bombs_on_client_side: (sprite_pool: number, particle_pool: number, bomb_image_pixels: number, bomb_image_width: number, bomb_image_height: number, scene: number, player_position_x: number, player_position_y: number, delta_time: number, bombs: number) => void } function createDisplay(wasmClient: WasmClient, backImageWidth: number, backImageHeight: number): Display { @@ -286,14 +286,14 @@ async function instantiateWasmClient(url: string): Promise { reset_sprite_pool: wasm.instance.exports.reset_sprite_pool as (sprite_pool: number) => void, render_floor_and_ceiling: wasm.instance.exports.render_floor_and_ceiling as (position_x: number, position_y: number, direction: number) => void, render_column_of_wall: wasm.instance.exports.render_column_of_wall as (display: number, display_width: number, display_height: number, zbuffer: number, cell: number, cell_width: number, cell_height: number, x: number, px: number, py: number, cx: number, cy: number) => void, - render_walls: wasm.instance.exports.render_walls as (display: number, display_width: number, display_height: number, zbuffer: number, wall: number, wall_width: number, wall_height: number, position_x: number, position_y: number, direction: number, scene: number, scene_width: number, scene_height: number) => void, - render_minimap: wasm.instance.exports.render_minimap as (display: number, display_width: number, display_height: number, camera_position_x: number, camera_position_y: number, camera_direction: number, player_position_x: number, player_position_y: number, scene: number, scene_width: number, scene_height: number, sprite_pool: number) => void, + render_walls: wasm.instance.exports.render_walls as (display: number, display_width: number, display_height: number, zbuffer: number, wall: number, wall_width: number, wall_height: number, position_x: number, position_y: number, direction: number, scene: number) => void, + render_minimap: wasm.instance.exports.render_minimap as (display: number, display_width: number, display_height: number, camera_position_x: number, camera_position_y: number, camera_direction: number, player_position_x: number, player_position_y: number, scene: number, sprite_pool: number) => void, cull_and_sort_sprites: wasm.instance.exports.cull_and_sort_sprites as (camera_position_x: number, camera_position_y: number, camera_direction: number, sprite_pool: number) => void, push_sprite: wasm.instance.exports.push_sprite as (sprite_pool: number, image_pixels: number, image_width: number, image_height: number, x: number, y: number, z: number, scale: number, crop_position_x: number, crop_position_y: number, crop_size_x: number, crop_size_y: number) => void, render_sprites: wasm.instance.exports.render_sprites as (display: number, display_width: number, display_height: number, zbuffer: number, sprite_pool: number) => void, allocate_particle_pool: wasm.instance.exports.allocate_particle_pool as () => number, emit_particle: wasm.instance.exports.emit_particle as (source_x: number, source_y: number, source_z: number, particle_pool: number) => void, - update_particles: wasm.instance.exports.update_particles as (image_pixels: number, image_width: number, image_height: number, sprite_pool: number, deltaTime: number, scene: number, scene_width: number, scene_height: number, particle_pool: number) => void, + update_particles: wasm.instance.exports.update_particles as (image_pixels: number, image_width: number, image_height: number, sprite_pool: number, deltaTime: number, scene: number, particle_pool: number) => void, kill_all_items: wasm.instance.exports.kill_all_items as (items: number) => void, verify_items_collected_batch_message: wasm.instance.exports.verify_items_collected_batch_message as (message: number) => boolean, apply_items_collected_batch_message_to_level_items: wasm.instance.exports.apply_items_collected_batch_message_to_level_items as (message: number, items: number, player_position_x: number, player_position_y: number) => boolean, @@ -305,7 +305,7 @@ async function instantiateWasmClient(url: string): Promise { apply_bombs_spawned_batch_message_to_level_items: wasm.instance.exports.apply_bombs_spawned_batch_message_to_level_items as (message: number, bombs: number) => boolean, verify_bombs_exploded_batch_message: wasm.instance.exports.verify_bombs_exploded_batch_message as (message: number) => boolean, apply_bombs_exploded_batch_message_to_level_items: wasm.instance.exports.apply_bombs_exploded_batch_message_to_level_items as (message: number, bombs: number, player_position_x: number, player_position_y: number, particle_pool: number) => boolean, - update_bombs_on_client_side: wasm.instance.exports.update_bombs_on_client_side as (sprite_pool: number, particle_pool: number, bomb_image_pixels: number, bomb_image_width: number, bomb_image_height: number, scene: number, scene_width: number, scene_height: number, player_position_x: number, player_position_y: number, delta_time: number, bombs: number) => void, + update_bombs_on_client_side: wasm.instance.exports.update_bombs_on_client_side as (sprite_pool: number, particle_pool: number, bomb_image_pixels: number, bomb_image_width: number, bomb_image_height: number, scene: number, player_position_x: number, player_position_y: number, delta_time: number, bombs: number) => void, }; } @@ -507,13 +507,13 @@ function renderGame(display: Display, deltaTime: number, time: number, game: Gam game.wasmClient.reset_sprite_pool(game.spritePoolPtr); game.players.forEach((player) => { - if (player !== game.me) updatePlayer(game.wasmClient, player, game.level.scene, deltaTime) + if (player !== game.me) updatePlayer(game.wasmClient, player, game.level.scenePtr, deltaTime) }); - updatePlayer(game.wasmClient, game.me, game.level.scene, deltaTime); + updatePlayer(game.wasmClient, game.me, game.level.scenePtr, deltaTime); updateCamera(game.me, game.camera); updateItems(game.wasmClient, game.ws, game.spritePoolPtr, time, game.me, game.level.itemsPtr, game.assets); - game.wasmClient.update_bombs_on_client_side(game.spritePoolPtr, game.particlesPtr, game.assets.bombImage.ptr, game.assets.bombImage.width, game.assets.bombImage.height, game.level.scene.wallsPtr, game.level.scene.width, game.level.scene.height, game.me.position.x, game.me.position.y, deltaTime, game.level.bombsPtr); - game.wasmClient.update_particles(game.assets.particleImage.ptr, game.assets.particleImage.width, game.assets.particleImage.height, game.spritePoolPtr, deltaTime, game.level.scene.wallsPtr, game.level.scene.width, game.level.scene.height, game.particlesPtr); + game.wasmClient.update_bombs_on_client_side(game.spritePoolPtr, game.particlesPtr, game.assets.bombImage.ptr, game.assets.bombImage.width, game.assets.bombImage.height, game.level.scenePtr, game.me.position.x, game.me.position.y, deltaTime, game.level.bombsPtr); + game.wasmClient.update_particles(game.assets.particleImage.ptr, game.assets.particleImage.width, game.assets.particleImage.height, game.spritePoolPtr, deltaTime, game.level.scenePtr, game.particlesPtr); game.players.forEach((player) => { if (player !== game.me) { @@ -523,12 +523,12 @@ function renderGame(display: Display, deltaTime: number, time: number, game: Gam }) game.wasmClient.render_floor_and_ceiling(display.backImage.ptr, display.backImage.width, display.backImage.height, game.camera.position.x, game.camera.position.y, game.camera.direction); - game.wasmClient.render_walls(display.backImage.ptr, display.backImage.width, display.backImage.height, display.zBufferPtr, game.assets.wallImage.ptr, game.assets.wallImage.width, game.assets.wallImage.height, game.camera.position.x, game.camera.position.y, game.camera.direction, game.level.scene.wallsPtr, game.level.scene.width, game.level.scene.height); + game.wasmClient.render_walls(display.backImage.ptr, display.backImage.width, display.backImage.height, display.zBufferPtr, game.assets.wallImage.ptr, game.assets.wallImage.width, game.assets.wallImage.height, game.camera.position.x, game.camera.position.y, game.camera.direction, game.level.scenePtr); game.wasmClient.cull_and_sort_sprites(game.camera.position.x, game.camera.position.y, game.camera.direction, game.spritePoolPtr) game.wasmClient.render_sprites(display.backImage.ptr, display.backImage.width, display.backImage.height, display.zBufferPtr, game.spritePoolPtr) displaySwapBackImageData(display, game.wasmClient); - if (MINIMAP) game.wasmClient.render_minimap(display.minimap.ptr, display.minimap.width, display.minimap.height, game.camera.position.x, game.camera.position.y, game.camera.direction, game.me.position.x, game.me.position.y, game.level.scene.wallsPtr, game.level.scene.width, game.level.scene.height, game.spritePoolPtr); + if (MINIMAP) game.wasmClient.render_minimap(display.minimap.ptr, display.minimap.width, display.minimap.height, game.camera.position.x, game.camera.position.y, game.camera.direction, game.me.position.x, game.me.position.y, game.level.scenePtr, game.spritePoolPtr); renderDebugInfo(display.ctx, deltaTime, game); } diff --git a/client.wasm b/client.wasm index 51106ea6523e1dc1f93a73daf00d399b3fd4abaa..b9acff36d3a4529bd6133a149df182f6c3527a69 100755 GIT binary patch delta 3637 zcmb_fO>87b6|U-@@k~#5Pq#fk-ScDbt4cslv@9V~Y$s86-B~3h(FX1yVq1$NF>)56 zHG<2~6F}j2EqS_E0!}$0p8%~dIRrK+mmmnqAr}M^hzk-Nh_q3Z14w+Ydj7mCIAO`^ zs#ov5dOzQLujbVc?e{*iYhv&EOHv3SUq7y0joh^O8xDu5*sWoM)o^(HUA%J&Bv;s)vaxkiG0QvR7*Y+X zOa&*@%9z~QQa3WyguD>Ps+9=UwzsK$i^5zxw35^ESViPeGpEK_dAPR)UP`|36z;C5 zn5wD+fk-*lwzXZD;pr8Z;!HO_2;Y(buzs=<#Wb* z?MR>d0bBD~bHlk4MV@pML}K)HMK12|v(xLA(0%GXk2OK1FK_^2Nh`A>MRdiNE(O#l zIv7xI@ChN3RDOlcb`f1iMVOdjxjWg0WHDb0(t2x5WQNe0_ zj8kt$ji%IHUs4P&I`BdkUYxm$sqEsJs{nX5o;`?-9TjpjjV)k8PKIyS{K; zvZMn@7 z|JTeHiO+u9u19V7!-#gnBd#wOPRfC9pT6IYYvrGo1DR&7>ZVpoWiG7>l}t!-RYh?) zHX~+owhH427v`;=bIAHZjut1LoI$*hwN;$UGfnMhA%2+qf zy8kHrZ_Y#4bg9Xu(=xcKC4E@Tf>EKz8|av~ow4p_pKGqAhOGsJmAO({)Z|L(Pb&q) zl3pz;WtIA-QYzG6|-j)jvr_s|(x{0Sfvy^Fiu z3r`gV9u{g@g->-h0r#V*&Gp#v6-W-#UDZQ5%~EEsTcZzbT}%E}c@AoABxLCj7n0Zj zb1ixM$ESa{H>^Iyds|kF4-9SM14Bc6tPQJ*kKC|I;D*o^)t(fa_Cpky!}G=AI@Pj+ z`{E@hSs!TU^!xW+_tSq=YZ7grTR*YiA@ih?sqq@%Hpk921uoN^oQoEZE}W<%GsK?Gz0^#P;4dj|E~e?aU}*Py-z z)OUgUU;*{f5!&w*_02izeOd;(LPmW$hrLg2P*I-<#RT9clqMb%Y-$4-KWU=+7b_tK zC=;myYEUCbBuwEr`LLGl(zLGs3H*3-V_@hCKbM|9Xp zkNhV%9aRJie*Fst1V^|f2jR=?D4ue^*+ztmXB&qPVJ}{8Tx5RsD{6mc$_pnF@ETa; z6JB;d#s<8+BmiT}!hGrSr0OOQf7*;^k9S_u@%@n*%}%EG2)w0bbTZBJl=fC66W$;WM!ee?~%dxy^W5CjJ1eK?poP zy+I-$Gpo_)+tm$GCHW*jPTv=sa5$AoIAgtk#;UP;+z74BPFH8Az1e9rJ8jQSo3qo8 z%}&>6tMh~8Wvftq0gP#3+9u8m;wJ}aX1H0vuKSHsB;9vxOAei4SlWym%qyEDULxg% zE>%8JOY<(ZWU39Av$G>D7ue*9s?69@RVn!R@xW>&Sx{>)@Y^Sd#QD zyD6^mmMScTAhDF;;TSAZQKzn370Fx2qtn|LU;jn)?{?|h-b?$!bFUtJY5&^cg|G1s az&B3E|GZ)Uw;yZ|WasXzo?f~0UgbYE7@a!+ delta 3401 zcmbVPO^h5z6|U}{*_od1>Z#qG+3ES&&8v!q)>aZlNM5gy@NTmTn8*qe2QDb_T9yx) zY#fnTf*h6}eiVejBYCF20Z;1tE|y$^i}#BrZq@4hWZf_9O4@e|rsa^|hBBA%ydb+l^~|nHGPir>B^R;}HKb=dat} zHg%s@dH$nM^m)nt@v1nkvSR9_$2Bbf_4jVy&3^7&asDtn28mCTLIdxMHqjO6B(Fe`XzdJEfy)hb%gpNq6 z@7S3nzmzpGD`d(&Bqwn$NpU2^=zyHNcl3I($HByu`bY1*uP)|PpHQ@znvG0vQd;g) zifa>+i>Ya4x{Y0hxUIJmp*!9ob?(x7ZhYFzX=9>e@~NFuYoa5#u?1a9o_|IPs_8D( zbq@xy_T5AGuy8IilDe6R-#q&1g*VRUBqy$kGG%sB8YJS9-d?Ku)XU7yT+yGH$Hs=f z$KHIwogJj5@@lzw1&h%w9pPAOUYHz1+SgG)5t*F2928UTt3sRCex0{it5Y*gRR$WNiGERQXdqfGcIH2Eik_mTz7z2@%6ou9(XIC(GGCx4NAU9AYxksgmQfJ~SKrxj8Ydu?bgTg;U)|MA9-O?SR@$|1HaxAQ`|> zHjImX!lCK%XK!NqV>4LUXE4><%gAmQ9M}S<+O)>R-!95vn`-=3rEOY68MNnRkV4oS z(;(9=;NBclm|4VJ1&Y?c4`f_dt%a~du@zE>*ukUwzrK8so0y}bh@qE~b(U=E7O2^0 z5_YN2k_~DVTUwk=N^O`_8STLY0mqJ?2JeVNKRS5f83}8+QtZonMAT##;6c0)@)5`o zi$e{5$wl70U3c`MXM*G}wP&%bhpepK#Cat4pqvKD{U6N!=Iyy?8#LH=V+`=Lg#n%_ z4DOy=$KdR_71+XRpMuYb3*OVk+7rdvt`)k31Bw&DwLLyLphmJgG>znE_uS8MIo)%d zN{X0@UAJOpe}DXWnf|8kJLvuncmJ|Fp}Hh_VWT!-MLw7dkaqzdOjyn$)XAr^Zv zNL7N2&bk6u0MzQbNyvTruYGnJ^(^g3gT5*-cHtSGQW`i2JEg;lJ%Ei%MA$5DI)soZ- z`CrxstXrm2T31XwG2OB0tr+*FmT_fa^=(BUL|hZ9sEK}26a9Hjq_vj+mlAV>cgxs)8@8&im zoS+m5DZOkn=dG80>MyIg$AzWPQ*hN6F7~Lma1pnugIg3^!5oxA#U>8lCBW2G-?YtF zeT#?k=4F~#_Pzk$qH`%|+5$?vFsv1ph3PVqifI@2-XTYU>4OJ_LueSRRP2G%3sIj7 zZ6QbGsGqzq9ua-?uqf>(Z{ucoz)~f@gh+AKepb36UxPOAhz_F+F(4at#0t3ni}C>w z?%GOygMs1!_p3uhxOl8_^bq#qy~ZULm0CiGgZwDF|0(~{+z_G!hL$6FX~?07@c~1g z5=gS|qVH_+g|?M^()Jbf27JlcPtKpMr9bps=d^}414dLiOlZ3QUhI^n;$;>yAQi1mxi*~U!t}Nj~VB=SHUej@`iXGg*qs=2>#a=h@yQr zN^<$a#LJtUlVxK73uU;5yM|+p)_z$EFdsJ$cjz($kbaRHJp&=16@kB01dkt*!dM1x zIgXb;4&tCh)Jw)aQ%r^S0NWvPVw~)CdFD$3FADA1t0Vc-EogJ*$xXr`0*l`OC?IrX zv2M*pkKA21gucViT@QY+%foM&?dI7Bf5Xhu4}Vwt82_V$Qvd(} diff --git a/common.c3 b/common.c3 index fb4c2f1..be45b31 100644 --- a/common.c3 +++ b/common.c3 @@ -41,21 +41,56 @@ struct Message @packed { /// Scene ////////////////////////////// -fn bool scene_get_tile(bool *scene, int scene_width, int scene_height, Vector2 p) { +struct Scene { + usz width; + usz height; + bool[*] walls; +} + +fn Scene *allocate_scene(usz width, usz height) { + Scene *scene = mem::calloc(Scene.sizeof + bool.sizeof*width*height); + scene.width = width; + scene.height = height; + for (usz i = 0; i < width*height; ++i) scene.walls[i] = false; + return scene; +} + +fn Scene *allocate_default_scene() @extern("allocate_default_scene") @wasm { + bool[*][*] default_walls = { + { false, false, true, true, true, false, false}, + { false, false, false, false, false, true, false}, + { true, false, false, false, false, true, false}, + { true, false, false, false, false, true, false}, + { true, false, false, false, false, false, false}, + { false, true, true, true, false, false, false}, + { false, false, false, false, false, false, false}, + }; + usz width = default_walls[0].len; + usz height = default_walls.len; + Scene *scene = allocate_scene(width, height); + for (usz y = 0; y < height; ++y) { + for (usz x = 0; x < width; ++x) { + scene.walls[y*width + x] = default_walls[y][x]; + } + } + return scene; +} + +fn bool scene_get_tile(Scene *scene, Vector2 p) { int x = (int)math::floor(p.x); int y = (int)math::floor(p.y); - if (!(0 <= x && x < scene_width && 0 <= y && y < scene_height)) return false; - return scene[y*scene_width + x]; + if (!(0 <= x && x < scene.width && 0 <= y && y < scene.height)) return false; + return scene.walls[y*scene.width + x]; } -fn bool scene_can_rectangle_fit_here(bool *scene, int scene_width, int scene_height, float px, float py, float sx, float sy) @extern("scene_can_rectangle_fit_here") @wasm { +fn bool scene_can_rectangle_fit_here(Scene *scene, float px, float py, float sx, float sy) @extern("scene_can_rectangle_fit_here") @wasm { int x1 = (int)math::floor(px - sx*0.5f); int x2 = (int)math::floor(px + sx*0.5f); int y1 = (int)math::floor(py - sy*0.5f); int y2 = (int)math::floor(py + sy*0.5f); for (int x = x1; x <= x2; ++x) { for (int y = y1; y <= y2; ++y) { - if (scene_get_tile(scene, scene_width, scene_height, {x, y})) { + if (scene_get_tile(scene, {x, y})) { return false; } } @@ -63,12 +98,6 @@ fn bool scene_can_rectangle_fit_here(bool *scene, int scene_width, int scene_hei return true; } -fn bool *allocate_scene(usz width, usz height) @extern("allocate_scene") @wasm { - bool[] scene = mem::new_array(bool, width*height); - foreach (&c: scene) *c = false; - return &scene[0]; -} - /// Items ////////////////////////////// enum ItemKind: char { @@ -205,14 +234,14 @@ fn int throw_bomb(float player_position_x, float player_position_y, float player return -1; } -fn bool update_bomb(Bomb *bomb, bool* scene, int scene_width, int scene_height, float delta_time) { +fn bool update_bomb(Bomb *bomb, Scene* scene, float delta_time) { bool collided = false; bomb.lifetime -= delta_time; bomb.velocity_z -= BOMB_GRAVITY*delta_time; float nx = bomb.position.x + bomb.velocity.x*delta_time; float ny = bomb.position.y + bomb.velocity.y*delta_time; - if (scene_get_tile(scene, scene_width, scene_height, {nx, ny})) { + if (scene_get_tile(scene, {nx, ny})) { float dx = math::abs(math::floor(bomb.position.x) - math::floor(nx)); float dy = math::abs(math::floor(bomb.position.y) - math::floor(ny)); diff --git a/common.mjs b/common.mjs index 9fecc65..0d977af 100644 --- a/common.mjs +++ b/common.mjs @@ -251,48 +251,22 @@ export function makeWasmCommon(wasm) { wasm, memory: wasm.instance.exports.memory, _initialize: wasm.instance.exports._initialize, - allocate_scene: wasm.instance.exports.allocate_scene, 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, throw_bomb: wasm.instance.exports.throw_bomb, scene_can_rectangle_fit_here: wasm.instance.exports.scene_can_rectangle_fit_here, + allocate_default_scene: wasm.instance.exports.allocate_default_scene, }; } -export function createScene(walls, wasmCommon) { - const scene = { - height: walls.length, - width: Number.MIN_VALUE, - wallsPtr: 0, - }; - for (let row of walls) { - scene.width = Math.max(scene.width, row.length); - } - scene.wallsPtr = wasmCommon.allocate_scene(scene.width, scene.height); - const wallsData = new Uint8ClampedArray(wasmCommon.memory.buffer, scene.wallsPtr, scene.width * scene.height); - for (let y = 0; y < walls.length; ++y) { - for (let x = 0; x < walls[y].length; ++x) { - wallsData[y * scene.width + x] = Number(walls[y][x]); - } - } - return scene; -} export function createLevel(wasmCommon) { - const scene = createScene([ - [false, false, true, true, true, false, false], - [false, false, false, false, false, true, false], - [true, false, false, false, false, true, false], - [true, false, false, false, false, true, false], - [true], - [false, true, true, true, false, false, false], - [false, false, false, false, false, false, false], - ], wasmCommon); + const scenePtr = wasmCommon.allocate_default_scene(); const itemsPtr = wasmCommon.allocate_items(); const bombsPtr = wasmCommon.allocate_bombs(); - return { scene, itemsPtr, bombsPtr }; + return { scenePtr, itemsPtr, bombsPtr }; } -export function updatePlayer(wasmCommon, player, scene, deltaTime) { +export function updatePlayer(wasmCommon, player, scenePtr, deltaTime) { const controlVelocity = new Vector2(); let angularVelocity = 0.0; if ((player.moving >> Moving.MovingForward) & 1) { @@ -309,11 +283,11 @@ export function updatePlayer(wasmCommon, player, scene, deltaTime) { } player.direction = player.direction + angularVelocity * deltaTime; const nx = player.position.x + controlVelocity.x * deltaTime; - if (wasmCommon.scene_can_rectangle_fit_here(scene.wallsPtr, scene.width, scene.height, nx, player.position.y, PLAYER_SIZE, PLAYER_SIZE)) { + if (wasmCommon.scene_can_rectangle_fit_here(scenePtr, nx, player.position.y, PLAYER_SIZE, PLAYER_SIZE)) { player.position.x = nx; } const ny = player.position.y + controlVelocity.y * deltaTime; - if (wasmCommon.scene_can_rectangle_fit_here(scene.wallsPtr, scene.width, scene.height, player.position.x, ny, PLAYER_SIZE, PLAYER_SIZE)) { + if (wasmCommon.scene_can_rectangle_fit_here(scenePtr, player.position.x, ny, PLAYER_SIZE, PLAYER_SIZE)) { player.position.y = ny; } } diff --git a/common.mts b/common.mts index 27c3e64..4fd6424 100644 --- a/common.mts +++ b/common.mts @@ -284,23 +284,17 @@ export function clamp(value: number, min: number, max: number) { return Math.min(Math.max(value, min), max); } -export interface Scene { - wallsPtr: number; - width: number; - height: number; -} - export interface WasmCommon { wasm: WebAssembly.WebAssemblyInstantiatedSource, memory: WebAssembly.Memory, _initialize: () => void, - allocate_scene: (width: number, height: number) => number, allocate_items: () => number, reset_temp_mark: () => void, allocate_temporary_buffer: (size: number) => number, allocate_bombs: () => number, throw_bomb: (player_position_x: number, player_position_y: number, player_direction: number, bombs: number) => number, - scene_can_rectangle_fit_here: (scene: number, scene_width: number, scene_height: number, px: number, py: number, sx: number, sy: number) => boolean, + scene_can_rectangle_fit_here: (scene: number, px: number, py: number, sx: number, sy: number) => boolean, + allocate_default_scene: () => number, } export function makeWasmCommon(wasm: WebAssembly.WebAssemblyInstantiatedSource): WasmCommon { @@ -308,60 +302,32 @@ export function makeWasmCommon(wasm: WebAssembly.WebAssemblyInstantiatedSource): wasm, memory: wasm.instance.exports.memory as WebAssembly.Memory, _initialize: wasm.instance.exports._initialize as () => void, - allocate_scene: wasm.instance.exports.allocate_scene as (width: number, height: number) => number, 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, throw_bomb: wasm.instance.exports.throw_bomb as (player_position_x: number, player_position_y: number, player_direction: number, bombs: number) => number, - scene_can_rectangle_fit_here: wasm.instance.exports.scene_can_rectangle_fit_here as (scene: number, scene_width: number, scene_height: number, px: number, py: number, sx: number, sy: number) => boolean, + scene_can_rectangle_fit_here: wasm.instance.exports.scene_can_rectangle_fit_here as (scenePtr: number, px: number, py: number, sx: number, sy: number) => boolean, + allocate_default_scene: wasm.instance.exports.allocate_default_scene as () => number, } } -export function createScene(walls: Array>, wasmCommon: WasmCommon): Scene { - const scene: Scene = { - height: walls.length, - width: Number.MIN_VALUE, - wallsPtr: 0, - }; - for (let row of walls) { - scene.width = Math.max(scene.width, row.length); - } - scene.wallsPtr = wasmCommon.allocate_scene(scene.width, scene.height); - const wallsData = new Uint8ClampedArray(wasmCommon.memory.buffer, scene.wallsPtr, scene.width*scene.height); - for (let y = 0; y < walls.length; ++y) { - for (let x = 0; x < walls[y].length; ++x) { - wallsData[y*scene.width + x] = Number(walls[y][x]); - } - } - return scene; -} - // NOTE: This is basically the part of the state of the Game that is shared // between Client and Server and constantly synced over the network. export interface Level { - scene: Scene, + scenePtr: number, itemsPtr: number, bombsPtr: number, } export function createLevel(wasmCommon: WasmCommon): Level { - const scene = createScene([ - [ false, false, true, true, true, false, false], - [ false, false, false, false, false, true, false], - [ true, false, false, false, false, true, false], - [ true, false, false, false, false, true, false], - [ true], - [ false, true, true, true, false, false, false], - [ false, false, false, false, false, false, false], - ], wasmCommon); - + const scenePtr = wasmCommon.allocate_default_scene(); const itemsPtr = wasmCommon.allocate_items(); const bombsPtr = wasmCommon.allocate_bombs(); - return {scene, itemsPtr, bombsPtr}; + return {scenePtr, itemsPtr, bombsPtr}; } -export function updatePlayer(wasmCommon: WasmCommon, player: Player, scene: Scene, deltaTime: number) { +export function updatePlayer(wasmCommon: WasmCommon, player: Player, scenePtr: number, deltaTime: number) { const controlVelocity = new Vector2(); let angularVelocity = 0.0; if ((player.moving>>Moving.MovingForward)&1) { @@ -379,11 +345,11 @@ export function updatePlayer(wasmCommon: WasmCommon, player: Player, scene: Scen player.direction = player.direction + angularVelocity*deltaTime; const nx = player.position.x + controlVelocity.x*deltaTime; - if (wasmCommon.scene_can_rectangle_fit_here(scene.wallsPtr, scene.width, scene.height, nx, player.position.y, PLAYER_SIZE, PLAYER_SIZE)) { + if (wasmCommon.scene_can_rectangle_fit_here(scenePtr, nx, player.position.y, PLAYER_SIZE, PLAYER_SIZE)) { player.position.x = nx; } const ny = player.position.y + controlVelocity.y*deltaTime; - if (wasmCommon.scene_can_rectangle_fit_here(scene.wallsPtr, scene.width, scene.height, player.position.x, ny, PLAYER_SIZE, PLAYER_SIZE)) { + if (wasmCommon.scene_can_rectangle_fit_here(scenePtr, player.position.x, ny, PLAYER_SIZE, PLAYER_SIZE)) { player.position.y = ny; } } diff --git a/server.c3 b/server.c3 index 3f57bad..2720fc3 100644 --- a/server.c3 +++ b/server.c3 @@ -259,10 +259,10 @@ fn ExplodedBombs *allocate_exploded_bombs() @extern("allocate_exploded_bombs") @ return mem::new(ExplodedBombs); } -fn void update_bombs_on_server_side(bool* scene, int scene_width, int scene_height, float delta_time, ExplodedBombs *exploded_bombs, Bombs *bombs) @extern("update_bombs_on_server_side") @wasm { +fn void update_bombs_on_server_side(Scene *scene, float delta_time, ExplodedBombs *exploded_bombs, Bombs *bombs) @extern("update_bombs_on_server_side") @wasm { foreach (bombIndex, &bomb: *bombs) { if (bomb.lifetime > 0) { - common::update_bomb(bomb, scene, scene_width, scene_height, delta_time); + common::update_bomb(bomb, scene, delta_time); if (bomb.lifetime <= 0) { exploded_bombs.push(bombIndex); } diff --git a/server.mjs b/server.mjs index 9f4dd86..c5a3fb0 100644 --- a/server.mjs +++ b/server.mjs @@ -270,7 +270,7 @@ function tick() { } { players.forEach((player) => { - common.updatePlayer(wasmServer, player, level.scene, deltaTime); + common.updatePlayer(wasmServer, player, level.scenePtr, deltaTime); wasmServer.collect_items_by_player_at(player.position.x, player.position.y, collectedItemsPtr, level.itemsPtr); }); const bufferItemsCollected = (() => { @@ -287,7 +287,7 @@ function tick() { messageSentCounter += 1; }); } - wasmServer.update_bombs_on_server_side(level.scene.wallsPtr, level.scene.width, level.scene.height, deltaTime, explodedBombsPtr, level.bombsPtr); + wasmServer.update_bombs_on_server_side(level.scenePtr, deltaTime, explodedBombsPtr, level.bombsPtr); const bufferBombsExploded = (() => { const message = wasmServer.exploded_bombs_as_batch_message(explodedBombsPtr, level.bombsPtr); if (message === 0) diff --git a/server.mts b/server.mts index 9be2bd7..fb7f78b 100644 --- a/server.mts +++ b/server.mts @@ -317,7 +317,7 @@ function tick() { // Simulating the world for one server tick. { players.forEach((player) => { - common.updatePlayer(wasmServer, player, level.scene, deltaTime); + common.updatePlayer(wasmServer, player, level.scenePtr, deltaTime); wasmServer.collect_items_by_player_at(player.position.x, player.position.y, collectedItemsPtr, level.itemsPtr); }); @@ -336,7 +336,7 @@ function tick() { }); } - wasmServer.update_bombs_on_server_side(level.scene.wallsPtr, level.scene.width, level.scene.height, deltaTime, explodedBombsPtr, level.bombsPtr); + wasmServer.update_bombs_on_server_side(level.scenePtr, deltaTime, explodedBombsPtr, level.bombsPtr); const bufferBombsExploded = (() => { const message = wasmServer.exploded_bombs_as_batch_message(explodedBombsPtr, level.bombsPtr); if (message === 0) return null; @@ -402,7 +402,7 @@ interface WasmServer extends common.WasmCommon { throw_bomb_on_server_side: (player_position_x: number, player_position_y: number, player_direction: number, bombs: number, thrown_bombs: number) => number, thrown_bombs_as_batch_message: (bombs: number, thrown_bombs: number) => number, allocate_exploded_bombs: () => number, - update_bombs_on_server_side: (scene: number, scene_width: number, scene_height: number, delta_time: number, exploded_bombs: number, bombs: number) => void, + update_bombs_on_server_side: (scene: number, delta_time: number, exploded_bombs: number, bombs: number) => void, exploded_bombs_as_batch_message: (exploded_bombs: number, bombs: number) => number, } @@ -437,7 +437,7 @@ async function instantiateWasmServer(path: string): Promise { throw_bomb_on_server_side: wasm.instance.exports.throw_bomb_on_server_side as (player_position_x: number, player_position_y: number, player_direction: number, bombs: number, thrown_bombs: number) => number, thrown_bombs_as_batch_message: wasm.instance.exports.thrown_bombs_as_batch_message as (bombs: number, thrown_bombs: number) => number, allocate_exploded_bombs: wasm.instance.exports.allocate_exploded_bombs as () => number, - update_bombs_on_server_side: wasm.instance.exports.update_bombs_on_server_side as (scene: number, scene_width: number, scene_height: number, delta_time: number, exploded_bombs: number, bombs: number) => void, + update_bombs_on_server_side: wasm.instance.exports.update_bombs_on_server_side as (scene: number, delta_time: number, exploded_bombs: number, bombs: number) => void, exploded_bombs_as_batch_message: wasm.instance.exports.exploded_bombs_as_batch_message as (exploded_bombs: number, bombs: number) => number }; } diff --git a/server.wasm b/server.wasm index 8ef422289929e6bfe3519bf95578349edd79983e..3ca0292d70cff73c1bb917db3fd85227653806b7 100755 GIT binary patch delta 1528 zcmaJ>&x>3|5U%R?X6F6eonN~<*|9eUxDje?9mZH?cFn(eHn_xv|w-ztVee z<9dH<{n}>lY7h2*XyfuI2p))?KMo!SSDvxIgeh9w4z1W0{6-t_JLHEzM@EK(=Kx6W z$%+P9<);>OMVA7q8WriMjEbKXpb{nltmi;6C4?iDz9fJm-7zYoj2ToJsf01%oFm;f zDtCS}mT-^vp;5DLU*A=Yp=#c8a&KHZ&w9FX)>@7!g>-~keM7N%4nX5a zE~)yEnj_g@kNF{WM*i^cFy5xQ&-joWi5exC(~JixqvYI=ch}DB`ASeuTmlOP&`0@u3(Wyv%+$`sdENm%TSH?Db?5@YgE-_RQ2G-seVO# zBSxQ}p0j^`#r|AY*G#B+C$?lT%9!fQ&C7i&qQ0Kw#xPbJY~+pDn|y+>$pD@Vfs@ba67jCb^}*_?va3S zcc>~;{ahK#>ZG5mML#12qoKkn8jfYHq*+-jc;TTel3>|jiXV(+RPtrSmr2zNGWN*` zp5=e3Clhv(YLu?s;~e-uq38nljsJ3F?mHoDmug=>nciblNr@Bi(PC}a*qwo5K68pM z=E$8K8#&`(FWIH^{}o+2kEYy;(1`%e@C?NL$qZ|lO^LHNil>aDWx~uj3`1k&sO`OB;Zol8ep7^h?^U?jgZCtwjR~wH- N;>8#CmzVJEe*mSd8SMZ7 delta 1311 zcma)6&1(}u6rYbxvb(dBZqvkmXv>??LoBEuB6_hMN>OU9Cr?sqskyAN(pJ<<81G83 zO~s3Mg9kwiEr_6hfZjX^9=v!GZ&mQ+ifqP4bQR$v$R1i$EVi-K1B$aO_eu974Yx#Nj3>NC?q;C2NE5QGol3s z5wai&gdl*obaaXl2+UBTB}j~bd`k=TF$WGzInhOj>jS=trkCh=+@jsTp-W9YaNp5g zoS#O%fwn3~RG54CyQ|8gLl7sr;t2Mo1nxArp@*7!xCc7iR$&(!X3)@(bE!-^hmUe* zm4VGkO8^}}>j{L(5kd@IaRU0AFwjyZls(vl;Z|DcW%O5I-vcZNS)sJ&EJ9!E1PNm5 z{kz$T&9rJLf-5)kxXx6-so%3vB?K0R+*J&vj5e(lo9PIQ69;@f4 z&*vDUJd-}F%)>=g98+_*AtbecR4?Gg$90T6i$Z{zD8VJ7h8k=M2KJ})WCU21MT&zX z2acCCe&{reI82;mDu8c|o*Mi#xLfg-7jM)^B-U!Hw`;3M@3=4B_RjeMT4VSr8|~Zw U`=@N@ONAct+40kl!!dg47nm6Og#Z8m