refactor build system
authorhgn <hgodden00@gmail.com>
Mon, 8 Aug 2022 20:54:32 +0000 (21:54 +0100)
committerhgn <hgodden00@gmail.com>
Mon, 8 Aug 2022 20:54:32 +0000 (21:54 +0100)
blender_graphics_cropper.py [new file with mode: 0644]
build.sh [new file with mode: 0755]
main.c
models/mp_dev.mdl
network.h [new file with mode: 0644]
network_msg.h [new file with mode: 0644]
player.h
road.h
server.c [new file with mode: 0644]

diff --git a/blender_graphics_cropper.py b/blender_graphics_cropper.py
new file mode 100644 (file)
index 0000000..37cd26c
--- /dev/null
@@ -0,0 +1,199 @@
+import bpy, math, gpu, blf
+from mathutils import *
+from gpu_extras.batch import batch_for_shader
+
+bl_info = {
+   "name":"Graphics Cropper",
+   "author": "Harry Godden (hgn)",
+   "version": (0,1),
+   "blender":(3,1,0),
+   "location":"",
+   "descriptin":"",
+   "warning":"",
+   "wiki_url":"",
+   "category":"",
+}
+
+# Clicky clicky GUI
+# ------------------------------------------------------------------------------
+
+cropper_view_draw_handler = None
+cropper_ui_draw_handler = None
+cropper_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
+
+def cropper_draw_ui():
+   mtx = bpy.context.region_data.perspective_matrix
+   w = bpy.context.region.width
+   h = bpy.context.region.height
+
+   for obj in bpy.context.scene.objects:
+      if obj.cropper_data.enabled and obj.visible_get():
+         x = obj.cropper_data.resolution[0]
+         y = obj.cropper_data.resolution[1]
+         c = Vector((1,1,0,1)) if obj.select_get() else Vector((0.6,0.4,0,1))
+         p0 = obj.matrix_world @ Vector((0,0,0))
+         p1 = obj.matrix_world @ Vector((x,0,0))
+         p2 = obj.matrix_world @ Vector((x,y,0))
+         p3 = obj.matrix_world @ Vector((0,y,0))
+
+         co = mtx @ Vector((p3[0],p3[1],p3[2],1.0))
+
+         co[0] /= co[3]
+         co[1] /= co[3]
+         co[0] *= 0.5
+         co[1] *= 0.5
+         co[0] += 0.5
+         co[1] += 0.5
+         co[0] *= w
+         co[1] *= h
+
+         blf.position(0,co[0],co[1]+8,0)
+         blf.size(0,20,48)
+         blf.color(0,c[0],c[1],c[2],c[3])
+         blf.draw(0,obj.cropper_data.filename)
+
+def cropper_draw():
+   global cropper_view_shader
+
+   verts = []
+   colours = []
+
+   for obj in bpy.context.scene.objects:
+      if obj.cropper_data.enabled and obj.visible_get():
+         x = obj.cropper_data.resolution[0]
+         y = obj.cropper_data.resolution[1]
+         c = Vector((1,1,0,1)) if obj.select_get() else Vector((0.6,0.4,0,1))
+         p0 = obj.matrix_world @ Vector((0,0,0))
+         p1 = obj.matrix_world @ Vector((x,0,0))
+         p2 = obj.matrix_world @ Vector((x,y,0))
+         p3 = obj.matrix_world @ Vector((0,y,0))
+
+         verts += [p0,p1,p1,p2,p2,p3,p3,p0]
+         colours += [c,c,c,c,c,c,c,c]
+
+   cropper_view_shader.bind()
+   gpu.state.depth_mask_set(False)
+   gpu.state.line_width_set(2.0)
+   gpu.state.face_culling_set('BACK')
+   gpu.state.depth_test_set('NONE')
+   gpu.state.blend_set('NONE')
+   lines = batch_for_shader( cropper_view_shader, 'LINES', \
+                             { "pos":verts, "color":colours })
+   lines.draw( cropper_view_shader )
+
+# Blender
+# ------------------------------------------------------------------------------
+
+def cropper_render_item(obj,context):
+   cam = context.scene.camera
+   original_pos_x = cam.location[0]
+   original_pos_y = cam.location[1]
+   original_res_x = context.scene.render.resolution_x
+   original_res_y = context.scene.render.resolution_y
+   original_file = context.scene.render.filepath
+   original_scale = cam.data.ortho_scale
+   
+   x = obj.cropper_data.resolution[0]
+   y = obj.cropper_data.resolution[1]
+   fmt = context.scene.render.image_settings.file_format.lower()
+   fn = F"{obj.cropper_data.filename}.{fmt}"
+
+   p0 = obj.matrix_world @ Vector((0,0,0))
+   p1 = obj.matrix_world @ Vector((x,0,0))
+   p2 = obj.matrix_world @ Vector((x,y,0))
+   p3 = obj.matrix_world @ Vector((0,y,0))
+   c = obj.matrix_world @ Vector((x*0.5,y*0.5,0))
+
+   if x > y: s = p2[0]-p0[0]
+   else: s = p2[1]-p0[1]
+   
+   cam.location[0] = c[0]
+   cam.location[1] = c[1]
+
+   cam.data.ortho_scale = s
+   context.scene.render.resolution_x = x
+   context.scene.render.resolution_y = y
+   context.scene.render.filepath = original_file + fn
+   print( F"render to: {context.scene.render.filepath}" )
+   bpy.ops.render.render(write_still=True)
+
+   # Reset
+   context.scene.render.resolution_x = original_res_x
+   context.scene.render.resolution_y = original_res_y
+   context.scene.render.filepath = original_file
+
+   cam.data.ortho_scale = original_scale
+   cam.location[0] = original_pos_x
+   cam.location[1] = original_pos_y
+
+class CROPPER_RENDER_ALL(bpy.types.Operator):
+   bl_idname="cropper.render_all"
+   bl_label="Render all"
+   def execute(_,context):
+      for obj in context.scene.objects:
+         if obj.cropper_data.enabled and obj.visible_get():
+            cropper_render_item(obj,context)
+
+      return {'FINISHED'}
+
+class CROPPER_RENDER(bpy.types.Operator):
+   bl_idname="cropper.render"
+   bl_label="Render"
+   def execute(_,context):
+      obj = context.active_object
+      if obj != None and obj.cropper_data.enabled: 
+         cropper_render_item(obj,context)
+
+      return {'FINISHED'}
+
+class CROPPER_OBJ_SETTINGS(bpy.types.PropertyGroup):
+   enabled: bpy.props.BoolProperty( name="enabled" )
+   filename: bpy.props.StringProperty( name="filename" )
+   resolution: bpy.props.IntVectorProperty( name="resolution",size=2 )
+
+class CROPPER_OBJ_PANEL(bpy.types.Panel):
+   bl_label="Cropper Settings"
+   bl_idname="SCENE_PT_cropper"
+   bl_space_type='PROPERTIES'
+   bl_region_type='WINDOW'
+   bl_context="object"
+
+   def draw(_,context):
+      active_object = context.active_object
+      if active_object == None: return
+      _.layout.prop( active_object.cropper_data, "enabled" )
+
+      box = _.layout.box()
+      if not active_object.cropper_data.enabled:
+         box.enabled = False
+
+      box.prop( active_object.cropper_data, "filename" )
+      box.prop( active_object.cropper_data, "resolution" )
+      box.operator( "cropper.render" )
+      _.layout.operator( "cropper.render_all" )
+
+classes = [ CROPPER_OBJ_SETTINGS, CROPPER_OBJ_PANEL, \
+            CROPPER_RENDER, CROPPER_RENDER_ALL ]
+
+def register():
+   global cropper_view_draw_handler, cropper_ui_draw_handler
+
+   for c in classes:
+      bpy.utils.register_class(c)
+
+   bpy.types.Object.cropper_data = \
+         bpy.props.PointerProperty(type=CROPPER_OBJ_SETTINGS)
+
+   cropper_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
+      cropper_draw,(),'WINDOW','POST_VIEW')
+   cropper_ui_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
+      cropper_draw_ui,(),'WINDOW','POST_PIXEL')
+
+def unregister():
+   global cropper_view_draw_handler, cropper_ui_draw_handler
+
+   for c in classes:
+      bpy.utils.unregister_class(c)
+
+   bpy.types.SpaceView3D.draw_handler_remove(cropper_view_draw_handler,'WINDOW')
+   bpy.types.SpaceView3D.draw_handler_remove(cropper_ui_draw_handler,'WINDOW')
diff --git a/build.sh b/build.sh
new file mode 100755 (executable)
index 0000000..26b2fe1
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,123 @@
+#!/bin/bash
+# Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved
+
+# Compiler Presets
+# ==============================================================================
+
+_linux_compiler="gcc -std=c99 -D_REENTRANT"
+_linux_linkgraphics="-lGL -lglfw3 -lX11 -lXxf86vm -lXrandr -lm -pthread -lXi -ldl"
+_linux_asan="-fsanitize=address"
+_linux_linksteam="-lsteam_api"
+_linux_folder="build.linux"
+_linux_server_folder="build.linux_server"
+
+_windows_compiler="i686-w64-mingw32-gcc"
+_windows_linkgraphics="-lglfw3dll -lopengl32 -lm -mwindows"
+_windows_asan=""
+_windows_linksteam="vg/dep/steam/steam_api.dll"
+_windows_folder="build.win32"
+
+_options_debugmode="-O0 -ggdb3 -fno-omit-frame-pointer"
+_options_release="-O3 -DVG_RELEASE"
+
+# Compiler lines
+# ==============================================================================
+
+_warnings="-Wall -Wno-unused-function -Wno-unused-variable"
+_include="-I. -I./vg/dep -I./vg/src"
+_library="-L. -L./vg/dep/glfw -L./vg/dep/steam"
+_epilogue="-Wl,-rpath=./"
+_ext=""
+
+# Compile scripts
+# ==============================================================================
+
+release(){
+   _linux_options=$_options_release
+   _windows_options=$_options_release
+}
+
+debug(){
+   _linux_options="$_linux_asan $_options_debugmode"
+   _windows_options="$_windows_asan $_options_debugmode"
+}
+debug
+
+compile_miniaudio(){
+   
+   temp_options=$_options
+   _options="-O3"
+
+   _link="-lm"
+   _folder="."
+   _src="-c vg/dep/dr_soft/miniaudio_impl.c"
+   _dst="vg/dep/dr_soft/miniaudio_$1"
+   _ext=".o"
+   compile_x
+
+   _options=$temp_options
+}
+
+game() {
+   _compiler=$_linux_compiler
+   _options=$_linux_options
+
+   compile_miniaudio linux
+
+   # Game tools
+   _folder="$_linux_folder"
+   _ext=""
+   vg_compile_tools
+   
+   # Main build
+   _link="$_linux_linkgraphics $_linux_linksteam"
+   _src="main.c vg/dep/glad/glad.c vg/dep/dr_soft/miniaudio_linux.o"
+   _dst="skaterift"
+   compile_x
+}
+
+game_windows() {
+   _compiler=$_windows_compiler
+   _options=$_windows_options
+
+   compile_miniaudio windows
+
+   # Game tools
+   _folder="$_windows_folder"
+   _ext=".exe"
+   vg_compile_tools
+   
+   # Main build
+   _link="$_windows_linkgraphics $_windows_linksteam"
+   _src="main.c vg/dep/glad/glad.c vg/dep/dr_soft/miniaudio_windows.o"
+   _dst="skaterift"
+   compile_x
+}
+server() {
+   _compiler=$_linux_compiler
+   _options=$_linux_options
+   _link="-lm $_linux_linksteam"
+   _folder="$_linux_server_folder"
+   _src="server.c"
+   _dst="skaterift_server"
+   _ext=""
+
+   compile_x
+}
+
+all() {
+   tools
+   game
+   server
+}
+
+distribution(){
+   release
+   tools
+   game
+   game_windows
+   server
+}
+
+source vg/vg_build.sh
diff --git a/main.c b/main.c
index a2a1f3a8a1151ec18293cbdbac7fb6e1e1dc28b9..a747e6fc2caea0a1edc144c29de32b0538ef3a1d 100644 (file)
--- a/main.c
+++ b/main.c
@@ -166,7 +166,7 @@ void vg_start(void)
 
    if( sv_scene == 0 )
    {
-      character_load( &player.mdl, "ch_outlaw" );
+      character_load( &player.mdl, "ch_default" );
       character_init_ragdoll( &player.mdl );
 
       world_load();
@@ -231,7 +231,7 @@ static void render_main_game(void)
    gpipeline.fov = freecam? 60.0f: 135.0f; /* 120 */
    m4x4_projection( vg_pv, gpipeline.fov, 
          (float)vg_window_x / (float)vg_window_y, 
-         0.1f, 2100.0f );
+         0.02f, 2100.0f );
 
    m4x4_mul( vg_pv, world_4x4, vg_pv );
 
index fc3981a2faef03f0027540408bfd704d162e3b79..4bc73486541d5076e1138e90d1e966771b231ecf 100644 (file)
Binary files a/models/mp_dev.mdl and b/models/mp_dev.mdl differ
diff --git a/network.h b/network.h
new file mode 100644 (file)
index 0000000..f97cfbc
--- /dev/null
+++ b/network.h
@@ -0,0 +1,8 @@
+#ifndef NETWORK_H
+#define NETWORK_H
+
+#include "vg/vg_stdint.h"
+
+
+
+#endif /* NETWORK_H */
diff --git a/network_msg.h b/network_msg.h
new file mode 100644 (file)
index 0000000..b263009
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef NETWORK_MSG_H
+#define NETWORK_MSG_H
+
+#include "vg/vg_stdint.h"
+
+#pragma pack(push,1)
+
+typedef struct netmsg_blank netmsg_blank;
+struct netmsg_blank
+{
+   u32 inetmsg_id;
+};
+enum{ k_inetmsg_blank = 0 };
+
+typedef struct netmsg_scores_request netmsg_scores_request;
+struct netmsg_scores_request
+{
+   u32 inetmsg_id;
+};
+enum{ k_inetmsg_scores_request = 1 };
+
+typedef struct netmsg_scores_info netmsg_scores_info;
+struct netmsg_scores_info
+{
+   u32 inetmsg_id;
+   
+   u32 record_count;
+   struct netmsg_score_record
+   {
+      u32 trackid;
+
+      struct netmsg_score_entry
+      {
+         u64 steamid64;
+         u16 points, time;
+      }
+      top10[10];
+   }
+   scores[];
+};
+enum{ k_inetmsg_scores_info = 2 };
+
+#pragma pack(pop)
+#endif /* NETWORK_MSG_H */
index b05714e4e893209fe2198628700419d72c600c50..0833e57fc681a8923daf1d417cc7a7017e3e97be 100644 (file)
--- a/player.h
+++ b/player.h
@@ -162,10 +162,7 @@ static void player_start_air(void)
    player.in_air = 1;
 
    float pstep = ktimestep*10.0f;
-
-   float best_velocity_mod = 0.0f,
-         best_velocity_delta = -9999.9f;
-
+   float best_velocity_delta = -9999.9f;
    float k_bias = 0.96f;
 
    v3f axis;
@@ -228,7 +225,6 @@ static void player_start_air(void)
             if( (land_delta < 0.0f) && (land_delta > best_velocity_delta) )
             {
                best_velocity_delta = land_delta;
-               best_velocity_mod = vmod;
 
                v3_copy( contact.pos, player.land_target );
                
@@ -1016,6 +1012,7 @@ static void player_walkgrid_iter(struct walkgrid *wg, int iter)
    pa[0] = wg->region[0][0] + (float)wg->cell_id[0] *k_gridscale;
    pa[1] = (wg->region[0][1] + wg->region[1][1]) * 0.5f + k_gridscale;
    pa[2] = wg->region[0][2] + (float)wg->cell_id[1] *k_gridscale;
+#if 0
    pb[0] = pa[0];
    pb[1] = pa[1];
    pb[2] = pa[2] + k_gridscale;
@@ -1025,7 +1022,6 @@ static void player_walkgrid_iter(struct walkgrid *wg, int iter)
    pd[0] = pa[0] + k_gridscale;
    pd[1] = pa[1];
    pd[2] = pa[2];
-#if 0
    /* if you want to draw the current cell */
    vg_line( pa, pb, 0xff00ffff );
    vg_line( pb, pc, 0xff00ffff );
diff --git a/road.h b/road.h
index 29a7a4d20ba58de300ea5927242b717f4e747d9a..bb4f5f6371a527418c17a03f2c6e146db56dc2ed 100644 (file)
--- a/road.h
+++ b/road.h
@@ -160,10 +160,12 @@ void draw_road_patch_dev( road_patch *road )
       v3_sub( next->pos, node->pos, dir );
       v3_normalize( dir );
       
+#if 0
       // Perpendicular vector
       norm[0] = -dir[2];
       norm[1] = 0.f;
       norm[2] = dir[0];
+#endif
       
       v3_muls( node->side,  k_road_width, p2 );
       v3_add( p2, node->pos, p2 );
diff --git a/server.c b/server.c
new file mode 100644 (file)
index 0000000..be19dcb
--- /dev/null
+++ b/server.c
@@ -0,0 +1,222 @@
+// Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
+
+/*
+ * This server application requires steamclient.so to be present in the 
+ * executable directory. This is not provided by vg system, it must be
+ * downloaded via steamcmd. It will likely be somewhere in /usr/.steam/ ...
+ */
+
+#define _DEFAULT_SOURCE
+#include <unistd.h>
+#include <signal.h>
+
+volatile sig_atomic_t sig_stop;
+
+void inthandler( int signum ) 
+{
+   sig_stop = 1;
+}
+
+#define VG_SERVER
+#include "vg/vg.h"
+#include "vg/vg_steam.h"
+#include "vg/vg_steam_networking.h"
+#include "vg/vg_steam_http.h"
+#include "vg/vg_steam_auth.h"
+#include "network_msg.h"
+
+void  *hSteamHTTP,
+      *hSteamNetworkingSockets;
+
+u8 steam_symetric_key[ k_nSteamEncryptedAppTicketSymmetricKeyLen ];
+HSteamNetPollGroup client_pollgroup;
+
+static void recieve_http( void *callresult, void *context )
+{
+   HTTPRequestCompleted_t *result = callresult;
+
+   HTTPRequestHandle request = result->m_hRequest;
+   u32 size = 0;
+
+   SteamAPI_ISteamHTTP_GetHTTPResponseBodySize( hSteamHTTP, request, &size );
+
+   u8 *buffer = malloc( size );
+   SteamAPI_ISteamHTTP_GetHTTPResponseBodyData( 
+         hSteamHTTP, request, buffer, size );
+
+   buffer[size-1] = '\0';
+   vg_info( "%s\n", (char *)buffer );
+
+   free( buffer );
+   SteamAPI_ISteamHTTP_ReleaseHTTPRequest( hSteamHTTP, result->m_hRequest );
+}
+
+static void new_client_connecting( HSteamNetConnection client )
+{
+   EResult accept_status = SteamAPI_ISteamNetworkingSockets_AcceptConnection(
+            hSteamNetworkingSockets, client );
+
+   if( accept_status == k_EResultOK )
+   {
+      vg_success( "Accepted client (id: %u)\n", client );
+      SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
+            hSteamNetworkingSockets,
+            client, client_pollgroup );
+   }
+   else
+   {
+      vg_warn( "Error accepting client (id: %u)\n", client );
+   }
+}
+
+static void handle_steam_callback( CallbackMsg_t *msg )
+{
+   if( msg->m_iCallback == k_iSteamNetConnectionStatusChangedCallBack )
+   {
+      SteamNetConnectionStatusChangedCallback_t *info = (void *)msg->m_pubParam;
+      vg_info( "  Connection status changed for %lu\n", info->m_hConn );
+
+      vg_info( "  %s -> %s\n", 
+            string_ESteamNetworkingConnectionState(info->m_info.m_eState),
+            string_ESteamNetworkingConnectionState(info->m_eOldState) );
+
+      if( info->m_info.m_eState==k_ESteamNetworkingConnectionState_Connecting )
+      {
+         new_client_connecting( info->m_hConn );
+      }
+   }
+   else if( msg->m_iCallback == k_iSteamNetAuthenticationStatus )
+   {
+      SteamNetAuthenticationStatus_t *info = (void *)msg->m_pubParam;
+      vg_info( "  Authentication availibility: %s\n", 
+            string_ESteamNetworkingAvailability(info->m_eAvail) );
+      vg_info( "  %s\n", info->m_debugMsg );
+   }
+}
+
+static void poll_connections(void)
+{
+   SteamNetworkingMessage_t *messages[32];
+   int len;
+
+   while(1)
+   {
+      len = SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(
+            hSteamNetworkingSockets,
+            client_pollgroup, messages, vg_list_size(messages) );
+
+      if( len <= 0 )
+         return;
+
+      for( int i=0; i<len; i++ )
+      {
+         SteamNetworkingMessage_t *msg = messages[i];
+
+         if( msg->m_cbSize < sizeof(netmsg_blank) )
+         {
+            vg_warn( "Discarding message (too small: %d)\n", 
+                  msg->m_cbSize );
+            continue;
+         }
+
+         netmsg_blank *tmp = msg->m_pData;
+         if( tmp->inetmsg_id == k_inetmsg_scores_request )
+         {
+            vg_log( "Recieved score request, sending records. (id: %u)\n", 
+                     msg->m_conn );
+
+            /* Send back current scores */
+            u32 data_size = sizeof(netmsg_scores_info) + 
+                              0*sizeof(struct netmsg_score_record);
+            netmsg_scores_info *return_info = malloc( data_size );
+
+            return_info->inetmsg_id = k_inetmsg_scores_info;
+            return_info->record_count = 0;
+
+            SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
+                  hSteamNetworkingSockets, msg->m_conn, 
+                  return_info, data_size,
+                  k_nSteamNetworkingSend_Reliable, NULL );
+         }
+
+         SteamAPI_SteamNetworkingMessage_t_Release( msg );
+      }
+   }
+}
+
+int main( int argc, char *argv[] )
+{
+   steamworks_ensure_txt( "2103940" );
+
+   signal( SIGINT, inthandler );
+
+   if( !vg_load_steam_symetric_key( "application_key", steam_symetric_key ) )
+      return 0;
+
+   if( !SteamGameServer_Init( 0, 27400, 27401, eServerModeAuthentication,
+                              "1.0.0.0" ) )
+   {
+      vg_error( "SteamGameServer_Init failed\n" );
+      return 0;
+   }
+
+   void *hSteamGameServer = SteamAPI_SteamGameServer();
+   SteamAPI_ISteamGameServer_LogOnAnonymous( hSteamGameServer );
+
+   SteamAPI_ManualDispatch_Init();
+   HSteamPipe hsteampipe = SteamGameServer_GetHSteamPipe();
+
+   //hSteamHTTP = SteamAPI_SteamGameServerHTTP();
+   hSteamNetworkingSockets = 
+      SteamAPI_SteamGameServerNetworkingSockets_SteamAPI();
+
+   /* 
+    * Server code
+    */
+
+   vg_success( "Steamworks API running\n" );
+   steamworks_event_loop( hsteampipe, handle_steam_callback );
+
+   /*
+    * Create a listener
+    */
+
+   HSteamListenSocket listener;
+   SteamNetworkingIPAddr localAddr;
+   SteamAPI_SteamNetworkingIPAddr_Clear( &localAddr );
+   localAddr.m_port = 27402;
+
+   listener = SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
+                  hSteamNetworkingSockets, &localAddr, 0, NULL );
+   client_pollgroup = SteamAPI_ISteamNetworkingSockets_CreatePollGroup(
+         hSteamNetworkingSockets );
+
+#if 0
+   HTTPRequestHandle test_req = SteamAPI_ISteamHTTP_CreateHTTPRequest( 
+         hSteamHTTP, k_EHTTPMethodGET, 
+         "https://www.harrygodden.com/hello.txt" );
+
+   steam_async *call1 = steam_new_async();
+   call1->data = NULL;
+   call1->p_handler = recieve_http;
+   SteamAPI_ISteamHTTP_SendHTTPRequest( hSteamHTTP, test_req, &call1->id );
+#endif
+
+   while( !sig_stop )
+   {
+      steamworks_event_loop( hsteampipe, handle_steam_callback );
+      poll_connections();
+
+      usleep(100000);
+   }
+   
+   SteamAPI_ISteamNetworkingSockets_DestroyPollGroup( hSteamNetworkingSockets,
+         client_pollgroup );
+   SteamAPI_ISteamNetworkingSockets_CloseListenSocket( 
+         hSteamNetworkingSockets, listener );
+   
+   vg_info( "Shutting down\n..." );
+   SteamGameServer_Shutdown();
+
+   return 0;
+}