//======================================================================= // Copyright XashXT Group 2007 © // host.c - dedicated and shared host //======================================================================= #include "common.h" #include "netchan.h" #include "cm_local.h" #include "input.h" render_exp_t *re; host_parm_t host; // host parms stdlib_api_t com, newcom; dll_info_t render_dll = { "", NULL, "CreateAPI", NULL, NULL, 0, sizeof(render_exp_t), sizeof(stdlib_api_t) }; convar_t *host_serverstate; convar_t *host_gameloaded; convar_t *host_limitlocal; convar_t *host_cheats; convar_t *host_maxfps; convar_t *host_framerate; convar_t *host_video; qboolean sound_restart; // these cvars will be duplicated on each client across network int Host_ServerState( void ) { return Cvar_VariableInteger( "host_serverstate" ); } int Host_CompareFileTime( long ft1, long ft2 ) { if( ft1 < ft2 ) { return -1; } else if( ft1 > ft2 ) { return 1; } return 0; } void Host_ShutdownServer( void ) { if( !SV_Active()) return; com.strncpy( host.finalmsg, "Server was killed\n", MAX_STRING ); SV_Shutdown( false ); } void Host_Null( void ) { // just a stub for some commands in dedicated-mode } /* ================ Host_NewGame ================ */ qboolean Host_NewGame( const char *mapName, qboolean loadGame ) { qboolean iRet; iRet = SV_NewGame( mapName, loadGame ); return iRet; } /* ================ Host_EndGame ================ */ void Host_EndGame( const char *message, ... ) { va_list argptr; static char string[MAX_SYSPATH]; va_start( argptr, message ); com.vsprintf( string, message, argptr ); va_end( argptr ); MsgDev( D_INFO, "Host_EndGame: %s\n", string ); if( SV_Active()) { com.snprintf( host.finalmsg, sizeof( host.finalmsg ), "Host_EndGame: %s\n", string ); SV_Shutdown( false ); } if( host.type == HOST_DEDICATED ) Sys_Break( "Host_EndGame: %s\n", string ); // dedicated servers exit if( CL_NextDemo( )); else CL_Disconnect(); Host_AbortCurrentFrame (); } static void Host_DrawDebugCollision( cmdraw_t drawPoly ) { if( !drawPoly ) return; // FIXME: get collision polys here } int Host_CreateDecalList( decallist_t *pList, qboolean changelevel ) { if( !re ) return 0; return re->CreateDecalList( pList, changelevel ); } void Host_FreeRender( void ) { if( render_dll.link ) { SCR_Shutdown (); re->Shutdown( true ); Mem_Set( &re, 0, sizeof( re )); } Sys_FreeLibrary( &render_dll ); } qboolean Host_InitRender( void ) { static render_imp_t ri; launch_t CreateRender; qboolean result = false; ri.api_size = sizeof( render_imp_t ); // studio callbacks ri.UpdateScreen = SCR_UpdateScreen; ri.StudioEvent = CL_StudioEvent; ri.ShowCollision = Host_DrawDebugCollision; ri.GetClientEdict = CL_GetEntityByIndex; ri.GetPlayerInfo = CL_GetPlayerInfo; ri.GetLocalPlayer = CL_GetLocalPlayer; ri.GetMaxClients = CL_GetMaxClients; ri.DrawTriangles = Tri_DrawTriangles; ri.ExtraUpdate = CL_ExtraUpdate; ri.GetLerpFrac = CL_GetLerpFrac; ri.IsThirdPerson = CL_IsThirdPerson; ri.WndProc = IN_WndProc; Sys_LoadLibrary( host_video->string, &render_dll ); if( render_dll.link ) { CreateRender = (void *)render_dll.main; re = CreateRender( &newcom, &ri ); if( re->Init( true )) result = true; } // video system not started, shutdown refresh subsystem if( !result ) Host_FreeRender(); return result; } void Host_CheckChanges( void ) { int num_changes; qboolean audio_disabled = false; if( FS_CheckParm( "-nosound" )) { if( host.state == HOST_INIT ) audio_disabled = true; sound_restart = false; } if( host_video->modified || sound_restart ) { if( host_video->modified ) CL_ForceVid(); if( sound_restart ) CL_ForceSnd(); } else return; num_changes = 0; if( host_video->modified && CL_Active( )) { // we're in game and want keep decals when renderer is changed host.decalList = (decallist_t *)Z_Malloc( sizeof( decallist_t ) * MAX_RENDER_DECALS ); host.numdecals = Host_CreateDecalList( host.decalList, false ); } if(( host_video->modified || sound_restart ) && CL_Active( )) { host.soundList = (soundlist_t *)Z_Malloc( sizeof( soundlist_t ) * 128 ); host.numsounds = S_GetCurrentStaticSounds( host.soundList, 128, CHAN_STATIC ); Msg( "Total stored %i sounds\n", host.numsounds ); } S_StopAllSounds(); // don't let them loop during the restart // restart or change renderer while( host_video->modified ) { host_video->modified = false; // predict state Host_FreeRender(); // release render.dll if( !Host_InitRender( )) // load it again { if( num_changes > host.num_video_dlls ) { Sys_NewInstance( va("#%s", GI->gamefolder ), "fallback to dedicated mode\n" ); return; } if( !com.strcmp( host.video_dlls[num_changes], host_video->string )) num_changes++; // already trying - failed Cvar_FullSet( "host_video", host.video_dlls[num_changes], CVAR_INIT|CVAR_ARCHIVE ); num_changes++; } else SCR_Init (); } if( audio_disabled ) MsgDev( D_INFO, "Audio: Disabled\n" ); num_changes = 0; // restart sound engine if( sound_restart ) { S_Shutdown(); S_Init( host.hWnd ); sound_restart = false; } } /* ================ Host_AbortCurrentFrame aborts the current host frame and goes on with the next one ================ */ void Host_AbortCurrentFrame( void ) { longjmp( host.abortframe, 1 ); } /* ================== Host_SetServerState ================== */ void Host_SetServerState( int state ) { Cvar_FullSet( "host_serverstate", va( "%i", state ), CVAR_INIT ); } /* ================= Host_VidRestart_f Restart the video subsystem ================= */ void Host_VidRestart_f( void ) { host_video->modified = true; } /* ================= Host_SndRestart_f Restart the audio subsystem ================= */ void Host_SndRestart_f( void ) { sound_restart = true; } /* ================= Host_ChangeGame_f Change game modification ================= */ void Host_ChangeGame_f( void ) { int i; if( Cmd_Argc() != 2 ) { Msg( "Usage: game \n" ); return; } // validate gamedir for( i = 0; i < SI->numgames; i++ ) { if( !com.stricmp( SI->games[i]->gamefolder, Cmd_Argv( 1 ))) break; } if( i == SI->numgames ) Msg( "%s not exist\n", Cmd_Argv( 1 )); else if( !com.stricmp( GI->gamefolder, Cmd_Argv( 1 ))) Msg( "%s already active\n", Cmd_Argv( 1 )); else Sys_NewInstance( Cmd_Argv( 1 ), "Host_ChangeGame\n" ); } void Host_Minimize_f( void ) { if( host.hWnd ) ShowWindow( host.hWnd, SW_MINIMIZE ); } qboolean Host_IsLocalGame( void ) { if( CL_Active() && SV_Active() && CL_GetMaxClients() == 1 ) return true; return false; } static int num_decals; /* ================= Host_RegisterDecal ================= */ qboolean Host_RegisterDecal( const char *name ) { char shortname[CS_SIZE]; int i; if( !name || !name[0] ) return 0; FS_FileBase( name, shortname ); for( i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ ) { if( !com.stricmp( host.draw_decals[i], shortname )) return true; } if( i == MAX_DECALS ) { MsgDev( D_ERROR, "Host_RegisterDecal: MAX_DECALS limit exceeded\n" ); return false; } // register new decal com.strncpy( host.draw_decals[i], shortname, sizeof( host.draw_decals[i] )); num_decals++; return true; } /* ================= Host_InitDecals ================= */ void Host_InitDecals( void ) { search_t *t; int i; Mem_Set( host.draw_decals, 0, sizeof( host.draw_decals )); num_decals = 0; // lookup all decals in decals.wad t = FS_Search( "decals.wad/*.*", true ); for( i = 0; t && i < t->numfilenames; i++ ) { if( !Host_RegisterDecal( t->filenames[i] )) break; } if( t ) Mem_Free( t ); MsgDev( D_NOTE, "InitDecals: %i decals\n", num_decals ); } /* ================= Host_InitEvents ================= */ void Host_InitEvents( void ) { Mem_Set( host.events, 0, sizeof( host.events )); host.events_head = 0; host.events_tail = 0; } /* ================= Host_PushEvent ================= */ void Host_PushEvent( sys_event_t *event ) { sys_event_t *ev; static qboolean overflow = false; ev = &host.events[host.events_head & (MAX_SYSEVENTS-1)]; if( host.events_head - host.events_tail >= MAX_SYSEVENTS ) { if( !overflow ) { MsgDev( D_WARN, "Host_PushEvent overflow\n" ); overflow = true; } if( ev->data ) Mem_Free( ev->data ); host.events_tail++; } else overflow = false; *ev = *event; host.events_head++; } /* ================= Host_GetEvent ================= */ sys_event_t Host_GetEvent( void ) { if( host.events_head > host.events_tail ) { host.events_tail++; return host.events[(host.events_tail - 1) & (MAX_SYSEVENTS-1)]; } return Sys_GetEvent(); } /* ================= Host_EventLoop Returns false while events is out ================= */ void Host_EventLoop( void ) { sys_event_t ev; while( 1 ) { ev = Sys_GetEvent(); switch( ev.type ) { case SE_NONE: Cbuf_Execute(); return; case SE_KEY: Key_Event( ev.value[0], ev.value[1] ); break; case SE_CHAR: CL_CharEvent( ev.value[0] ); break; case SE_MOUSE: CL_MouseEvent( ev.value[0], ev.value[1] ); break; case SE_CONSOLE: Cbuf_AddText( va( "%s\n", ev.data )); break; default: MsgDev( D_ERROR, "Host_EventLoop: bad event type %i", ev.type ); return; } if( ev.data ) Mem_Free( ev.data ); } } /* =================== Host_RestartAmbientSounds Restarts the sounds to let demo writing them =================== */ void Host_RestartAmbientSounds( void ) { soundlist_t soundInfo[100]; int i, nSounds; if( !SV_Active( )) return; nSounds = S_GetCurrentStaticSounds( soundInfo, 100, CHAN_STATIC ); for( i = 0; i < nSounds; i++) { if( soundInfo[i].looping && soundInfo[i].entnum != -1 ) { S_StopSound( soundInfo[i].entnum, soundInfo[i].entchannel, soundInfo[i].name ); // FIXME: replace with SV_StartAmbientSound SV_StartSound( pfnPEntityOfEntIndex( soundInfo[i].entnum ), CHAN_STATIC, soundInfo[i].name, soundInfo[i].volume, soundInfo[i].attenuation, 0, soundInfo[i].pitch ); } } } /* =================== Host_FilterTime Returns false if the time is too short to run a frame =================== */ qboolean Host_FilterTime( float time ) { static double oldtime; float fps; host.realtime += time; // dedicated's tic_rate regulates server frame rate. Don't apply fps filter here. fps = host_maxfps->value; if( fps != 0 ) { float minframetime; // limit fps to withing tolerable range fps = bound( MIN_FPS, fps, MAX_FPS ); minframetime = 1.0f / fps; if(( host.realtime - oldtime ) < minframetime ) { // framerate is too high return false; } } host.frametime = host.realtime - oldtime; host.realframetime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME ); oldtime = host.realtime; if( host_framerate->value > 0 && ( Host_IsLocalGame() || CL_IsPlaybackDemo() )) { float fps = host_framerate->value; if( fps > 1 ) fps = 1.0f / fps; host.frametime = fps; } else { // don't allow really long or short frames host.frametime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME ); } return true; } /* ================= Host_Frame ================= */ void Host_Frame( float time ) { if( setjmp( host.abortframe )) return; Host_InputFrame (); // input frame Host_EventLoop(); // decide the simulation time if( !Host_FilterTime( time )) return; Host_ServerFrame (); // server frame Host_ClientFrame (); // client frame host.framecount++; } /* ================ Host_Print Handles cursor positioning, line wrapping, etc All console printing must go through this in order to be logged to disk If no console is visible, the text will appear at the top of the game window ================ */ void Host_Print( const char *txt ) { if( host.rd.target ) { if(( com.strlen( txt ) + com.strlen( host.rd.buffer )) > ( host.rd.buffersize - 1 )) { if( host.rd.flush ) { host.rd.flush( host.rd.address, host.rd.target, host.rd.buffer ); *host.rd.buffer = 0; } } com.strcat( host.rd.buffer, txt ); return; } Con_Print( txt ); // echo to client console } /* ================= 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; va_start( argptr, error ); com.vsprintf( hosterror1, error, argptr ); va_end( argptr ); CL_WriteMessageHistory (); // before com.error call if( host.framecount < 3 || host.state == HOST_SHUTDOWN ) { SV_SysError( hosterror1 ); com.error( "Host_InitError: %s", hosterror1 ); } else if( host.framecount == host.errorframe ) { SV_SysError( hosterror2 ); com.error( "Host_MultiError: %s", hosterror2 ); return; } else Msg( "Host_Error: %s", hosterror1 ); if( recursive ) { Msg( "Host_RecursiveError: %s", hosterror2 ); SV_SysError( hosterror1 ); com.error( hosterror1 ); return; // don't multiple executes } recursive = true; com.strncpy( hosterror2, hosterror1, MAX_SYSPATH ); host.errorframe = host.framecount; // to avoid multply calls per frame com.sprintf( host.finalmsg, "Server crashed: %s\n", hosterror1 ); SV_Shutdown( false ); CL_Drop(); // drop clients recursive = false; Host_AbortCurrentFrame(); host.state = HOST_ERROR; } 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"; SV_SysError( error ); com.error( "%s\n", error ); } void Net_Error_f( void ) { com.strncpy( host.finalmsg, Cmd_Argv( 1 ), sizeof( host.finalmsg )); SV_ForceError(); } /* ================= Host_Crash_f ================= */ static void Host_Crash_f( void ) { *(int *)0 = 0xffffffff; } void Host_InitCommon( const int argc, const char **argv ) { dll_info_t check_vid; search_t *dlls; int i; newcom = com; // overload some funcs newcom.error = Host_Error; // get developer mode host.developer = SI->developer; FS_LoadGameInfo( NULL ); Image_Init( GI->gameHint, -1 ); Sound_Init( GI->gameHint, -1 ); host.mempool = Mem_AllocPool( "Zone Engine" ); Host_InitEvents(); Host_InitDecals(); IN_Init(); // initialize video multi-dlls system host.num_video_dlls = 0; // make sure what global copy has no changed with any dll checking Mem_Copy( &check_vid, &render_dll, sizeof( dll_info_t )); // checking dlls don't invoke crash! check_vid.crash = false; dlls = FS_Search( "*.dll", true ); // couldn't find any dlls, render is missing (but i'm don't know how laucnher find engine :) // probably this should never happen if( !dlls ) Sys_NewInstance( "©", "" ); for( i = 0; i < dlls->numfilenames; i++ ) { if( !com.strnicmp( "vid_", dlls->filenames[i], 4 )) { // make sure what found library is valid if( Sys_LoadLibrary( dlls->filenames[i], &check_vid )) { MsgDev( D_NOTE, "Video[%i]: %s\n", host.num_video_dlls, dlls->filenames[i] ); host.video_dlls[host.num_video_dlls] = copystring( dlls->filenames[i] ); Sys_FreeLibrary( &check_vid ); host.num_video_dlls++; } } } Mem_Free( dlls ); } void Host_FreeCommon( void ) { Netchan_Shutdown(); Mem_FreePool( &host.mempool ); } /* ================= Host_Init ================= */ void Host_Init( const int argc, const char **argv ) { host.state = HOST_INIT; // initialzation started host.type = g_Instance(); Host_InitCommon( argc, argv ); Key_Init(); if( host.type != HOST_DEDICATED ) { // NOTE: client.dll must be loaded first to get mlook state from config.cfg if( !CL_LoadProgs( va( "%s/client.dll", GI->dll_path ))) Host_Error( "CL_InitGame: can't initialize client.dll\n" ); // get the user configuration Cbuf_AddText( "exec config.cfg\n" ); Cbuf_Execute(); } // init commands and vars if( host.developer >= 3 ) { 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"); Cmd_AddCommand ( "net_error", Net_Error_f, "send network bad message from random place"); } host_video = Cvar_Get( "host_video", "vid_gl.dll", CVAR_INIT|CVAR_ARCHIVE, "name of video rendering library"); host_cheats = Cvar_Get( "sv_cheats", "0", CVAR_LATCH, "allow cheat variables to enable" ); host_maxfps = Cvar_Get( "fps_max", "72", CVAR_ARCHIVE, "host fps upper limit" ); host_framerate = Cvar_Get( "host_framerate", "0", 0, "locks frame timing to this value in seconds" ); host_serverstate = Cvar_Get( "host_serverstate", "0", CVAR_INIT, "displays current server state" ); host_gameloaded = Cvar_Get( "host_gameloaded", "0", CVAR_INIT, "inidcates a loaded game.dll" ); host_limitlocal = Cvar_Get( "host_limitlocal", "0", 0, "apply cl_cmdrate and rate to loopback connection" ); // content control Cvar_Get( "violence_hgibs", "1", CVAR_INIT|CVAR_ARCHIVE, "content control disables human gibs" ); Cvar_Get( "violence_agibs", "1", CVAR_INIT|CVAR_ARCHIVE, "content control disables alien gibs" ); Cvar_Get( "violence_hblood", "1", CVAR_INIT|CVAR_ARCHIVE, "content control disables human blood" ); Cvar_Get( "violence_ablood", "1", CVAR_INIT|CVAR_ARCHIVE, "content control disables alien blood" ); sound_restart = true; // initialize sound engine if( host.type != HOST_DEDICATED ) { // when we in developer-mode automatically turn cheats on if( host.developer > 1 ) Cvar_SetFloat( "sv_cheats", 1.0f ); } Mod_Init(); NET_Init(); Netchan_Init(); SV_Init(); CL_Init(); if( host.type == HOST_DEDICATED ) { Cmd_AddCommand( "quit", Sys_Quit, "quit the game" ); Cmd_AddCommand( "exit", Sys_Quit, "quit the game" ); Cmd_AddCommand( "@crashed", Host_Null, "" ); // dedicated servers using settings from server.cfg file Cbuf_AddText( va( "exec %s\n", Cvar_VariableString( "servercfgfile" ))); Cbuf_Execute(); Cbuf_AddText( va( "map %s\n", Cvar_VariableString( "defaultmap" ))); } else { Cmd_AddCommand( "minimize", Host_Minimize_f, "minimize main window to tray" ); Cmd_AddCommand( "vid_restart", Host_VidRestart_f, "restarts video system" ); Cmd_AddCommand( "snd_restart", Host_SndRestart_f, "restarts audio system" ); } // allow to change game from the console Cmd_AddCommand( "game", Host_ChangeGame_f, "change game" ); host.errorframe = 0; Cbuf_Execute(); SCR_CheckStartupVids(); // must be last } /* ================= Host_Main ================= */ void Host_Main( void ) { static double oldtime, newtime; oldtime = Sys_DoubleTime(); // main window message loop while( host.type != HOST_OFFLINE ) { newtime = Sys_DoubleTime (); Host_Frame( newtime - oldtime ); oldtime = newtime; } } /* ================= Host_Shutdown ================= */ void Host_Free( void ) { if( host.state == HOST_SHUTDOWN ) return; host.state = HOST_SHUTDOWN; // prepare host to normal shutdown com.strncpy( host.finalmsg, "Server shutdown\n", MAX_STRING ); Mod_Shutdown(); Host_FreeRender(); S_Shutdown(); SV_Shutdown( false ); CL_Shutdown(); NET_Shutdown(); Host_FreeCommon(); } // main DLL entry point BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ) { return TRUE; } /* ================= Engine entry point ================= */ launch_exp_t EXPORT *CreateAPI( stdlib_api_t *input, void *unused ) { static launch_exp_t Host; com = *input; Host.api_size = sizeof( launch_exp_t ); Host.com_size = sizeof( stdlib_api_t ); Host.Init = Host_Init; Host.Main = Host_Main; Host.Free = Host_Free; Host.CPrint = Host_Print; Host.CmdForward = Cmd_ForwardToServer; Host.CmdComplete = Cmd_AutoComplete; return &Host; }