mirror of
https://github.com/FWGS/xash3d-fwgs
synced 2024-11-26 20:00:53 +01:00
1332 lines
32 KiB
C
1332 lines
32 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
|
|
#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 "common.h"
|
|
#include "base_cmd.h"
|
|
#include "client.h"
|
|
#include "server.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
|
|
|
|
#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_FILTERABLE, "scale frame time" );
|
|
CVAR_DEFINE_AUTO( sys_ticrate, "100", FCVAR_SERVER, "framerate in dedicated mode" );
|
|
|
|
static CVAR_DEFINE_AUTO( host_serverstate, "0", FCVAR_READ_ONLY, "displays current server state" );
|
|
static CVAR_DEFINE_AUTO( host_gameloaded, "0", FCVAR_READ_ONLY, "inidcates a loaded game.dll" );
|
|
static CVAR_DEFINE_AUTO( host_clientloaded, "0", FCVAR_READ_ONLY, "inidcates a loaded client.dll" );
|
|
CVAR_DEFINE_AUTO( host_limitlocal, "0", 0, "apply cl_cmdrate and rate to loopback connection" );
|
|
CVAR_DEFINE( host_maxfps, "fps_max", "72", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "host fps upper limit" );
|
|
static CVAR_DEFINE_AUTO( host_framerate, "0", FCVAR_FILTERABLE, "locks frame timing to this value in seconds" );
|
|
static CVAR_DEFINE( host_sleeptime, "sleeptime", "1", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "milliseconds to sleep for each frame. higher values reduce fps accuracy" );
|
|
static CVAR_DEFINE_AUTO( host_sleeptime_debug, "0", 0, "print sleeps between frames" );
|
|
CVAR_DEFINE_AUTO( host_allow_materials, "0", FCVAR_LATCH|FCVAR_ARCHIVE, "allow texture replacements from materials/ folder" );
|
|
CVAR_DEFINE( con_gamemaps, "con_mapfilter", "1", FCVAR_ARCHIVE, "when true show only maps in game folder" );
|
|
|
|
typedef struct feature_message_s
|
|
{
|
|
uint32_t mask;
|
|
const char *msg;
|
|
const char *arg;
|
|
} feature_message_t;
|
|
|
|
static feature_message_t bugcomp_features[] =
|
|
{
|
|
{ BUGCOMP_PENTITYOFENTINDEX_FLAG, "pfnPEntityOfEntIndex bugfix revert", "peoei" },
|
|
{ BUGCOMP_MESSAGE_REWRITE_FACILITY_FLAG, "GoldSrc Message Rewrite Facility", "gsmrf" },
|
|
{ BUGCOMP_SPATIALIZE_SOUND_WITH_ATTN_NONE, "spatialize sounds with zero attenuation", "sp_attn_none" },
|
|
};
|
|
|
|
static feature_message_t engine_features[] =
|
|
{
|
|
{ ENGINE_WRITE_LARGE_COORD, "Big World Support" },
|
|
{ ENGINE_QUAKE_COMPATIBLE, "Quake Compatibility" },
|
|
{ ENGINE_LOAD_DELUXEDATA, "Deluxemap Support" },
|
|
{ ENGINE_PHYSICS_PUSHER_EXT, "Improved MOVETYPE_PUSH" },
|
|
{ ENGINE_LARGE_LIGHTMAPS, "Large Lightmaps" },
|
|
{ ENGINE_COMPENSATE_QUAKE_BUG, "Stupid Quake Bug Compensation" },
|
|
{ ENGINE_IMPROVED_LINETRACE, "Improved Trace Line" },
|
|
{ ENGINE_COMPUTE_STUDIO_LERP, "Studio MOVETYPE_STEP Lerping" },
|
|
{ ENGINE_LINEAR_GAMMA_SPACE, "Linear Gamma Space" },
|
|
{ ENGINE_STEP_POSHISTORY_LERP, "MOVETYPE_STEP Position History Based Lerping" },
|
|
};
|
|
|
|
static void Sys_PrintUsage( const char *exename )
|
|
{
|
|
string version_str;
|
|
const char *usage_str;
|
|
|
|
Q_snprintf( version_str, sizeof( version_str ),
|
|
XASH_ENGINE_NAME " %i/" XASH_VERSION " (%s-%s build %i)", PROTOCOL_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( ));
|
|
|
|
#if XASH_MESSAGEBOX != MSGBOX_STDERR
|
|
#if XASH_WIN32
|
|
#define XASH_EXE "(xash).exe"
|
|
#else
|
|
#define XASH_EXE "(xash)"
|
|
#endif
|
|
#else
|
|
#define XASH_EXE "%s"
|
|
#endif
|
|
|
|
#define O( x, y ) " "x" "y"\n"
|
|
|
|
usage_str = S_USAGE XASH_EXE " [options] [+command] [+command2 arg] ...\n"
|
|
|
|
"\nCommon options:\n"
|
|
O("-dev [level] ", "set log verbosity 0-2")
|
|
O("-log ", "write log to \"engine.log\"")
|
|
O("-nowriteconfig ", "disable config save")
|
|
O("-noch ", "disable crashhandler")
|
|
#if XASH_WIN32 // !!!!
|
|
O("-minidumps ", "enable writing minidumps when game is crashed")
|
|
#endif
|
|
O("-rodir <path> ", "set read-only base directory")
|
|
O("-bugcomp [opts] ", "enable precise bug compatibility")
|
|
O(" ", "will break games that don't require it")
|
|
O(" ", "refer to engine documentation for more info")
|
|
O("-disablehelp ", "disable this message")
|
|
#if !XASH_DEDICATED
|
|
O("-dedicated ", "run engine in dedicated mode")
|
|
#endif
|
|
|
|
"\nNetworking options:\n"
|
|
O("-noip ", "disable IPv4")
|
|
O("-ip <ip> ", "set IPv4 address")
|
|
O("-port <port> ", "set IPv4 port")
|
|
O("-noip6 ", "disable IPv6")
|
|
O("-ip6 <ip> ", "set IPv6 address")
|
|
O("-port6 <port> ", "set IPv6 port")
|
|
O("-clockwindow <cw>", "adjust clockwindow used to ignore client commands")
|
|
O(" ", "to prevent speed hacks")
|
|
|
|
"\nGame options:\n"
|
|
O("-game <directory>", "set game directory to start engine with")
|
|
O("-dll <path> ", "override server DLL path")
|
|
#if !XASH_DEDICATED
|
|
O("-clientlib <path>", "override client DLL path")
|
|
O("-console ", "run engine with console enabled")
|
|
O("-toconsole ", "run engine witn console open")
|
|
O("-oldfont ", "enable unused Quake font in Half-Life")
|
|
O("-width <n> ", "set window width")
|
|
O("-height <n> ", "set window height")
|
|
O("-borderless ", "run engine in fullscreen borderless mode")
|
|
O("-fullscreen ", "run engine in fullscreen mode")
|
|
O("-windowed ", "run engine in windowed mode")
|
|
O("-ref <name> ", "use selected renderer dll")
|
|
O("-gldebug ", "enable OpenGL debug log")
|
|
#if XASH_WIN32
|
|
O("-noavi ", "disable AVI support")
|
|
O("-nointro ", "disable intro video")
|
|
#endif
|
|
O("-noenginejoy ", "disable engine builtin joystick support")
|
|
O("-noenginemouse ", "disable engine builtin mouse support")
|
|
O("-nosound ", "disable sound output")
|
|
O("-timedemo ", "run timedemo and exit")
|
|
#endif
|
|
|
|
"\nPlatform-specific options:\n"
|
|
#if !XASH_MOBILE_PLATFORM
|
|
O("-daemonize ", "run engine as a daemon")
|
|
#endif
|
|
#if XASH_SDL == 2
|
|
O("-sdl_joy_old_api ","use SDL legacy joystick API")
|
|
O("-sdl_renderer <n>","use alternative SDL_Renderer for software")
|
|
#endif // XASH_SDL
|
|
#if XASH_ANDROID && !XASH_SDL
|
|
O("-nativeegl ","use native egl implementation. Use if screen does not update or black")
|
|
#endif // XASH_ANDROID
|
|
#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
|
|
;
|
|
#undef O
|
|
|
|
// HACKHACK: pretty output in dedicated
|
|
#if XASH_MESSAGEBOX != MSGBOX_STDERR
|
|
Platform_MessageBox( version_str, usage_str, false );
|
|
#else
|
|
fprintf( stderr, "%s\n", version_str );
|
|
fprintf( stderr, usage_str, exename );
|
|
#endif
|
|
|
|
Sys_Quit();
|
|
}
|
|
|
|
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
|
|
================
|
|
*/
|
|
static void Host_PrintFeatures( uint32_t flags, const char *s, feature_message_t *features, size_t size )
|
|
{
|
|
size_t i;
|
|
|
|
for( i = 0; i < size; i++ )
|
|
{
|
|
if( FBitSet( flags, features[i].mask ))
|
|
Con_Printf( "^3%s:^7 %s is enabled\n", s, features[i].msg );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
Host_ValidateEngineFeatures
|
|
|
|
validate features bits and set host.features
|
|
==============
|
|
*/
|
|
void Host_ValidateEngineFeatures( uint32_t features )
|
|
{
|
|
uint32_t mask = ENGINE_FEATURES_MASK;
|
|
|
|
#if !XASH_DEDICATED
|
|
if( !Host_IsDedicated( ) && cls.legacymode )
|
|
mask = ENGINE_LEGACY_FEATURES_MASK;
|
|
#endif
|
|
|
|
// don't allow unsupported bits
|
|
features &= mask;
|
|
|
|
// force bits for some games
|
|
if( !Q_stricmp( GI->gamefolder, "cstrike" ) || !Q_stricmp( GI->gamefolder, "czero" ))
|
|
SetBits( features, ENGINE_STEP_POSHISTORY_LERP );
|
|
|
|
// print requested first
|
|
Host_PrintFeatures( features, "EXT", engine_features, ARRAYSIZE( engine_features ));
|
|
|
|
// now warn about incompatible bits
|
|
if( FBitSet( features, ENGINE_STEP_POSHISTORY_LERP|ENGINE_COMPUTE_STUDIO_LERP ) == ( ENGINE_STEP_POSHISTORY_LERP|ENGINE_COMPUTE_STUDIO_LERP ))
|
|
Con_Printf( S_WARN "%s: incompatible ENGINE_STEP_POSHISTORY_LERP and ENGINE_COMPUTE_STUDIO_LERP are enabled!\n", __func__ );
|
|
|
|
// finally set global variable
|
|
host.features = features;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
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_CalcSleep
|
|
==================
|
|
*/
|
|
static int Host_CalcSleep( void )
|
|
{
|
|
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;
|
|
}
|
|
|
|
static 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
|
|
=================
|
|
*/
|
|
static 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
|
|
{
|
|
char finalmsg[MAX_VA_STRING];
|
|
|
|
Q_snprintf( finalmsg, sizeof( finalmsg ), "change game to '%s'", FI->games[i]->title );
|
|
Host_NewInstance( Cmd_Argv( 1 ), finalmsg );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Host_Exec_f
|
|
===============
|
|
*/
|
|
static 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;
|
|
}
|
|
|
|
Q_strncpy( cfgpath, Cmd_Argv( 1 ), sizeof( cfgpath ));
|
|
COM_DefaultExtension( cfgpath, ".cfg", sizeof( cfgpath )); // append as default
|
|
|
|
#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;
|
|
char temp[MAX_VA_STRING];
|
|
qboolean allow = false;
|
|
|
|
Q_snprintf( temp, sizeof( temp ), "%s.cfg", clgame.mapname );
|
|
unprivilegedWhitelist[0] = temp;
|
|
|
|
for( i = 0; i < ARRAYSIZE( unprivilegedWhitelist ); i++ )
|
|
{
|
|
if( !Q_strcmp( cfgpath, unprivilegedWhitelist[i] ))
|
|
{
|
|
allow = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !allow )
|
|
{
|
|
Con_Printf( "exec %s: not privileged or in whitelist\n", cfgpath );
|
|
return;
|
|
}
|
|
}
|
|
#endif // XASH_DEDICATED
|
|
|
|
// don't execute game.cfg in singleplayer
|
|
if( SV_GetMaxClients() == 1 && !Q_stricmp( "game.cfg", cfgpath ))
|
|
return;
|
|
|
|
f = FS_LoadFile( cfgpath, &len, false );
|
|
if( !f )
|
|
{
|
|
Con_Reportf( "couldn't exec %s\n", Cmd_Argv( 1 ));
|
|
return;
|
|
}
|
|
|
|
if( !Q_stricmp( "config.cfg", cfgpath ))
|
|
host.config_executed = true;
|
|
|
|
// adds \n\0 at end of the file
|
|
txt = Z_Calloc( len + 2 );
|
|
memcpy( txt, f, len );
|
|
txt[len] = '\n';
|
|
txt[len + 1] = '\0';
|
|
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
|
|
===============
|
|
*/
|
|
static 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;
|
|
}
|
|
}
|
|
|
|
static void Host_Minimize_f( void )
|
|
{
|
|
#ifdef XASH_SDL
|
|
if( host.hWnd ) SDL_MinimizeWindow( host.hWnd );
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Host_RegisterDecal
|
|
=================
|
|
*/
|
|
static qboolean Host_RegisterDecal( const char *name, int *count )
|
|
{
|
|
char shortname[MAX_QPATH];
|
|
int i;
|
|
|
|
if( !COM_CheckString( name ))
|
|
return 0;
|
|
|
|
COM_FileBase( name, shortname, sizeof( 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
|
|
=================
|
|
*/
|
|
static void Host_InitDecals( void )
|
|
{
|
|
int i, num_decals = 0;
|
|
search_t *t;
|
|
|
|
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( "%s: %i decals\n", __func__, num_decals );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Host_GetCommands
|
|
|
|
Add them exactly as if they had been typed at the console
|
|
===================
|
|
*/
|
|
static void Host_GetCommands( void )
|
|
{
|
|
char *cmd;
|
|
|
|
while( ( cmd = Sys_Input() ) )
|
|
{
|
|
Cbuf_AddText( cmd );
|
|
Cbuf_Execute();
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Host_CalcFPS
|
|
|
|
compute actual FPS for various modes
|
|
===================
|
|
*/
|
|
static 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( !gl_vsync.value )
|
|
fps = host_maxfps.value;
|
|
}
|
|
else
|
|
{
|
|
if( !gl_vsync.value )
|
|
{
|
|
fps = host_maxfps.value;
|
|
if( fps == 0.0 ) fps = MAX_FPS;
|
|
fps = bound( MIN_FPS, fps, MAX_FPS );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return fps;
|
|
}
|
|
|
|
static qboolean Host_Autosleep( double dt, double scale )
|
|
{
|
|
double targetframetime, fps;
|
|
int sleep;
|
|
|
|
fps = Host_CalcFPS();
|
|
|
|
if( fps <= 0 )
|
|
return true;
|
|
|
|
// 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 );
|
|
|
|
sleep = Host_CalcSleep();
|
|
if( sleep == 0 ) // no sleeps between frames, much simpler code
|
|
{
|
|
if( dt < targetframetime * scale )
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
static double timewindow; // allocate a time window for sleeps
|
|
static int counter; // for debug
|
|
static double realsleeptime;
|
|
const double sleeptime = sleep * 0.001;
|
|
|
|
if( dt < targetframetime * scale )
|
|
{
|
|
// if we have allocated time window, try to sleep
|
|
if( timewindow > realsleeptime )
|
|
{
|
|
// Sys_Sleep isn't guaranteed to sleep an exact amount of milliseconds
|
|
// so we measure the real sleep time and use it to decrease the window
|
|
double t1 = Sys_DoubleTime(), t2;
|
|
Sys_Sleep( sleep ); // in msec!
|
|
t2 = Sys_DoubleTime();
|
|
realsleeptime = t2 - t1;
|
|
|
|
timewindow -= realsleeptime;
|
|
|
|
if( host_sleeptime_debug.value )
|
|
{
|
|
counter++;
|
|
|
|
Con_NPrintf( counter, "%d: %.4f %.4f", counter, timewindow, realsleeptime );
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// if we exhausted this time window, allocate a new one after new frame
|
|
if( timewindow <= realsleeptime )
|
|
{
|
|
double targetsleeptime = targetframetime - host.pureframetime * 2;
|
|
|
|
if( targetsleeptime > 0 )
|
|
timewindow = targetsleeptime;
|
|
else timewindow = 0;
|
|
|
|
realsleeptime = sleeptime; // reset in case CPU was too busy
|
|
|
|
if( host_sleeptime_debug.value )
|
|
{
|
|
counter = 0;
|
|
|
|
Con_NPrintf( 0, "tgt = %.4f, pft = %.4f, wnd = %.4f", targetframetime, host.pureframetime, timewindow );
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Host_FilterTime
|
|
|
|
Returns false if the time is too short to run a frame
|
|
===================
|
|
*/
|
|
static qboolean Host_FilterTime( double time )
|
|
{
|
|
static double oldtime;
|
|
double dt;
|
|
double scale = sys_timescale.value;
|
|
|
|
host.realtime += time * scale;
|
|
dt = host.realtime - oldtime;
|
|
|
|
// clamp the fps in multiplayer games
|
|
if( !Host_Autosleep( dt, scale ))
|
|
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 * scale, MAX_FRAMETIME );
|
|
else host.frametime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Host_Frame
|
|
=================
|
|
*/
|
|
void Host_Frame( double time )
|
|
{
|
|
double t1;
|
|
|
|
// 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
|
|
|
|
host.framecount++;
|
|
host.pureframetime = Sys_DoubleTime() - t1;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
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_vsnprintf( hosterror1, sizeof( hosterror1 ), error, argptr );
|
|
va_end( argptr );
|
|
|
|
CL_WriteMessageHistory (); // before Q_error call
|
|
|
|
if( host.framecount < 3 )
|
|
{
|
|
Sys_Error( "%sInit: %s", __func__, hosterror1 );
|
|
}
|
|
else if( host.framecount == host.errorframe )
|
|
{
|
|
Sys_Error( "%sMulti: %s", __func__, hosterror2 );
|
|
}
|
|
else
|
|
{
|
|
if( host.allow_console )
|
|
{
|
|
UI_SetActiveMenu( false );
|
|
Key_SetKeyDest( key_console );
|
|
Con_Printf( "%s: %s", __func__, hosterror1 );
|
|
}
|
|
else Platform_MessageBox( "Host Error", hosterror1, true );
|
|
}
|
|
|
|
// host is shutting down. don't invoke infinite loop
|
|
if( host.status == HOST_SHUTDOWN ) return;
|
|
|
|
if( recursive )
|
|
{
|
|
Con_Printf( "%sRecursive: %s", __func__, hosterror2 );
|
|
Sys_Error( "%s", hosterror1 );
|
|
}
|
|
|
|
recursive = true;
|
|
Q_strncpy( hosterror2, hosterror1, sizeof( hosterror2 ));
|
|
host.errorframe = host.framecount; // to avoid multply calls per frame
|
|
Q_snprintf( host.finalmsg, sizeof( 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();
|
|
}
|
|
|
|
static void Host_Error_f( void )
|
|
{
|
|
const char *error = Cmd_Argv( 1 );
|
|
|
|
if( !*error ) error = "Invoked host error";
|
|
Host_Error( "%s\n", error );
|
|
}
|
|
|
|
static 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
|
|
=================
|
|
*/
|
|
static 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_AddTextf( "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 );
|
|
error_on_exit = tests_stats.failed > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
|
|
Sys_Quit();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static uint32_t Host_CheckBugcomp( void )
|
|
{
|
|
const char *prev, *next;
|
|
uint32_t flags = 0;
|
|
string args, arg;
|
|
size_t i;
|
|
|
|
if( !Sys_CheckParm( "-bugcomp" ))
|
|
return 0;
|
|
|
|
if( Sys_GetParmFromCmdLine( "-bugcomp", args ) && isalpha( args[0] ))
|
|
{
|
|
for( prev = args, next = Q_strchrnul( prev, '+' ); ; prev = next + 1, next = Q_strchrnul( prev, '+' ))
|
|
{
|
|
Q_strncpy( arg, prev, next - prev + 1 );
|
|
for( i = 0; i < ARRAYSIZE( bugcomp_features ); i++ )
|
|
{
|
|
if( !Q_stricmp( bugcomp_features[i].arg, arg ))
|
|
{
|
|
SetBits( flags, bugcomp_features[i].mask );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( i == ARRAYSIZE( bugcomp_features ))
|
|
{
|
|
Con_Printf( S_ERROR "Unknown bugcomp flag %s\n", arg );
|
|
Con_Printf( "Valid flags are:\n" );
|
|
for( i = 0; i < ARRAYSIZE( bugcomp_features ); i++ )
|
|
Con_Printf( "\t%s: %s\n", bugcomp_features[i].arg, bugcomp_features[i].msg );
|
|
}
|
|
|
|
if( !*next )
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no argument specified -bugcomp just enables everything
|
|
flags = -1;
|
|
}
|
|
|
|
Host_PrintFeatures( flags, "BUGCOMP", bugcomp_features, ARRAYSIZE( bugcomp_features ));
|
|
|
|
return flags;
|
|
}
|
|
|
|
static void Host_DetermineExecutableName( char *out, size_t size )
|
|
{
|
|
#if XASH_WIN32
|
|
char temp[MAX_SYSPATH];
|
|
|
|
if( GetModuleFileName( NULL, temp, sizeof( temp )))
|
|
COM_FileBase( temp, out, size );
|
|
#else
|
|
if( host.argc > 0 )
|
|
COM_FileBase( host.argv[0], out, size );
|
|
else
|
|
Q_strncpy( out, "xash", size );
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Host_InitCommon
|
|
=================
|
|
*/
|
|
static void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bChangeGame, char *exename, size_t exename_size )
|
|
{
|
|
const char *basedir = progname[0] == '#' ? progname + 1 : progname;
|
|
char dev_level[4], ticrate[16];
|
|
int developer = DEFAULT_DEV;
|
|
|
|
// 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 );
|
|
Host_DetermineExecutableName( exename, exename_size );
|
|
|
|
if( !Sys_CheckParm( "-disablehelp" ))
|
|
{
|
|
if( Sys_CheckParm( "-help" ) || Sys_CheckParm( "-h" ) || Sys_CheckParm( "--help" ))
|
|
Sys_PrintUsage( exename );
|
|
}
|
|
|
|
if( !Sys_CheckParm( "-noch" ))
|
|
Sys_SetupCrashHandler();
|
|
|
|
#if XASH_DLL_LOADER
|
|
host.enabledll = !Sys_CheckParm( "-nodll" );
|
|
#endif
|
|
|
|
host.change_game = bChangeGame || Sys_CheckParm( "-changegame" );
|
|
host.config_executed = false;
|
|
host.status = HOST_INIT; // initialzation started
|
|
host.type = HOST_DEDICATED; // predict state
|
|
#ifndef XASH_DEDICATED
|
|
if( !Sys_CheckParm( "-dedicated" ))
|
|
host.type = HOST_NORMAL;
|
|
#endif
|
|
|
|
Memory_Init(); // init memory subsystem
|
|
|
|
host.mempool = Mem_AllocPool( "Zone Engine" );
|
|
|
|
host.allow_console = DEFAULT_ALLOWCONSOLE;
|
|
|
|
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
|
|
|
|
// always enable console for Quake and dedicated
|
|
if( !host.allow_console && ( Host_IsDedicated() || Sys_CheckParm( "-console" ) || !Q_strnicmp( exename, "quake", 5 )))
|
|
host.allow_console = true;
|
|
|
|
// 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
|
|
|
|
#if XASH_ENGINE_TESTS
|
|
if( Sys_CheckParm( "-runtests" ))
|
|
Host_RunTests( 0 );
|
|
#endif
|
|
|
|
#if XASH_DEDICATED
|
|
Platform_SetupSigtermHandling();
|
|
#endif
|
|
Platform_Init( Host_IsDedicated( ) || developer >= DEV_EXTENDED );
|
|
FS_Init( basedir );
|
|
|
|
Sys_InitLog();
|
|
|
|
// print current developer level to simplify processing users feedback
|
|
if( developer > 0 )
|
|
{
|
|
int i;
|
|
|
|
Con_Printf( "Program args: " S_YELLOW );
|
|
for( i = 0; i < host.argc; i++ )
|
|
Con_Printf( "%s ", host.argv[i] );
|
|
Con_Printf( S_DEFAULT "\n" );
|
|
|
|
Con_Printf( "Developer level: " S_YELLOW "%i" S_DEFAULT "\n", developer );
|
|
}
|
|
|
|
host.bugcomp = Host_CheckBugcomp();
|
|
|
|
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();
|
|
|
|
Image_CheckPaletteQ1 ();
|
|
|
|
// NOTE: only once resource without which engine can't continue work
|
|
if( !FS_FileExists( "gfx/conchars", false ))
|
|
Sys_Error( "%s: couldn't load gfx.wad\n", __func__ );
|
|
|
|
Host_InitDecals (); // reload decals
|
|
|
|
HPAK_Init();
|
|
|
|
IN_Init();
|
|
Key_Init();
|
|
}
|
|
|
|
static 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;
|
|
string demoname, exename;
|
|
|
|
host.starttime = Sys_DoubleTime();
|
|
|
|
pChangeGame = func; // may be NULL
|
|
|
|
Host_InitCommon( argc, argv, progname, bChangeGame, exename, sizeof( exename ));
|
|
|
|
// 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");
|
|
}
|
|
|
|
Cvar_RegisterVariable( &host_allow_materials );
|
|
Cvar_RegisterVariable( &host_serverstate );
|
|
Cvar_RegisterVariable( &host_maxfps );
|
|
Cvar_RegisterVariable( &host_framerate );
|
|
Cvar_RegisterVariable( &host_sleeptime );
|
|
Cvar_RegisterVariable( &host_sleeptime_debug );
|
|
Cvar_RegisterVariable( &host_gameloaded );
|
|
Cvar_RegisterVariable( &host_clientloaded );
|
|
Cvar_RegisterVariable( &host_limitlocal );
|
|
Cvar_RegisterVariable( &con_gamemaps );
|
|
Cvar_RegisterVariable( &sys_timescale );
|
|
|
|
Cvar_Getf( "buildnum", FCVAR_READ_ONLY, "returns a current build number", "%i", Q_buildnum_compat());
|
|
Cvar_Getf( "ver", FCVAR_READ_ONLY, "shows an engine version", "%i/%s (hw build %i)", PROTOCOL_VERSION, XASH_COMPAT_VERSION, Q_buildnum_compat());
|
|
Cvar_Getf( "host_ver", FCVAR_READ_ONLY, "detailed info about this build", "%i " XASH_VERSION " %s %s %s", Q_buildnum(), Q_buildos(), Q_buildarch(), Q_buildcommit());
|
|
Cvar_Getf( "host_lowmemorymode", FCVAR_READ_ONLY, "indicates if engine compiled for low RAM consumption (0 - normal, 1 - low engine limits, 2 - low protocol limits)", "%i", XASH_LOW_MEMORY );
|
|
|
|
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();
|
|
SoundList_Init();
|
|
|
|
if( Host_IsDedicated( ))
|
|
{
|
|
#ifdef _WIN32
|
|
Wcon_InitConsoleCommands ();
|
|
#endif
|
|
|
|
// disable texture replacements for dedicated
|
|
Cvar_FullSet( "host_allow_materials", "0", FCVAR_READ_ONLY );
|
|
|
|
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;
|
|
|
|
if( progname[0] == '#' )
|
|
progname++;
|
|
|
|
// post initializations
|
|
switch( host.type )
|
|
{
|
|
case HOST_NORMAL:
|
|
#ifdef _WIN32
|
|
Wcon_ShowConsole( false ); // hide console
|
|
#endif
|
|
// execute startup config and cmdline
|
|
if( FS_FileExists( va( "%s.rc", progname ), false )) // e.g. valve.rc
|
|
Cbuf_AddTextf( "exec %s.rc", progname );
|
|
else if( FS_FileExists( va( "%s.rc", exename ), false )) // e.g. quake.rc
|
|
Cbuf_AddTextf( "exec %s.rc", exename );
|
|
else if( FS_FileExists( va( "%s.rc", GI->gamefolder ), false )) // e.g. game.rc (ran from default launcher)
|
|
Cbuf_AddTextf( "exec %s.rc", GI->gamefolder );
|
|
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
|
|
|
|
if( Sys_GetParmFromCmdLine( "-timedemo", demoname ))
|
|
Cbuf_AddTextf( "timedemo %s\n", demoname );
|
|
|
|
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 )
|
|
#if XASH_WIN32 // FIXME: implement autocomplete on *nix
|
|
Con_Printf( "Type 'map <mapname>' to start game... (TAB-autocomplete is working too)\n" );
|
|
#else // !XASH_WIN32
|
|
Con_Printf( "Type 'map <mapname>' to start game...\n" );
|
|
#endif // !XASH_WIN32
|
|
|
|
// execute server.cfg after commandline
|
|
// so we have a chance to set servercfgfile
|
|
Cbuf_AddTextf( "exec %s\n", Cvar_VariableString( "servercfgfile" ));
|
|
Cbuf_Execute();
|
|
}
|
|
|
|
// check after all configs were executed
|
|
HPAK_CheckIntegrity( hpk_custom_file.string );
|
|
|
|
// 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 )
|
|
{
|
|
qboolean error = host.status == HOST_ERR_FATAL;
|
|
|
|
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 && !error )
|
|
Host_WriteConfig();
|
|
#endif
|
|
|
|
SV_Shutdown( "Server shutdown\n" );
|
|
SV_UnloadProgs();
|
|
SV_ShutdownFilter();
|
|
CL_Shutdown();
|
|
|
|
SoundList_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();
|
|
}
|