medfall

A super great game engine
Log | Files | Refs

commit 8d9917332526c3f791c64c5b4354f3d6689226d0
parent 9b71f78c33d4ed0f7347eb7a532aea695bd3dec9
Author: Michael Savage <mikejsavage@gmail.com>
Date:   Sat,  4 Nov 2017 19:48:46 +0200

Add networking, fireballs, and semi broken walking to the clipmap engine

Diffstat:
clipmap.cc | 418+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
make.lua | 2+-
2 files changed, 413 insertions(+), 7 deletions(-)

diff --git a/clipmap.cc b/clipmap.cc @@ -8,6 +8,11 @@ #include "decompress_bc.h" #include "gl.h" #include "skybox.h" +#include "stream.h" +#include "hashtable.h" +#include "pool.h" +#include "profiler.h" +#include "platform_network.h" #include "libs/lz4/lz4.h" @@ -20,8 +25,37 @@ struct ClipmapTerrain { HeightmapQuadTree quadtree; }; +static const float EYE_HEIGHT = 1.8f; + static ClipmapTerrain clipmap; +struct Fireball { + v3 pos; + v3 velocity; +}; + +struct Explosion { + v3 pos; + double created_at; +}; + +struct Player { + u64 sid; + v3 pos; +}; + +static UDPSocket sock; +static NetAddress server_addr; +static u64 sid; +static double last_connection_attempt = -5.0; // TODO +static bool connected = false; + +static Pool< Fireball, 1024 > fireballs; +static Pool< Explosion, 1024 > explosions; + +static Pool< Player, 1024 > players; +static HashTable< Player *, 1024 * 2 > sid_to_player; + static constexpr u32 PATCH_RESOLUTION = 48; static constexpr u32 NUM_LODS = 7; @@ -163,6 +197,13 @@ GAME_INIT( game_init ) { game->sun_angle = 0.3f; skybox_init( &game->skybox ); + + sock = net_new_udp( NET_NONBLOCKING ); + + if( !dns_first( "localhost", &server_addr ) ) { + FATAL( "dns_first" ); + } + server_addr.port = 13337; } static void draw_qt( MinMaxu32 aabb, const array< HeightmapQuadTreeNode > nodes, size_t node_idx ) { @@ -180,6 +221,30 @@ static void draw_qt( MinMaxu32 aabb, const array< HeightmapQuadTreeNode > nodes, } } +static bool segment_vs_quadtree( const HeightmapQuadTree * qt, const v3 & start, const v3 & end, float * t, v3 * xnormal ) { + float dont_care_t; + v3 dont_care_normal; + if( t == NULL ) + t = &dont_care_t; + if( xnormal == NULL ) + xnormal = &dont_care_normal; + + *t = 1.0f; + + if( start == end ) return false; + Ray3 ray( start, end - start ); + float local_t; + v3 local_normal; + bool hit = ray_vs_quadtree( qt, ray, &local_t, &local_normal ); + if( !hit || local_t > 1.0f ) + return false; + + *t = local_t; + *xnormal = local_normal; + + return true; +} + GAME_FRAME( game_frame ) { float fb = float( input->keys[ KEY_W ] - input->keys[ KEY_S ] ); float lr = float( input->keys[ KEY_D ] - input->keys[ KEY_A ] ); @@ -195,6 +260,10 @@ GAME_FRAME( game_frame ) { game->draw_wireframe = !game->draw_wireframe; if( input->keys[ KEY_T ] && input->key_edges[ KEY_T ] ) game->draw_quadtree = !game->draw_quadtree; + if( input->keys[ KEY_N ] && input->key_edges[ KEY_N ] ) { + game->noclip = !game->noclip; + game->velocity = v3( 0 ); + } dpitch -= float( input->mouse_dy * 0.25 ); dyaw -= float( input->mouse_dx * 0.25 ); @@ -210,13 +279,156 @@ GAME_FRAME( game_frame ) { v3 right = normalize( cross( forward, world_up ) ); v3 up = normalize( cross( right, forward ) ); - const float speed = 100.0f; - game->pos += forward * dt * fb * speed; - game->pos += right * dt * lr * speed; - game->pos.z += dt * dz * speed; + if( game->noclip ) { + const float speed = 100.0f; + game->pos += forward * dt * fb * speed; + game->pos += right * dt * lr * speed; + game->pos.z += dt * dz * speed; + } + else { + if( game->on_ground ) { + if( input->keys[ KEY_SPACE ] ) { + game->velocity.z = 9.8; + game->on_ground = false; + } + } + + const float acceleration = 12; + if( game->on_ground ) { + // apply friction + if( dot( game->velocity, game->velocity ) < 0.01 ) { + game->velocity.x = 0; + game->velocity.y = 0; + } + else { + float current_speed = length( game->velocity ); + float drop = max( acceleration, current_speed ) * acceleration * dt / 2.0f; + float new_speed = max( 0.0f, current_speed - drop ); + game->velocity = new_speed * ( game->velocity / current_speed ); + } + + if( fb != 0 || lr != 0 ) { + // apply acceleration + float max_speed = 10; + + v3 forward_xy = v3_forward_xy( game->yaw ); + v3 right_xy = normalize( cross( forward_xy, world_up ) ); + v3 desired_direction = fb * forward_xy + lr * right_xy; + + desired_direction = normalize( desired_direction ); + + float speed_in_desired_direction = dot( game->velocity, desired_direction ); + + if( speed_in_desired_direction < max_speed ) { + float accel_speed = min( acceleration * dt * max_speed, max_speed - speed_in_desired_direction ); + game->velocity += accel_speed * desired_direction; + } + } + } + + int spins = 0; + float remaining_t = dt; + while( remaining_t > 0 && spins < 5 ) { + spins++; + if( game->on_ground ) { + const float step_height = 0.1f; + + v3 low_start = game->pos + v3( 2048, 2048, 0 ); + v3 low_end = low_start + game->velocity * remaining_t; + + v3 high_start = game->pos + v3( 2048, 2048, 0 ) + v3( 0, 0, step_height ); + v3 high_end = high_start + game->velocity * remaining_t; + + float low_t; + // initialise high_t to -1 so we never pick the high trace + // if we couldn't step up (fails the high_t > low_t test) + float high_t = -1; + v3 low_normal, high_normal; + + bool low_hit = segment_vs_quadtree( &clipmap.quadtree, low_start, low_end, &low_t, &low_normal ); + bool high_hit = false; + + bool step_up_hit = segment_vs_quadtree( &clipmap.quadtree, low_start, high_start, NULL, NULL ); + if( !step_up_hit ) { + high_hit = segment_vs_quadtree( &clipmap.quadtree, high_start, high_end, &high_t, &high_normal ); + } + + v3 step_down_start; + float step_down_distance; + bool should_step_down = false; + + if( high_t > low_t ) { + if( high_hit ) { + game->pos = high_start + game->velocity * high_t * remaining_t - v3( 2048, 2048, 0 ); + remaining_t = ( 1.0f - high_t ) * remaining_t; + } + else { + step_down_start = high_end; + step_down_distance = step_height * 2; + should_step_down = true; + remaining_t = 0; + } + } + else { + if( low_hit ) { + game->pos = low_start + game->velocity * low_t * remaining_t - v3( 2048, 2048, 0 ); + remaining_t = ( 1.0f - low_t ) * remaining_t; + } + else { + step_down_start = low_end; + step_down_distance = step_height; + should_step_down = true; + remaining_t = 0; + } + } + + if( should_step_down ) { + float step_down_t; + v3 step_down_end = step_down_start - v3( 0, 0, step_down_distance ); + + bool step_down_hit = segment_vs_quadtree( &clipmap.quadtree, step_down_start, step_down_end, &step_down_t, NULL ); + + game->pos = lerp( step_down_start, step_down_t, step_down_end ) - v3( 2048, 2048, 0 ); + game->on_ground = step_down_hit; + } + } + + // TODO: this might be quite harmful + if( !game->on_ground && remaining_t > 0 ) { + // TODO: rk4 integration + const float gravity = -30.0f; + game->velocity += v3( 0, 0, gravity ) * remaining_t; + + v3 start = game->pos + v3( 2048, 2048, 0 ); + v3 end = start + game->velocity * remaining_t; + float t; + v3 normal; + bool hit = segment_vs_quadtree( &clipmap.quadtree, start, end, &t, &normal ); + if( hit ) { + // TODO: check normal is flat enough, otherwise stay in the air + game->on_ground = true; + game->pos += game->velocity * t * remaining_t; + // TODO: truncate velocity + game->velocity.z = 0; + + remaining_t = ( 1.0f - t ) * remaining_t; + } + else { + game->pos = end - v3( 2048, 2048, 0 ); + remaining_t = 0; + } + } + + if( game->on_ground ) { + // move up 1mm so float imprecision doesn't make us fall through the world + game->pos.z += 0.001f; + } + } + } + v3 eye_pos = game->pos + v3( 0, 0, EYE_HEIGHT ); m4 P = m4_perspective( VERTICAL_FOV, get_aspect_ratio(), NEAR_PLANE_DEPTH, FAR_PLANE_DEPTH ); - m4 V = m4_view( forward, right, up, game->pos ); + m4 V = m4_view( forward, right, up, eye_pos ); m4 Vsky = m4_view( forward, right, up, v3( 0 ) ); v3 sun_dir = v3( -cosf( game->sun_angle ), 0, sinf( game->sun_angle ) ); @@ -267,6 +479,199 @@ GAME_FRAME( game_frame ) { immediate_arrow( impact, normal, 16, v4( 0, 1, 1, 1 ) ); } + RenderState trace_render_state; + trace_render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS ); + trace_render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms; + trace_render_state.wireframe = break3; + immediate_render( trace_render_state ); + } + + { + const float aspect = get_aspect_ratio(); + const float crosshair_thickness = 0.0025f; + const float crosshair_length = 0.01f; + + const v4 red( 1, 0, 0, 1 ); + immediate_triangle( + v3( -crosshair_length, crosshair_thickness, 0 ), + v3( -crosshair_length, -crosshair_thickness, 0 ), + v3( crosshair_length, crosshair_thickness, 0 ), + red + ); + immediate_triangle( + v3( crosshair_length, -crosshair_thickness, 0 ), + v3( crosshair_length, crosshair_thickness, 0 ), + v3( -crosshair_length, -crosshair_thickness, 0 ), + red + ); + immediate_triangle( + v3( crosshair_thickness / aspect, crosshair_length * aspect, 0 ), + v3( -crosshair_thickness / aspect, crosshair_length * aspect, 0 ), + v3( crosshair_thickness / aspect, -crosshair_length * aspect, 0 ), + red + ); + immediate_triangle( + v3( -crosshair_thickness / aspect, -crosshair_length * aspect, 0 ), + v3( crosshair_thickness / aspect, -crosshair_length * aspect, 0 ), + v3( -crosshair_thickness / aspect, crosshair_length * aspect, 0 ), + red + ); + RenderState render_state; + render_state.shader = get_shader( SHADER_UI ); + render_state.depth_func = DEPTHFUNC_DISABLED; + immediate_render( render_state ); + } + + if( input->keys[ KEY_F ] ) { + Fireball * fireball = fireballs.acquire(); + fireball->pos = game->pos + v3( 0.0f, 0.0f, EYE_HEIGHT ); + fireball->velocity = 128.0f * forward; + } + + { + { + PROFILE_BLOCK( "Update fireballs" ); + for( size_t i = 0; i < fireballs.elems.n; i++ ) { + Fireball * fireball = &fireballs.elems[ i ]; + Ray3 ray( fireball->pos + v3( 2048.0f, 2048.0f, 0 ), fireball->velocity * dt ); + + float t; + v3 normal; + if( ray_vs_quadtree( &clipmap.quadtree, ray, &t, &normal ) && t <= 1 ) { + Explosion * explosion = explosions.acquire(); + explosion->pos = fireball->pos + t * fireball->velocity; + explosion->created_at = current_time; + + fireballs.release( fireball ); + i--; + } + else { + const float gravity = 9.81f; + fireball->pos += fireball->velocity * dt; + fireball->velocity.z -= dt * gravity; + immediate_sphere( fireball->pos, 4, v4( 1, 0, 0, 1 ), 4 ); + } + } + } + + RenderState fireball_render_state; + fireball_render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS ); + fireball_render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms; + immediate_render( fireball_render_state ); + + for( size_t i = 0; i < explosions.elems.n; i++ ) { + Explosion * explosion = &explosions.elems[ i ]; + float t = float( current_time - explosion->created_at ); + if( t < 0.5f ) { + immediate_sphere( explosion->pos, t * 32.0f, v4( 1, 0.5, 0, 1 ), 4 ); + } + else { + explosions.release( explosion ); + i--; + } + } + + RenderState explosion_render_state; + explosion_render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS ); + explosion_render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms; + immediate_render( explosion_render_state ); + } + + { + char buf[ 1400 ]; + NetAddress recv_addr; + size_t len; + if( !net_tryrecv( sock, buf, sizeof( buf ), &recv_addr, &len ) ) { + len = 0; + } + if( recv_addr != server_addr ) { + len = 0; + } + + ReadStream rs( buf, len ); + + if( connected && len > 0 ) { + char write_buf[ 1400 ]; + WriteStream ws( write_buf ); + write( &ws, sid ); + write( &ws, game->pos ); + + net_send( sock, ws.start, ws.len(), server_addr ); + + u16 player_count = read_u16( &rs ); + ReadStreamCheckpoint rsc = rs.checkpoint(); + + for( u16 i = 0; i < player_count; i++ ) { + read_u64( &rs ); + u8 player_state = read_u8( &rs ); + if( player_state == 0 ) { + v3 pos; + read( &rs, &pos ); + } + } + + if( rs.done() ) { + rs.reset( &rsc ); + for( u16 i = 0; i < player_count; i++ ) { + u64 player_sid = read_u64( &rs ); + u8 player_state = read_u8( &rs ); + v3 pos; + if( player_state == 0 ) { + read( &rs, &pos ); + } + + Player * player = NULL; + if( !sid_to_player.get( player_sid, &player ) && player_state == 0 ) { + player = players.acquire(); + ASSERT( player != NULL ); // TODO + sid_to_player.add( player_sid, player ); + player->sid = player_sid; + } + + if( player_state == 0 ) { + player->pos = pos; + } + else if( player_state == 1 ) { + ggprint( "{08x} disconnected\n", player->sid ); + players.release( player ); + } + } + + ASSERT( rs.done() ); + } + else { + if( len > 0 ) { + FATAL( "bad message from the server" ); + } + } + } + else if( len > 0 ) { + sid = read_u64( &rs ); + if( rs.ok ) { + connected = true; + } + } + + if( !connected && current_time - last_connection_attempt > 1.0 ) { + char write_buf[ 1400 ]; + WriteStream ws( write_buf ); + + write_u64( &ws, ~u64( 0 ) ); + write( &ws, game->pos ); + + net_send( sock, ws.start, ws.len(), server_addr ); + + last_connection_attempt = current_time; + } + + for( const Player & player : players.elems ) { + if( player.sid != sid ) { + v3 mins = player.pos - v3( 0.4f, 0.4f, 0.0f ); + v3 maxs = player.pos + v3( 0.4f, 0.4f, EYE_HEIGHT + 0.1f ); + immediate_aabb( mins, maxs, v4( 1, 1, 0, 1 ) ); + } + } + RenderState impact_render_state; impact_render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS ); impact_render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms; @@ -286,7 +691,8 @@ GAME_FRAME( game_frame ) { } { - const str< 128 > status( "Frame time: {.1}ms FPS: {.1}", dt * 1000.0f, 1.0f / dt ); + const str< 128 > status( "Frame time: {.1}ms FPS: {.1} {}", dt * 1000.0f, 1.0f / dt, + connected ? "connected!" : "connecting" ); draw_text( status.c_str(), 2, 2, 16.0f ); draw_text( str< 128 >( "pos = {.1}", game->pos ).c_str(), 2, 20, 16 ); draw_text( str< 128 >( "drawcalls = {}, verts = {}", renderer_num_draw_calls(), renderer_num_vertices() ).c_str(), 2, 38, 16 ); diff --git a/make.lua b/make.lua @@ -49,7 +49,7 @@ require( "libs/squish" ) bin( "bsp", { "main", "bsp", "bsp_renderer", game_objs }, { game_libs } ) bin( "sm", { "main", "shadow_map", game_objs }, { game_libs } ) -bin( "clipmap", { "main", "clipmap", "heightmap", "skybox", "decompress_bc", game_objs }, { "lz4", game_libs } ) +bin( "clipmap", { "main", "clipmap", "heightmap", "skybox", "decompress_bc", "platform_network", game_objs }, { "lz4", game_libs } ) msvc_bin_ldflags( "bsp", "opengl32.lib gdi32.lib" ) gcc_bin_ldflags( "bsp", game_ldflags )