xash3d-fwgs/engine/common/host.c
Gleb Mazovetskiy 5e0a0765ce Trim all trailing whitespace
The `.editorconfig` file in this repo is configured to trim all trailing
whitespace regardless of whether the line is modified.

Trims all trailing whitespace in the repository to make the codebase easier
to work with in editors that respect `.editorconfig`.

`git blame` becomes less useful on these lines but it already isn't very useful.

Commands:

```
find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
```
2021-01-04 20:55:10 +03:00

1150 lines
26 KiB
C

/*
host.c - dedicated and normal host
Copyright (C) 2007 Uncle Mike
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "build.h"
#ifdef XASH_SDL
#include <SDL.h>
#endif // XASH_SDL
#include <stdarg.h> // va_args
#include <errno.h> // errno
#include <string.h> // strerror
#if !XASH_WIN32
#include <unistd.h> // fork
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif
#if XASH_EMSCRIPTEN
#include <emscripten/emscripten.h>
#endif
#include <errno.h>
#include "common.h"
#include "base_cmd.h"
#include "client.h"
#include "netchan.h"
#include "protocol.h"
#include "mod_local.h"
#include "xash3d_mathlib.h"
#include "input.h"
#include "enginefeatures.h"
#include "render_api.h" // decallist_t
pfnChangeGame pChangeGame = NULL;
host_parm_t host; // host parms
sysinfo_t SI;
CVAR_DEFINE( host_developer, "developer", "0", 0, "engine is in development-mode" );
CVAR_DEFINE_AUTO( sys_ticrate, "100", 0, "framerate in dedicated mode" );
convar_t *host_serverstate;
convar_t *host_gameloaded;
convar_t *host_clientloaded;
convar_t *host_limitlocal;
convar_t *host_maxfps;
convar_t *host_framerate;
convar_t *host_sleeptime;
convar_t *con_gamemaps;
convar_t *build, *ver;
void Sys_PrintUsage( void )
{
const char *usage_str;
#define O(x,y) " "x" "y"\n"
usage_str = ""
#if XASH_MESSAGEBOX == MSGBOX_STDERR
"\n" // dirty hack to not have Xash Error: Usage: on same line
#endif // XASH_MESSAGEBOX == MSGBOX_STDERR
"Usage:\n"
#if !XASH_MOBILE_PLATFORM
#if XASH_WIN32
O("<xash>.exe [options] [+command1] [+command2 arg]","")
#else // XASH_WIN32
O("<xash> [options] [+command1] [+command2 arg]","")
#endif // !XASH_WIN32
#endif // !XASH_MOBILE_PLATFORM
"Options:\n"
O("-dev [level] ","set log verbosity 0-2")
O("-log ","write log to \"engine.log\"")
O("-nowriteconfig ","disable config save")
#if !XASH_WIN32
O("-casesensitive ","disable case-insensitive FS emulation")
#endif // !XASH_WIN32
#if !XASH_MOBILE_PLATFORM
O("-daemonize ","run engine in background, dedicated only")
#endif // !XASH_MOBILE_PLATFORM
#if !XASH_DEDICATED
O("-toconsole ","run engine witn console open")
O("-width <n> ","set window width")
O("-height <n> ","set window height")
O("-oldfont ","enable unused Quake font in Half-Life")
#if !XASH_MOBILE_PLATFORM
O("-fullscreen ","run engine in fullscreen mode")
O("-windowed ","run engine in windowed mode")
O("-dedicated ","run engine in dedicated server mode")
#endif // XASH_MOBILE_PLATFORM
#if XASH_ANDROID
O("-nativeegl ","use native egl implementation. Use if screen does not update or black")
#endif // XASH_ANDROID
#if XASH_WIN32
O("-noavi ","disable AVI support")
O("-nointro ","disable intro video")
#endif // XASH_WIN32
#if XASH_DOS
O("-novesa ","disable vesa")
#endif // XASH_DOS
#if XASH_VIDEO == VIDEO_FBDEV
O("-fbdev <path> ","open selected framebuffer")
O("-ttygfx ","set graphics mode in tty")
O("-doublebuffer ","enable doublebuffering")
#endif // XASH_VIDEO == VIDEO_FBDEV
#if XASH_SOUND == SOUND_ALSA
O("-alsadev <dev> ","open selected ALSA device")
#endif // XASH_SOUND == SOUND_ALSA
O("-nojoy ","disable joystick support")
#ifdef XASH_SDL
O("-sdl_joy_old_api ","use SDL legacy joystick API")
O("-sdl_renderer <n>","use alternative SDL_Renderer for software")
#endif // XASH_SDL
O("-nosound ","disable sound")
O("-noenginemouse ","disable mouse completely")
O("-ref <name> ","use selected renderer dll")
O("-gldebug ","enable OpenGL debug log")
#endif // XASH_DEDICATED
O("-noip ","disable TCP/IP")
O("-noch ","disable crashhandler")
O("-disablehelp ","disable this message")
O("-dll <path> ","override server DLL path")
#ifndef XASH_DEDICATED
O("-clientlib <path>","override client DLL path")
#endif
O("-rodir <path> ","set read-only base directory, experimental")
O("-ip <ip> ","set custom ip")
O("-port <port> ","set custom host port")
O("-clockwindow <cw>","adjust clockwindow")
;
#undef O
Sys_Error( "%s", usage_str );
}
int Host_CompareFileTime( int ft1, int ft2 )
{
if( ft1 < ft2 )
{
return -1;
}
else if( ft1 > ft2 )
{
return 1;
}
return 0;
}
void Host_ShutdownServer( void )
{
SV_Shutdown( "Server was killed\n" );
}
/*
================
Host_PrintEngineFeatures
================
*/
void Host_PrintEngineFeatures( void )
{
if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD ))
Con_Reportf( "^3EXT:^7 big world support enabled\n" );
if( FBitSet( host.features, ENGINE_LOAD_DELUXEDATA ))
Con_Reportf( "^3EXT:^7 deluxemap support enabled\n" );
if( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT ))
Con_Reportf( "^3EXT:^7 Improved MOVETYPE_PUSH is used\n" );
if( FBitSet( host.features, ENGINE_LARGE_LIGHTMAPS ))
Con_Reportf( "^3EXT:^7 Large lightmaps enabled\n" );
if( FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG ))
Con_Reportf( "^3EXT:^7 Compensate quake bug enabled\n" );
}
/*
==============
Host_IsQuakeCompatible
==============
*/
qboolean Host_IsQuakeCompatible( void )
{
// feature set
if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))
return true;
#if !XASH_DEDICATED
// quake demo playing
if( cls.demoplayback == DEMO_QUAKE1 )
return true;
#endif // XASH_DEDICATED
return false;
}
/*
================
Host_EndGame
================
*/
void Host_EndGame( qboolean abort, const char *message, ... )
{
va_list argptr;
static char string[MAX_SYSPATH];
va_start( argptr, message );
Q_vsnprintf( string, sizeof( string ), message, argptr );
va_end( argptr );
Con_Printf( "Host_EndGame: %s\n", string );
SV_Shutdown( "\n" );
#if !XASH_DEDICATED
CL_Disconnect();
// recreate world if needs
CL_ClearEdicts ();
#endif
// release all models
Mod_FreeAll();
if( abort ) Host_AbortCurrentFrame ();
}
/*
================
Host_AbortCurrentFrame
aborts the current host frame and goes on with the next one
================
*/
void Host_AbortCurrentFrame( void )
{
longjmp( host.abortframe, 1 );
}
/*
==================
Host_CheckSleep
==================
*/
void Host_CheckSleep( void )
{
int sleeptime = host_sleeptime->value;
if( Host_IsDedicated() )
{
// let the dedicated server some sleep
Sys_Sleep( sleeptime );
}
else
{
if( host.status == HOST_NOFOCUS )
{
if( SV_Active() && CL_IsInGame( ))
Sys_Sleep( sleeptime ); // listenserver
else Sys_Sleep( 20 ); // sleep 20 ms otherwise
}
else if( host.status == HOST_SLEEP )
{
// completely sleep in minimized state
Sys_Sleep( 20 );
}
else
{
Sys_Sleep( sleeptime );
}
}
}
void Host_NewInstance( const char *name, const char *finalmsg )
{
if( !pChangeGame ) return;
host.change_game = true;
Q_strncpy( host.finalmsg, finalmsg, sizeof( host.finalmsg ));
pChangeGame( name ); // call from hl.exe
}
/*
=================
Host_ChangeGame_f
Change game modification
=================
*/
void Host_ChangeGame_f( void )
{
int i;
if( Cmd_Argc() != 2 )
{
Con_Printf( S_USAGE "game <directory>\n" );
return;
}
// validate gamedir
for( i = 0; i < SI.numgames; i++ )
{
if( !Q_stricmp( SI.games[i]->gamefolder, Cmd_Argv( 1 )))
break;
}
if( i == SI.numgames )
{
Con_Printf( "%s not exist\n", Cmd_Argv( 1 ));
}
else if( !Q_stricmp( GI->gamefolder, Cmd_Argv( 1 )))
{
Con_Printf( "%s already active\n", Cmd_Argv( 1 ));
}
else
{
const char *arg1 = va( "%s%s", (host.type == HOST_NORMAL) ? "" : "#", Cmd_Argv( 1 ));
const char *arg2 = va( "change game to '%s'", SI.games[i]->title );
Host_NewInstance( arg1, arg2 );
}
}
/*
===============
Host_Exec_f
===============
*/
void Host_Exec_f( void )
{
string cfgpath;
byte *f;
char *txt;
fs_offset_t len;
if( Cmd_Argc() != 2 )
{
Con_Printf( S_USAGE "exec <filename>\n" );
return;
}
if( !Q_stricmp( "game.cfg", Cmd_Argv( 1 )))
{
// don't execute game.cfg in singleplayer
if( SV_GetMaxClients() == 1 )
return;
}
Q_strncpy( cfgpath, Cmd_Argv( 1 ), sizeof( cfgpath ));
COM_DefaultExtension( cfgpath, ".cfg" ); // append as default
f = FS_LoadFile( cfgpath, &len, false );
if( !f )
{
Con_Reportf( "couldn't exec %s\n", Cmd_Argv( 1 ));
return;
}
if( !Q_stricmp( "config.cfg", Cmd_Argv( 1 )))
host.config_executed = true;
// adds \n\0 at end of the file
txt = Z_Calloc( len + 2 );
memcpy( txt, f, len );
Q_strncat( txt, "\n", len + 2 );
Mem_Free( f );
if( !host.apply_game_config )
Con_Printf( "execing %s\n", Cmd_Argv( 1 ));
Cbuf_InsertText( txt );
Mem_Free( txt );
}
/*
===============
Host_MemStats_f
===============
*/
void Host_MemStats_f( void )
{
switch( Cmd_Argc( ))
{
case 1:
Mem_PrintList( 1<<30 );
Mem_PrintStats();
break;
case 2:
Mem_PrintList( Q_atoi( Cmd_Argv( 1 )) * 1024 );
Mem_PrintStats();
break;
default:
Con_Printf( S_USAGE "memlist <all>\n" );
break;
}
}
void Host_Minimize_f( void )
{
#ifdef XASH_SDL
if( host.hWnd ) SDL_MinimizeWindow( host.hWnd );
#endif
}
/*
=================
Host_IsLocalGame
singleplayer game detect
=================
*/
qboolean Host_IsLocalGame( void )
{
if( SV_Active( ))
{
return ( SV_GetMaxClients() == 1 ) ? true : false;
}
else
{
return ( CL_GetMaxClients() == 1 ) ? true : false;
}
}
qboolean Host_IsLocalClient( void )
{
// only the local client have the active server
if( CL_Initialized( ) && SV_Initialized( ))
return true;
return false;
}
/*
=================
Host_RegisterDecal
=================
*/
qboolean Host_RegisterDecal( const char *name, int *count )
{
char shortname[MAX_QPATH];
int i;
if( !COM_CheckString( name ))
return 0;
COM_FileBase( name, shortname );
for( i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ )
{
if( !Q_stricmp( host.draw_decals[i], shortname ))
return true;
}
if( i == MAX_DECALS )
{
Con_DPrintf( S_ERROR "MAX_DECALS limit exceeded (%d)\n", MAX_DECALS );
return false;
}
// register new decal
Q_strncpy( host.draw_decals[i], shortname, sizeof( host.draw_decals[i] ));
*count += 1;
return true;
}
/*
=================
Host_InitDecals
=================
*/
void Host_InitDecals( void )
{
int i, num_decals = 0;
search_t *t;
// NOTE: only once resource without which engine can't continue work
if( !FS_FileExists( "gfx/conchars", false ))
Sys_Error( "W_LoadWadFile: couldn't load gfx.wad\n" );
memset( host.draw_decals, 0, sizeof( host.draw_decals ));
// lookup all the decals in decals.wad (basedir, gamedir, falldir)
t = FS_Search( "decals.wad/*.*", true, false );
for( i = 0; t && i < t->numfilenames; i++ )
{
if( !Host_RegisterDecal( t->filenames[i], &num_decals ))
break;
}
if( t ) Mem_Free( t );
Con_Reportf( "InitDecals: %i decals\n", num_decals );
}
/*
===================
Host_GetCommands
Add them exactly as if they had been typed at the console
===================
*/
void Host_GetCommands( void )
{
char *cmd;
while( ( cmd = Sys_Input() ) )
{
Cbuf_AddText( cmd );
Cbuf_Execute();
}
}
/*
===================
Host_CalcFPS
compute actual FPS for various modes
===================
*/
double Host_CalcFPS( void )
{
double fps = 0.0;
if( Host_IsDedicated() )
{
fps = sys_ticrate.value;
}
#if !XASH_DEDICATED
else if( CL_IsPlaybackDemo() || CL_IsRecordDemo( )) // NOTE: we should play demos with same fps as it was recorded
{
fps = CL_GetDemoFramerate();
}
else if( Host_IsLocalGame( ))
{
fps = host_maxfps->value;
}
else
{
fps = host_maxfps->value;
if( fps == 0.0 ) fps = MAX_FPS;
fps = bound( MIN_FPS, fps, MAX_FPS );
}
// probably left part of this condition is redundant :-)
if( host.type != HOST_DEDICATED && Host_IsLocalGame( ) && !CL_IsTimeDemo( ))
{
// ajdust fps for vertical synchronization
if( CVAR_TO_BOOL( gl_vsync ))
{
if( vid_displayfrequency->value != 0.0f )
fps = vid_displayfrequency->value;
else fps = 60.0; // default
}
}
#endif
return fps;
}
/*
===================
Host_FilterTime
Returns false if the time is too short to run a frame
===================
*/
qboolean Host_FilterTime( float time )
{
static double oldtime;
double fps;
host.realtime += time;
fps = Host_CalcFPS( );
// clamp the fps in multiplayer games
if( fps != 0.0 )
{
// limit fps to withing tolerable range
fps = bound( MIN_FPS, fps, MAX_FPS );
if( Host_IsDedicated() )
{
if(( host.realtime - oldtime ) < ( 1.0 / ( fps + 1.0 )))
return false;
}
else
{
if(( host.realtime - oldtime ) < ( 1.0 / fps ))
return false;
}
}
host.frametime = host.realtime - oldtime;
host.realframetime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME );
oldtime = host.realtime;
// NOTE: allow only in singleplayer while demos are not active
if( host_framerate->value > 0.0f && Host_IsLocalGame() && !CL_IsPlaybackDemo() && !CL_IsRecordDemo( ))
host.frametime = bound( MIN_FRAMETIME, host_framerate->value, MAX_FRAMETIME );
else host.frametime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME );
return true;
}
/*
=================
Host_Frame
=================
*/
void Host_Frame( float time )
{
Host_CheckSleep();
// decide the simulation time
if( !Host_FilterTime( time ))
return;
Host_InputFrame (); // input frame
Host_ClientBegin (); // begin client
Host_GetCommands (); // dedicated in
Host_ServerFrame (); // server frame
Host_ClientFrame (); // client frame
HTTP_Run(); // both server and client
host.framecount++;
}
/*
=================
Host_Error
=================
*/
void GAME_EXPORT Host_Error( const char *error, ... )
{
static char hosterror1[MAX_SYSPATH];
static char hosterror2[MAX_SYSPATH];
static qboolean recursive = false;
va_list argptr;
if( host.mouse_visible && !CL_IsInMenu( ))
{
// hide VGUI mouse
#ifdef XASH_SDL
SDL_ShowCursor( 0 );
#endif
host.mouse_visible = false;
}
va_start( argptr, error );
Q_vsprintf( hosterror1, error, argptr );
va_end( argptr );
CL_WriteMessageHistory (); // before Q_error call
if( host.framecount < 3 )
{
Sys_Error( "Host_InitError: %s", hosterror1 );
}
else if( host.framecount == host.errorframe )
{
Sys_Error( "Host_MultiError: %s", hosterror2 );
return;
}
else
{
if( host.allow_console )
{
UI_SetActiveMenu( false );
Key_SetKeyDest( key_console );
Con_Printf( "Host_Error: %s", hosterror1 );
}
else MSGBOX2( hosterror1 );
}
// host is shutting down. don't invoke infinite loop
if( host.status == HOST_SHUTDOWN ) return;
if( recursive )
{
Con_Printf( "Host_RecursiveError: %s", hosterror2 );
Sys_Error( "%s", hosterror1 );
return; // don't multiple executes
}
recursive = true;
Q_strncpy( hosterror2, hosterror1, MAX_SYSPATH );
host.errorframe = host.framecount; // to avoid multply calls per frame
Q_sprintf( host.finalmsg, "Server crashed: %s", hosterror1 );
// clearing cmd buffer to prevent execute any commands
COM_InitHostState();
Cbuf_Clear();
Host_ShutdownServer();
CL_Drop(); // drop clients
// recreate world if needs
CL_ClearEdicts ();
// release all models
Mod_FreeAll();
recursive = false;
Host_AbortCurrentFrame();
}
void Host_Error_f( void )
{
const char *error = Cmd_Argv( 1 );
if( !*error ) error = "Invoked host error";
Host_Error( "%s\n", error );
}
void Sys_Error_f( void )
{
const char *error = Cmd_Argv( 1 );
if( !*error ) error = "Invoked sys error";
Sys_Error( "%s\n", error );
}
/*
=================
Host_Crash_f
=================
*/
static void Host_Crash_f( void )
{
*(volatile int *)0 = 0xffffffff;
}
/*
=================
Host_Userconfigd_f
=================
*/
void Host_Userconfigd_f( void )
{
search_t *t;
int i;
t = FS_Search( "userconfig.d/*.cfg", true, false );
if( !t ) return;
for( i = 0; i < t->numfilenames; i++ )
{
Cbuf_AddText( va("exec %s\n", t->filenames[i] ) );
}
Mem_Free( t );
}
/*
=================
Host_InitCommon
=================
*/
void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bChangeGame )
{
char dev_level[4];
int developer = DEFAULT_DEV;
const char *baseDir;
char ticrate[16];
int len;
// some commands may turn engine into infinite loop,
// e.g. xash.exe +game xash -game xash
// so we clear all cmd_args, but leave dbg states as well
Sys_ParseCommandLine( argc, argv );
if( !Sys_CheckParm( "-disablehelp" ) )
{
if( Sys_CheckParm( "-help" ) || Sys_CheckParm( "-h" ) || Sys_CheckParm( "--help" ) )
{
Sys_PrintUsage();
}
}
if( !Sys_CheckParm( "-noch" ) )
Sys_SetupCrashHandler();
host.enabledll = !Sys_CheckParm( "-nodll" );
host.change_game = bChangeGame;
host.config_executed = false;
host.status = HOST_INIT; // initialzation started
Memory_Init(); // init memory subsystem
host.mempool = Mem_AllocPool( "Zone Engine" );
// HACKHACK: Quake console is always allowed
// TODO: determine if we are running QWrap more reliable
if( Sys_CheckParm( "-console" ) || !Q_stricmp( SI.exeName, "quake" ))
host.allow_console = true;
if( Sys_CheckParm( "-dev" ))
{
host.allow_console = true;
developer = DEV_NORMAL;
if( Sys_GetParmFromCmdLine( "-dev", dev_level ))
{
if( Q_isdigit( dev_level ))
developer = bound( DEV_NONE, abs( Q_atoi( dev_level )), DEV_EXTENDED );
}
}
host.con_showalways = true;
#if XASH_DEDICATED
host.type = HOST_DEDICATED; // predict state
#else
if( Sys_CheckParm("-dedicated") || progname[0] == '#' )
{
host.type = HOST_DEDICATED;
}
else
{
host.type = HOST_NORMAL;
}
#endif
// set default gamedir
if( progname[0] == '#' )
progname++;
Q_strncpy( SI.exeName, progname, sizeof( SI.exeName ));
Q_strncpy( SI.basedirName, progname, sizeof( SI.exeName ));
if( Host_IsDedicated() )
{
Sys_MergeCommandLine( );
host.allow_console = true;
}
else
{
// don't show console as default
if( developer <= DEV_NORMAL )
host.con_showalways = false;
}
// member console allowing
host.allow_console_init = host.allow_console;
// timeBeginPeriod( 1 ); // a1ba: Do we need this?
// NOTE: this message couldn't be passed into game console but it doesn't matter
// Con_Reportf( "Sys_LoadLibrary: Loading xash.dll - ok\n" );
// get default screen res
VID_InitDefaultResolution();
// init host state machine
COM_InitHostState();
// init hashed commands
BaseCmd_Init();
// startup cmds and cvars subsystem
Cmd_Init();
Cvar_Init();
// share developer level across all dlls
Q_snprintf( dev_level, sizeof( dev_level ), "%i", developer );
Cvar_DirectSet( &host_developer, dev_level );
Cvar_RegisterVariable( &sys_ticrate );
if( Sys_GetParmFromCmdLine( "-sys_ticrate", ticrate ))
{
double fps = bound( MIN_FPS, atof( ticrate ), MAX_FPS );
Cvar_SetValue( "sys_ticrate", fps );
}
Con_Init(); // early console running to catch all the messages
Platform_Init();
baseDir = getenv( "XASH3D_BASEDIR" );
if( COM_CheckString( baseDir ) )
{
Q_strncpy( host.rootdir, baseDir, sizeof(host.rootdir) );
}
else
{
#if TARGET_OS_IOS
const char *IOS_GetDocsDir();
Q_strncpy( host.rootdir, IOS_GetDocsDir(), sizeof(host.rootdir) );
#elif XASH_SDL == 2
char *szBasePath;
if( !( szBasePath = SDL_GetBasePath() ) )
Sys_Error( "couldn't determine current directory: %s", SDL_GetError() );
Q_strncpy( host.rootdir, szBasePath, sizeof( host.rootdir ) );
SDL_free( szBasePath );
#else
if( !getcwd( host.rootdir, sizeof(host.rootdir) ) )
{
Sys_Error( "couldn't determine current directory: %s", strerror( errno ) );
host.rootdir[0] = 0;
}
#endif
}
len = Q_strlen( host.rootdir );
if( host.rootdir[len - 1] == '/' )
host.rootdir[len - 1] = 0;
// get readonly root. The order is: check for arg, then env.
// if still not got it, rodir is disabled.
host.rodir[0] = '\0';
if( !Sys_GetParmFromCmdLine( "-rodir", host.rodir ))
{
char *roDir = getenv( "XASH3D_RODIR" );
if( COM_CheckString( roDir ))
Q_strncpy( host.rodir, roDir, sizeof( host.rodir ));
}
len = Q_strlen( host.rodir );
if( len && host.rodir[len - 1] == '/' )
host.rodir[len - 1] = 0;
if( !COM_CheckStringEmpty( host.rootdir ) || SetCurrentDirectory( host.rootdir ) != 0 )
Con_Reportf( "%s is working directory now\n", host.rootdir );
else
Sys_Error( "Changing working directory to %s failed.\n", host.rootdir );
Sys_InitLog();
Cmd_AddCommand( "exec", Host_Exec_f, "execute a script file" );
Cmd_AddCommand( "memlist", Host_MemStats_f, "prints memory pool information" );
Cmd_AddCommand( "userconfigd", Host_Userconfigd_f, "execute all scripts from userconfig.d" );
FS_Init();
Image_Init();
Sound_Init();
FS_LoadGameInfo( NULL );
Q_strncpy( host.gamefolder, GI->gamefolder, sizeof( host.gamefolder ));
// DEPRECATED: by FWGS fork
#if 0
if( GI->secure )
{
// clear all developer levels when game is protected
Cvar_DirectSet( &host_developer, "0" );
host.allow_console_init = false;
host.con_showalways = false;
host.allow_console = false;
}
#endif
HPAK_Init();
IN_Init();
Key_Init();
}
void Host_FreeCommon( void )
{
Image_Shutdown();
Sound_Shutdown();
Netchan_Shutdown();
HPAK_FlushHostQueue();
FS_Shutdown();
}
/*
=================
Host_Main
=================
*/
int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func )
{
static double oldtime, newtime;
pChangeGame = func; // may be NULL
Host_InitCommon( argc, argv, progname, bChangeGame );
// init commands and vars
if( host_developer.value >= DEV_EXTENDED )
{
Cmd_AddCommand ( "sys_error", Sys_Error_f, "just throw a fatal error to test shutdown procedures");
Cmd_AddCommand ( "host_error", Host_Error_f, "just throw a host error to test shutdown procedures");
Cmd_AddCommand ( "crash", Host_Crash_f, "a way to force a bus error for development reasons");
}
host_serverstate = Cvar_Get( "host_serverstate", "0", FCVAR_READ_ONLY, "displays current server state" );
host_maxfps = Cvar_Get( "fps_max", "72", FCVAR_ARCHIVE, "host fps upper limit" );
host_framerate = Cvar_Get( "host_framerate", "0", 0, "locks frame timing to this value in seconds" );
host_sleeptime = Cvar_Get( "sleeptime", "1", FCVAR_ARCHIVE, "milliseconds to sleep for each frame. higher values reduce fps accuracy" );
host_gameloaded = Cvar_Get( "host_gameloaded", "0", FCVAR_READ_ONLY, "inidcates a loaded game.dll" );
host_clientloaded = Cvar_Get( "host_clientloaded", "0", FCVAR_READ_ONLY, "inidcates a loaded client.dll" );
host_limitlocal = Cvar_Get( "host_limitlocal", "0", 0, "apply cl_cmdrate and rate to loopback connection" );
con_gamemaps = Cvar_Get( "con_mapfilter", "1", FCVAR_ARCHIVE, "when true show only maps in game folder" );
build = Cvar_Get( "buildnum", va( "%i", Q_buildnum_compat()), FCVAR_READ_ONLY, "returns a current build number" );
ver = Cvar_Get( "ver", va( "%i/%s (hw build %i)", PROTOCOL_VERSION, XASH_COMPAT_VERSION, Q_buildnum_compat()), FCVAR_READ_ONLY, "shows an engine version" );
Cvar_Get( "host_ver", va( "%i %s %s %s %s", Q_buildnum(), XASH_VERSION, Q_buildos(), Q_buildarch(), Q_buildcommit() ), FCVAR_READ_ONLY, "detailed info about this build" );
Mod_Init();
NET_Init();
NET_InitMasters();
Netchan_Init();
// allow to change game from the console
if( pChangeGame != NULL )
{
Cmd_AddCommand( "game", Host_ChangeGame_f, "change game" );
Cvar_Get( "host_allow_changegame", "1", FCVAR_READ_ONLY, "allows to change games" );
}
else
{
Cvar_Get( "host_allow_changegame", "0", FCVAR_READ_ONLY, "allows to change games" );
}
SV_Init();
CL_Init();
HTTP_Init();
ID_Init();
if( Host_IsDedicated() )
{
#ifdef _WIN32
Wcon_InitConsoleCommands ();
#endif
Cmd_AddCommand( "quit", Sys_Quit, "quit the game" );
Cmd_AddCommand( "exit", Sys_Quit, "quit the game" );
}
else Cmd_AddCommand( "minimize", Host_Minimize_f, "minimize main window to tray" );
host.errorframe = 0;
// post initializations
switch( host.type )
{
case HOST_NORMAL:
#ifdef _WIN32
Wcon_ShowConsole( false ); // hide console
#endif
// execute startup config and cmdline
Cbuf_AddText( va( "exec %s.rc\n", SI.rcName ));
Cbuf_Execute();
if( !host.config_executed )
{
Cbuf_AddText( "exec config.cfg\n" );
Cbuf_Execute();
}
// exec all files from userconfig.d
Host_Userconfigd_f();
break;
case HOST_DEDICATED:
// allways parse commandline in dedicated-mode
host.stuffcmds_pending = true;
break;
}
host.change_game = false; // done
Cmd_RemoveCommand( "setgl" );
Cbuf_ExecStuffCmds(); // execute stuffcmds (commandline)
SCR_CheckStartupVids(); // must be last
oldtime = Sys_DoubleTime() - 0.1;
if( Host_IsDedicated() && GameState->nextstate == STATE_RUNFRAME )
{
Con_Printf( "type 'map <mapname>' to run server... (TAB-autocomplete is working too)\n" );
// execute server.cfg after commandline
// so we have a chance to set servercfgfile
Cbuf_AddText( va( "exec %s\n", Cvar_VariableString( "servercfgfile" )));
Cbuf_Execute();
}
// main window message loop
while( !host.crashed )
{
newtime = Sys_DoubleTime ();
COM_Frame( newtime - oldtime );
oldtime = newtime;
}
// never reached
return 0;
}
/*
=================
Host_Shutdown
=================
*/
void EXPORT Host_Shutdown( void )
{
if( host.shutdown_issued ) return;
host.shutdown_issued = true;
if( host.status != HOST_ERR_FATAL ) host.status = HOST_SHUTDOWN; // prepare host to normal shutdown
if( !host.change_game ) Q_strncpy( host.finalmsg, "Server shutdown", sizeof( host.finalmsg ));
#if !XASH_DEDICATED
if( host.type == HOST_NORMAL )
Host_WriteConfig();
#endif
SV_Shutdown( "Server shutdown\n" );
SV_ShutdownFilter();
CL_Shutdown();
Mod_Shutdown();
NET_Shutdown();
HTTP_Shutdown();
Host_FreeCommon();
Platform_Shutdown();
// must be last, console uses this
Mem_FreePool( &host.mempool );
// restore filter
Sys_RestoreCrashHandler();
Sys_CloseLog();
}