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