2
0
mirror of https://github.com/FWGS/xash3d-fwgs synced 2024-11-24 18:59:50 +01:00
xash3d-fwgs/engine/common/host.c

1292 lines
29 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
#include "tests.h"
pfnChangeGame pChangeGame = NULL;
host_parm_t host; // host parms
sysinfo_t SI;
#ifdef XASH_ENGINE_TESTS
struct tests_stats_s tests_stats;
#endif
CVAR_DEFINE( host_developer, "developer", "0", FCVAR_FILTERABLE, "engine is in development-mode" );
CVAR_DEFINE_AUTO( sys_timescale, "1.0", FCVAR_CHEAT|FCVAR_FILTERABLE, "scale frame time" );
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
S_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")
#if !XASH_DEDICATED
O("-clientlib <path>","override client DLL path")
#endif
O("-rodir <path> ","set read-only base directory, experimental")
O("-bugcomp ","enable precise bug compatibility. Will break games that don't require it")
O(" ","Refer to engine documentation for more info")
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_CalcSleep
==================
*/
static int Host_CalcSleep( void )
{
#ifndef XASH_DEDICATED
// never sleep in timedemo for benchmarking purposes
// also don't sleep with vsync for less lag
if( CL_IsTimeDemo( ) || CVAR_TO_BOOL( gl_vsync ))
return 0;
#endif
if( Host_IsDedicated() )
{
// let the dedicated server some sleep
return host_sleeptime->value;
}
switch( host.status )
{
case HOST_NOFOCUS:
if( SV_Active() && CL_IsInGame())
return host_sleeptime->value;
// fallthrough
case HOST_SLEEP:
return 20;
}
return host_sleeptime->value;
}
void Host_NewInstance( const char *name, const char *finalmsg )
{
if( !pChangeGame ) return;
host.change_game = true;
Q_strncpy( host.finalmsg, finalmsg, sizeof( host.finalmsg ));
if( !Sys_NewInstance( name ))
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 < FI->numgames; i++ )
{
if( !Q_stricmp( FI->games[i]->gamefolder, Cmd_Argv( 1 )))
break;
}
if( i == FI->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", Cmd_Argv( 1 ));
const char *arg2 = va( "change game to '%s'", FI->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;
const char *arg;
if( Cmd_Argc() != 2 )
{
Con_Printf( S_USAGE "exec <filename>\n" );
return;
}
arg = Cmd_Argv( 1 );
#ifndef XASH_DEDICATED
if( !Cmd_CurrentCommandIsPrivileged() )
{
const char *unprivilegedWhitelist[] =
{
NULL, "mapdefault.cfg", "scout.cfg", "sniper.cfg",
"soldier.cfg", "demoman.cfg", "medic.cfg", "hwguy.cfg",
"pyro.cfg", "spy.cfg", "engineer.cfg", "civilian.cfg"
};
int i;
qboolean allow = false;
unprivilegedWhitelist[0] = va( "%s.cfg", clgame.mapname );
for( i = 0; i < ARRAYSIZE( unprivilegedWhitelist ); i++ )
{
if( !Q_strcmp( arg, unprivilegedWhitelist[i] ))
{
allow = true;
break;
}
}
if( !allow )
{
Con_Printf( "exec %s: not privileged or in whitelist\n", arg );
return;
}
}
#endif // XASH_DEDICATED
if( !Q_stricmp( "game.cfg", arg ))
{
// don't execute game.cfg in singleplayer
if( SV_GetMaxClients() == 1 )
return;
}
Q_strncpy( cfgpath, arg, 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", arg ))
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", arg );
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( ))
{
if( !CVAR_TO_BOOL( gl_vsync ))
fps = host_maxfps->value;
}
else
{
if( !CVAR_TO_BOOL( gl_vsync ))
{
fps = host_maxfps->value;
if( fps == 0.0 ) fps = MAX_FPS;
fps = bound( MIN_FPS, fps, MAX_FPS );
}
}
#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, scale = sys_timescale.value;
host.realtime += time * scale;
fps = Host_CalcFPS();
// clamp the fps in multiplayer games
if( fps != 0.0 )
{
static int sleeps;
double targetframetime;
int sleeptime = Host_CalcSleep();
// limit fps to withing tolerable range
fps = bound( MIN_FPS, fps, MAX_FPS );
if( Host_IsDedicated( ))
targetframetime = ( 1.0 / ( fps + 1.0 ));
else targetframetime = ( 1.0 / fps );
if(( host.realtime - oldtime ) < targetframetime * scale )
{
if( sleeptime > 0 && sleeps > 0 )
{
Sys_Sleep( sleeptime );
sleeps--;
}
return false;
}
if( sleeptime > 0 && sleeps <= 0 )
{
if( host.status == HOST_FRAME )
{
// give few sleeps this frame with small margin
double targetsleeptime = targetframetime - host.pureframetime * 2;
// don't sleep if we can't keep up with the framerate
if( targetsleeptime > 0 )
sleeps = targetsleeptime / ( sleeptime * 0.001 );
else sleeps = 0;
}
else
{
// always sleep at least once in minimized/nofocus state
sleeps = 1;
}
}
}
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 * scale, MAX_FRAMETIME );
else host.frametime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME );
return true;
}
/*
=================
Host_Frame
=================
*/
void Host_Frame( float time )
{
double t1, t2;
// decide the simulation time
if( !Host_FilterTime( time ))
return;
t1 = Sys_DoubleTime();
if( host.framecount == 0 )
Con_DPrintf( "Time to first frame: %.3f seconds\n", t1 - host.starttime );
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
t2 = Sys_DoubleTime();
host.pureframetime = t2 - t1;
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;
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 );
}
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 );
}
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 );
}
#if XASH_ENGINE_TESTS
static void Host_RunTests( int stage )
{
switch( stage )
{
case 0: // early engine load
memset( &tests_stats, 0, sizeof( tests_stats ));
TEST_LIST_0;
#if !XASH_DEDICATED
TEST_LIST_0_CLIENT;
#endif /* XASH_DEDICATED */
break;
case 1: // after FS load
TEST_LIST_1;
#if !XASH_DEDICATED
TEST_LIST_1_CLIENT;
#endif
Msg( "Done! %d passed, %d failed\n", tests_stats.passed, tests_stats.failed );
Sys_Quit();
}
}
#endif
/*
=================
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 || Sys_CheckParm( "-changegame" );
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 );
}
}
#if XASH_ENGINE_TESTS
if( Sys_CheckParm( "-runtests" ))
{
host.allow_console = true;
developer = DEV_EXTENDED;
}
#endif
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;
if( Sys_CheckParm( "-bugcomp" ))
{
// add argument check here when we add other levels
// of bugcompatibility
host.bugcomp = BUGCOMP_GOLDSRC;
}
// 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
#if XASH_ENGINE_TESTS
if( Sys_CheckParm( "-runtests" ))
Host_RunTests( 0 );
#endif
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( len && 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 ))
{
Sys_Error( "Changing working directory failed (empty working directory)\n" );
return;
}
FS_LoadProgs();
if( FS_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 );
FS_Init();
Sys_InitLog();
// print bugcompatibility level here, after log was initialized
if( host.bugcomp == BUGCOMP_GOLDSRC )
{
Con_Printf( "^3BUGCOMP^7: GoldSrc bug-compatibility enabled\n" );
}
Cmd_AddCommand( "exec", Host_Exec_f, "execute a script file" );
Cmd_AddCommand( "memlist", Host_MemStats_f, "prints memory pool information" );
Cmd_AddRestrictedCommand( "userconfigd", Host_Userconfigd_f, "execute all scripts from userconfig.d" );
Image_Init();
Sound_Init();
#if XASH_ENGINE_TESTS
if( Sys_CheckParm( "-runtests" ))
Host_RunTests( 1 );
#endif
FS_LoadGameInfo( NULL );
Cvar_PostFSInit();
if( FS_FileExists( va( "%s.rc", SI.basedirName ), false ))
Q_strncpy( SI.rcName, SI.basedirName, sizeof( SI.rcName )); // e.g. valve.rc
else Q_strncpy( SI.rcName, SI.exeName, sizeof( SI.rcName )); // e.g. quake.rc
Q_strncpy( host.gamefolder, GI->gamefolder, sizeof( host.gamefolder ));
Image_CheckPaletteQ1 ();
Host_InitDecals (); // reload decals
// 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;
host.starttime = Sys_DoubleTime();
pChangeGame = func; // may be NULL
Host_InitCommon( argc, argv, progname, bChangeGame );
// init commands and vars
if( host_developer.value >= DEV_EXTENDED )
{
Cmd_AddRestrictedCommand ( "sys_error", Sys_Error_f, "just throw a fatal error to test shutdown procedures");
Cmd_AddRestrictedCommand ( "host_error", Host_Error_f, "just throw a host error to test shutdown procedures");
Cmd_AddRestrictedCommand ( "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|FCVAR_FILTERABLE, "host fps upper limit" );
host_framerate = Cvar_Get( "host_framerate", "0", FCVAR_FILTERABLE, "locks frame timing to this value in seconds" );
host_sleeptime = Cvar_Get( "sleeptime", "1", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "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" );
Cvar_RegisterVariable( &sys_timescale );
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 " XASH_VERSION " %s %s %s", Q_buildnum(), Q_buildos(), Q_buildarch(), Q_buildcommit() ), FCVAR_READ_ONLY, "detailed info about this build" );
Cvar_Get( "host_lowmemorymode", va( "%i", XASH_LOW_MEMORY ), FCVAR_READ_ONLY, "indicates if engine compiled for low RAM consumption (0 - normal, 1 - low engine limits, 2 - low protocol limits)" );
Mod_Init();
NET_Init();
NET_InitMasters();
Netchan_Init();
// allow to change game from the console
if( pChangeGame != NULL )
{
Cmd_AddRestrictedCommand( "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_AddRestrictedCommand( "quit", Sys_Quit, "quit the game" );
Cmd_AddRestrictedCommand( "exit", Sys_Quit, "quit the game" );
}
else Cmd_AddRestrictedCommand( "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( ))
{
// in dedicated server input system can't set HOST_FRAME status
// so set it here as we're finished initializing
host.status = HOST_FRAME;
if( GameState->nextstate == STATE_RUNFRAME )
Con_Printf( "Type 'map <mapname>' to start game... (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();
}