|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef XASH_SDL
|
|
|
|
#include <SDL.h>
|
|
|
|
#endif // XASH_SDL
|
|
|
|
#include <stdarg.h> // va_args
|
|
|
|
#include <errno.h> // errno
|
|
|
|
#include <string.h> // strerror
|
|
|
|
#ifndef _WIN32
|
|
|
|
#include <unistd.h> // fork
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#endif
|
|
|
|
#ifdef __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 "mathlib.h"
|
|
|
|
#include "input.h"
|
|
|
|
#include "enginefeatures.h"
|
|
|
|
#include "render_api.h" // decallist_t
|
|
|
|
|
|
|
|
|
|
|
|
typedef void (*pfnChangeGame)( const char *progname );
|
|
|
|
|
|
|
|
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_gameloaded;
|
|
|
|
convar_t *host_clientloaded;
|
|
|
|
convar_t *host_limitlocal;
|
|
|
|
convar_t *host_maxfps;
|
|
|
|
convar_t *host_framerate;
|
|
|
|
convar_t *con_gamemaps;
|
|
|
|
convar_t *build, *ver;
|
|
|
|
|
|
|
|
int Host_CompareFileTime( long ft1, long 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;
|
|
|
|
|
|
|
|
#ifndef 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" );
|
|
|
|
#ifndef 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 )
|
|
|
|
{
|
|
|
|
if( Host_IsDedicated() )
|
|
|
|
{
|
|
|
|
// let the dedicated server some sleep
|
|
|
|
Sys_Sleep( 1 );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if( host.status == HOST_NOFOCUS )
|
|
|
|
{
|
|
|
|
if( SV_Active() && CL_IsInGame( ))
|
|
|
|
Sys_Sleep( 1 ); // listenserver
|
|
|
|
else Sys_Sleep( 20 ); // sleep 20 ms otherwise
|
|
|
|
}
|
|
|
|
else if( host.status == HOST_SLEEP )
|
|
|
|
{
|
|
|
|
// completely sleep in minimized state
|
|
|
|
Sys_Sleep( 20 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
char *f, *txt;
|
|
|
|
size_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;
|
|
|
|
|
|
|
|
// NOTE: we should play demos with same fps as it was recorded
|
|
|
|
#ifndef XASH_DEDICATED
|
|
|
|
if( CL_IsPlaybackDemo() || CL_IsRecordDemo( ))
|
|
|
|
{
|
|
|
|
fps = CL_GetDemoFramerate();
|
|
|
|
}
|
|
|
|
else if( Host_IsLocalGame( ))
|
|
|
|
{
|
|
|
|
fps = host_maxfps->value;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
if( Host_IsDedicated() )
|
|
|
|
{
|
|
|
|
fps = sys_ticrate.value;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fps = host_maxfps->value;
|
|
|
|
fps = bound( MIN_FPS, fps, MAX_FPS );
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef XASH_DEDICATED
|
|
|
|
// 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
|
|
|
|
|
|
|
|
host.framecount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Host_Error
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
void 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 )
|
|
|
|
{
|
|
|
|
*(int *)0 = 0xffffffff;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Host_InitCommon
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bChangeGame )
|
|
|
|
{
|
|
|
|
char dev_level[4];
|
|
|
|
int developer = 0;
|
|
|
|
const char *baseDir;
|
|
|
|
char ticrate[16];
|
|
|
|
|
|
|
|
// 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( "-noch" ) )
|
|
|
|
Sys_SetupCrashHandler();
|
|
|
|
|
|
|
|
// to be accessed later
|
|
|
|
if( ( host.daemonized = Sys_CheckParm( "-daemonize" ) ) )
|
|
|
|
{
|
|
|
|
#if defined(_POSIX_VERSION) && !defined(XASH_MOBILE_PLATFORM)
|
|
|
|
pid_t daemon;
|
|
|
|
|
|
|
|
daemon = fork();
|
|
|
|
|
|
|
|
if( daemon < 0 )
|
|
|
|
{
|
|
|
|
Host_Error( "fork() failed: %s\n", strerror( errno ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( daemon > 0 )
|
|
|
|
{
|
|
|
|
// parent
|
|
|
|
Con_Reportf( "Child pid: %i\n", daemon );
|
|
|
|
exit( 0 );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// don't be closed by parent
|
|
|
|
if( setsid() < 0 )
|
|
|
|
{
|
|
|
|
Host_Error( "setsid() failed: %s\n", strerror( errno ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
// set permissions
|
|
|
|
umask( 0 );
|
|
|
|
|
|
|
|
// engine will still use stdin/stdout,
|
|
|
|
// so just redirect them to /dev/null
|
|
|
|
close( STDIN_FILENO );
|
|
|
|
close( STDOUT_FILENO );
|
|
|
|
close( STDERR_FILENO );
|
|
|
|
open("/dev/null", O_RDONLY); // becomes stdin
|
|
|
|
open("/dev/null", O_RDWR); // stdout
|
|
|
|
open("/dev/null", O_RDWR); // stderr
|
|
|
|
|
|
|
|
// fallthrough
|
|
|
|
}
|
|
|
|
#elif defined(XASH_MOBILE_PLATFORM)
|
|
|
|
Sys_Error( "Can't run in background on mobile platforms!" );
|
|
|
|
#else
|
|
|
|
Sys_Error( "Daemonize not supported on this platform!" );
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
if( ( baseDir = getenv( "XASH3D_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 defined(XASH_SDL)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
if( host.rootdir[Q_strlen( host.rootdir ) - 1] == '/' )
|
|
|
|
host.rootdir[Q_strlen( host.rootdir ) - 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;
|
|
|
|
|
|
|
|
if(( roDir = getenv( "XASH3D_RODIR" )))
|
|
|
|
Q_strncpy( host.rodir, roDir, sizeof( host.rodir ));
|
|
|
|
}
|
|
|
|
|
|
|
|
if( host.rodir[0] && host.rodir[Q_strlen( host.rodir ) - 1] == '/' )
|
|
|
|
host.rodir[Q_strlen( host.rodir ) - 1] = 0;
|
|
|
|
|
|
|
|
host.enabledll = !Sys_CheckParm( "-nodll" );
|
|
|
|
|
|
|
|
#ifdef DLL_LOADER
|
|
|
|
if( host.enabledll )
|
|
|
|
Setup_LDT_Keeper( ); // Must call before creating any thread
|
|
|
|
#endif
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
#ifdef 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
|
|
|
|
|
|
|
|
#ifdef XASH_SDL
|
|
|
|
// should work even if it failed
|
|
|
|
SDL_Init( SDL_INIT_TIMER );
|
|
|
|
|
|
|
|
if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_EVENTS ) )
|
|
|
|
{
|
|
|
|
Sys_Warn( "SDL_Init failed: %s", SDL_GetError() );
|
|
|
|
host.type = HOST_DEDICATED;
|
|
|
|
}
|
|
|
|
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
|
|
|
|
#if defined XASH_GLES && !defined __EMSCRIPTEN__ && !TARGET_OS_IOS
|
|
|
|
SDL_SetHint( SDL_HINT_OPENGL_ES_DRIVER, "1" );
|
|
|
|
#endif
|
|
|
|
|
|
|
|
SDL_StopTextInput();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if ( !host.rootdir[0] || 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();
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
Wcon_CreateConsole(); // system console used by dedicated server or show fatal errors
|
|
|
|
#endif
|
|
|
|
// 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
|
|
|
|
Cmd_AddCommand( "exec", Host_Exec_f, "execute a script file" );
|
|
|
|
Cmd_AddCommand( "memlist", Host_MemStats_f, "prints memory pool information" );
|
|
|
|
|
|
|
|
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_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_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()), FCVAR_READ_ONLY, "returns a current build number" );
|
|
|
|
|
|
|
|
ver = Cvar_Get( "ver", va( "%i/%s (hw build %i)", PROTOCOL_VERSION, XASH_VERSION, Q_buildnum()), FCVAR_READ_ONLY, "shows an engine version" );
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
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" );
|
|
|
|
|
|
|
|
// 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 ));
|
|
|
|
|
|
|
|
#ifndef XASH_DEDICATED
|
|
|
|
if( host.type == HOST_NORMAL )
|
|
|
|
Host_WriteConfig();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
SV_Shutdown( "Server shutdown\n" );
|
|
|
|
CL_Shutdown();
|
|
|
|
|
|
|
|
Mod_Shutdown();
|
|
|
|
NET_Shutdown();
|
|
|
|
Host_FreeCommon();
|
|
|
|
#ifdef _WIN32
|
|
|
|
Wcon_DestroyConsole();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// must be last, console uses this
|
|
|
|
Mem_FreePool( &host.mempool );
|
|
|
|
|
|
|
|
// restore filter
|
|
|
|
Sys_RestoreCrashHandler();
|
|
|
|
Sys_CloseLog();
|
|
|
|
}
|