2018-04-13 19:23:45 +03:00
/*
cl_main . c - client main loop
Copyright ( C ) 2009 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 "common.h"
# include "client.h"
# include "net_encode.h"
# include "cl_tent.h"
# include "input.h"
2018-04-13 19:58:17 +03:00
# include "kbutton.h"
2018-04-13 19:23:45 +03:00
# include "vgui_draw.h"
2018-04-17 03:53:01 +03:00
# include "library.h"
2019-02-18 21:25:26 +03:00
# include "vid_common.h"
2023-01-06 00:14:49 +03:00
# include "pm_local.h"
2024-10-10 19:06:26 +03:00
# include "multi_emulator.h"
2018-04-13 19:23:45 +03:00
2024-11-05 21:57:31 +03:00
# define MAX_CMD_BUFFER 8000
# define CL_CONNECTION_TIMEOUT 15.0f
# define CL_CONNECTION_RETRIES 10
# define CL_TEST_RETRIES 5
2018-04-13 19:23:45 +03:00
2025-03-17 10:01:06 -04:00
//Xrasher random name selection
# define XRASHER_NAMES 263
const char * xrasher_names [ XRASHER_NAMES ] = {
" TomFazano " ,
" SlavaBozhie " ,
" saintlyWarrior " ,
" FAITHFUL_GUARDIAN " ,
" holy_light_seeker " ,
" DivinePathfinder " ,
" orthodox_knight " ,
" SeraphicScribe " ,
" gracefulPilgrim " ,
" CROWNED_IN_FAITH " ,
" VirtuousVanguard " ,
" celestialHerald " ,
" eternal_hope " ,
" BlessedDefender " ,
" trinityChampion " ,
" peacefuldisciple "
" FerventPrayer " ,
" cobson " ,
" feraljak " ,
" soyboy27 " ,
" CHRIST_WON " ,
" nothacking_justgood58 " ,
" Kingforever " ,
" HappyPeanut " ,
" Davidcat " ,
" FrogPaste " ,
" Matikas48 " ,
" luke_smith-http://lukesmith.xyz " ,
" LukeSmith-http://lukesmith.xyz " ,
" matika_adventure " ,
" GabeNewell " ,
" etymologically " ,
" TempleOS " ,
" t3rryd4vis " ,
" coughmeyer " ,
" admer45 " ,
" dimbeak " ,
" simon " ,
" therealfreeman " ,
" retard " ,
" DavidShekelberg " ,
" AdonaiSilverman " ,
" rothschild " ,
" rosenberg " ,
" MentalOutlaw " ,
" ban_video_games " ,
" keepitfoss " ,
" GMan " ,
" Nihilanth " ,
" Zombie " ,
" epistemology " ,
" the_logos " ,
" RealMichelThomas " ,
" abraham " ,
" abram " ,
" Itslindy " ,
" ITS_OVER " ,
" gigachud " ,
" gigathud " ,
" WriteInC " ,
" the-third-temple " ,
" xmmp_gt_matrix " ,
" HUMPHREYSALATIN " ,
" veganism_sucks " ,
" I_HATE_HALFLIFE " ,
" I_HATE_VIDEOGAMES " ,
" UseSearx " ,
" IUseYandex " ,
" wrote_this_one " ,
" baskingshark " ,
" sharks " ,
" shellsavant " ,
" ilovecasey " ,
" King4ALife " ,
" SuperDogBoy " ,
" Woods " ,
" JackRobinson " ,
" Gordon!!!! " ,
" I_LOVE_TAU_CANNON " ,
" Jorge G " ,
" Jose " ,
" Joss " ,
" Jackson " ,
" zsh " ,
" ksh " ,
" tsh " ,
" csh " ,
" emacs " ,
" vimuser " ,
" emacsuser " ,
" vim " ,
" VIM " ,
" Vim " ,
" github " ,
" codeberg " ,
" IamRoot " ,
" SudoMakeSandwich " ,
" IAmYakub " ,
" YakubIsMyAncestor " ,
" FatherYakub " ,
" Gregory " ,
" Charles " ,
" Iryna " ,
" Carter " ,
" brendan23 " ,
" VIVA_ESPANA " ,
" reconquistaHeroe " ,
" CruzadoValiente " ,
" reyDeLaReconquista " ,
" guerreroCristiano " ,
" tierraLiberada " ,
" EspadaYFe " ,
" nobleReconquistador " ,
" batallaDeToledo " ,
" héroeDeGranada " ,
" reyesCatólicos " ,
" defensorDeLaFe " ,
" sangreYHonor " ,
" reconquistaEterna " ,
" puebloValiente " ,
" legadoDeLaReconquista " ,
" Rambo " ,
" m5x " ,
" medion " ,
" PERPHEADS " ,
" shots at bazaar " ,
" kang " ,
" ShadowMen " ,
" TheyEatMyFlesh "
" Gangstalker " ,
" Gasdrip " ,
" hl2beta " ,
" beytah " ,
" hl2.exe " ,
" hl1.exe " ,
" hl.exe " ,
" css.exe " ,
" XASHER " ,
" xrasher.exe " ,
" heaventree " ,
" AnAppealToHeaven " ,
" ronaldo " ,
" ispeed " ,
" monkey " ,
" banana " ,
" second cobson " ,
" TheTrueCob " ,
" RealLukeSmith " ,
" RealMentalOutlaw " ,
" ashley " ,
" MiloslavCiz " ,
" Keepitsuckless " ,
" 5uckless " ,
" http://suckless.org " ,
" http://libgen.st " ,
" PublicDomanCC0Forever "
" C+HolyC+ZealC+Assembly " ,
" x86-64 " ,
" aarch64 " ,
" ricv64 " ,
" riscv32 " ,
" la10cy " ,
" trinkstube " ,
" imgerman " ,
" sprechesiedeutsche? " ,
" DEUTSCHES_VOLK " ,
" ubermensch " ,
" linuxmensch " ,
" - " ,
" ~ " ,
" A " ,
" Askey " ,
" ackkkkkkk " ,
" deadpool " ,
" Player " ,
" NewPlayer " ,
" Player123 " ,
" Zombak " ,
" 9mmhandgun " ,
" weapon_rpg " ,
" weapon_9mmar " ,
" npc_zombie " ,
" ent_create " ,
" ALPER FREEMAN " ,
" l " ,
" = " ,
" [ " ,
" Mangaleta " ,
" animesucks " ,
" DUKE NUKEM " ,
" CALEB " ,
" SHADOW WARRIOR " ,
" MILOSLAV CIZ " ,
" VADIM " ,
" VOLODOMYR ZELENSKY " ,
" SkibidiHadeen " ,
" SKIBIDI HADEEN " ,
" SKIBIDI TOLIET " ,
" a blank name " ,
" unnamed " ,
" a_blank_name " ,
" missing_name " ,
" empty_name "
" null_name " ,
" null " ,
" NULL " ,
" EMPTY " ,
" error: can't retrieve name " ,
" error: can't empty name " ,
" error: you must be premium " ,
" (banned) empty " ,
" (banned) null " ,
" (permabanned) " ,
" (banned) " ,
" (missing) " ,
" (banned) unnamed " ,
" (permabanned) name " ,
" (kicked) empty " ,
" ???????????? " ,
" !!!!!!!!!!!! " ,
" @@@@@@@@@@@@ " ,
" ############ " ,
" $$$$$$$$$$$$ " ,
" %%%%%%%%%%%% " ,
" ^^^^^^^^^^^^ " ,
" &&&&&&&&&&&& " ,
" JOHN WICK " ,
" HACI AZER? " ,
" ESPANA RONALDO " ,
" RECONQUISTA " ,
" TempleOSROCKS " ,
" HALFLIFE_SUCKS " ,
" BAN_HALFLIFE " ,
" I_HATE_GAMES " ,
" Ban_Computers " ,
" Techonology_Is-bad " ,
" You'reHacking " ,
" '''''''''''' " ,
" ))))))))))))) " ,
" ((((((((((((( "
" {{{{{{{{{{{{{ "
" }}}}}}}}}}}}} " ,
" ============= " ,
" sssssssssssssss " ,
" wwwwwwwwwwwwwwww " ,
" vvvvvvvvvvvvvvvv " ,
" zzzzzzzzzzzzzzzz " ,
" ZZZZZZZZZZZZZZZZ "
" BubGames " ,
" bub_games " ,
" RealBubGames " ,
" bub " ,
" b " ,
" 5 " ,
" soy " ,
" DontHurtEachOther " ,
" AnarchPacifist " ,
" syndacilst_larper " ,
" pagan_larper " ,
" you_larp " ,
" itneuhitduetihtdh " ,
" ISeeYou " ,
" essen " ,
} ;
/*********************************************
* Description - Xrasher Aim Assist
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CL_AimAssist_f ( void ) ;
void CL_AimAssist_f ( void ) {
Con_Printf ( " Xrasher aimbot activated... \n " ) ;
if ( xrasher_aimnear . value = = 1 ) {
xrasher_aimnear . value = 0 ;
}
else {
xrasher_aimnear . value = 1 ;
}
}
/*********************************************
* Description - Xrasher picking a random name to help blend in
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CL_XrasherNewName ( void ) ;
void CL_XrasherNewName ( void ) {
const char * random_name = xrasher_names [ rand ( ) % XRASHER_NAMES ] ;
Cvar_Set ( " name " , random_name ) ;
Msg ( " Xrasher has changed your identity to: %s \n " , random_name ) ;
}
2024-10-07 20:59:40 +03:00
CVAR_DEFINE_AUTO ( showpause , " 1 " , 0 , " show pause logo when paused " ) ;
2021-11-03 23:42:44 +06:00
CVAR_DEFINE_AUTO ( mp_decals , " 300 " , FCVAR_ARCHIVE , " decals limit in multiplayer " ) ;
2024-06-13 05:59:49 +03:00
static CVAR_DEFINE_AUTO ( dev_overview , " 0 " , 0 , " draw level in overview-mode " ) ;
static CVAR_DEFINE_AUTO ( cl_resend , " 6.0 " , 0 , " time to resend connect " ) ;
2023-10-27 07:25:09 +03:00
CVAR_DEFINE ( cl_allow_download , " cl_allowdownload " , " 1 " , FCVAR_ARCHIVE , " allow to downloading resources from the server " ) ;
2024-06-13 05:59:49 +03:00
static CVAR_DEFINE ( cl_allow_upload , " cl_allowupload " , " 1 " , FCVAR_ARCHIVE , " allow to uploading resources to the server " ) ;
2018-04-13 19:23:45 +03:00
CVAR_DEFINE_AUTO ( cl_download_ingame , " 1 " , FCVAR_ARCHIVE , " allow to downloading resources while client is active " ) ;
2024-06-11 07:47:01 +03:00
static CVAR_DEFINE_AUTO ( cl_logofile , " lambda " , FCVAR_ARCHIVE , " player logo name " ) ;
static CVAR_DEFINE_AUTO ( cl_logocolor , " orange " , FCVAR_ARCHIVE , " player logo color " ) ;
static CVAR_DEFINE_AUTO ( cl_logoext , " bmp " , FCVAR_ARCHIVE , " temporary cvar to tell engine which logo must be packed " ) ;
CVAR_DEFINE_AUTO ( cl_logomaxdim , " 96 " , FCVAR_ARCHIVE , " maximum decal dimension " ) ;
2024-06-13 05:59:49 +03:00
static CVAR_DEFINE_AUTO ( cl_test_bandwidth , " 1 " , FCVAR_ARCHIVE , " test network bandwith before connection " ) ;
2023-05-19 06:49:14 +03:00
CVAR_DEFINE ( cl_draw_particles , " r_drawparticles " , " 1 " , FCVAR_CHEAT , " render particles " ) ;
CVAR_DEFINE ( cl_draw_tracers , " r_drawtracers " , " 1 " , FCVAR_CHEAT , " render tracers " ) ;
CVAR_DEFINE ( cl_draw_beams , " r_drawbeams " , " 1 " , FCVAR_CHEAT , " render beams " ) ;
static CVAR_DEFINE_AUTO ( rcon_address , " " , FCVAR_PRIVILEGED , " remote control address " ) ;
CVAR_DEFINE_AUTO ( cl_timeout , " 60 " , 0 , " connect timeout (in-seconds) " ) ;
CVAR_DEFINE_AUTO ( cl_nopred , " 0 " , FCVAR_ARCHIVE | FCVAR_USERINFO , " disable client movement prediction " ) ;
2024-06-13 05:59:49 +03:00
static CVAR_DEFINE_AUTO ( cl_nodelta , " 0 " , 0 , " disable delta-compression for server messages " ) ;
2023-05-19 06:49:14 +03:00
CVAR_DEFINE ( cl_crosshair , " crosshair " , " 1 " , FCVAR_ARCHIVE , " show weapon chrosshair " ) ;
static CVAR_DEFINE_AUTO ( cl_cmdbackup , " 10 " , FCVAR_ARCHIVE , " how many additional history commands are sent " ) ;
CVAR_DEFINE_AUTO ( cl_showerror , " 0 " , FCVAR_ARCHIVE , " show prediction error " ) ;
CVAR_DEFINE_AUTO ( cl_bmodelinterp , " 1 " , FCVAR_ARCHIVE , " enable bmodel interpolation " ) ;
2024-06-13 05:59:49 +03:00
static CVAR_DEFINE_AUTO ( cl_lightstyle_lerping , " 0 " , FCVAR_ARCHIVE , " enables animated light lerping (perfomance option) " ) ;
2023-05-19 06:49:14 +03:00
CVAR_DEFINE_AUTO ( cl_idealpitchscale , " 0.8 " , 0 , " how much to look up/down slopes and stairs when not using freelook " ) ;
CVAR_DEFINE_AUTO ( cl_nosmooth , " 0 " , FCVAR_ARCHIVE , " disable smooth up stair climbing " ) ;
CVAR_DEFINE_AUTO ( cl_smoothtime , " 0.1 " , FCVAR_ARCHIVE , " time to smooth up " ) ;
CVAR_DEFINE_AUTO ( cl_clockreset , " 0.1 " , FCVAR_ARCHIVE , " frametime delta maximum value before reset " ) ;
2024-06-13 05:59:49 +03:00
static CVAR_DEFINE_AUTO ( cl_fixtimerate , " 7.5 " , FCVAR_ARCHIVE , " time in msec to client clock adjusting " ) ;
2023-05-19 06:49:14 +03:00
CVAR_DEFINE_AUTO ( hud_fontscale , " 1.0 " , FCVAR_ARCHIVE | FCVAR_LATCH , " scale hud font texture " ) ;
2024-02-09 07:25:47 +03:00
CVAR_DEFINE_AUTO ( hud_fontrender , " 0 " , FCVAR_ARCHIVE , " hud font render mode (0: additive, 1: holes, 2: trans) " ) ;
2023-05-19 06:49:14 +03:00
CVAR_DEFINE_AUTO ( hud_scale , " 0 " , FCVAR_ARCHIVE | FCVAR_LATCH , " scale hud at current resolution " ) ;
2024-01-29 04:58:04 +03:00
CVAR_DEFINE_AUTO ( hud_scale_minimal_width , " 640 " , FCVAR_ARCHIVE | FCVAR_LATCH , " if hud_scale results in a HUD virtual screen smaller than this value, it won't be applied " ) ;
2023-05-19 06:49:14 +03:00
CVAR_DEFINE_AUTO ( cl_solid_players , " 1 " , 0 , " Make all players not solid (can't traceline them) " ) ;
CVAR_DEFINE_AUTO ( cl_updaterate , " 20 " , FCVAR_USERINFO | FCVAR_ARCHIVE , " refresh rate of server messages " ) ;
CVAR_DEFINE_AUTO ( cl_showevents , " 0 " , FCVAR_ARCHIVE , " show events playback " ) ;
2025-02-25 19:52:48 +03:00
CVAR_DEFINE_AUTO ( cl_cmdrate , " 30 " , FCVAR_ARCHIVE , " Max number of command packets sent to server per second " ) ;
2023-05-19 06:49:14 +03:00
CVAR_DEFINE ( cl_interp , " ex_interp " , " 0.1 " , FCVAR_ARCHIVE | FCVAR_FILTERABLE , " Interpolate object positions starting this many seconds in past " ) ;
2024-06-13 05:59:49 +03:00
CVAR_DEFINE_AUTO ( cl_nointerp , " 0 " , 0 , " disable interpolation of entities and players " ) ;
2023-05-19 06:49:14 +03:00
static CVAR_DEFINE_AUTO ( cl_dlmax , " 0 " , FCVAR_USERINFO | FCVAR_ARCHIVE , " max allowed outcoming fragment size " ) ;
static CVAR_DEFINE_AUTO ( cl_upmax , " 1200 " , FCVAR_ARCHIVE , " max allowed incoming fragment size " ) ;
CVAR_DEFINE_AUTO ( cl_lw , " 1 " , FCVAR_ARCHIVE | FCVAR_USERINFO , " enable client weapon predicting " ) ;
CVAR_DEFINE_AUTO ( cl_charset , " utf-8 " , FCVAR_ARCHIVE , " 1-byte charset to use (iconv style) " ) ;
2025-01-23 13:57:15 +03:00
CVAR_DEFINE_AUTO ( cl_trace_consistency , " 0 " , FCVAR_ARCHIVE , " enable consistency info tracing (good for developers) " ) ;
2024-10-08 04:06:49 +03:00
CVAR_DEFINE_AUTO ( cl_trace_stufftext , " 0 " , FCVAR_ARCHIVE , " enable stufftext (server-to-client console commands) tracing (good for developers) " ) ;
2024-02-17 22:16:39 +03:00
CVAR_DEFINE_AUTO ( cl_trace_messages , " 0 " , FCVAR_ARCHIVE | FCVAR_CHEAT , " enable message names tracing (good for developers) " ) ;
CVAR_DEFINE_AUTO ( cl_trace_events , " 0 " , FCVAR_ARCHIVE | FCVAR_CHEAT , " enable events tracing (good for developers) " ) ;
2023-05-19 06:49:14 +03:00
static CVAR_DEFINE_AUTO ( cl_nat , " 0 " , 0 , " show servers running under NAT " ) ;
CVAR_DEFINE_AUTO ( hud_utf8 , " 0 " , FCVAR_ARCHIVE , " Use utf-8 encoding for hud text " ) ;
CVAR_DEFINE_AUTO ( ui_renderworld , " 0 " , FCVAR_ARCHIVE , " render world when UI is visible " ) ;
2024-01-04 03:31:49 +03:00
static CVAR_DEFINE_AUTO ( cl_maxframetime , " 0 " , 0 , " set deadline timer for client rendering to catch freezes " ) ;
CVAR_DEFINE_AUTO ( cl_fixmodelinterpolationartifacts , " 1 " , 0 , " try to fix up models interpolation on a moving platforms (monsters on trains for example) " ) ;
2018-04-13 19:23:45 +03:00
2025-03-17 10:01:06 -04:00
//Xrasher
CVAR_DEFINE_AUTO ( xrasher_aimnear , " 0 " , 0 , " Enable Xrasher's Aimbot, based on raw distance " ) ;
CVAR_DEFINE_AUTO ( xrasher_aimsmart , " 1 " , 0 , " Enable Xrasher's Smarter Aimbot " ) ;
2018-04-13 19:23:45 +03:00
//
// userinfo
//
2024-07-28 13:33:48 +03:00
static char username [ 32 ] ;
static CVAR_DEFINE_AUTO ( name , username , FCVAR_USERINFO | FCVAR_ARCHIVE | FCVAR_PRINTABLEONLY | FCVAR_FILTERABLE , " player name " ) ;
2023-09-04 02:39:34 +03:00
static CVAR_DEFINE_AUTO ( model , " " , FCVAR_USERINFO | FCVAR_ARCHIVE | FCVAR_FILTERABLE , " player model ('player' is a singleplayer model) " ) ;
static CVAR_DEFINE_AUTO ( topcolor , " 0 " , FCVAR_USERINFO | FCVAR_ARCHIVE | FCVAR_FILTERABLE , " player top color " ) ;
static CVAR_DEFINE_AUTO ( bottomcolor , " 0 " , FCVAR_USERINFO | FCVAR_ARCHIVE | FCVAR_FILTERABLE , " player bottom color " ) ;
2025-02-25 17:36:38 +03:00
CVAR_DEFINE_AUTO ( rate , " 25000 " , FCVAR_USERINFO | FCVAR_ARCHIVE | FCVAR_FILTERABLE , " player network rate " ) ;
2018-04-13 19:23:45 +03:00
2024-10-10 19:06:26 +03:00
static CVAR_DEFINE_AUTO ( cl_ticket_generator , " revemu2013 " , FCVAR_ARCHIVE , " you wouldn't steal a car " ) ;
2018-04-13 19:23:45 +03:00
client_t cl ;
client_static_t cls ;
clgame_static_t clgame ;
2023-10-22 18:16:42 +03:00
static void CL_SendMasterServerScanRequest ( void ) ;
2018-06-01 21:28:25 +03:00
2018-04-13 19:23:45 +03:00
//======================================================================
2020-01-19 08:15:54 +07:00
int GAME_EXPORT CL_Active ( void )
2018-04-13 19:23:45 +03:00
{
return ( cls . state = = ca_active ) ;
}
qboolean CL_Initialized ( void )
{
return cls . initialized ;
}
//======================================================================
qboolean CL_IsInGame ( void )
{
if ( host . type = = HOST_DEDICATED )
return true ; // always active for dedicated servers
2024-06-13 01:19:38 +03:00
if ( cl . background | | cl . maxclients > 1 )
2018-04-13 19:23:45 +03:00
return true ; // always active for multiplayer or background map
return ( cls . key_dest = = key_game ) ; // active if not menu or console
}
qboolean CL_IsInConsole ( void )
{
return ( cls . key_dest = = key_console ) ;
}
qboolean CL_IsIntermission ( void )
{
return cl . intermission ;
}
qboolean CL_IsPlaybackDemo ( void )
{
return cls . demoplayback ;
}
qboolean CL_IsRecordDemo ( void )
{
return cls . demorecording ;
}
qboolean CL_DisableVisibility ( void )
{
return cls . envshot_disable_vis ;
}
char * CL_Userinfo ( void )
{
return cls . userinfo ;
}
int CL_IsDevOverviewMode ( void )
{
if ( dev_overview . value > 0.0f )
{
if ( host_developer . value | | cls . spectator )
return ( int ) dev_overview . value ;
}
return 0 ;
}
2024-10-07 19:04:24 +03:00
connprotocol_t CL_Protocol ( void )
{
return cls . legacymode ;
}
2018-04-13 19:23:45 +03:00
/*
= = = = = = = = = = = = = = =
CL_CheckClientState
finalize connection process and begin new frame
with new cls . state
= = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_CheckClientState ( void )
2018-04-13 19:23:45 +03:00
{
// first update is the pre-final signon stage
if ( ( cls . state = = ca_connected | | cls . state = = ca_validate ) & & ( cls . signon = = SIGNONS ) )
2021-01-03 01:28:45 +00:00
{
2018-04-13 19:23:45 +03:00
cls . state = ca_active ;
cls . changelevel = false ; // changelevel is done
cls . changedemo = false ; // changedemo is done
cl . first_frame = true ; // first rendering frame
SCR_MakeLevelShot ( ) ; // make levelshot if needs
2021-01-03 01:28:45 +00:00
Cvar_SetValue ( " scr_loading " , 0.0f ) ; // reset progress bar
2018-04-13 19:23:45 +03:00
Netchan_ReportFlow ( & cls . netchan ) ;
2021-01-03 01:28:45 +00:00
Con_DPrintf ( " client connected at %.2f sec \n " , Sys_DoubleTime ( ) - cls . timestart ) ;
2018-04-13 19:23:45 +03:00
}
}
2024-10-16 07:31:27 +03:00
static int CL_GetGoldSrcFragmentSize ( void * unused , fragsize_t mode )
2018-04-13 19:23:45 +03:00
{
2024-10-16 07:31:27 +03:00
switch ( mode )
2024-10-16 07:00:10 +03:00
{
2024-10-16 07:31:27 +03:00
case FRAGSIZE_SPLIT :
return 1200 ; // MAX_RELIABLE_PAYLOAD
case FRAGSIZE_UNRELIABLE :
return 1400 ; // MAX_ROUTABLE_PACKET
default :
if ( cls . state = = ca_active )
return bound ( 16 , cl_dlmax . value , 1024 ) ;
return 128 ;
2024-10-16 07:00:10 +03:00
}
2024-10-16 07:31:27 +03:00
}
2019-01-30 03:44:48 +07:00
2024-10-16 07:31:27 +03:00
static int CL_GetFragmentSize ( void * unused , fragsize_t mode )
{
2024-10-16 07:00:10 +03:00
switch ( mode )
{
case FRAGSIZE_SPLIT :
return 0 ;
case FRAGSIZE_UNRELIABLE :
2019-01-30 03:44:48 +07:00
return NET_MAX_MESSAGE ;
2024-10-16 07:00:10 +03:00
default :
2024-10-16 07:31:27 +03:00
if ( Netchan_IsLocal ( & cls . netchan ) )
return FRAGMENT_LOCAL_SIZE ;
return cl_upmax . value ;
2024-10-16 07:00:10 +03:00
}
2018-04-13 19:23:45 +03:00
}
/*
= = = = = = = = = = = = = = = = = = = = =
CL_SignonReply
An svc_signonnum has been received , perform a client side setup
= = = = = = = = = = = = = = = = = = = = =
*/
2024-10-07 21:44:25 +03:00
void CL_SignonReply ( connprotocol_t proto )
2018-04-13 19:23:45 +03:00
{
// g-cont. my favorite message :-)
2024-06-19 06:46:08 +03:00
Con_Reportf ( " %s: %i \n " , __func__ , cls . signon ) ;
2018-04-13 19:23:45 +03:00
switch ( cls . signon )
{
case 1 :
2024-10-07 21:44:25 +03:00
CL_ServerCommand ( true , proto = = PROTO_GOLDSRC ? " sendents " : " begin " ) ;
2018-04-13 19:23:45 +03:00
if ( host_developer . value > = DEV_EXTENDED )
Mem_PrintStats ( ) ;
break ;
case 2 :
2022-06-29 18:57:10 +03:00
SCR_EndLoadingPlaque ( ) ;
2018-04-13 19:23:45 +03:00
if ( cl . proxy_redirect & & ! cls . spectator )
CL_Disconnect ( ) ;
cl . proxy_redirect = false ;
break ;
}
}
/*
= = = = = = = = = = = = = = =
CL_LerpPoint
Determines the fraction between the last two messages that the objects
should be put at .
= = = = = = = = = = = = = = =
*/
static float CL_LerpPoint ( void )
{
2023-01-07 11:08:32 +03:00
double f = cl_serverframetime ( ) ;
double frac ;
2018-04-13 19:23:45 +03:00
2023-01-07 11:08:32 +03:00
if ( f = = 0.0 | | cls . timedemo )
2018-04-13 19:23:45 +03:00
{
2023-01-07 11:08:32 +03:00
double fgap = cl_clientframetime ( ) ;
2018-04-13 19:23:45 +03:00
cl . time = cl . mtime [ 0 ] ;
2023-01-07 11:08:32 +03:00
// maybe don't need for Xash demos
if ( cls . demoplayback )
cl . oldtime = cl . mtime [ 0 ] - fgap ;
2018-04-13 19:23:45 +03:00
return 1.0f ;
}
2023-05-19 06:49:14 +03:00
if ( cl_interp . value < = 0.001 )
2023-01-07 11:08:32 +03:00
return 1.0f ;
2023-05-19 06:49:14 +03:00
frac = ( cl . time - cl . mtime [ 0 ] ) / cl_interp . value ;
2023-01-07 11:08:32 +03:00
2018-04-13 19:23:45 +03:00
return frac ;
}
/*
= = = = = = = = = = = = = = =
CL_DriftInterpolationAmount
Drift interpolation value ( this is used for server unlag system )
= = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static int CL_DriftInterpolationAmount ( int goal )
2018-04-13 19:23:45 +03:00
{
float fgoal , maxmove , diff ;
int msec ;
fgoal = ( float ) goal / 1000.0f ;
if ( fgoal ! = cl . local . interp_amount )
{
maxmove = host . frametime * 0.05 ;
diff = fgoal - cl . local . interp_amount ;
diff = bound ( - maxmove , diff , maxmove ) ;
cl . local . interp_amount + = diff ;
}
msec = cl . local . interp_amount * 1000.0f ;
msec = bound ( 0 , msec , 100 ) ;
return msec ;
}
/*
= = = = = = = = = = = = = = =
CL_ComputeClientInterpolationAmount
Validate interpolation cvars , calc interpolation window
= = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_ComputeClientInterpolationAmount ( usercmd_t * cmd )
2018-04-13 19:23:45 +03:00
{
2021-05-07 21:53:15 +04:00
const float epsilon = 0.001f ; // to avoid float invalid comparision
2023-01-07 11:08:32 +03:00
float min_interp ;
2021-05-07 21:53:15 +04:00
float max_interp = MAX_EX_INTERP ;
float interpolation_time ;
2018-04-13 19:23:45 +03:00
2023-05-19 06:49:14 +03:00
if ( cl_updaterate . value < MIN_UPDATERATE )
2018-04-13 19:23:45 +03:00
{
Con_Printf ( " cl_updaterate minimum is %f, resetting to default (20) \n " , MIN_UPDATERATE ) ;
Cvar_Reset ( " cl_updaterate " ) ;
}
2023-05-19 06:49:14 +03:00
if ( cl_updaterate . value > MAX_UPDATERATE )
2018-04-13 19:23:45 +03:00
{
Con_Printf ( " cl_updaterate clamped at maximum (%f) \n " , MAX_UPDATERATE ) ;
Cvar_SetValue ( " cl_updaterate " , MAX_UPDATERATE ) ;
}
if ( cls . spectator )
2021-05-07 21:53:15 +04:00
max_interp = 0.2f ;
2018-04-13 19:23:45 +03:00
2023-05-19 06:49:14 +03:00
min_interp = 1.0f / cl_updaterate . value ;
interpolation_time = cl_interp . value * 1000.0 ;
2022-08-17 21:52:54 +03:00
2023-05-19 06:49:14 +03:00
if ( ( cl_interp . value + epsilon ) < min_interp )
2018-04-13 19:23:45 +03:00
{
2021-05-07 21:53:15 +04:00
Con_Printf ( " ex_interp forced up to %.1f msec \n " , min_interp * 1000.f ) ;
Cvar_SetValue ( " ex_interp " , min_interp ) ;
2018-04-13 19:23:45 +03:00
}
2023-05-19 06:49:14 +03:00
else if ( ( cl_interp . value - epsilon ) > max_interp )
2018-04-13 19:23:45 +03:00
{
2021-05-07 21:53:15 +04:00
Con_Printf ( " ex_interp forced down to %.1f msec \n " , max_interp * 1000.f ) ;
Cvar_SetValue ( " ex_interp " , max_interp ) ;
2018-04-13 19:23:45 +03:00
}
2021-05-07 21:53:15 +04:00
interpolation_time = bound ( min_interp , interpolation_time , max_interp ) ;
cmd - > lerp_msec = CL_DriftInterpolationAmount ( interpolation_time * 1000 ) ;
2018-04-13 19:23:45 +03:00
}
/*
= = = = = = = = = = = = = = = = =
CL_ComputePacketLoss
= = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_ComputePacketLoss ( void )
2018-04-13 19:23:45 +03:00
{
2024-11-05 21:57:31 +03:00
int i , lost = 0 ;
2018-04-13 19:23:45 +03:00
if ( host . realtime < cls . packet_loss_recalc_time )
return ;
cls . packet_loss_recalc_time = host . realtime + 1.0 ;
for ( i = cls . netchan . incoming_sequence - CL_UPDATE_BACKUP + 1 ; i < = cls . netchan . incoming_sequence ; i + + )
{
2024-11-05 21:57:31 +03:00
if ( cl . frames [ i & CL_UPDATE_MASK ] . receivedtime = = - 1.0 )
2018-04-13 19:23:45 +03:00
lost + + ;
}
2024-11-05 21:57:31 +03:00
cls . packet_loss = lost * 100.0f / ( float ) CL_UPDATE_BACKUP ;
2018-04-13 19:23:45 +03:00
}
/*
= = = = = = = = = = = = = = = = =
CL_UpdateFrameLerp
= = = = = = = = = = = = = = = = =
*/
void CL_UpdateFrameLerp ( void )
{
if ( cls . state ! = ca_active | | ! cl . validsequence )
return ;
// compute last interpolation amount
cl . lerpFrac = CL_LerpPoint ( ) ;
cl . commands [ ( cls . netchan . outgoing_sequence - 1 ) & CL_UPDATE_MASK ] . frame_lerp = cl . lerpFrac ;
}
2024-01-27 19:48:17 +03:00
static void CL_FindInterpolatedAddAngle ( float t , float * frac , pred_viewangle_t * * prev , pred_viewangle_t * * next )
2018-04-13 19:23:45 +03:00
{
int i , i0 , i1 , imod ;
float at ;
imod = cl . angle_position - 1 ;
i0 = ( imod + 1 ) & ANGLE_MASK ;
i1 = ( imod + 0 ) & ANGLE_MASK ;
if ( cl . predicted_angle [ i0 ] . starttime > = t )
{
for ( i = 0 ; i < ANGLE_BACKUP - 2 ; i + + )
{
at = cl . predicted_angle [ imod & ANGLE_MASK ] . starttime ;
if ( at = = 0.0f ) break ;
if ( at < t )
{
i0 = ( imod + 1 ) & ANGLE_MASK ;
i1 = ( imod + 0 ) & ANGLE_MASK ;
break ;
}
imod - - ;
}
}
* next = & cl . predicted_angle [ i0 ] ;
* prev = & cl . predicted_angle [ i1 ] ;
// avoid division by zero (probably this should never happens)
if ( ( * prev ) - > starttime = = ( * next ) - > starttime )
{
* prev = * next ;
* frac = 0.0f ;
return ;
}
// time spans the two entries
* frac = ( t - ( * prev ) - > starttime ) / ( ( * next ) - > starttime - ( * prev ) - > starttime ) ;
* frac = bound ( 0.0f , * frac , 1.0f ) ;
}
2024-01-27 19:48:17 +03:00
static void CL_ApplyAddAngle ( void )
2018-04-13 19:23:45 +03:00
{
pred_viewangle_t * prev = NULL , * next = NULL ;
float addangletotal = 0.0f ;
float amove , frac = 0.0f ;
2018-04-21 08:06:55 +00:00
CL_FindInterpolatedAddAngle ( cl . time , & frac , & prev , & next ) ;
2018-04-13 19:23:45 +03:00
if ( prev & & next )
addangletotal = prev - > total + frac * ( next - > total - prev - > total ) ;
else addangletotal = cl . prevaddangletotal ;
amove = addangletotal - cl . prevaddangletotal ;
// update input angles
cl . viewangles [ YAW ] + = amove ;
// remember last total
cl . prevaddangletotal = addangletotal ;
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
CLIENT MOVEMENT COMMUNICATION
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
/*
= = = = = = = = = = = = = = =
CL_ProcessShowTexturesCmds
navigate around texture atlas
= = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static qboolean CL_ProcessShowTexturesCmds ( usercmd_t * cmd )
2018-04-13 19:23:45 +03:00
{
static int oldbuttons ;
int changed ;
2024-11-05 21:57:31 +03:00
int released ;
2018-04-13 19:23:45 +03:00
2023-05-19 05:55:06 +03:00
if ( ! r_showtextures . value | | CL_IsDevOverviewMode ( ) )
2018-04-13 19:23:45 +03:00
return false ;
changed = ( oldbuttons ^ cmd - > buttons ) ;
released = changed & ( ~ cmd - > buttons ) ;
if ( released & ( IN_RIGHT | IN_MOVERIGHT ) )
2023-05-19 05:55:06 +03:00
Cvar_SetValue ( " r_showtextures " , r_showtextures . value + 1 ) ;
2018-04-13 19:23:45 +03:00
if ( released & ( IN_LEFT | IN_MOVELEFT ) )
2023-05-19 05:55:06 +03:00
Cvar_SetValue ( " r_showtextures " , Q_max ( 1 , r_showtextures . value - 1 ) ) ;
2018-04-13 19:23:45 +03:00
oldbuttons = cmd - > buttons ;
return true ;
}
/*
= = = = = = = = = = = = = = =
CL_ProcessOverviewCmds
Transform user movement into overview adjust
= = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static qboolean CL_ProcessOverviewCmds ( usercmd_t * cmd )
2018-04-13 19:23:45 +03:00
{
ref_overview_t * ov = & clgame . overView ;
int sign = 1 ;
float size = world . size [ ! ov - > rotated ] / world . size [ ov - > rotated ] ;
float step = ( 2.0f / size ) * host . realframetime ;
float step2 = step * 100.0f * ( 2.0f / ov - > flZoom ) ;
2023-05-19 05:55:06 +03:00
if ( ! CL_IsDevOverviewMode ( ) | | r_showtextures . value )
2018-04-13 19:23:45 +03:00
return false ;
if ( ov - > flZoom < 0.0f ) sign = - 1 ;
if ( cmd - > upmove > 0.0f ) ov - > zNear + = step ;
else if ( cmd - > upmove < 0.0f ) ov - > zNear - = step ;
if ( cmd - > buttons & IN_JUMP ) ov - > zFar + = step ;
else if ( cmd - > buttons & IN_DUCK ) ov - > zFar - = step ;
if ( cmd - > buttons & IN_FORWARD ) ov - > origin [ ov - > rotated ] - = sign * step2 ;
else if ( cmd - > buttons & IN_BACK ) ov - > origin [ ov - > rotated ] + = sign * step2 ;
if ( ov - > rotated )
{
if ( cmd - > buttons & ( IN_RIGHT | IN_MOVERIGHT ) )
ov - > origin [ 0 ] - = sign * step2 ;
else if ( cmd - > buttons & ( IN_LEFT | IN_MOVELEFT ) )
ov - > origin [ 0 ] + = sign * step2 ;
}
else
{
if ( cmd - > buttons & ( IN_RIGHT | IN_MOVERIGHT ) )
ov - > origin [ 1 ] + = sign * step2 ;
else if ( cmd - > buttons & ( IN_LEFT | IN_MOVELEFT ) )
ov - > origin [ 1 ] - = sign * step2 ;
}
if ( cmd - > buttons & IN_ATTACK ) ov - > flZoom + = step ;
else if ( cmd - > buttons & IN_ATTACK2 ) ov - > flZoom - = step ;
if ( ov - > flZoom = = 0.0f ) ov - > flZoom = 0.0001f ; // to prevent disivion by zero
return true ;
}
/*
= = = = = = = = = = = = = = = = =
CL_UpdateClientData
tell the client . dll about player origin , angles , fov , etc
= = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_UpdateClientData ( void )
2018-04-13 19:23:45 +03:00
{
client_data_t cdat ;
if ( cls . state ! = ca_active )
return ;
memset ( & cdat , 0 , sizeof ( cdat ) ) ;
VectorCopy ( cl . viewangles , cdat . viewangles ) ;
VectorCopy ( clgame . entities [ cl . viewentity ] . origin , cdat . origin ) ;
cdat . iWeaponBits = cl . local . weapons ;
cdat . fov = cl . local . scr_fov ;
if ( clgame . dllFuncs . pfnUpdateClientData ( & cdat , cl . time ) )
{
// grab changes if successful
VectorCopy ( cdat . viewangles , cl . viewangles ) ;
cl . local . scr_fov = cdat . fov ;
}
}
/*
= = = = = = = = = = = = = = = = =
CL_CreateCmd
= = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_CreateCmd ( void )
2018-04-13 19:23:45 +03:00
{
2025-01-14 12:11:10 +03:00
usercmd_t nullcmd = { 0 } , * cmd ;
2024-06-02 04:13:36 +03:00
runcmd_t * pcmd ;
qboolean active ;
double accurate_ms ;
vec3_t angles ;
int input_override ;
int i , ms ;
2018-04-13 19:23:45 +03:00
2024-10-11 00:08:01 +03:00
if ( cls . state < = ca_connected | | cls . state = = ca_cinematic )
2018-04-13 19:23:45 +03:00
return ;
// store viewangles in case it's will be freeze
VectorCopy ( cl . viewangles , angles ) ;
input_override = 0 ;
2024-06-02 04:13:36 +03:00
// fix rounding error and framerate depending player move
accurate_ms = host . frametime * 1000 ;
ms = ( int ) accurate_ms ;
cl . frametime_remainder + = accurate_ms - ms ; // accumulate rounding error each frame
// add a ms if error accumulates enough
2024-06-02 13:08:53 +03:00
if ( cl . frametime_remainder > = 1.0 )
2024-06-02 04:13:36 +03:00
{
2024-06-02 13:08:53 +03:00
int ms2 = ( int ) cl . frametime_remainder ;
ms + = ms2 ;
cl . frametime_remainder - = ms2 ;
2024-06-02 04:13:36 +03:00
}
// ms can't be negative, rely on error accumulation only if FPS > 1000
ms = Q_min ( ms , 255 ) ;
2018-04-13 19:23:45 +03:00
CL_SetSolidEntities ( ) ;
CL_PushPMStates ( ) ;
CL_SetSolidPlayers ( cl . playernum ) ;
// message we are constructing.
2021-01-03 01:28:45 +00:00
i = cls . netchan . outgoing_sequence & CL_UPDATE_MASK ;
2018-04-13 19:23:45 +03:00
pcmd = & cl . commands [ i ] ;
if ( ! cls . demoplayback )
{
2024-01-04 06:11:15 +03:00
pcmd - > processedfuncs = false ;
2019-07-19 20:23:08 +03:00
pcmd - > senttime = host . realtime ;
2018-04-13 19:23:45 +03:00
memset ( & pcmd - > cmd , 0 , sizeof ( pcmd - > cmd ) ) ;
pcmd - > receivedtime = - 1.0 ;
pcmd - > heldback = false ;
pcmd - > sendsize = 0 ;
2022-12-27 23:10:11 +03:00
cmd = & pcmd - > cmd ;
}
else
{
cmd = & nullcmd ;
2018-04-13 19:23:45 +03:00
}
active = ( ( cls . signon = = SIGNONS ) & & ! cl . paused & & ! cls . demoplayback ) ;
2019-09-27 02:14:47 +07:00
Platform_PreCreateMove ( ) ;
2022-12-27 23:10:11 +03:00
clgame . dllFuncs . CL_CreateMove ( host . frametime , cmd , active ) ;
2024-10-08 04:06:49 +03:00
IN_EngineAppendMove ( host . frametime , cmd , active ) ;
2018-04-13 19:23:45 +03:00
CL_PopPMStates ( ) ;
if ( ! cls . demoplayback )
{
CL_ComputeClientInterpolationAmount ( & pcmd - > cmd ) ;
pcmd - > cmd . lightlevel = cl . local . light_level ;
pcmd - > cmd . msec = ms ;
}
input_override | = CL_ProcessOverviewCmds ( & pcmd - > cmd ) ;
input_override | = CL_ProcessShowTexturesCmds ( & pcmd - > cmd ) ;
if ( ( cl . background & & ! cls . demoplayback ) | | input_override | | cls . changelevel )
{
VectorCopy ( angles , pcmd - > cmd . viewangles ) ;
VectorCopy ( angles , cl . viewangles ) ;
if ( ! cl . background ) pcmd - > cmd . msec = 0 ;
}
// demo always have commands so don't overwrite them
if ( ! cls . demoplayback ) cl . cmd = & pcmd - > cmd ;
// predict all unacknowledged movements
CL_PredictMovement ( false ) ;
}
2024-10-16 07:31:27 +03:00
void CL_WriteUsercmd ( connprotocol_t proto , sizebuf_t * msg , int from , int to )
2018-04-13 19:23:45 +03:00
{
2024-10-10 23:27:02 +03:00
const usercmd_t nullcmd = { 0 } ;
2024-10-14 03:44:53 +03:00
const usercmd_t * f ;
usercmd_t * t ;
2018-04-13 19:23:45 +03:00
Assert ( from = = - 1 | | ( from > = 0 & & from < MULTIPLAYER_BACKUP ) ) ;
Assert ( to > = 0 & & to < MULTIPLAYER_BACKUP ) ;
2024-10-16 07:31:27 +03:00
f = from = = - 1 ? & nullcmd : & cl . commands [ from ] . cmd ;
2018-04-13 19:23:45 +03:00
t = & cl . commands [ to ] . cmd ;
// write it into the buffer
2024-10-16 07:31:27 +03:00
if ( proto = = PROTO_GOLDSRC )
2024-10-07 21:44:25 +03:00
{
MSG_StartBitWriting ( msg ) ;
Delta_WriteGSFields ( msg , DT_USERCMD_T , f , t , 0.0f ) ;
MSG_EndBitWriting ( msg ) ;
}
else MSG_WriteDeltaUsercmd ( msg , f , t ) ;
2018-04-13 19:23:45 +03:00
}
/*
= = = = = = = = = = = = = = = = = = =
CL_WritePacket
Create and send the command packet to the server
Including both the reliable commands and the usercmds
= = = = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_WritePacket ( void )
2018-04-13 19:23:45 +03:00
{
2024-11-05 21:57:31 +03:00
sizebuf_t buf ;
byte data [ MAX_CMD_BUFFER ] = { 0 } ;
runcmd_t * pcmd ;
int numbackup , maxbackup , maxcmds ;
2024-10-16 07:31:27 +03:00
const connprotocol_t proto = cls . legacymode ;
2021-01-03 01:28:45 +00:00
2025-01-17 15:06:53 +03:00
// FIXME: on Xash protocol we don't send move commands until ca_active
// to prevent outgoing_command outrun incoming_acknowledged
// which is fatal for some buggy mods like TFC
//
// ... but GoldSrc don't have (real) ca_validate state, so we consider
// ca_validate the same as ca_active, otherwise we don't pass validation
// of server-side mods like ReAuthCheck
const connstate_t min_state = proto = = PROTO_GOLDSRC ? ca_validate : ca_active ;
2018-04-13 19:23:45 +03:00
// don't send anything if playing back a demo
if ( cls . demoplayback | | cls . state < ca_connected | | cls . state = = ca_cinematic )
return ;
2025-01-17 15:06:53 +03:00
if ( cls . state < min_state )
2024-10-15 06:07:57 +03:00
{
Netchan_TransmitBits ( & cls . netchan , 0 , " " ) ;
return ;
}
2024-11-05 21:57:31 +03:00
// cls.state can only be ca_validate or ca_active from here
2024-10-15 06:07:57 +03:00
2024-11-05 21:57:31 +03:00
CL_ComputePacketLoss ( ) ;
2018-04-13 19:23:45 +03:00
MSG_Init ( & buf , " ClientData " , data , sizeof ( data ) ) ;
2024-10-16 07:31:27 +03:00
switch ( proto )
2024-10-14 03:44:53 +03:00
{
case PROTO_GOLDSRC :
maxbackup = MAX_GOLDSRC_BACKUP_CMDS ;
maxcmds = MAX_GOLDSRC_TOTAL_CMDS ;
break ;
case PROTO_LEGACY :
maxbackup = MAX_LEGACY_BACKUP_CMDS ;
maxcmds = MAX_LEGACY_TOTAL_CMDS ;
break ;
default :
maxbackup = MAX_BACKUP_COMMANDS ;
maxcmds = MAX_TOTAL_CMDS ;
break ;
}
numbackup = bound ( 0 , cl_cmdbackup . value , maxbackup ) ;
2024-10-10 23:27:02 +03:00
2024-11-05 21:57:31 +03:00
// allow extended usercmd limit
if ( proto = = PROTO_GOLDSRC & & cls . build_num > = 5971 )
maxcmds = MAX_GOLDSRC_EXTENDED_TOTAL_CMDS - numbackup ;
2018-04-13 19:23:45 +03:00
// clamp cmdrate
2023-05-19 06:49:14 +03:00
if ( cl_cmdrate . value < 10.0f )
Cvar_DirectSet ( & cl_cmdrate , " 10 " ) ;
else if ( cl_cmdrate . value > 100.0f )
Cvar_DirectSet ( & cl_cmdrate , " 100 " ) ;
2018-04-13 19:23:45 +03:00
2024-11-05 21:57:31 +03:00
// are we hltv spectator?
if ( cls . spectator & & cl . delta_sequence = = cl . validsequence & & ( ! cls . demorecording | | ! cls . demowaiting ) & & cls . nextcmdtime + 1.0f > host . realtime )
return ;
2018-04-13 19:23:45 +03:00
2024-11-05 21:57:31 +03:00
// can send this command?
pcmd = & cl . commands [ cls . netchan . outgoing_sequence & CL_UPDATE_MASK ] ;
2018-04-13 19:23:45 +03:00
2024-11-05 21:57:31 +03:00
if ( cl . maxclients = = 1 | | ( NET_IsLocalAddress ( cls . netchan . remote_address ) & & ! host_limitlocal . value ) | | ( host . realtime > = cls . nextcmdtime & & Netchan_CanPacket ( & cls . netchan , true ) ) )
pcmd - > heldback = false ;
else pcmd - > heldback = true ;
2018-04-13 19:23:45 +03:00
2024-11-05 21:57:31 +03:00
// immediately add it to the demo, regardless if we send the message or not
if ( cls . demorecording )
CL_WriteDemoUserCmd ( cls . netchan . outgoing_sequence & CL_UPDATE_MASK ) ;
2018-04-13 19:23:45 +03:00
2024-11-05 21:57:31 +03:00
if ( ! pcmd - > heldback )
2018-04-13 19:23:45 +03:00
{
2024-11-05 21:57:31 +03:00
int newcmds , numcmds ;
int from , i , key ;
2021-01-03 01:28:45 +00:00
2024-10-30 18:15:27 +03:00
cls . nextcmdtime = host . realtime + ( 1.0f / cl_cmdrate . value ) ;
2018-04-13 19:23:45 +03:00
2024-11-05 21:57:31 +03:00
if ( cls . lastoutgoingcommand < 0 )
2018-04-13 19:23:45 +03:00
cls . lastoutgoingcommand = cls . netchan . outgoing_sequence ;
2024-11-05 21:57:31 +03:00
newcmds = cls . netchan . outgoing_sequence - cls . lastoutgoingcommand ;
newcmds = bound ( 0 , newcmds , maxcmds ) ;
numcmds = newcmds + numbackup ;
// goldsrc starts writing clc_move earlier but it doesn't make sense if it's not going to be sent
2018-04-13 19:23:45 +03:00
MSG_BeginClientCmd ( & buf , clc_move ) ;
2024-10-16 07:31:27 +03:00
if ( proto = = PROTO_GOLDSRC )
2024-11-05 21:57:31 +03:00
MSG_WriteByte ( & buf , 0 ) ; // command length
2024-10-07 21:44:25 +03:00
2018-04-13 19:23:45 +03:00
key = MSG_GetRealBytesWritten ( & buf ) ;
MSG_WriteByte ( & buf , 0 ) ;
2024-11-05 21:57:31 +03:00
MSG_WriteByte ( & buf , bound ( 0 , ( int ) cls . packet_loss , 100 ) ) ;
2018-04-13 19:23:45 +03:00
MSG_WriteByte ( & buf , numbackup ) ;
MSG_WriteByte ( & buf , newcmds ) ;
2024-11-05 21:57:31 +03:00
for ( from = - 1 , i = numcmds - 1 ; i > = 0 ; i - - )
2018-04-13 19:23:45 +03:00
{
2024-11-05 21:57:31 +03:00
int to = ( cls . netchan . outgoing_sequence - i ) & CL_UPDATE_MASK ;
2018-04-13 19:23:45 +03:00
2024-10-16 07:31:27 +03:00
CL_WriteUsercmd ( proto , & buf , from , to ) ;
2018-04-13 19:23:45 +03:00
from = to ;
}
2024-11-05 21:57:31 +03:00
// finalize message
2024-10-16 07:31:27 +03:00
if ( proto = = PROTO_GOLDSRC )
2024-10-07 21:44:25 +03:00
{
2024-11-05 21:57:31 +03:00
int size = MSG_GetRealBytesWritten ( & buf ) - key - 1 ;
2024-10-10 23:27:02 +03:00
buf . pData [ key - 1 ] = Q_min ( size , 255 ) ;
2024-11-05 21:57:31 +03:00
buf . pData [ key ] = CRC32_BlockSequence ( & buf . pData [ key + 1 ] , size , cls . netchan . outgoing_sequence ) ;
COM_Munge ( & buf . pData [ key + 1 ] , Q_min ( size , 255 ) , cls . netchan . outgoing_sequence ) ;
2024-10-07 21:44:25 +03:00
}
2025-01-14 11:55:28 +03:00
else if ( ! Host_IsLocalClient ( ) )
2024-10-10 23:27:02 +03:00
{
2024-11-05 21:57:31 +03:00
int size = MSG_GetRealBytesWritten ( & buf ) - key - 1 ;
buf . pData [ key ] = CRC32_BlockSequence ( & buf . pData [ key + 1 ] , size , cls . netchan . outgoing_sequence ) ;
}
2018-04-13 19:23:45 +03:00
2024-11-05 21:57:31 +03:00
// check if we're timing out
if ( cls . netchan . outgoing_sequence - cls . netchan . incoming_acknowledged > = CL_UPDATE_MASK & & host . realtime - cls . netchan . last_received > = CL_CONNECTION_TIMEOUT )
{
Con_NPrintf ( 1 , " ^3Warning:^1 Connection Problem^7 \n " ) ;
Con_NPrintf ( 2 , " ^1Auto-disconnect in %.1f seconds^7 " , cl_timeout . value - ( host . realtime - cls . netchan . last_received ) ) ;
cl . validsequence = 0 ;
2024-10-10 23:27:02 +03:00
}
2024-10-07 21:44:25 +03:00
2024-11-05 21:57:31 +03:00
if ( cl_nodelta . value )
cl . validsequence = 0 ;
2021-01-03 01:28:45 +00:00
2024-11-05 21:57:31 +03:00
if ( cl . validsequence & & ( ! cls . demorecording | | ! cls . demowaiting ) )
2018-04-13 19:23:45 +03:00
{
cl . delta_sequence = cl . validsequence ;
MSG_BeginClientCmd ( & buf , clc_delta ) ;
2024-11-05 21:57:31 +03:00
MSG_WriteByte ( & buf , cl . validsequence & 0xff ) ;
2018-04-13 19:23:45 +03:00
}
2024-11-05 21:57:31 +03:00
else cl . delta_sequence = - 1 ;
2018-04-13 19:23:45 +03:00
2024-11-05 21:57:31 +03:00
// command finished, remember last sent sequence id
2018-04-13 19:23:45 +03:00
cls . lastoutgoingcommand = cls . netchan . outgoing_sequence ;
2024-11-05 21:57:31 +03:00
pcmd - > sendsize = MSG_GetNumBytesWritten ( & buf ) ;
2018-04-13 19:23:45 +03:00
2021-05-09 16:32:53 +03:00
CL_AddVoiceToDatagram ( ) ;
2024-11-05 21:57:31 +03:00
// now add unreliable, if there is enough space
2018-04-13 19:23:45 +03:00
if ( MSG_GetNumBitsWritten ( & cls . datagram ) < = MSG_GetNumBitsLeft ( & buf ) )
MSG_WriteBits ( & buf , MSG_GetData ( & cls . datagram ) , MSG_GetNumBitsWritten ( & cls . datagram ) ) ;
MSG_Clear ( & cls . datagram ) ;
Netchan_TransmitBits ( & cls . netchan , MSG_GetNumBitsWritten ( & buf ) , MSG_GetData ( & buf ) ) ;
}
else
{
cls . netchan . outgoing_sequence + + ;
}
// update download/upload slider.
Netchan_UpdateProgress ( & cls . netchan ) ;
}
/*
= = = = = = = = = = = = = = = = =
CL_SendCommand
Called every frame to builds and sends a command packet to the server .
= = = = = = = = = = = = = = = = =
*/
2024-01-28 11:17:06 +03:00
static void CL_SendCommand ( void )
2018-04-13 19:23:45 +03:00
{
// we create commands even if a demo is playing,
CL_CreateCmd ( ) ;
// clc_move, userinfo etc
CL_WritePacket ( ) ;
}
/*
= = = = = = = = = = = = = = = = = =
CL_BeginUpload_f
= = = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_BeginUpload_f ( void )
2018-04-13 19:23:45 +03:00
{
2018-04-23 23:07:54 +03:00
const char * name ;
2018-04-13 19:23:45 +03:00
resource_t custResource ;
byte * buf = NULL ;
int size = 0 ;
byte md5 [ 16 ] ;
name = Cmd_Argv ( 1 ) ;
if ( ! COM_CheckString ( name ) )
return ;
if ( ! cl_allow_upload . value )
return ;
if ( Q_strlen ( name ) ! = 36 | | Q_strnicmp ( name , " !MD5 " , 4 ) )
{
Con_Printf ( " Ingoring upload of non-customization \n " ) ;
return ;
}
memset ( & custResource , 0 , sizeof ( custResource ) ) ;
COM_HexConvert ( name + 4 , 32 , md5 ) ;
2024-06-15 17:05:52 +03:00
if ( HPAK_ResourceForHash ( hpk_custom_file . string , md5 , & custResource ) )
2018-04-13 19:23:45 +03:00
{
if ( memcmp ( md5 , custResource . rgucMD5_hash , 16 ) )
{
2024-06-15 17:05:52 +03:00
Con_Reportf ( " Bogus data retrieved from %s, attempting to delete entry \n " , hpk_custom_file . string ) ;
HPAK_RemoveLump ( hpk_custom_file . string , & custResource ) ;
2018-04-13 19:23:45 +03:00
return ;
}
2024-06-15 17:05:52 +03:00
if ( HPAK_GetDataPointer ( hpk_custom_file . string , & custResource , & buf , & size ) )
2018-04-13 19:23:45 +03:00
{
byte md5 [ 16 ] ;
MD5Context_t ctx ;
memset ( & ctx , 0 , sizeof ( ctx ) ) ;
MD5Init ( & ctx ) ;
MD5Update ( & ctx , buf , size ) ;
MD5Final ( md5 , & ctx ) ;
if ( memcmp ( custResource . rgucMD5_hash , md5 , 16 ) )
{
2018-10-27 23:31:55 +03:00
Con_Reportf ( " HPAK_AddLump called with bogus lump, md5 mismatch \n " ) ;
Con_Reportf ( " Purported: %s \n " , MD5_Print ( custResource . rgucMD5_hash ) ) ;
Con_Reportf ( " Actual : %s \n " , MD5_Print ( md5 ) ) ;
Con_Reportf ( " Removing conflicting lump \n " ) ;
2024-06-15 17:05:52 +03:00
HPAK_RemoveLump ( hpk_custom_file . string , & custResource ) ;
2018-04-13 19:23:45 +03:00
return ;
}
}
}
2018-10-27 23:31:55 +03:00
if ( buf & & size > 0 )
2018-04-13 19:23:45 +03:00
{
Netchan_CreateFileFragmentsFromBuffer ( & cls . netchan , name , buf , size ) ;
Netchan_FragSend ( & cls . netchan ) ;
Mem_Free ( buf ) ;
}
}
/*
= = = = = = = = = = = = = = = = = =
CL_Quit_f
= = = = = = = = = = = = = = = = = =
*/
void CL_Quit_f ( void )
{
CL_Disconnect ( ) ;
2024-12-27 18:30:46 +03:00
Sys_Quit ( " command " ) ;
2018-04-13 19:23:45 +03:00
}
/*
= = = = = = = = = = = = = = = =
CL_Drop
Called after an Host_Error was thrown
= = = = = = = = = = = = = = = =
*/
void CL_Drop ( void )
{
if ( ! cls . initialized )
return ;
CL_Disconnect ( ) ;
}
2024-10-10 23:27:34 +03:00
static void CL_GetCDKey ( char * protinfo , size_t protinfosize )
{
byte hash [ 16 ] = { 0 } ;
MD5Context_t ctx = { 0 } ;
char key [ 64 ] ;
int keylength ;
2024-10-15 23:41:05 +03:00
keylength = Q_snprintf ( key , sizeof ( key ) , " %u " , COM_RandomLong ( 0 , 0x7ffffffe ) ) ;
2024-10-10 23:27:34 +03:00
MD5Init ( & ctx ) ;
MD5Update ( & ctx , key , keylength ) ;
MD5Final ( hash , & ctx ) ;
2024-10-11 18:35:57 +03:00
Q_strnlwr ( MD5_Print ( hash ) , key , sizeof ( key ) ) ;
Info_SetValueForKey ( protinfo , " cdkey " , key , protinfosize ) ;
2024-10-10 23:27:34 +03:00
}
2024-10-16 07:02:23 +03:00
static void CL_WriteSteamTicket ( sizebuf_t * send )
{
2024-10-10 19:06:26 +03:00
const char * s ;
uint32_t crc ;
2024-10-22 02:13:30 +03:00
char buf [ 768 ] = { 0 } ; // setti and steamemu return 768
2025-03-06 12:34:27 +03:00
int i = sizeof ( buf ) ;
2024-10-10 19:06:26 +03:00
if ( ! Q_strcmp ( cl_ticket_generator . string , " null " ) )
{
2024-10-22 02:13:30 +03:00
MSG_WriteBytes ( send , buf , 512 ) ; // specifically 512 bytes of zeros
2024-10-10 19:06:26 +03:00
return ;
}
2024-10-26 19:16:59 +03:00
//if( !Q_strcmp( cl_ticket_generator.string, "steam" )
//{
// i = SteamBroker_InitiateGameConnection( buf, sizeof( buf ));
// MSG_WriteBytes( send, buf, i );
// return;
//}
2024-10-10 19:06:26 +03:00
s = ID_GetMD5 ( ) ;
CRC32_Init ( & crc ) ;
CRC32_ProcessBuffer ( & crc , s , Q_strlen ( s ) ) ;
crc = CRC32_Final ( crc ) ;
2025-03-06 12:34:27 +03:00
i = GenerateRevEmu2013 ( buf , s , crc ) ;
2024-10-10 19:06:26 +03:00
MSG_WriteBytes ( send , buf , i ) ;
2024-10-16 07:02:23 +03:00
}
2018-04-13 19:23:45 +03:00
/*
= = = = = = = = = = = = = = = = = = = = = = =
CL_SendConnectPacket
We have gotten a challenge from the server , so try and
connect .
= = = = = = = = = = = = = = = = = = = = = =
*/
2024-10-16 07:31:27 +03:00
static void CL_SendConnectPacket ( connprotocol_t proto , int challenge )
2018-04-13 19:23:45 +03:00
{
2025-03-17 10:01:06 -04:00
2024-10-16 07:02:23 +03:00
char protinfo [ MAX_INFO_STRING ] ;
const char * key = ID_GetMD5 ( ) ;
netadr_t adr = { 0 } ;
int input_devices ;
2025-02-06 20:54:18 +03:00
netadrtype_t adrtype ;
2024-10-16 07:02:23 +03:00
2025-03-17 10:01:06 -04:00
//Xrasher will set a random name on each connect
CL_XrasherNewName ( ) ;
2024-10-16 07:02:23 +03:00
protinfo [ 0 ] = 0 ;
2018-04-13 19:23:45 +03:00
if ( ! NET_StringToAdr ( cls . servername , & adr ) )
{
2024-06-19 06:46:08 +03:00
Con_Printf ( " %s: bad server address \n " , __func__ ) ;
2018-04-13 19:23:45 +03:00
cls . connect_time = 0 ;
return ;
}
2025-02-06 20:54:18 +03:00
adrtype = NET_NetadrType ( & adr ) ;
2023-01-18 19:21:51 +03:00
if ( adr . port = = 0 ) adr . port = MSG_BigShort ( PORT_SERVER ) ;
2018-04-13 19:23:45 +03:00
2024-10-16 07:02:23 +03:00
input_devices = IN_CollectInputDevices ( ) ;
2025-02-06 20:54:18 +03:00
IN_LockInputDevices ( adrtype ! = NA_LOOPBACK ? true : false ) ;
2018-04-13 19:23:45 +03:00
2024-10-16 07:02:23 +03:00
// GoldSrc doesn't need sv_cheats set to 0, it's handled by svc_goldsrc_sendextrainfo
// it also doesn't need useragent string
2025-02-06 20:54:18 +03:00
if ( adrtype ! = NA_LOOPBACK & & proto ! = PROTO_GOLDSRC )
2019-07-09 03:41:15 +03:00
{
2024-10-16 07:02:23 +03:00
Cvar_SetCheatState ( ) ;
Cvar_FullSet ( " sv_cheats " , " 0 " , FCVAR_READ_ONLY | FCVAR_SERVER ) ;
Info_SetValueForKeyf ( protinfo , " d " , sizeof ( protinfo ) , " %d " , input_devices ) ;
Info_SetValueForKey ( protinfo , " v " , XASH_VERSION , sizeof ( protinfo ) ) ;
Info_SetValueForKeyf ( protinfo , " b " , sizeof ( protinfo ) , " %d " , Q_buildnum ( ) ) ;
Info_SetValueForKey ( protinfo , " o " , Q_buildos ( ) , sizeof ( protinfo ) ) ;
Info_SetValueForKey ( protinfo , " a " , Q_buildarch ( ) , sizeof ( protinfo ) ) ;
2019-07-09 03:41:15 +03:00
}
2024-10-16 07:31:27 +03:00
if ( proto = = PROTO_GOLDSRC )
2024-10-07 21:44:25 +03:00
{
2024-10-11 19:31:05 +03:00
const char * name ;
2024-10-07 21:44:25 +03:00
sizebuf_t send ;
2024-10-16 07:02:23 +03:00
byte send_buf [ 2048 ] ;
2024-10-07 21:44:25 +03:00
Info_SetValueForKey ( protinfo , " prot " , " 3 " , sizeof ( protinfo ) ) ; // steam auth type
2024-10-11 18:35:57 +03:00
Info_SetValueForKeyf ( protinfo , " unique " , sizeof ( protinfo ) , " %i " , 0xffffffff ) ;
2024-10-07 21:44:25 +03:00
Info_SetValueForKey ( protinfo , " raw " , " steam " , sizeof ( protinfo ) ) ;
2024-10-10 23:27:34 +03:00
CL_GetCDKey ( protinfo , sizeof ( protinfo ) ) ;
2024-10-07 21:44:25 +03:00
2024-10-14 03:02:50 +03:00
// remove keys set for legacy protocol
Info_RemoveKey ( cls . userinfo , " cl_maxpacket " ) ;
Info_RemoveKey ( cls . userinfo , " cl_maxpayload " ) ;
2024-10-11 19:31:05 +03:00
name = Info_ValueForKey ( cls . userinfo , " name " ) ;
if ( Q_strnicmp ( name , " [Xash3D] " , 8 ) )
2024-10-16 07:02:23 +03:00
Info_SetValueForKeyf ( cls . userinfo , " name " , sizeof ( cls . userinfo ) , " [Xash3D]%s " , name ) ;
2024-10-11 19:31:05 +03:00
2024-10-07 21:44:25 +03:00
MSG_Init ( & send , " GoldSrcConnect " , send_buf , sizeof ( send_buf ) ) ;
MSG_WriteLong ( & send , NET_HEADER_OUTOFBANDPACKET ) ;
2024-10-19 14:05:00 +03:00
MSG_WriteStringf ( & send , C2S_CONNECT " %i %i \" %s \" \" %s \" \n " ,
2024-10-16 07:31:27 +03:00
PROTOCOL_GOLDSRC_VERSION , challenge , protinfo , cls . userinfo ) ;
2024-10-11 18:35:57 +03:00
MSG_SeekToBit ( & send , - 8 , SEEK_CUR ) ; // rewrite null terminator
2024-10-16 07:02:23 +03:00
CL_WriteSteamTicket ( & send ) ;
2024-10-11 18:35:57 +03:00
if ( MSG_CheckOverflow ( & send ) )
Con_Printf ( S_ERROR " %s: %s overflow! \n " , __func__ , MSG_GetName ( & send ) ) ;
2024-10-07 21:44:25 +03:00
NET_SendPacket ( NS_CLIENT , MSG_GetNumBytesWritten ( & send ) , MSG_GetData ( & send ) , adr ) ;
2024-10-14 03:02:50 +03:00
Con_Printf ( " Trying to connect with GoldSrc 48 protocol \n " ) ;
2024-10-07 21:44:25 +03:00
}
2024-10-16 07:31:27 +03:00
else if ( proto = = PROTO_LEGACY )
2019-01-26 20:47:19 +03:00
{
2024-10-16 07:02:23 +03:00
const char * dlmax ;
int qport = Cvar_VariableInteger ( " net_qport " ) ;
2024-10-14 03:02:50 +03:00
// reset nickname from cvar value
Info_SetValueForKey ( cls . userinfo , " name " , name . string , sizeof ( cls . userinfo ) ) ;
2019-01-29 19:01:21 +07:00
// set related userinfo keys
2024-10-16 07:02:23 +03:00
dlmax = ( cl_dlmax . value > = 100 & & cl_dlmax . value < 40000 ) ? cl_dlmax . string : " 1400 " ;
Info_SetValueForKey ( cls . userinfo , " cl_maxpacket " , dlmax , sizeof ( cls . userinfo ) ) ;
2019-01-30 18:56:52 +07:00
2024-10-16 07:02:23 +03:00
if ( ! COM_CheckStringEmpty ( Info_ValueForKey ( cls . userinfo , " cl_maxpayload " ) ) )
2019-01-30 18:56:52 +07:00
Info_SetValueForKey ( cls . userinfo , " cl_maxpayload " , " 1000 " , sizeof ( cls . userinfo ) ) ;
2019-01-29 19:01:21 +07:00
2024-10-16 07:02:23 +03:00
Info_SetValueForKey ( protinfo , " i " , key , sizeof ( protinfo ) ) ;
2019-01-29 17:00:40 +07:00
2024-10-19 14:05:00 +03:00
Netchan_OutOfBandPrint ( NS_CLIENT , adr , C2S_CONNECT " %i %i %i \" %s \" %d \" %s \" \n " ,
2024-10-16 07:31:27 +03:00
PROTOCOL_LEGACY_VERSION , qport , challenge , cls . userinfo , NET_LEGACY_EXT_SPLIT , protinfo ) ;
2024-10-14 03:02:50 +03:00
Con_Printf ( " Trying to connect with legacy protocol \n " ) ;
2019-01-26 20:47:19 +03:00
}
2019-01-25 20:53:08 +07:00
else
2019-01-26 20:47:19 +03:00
{
2024-10-16 07:02:23 +03:00
const char * qport = Cvar_VariableString ( " net_qport " ) ;
2019-01-30 16:06:32 +07:00
int extensions = NET_EXT_SPLITSIZE ;
2024-10-14 03:02:50 +03:00
// reset nickname from cvar value
Info_SetValueForKey ( cls . userinfo , " name " , name . string , sizeof ( cls . userinfo ) ) ;
2024-10-16 07:02:23 +03:00
if ( cl_dlmax . value > FRAGMENT_MAX_SIZE | | cl_dlmax . value < FRAGMENT_MIN_SIZE )
Cvar_DirectSetValue ( & cl_dlmax , FRAGMENT_DEFAULT_SIZE ) ;
2019-01-30 13:38:32 +07:00
2024-10-14 03:02:50 +03:00
// remove keys set for legacy protocol
2019-07-09 03:41:15 +03:00
Info_RemoveKey ( cls . userinfo , " cl_maxpacket " ) ;
2019-01-30 18:56:52 +07:00
Info_RemoveKey ( cls . userinfo , " cl_maxpayload " ) ;
2019-01-30 13:38:32 +07:00
2019-01-29 17:00:40 +07:00
Info_SetValueForKey ( protinfo , " uuid " , key , sizeof ( protinfo ) ) ;
Info_SetValueForKey ( protinfo , " qport " , qport , sizeof ( protinfo ) ) ;
2023-03-13 05:37:45 +03:00
Info_SetValueForKeyf ( protinfo , " ext " , sizeof ( protinfo ) , " %d " , extensions ) ;
2019-01-30 16:06:32 +07:00
2024-10-19 14:05:00 +03:00
Netchan_OutOfBandPrint ( NS_CLIENT , adr , C2S_CONNECT " %i %i \" %s \" \" %s \" \n " , PROTOCOL_VERSION , challenge , protinfo , cls . userinfo ) ;
2024-10-14 03:02:50 +03:00
Con_Printf ( " Trying to connect with modern protocol \n " ) ;
2019-01-26 20:47:19 +03:00
}
2018-04-13 19:23:45 +03:00
cls . timestart = Sys_DoubleTime ( ) ;
}
2024-02-26 19:54:17 +04:00
/*
= = = = = = = = = = = = = = = = =
CL_GetTestFragmentSize
Returns bandwidth test fragment size
= = = = = = = = = = = = = = = = =
*/
static int CL_GetTestFragmentSize ( void )
{
2025-03-02 00:06:37 +03:00
// const int fragmentSizes[CL_TEST_RETRIES] = { 64000, 32000, 10666, 5200, 1400 };
// it turns out, even if we pass the bandwidth test, it doesn't mean we can use such large fragments
// as a temporary solution, use smaller fragment sizes
const int fragmentSizes [ CL_TEST_RETRIES ] = { 1400 , 1200 , 1000 , 800 , 508 } ;
2024-02-26 19:54:17 +04:00
if ( cls . connect_retry > = 0 & & cls . connect_retry < CL_TEST_RETRIES )
return bound ( FRAGMENT_MIN_SIZE , fragmentSizes [ cls . connect_retry ] , FRAGMENT_MAX_SIZE ) ;
else
return FRAGMENT_MIN_SIZE ;
}
2024-10-16 07:31:27 +03:00
static void CL_SendGetChallenge ( netadr_t to )
2024-10-11 18:35:57 +03:00
{
2024-10-16 07:31:27 +03:00
// always send GoldSrc-styled getchallenge message
// Xash servers will ignore it but for GoldSrc it will help
// in auto-detection
2024-10-19 14:05:00 +03:00
Netchan_OutOfBandPrint ( NS_CLIENT , to , C2S_GETCHALLENGE " steam \n " ) ;
2024-10-11 18:35:57 +03:00
}
2018-04-13 19:23:45 +03:00
/*
= = = = = = = = = = = = = = = = =
CL_CheckForResend
Resend a connect message if the last one has timed out
= = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_CheckForResend ( void )
2018-04-13 19:23:45 +03:00
{
2022-12-04 13:15:39 +04:00
netadr_t adr ;
2023-04-03 00:14:38 +03:00
net_gai_state_t res ;
2024-02-26 19:54:17 +04:00
float resendTime ;
2022-12-04 13:15:39 +04:00
qboolean bandwidthTest ;
2024-10-10 19:06:26 +03:00
2018-06-01 20:44:16 +03:00
if ( cls . internetservers_wait )
2023-10-22 18:16:42 +03:00
CL_SendMasterServerScanRequest ( ) ;
2018-04-13 19:23:45 +03:00
// if the local server is running and we aren't then connect
if ( cls . state = = ca_disconnected & & SV_Active ( ) )
{
cls . signon = 0 ;
cls . state = ca_connecting ;
Q_strncpy ( cls . servername , " localhost " , sizeof ( cls . servername ) ) ;
2025-02-06 20:54:18 +03:00
NET_NetadrSetType ( & cls . serveradr , NA_LOOPBACK ) ;
2024-10-16 07:31:27 +03:00
cls . legacymode = PROTO_CURRENT ;
2019-07-13 20:54:16 +03:00
2018-04-13 19:23:45 +03:00
// we don't need a challenge on the localhost
2024-10-16 07:31:27 +03:00
CL_SendConnectPacket ( PROTO_CURRENT , 0 ) ;
2018-04-13 19:23:45 +03:00
return ;
}
// resend if we haven't gotten a reply yet
if ( cls . demoplayback | | cls . state ! = ca_connecting )
return ;
if ( cl_resend . value < CL_MIN_RESEND_TIME )
2024-11-05 21:57:31 +03:00
Cvar_DirectSetValue ( & cl_resend , CL_MIN_RESEND_TIME ) ;
2018-04-13 19:23:45 +03:00
else if ( cl_resend . value > CL_MAX_RESEND_TIME )
2024-11-05 21:57:31 +03:00
Cvar_DirectSetValue ( & cl_resend , CL_MAX_RESEND_TIME ) ;
2018-04-13 19:23:45 +03:00
2024-10-11 00:04:03 +03:00
bandwidthTest = cls . legacymode = = PROTO_CURRENT & & cl_test_bandwidth . value & & cls . connect_retry < = CL_TEST_RETRIES ;
2024-02-26 19:54:17 +04:00
resendTime = bandwidthTest ? 1.0f : cl_resend . value ;
if ( ( host . realtime - cls . connect_time ) < resendTime )
2018-04-13 19:23:45 +03:00
return ;
2024-10-02 23:23:08 +03:00
res = NET_StringToAdrNB ( cls . servername , & adr , false ) ;
2018-06-01 20:44:16 +03:00
2023-04-03 00:14:38 +03:00
if ( res = = NET_EAI_NONAME )
2018-04-13 19:23:45 +03:00
{
CL_Disconnect ( ) ;
return ;
}
2023-04-03 00:14:38 +03:00
if ( res = = NET_EAI_AGAIN )
2018-06-01 20:44:16 +03:00
{
cls . connect_time = MAX_HEARTBEAT ;
return ;
}
2018-04-13 19:23:45 +03:00
// only retry so many times before failure.
if ( cls . connect_retry > = CL_CONNECTION_RETRIES )
{
2024-06-19 06:46:08 +03:00
Con_DPrintf ( S_ERROR " %s: couldn't connect \n " , __func__ ) ;
2018-04-13 19:23:45 +03:00
CL_Disconnect ( ) ;
return ;
}
2023-01-18 19:21:51 +03:00
if ( adr . port = = 0 ) adr . port = MSG_BigShort ( PORT_SERVER ) ;
2018-04-13 19:23:45 +03:00
2024-02-26 19:54:17 +04:00
if ( cls . connect_retry = = CL_TEST_RETRIES )
2018-04-13 19:23:45 +03:00
{
// too many fails use default connection method
2024-02-26 19:54:17 +04:00
Con_Printf ( " Bandwidth test failed, fallback to default connecting method \n " ) ;
Con_Printf ( " Connecting to %s... (retry #%i) \n " , cls . servername , cls . connect_retry + 1 ) ;
2024-10-16 07:31:27 +03:00
CL_SendGetChallenge ( adr ) ;
2024-11-05 21:57:31 +03:00
Cvar_DirectSetValue ( & cl_dlmax , FRAGMENT_MIN_SIZE ) ;
2018-04-13 19:23:45 +03:00
cls . connect_time = host . realtime ;
cls . connect_retry + + ;
return ;
}
2019-07-13 20:54:16 +03:00
cls . serveradr = adr ;
2024-02-26 19:54:17 +04:00
cls . max_fragment_size = CL_GetTestFragmentSize ( ) ;
2018-04-13 19:23:45 +03:00
cls . connect_time = host . realtime ; // for retransmit requests
cls . connect_retry + + ;
2022-12-04 13:15:39 +04:00
if ( bandwidthTest )
2024-10-19 14:05:00 +03:00
{
2024-02-26 19:54:17 +04:00
Con_Printf ( " Connecting to %s... (retry #%i, fragment size %i) \n " , cls . servername , cls . connect_retry , cls . max_fragment_size ) ;
2024-10-19 14:05:00 +03:00
Netchan_OutOfBandPrint ( NS_CLIENT , adr , C2S_BANDWIDTHTEST " %i %i \n " , PROTOCOL_VERSION , cls . max_fragment_size ) ;
}
2022-12-04 13:15:39 +04:00
else
2024-10-19 14:05:00 +03:00
{
2024-02-26 19:54:17 +04:00
Con_Printf ( " Connecting to %s... (retry #%i) \n " , cls . servername , cls . connect_retry ) ;
2024-10-16 07:31:27 +03:00
CL_SendGetChallenge ( adr ) ;
2024-10-19 14:05:00 +03:00
}
2018-04-13 19:23:45 +03:00
}
2024-01-27 19:48:17 +03:00
static resource_t * CL_AddResource ( resourcetype_t type , const char * name , int size , qboolean bFatalIfMissing , int index )
2018-04-13 19:23:45 +03:00
{
resource_t * r = & cl . resourcelist [ cl . num_resources ] ;
if ( cl . num_resources > = MAX_RESOURCES )
Host_Error ( " Too many resources on client \n " ) ;
cl . num_resources + + ;
Q_strncpy ( r - > szFileName , name , sizeof ( r - > szFileName ) ) ;
r - > ucFlags | = bFatalIfMissing ? RES_FATALIFMISSING : 0 ;
r - > nDownloadSize = size ;
r - > nIndex = index ;
r - > type = type ;
return r ;
}
2024-01-27 19:48:17 +03:00
static void CL_CreateResourceList ( void )
2018-04-13 19:23:45 +03:00
{
2022-12-01 05:50:28 +03:00
char szFileName [ MAX_OSPATH ] ;
2024-10-26 19:16:59 +03:00
byte rgucMD5_hash [ 16 ] = { 0 } ;
2018-04-13 19:23:45 +03:00
resource_t * pNewResource ;
int nSize ;
file_t * fp ;
HPAK_FlushHostQueue ( ) ;
cl . num_resources = 0 ;
memset ( rgucMD5_hash , 0 , sizeof ( rgucMD5_hash ) ) ;
2024-10-26 19:16:59 +03:00
if ( cls . legacymode = = PROTO_GOLDSRC )
{
// TODO: actually repack remapped.bmp into a WAD for GoldSrc servers
Q_strncpy ( szFileName , " tempdecal.wad " , sizeof ( szFileName ) ) ;
}
else
{
// sanitize cvar value
if ( Q_strcmp ( cl_logoext . string , " bmp " ) & & Q_strcmp ( cl_logoext . string , " png " ) )
Cvar_DirectSet ( & cl_logoext , " bmp " ) ;
2022-12-01 05:50:28 +03:00
2024-10-26 19:16:59 +03:00
Q_snprintf ( szFileName , sizeof ( szFileName ) , " logos/remapped.%s " , cl_logoext . string ) ;
}
2018-04-13 19:23:45 +03:00
fp = FS_Open ( szFileName , " rb " , true ) ;
2022-12-01 05:50:28 +03:00
if ( ! fp )
return ;
MD5_HashFile ( rgucMD5_hash , szFileName , NULL ) ;
nSize = FS_FileLength ( fp ) ;
if ( nSize ! = 0 )
2018-04-13 19:23:45 +03:00
{
2022-12-01 05:50:28 +03:00
pNewResource = CL_AddResource ( t_decal , szFileName , nSize , false , 0 ) ;
2018-04-13 19:23:45 +03:00
2022-12-01 05:50:28 +03:00
if ( pNewResource )
2018-04-13 19:23:45 +03:00
{
2022-12-01 05:50:28 +03:00
SetBits ( pNewResource - > ucFlags , RES_CUSTOM ) ;
memcpy ( pNewResource - > rgucMD5_hash , rgucMD5_hash , 16 ) ;
2024-06-15 17:05:52 +03:00
HPAK_AddLump ( false , hpk_custom_file . string , pNewResource , NULL , fp ) ;
2018-04-13 19:23:45 +03:00
}
}
2022-12-01 05:50:28 +03:00
FS_Close ( fp ) ;
2018-04-13 19:23:45 +03:00
}
2024-10-20 02:22:09 +03:00
static qboolean CL_StringToProtocol ( const char * s , connprotocol_t * proto )
{
if ( ! Q_stricmp ( s , " current " ) | | ! Q_strcmp ( s , " 49 " ) )
{
* proto = PROTO_CURRENT ;
return true ;
}
if ( ! Q_stricmp ( s , " legacy " ) | | ! Q_strcmp ( s , " 48 " ) )
{
* proto = PROTO_LEGACY ;
return true ;
}
2024-10-26 05:15:46 +03:00
if ( ! Q_stricmp ( s , " goldsrc " ) | | ! Q_stricmp ( s , " gs " ) )
2024-10-20 02:22:09 +03:00
{
* proto = PROTO_GOLDSRC ;
return true ;
}
// quake protocol only used for demos
Con_Printf ( " Unknown protocol. Supported are: 49 (current), 48 (legacy), gs (goldsrc) \n " ) ;
return false ;
}
2018-04-13 19:23:45 +03:00
/*
= = = = = = = = = = = = = = = =
CL_Connect_f
= = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_Connect_f ( void )
2018-04-13 19:23:45 +03:00
{
string server ;
2024-06-15 10:51:05 +03:00
connprotocol_t proto = PROTO_CURRENT ;
2018-04-13 19:23:45 +03:00
2024-06-15 10:51:05 +03:00
// hint to connect by using legacy protocol
2024-10-20 02:22:09 +03:00
if ( Cmd_Argc ( ) = = 3 & & ! CL_StringToProtocol ( Cmd_Argv ( 2 ) , & proto ) & & Cmd_Argc ( ) ! = 2 )
2018-04-13 19:23:45 +03:00
{
2024-06-15 10:51:05 +03:00
Con_Printf ( S_USAGE " connect <server> [protocol] \n " ) ;
2021-01-03 01:28:45 +00:00
return ;
2018-04-13 19:23:45 +03:00
}
Q_strncpy ( server , Cmd_Argv ( 1 ) , sizeof ( server ) ) ;
// if running a local server, kill it and reissue
2024-12-26 02:13:13 +03:00
if ( SV_Active ( ) )
SV_Shutdown ( " Server was killed due to connection to remote server \n " ) ;
2023-05-19 06:49:14 +03:00
NET_Config ( true , ! cl_nat . value ) ; // allow remote
2018-04-13 19:23:45 +03:00
Con_Printf ( " server %s \n " , server ) ;
CL_Disconnect ( ) ;
2019-10-06 07:43:57 +03:00
// TESTTEST: a see console during connection
UI_SetActiveMenu ( false ) ;
Key_SetKeyDest ( key_console ) ;
2018-04-13 19:23:45 +03:00
cls . state = ca_connecting ;
2024-06-15 10:51:05 +03:00
cls . legacymode = proto ;
2018-04-13 19:23:45 +03:00
Q_strncpy ( cls . servername , server , sizeof ( cls . servername ) ) ;
cls . connect_time = MAX_HEARTBEAT ; // CL_CheckForResend() will fire immediately
cls . max_fragment_size = FRAGMENT_MAX_SIZE ; // guess a we can establish connection with maximum fragment size
cls . connect_retry = 0 ;
cls . spectator = false ;
cls . signon = 0 ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
CL_Rcon_f
Send the rest of the command line over as
an unconnected command .
= = = = = = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_Rcon_f ( void )
2018-04-13 19:23:45 +03:00
{
2024-10-26 19:37:26 +03:00
char message [ 1024 ] ;
sizebuf_t msg ;
netadr_t to ;
2018-04-13 19:23:45 +03:00
int i ;
2022-12-12 08:13:24 +03:00
if ( ! COM_CheckString ( rcon_password . string ) )
2018-04-13 19:23:45 +03:00
{
Con_Printf ( " You must set 'rcon_password' before issuing an rcon command. \n " ) ;
return ;
}
2022-07-10 02:04:13 +04:00
NET_Config ( true , false ) ; // allow remote
2018-04-13 19:23:45 +03:00
if ( cls . state > = ca_connected )
{
to = cls . netchan . remote_address ;
}
else
{
2023-05-19 06:49:14 +03:00
if ( ! COM_CheckString ( rcon_address . string ) )
2018-04-13 19:23:45 +03:00
{
Con_Printf ( " You must either be connected or set the 'rcon_address' cvar to issue rcon commands \n " ) ;
return ;
}
2023-05-19 06:49:14 +03:00
NET_StringToAdr ( rcon_address . string , & to ) ;
2024-10-26 19:37:26 +03:00
if ( to . port = = 0 )
to . port = MSG_BigShort ( PORT_SERVER ) ;
}
MSG_Init ( & msg , " RconMessage " , message , sizeof ( message ) ) ;
MSG_WriteLong ( & msg , - 1 ) ;
MSG_WriteStringf ( & msg , C2S_RCON " %s " , rcon_password . string ) ;
MSG_SeekToBit ( & msg , - 8 , SEEK_CUR ) ;
for ( i = 1 ; i < Cmd_Argc ( ) ; i + + )
{
string command ;
Cmd_Escape ( command , Cmd_Argv ( i ) , sizeof ( command ) ) ;
MSG_WriteString ( & msg , command ) ;
MSG_SeekToBit ( & msg , - 8 , SEEK_CUR ) ;
MSG_WriteChar ( & msg , ' ' ) ;
2018-04-13 19:23:45 +03:00
}
2024-10-26 19:37:26 +03:00
MSG_WriteByte ( & msg , 0 ) ;
2021-01-03 01:28:45 +00:00
2024-10-26 19:37:26 +03:00
NET_SendPacket ( NS_CLIENT , MSG_GetNumBytesWritten ( & msg ) , MSG_GetData ( & msg ) , to ) ;
2018-04-13 19:23:45 +03:00
}
/*
= = = = = = = = = = = = = = = = = = = = =
CL_ClearState
= = = = = = = = = = = = = = = = = = = = =
*/
void CL_ClearState ( void )
{
int i ;
CL_ClearResourceLists ( ) ;
for ( i = 0 ; i < MAX_CLIENTS ; i + + )
COM_ClearCustomizationList ( & cl . players [ i ] . customdata , false ) ;
S_StopAllSounds ( true ) ;
CL_ClearEffects ( ) ;
CL_FreeEdicts ( ) ;
2023-01-06 00:14:49 +03:00
PM_ClearPhysEnts ( clgame . pmove ) ;
2018-04-13 19:23:45 +03:00
NetAPI_CancelAllRequests ( ) ;
// wipe the entire cl structure
memset ( & cl , 0 , sizeof ( cl ) ) ;
MSG_Clear ( & cls . netchan . message ) ;
memset ( & clgame . fade , 0 , sizeof ( clgame . fade ) ) ;
memset ( & clgame . shake , 0 , sizeof ( clgame . shake ) ) ;
2023-02-11 06:45:20 +03:00
clgame . mapname [ 0 ] = ' \0 ' ;
2018-04-13 19:23:45 +03:00
Cvar_FullSet ( " cl_background " , " 0 " , FCVAR_READ_ONLY ) ;
cl . maxclients = 1 ; // allow to drawing player in menu
cl . mtime [ 0 ] = cl . mtime [ 1 ] = 1.0f ; // because level starts from 1.0f second
cls . signon = 0 ;
cl . resourcesneeded . pNext = cl . resourcesneeded . pPrev = & cl . resourcesneeded ;
cl . resourcesonhand . pNext = cl . resourcesonhand . pPrev = & cl . resourcesonhand ;
CL_CreateResourceList ( ) ;
CL_ClearSpriteTextures ( ) ; // now all hud sprites are invalid
cl . local . interp_amount = 0.1f ;
cl . local . scr_fov = 90.0f ;
Cvar_SetValue ( " scr_download " , - 1.0f ) ;
Cvar_SetValue ( " scr_loading " , 0.0f ) ;
host . allow_console = host . allow_console_init ;
2019-02-02 05:15:59 +07:00
HTTP_ClearCustomServers ( ) ;
2018-04-13 19:23:45 +03:00
}
/*
= = = = = = = = = = = = = = = = = = = = =
CL_SendDisconnectMessage
Sends a disconnect message to the server
= = = = = = = = = = = = = = = = = = = = =
*/
2024-10-15 06:08:21 +03:00
static void CL_SendDisconnectMessage ( connprotocol_t proto )
2018-04-13 19:23:45 +03:00
{
sizebuf_t buf ;
byte data [ 32 ] ;
if ( cls . state = = ca_disconnected ) return ;
MSG_Init ( & buf , " LastMessage " , data , sizeof ( data ) ) ;
MSG_BeginClientCmd ( & buf , clc_stringcmd ) ;
2024-10-15 06:08:21 +03:00
if ( proto = = PROTO_GOLDSRC )
MSG_WriteString ( & buf , " dropclient \n " ) ;
else MSG_WriteString ( & buf , " disconnect " ) ;
2018-04-13 19:23:45 +03:00
2025-02-06 20:54:18 +03:00
if ( NET_NetadrType ( & cls . netchan . remote_address ) = = NA_UNDEFINED )
NET_NetadrSetType ( & cls . netchan . remote_address , NA_LOOPBACK ) ;
2018-04-13 19:23:45 +03:00
// make sure message will be delivered
Netchan_TransmitBits ( & cls . netchan , MSG_GetNumBitsWritten ( & buf ) , MSG_GetData ( & buf ) ) ;
Netchan_TransmitBits ( & cls . netchan , MSG_GetNumBitsWritten ( & buf ) , MSG_GetData ( & buf ) ) ;
Netchan_TransmitBits ( & cls . netchan , MSG_GetNumBitsWritten ( & buf ) , MSG_GetData ( & buf ) ) ;
}
2019-01-30 16:06:32 +07:00
int CL_GetSplitSize ( void )
{
2024-07-10 10:34:30 +03:00
int splitsize = ( int ) cl_dlmax . value ;
2019-01-30 16:06:32 +07:00
2024-07-10 10:34:30 +03:00
if ( ! FBitSet ( cls . extensions , NET_EXT_SPLITSIZE ) )
2019-01-30 16:06:32 +07:00
return 1400 ;
2024-07-10 10:34:30 +03:00
if ( ( splitsize < FRAGMENT_MIN_SIZE ) | | ( splitsize > FRAGMENT_MAX_SIZE ) )
{
2019-01-30 16:06:32 +07:00
Cvar_SetValue ( " cl_dlmax " , FRAGMENT_DEFAULT_SIZE ) ;
2024-07-10 10:34:30 +03:00
return FRAGMENT_DEFAULT_SIZE ;
}
2019-01-30 16:06:32 +07:00
2024-07-10 10:34:30 +03:00
return ( int ) cl_dlmax . value ;
2019-01-30 16:06:32 +07:00
}
2024-10-16 07:31:27 +03:00
void CL_SetupNetchanForProtocol ( connprotocol_t proto )
{
int ( * pfnBlockSize ) ( void * , fragsize_t ) = CL_GetFragmentSize ;
uint flags = 0 ;
switch ( proto )
{
case PROTO_GOLDSRC :
SetBits ( flags , NETCHAN_USE_MUNGE | NETCHAN_USE_BZIP2 | NETCHAN_GOLDSRC ) ;
pfnBlockSize = CL_GetGoldSrcFragmentSize ;
break ;
case PROTO_LEGACY :
if ( FBitSet ( Q_atoi ( Cmd_Argv ( 1 ) ) , NET_LEGACY_EXT_SPLIT ) )
{
SetBits ( flags , NETCHAN_USE_LEGACY_SPLIT ) ;
Con_Reportf ( " ^2NET_EXT_SPLIT enabled^7 (packet sizes is %d/%d) \n " , ( int ) cl_dlmax . value , 65536 ) ;
}
break ;
default :
2025-01-22 21:18:11 +03:00
if ( ! Host_IsLocalClient ( ) )
SetBits ( flags , NETCHAN_USE_LZSS ) ;
2024-10-16 07:31:27 +03:00
cls . extensions = Q_atoi ( Info_ValueForKey ( Cmd_Argv ( 1 ) , " ext " ) ) ;
if ( FBitSet ( cls . extensions , NET_EXT_SPLITSIZE ) )
Con_Reportf ( " ^2NET_EXT_SPLITSIZE enabled^7 (packet size is %d) \n " , ( int ) cl_dlmax . value ) ;
break ;
}
Netchan_Setup ( NS_CLIENT , & cls . netchan , net_from , Cvar_VariableInteger ( " net_qport " ) , NULL , pfnBlockSize , flags ) ;
}
2018-04-13 19:23:45 +03:00
/*
= = = = = = = = = = = = = = = = = = = = =
CL_Reconnect
build a request to reconnect client
= = = = = = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_Reconnect ( qboolean setup_netchan )
2018-04-13 19:23:45 +03:00
{
if ( setup_netchan )
{
2024-10-16 07:31:27 +03:00
CL_SetupNetchanForProtocol ( cls . legacymode ) ;
2018-04-13 19:23:45 +03:00
}
else
{
// clear channel and stuff
Netchan_Clear ( & cls . netchan ) ;
MSG_Clear ( & cls . netchan . message ) ;
}
cls . demonum = cls . movienum = - 1 ; // not in the demo loop now
cls . state = ca_connected ;
cls . signon = 0 ;
CL_ServerCommand ( true , " new " ) ;
cl . validsequence = 0 ; // haven't gotten a valid frame update yet
cl . delta_sequence = - 1 ; // we'll request a full delta from the baseline
cls . lastoutgoingcommand = - 1 ; // we don't have a backed up cmd history yet
cls . nextcmdtime = host . realtime ; // we can send a cmd right away
cl . last_command_ack = - 1 ;
CL_StartupDemoHeader ( ) ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
CL_Disconnect
Goes from a connected state to full screen console state
Sends a disconnect message to the server
This is also called on Host_Error , so it shouldn ' t cause any errors
= = = = = = = = = = = = = = = = = = = = =
*/
void CL_Disconnect ( void )
{
if ( cls . state = = ca_disconnected )
return ;
cls . connect_time = 0 ;
cls . changedemo = false ;
cls . max_fragment_size = FRAGMENT_MAX_SIZE ; // reset fragment size
2022-08-18 13:17:35 +04:00
Voice_Disconnect ( ) ;
2018-04-13 19:23:45 +03:00
CL_Stop_f ( ) ;
// send a disconnect message to the server
2024-10-15 06:08:21 +03:00
CL_SendDisconnectMessage ( cls . legacymode ) ;
2018-04-13 19:23:45 +03:00
CL_ClearState ( ) ;
S_StopBackgroundTrack ( ) ;
SCR_EndLoadingPlaque ( ) ; // get rid of loading plaque
// clear the network channel, too.
Netchan_Clear ( & cls . netchan ) ;
2019-07-09 03:41:15 +03:00
IN_LockInputDevices ( false ) ; // unlock input devices
2018-04-13 19:23:45 +03:00
cls . state = ca_disconnected ;
2019-07-13 20:54:16 +03:00
memset ( & cls . serveradr , 0 , sizeof ( cls . serveradr ) ) ;
2018-11-27 16:11:26 +03:00
cls . set_lastdemo = false ;
2018-04-13 19:23:45 +03:00
cls . connect_retry = 0 ;
cls . signon = 0 ;
2024-10-15 06:08:21 +03:00
cls . legacymode = PROTO_CURRENT ;
2018-04-13 19:23:45 +03:00
// back to menu in non-developer mode
2024-07-10 10:23:59 +03:00
if ( host_developer . value | | cls . key_dest = = key_menu )
2018-04-13 19:23:45 +03:00
return ;
UI_SetActiveMenu ( true ) ;
}
void CL_Disconnect_f ( void )
{
if ( Host_IsLocalClient ( ) )
Host_EndGame ( true , " disconnected from server \n " ) ;
else CL_Disconnect ( ) ;
}
void CL_Crashed ( void )
{
// already freed
if ( host . status = = HOST_CRASHED ) return ;
if ( host . type ! = HOST_NORMAL ) return ;
if ( ! cls . initialized ) return ;
host . status = HOST_CRASHED ;
CL_Stop_f ( ) ; // stop any demos
// send a disconnect message to the server
2024-10-15 06:08:21 +03:00
CL_SendDisconnectMessage ( cls . legacymode ) ;
2018-04-13 19:23:45 +03:00
Host_WriteOpenGLConfig ( ) ;
Host_WriteConfig ( ) ; // write config
}
/*
= = = = = = = = = = = = = = = = =
CL_LocalServers_f
= = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_LocalServers_f ( void )
2018-04-13 19:23:45 +03:00
{
2025-02-06 20:54:18 +03:00
netadr_t adr = { 0 } ;
2023-08-03 01:11:31 +03:00
2018-04-13 19:23:45 +03:00
Con_Printf ( " Scanning for servers on the local network area... \n " ) ;
2022-07-10 02:04:13 +04:00
NET_Config ( true , true ) ; // allow remote
2019-01-25 20:53:08 +07:00
2018-04-13 19:23:45 +03:00
// send a broadcast packet
2025-02-06 20:54:18 +03:00
NET_NetadrSetType ( & adr , NA_BROADCAST ) ;
2023-01-18 19:21:51 +03:00
adr . port = MSG_BigShort ( PORT_SERVER ) ;
2024-10-19 14:05:00 +03:00
Netchan_OutOfBandPrint ( NS_CLIENT , adr , A2A_INFO " %i " , PROTOCOL_VERSION ) ;
2018-04-13 19:23:45 +03:00
2025-02-06 20:54:18 +03:00
NET_NetadrSetType ( & adr , NA_MULTICAST_IP6 ) ;
2024-10-19 14:05:00 +03:00
Netchan_OutOfBandPrint ( NS_CLIENT , adr , A2A_INFO " %i " , PROTOCOL_VERSION ) ;
2018-04-13 19:23:45 +03:00
}
2022-12-01 01:51:07 +03:00
/*
= = = = = = = = = = = = = = = = =
CL_BuildMasterServerScanRequest
= = = = = = = = = = = = = = = = =
*/
2023-10-22 18:16:42 +03:00
static size_t NONNULL CL_BuildMasterServerScanRequest ( char * buf , size_t size , uint32_t * key , qboolean nat , const char * filter )
2022-12-01 01:51:07 +03:00
{
size_t remaining ;
2023-10-22 18:16:42 +03:00
char * info , temp [ 32 ] ;
2022-12-01 01:51:07 +03:00
if ( unlikely ( size < sizeof ( MS_SCAN_REQUEST ) ) )
return 0 ;
Q_strncpy ( buf , MS_SCAN_REQUEST , size ) ;
info = buf + sizeof ( MS_SCAN_REQUEST ) - 1 ;
remaining = size - sizeof ( MS_SCAN_REQUEST ) ;
2023-10-22 17:55:37 +03:00
Q_strncpy ( info , filter , remaining ) ;
2022-12-01 01:51:07 +03:00
2023-10-22 18:16:42 +03:00
* key = COM_RandomLong ( 0 , 0x7FFFFFFF ) ;
2023-08-08 09:23:23 +03:00
# ifndef XASH_ALL_SERVERS
2022-12-01 01:51:07 +03:00
Info_SetValueForKey ( info , " gamedir " , GI - > gamefolder , remaining ) ;
2023-08-08 09:23:23 +03:00
# endif
2024-06-10 19:48:02 +04:00
// let master know about client version
Info_SetValueForKey ( info , " clver " , XASH_VERSION , remaining ) ;
2022-12-01 01:51:07 +03:00
Info_SetValueForKey ( info , " nat " , nat ? " 1 " : " 0 " , remaining ) ;
2025-01-13 19:20:53 +03:00
Info_SetValueForKey ( info , " commit " , g_buildcommit , remaining ) ;
Info_SetValueForKey ( info , " branch " , g_buildbranch , remaining ) ;
2024-06-10 19:48:02 +04:00
Info_SetValueForKey ( info , " os " , Q_buildos ( ) , remaining ) ;
Info_SetValueForKey ( info , " arch " , Q_buildarch ( ) , remaining ) ;
2022-12-01 01:51:07 +03:00
2024-06-10 19:48:02 +04:00
Q_snprintf ( temp , sizeof ( temp ) , " %d " , Q_buildnum ( ) ) ;
Info_SetValueForKey ( info , " buildnum " , temp , remaining ) ;
2024-10-10 19:06:26 +03:00
2023-10-22 18:16:42 +03:00
Q_snprintf ( temp , sizeof ( temp ) , " %x " , * key ) ;
Info_SetValueForKey ( info , " key " , temp , remaining ) ;
2022-12-01 01:51:07 +03:00
return sizeof ( MS_SCAN_REQUEST ) + Q_strlen ( info ) ;
}
2018-06-01 20:44:16 +03:00
2023-10-22 18:16:42 +03:00
/*
= = = = = = = = = = = = = = = = =
CL_SendMasterServerScanRequest
= = = = = = = = = = = = = = = = =
*/
static void CL_SendMasterServerScanRequest ( void )
{
cls . internetservers_wait = NET_SendToMasters ( NS_CLIENT ,
cls . internetservers_query_len , cls . internetservers_query ) ;
cls . internetservers_pending = true ;
}
2018-04-13 19:23:45 +03:00
/*
= = = = = = = = = = = = = = = = =
CL_InternetServers_f
= = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_InternetServers_f ( void )
2018-04-13 19:23:45 +03:00
{
2023-05-19 06:49:14 +03:00
qboolean nat = cl_nat . value ! = 0.0f ;
2023-10-22 18:16:42 +03:00
uint32_t key ;
2018-04-13 19:23:45 +03:00
2023-10-22 18:16:42 +03:00
if ( Cmd_Argc ( ) > 2 | | ( Cmd_Argc ( ) = = 2 & & ! Info_IsValid ( Cmd_Argv ( 1 ) ) ) )
2023-10-22 17:55:37 +03:00
{
Con_Printf ( S_USAGE " internetservers [filter] \n " ) ;
return ;
}
2023-10-22 18:16:42 +03:00
cls . internetservers_query_len = CL_BuildMasterServerScanRequest (
cls . internetservers_query , sizeof ( cls . internetservers_query ) ,
& cls . internetservers_key , nat , Cmd_Argv ( 1 ) ) ;
2018-04-13 19:23:45 +03:00
2018-10-27 23:31:55 +03:00
Con_Printf ( " Scanning for servers on the internet area... \n " ) ;
2018-04-13 19:23:45 +03:00
2022-12-01 01:51:07 +03:00
NET_Config ( true , true ) ; // allow remote
2023-10-22 18:16:42 +03:00
CL_SendMasterServerScanRequest ( ) ;
2018-04-13 19:23:45 +03:00
}
2024-10-20 02:22:09 +03:00
static void CL_QueryServer_f ( void )
{
netadr_t adr ;
connprotocol_t proto ;
if ( Cmd_Argc ( ) ! = 3 )
{
Con_Printf ( S_USAGE " queryserver <adr> <protocol> \n " ) ;
return ;
}
NET_Config ( true , false ) ;
if ( ! NET_StringToAdr ( Cmd_Argv ( 1 ) , & adr ) )
{
Con_Printf ( S_ERROR " %s: can't parse %s " , __func__ , Cmd_Argv ( 1 ) ) ;
return ;
}
if ( adr . port = = 0 )
adr . port = PORT_SERVER ;
if ( ! CL_StringToProtocol ( Cmd_Argv ( 2 ) , & proto ) )
return ;
switch ( proto )
{
case PROTO_GOLDSRC :
2024-10-26 20:21:45 +03:00
Netchan_OutOfBand ( NS_CLIENT , adr , sizeof ( A2S_GOLDSRC_INFO ) , A2S_GOLDSRC_INFO ) ; // includes null terminator!
2024-10-20 02:22:09 +03:00
break ;
case PROTO_LEGACY :
Netchan_OutOfBandPrint ( NS_CLIENT , adr , A2A_INFO " %i " , PROTOCOL_LEGACY_VERSION ) ;
break ;
case PROTO_CURRENT :
Netchan_OutOfBandPrint ( NS_CLIENT , adr , A2A_INFO " %i " , PROTOCOL_VERSION ) ;
break ;
}
}
2018-04-13 19:23:45 +03:00
/*
= = = = = = = = = = = = = = = = =
CL_Reconnect_f
The server is changing levels
= = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_Reconnect_f ( void )
2018-04-13 19:23:45 +03:00
{
if ( cls . state = = ca_disconnected )
return ;
S_StopAllSounds ( true ) ;
if ( cls . state = = ca_connected )
{
CL_Reconnect ( false ) ;
return ;
}
if ( COM_CheckString ( cls . servername ) )
{
2024-06-15 10:51:05 +03:00
connprotocol_t proto = cls . legacymode ;
2023-01-03 03:19:46 +03:00
2018-04-13 19:23:45 +03:00
if ( cls . state > = ca_connected )
CL_Disconnect ( ) ;
cls . connect_time = MAX_HEARTBEAT ; // fire immediately
cls . demonum = cls . movienum = - 1 ; // not in the demo loop now
cls . state = ca_connecting ;
cls . signon = 0 ;
2024-06-15 10:51:05 +03:00
cls . legacymode = proto ; // don't change protocol
2018-04-13 19:23:45 +03:00
Con_Printf ( " reconnecting... \n " ) ;
}
}
/*
= = = = = = = = = = = = = = = = =
CL_FixupColorStringsForInfoString
all the keys and values must be ends with ^ 7
= = = = = = = = = = = = = = = = =
*/
2024-07-07 06:17:38 +03:00
static void CL_FixupColorStringsForInfoString ( const char * in , char * out , size_t len )
2018-04-13 19:23:45 +03:00
{
qboolean hasPrefix = false ;
qboolean endOfKeyVal = false ;
int color = 7 ;
int count = 0 ;
if ( * in = = ' \\ ' )
{
* out + + = * in + + ;
count + + ;
}
2024-07-07 06:17:38 +03:00
while ( * in & & count < len )
2018-04-13 19:23:45 +03:00
{
if ( IsColorString ( in ) )
color = ColorIndex ( * ( in + 1 ) ) ;
// color the not reset while end of key (or value) was found!
if ( * in = = ' \\ ' & & color ! = 7 )
{
if ( IsColorString ( out - 2 ) )
{
* ( out - 1 ) = ' 7 ' ;
}
else
{
* out + + = ' ^ ' ;
* out + + = ' 7 ' ;
count + = 2 ;
}
color = 7 ;
}
* out + + = * in + + ;
count + + ;
}
// check the remaining value
if ( color ! = 7 )
{
// if the ends with another color rewrite it
if ( IsColorString ( out - 2 ) )
{
* ( out - 1 ) = ' 7 ' ;
}
else
{
* out + + = ' ^ ' ;
* out + + = ' 7 ' ;
count + = 2 ;
}
}
* out = ' \0 ' ;
}
/*
= = = = = = = = = = = = = = = = =
CL_ParseStatusMessage
Handle a reply from a info
= = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_ParseStatusMessage ( netadr_t from , sizebuf_t * msg )
2018-04-13 19:23:45 +03:00
{
2024-10-21 02:31:45 +03:00
static char infostring [ 512 + 8 ] ;
2018-04-13 19:23:45 +03:00
char * s = MSG_ReadString ( msg ) ;
2019-02-04 23:53:46 +07:00
int i ;
2024-10-21 02:31:45 +03:00
const char * magic = " : wrong version \n " , * p ;
2022-08-17 21:52:54 +03:00
size_t len = Q_strlen ( s ) , magiclen = Q_strlen ( magic ) ;
2018-04-13 19:23:45 +03:00
2022-08-17 21:52:54 +03:00
if ( len > = magiclen & & ! Q_strcmp ( s + len - magiclen , magic ) )
2019-01-26 18:54:23 +03:00
{
2024-10-19 14:05:00 +03:00
Netchan_OutOfBandPrint ( NS_CLIENT , from , A2A_INFO " %i " , PROTOCOL_LEGACY_VERSION ) ;
2019-01-26 18:54:23 +03:00
return ;
}
2022-08-17 21:52:54 +03:00
if ( ! Info_IsValid ( s ) )
{
Con_Printf ( " ^1Server^7: %s, invalid infostring \n " , NET_AdrToString ( from ) ) ;
return ;
}
2024-07-07 06:17:38 +03:00
CL_FixupColorStringsForInfoString ( s , infostring , sizeof ( infostring ) ) ;
2022-08-17 21:52:54 +03:00
2018-04-21 08:06:55 +00:00
if ( ! COM_CheckString ( Info_ValueForKey ( infostring , " gamedir " ) ) )
2018-10-04 13:08:48 +07:00
{
Con_Printf ( " ^1Server^7: %s, Info: %s \n " , NET_AdrToString ( from ) , infostring ) ;
return ; // unsupported proto
}
2018-04-21 08:06:55 +00:00
2024-10-21 02:31:45 +03:00
Info_RemoveKey ( infostring , " gs " ) ; // don't let servers pretend they're something else
p = Info_ValueForKey ( infostring , " p " ) ;
if ( ! COM_CheckStringEmpty ( p ) )
{
Info_SetValueForKey ( infostring , " legacy " , " 1 " , sizeof ( infostring ) ) ;
Info_SetValueForKey ( infostring , " p " , " 48 " , sizeof ( infostring ) ) ;
Con_Printf ( " ^3Server^7: %s, Game: %s \n " , NET_AdrToString ( from ) , Info_ValueForKey ( infostring , " gamedir " ) ) ;
}
else if ( ! Q_strcmp ( p , " 48 " ) )
2019-02-04 23:53:46 +07:00
{
2024-10-21 02:31:45 +03:00
Info_SetValueForKey ( infostring , " legacy " , " 1 " , sizeof ( infostring ) ) ;
2022-08-17 21:52:54 +03:00
Con_Printf ( " ^3Server^7: %s, Game: %s \n " , NET_AdrToString ( from ) , Info_ValueForKey ( infostring , " gamedir " ) ) ;
}
else
{
// more info about servers
Con_Printf ( " ^2Server^7: %s, Game: %s \n " , NET_AdrToString ( from ) , Info_ValueForKey ( infostring , " gamedir " ) ) ;
2019-02-04 23:53:46 +07:00
}
2018-04-13 19:23:45 +03:00
UI_AddServerToList ( from , infostring ) ;
}
2024-10-21 02:31:45 +03:00
static void CL_ParseGoldSrcStatusMessage ( netadr_t from , sizebuf_t * msg )
{
static char s [ 512 + 8 ] ;
int p , numcl , maxcl , password , remaining ;
string host , map , gamedir , version ;
connprotocol_t proto ;
2024-11-04 13:16:39 +03:00
char * replace ;
2024-10-21 02:31:45 +03:00
// set to beginning but skip header
MSG_SeekToBit ( msg , ( sizeof ( uint32_t ) + sizeof ( uint8_t ) ) < < 3 , SEEK_SET ) ;
p = MSG_ReadByte ( msg ) ;
Q_strncpy ( host , MSG_ReadString ( msg ) , sizeof ( host ) ) ;
Q_strncpy ( map , MSG_ReadString ( msg ) , sizeof ( map ) ) ;
Q_strncpy ( gamedir , MSG_ReadString ( msg ) , sizeof ( gamedir ) ) ;
MSG_ReadString ( msg ) ; // game description
MSG_ReadShort ( msg ) ; // app id
numcl = MSG_ReadByte ( msg ) ;
maxcl = MSG_ReadByte ( msg ) ;
MSG_ReadByte ( msg ) ; // bots count
MSG_ReadByte ( msg ) ; // dedicated
MSG_ReadByte ( msg ) ; // operating system
password = MSG_ReadByte ( msg ) ;
Q_strncpy ( version , MSG_ReadString ( msg ) , sizeof ( version ) ) ;
if ( MSG_CheckOverflow ( msg ) )
{
Con_Printf ( " %s: malfored info packet from %s \n " , __func__ , NET_AdrToString ( from ) ) ;
return ;
}
// time to figure out protocol
if ( p = = PROTOCOL_VERSION )
proto = PROTO_CURRENT ;
else if ( p = = PROTOCOL_LEGACY_VERSION )
{
if ( Q_stristr ( version , " Stdio " ) )
proto = PROTO_GOLDSRC ;
else
proto = PROTO_LEGACY ;
}
else
{
Con_Printf ( " %s: unsupported protocol %d from %s \n " , __func__ , p , NET_AdrToString ( from ) ) ;
return ;
}
// now construct infostring for mainui
Info_SetValueForKeyf ( s , " p " , sizeof ( s ) , " %i " , proto = = PROTO_CURRENT ? PROTOCOL_VERSION : PROTOCOL_LEGACY_VERSION ) ;
Info_SetValueForKey ( s , " gs " , proto = = PROTO_GOLDSRC ? " 1 " : " 0 " , sizeof ( s ) ) ;
Info_SetValueForKey ( s , " map " , map , sizeof ( s ) ) ;
Info_SetValueForKey ( s , " dm " , " 0 " , sizeof ( s ) ) ; // obsolete keys
Info_SetValueForKey ( s , " team " , " 0 " , sizeof ( s ) ) ;
Info_SetValueForKey ( s , " coop " , " 0 " , sizeof ( s ) ) ;
Info_SetValueForKeyf ( s , " numcl " , sizeof ( s ) , " %i " , numcl ) ;
Info_SetValueForKeyf ( s , " maxcl " , sizeof ( s ) , " %i " , maxcl ) ;
Info_SetValueForKey ( s , " gamedir " , gamedir , sizeof ( s ) ) ;
Info_SetValueForKey ( s , " password " , password ? " 1 " : " 0 " , sizeof ( s ) ) ;
// write host last so we can try to cut off too long hostnames
// TODO: value size limit for infostrings
remaining = sizeof ( s ) - Q_strlen ( s ) - sizeof ( " \\ host \\ " ) - 1 ;
if ( remaining < 0 )
{
// should never happen?
Con_Printf ( S_ERROR " %s: infostring overflow! \n " , __func__ ) ;
return ;
}
2024-11-04 13:16:39 +03:00
while ( ( replace = Q_strpbrk ( host , " \\ \" " ) ) )
{
* replace = ' ' ; // find a better replacement?
}
2024-10-21 02:31:45 +03:00
Info_SetValueForKey ( s , " host " , host , sizeof ( s ) ) ;
UI_AddServerToList ( from , s ) ;
}
2018-04-13 19:23:45 +03:00
/*
= = = = = = = = = = = = = = = = =
CL_ParseNETInfoMessage
Handle a reply from a netinfo
= = = = = = = = = = = = = = = = =
*/
2024-07-07 03:30:34 +03:00
static void CL_ParseNETInfoMessage ( netadr_t from , const char * s )
2018-04-13 19:23:45 +03:00
{
2024-07-07 03:30:34 +03:00
net_request_t * nr = NULL ;
2024-07-07 06:17:38 +03:00
static char infostring [ MAX_PRINT_MSG ] ;
2018-04-13 19:23:45 +03:00
int i , context , type ;
int errorBits = 0 ;
2018-04-23 23:07:54 +03:00
const char * val ;
2024-07-07 06:49:54 +03:00
size_t slen ;
2018-04-13 19:23:45 +03:00
context = Q_atoi ( Cmd_Argv ( 1 ) ) ;
type = Q_atoi ( Cmd_Argv ( 2 ) ) ;
2024-07-07 03:30:34 +03:00
// find request with specified context and type
for ( i = 0 ; i < MAX_REQUESTS ; i + + )
{
if ( clgame . net_requests [ i ] . resp . context = = context & & clgame . net_requests [ i ] . resp . type = = type )
{
nr = & clgame . net_requests [ i ] ;
break ;
}
}
2018-04-13 19:23:45 +03:00
2024-07-07 03:30:34 +03:00
// not found, ignore
if ( nr = = NULL )
return ;
2018-04-13 19:23:45 +03:00
2024-07-07 06:49:54 +03:00
// find the payload
s = Q_strchr ( s , ' ' ) ; // skip netinfo
if ( ! s )
return ;
s = Q_strchr ( s + 1 , ' ' ) ; // skip challenge
if ( ! s )
return ;
2018-04-13 19:23:45 +03:00
2024-07-07 06:49:54 +03:00
s = Q_strchr ( s + 1 , ' ' ) ; // skip type
if ( s )
s + + ; // skip final whitespace
else if ( type ! = NETAPI_REQUEST_PING ) // ping have no payload, and that's ok
return ;
if ( s )
2018-04-13 19:23:45 +03:00
{
2024-07-07 06:49:54 +03:00
if ( s [ 0 ] = = ' \\ ' )
{
// check for errors
val = Info_ValueForKey ( s , " neterror " ) ;
2018-04-13 19:23:45 +03:00
2024-07-07 06:49:54 +03:00
if ( ! Q_stricmp ( val , " protocol " ) )
SetBits ( errorBits , NET_ERROR_PROTO_UNSUPPORTED ) ;
else if ( ! Q_stricmp ( val , " undefined " ) )
SetBits ( errorBits , NET_ERROR_UNDEFINED ) ;
else if ( ! Q_stricmp ( val , " forbidden " ) )
SetBits ( errorBits , NET_ERROR_FORBIDDEN ) ;
2018-04-13 19:23:45 +03:00
2024-07-07 06:49:54 +03:00
CL_FixupColorStringsForInfoString ( s , infostring , sizeof ( infostring ) ) ;
}
else
{
Q_strncpy ( infostring , s , sizeof ( infostring ) ) ;
}
}
else
{
infostring [ 0 ] = 0 ;
2024-07-07 03:30:34 +03:00
}
2018-04-13 19:23:45 +03:00
2024-07-07 03:30:34 +03:00
// setup the answer
nr - > resp . response = infostring ;
nr - > resp . remote_address = from ;
nr - > resp . error = NET_SUCCESS ;
nr - > resp . ping = host . realtime - nr - > timesend ;
2018-04-13 19:23:45 +03:00
2024-07-07 03:30:34 +03:00
if ( nr - > timeout < = host . realtime )
SetBits ( nr - > resp . error , NET_ERROR_TIMEOUT ) ;
SetBits ( nr - > resp . error , errorBits ) ; // misc error bits
nr - > pfnFunc ( & nr - > resp ) ;
if ( ! FBitSet ( nr - > flags , FNETAPI_MULTIPLE_RESPONSE ) )
memset ( nr , 0 , sizeof ( * nr ) ) ; // done
2018-04-13 19:23:45 +03:00
}
/*
= = = = = = = = = = = = = = = = =
CL_ProcessNetRequests
check for timeouts
= = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_ProcessNetRequests ( void )
2018-04-13 19:23:45 +03:00
{
net_request_t * nr ;
int i ;
// find a request with specified context
for ( i = 0 ; i < MAX_REQUESTS ; i + + )
{
nr = & clgame . net_requests [ i ] ;
if ( ! nr - > pfnFunc ) continue ; // not used
if ( nr - > timeout < = host . realtime )
{
// setup the answer
SetBits ( nr - > resp . error , NET_ERROR_TIMEOUT ) ;
nr - > resp . ping = host . realtime - nr - > timesend ;
nr - > pfnFunc ( & nr - > resp ) ;
memset ( nr , 0 , sizeof ( * nr ) ) ; // done
}
}
}
//===================================================================
/*
= = = = = = = = = = = = = = =
CL_SetupOverviewParams
Get initial overview values
= = = = = = = = = = = = = = =
*/
void CL_SetupOverviewParams ( void )
{
ref_overview_t * ov = & clgame . overView ;
float mapAspect , screenAspect , aspect ;
ov - > rotated = ( world . size [ 1 ] < = world . size [ 0 ] ) ? true : false ;
// calculate nearest aspect
mapAspect = world . size [ ! ov - > rotated ] / world . size [ ov - > rotated ] ;
2019-02-18 21:25:26 +03:00
screenAspect = ( float ) refState . width / ( float ) refState . height ;
2018-04-13 19:23:45 +03:00
aspect = Q_max ( mapAspect , screenAspect ) ;
ov - > zNear = world . maxs [ 2 ] ;
ov - > zFar = world . mins [ 2 ] ;
ov - > flZoom = ( 8192.0f / world . size [ ov - > rotated ] ) / aspect ;
VectorAverage ( world . mins , world . maxs , ov - > origin ) ;
memset ( & cls . spectator_state , 0 , sizeof ( cls . spectator_state ) ) ;
if ( cls . spectator )
{
cls . spectator_state . playerstate . friction = 1 ;
cls . spectator_state . playerstate . gravity = 1 ;
cls . spectator_state . playerstate . number = cl . playernum + 1 ;
cls . spectator_state . playerstate . usehull = 1 ;
cls . spectator_state . playerstate . movetype = MOVETYPE_NOCLIP ;
cls . spectator_state . client . maxspeed = clgame . movevars . spectatormaxspeed ;
}
}
2019-07-13 20:54:16 +03:00
/*
= = = = = = = = = = = = = = = = =
CL_IsFromConnectingServer
Used for connectionless packets , when netchan may not be ready .
= = = = = = = = = = = = = = = = =
*/
static qboolean CL_IsFromConnectingServer ( netadr_t from )
{
return NET_IsLocalAddress ( from ) | |
NET_CompareAdr ( cls . serveradr , from ) ;
}
2024-10-19 14:05:00 +03:00
static void CL_HandleTestPacket ( netadr_t from , sizebuf_t * msg )
2018-04-13 19:23:45 +03:00
{
2024-10-19 14:05:00 +03:00
byte recv_buf [ NET_MAX_FRAGMENT ] ;
dword crcValue ;
int realsize ;
dword crcValue2 = 0 ;
2021-01-03 01:28:45 +00:00
2024-10-19 14:05:00 +03:00
// this message only used during connection
// it doesn't make sense after client_connect
if ( cls . state ! = ca_connecting )
return ;
2018-04-13 19:23:45 +03:00
2024-10-19 14:05:00 +03:00
if ( ! CL_IsFromConnectingServer ( from ) )
return ;
2018-04-13 19:23:45 +03:00
2024-10-19 14:05:00 +03:00
crcValue = MSG_ReadLong ( msg ) ;
realsize = MSG_GetMaxBytes ( msg ) - MSG_GetNumBytesRead ( msg ) ;
2018-04-13 19:23:45 +03:00
2024-10-19 14:05:00 +03:00
if ( cls . max_fragment_size ! = MSG_GetMaxBytes ( msg ) )
2018-04-13 19:23:45 +03:00
{
2024-10-19 14:05:00 +03:00
if ( cls . connect_retry > = CL_TEST_RETRIES )
2024-10-16 07:31:27 +03:00
{
2024-10-19 14:05:00 +03:00
// too many fails use default connection method
Con_Printf ( " hi-speed connection is failed, use default method \n " ) ;
CL_SendGetChallenge ( from ) ;
Cvar_SetValue ( " cl_dlmax " , FRAGMENT_DEFAULT_SIZE ) ;
cls . connect_time = host . realtime ;
2024-10-16 07:31:27 +03:00
return ;
}
2024-10-19 14:05:00 +03:00
// if we waiting more than cl_timeout or packet was trashed
cls . connect_time = MAX_HEARTBEAT ;
return ; // just wait for a next responce
2018-04-13 19:23:45 +03:00
}
2024-10-19 14:05:00 +03:00
// reading test buffer
MSG_ReadBytes ( msg , recv_buf , realsize ) ;
// procssing the CRC
CRC32_ProcessBuffer ( & crcValue2 , recv_buf , realsize ) ;
if ( crcValue = = crcValue2 )
2018-04-13 19:23:45 +03:00
{
2024-10-19 14:05:00 +03:00
// packet was sucessfully delivered, adjust the fragment size and get challenge
Con_DPrintf ( " CRC %x is matched, get challenge, fragment size %d \n " , crcValue , cls . max_fragment_size ) ;
CL_SendGetChallenge ( from ) ;
Cvar_SetValue ( " cl_dlmax " , cls . max_fragment_size ) ;
cls . connect_time = host . realtime ;
2018-04-13 19:23:45 +03:00
}
2024-10-19 14:05:00 +03:00
else
2018-04-13 19:23:45 +03:00
{
2024-10-19 14:05:00 +03:00
if ( cls . connect_retry > = CL_TEST_RETRIES )
2018-04-13 19:23:45 +03:00
{
2024-10-19 14:05:00 +03:00
// too many fails use default connection method
Con_Printf ( " hi-speed connection is failed, use default method \n " ) ;
CL_SendGetChallenge ( from ) ;
Cvar_SetValue ( " cl_dlmax " , FRAGMENT_MIN_SIZE ) ;
cls . connect_time = host . realtime ;
2018-04-13 19:23:45 +03:00
return ;
}
2024-10-19 14:05:00 +03:00
Msg ( " got testpacket, CRC mismatched 0x%08x should be 0x%08x, trying next fragment size %d \n " , crcValue2 , crcValue , cls . max_fragment_size > > 1 ) ;
2024-10-10 23:28:04 +03:00
2024-10-19 14:05:00 +03:00
// trying the next size of packet
cls . connect_time = MAX_HEARTBEAT ;
2024-10-10 23:28:04 +03:00
}
2024-10-19 14:05:00 +03:00
}
2024-11-05 21:57:31 +03:00
static void CL_ClientConnect ( connprotocol_t proto , const char * c , netadr_t from )
2024-10-19 14:05:00 +03:00
{
if ( ! CL_IsFromConnectingServer ( from ) )
return ;
2024-06-13 05:15:57 +03:00
2024-10-19 14:05:00 +03:00
if ( cls . state = = ca_connected )
{
Con_DPrintf ( S_ERROR " dup connect received. ignored \n " ) ;
return ;
2018-04-13 19:23:45 +03:00
}
2024-10-19 14:05:00 +03:00
2024-11-05 21:57:31 +03:00
if ( proto = = PROTO_GOLDSRC )
{
if ( Q_strcmp ( c , S2C_GOLDSRC_CONNECTION ) )
{
Con_DPrintf ( S_ERROR " GoldSrc client connect expected but wasn't received, ignored \n " ) ;
return ;
}
if ( Cmd_Argc ( ) > 4 )
cls . build_num = Q_atoi ( Cmd_Argv ( 4 ) ) ;
}
else if ( ! Q_strcmp ( c , S2C_GOLDSRC_CONNECTION ) )
2018-04-13 19:23:45 +03:00
{
2024-10-19 14:05:00 +03:00
Con_DPrintf ( S_ERROR " GoldSrc client connect received but wasn't expected, ignored \n " ) ;
return ;
}
2018-04-13 19:23:45 +03:00
2024-10-19 14:05:00 +03:00
CL_Reconnect ( true ) ;
UI_SetActiveMenu ( cl . background ) ;
}
2023-06-11 07:06:01 +03:00
2024-10-19 14:05:00 +03:00
static void CL_Print ( const char * c , const char * args , netadr_t from , sizebuf_t * msg )
{
const char * s ;
2019-07-13 20:54:16 +03:00
2024-10-19 14:05:00 +03:00
s = c [ 0 ] = = A2C_GOLDSRC_PRINT ? args + 1 : MSG_ReadString ( msg ) ;
2019-07-13 20:54:16 +03:00
2024-10-19 14:05:00 +03:00
if ( ! COM_CheckStringEmpty ( s ) )
return ;
2018-04-13 19:23:45 +03:00
2024-10-19 14:05:00 +03:00
Con_Printf ( " Remote message from %s: \n " , NET_AdrToString ( from ) ) ;
Con_Printf ( " %s%c " , s , s [ Q_strlen ( s ) - 1 ] ! = ' \n ' ? ' \n ' : ' \0 ' ) ;
}
2018-04-13 19:23:45 +03:00
2024-10-19 14:05:00 +03:00
static void CL_Challenge ( const char * c , netadr_t from )
{
if ( cls . state ! = ca_connecting )
return ;
2018-04-13 19:23:45 +03:00
2024-10-19 14:05:00 +03:00
if ( ! CL_IsFromConnectingServer ( from ) )
return ;
2018-04-13 19:23:45 +03:00
2024-10-19 14:05:00 +03:00
// try to autodetect protocol by challenge response
if ( ! Q_strcmp ( c , S2C_GOLDSRC_CHALLENGE ) )
cls . legacymode = PROTO_GOLDSRC ;
2018-10-04 14:27:14 +07:00
2024-10-19 14:05:00 +03:00
// challenge from the server we are connecting to
CL_SendConnectPacket ( cls . legacymode , Q_atoi ( Cmd_Argv ( 1 ) ) ) ;
}
2018-04-13 19:23:45 +03:00
2024-10-19 14:05:00 +03:00
static void CL_ErrorMsg ( const char * c , const char * args , netadr_t from , sizebuf_t * msg )
{
char formatted_msg [ MAX_VA_STRING ] ;
2018-10-04 14:27:14 +07:00
2024-10-19 14:05:00 +03:00
if ( ! CL_IsFromConnectingServer ( from ) )
return ;
if ( msg ! = NULL & & ! Q_strcmp ( c , S2C_ERRORMSG ) )
{
const char * s = MSG_ReadString ( msg ) ;
Q_snprintf ( formatted_msg , sizeof ( formatted_msg ) , " ^3Server message^7 \n %s " , s ) ;
2018-04-13 19:23:45 +03:00
}
2024-10-19 14:05:00 +03:00
else if ( c [ 0 ] = = S2C_GOLDSRC_REJECT )
2018-04-13 19:23:45 +03:00
{
2024-10-19 14:05:00 +03:00
Q_snprintf ( formatted_msg , sizeof ( formatted_msg ) , " ^3Server message^7 \n %s " , args + 1 ) ;
2018-04-13 19:23:45 +03:00
}
2024-10-19 14:05:00 +03:00
else if ( c [ 0 ] = = S2C_GOLDSRC_REJECT_BADPASSWORD )
2018-04-13 19:23:45 +03:00
{
2024-10-19 14:05:00 +03:00
if ( ! Q_strnicmp ( & c [ 1 ] , " BADPASSWORD " , 11 ) )
Q_snprintf ( formatted_msg , sizeof ( formatted_msg ) , " ^3Server message^7 \n %s " , args + 12 ) ;
else
Q_snprintf ( formatted_msg , sizeof ( formatted_msg ) , " ^3Server message^7 \n %s " , args + 1 ) ;
}
2023-06-11 07:06:01 +03:00
2024-10-19 14:05:00 +03:00
// in case we're in console or it's classic mainui which doesn't support messageboxes
if ( ! UI_IsVisible ( ) | | ! UI_ShowMessageBox ( formatted_msg ) )
Msg ( " %s \n " , formatted_msg ) ;
2019-07-13 20:54:16 +03:00
2024-10-19 14:05:00 +03:00
// don't disconnect, errormsg is a FWGS extension and
// always followed by disconnect message
}
2024-10-16 07:31:27 +03:00
2024-10-19 14:05:00 +03:00
static void CL_Reject ( const char * c , const char * args , netadr_t from )
{
// this message only used during connection
// it doesn't make sense after client_connect
if ( cls . state ! = ca_connecting )
2018-04-13 19:23:45 +03:00
return ;
2019-07-13 20:54:16 +03:00
2024-10-19 14:05:00 +03:00
if ( ! CL_IsFromConnectingServer ( from ) )
return ;
2023-06-11 07:06:01 +03:00
2024-10-19 14:05:00 +03:00
CL_ErrorMsg ( c , args , from , NULL ) ;
2019-07-13 20:54:16 +03:00
2024-10-19 14:05:00 +03:00
// a disconnect message from the server, which will happen if the server
// dropped the connection but it is still getting packets from us
CL_Disconnect_f ( ) ;
}
static void CL_ServerList ( netadr_t from , sizebuf_t * msg )
{
if ( ! NET_IsMasterAdr ( from ) )
{
Con_Printf ( S_WARN " unexpected server list packet from %s \n " , NET_AdrToString ( from ) ) ;
return ;
2018-04-13 19:23:45 +03:00
}
2024-10-19 14:05:00 +03:00
// check the extra header
if ( MSG_ReadByte ( msg ) = = 0x7f )
2019-07-13 20:54:16 +03:00
{
2024-10-19 14:05:00 +03:00
uint32_t key = MSG_ReadDword ( msg ) ;
2024-06-15 10:51:05 +03:00
2024-10-19 14:05:00 +03:00
if ( cls . internetservers_key ! = key )
{
Con_Printf ( S_WARN " unexpected server list packet from %s (invalid key) \n " , NET_AdrToString ( from ) ) ;
2019-07-13 20:54:16 +03:00
return ;
2024-10-19 14:05:00 +03:00
}
2019-07-13 20:54:16 +03:00
2024-10-19 14:05:00 +03:00
MSG_ReadByte ( msg ) ; // reserved byte
2019-07-13 20:54:16 +03:00
}
2024-10-19 14:05:00 +03:00
else
2019-07-13 20:54:16 +03:00
{
2024-10-19 14:05:00 +03:00
Con_Printf ( S_WARN " invalid server list packet from %s (missing extra header) \n " , NET_AdrToString ( from ) ) ;
return ;
}
2019-07-13 20:54:16 +03:00
2024-10-19 14:05:00 +03:00
// serverlist got from masterserver
while ( MSG_GetNumBitsLeft ( msg ) > 8 )
{
uint8_t addr [ 16 ] ;
2025-02-06 20:54:18 +03:00
netadr_t servadr = { 0 } ;
2019-07-13 20:54:16 +03:00
2025-02-06 20:54:18 +03:00
if ( NET_NetadrType ( & from ) = = NA_IP6 ) // IPv6 master server only sends IPv6 addresses
2019-07-13 20:54:16 +03:00
{
2024-10-19 14:05:00 +03:00
MSG_ReadBytes ( msg , addr , sizeof ( addr ) ) ;
NET_IP6BytesToNetadr ( & servadr , addr ) ;
2025-02-06 20:54:18 +03:00
NET_NetadrSetType ( & servadr , NA_IP6 ) ;
2019-07-13 20:54:16 +03:00
}
else
{
2024-10-19 14:05:00 +03:00
MSG_ReadBytes ( msg , servadr . ip , sizeof ( servadr . ip ) ) ; // 4 bytes for IP
2025-02-06 20:54:18 +03:00
NET_NetadrSetType ( & servadr , NA_IP ) ;
2019-07-13 20:54:16 +03:00
}
2024-10-19 14:05:00 +03:00
servadr . port = MSG_ReadShort ( msg ) ; // 2 bytes for Port
// list is ends here
if ( ! servadr . port )
break ;
NET_Config ( true , false ) ; // allow remote
Netchan_OutOfBandPrint ( NS_CLIENT , servadr , A2A_INFO " %i " , PROTOCOL_VERSION ) ;
2019-07-13 20:54:16 +03:00
}
2024-10-19 14:05:00 +03:00
if ( cls . internetservers_pending )
2018-04-13 19:23:45 +03:00
{
2024-10-19 14:05:00 +03:00
UI_ResetPing ( ) ;
cls . internetservers_pending = false ;
}
}
2022-08-17 21:17:51 +03:00
2024-10-19 14:05:00 +03:00
/*
= = = = = = = = = = = = = = = = =
CL_ConnectionlessPacket
2023-10-22 18:16:42 +03:00
2024-10-19 14:05:00 +03:00
Responses to broadcasts , etc
= = = = = = = = = = = = = = = = =
*/
static void CL_ConnectionlessPacket ( netadr_t from , sizebuf_t * msg )
{
2024-10-21 02:31:45 +03:00
char * args ;
const char * c ;
2023-10-22 18:16:42 +03:00
2024-10-19 14:05:00 +03:00
MSG_Clear ( msg ) ;
MSG_ReadLong ( msg ) ; // skip the -1
2023-10-22 18:16:42 +03:00
2024-10-19 14:05:00 +03:00
args = MSG_ReadStringLine ( msg ) ;
2024-01-09 16:59:06 +03:00
2024-10-19 14:05:00 +03:00
Cmd_TokenizeString ( args ) ;
c = Cmd_Argv ( 0 ) ;
2018-04-13 19:23:45 +03:00
2024-10-19 14:05:00 +03:00
Con_Reportf ( " %s: %s : %s \n " , __func__ , NET_AdrToString ( from ) , c ) ;
2018-04-13 19:23:45 +03:00
2024-10-19 14:05:00 +03:00
// server connection
if ( ! Q_strcmp ( c , S2C_GOLDSRC_CONNECTION ) | | ! Q_strcmp ( c , S2C_CONNECTION ) )
{
2024-11-05 21:57:31 +03:00
CL_ClientConnect ( cls . legacymode , c , from ) ;
2024-10-19 14:05:00 +03:00
}
else if ( ! Q_strcmp ( c , A2A_INFO ) )
{
CL_ParseStatusMessage ( from , msg ) ; // server responding to a status broadcast
}
2024-10-21 02:31:45 +03:00
else if ( c [ 0 ] = = S2A_GOLDSRC_INFO )
{
CL_ParseGoldSrcStatusMessage ( from , msg ) ;
}
2024-10-19 14:05:00 +03:00
else if ( ! Q_strcmp ( c , A2A_NETINFO ) )
{
CL_ParseNETInfoMessage ( from , args ) ; // server responding to a status broadcast
}
else if ( c [ 0 ] = = A2C_GOLDSRC_PRINT | | ! Q_strcmp ( c , A2C_PRINT ) )
{
CL_Print ( c , args , from , msg ) ;
}
else if ( ! Q_strcmp ( c , S2C_BANDWIDTHTEST ) )
{
CL_HandleTestPacket ( from , msg ) ;
}
else if ( ! Q_strcmp ( c , A2A_PING ) )
{
Netchan_OutOfBandPrint ( NS_CLIENT , from , A2A_ACK ) ;
}
else if ( ! Q_strcmp ( c , A2A_GOLDSRC_PING ) )
{
Netchan_OutOfBandPrint ( NS_CLIENT , from , A2A_GOLDSRC_ACK ) ;
}
else if ( ! Q_strcmp ( c , A2A_ACK ) | | ! Q_strcmp ( c , A2A_GOLDSRC_ACK ) )
{
// no-op
}
else if ( ! Q_strcmp ( c , S2C_CHALLENGE ) | | ! Q_strcmp ( c , S2C_GOLDSRC_CHALLENGE ) )
{
CL_Challenge ( c , from ) ;
}
else if ( ! Q_strcmp ( c , S2C_REJECT ) | | c [ 0 ] = = S2C_GOLDSRC_REJECT | | c [ 0 ] = = S2C_GOLDSRC_REJECT_BADPASSWORD )
{
CL_Reject ( c , args , from ) ;
}
else if ( ! Q_strcmp ( c , S2C_ERRORMSG ) )
{
CL_ErrorMsg ( c , args , from , msg ) ;
}
else if ( ! Q_strcmp ( c , M2A_SERVERSLIST ) )
{
CL_ServerList ( from , msg ) ;
}
else
{
char buf [ MAX_SYSPATH ] ;
int len = sizeof ( buf ) ;
2018-06-01 20:44:16 +03:00
2024-10-19 14:05:00 +03:00
if ( clgame . dllFuncs . pfnConnectionlessPacket ( & from , args , buf , & len ) )
2018-06-01 20:44:16 +03:00
{
2024-10-19 14:05:00 +03:00
// user out of band message (must be handled in SV_ConnectionlessPacket)
if ( len > 0 )
Netchan_OutOfBand ( NS_SERVER , from , len , ( byte * ) buf ) ;
}
else
{
Con_DPrintf ( S_ERROR " bad connectionless packet from %s: \n %s \n " , NET_AdrToString ( from ) , args ) ;
2018-06-01 20:44:16 +03:00
}
2018-04-13 19:23:45 +03:00
}
}
/*
= = = = = = = = = = = = = = = = = = = =
CL_GetMessage
Handles recording and playback of demos , on top of NET_ code
= = = = = = = = = = = = = = = = = = = =
*/
2024-11-18 10:28:21 +03:00
static qboolean CL_GetMessage ( byte * data , size_t * length )
2018-04-13 19:23:45 +03:00
{
if ( cls . demoplayback )
2024-11-18 10:28:21 +03:00
return CL_DemoReadMessage ( data , length ) ;
2018-04-13 19:23:45 +03:00
2024-11-18 10:28:21 +03:00
return NET_GetPacket ( NS_CLIENT , & net_from , data , length ) ;
2018-04-13 19:23:45 +03:00
}
2024-06-15 13:51:02 +03:00
static void CL_ParseNetMessage ( sizebuf_t * msg , void ( * parsefn ) ( sizebuf_t * ) )
{
cls . starting_count = MSG_GetNumBytesRead ( msg ) ; // updates each frame
CL_Parse_Debug ( true ) ; // begin parsing
parsefn ( msg ) ;
cl . frames [ cl . parsecountmod ] . graphdata . msgbytes + = MSG_GetNumBytesRead ( msg ) - cls . starting_count ;
CL_Parse_Debug ( false ) ; // done
// we don't know if it is ok to save a demo message until
// after we have parsed the frame
if ( ! cls . demoplayback )
{
2024-08-14 19:10:00 +03:00
if ( cls . state ! = ca_active )
CL_WriteDemoMessage ( true , cls . starting_count , msg ) ;
2024-06-15 13:51:02 +03:00
if ( cls . demorecording & & ! cls . demowaiting )
CL_WriteDemoMessage ( false , cls . starting_count , msg ) ;
}
}
2018-04-13 19:23:45 +03:00
/*
= = = = = = = = = = = = = = = = =
CL_ReadNetMessage
= = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_ReadNetMessage ( void )
2018-04-13 19:23:45 +03:00
{
size_t curSize ;
2024-06-15 13:51:02 +03:00
void ( * parsefn ) ( sizebuf_t * msg ) ;
switch ( cls . legacymode )
{
case PROTO_LEGACY :
parsefn = CL_ParseLegacyServerMessage ;
break ;
case PROTO_QUAKE :
parsefn = CL_ParseQuakeMessage ;
break ;
case PROTO_GOLDSRC :
2024-10-07 21:44:25 +03:00
parsefn = CL_ParseGoldSrcServerMessage ;
break ;
2024-06-15 13:51:02 +03:00
default :
2024-10-16 07:31:27 +03:00
parsefn = CL_ParseServerMessage ;
break ;
2024-06-15 13:51:02 +03:00
}
2018-04-13 19:23:45 +03:00
while ( CL_GetMessage ( net_message_buffer , & curSize ) )
{
2023-10-22 18:41:44 +03:00
const int split_header = LittleLong ( 0xFFFFFFFE ) ;
2024-06-15 13:51:02 +03:00
if ( cls . legacymode = = PROTO_LEGACY & & ! memcmp ( & split_header , net_message_buffer , sizeof ( split_header ) ) )
2023-10-22 18:41:44 +03:00
{
2019-01-29 19:01:21 +07:00
// Will rewrite existing packet by merged
if ( ! NetSplit_GetLong ( & cls . netchan . netsplit , & net_from , net_message_buffer , & curSize ) )
continue ;
2023-10-22 18:41:44 +03:00
}
2019-01-29 19:01:21 +07:00
2018-04-13 19:23:45 +03:00
MSG_Init ( & net_message , " ServerData " , net_message_buffer , curSize ) ;
// check for connectionless packet (0xffffffff) first
if ( MSG_GetMaxBytes ( & net_message ) > = 4 & & * ( int * ) net_message . pData = = - 1 )
{
CL_ConnectionlessPacket ( net_from , & net_message ) ;
continue ;
}
2021-01-03 01:28:45 +00:00
// can't be a valid sequenced packet
2018-04-13 19:23:45 +03:00
if ( cls . state < ca_connected ) continue ;
2024-06-15 13:56:15 +03:00
if ( ! cls . demoplayback )
2018-04-13 19:23:45 +03:00
{
2024-06-15 13:56:15 +03:00
if ( MSG_GetMaxBytes ( & net_message ) < 8 )
{
2024-06-19 06:46:08 +03:00
Con_Printf ( S_WARN " %s: %s:runt packet \n " , __func__ , NET_AdrToString ( net_from ) ) ;
2024-06-15 13:56:15 +03:00
continue ;
}
2018-04-13 19:23:45 +03:00
2024-06-15 13:56:15 +03:00
// packet from server
if ( ! NET_CompareAdr ( net_from , cls . netchan . remote_address ) )
{
2024-06-19 06:46:08 +03:00
Con_DPrintf ( S_ERROR " %s: %s:sequenced packet without connection \n " , __func__ , NET_AdrToString ( net_from ) ) ;
2024-06-15 13:56:15 +03:00
continue ;
}
2018-04-13 19:23:45 +03:00
2024-06-15 13:56:15 +03:00
if ( ! Netchan_Process ( & cls . netchan , & net_message ) )
continue ; // wasn't accepted for some reason
}
2018-04-13 19:23:45 +03:00
2024-06-15 13:51:02 +03:00
if ( cls . state = = ca_active )
{
cl . frames [ cls . netchan . incoming_sequence & CL_UPDATE_MASK ] . valid = false ;
cl . frames [ cls . netchan . incoming_sequence & CL_UPDATE_MASK ] . choked = false ;
}
else
{
CL_ResetFrame ( & cl . frames [ cls . netchan . incoming_sequence & CL_UPDATE_MASK ] ) ;
}
CL_ParseNetMessage ( & net_message , parsefn ) ;
2018-04-13 19:23:45 +03:00
}
// build list of all solid entities per next frame (exclude clients)
CL_SetSolidEntities ( ) ;
// check for fragmentation/reassembly related packets.
if ( cls . state ! = ca_disconnected & & Netchan_IncomingReady ( & cls . netchan ) )
{
// process the incoming buffer(s)
if ( Netchan_CopyNormalFragments ( & cls . netchan , & net_message , & curSize ) )
{
MSG_Init ( & net_message , " ServerData " , net_message_buffer , curSize ) ;
2024-06-15 13:51:02 +03:00
CL_ParseNetMessage ( & net_message , parsefn ) ;
2018-04-13 19:23:45 +03:00
}
2021-01-03 01:28:45 +00:00
2018-04-13 19:23:45 +03:00
if ( Netchan_CopyFileFragments ( & cls . netchan , & net_message ) )
{
// remove from resource request stuff.
CL_ProcessFile ( true , cls . netchan . incomingfilename ) ;
}
}
Netchan_UpdateProgress ( & cls . netchan ) ;
// check requests for time-expire
CL_ProcessNetRequests ( ) ;
}
/*
= = = = = = = = = = = = = = = = =
CL_ReadPackets
Updates the local time and reads / handles messages
on client net connection .
= = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_ReadPackets ( void )
2018-04-13 19:23:45 +03:00
{
// decide the simulation time
cl . oldtime = cl . time ;
2023-10-14 15:11:26 +03:00
if ( ! cl . paused )
2018-04-13 19:23:45 +03:00
cl . time + = host . frametime ;
// demo time
if ( cls . demorecording & & ! cls . demowaiting )
cls . demotime + = host . frametime ;
CL_ReadNetMessage ( ) ;
2018-04-21 08:06:55 +00:00
CL_ApplyAddAngle ( ) ;
2018-04-13 19:23:45 +03:00
#if 0
// keep cheat cvars are unchanged
if ( cl . maxclients > 1 & & cls . state = = ca_active & & ! host_developer . value )
Cvar_SetCheatState ( ) ;
# endif
// hot precache and downloading resources
if ( cls . signon = = SIGNONS & & cl . lastresourcecheck < host . realtime )
{
2018-06-09 01:28:35 +03:00
double checktime = Host_IsLocalGame ( ) ? 0.1 : 1.0 ;
2018-04-13 19:23:45 +03:00
if ( ! cls . dl . custom & & cl . resourcesneeded . pNext ! = & cl . resourcesneeded )
{
// check resource for downloading and precache
CL_EstimateNeededResources ( ) ;
CL_BatchResourceRequest ( false ) ;
2019-05-19 15:01:23 +03:00
cls . dl . doneregistering = false ;
2018-04-13 19:23:45 +03:00
cls . dl . custom = true ;
}
2018-06-09 01:28:35 +03:00
cl . lastresourcecheck = host . realtime + checktime ;
2018-04-13 19:23:45 +03:00
}
2018-06-09 01:28:35 +03:00
// singleplayer never has connection timeout
if ( NET_IsLocalAddress ( cls . netchan . remote_address ) )
return ;
2018-04-13 19:23:45 +03:00
// if in the debugger last frame, don't timeout
if ( host . frametime > 5.0f ) cls . netchan . last_received = Sys_DoubleTime ( ) ;
2019-07-19 20:23:08 +03:00
2018-04-13 19:23:45 +03:00
// check timeout
if ( cls . state > = ca_connected & & cls . state ! = ca_cinematic & & ! cls . demoplayback )
{
2023-05-19 06:49:14 +03:00
if ( host . realtime - cls . netchan . last_received > cl_timeout . value )
2018-04-13 19:23:45 +03:00
{
Con_Printf ( " \n Server connection timed out. \n " ) ;
CL_Disconnect ( ) ;
return ;
}
}
2021-01-03 01:28:45 +00:00
2018-04-13 19:23:45 +03:00
}
/*
= = = = = = = = = = = = = = = = = = = =
CL_CleanFileName
Replace the displayed name for some resources
= = = = = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static const char * CL_CleanFileName ( const char * filename )
2018-04-13 19:23:45 +03:00
{
if ( COM_CheckString ( filename ) & & filename [ 0 ] = = ' ! ' )
2022-05-25 03:50:06 +03:00
return " customization " ;
return filename ;
2018-04-13 19:23:45 +03:00
}
/*
= = = = = = = = = = = = = = = = = = = =
CL_RegisterCustomization
register custom resource for player
= = = = = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_RegisterCustomization ( resource_t * resource )
2018-04-13 19:23:45 +03:00
{
qboolean bFound = false ;
customization_t * pList ;
for ( pList = cl . players [ resource - > playernum ] . customdata . pNext ; pList ; pList = pList - > pNext )
{
if ( ! memcmp ( pList - > resource . rgucMD5_hash , resource - > rgucMD5_hash , 16 ) )
{
bFound = true ;
break ;
}
}
if ( ! bFound )
{
player_info_t * player = & cl . players [ resource - > playernum ] ;
if ( ! COM_CreateCustomization ( & player - > customdata , resource , resource - > playernum , FCUST_FROMHPAK , NULL , NULL ) )
Con_Printf ( " Unable to create custom decal for player %i \n " , resource - > playernum ) ;
}
else
{
Con_DPrintf ( " Duplicate resource received and ignored. \n " ) ;
}
}
/*
= = = = = = = = = = = = = = = = = = = =
CL_ProcessFile
A file has been received via the fragmentation / reassembly layer , put it in the right spot and
see if we have finished downloading files .
= = = = = = = = = = = = = = = = = = = =
*/
void CL_ProcessFile ( qboolean successfully_received , const char * filename )
{
2020-11-29 04:05:42 +05:00
int sound_len = sizeof ( DEFAULT_SOUNDPATH ) - 1 ;
2018-04-13 19:23:45 +03:00
byte rgucMD5_hash [ 16 ] ;
resource_t * p ;
if ( COM_CheckString ( filename ) & & successfully_received )
{
if ( filename [ 0 ] ! = ' ! ' )
Con_Printf ( " processing %s \n " , filename ) ;
2022-12-09 19:30:07 +04:00
2024-07-30 01:56:45 +03:00
if ( ! Q_strnicmp ( filename , DEFAULT_DOWNLOADED_DIRECTORY , sizeof ( DEFAULT_DOWNLOADED_DIRECTORY ) - 1 ) )
2022-12-09 19:30:07 +04:00
{
// skip "downloaded/" part to avoid mismatch with needed resources list
2024-07-30 01:56:45 +03:00
filename + = sizeof ( DEFAULT_DOWNLOADED_DIRECTORY ) - 1 ;
2022-12-09 19:30:07 +04:00
}
2018-04-13 19:23:45 +03:00
}
else if ( ! successfully_received )
{
Con_Printf ( S_ERROR " server failed to transmit file '%s' \n " , CL_CleanFileName ( filename ) ) ;
}
2022-12-09 19:30:07 +04:00
2024-10-10 23:28:21 +03:00
if ( cls . legacymode = = PROTO_LEGACY )
2019-02-05 01:10:26 +07:00
{
if ( host . downloadcount > 0 )
host . downloadcount - - ;
2024-10-10 23:28:21 +03:00
2019-02-05 01:10:26 +07:00
if ( ! host . downloadcount )
{
MSG_WriteByte ( & cls . netchan . message , clc_stringcmd ) ;
MSG_WriteString ( & cls . netchan . message , " continueloading " ) ;
}
return ;
}
2018-04-13 19:23:45 +03:00
for ( p = cl . resourcesneeded . pNext ; p ! = & cl . resourcesneeded ; p = p - > pNext )
{
if ( ! Q_strnicmp ( filename , " !MD5 " , 4 ) )
{
COM_HexConvert ( filename + 4 , 32 , rgucMD5_hash ) ;
if ( ! memcmp ( p - > rgucMD5_hash , rgucMD5_hash , 16 ) )
break ;
}
2024-05-30 06:47:46 +03:00
else if ( p - > type = = t_sound )
{
const char * pfilename = filename ;
if ( ! Q_strnicmp ( filename , DEFAULT_SOUNDPATH , sound_len ) )
pfilename + = sound_len ;
if ( ! Q_stricmp ( p - > szFileName , pfilename ) )
break ;
}
2018-04-13 19:23:45 +03:00
else
{
2024-05-30 06:47:46 +03:00
if ( ! Q_stricmp ( p - > szFileName , filename ) )
2018-04-13 19:23:45 +03:00
break ;
}
}
if ( p ! = & cl . resourcesneeded )
{
if ( successfully_received )
ClearBits ( p - > ucFlags , RES_WASMISSING ) ;
if ( filename [ 0 ] = = ' ! ' )
{
if ( cls . netchan . tempbuffer )
{
if ( p - > nDownloadSize = = cls . netchan . tempbuffersize )
{
if ( p - > ucFlags & RES_CUSTOM )
{
2024-06-15 17:05:52 +03:00
HPAK_AddLump ( true , hpk_custom_file . string , p , cls . netchan . tempbuffer , NULL ) ;
2018-04-13 19:23:45 +03:00
CL_RegisterCustomization ( p ) ;
}
}
else
{
2019-01-28 18:32:29 +00:00
Con_Printf ( " Downloaded %i bytes for purported %i byte file, ignoring download \n " ,
2024-07-31 00:04:57 +03:00
cls . netchan . tempbuffersize , p - > nDownloadSize ) ;
2018-04-13 19:23:45 +03:00
}
if ( cls . netchan . tempbuffer )
Mem_Free ( cls . netchan . tempbuffer ) ;
}
cls . netchan . tempbuffersize = 0 ;
cls . netchan . tempbuffer = NULL ;
}
// moving to 'onhandle' list even if file was missed
CL_MoveToOnHandList ( p ) ;
}
if ( cls . state ! = ca_disconnected )
{
host . downloadcount = 0 ;
for ( p = cl . resourcesneeded . pNext ; p ! = & cl . resourcesneeded ; p = p - > pNext )
host . downloadcount + + ;
if ( cl . resourcesneeded . pNext = = & cl . resourcesneeded )
{
byte msg_buf [ MAX_INIT_MSG ] ;
sizebuf_t msg ;
MSG_Init ( & msg , " Resource Registration " , msg_buf , sizeof ( msg_buf ) ) ;
if ( CL_PrecacheResources ( ) )
2024-10-07 20:39:54 +03:00
CL_RegisterResources ( & msg , cls . legacymode ) ;
2018-04-13 19:23:45 +03:00
if ( MSG_GetNumBytesWritten ( & msg ) > 0 )
{
Netchan_CreateFragments ( & cls . netchan , & msg ) ;
Netchan_FragSend ( & cls . netchan ) ;
}
}
if ( cls . netchan . tempbuffer )
{
2024-05-30 06:47:46 +03:00
Con_Printf ( " Received a decal %s, but didn't find it in resources needed list! \n " , filename ) ;
2018-04-13 19:23:45 +03:00
Mem_Free ( cls . netchan . tempbuffer ) ;
}
cls . netchan . tempbuffer = NULL ;
cls . netchan . tempbuffersize = 0 ;
}
}
/*
= = = = = = = = = = = = = = = = = = = =
CL_ServerCommand
send command to a server
= = = = = = = = = = = = = = = = = = = =
*/
2021-07-20 16:02:59 +03:00
void CL_ServerCommand ( qboolean reliable , const char * fmt , . . . )
2018-04-13 19:23:45 +03:00
{
char string [ MAX_SYSPATH ] ;
va_list argptr ;
if ( cls . state < ca_connecting )
return ;
va_start ( argptr , fmt ) ;
2023-04-23 18:17:06 +03:00
Q_vsnprintf ( string , sizeof ( string ) , fmt , argptr ) ;
2018-04-13 19:23:45 +03:00
va_end ( argptr ) ;
if ( reliable )
{
MSG_BeginClientCmd ( & cls . netchan . message , clc_stringcmd ) ;
MSG_WriteString ( & cls . netchan . message , string ) ;
}
else
{
MSG_BeginClientCmd ( & cls . datagram , clc_stringcmd ) ;
MSG_WriteString ( & cls . datagram , string ) ;
}
}
2024-07-06 06:09:19 +03:00
/*
= = = = = = = = = = = = = = =
CL_UpdateInfo
tell server about changed userinfo
= = = = = = = = = = = = = = =
*/
void CL_UpdateInfo ( const char * key , const char * value )
{
2024-10-14 03:02:50 +03:00
switch ( cls . legacymode )
2024-07-06 06:09:19 +03:00
{
2024-10-14 03:02:50 +03:00
case PROTO_LEGACY :
2024-07-06 06:09:19 +03:00
if ( cls . state ! = ca_active )
2024-10-14 03:02:50 +03:00
break ;
2024-07-06 06:09:19 +03:00
MSG_BeginClientCmd ( & cls . netchan . message , clc_legacy_userinfo ) ;
MSG_WriteString ( & cls . netchan . message , cls . userinfo ) ;
2024-10-14 03:02:50 +03:00
break ;
case PROTO_GOLDSRC :
if ( ! Q_stricmp ( key , " name " ) & & Q_strnicmp ( value , " [Xash3D] " , 8 ) )
{
// always prepend [Xash3D] on GoldSrc protocol :)
CL_ServerCommand ( true , " setinfo \" %s \" \" [Xash3D]%s \" \n " , key , value ) ;
break ;
}
// intentional fallthrough
default :
CL_ServerCommand ( true , " setinfo \" %s \" \" %s \" \n " , key , value ) ;
break ;
2024-07-06 06:09:19 +03:00
}
}
2018-04-13 19:23:45 +03:00
//=============================================================================
/*
= = = = = = = = = = = = = =
CL_SetInfo_f
= = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_SetInfo_f ( void )
2018-04-13 19:23:45 +03:00
{
convar_t * var ;
if ( Cmd_Argc ( ) = = 1 )
{
Con_Printf ( " User info settings: \n " ) ;
Info_Print ( cls . userinfo ) ;
2024-06-22 07:31:37 +03:00
Con_Printf ( " Total %zu symbols \n " , Q_strlen ( cls . userinfo ) ) ;
2018-04-13 19:23:45 +03:00
return ;
}
if ( Cmd_Argc ( ) ! = 3 )
{
Con_Printf ( S_USAGE " setinfo [ <key> <value> ] \n " ) ;
return ;
}
// NOTE: some userinfo comed from cvars, e.g. cl_lw but we can call "setinfo cl_lw 1"
// without real cvar changing. So we need to lookup for cvar first to make sure what
// our key is not linked with console variable
var = Cvar_FindVar ( Cmd_Argv ( 1 ) ) ;
// make sure what cvar is existed and really part of userinfo
if ( var & & FBitSet ( var - > flags , FCVAR_USERINFO ) )
{
Cvar_DirectSet ( var , Cmd_Argv ( 2 ) ) ;
}
2024-07-07 03:01:37 +03:00
else if ( Info_SetValueForKey ( cls . userinfo , Cmd_Argv ( 1 ) , Cmd_Argv ( 2 ) , sizeof ( cls . userinfo ) ) )
2018-04-13 19:23:45 +03:00
{
// send update only on successfully changed userinfo
Cmd_ForwardToServer ( ) ;
}
}
/*
= = = = = = = = = = = = = =
CL_Physinfo_f
= = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_Physinfo_f ( void )
2018-04-13 19:23:45 +03:00
{
Con_Printf ( " Phys info settings: \n " ) ;
Info_Print ( cls . physinfo ) ;
2024-06-22 07:31:37 +03:00
Con_Printf ( " Total %zu symbols \n " , Q_strlen ( cls . physinfo ) ) ;
2018-04-13 19:23:45 +03:00
}
2024-05-30 06:48:17 +03:00
static qboolean CL_ShouldRescanFilesystem ( void )
{
resource_t * res ;
2024-07-30 15:22:32 +03:00
qboolean retval = false ;
2024-05-30 06:48:17 +03:00
for ( res = cl . resourcesonhand . pNext ; res & & res ! = & cl . resourcesonhand ; res = res - > pNext )
{
if ( res - > type = = t_generic )
{
const char * ext = COM_FileExtension ( res - > szFileName ) ;
2024-07-30 15:22:32 +03:00
if ( ! g_fsapi . IsArchiveExtensionSupported ( ext , IAES_ONLY_REAL_ARCHIVES ) )
continue ;
if ( FBitSet ( res - > ucExtraFlags , RES_EXTRA_ARCHIVE_CHECKED ) )
continue ;
SetBits ( res - > ucExtraFlags , RES_EXTRA_ARCHIVE_CHECKED ) ;
retval = true ;
2024-05-30 06:48:17 +03:00
}
}
2024-07-30 15:22:32 +03:00
2025-02-05 18:41:09 +03:00
if ( FBitSet ( fs_mount_lv . flags | fs_mount_hd . flags | fs_mount_addon . flags | fs_mount_l10n . flags | ui_language . flags , FCVAR_CHANGED ) )
retval = true ;
2024-07-30 15:22:32 +03:00
return retval ;
2024-05-30 06:48:17 +03:00
}
2018-04-13 19:23:45 +03:00
qboolean CL_PrecacheResources ( void )
{
resource_t * pRes ;
2024-05-30 06:48:17 +03:00
// if we downloaded new WAD files or any other archives they must be added to searchpath
if ( CL_ShouldRescanFilesystem ( ) )
2025-02-05 18:22:48 +03:00
FS_Rescan_f ( ) ;
2024-05-30 06:48:17 +03:00
2018-04-13 19:23:45 +03:00
// NOTE: world need to be loaded as first model
for ( pRes = cl . resourcesonhand . pNext ; pRes & & pRes ! = & cl . resourcesonhand ; pRes = pRes - > pNext )
{
if ( FBitSet ( pRes - > ucFlags , RES_PRECACHED ) )
continue ;
if ( pRes - > type ! = t_model | | pRes - > nIndex ! = WORLD_INDEX )
continue ;
cl . models [ pRes - > nIndex ] = Mod_LoadWorld ( pRes - > szFileName , true ) ;
SetBits ( pRes - > ucFlags , RES_PRECACHED ) ;
cl . nummodels = 1 ;
break ;
}
// then we set up all the world submodels
for ( pRes = cl . resourcesonhand . pNext ; pRes & & pRes ! = & cl . resourcesonhand ; pRes = pRes - > pNext )
{
if ( FBitSet ( pRes - > ucFlags , RES_PRECACHED ) )
continue ;
if ( pRes - > type = = t_model & & pRes - > szFileName [ 0 ] = = ' * ' )
{
cl . models [ pRes - > nIndex ] = Mod_ForName ( pRes - > szFileName , false , false ) ;
cl . nummodels = Q_max ( cl . nummodels , pRes - > nIndex + 1 ) ;
SetBits ( pRes - > ucFlags , RES_PRECACHED ) ;
if ( cl . models [ pRes - > nIndex ] = = NULL )
{
2018-06-12 12:14:56 +03:00
Con_Printf ( S_ERROR " submodel %s not found \n " , pRes - > szFileName ) ;
2018-04-13 19:23:45 +03:00
if ( FBitSet ( pRes - > ucFlags , RES_FATALIFMISSING ) )
{
CL_Disconnect_f ( ) ;
return false ;
}
}
}
}
if ( cls . state ! = ca_active )
S_BeginRegistration ( ) ;
// precache all the remaining resources where order is doesn't matter
for ( pRes = cl . resourcesonhand . pNext ; pRes & & pRes ! = & cl . resourcesonhand ; pRes = pRes - > pNext )
{
if ( FBitSet ( pRes - > ucFlags , RES_PRECACHED ) )
continue ;
switch ( pRes - > type )
{
case t_sound :
2024-10-28 12:13:26 +03:00
if ( pRes - > nIndex > = 0 & & pRes - > nIndex < ARRAYSIZE ( cl . sound_precache ) & & pRes - > nIndex < ARRAYSIZE ( cl . sound_index ) )
2018-04-13 19:23:45 +03:00
{
if ( FBitSet ( pRes - > ucFlags , RES_WASMISSING ) )
{
2020-11-29 04:05:42 +05:00
Con_Printf ( S_ERROR " Could not load sound " DEFAULT_SOUNDPATH " %s \n " , pRes - > szFileName ) ;
2018-04-13 19:23:45 +03:00
cl . sound_precache [ pRes - > nIndex ] [ 0 ] = 0 ;
cl . sound_index [ pRes - > nIndex ] = 0 ;
}
else
{
2021-01-03 01:28:45 +00:00
Q_strncpy ( cl . sound_precache [ pRes - > nIndex ] , pRes - > szFileName , sizeof ( cl . sound_precache [ 0 ] ) ) ;
2018-04-13 19:23:45 +03:00
cl . sound_index [ pRes - > nIndex ] = S_RegisterSound ( pRes - > szFileName ) ;
if ( ! cl . sound_index [ pRes - > nIndex ] )
{
if ( FBitSet ( pRes - > ucFlags , RES_FATALIFMISSING ) )
{
S_EndRegistration ( ) ;
CL_Disconnect_f ( ) ;
return false ;
}
}
}
}
else
{
// client sounds
S_RegisterSound ( pRes - > szFileName ) ;
}
break ;
case t_skin :
break ;
case t_model :
2024-10-28 12:13:26 +03:00
if ( pRes - > nIndex > = 0 & & pRes - > nIndex < ARRAYSIZE ( cl . models ) )
2018-04-13 19:23:45 +03:00
{
2024-10-28 12:13:26 +03:00
cl . nummodels = Q_max ( cl . nummodels , pRes - > nIndex + 1 ) ;
if ( pRes - > szFileName [ 0 ] ! = ' * ' )
2018-04-13 19:23:45 +03:00
{
2024-10-28 12:13:26 +03:00
if ( pRes - > nIndex ! = - 1 )
2018-04-13 19:23:45 +03:00
{
2024-10-28 12:13:26 +03:00
cl . models [ pRes - > nIndex ] = Mod_ForName ( pRes - > szFileName , false , true ) ;
if ( cl . models [ pRes - > nIndex ] = = NULL )
2018-04-13 19:23:45 +03:00
{
2024-10-28 12:13:26 +03:00
if ( FBitSet ( pRes - > ucFlags , RES_FATALIFMISSING ) )
{
S_EndRegistration ( ) ;
CL_Disconnect_f ( ) ;
return false ;
}
2018-04-13 19:23:45 +03:00
}
}
2024-10-28 12:13:26 +03:00
else
{
CL_LoadClientSprite ( pRes - > szFileName ) ;
}
2018-04-13 19:23:45 +03:00
}
}
break ;
case t_decal :
2024-10-28 12:13:26 +03:00
if ( ! FBitSet ( pRes - > ucFlags , RES_CUSTOM ) & & pRes - > nIndex > = 0 & & pRes - > nIndex < ARRAYSIZE ( host . draw_decals ) )
2018-04-13 19:23:45 +03:00
Q_strncpy ( host . draw_decals [ pRes - > nIndex ] , pRes - > szFileName , sizeof ( host . draw_decals [ 0 ] ) ) ;
break ;
case t_generic :
2024-10-28 12:13:26 +03:00
if ( pRes - > nIndex > = 0 & & pRes - > nIndex < ARRAYSIZE ( cl . files_precache ) )
{
Q_strncpy ( cl . files_precache [ pRes - > nIndex ] , pRes - > szFileName , sizeof ( cl . files_precache [ 0 ] ) ) ;
cl . numfiles = Q_max ( cl . numfiles , pRes - > nIndex + 1 ) ;
}
2018-04-13 19:23:45 +03:00
break ;
case t_eventscript :
2024-10-28 12:13:26 +03:00
if ( pRes - > nIndex > = 0 & & pRes - > nIndex < ARRAYSIZE ( cl . event_precache ) )
{
Q_strncpy ( cl . event_precache [ pRes - > nIndex ] , pRes - > szFileName , sizeof ( cl . event_precache [ 0 ] ) ) ;
CL_SetEventIndex ( cl . event_precache [ pRes - > nIndex ] , pRes - > nIndex ) ;
}
2018-04-13 19:23:45 +03:00
break ;
default :
break ;
}
SetBits ( pRes - > ucFlags , RES_PRECACHED ) ;
}
// make sure modelcount is in-range
cl . nummodels = bound ( 0 , cl . nummodels , MAX_MODELS ) ;
cl . numfiles = bound ( 0 , cl . numfiles , MAX_CUSTOM ) ;
if ( cls . state ! = ca_active )
S_EndRegistration ( ) ;
return true ;
}
/*
= = = = = = = = = = = = = = = = = =
CL_FullServerinfo_f
Sent by server when serverinfo changes
= = = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_FullServerinfo_f ( void )
2018-04-13 19:23:45 +03:00
{
if ( Cmd_Argc ( ) ! = 2 )
{
Con_Printf ( S_USAGE " fullserverinfo <complete info string> \n " ) ;
return ;
}
Q_strncpy ( cl . serverinfo , Cmd_Argv ( 1 ) , sizeof ( cl . serverinfo ) ) ;
}
/*
= = = = = = = = = = = = = = = = =
CL_Escape_f
Escape to menu from game
= = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_Escape_f ( void )
2018-04-13 19:23:45 +03:00
{
if ( cls . key_dest = = key_menu )
return ;
// the final credits is running
if ( UI_CreditsActive ( ) ) return ;
if ( cls . state = = ca_cinematic )
SCR_NextMovie ( ) ; // jump to next movie
else UI_SetActiveMenu ( true ) ;
}
2024-12-20 01:46:52 +03:00
static void CL_ListMessages_f ( void )
{
int i ;
Con_Printf ( " num size name \n " ) ;
for ( i = 0 ; i < MAX_USER_MESSAGES ; i + + )
{
if ( ! COM_CheckStringEmpty ( clgame . msg [ i ] . name ) )
break ;
Con_Printf ( " %3d \t %3d \t %s \n " , clgame . msg [ i ] . number , clgame . msg [ i ] . size , clgame . msg [ i ] . name ) ;
}
Con_Printf ( " Total %i messages \n " , i ) ;
}
2025-03-17 10:01:06 -04:00
2018-04-13 19:23:45 +03:00
/*
= = = = = = = = = = = = = = = = =
CL_InitLocal
= = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_InitLocal ( void )
2018-04-13 19:23:45 +03:00
{
cls . state = ca_disconnected ;
cls . signon = 0 ;
2019-07-13 20:54:16 +03:00
memset ( & cls . serveradr , 0 , sizeof ( cls . serveradr ) ) ;
2018-04-13 19:23:45 +03:00
cl . resourcesneeded . pNext = cl . resourcesneeded . pPrev = & cl . resourcesneeded ;
cl . resourcesonhand . pNext = cl . resourcesonhand . pPrev = & cl . resourcesonhand ;
2024-10-10 19:06:26 +03:00
Cvar_RegisterVariable ( & cl_ticket_generator ) ;
2024-10-07 20:59:40 +03:00
Cvar_RegisterVariable ( & showpause ) ;
2018-04-13 19:23:45 +03:00
Cvar_RegisterVariable ( & mp_decals ) ;
Cvar_RegisterVariable ( & dev_overview ) ;
Cvar_RegisterVariable ( & cl_resend ) ;
Cvar_RegisterVariable ( & cl_allow_upload ) ;
Cvar_RegisterVariable ( & cl_allow_download ) ;
Cvar_RegisterVariable ( & cl_download_ingame ) ;
Cvar_RegisterVariable ( & cl_logofile ) ;
Cvar_RegisterVariable ( & cl_logocolor ) ;
2022-12-01 05:50:28 +03:00
Cvar_RegisterVariable ( & cl_logoext ) ;
2024-06-11 07:47:01 +03:00
Cvar_RegisterVariable ( & cl_logomaxdim ) ;
2018-04-13 19:23:45 +03:00
Cvar_RegisterVariable ( & cl_test_bandwidth ) ;
2021-05-09 16:32:53 +03:00
Voice_RegisterCvars ( ) ;
2023-04-03 06:03:29 +03:00
VGui_RegisterCvars ( ) ;
2021-05-09 16:32:53 +03:00
2018-04-13 19:23:45 +03:00
// register our variables
2023-05-19 06:49:14 +03:00
Cvar_RegisterVariable ( & cl_crosshair ) ;
Cvar_RegisterVariable ( & cl_nodelta ) ;
Cvar_RegisterVariable ( & cl_idealpitchscale ) ;
Cvar_RegisterVariable ( & cl_solid_players ) ;
Cvar_RegisterVariable ( & cl_interp ) ;
Cvar_RegisterVariable ( & cl_timeout ) ;
Cvar_RegisterVariable ( & cl_charset ) ;
Cvar_RegisterVariable ( & hud_utf8 ) ;
2018-04-13 19:23:45 +03:00
2023-05-19 06:49:14 +03:00
Cvar_RegisterVariable ( & rcon_address ) ;
2018-04-13 19:23:45 +03:00
2025-01-23 13:57:15 +03:00
Cvar_RegisterVariable ( & cl_trace_consistency ) ;
2024-02-17 22:16:39 +03:00
Cvar_RegisterVariable ( & cl_trace_stufftext ) ;
2023-05-19 06:49:14 +03:00
Cvar_RegisterVariable ( & cl_trace_messages ) ;
2023-07-02 04:48:43 +03:00
Cvar_RegisterVariable ( & cl_trace_events ) ;
2018-04-29 04:25:30 +03:00
2018-04-13 19:23:45 +03:00
// userinfo
2023-05-19 06:49:14 +03:00
Cvar_RegisterVariable ( & cl_nopred ) ;
2024-07-28 13:33:48 +03:00
Q_strncpy ( username , Sys_GetCurrentUser ( ) , sizeof ( username ) ) ; // initialize before registering variable
2023-05-19 06:49:14 +03:00
Cvar_RegisterVariable ( & name ) ;
2024-07-28 13:33:48 +03:00
Cvar_Get ( " ui_username " , username , FCVAR_READ_ONLY | FCVAR_PRIVILEGED , " default user name " ) ;
2023-05-19 06:49:14 +03:00
Cvar_RegisterVariable ( & model ) ;
Cvar_RegisterVariable ( & cl_updaterate ) ;
Cvar_RegisterVariable ( & cl_dlmax ) ;
Cvar_RegisterVariable ( & cl_upmax ) ;
Cvar_RegisterVariable ( & cl_nat ) ;
Cvar_RegisterVariable ( & rate ) ;
Cvar_RegisterVariable ( & topcolor ) ;
Cvar_RegisterVariable ( & bottomcolor ) ;
Cvar_RegisterVariable ( & cl_lw ) ;
2018-04-13 19:23:45 +03:00
Cvar_Get ( " cl_lc " , " 1 " , FCVAR_ARCHIVE | FCVAR_USERINFO , " enable lag compensation " ) ;
Cvar_Get ( " password " , " " , FCVAR_USERINFO , " server password " ) ;
Cvar_Get ( " team " , " " , FCVAR_USERINFO , " player team " ) ;
Cvar_Get ( " skin " , " " , FCVAR_USERINFO , " player skin " ) ;
2023-05-19 06:49:14 +03:00
Cvar_RegisterVariable ( & cl_nosmooth ) ;
Cvar_RegisterVariable ( & cl_nointerp ) ;
Cvar_RegisterVariable ( & cl_smoothtime ) ;
Cvar_RegisterVariable ( & cl_cmdbackup ) ;
Cvar_RegisterVariable ( & cl_cmdrate ) ;
Cvar_RegisterVariable ( & cl_draw_particles ) ;
Cvar_RegisterVariable ( & cl_draw_tracers ) ;
Cvar_RegisterVariable ( & cl_draw_beams ) ;
Cvar_RegisterVariable ( & cl_lightstyle_lerping ) ;
Cvar_RegisterVariable ( & cl_showerror ) ;
Cvar_RegisterVariable ( & cl_bmodelinterp ) ;
Cvar_RegisterVariable ( & cl_clockreset ) ;
Cvar_RegisterVariable ( & cl_fixtimerate ) ;
Cvar_RegisterVariable ( & hud_fontscale ) ;
2024-02-09 07:25:47 +03:00
Cvar_RegisterVariable ( & hud_fontrender ) ;
2023-05-19 06:49:14 +03:00
Cvar_RegisterVariable ( & hud_scale ) ;
2024-01-29 04:58:04 +03:00
Cvar_RegisterVariable ( & hud_scale_minimal_width ) ;
2018-04-13 19:23:45 +03:00
Cvar_Get ( " cl_background " , " 0 " , FCVAR_READ_ONLY , " indicate what background map is running " ) ;
2023-05-19 06:49:14 +03:00
Cvar_RegisterVariable ( & cl_showevents ) ;
2018-11-27 16:11:26 +03:00
Cvar_Get ( " lastdemo " , " " , FCVAR_ARCHIVE , " last played demo " ) ;
2023-05-19 06:49:14 +03:00
Cvar_RegisterVariable ( & ui_renderworld ) ;
2023-12-24 07:46:09 +03:00
Cvar_RegisterVariable ( & cl_maxframetime ) ;
2024-01-04 03:31:49 +03:00
Cvar_RegisterVariable ( & cl_fixmodelinterpolationartifacts ) ;
2018-04-13 19:23:45 +03:00
// server commands
Cmd_AddCommand ( " noclip " , NULL , " enable or disable no clipping mode " ) ;
Cmd_AddCommand ( " notarget " , NULL , " notarget mode (monsters do not see you) " ) ;
Cmd_AddCommand ( " fullupdate " , NULL , " re-init HUD on start demo recording " ) ;
Cmd_AddCommand ( " give " , NULL , " give specified item or weapon " ) ;
Cmd_AddCommand ( " drop " , NULL , " drop current/specified item or weapon " ) ;
Cmd_AddCommand ( " gametitle " , NULL , " show game logo " ) ;
2021-11-02 12:51:47 +06:00
Cmd_AddRestrictedCommand ( " kill " , NULL , " die instantly " ) ;
2018-04-13 19:23:45 +03:00
Cmd_AddCommand ( " god " , NULL , " enable godmode " ) ;
Cmd_AddCommand ( " fov " , NULL , " set client field of view " ) ;
2021-01-03 01:28:45 +00:00
2022-11-05 17:24:46 +04:00
Cmd_AddRestrictedCommand ( " ent_list " , NULL , " list entities on server " ) ;
Cmd_AddRestrictedCommand ( " ent_fire " , NULL , " fire entity command (be careful) " ) ;
Cmd_AddRestrictedCommand ( " ent_info " , NULL , " dump entity information " ) ;
Cmd_AddRestrictedCommand ( " ent_create " , NULL , " create entity with specified values (be careful) " ) ;
Cmd_AddRestrictedCommand ( " ent_getvars " , NULL , " put parameters of specified entities to client's' ent_last_* cvars " ) ;
2021-01-03 01:28:45 +00:00
2018-04-13 19:23:45 +03:00
// register our commands
Cmd_AddCommand ( " pause " , NULL , " pause the game (if the server allows pausing) " ) ;
2024-10-20 02:22:09 +03:00
Cmd_AddRestrictedCommand ( " localservers " , CL_LocalServers_f , " collect info about local servers " ) ;
Cmd_AddRestrictedCommand ( " internetservers " , CL_InternetServers_f , " collect info about internet servers " ) ;
Cmd_AddRestrictedCommand ( " queryserver " , CL_QueryServer_f , " query server info from console " ) ;
2018-04-13 19:23:45 +03:00
Cmd_AddCommand ( " cd " , CL_PlayCDTrack_f , " Play cd-track (not real cd-player of course) " ) ;
Cmd_AddCommand ( " mp3 " , CL_PlayCDTrack_f , " Play mp3-track (based on virtual cd-player) " ) ;
2021-03-07 02:51:03 +03:00
Cmd_AddCommand ( " waveplaylen " , CL_WavePlayLen_f , " Get approximate length of wave file " ) ;
2018-04-13 19:23:45 +03:00
2021-11-02 12:51:47 +06:00
Cmd_AddRestrictedCommand ( " setinfo " , CL_SetInfo_f , " examine or change the userinfo string (alias of userinfo) " ) ;
Cmd_AddRestrictedCommand ( " userinfo " , CL_SetInfo_f , " examine or change the userinfo string (alias of setinfo) " ) ;
2018-04-13 19:23:45 +03:00
Cmd_AddCommand ( " physinfo " , CL_Physinfo_f , " print current client physinfo " ) ;
Cmd_AddCommand ( " disconnect " , CL_Disconnect_f , " disconnect from server " ) ;
Cmd_AddCommand ( " record " , CL_Record_f , " record a demo " ) ;
Cmd_AddCommand ( " playdemo " , CL_PlayDemo_f , " play a demo " ) ;
Cmd_AddCommand ( " timedemo " , CL_TimeDemo_f , " demo benchmark " ) ;
2018-11-27 16:11:26 +03:00
Cmd_AddCommand ( " killdemo " , CL_DeleteDemo_f , " delete a specified demo file " ) ;
2018-04-13 19:23:45 +03:00
Cmd_AddCommand ( " startdemos " , CL_StartDemos_f , " start playing back the selected demos sequentially " ) ;
Cmd_AddCommand ( " demos " , CL_Demos_f , " restart looping demos defined by the last startdemos command " ) ;
Cmd_AddCommand ( " movie " , CL_PlayVideo_f , " play a movie " ) ;
Cmd_AddCommand ( " stop " , CL_Stop_f , " stop playing or recording a demo " ) ;
2024-08-03 09:16:00 +03:00
Cmd_AddCommand ( " listdemo " , CL_ListDemo_f , " list demo entries " ) ;
2018-04-13 19:23:45 +03:00
Cmd_AddCommand ( " info " , NULL , " collect info about local servers with specified protocol " ) ;
Cmd_AddCommand ( " escape " , CL_Escape_f , " escape from game to menu " ) ;
Cmd_AddCommand ( " togglemenu " , CL_Escape_f , " toggle between game and menu " ) ;
Cmd_AddCommand ( " pointfile " , CL_ReadPointFile_f , " show leaks on a map (if present of course) " ) ;
Cmd_AddCommand ( " linefile " , CL_ReadLineFile_f , " show leaks on a map (if present of course) " ) ;
Cmd_AddCommand ( " fullserverinfo " , CL_FullServerinfo_f , " sent by server when serverinfo changes " ) ;
Cmd_AddCommand ( " upload " , CL_BeginUpload_f , " uploading file to the server " ) ;
2021-01-03 01:28:45 +00:00
2025-02-02 05:22:46 +03:00
Cmd_AddRestrictedCommand ( " replaybufferdat " , CL_ReplayBufferDat_f , " development and debugging tool " ) ;
2021-11-02 12:51:47 +06:00
Cmd_AddRestrictedCommand ( " quit " , CL_Quit_f , " quit from game " ) ;
Cmd_AddRestrictedCommand ( " exit " , CL_Quit_f , " quit from game " ) ;
2018-04-13 19:23:45 +03:00
2024-01-28 10:43:37 +03:00
Cmd_AddCommand ( " screenshot " , CL_GenericShot_f , " takes a screenshot of the next rendered frame " ) ;
Cmd_AddCommand ( " snapshot " , CL_GenericShot_f , " takes a snapshot of the next rendered frame " ) ;
Cmd_AddCommand ( " envshot " , CL_GenericShot_f , " takes a six-sides cubemap shot with specified name " ) ;
Cmd_AddCommand ( " skyshot " , CL_GenericShot_f , " takes a six-sides envmap (skybox) shot with specified name " ) ;
2018-04-13 19:23:45 +03:00
Cmd_AddCommand ( " levelshot " , CL_LevelShot_f , " same as \" screenshot \" , used for create plaque images " ) ;
2024-01-28 10:43:37 +03:00
Cmd_AddCommand ( " saveshot " , CL_GenericShot_f , " used for create save previews with LoadGame menu " ) ;
2018-04-13 19:23:45 +03:00
2021-12-23 01:25:03 +03:00
Cmd_AddCommand ( " connect " , CL_Connect_f , " connect to a server by hostname " ) ;
2018-04-13 19:23:45 +03:00
Cmd_AddCommand ( " reconnect " , CL_Reconnect_f , " reconnect to current level " ) ;
Cmd_AddCommand ( " rcon " , CL_Rcon_f , " sends a command to the server console (rcon_password and rcon_address required) " ) ;
2019-01-25 20:53:08 +07:00
Cmd_AddCommand ( " precache " , CL_LegacyPrecache_f , " legacy server compatibility " ) ;
2024-10-16 05:57:24 +03:00
Cmd_AddCommand ( " richpresence_gamemode " , Cmd_Null_f , " compatibility command, does nothing " ) ;
Cmd_AddCommand ( " richpresence_update " , Cmd_Null_f , " compatibility command, does nothing " ) ;
2024-12-20 01:46:52 +03:00
Cmd_AddCommand ( " cl_list_messages " , CL_ListMessages_f , " list registered user messages " ) ;
2025-03-17 10:01:06 -04:00
//Xrasher aim assist
Cvar_RegisterVariable ( & xrasher_aimnear ) ;
Cvar_RegisterVariable ( & xrasher_aimsmart ) ;
Cmd_AddCommand ( " xrasher_newname " , CL_XrasherNewName , " Pick a new random name. " ) ;
2018-04-13 19:23:45 +03:00
}
//============================================================================
/*
= = = = = = = = = = = = = = = = = =
CL_AdjustClock
slowly adjuct client clock
to smooth lag effect
= = = = = = = = = = = = = = = = = =
*/
2024-01-27 19:48:17 +03:00
static void CL_AdjustClock ( void )
2018-04-13 19:23:45 +03:00
{
2023-05-19 06:49:14 +03:00
if ( cl . timedelta = = 0.0f | | ! cl_fixtimerate . value )
2018-04-13 19:23:45 +03:00
return ;
2023-05-19 06:49:14 +03:00
if ( cl_fixtimerate . value < 0.0f )
2018-04-13 19:23:45 +03:00
Cvar_SetValue ( " cl_fixtimerate " , 7.5f ) ;
if ( fabs ( cl . timedelta ) > = 0.001f )
{
2019-10-10 04:21:50 +03:00
double msec , adjust ;
2023-01-07 07:20:38 +03:00
double sign ;
2018-04-13 19:23:45 +03:00
2023-01-07 07:20:38 +03:00
msec = ( cl . timedelta * 1000.0 ) ;
sign = ( msec < 0 ) ? 1.0 : - 1.0 ;
2023-05-19 06:49:14 +03:00
msec = Q_min ( cl_fixtimerate . value , fabs ( msec ) ) ;
2023-01-07 07:20:38 +03:00
adjust = sign * ( msec / 1000.0 ) ;
2018-04-13 19:23:45 +03:00
if ( fabs ( adjust ) < fabs ( cl . timedelta ) )
{
cl . timedelta + = adjust ;
cl . time + = adjust ;
}
if ( cl . oldtime > cl . time )
cl . oldtime = cl . time ;
}
}
/*
= = = = = = = = = = = = = = = = = =
Host_ClientBegin
= = = = = = = = = = = = = = = = = =
*/
void Host_ClientBegin ( void )
{
// exec console commands
Cbuf_Execute ( ) ;
2021-06-06 16:19:43 +03:00
// if client is not active, do nothing
if ( ! cls . initialized ) return ;
2018-04-13 19:23:45 +03:00
// finalize connection process if needs
CL_CheckClientState ( ) ;
// tell the client.dll about client data
CL_UpdateClientData ( ) ;
// if running the server locally, make intentions now
if ( SV_Active ( ) ) CL_SendCommand ( ) ;
}
/*
= = = = = = = = = = = = = = = = = =
Host_ClientFrame
= = = = = = = = = = = = = = = = = =
*/
void Host_ClientFrame ( void )
{
// if client is not active, do nothing
if ( ! cls . initialized ) return ;
2023-12-24 07:46:09 +03:00
if ( cls . key_dest = = key_game & & cls . state = = ca_active & & ! Con_Visible ( ) )
Platform_SetTimer ( cl_maxframetime . value ) ;
2018-04-13 19:23:45 +03:00
// if running the server remotely, send intentions now after
// the incoming messages have been read
if ( ! SV_Active ( ) ) CL_SendCommand ( ) ;
clgame . dllFuncs . pfnFrame ( host . frametime ) ;
// remember last received framenum
CL_SetLastUpdate ( ) ;
// read updates from server
CL_ReadPackets ( ) ;
2025-03-17 10:01:06 -04:00
// Xrasher flashbang be-gone!
memset ( & clgame . fade , 0 , sizeof ( clgame . fade ) ) ;
memset ( & clgame . shake , 0 , sizeof ( clgame . shake ) ) ;
2018-04-13 19:23:45 +03:00
// do prediction again in case we got
// a new portion updates from server
CL_RedoPrediction ( ) ;
2021-05-09 16:32:53 +03:00
// update voice
Voice_Idle ( host . frametime ) ;
2018-04-13 19:23:45 +03:00
// emit visible entities
CL_EmitEntities ( ) ;
// in case we lost connection
CL_CheckForResend ( ) ;
// procssing resources on handle
while ( CL_RequestMissingResources ( ) ) ;
// handle thirdperson camera
CL_MoveThirdpersonCamera ( ) ;
// handle spectator movement
CL_MoveSpectatorCamera ( ) ;
// catch changes video settings
VID_CheckChanges ( ) ;
// update the screen
SCR_UpdateScreen ( ) ;
// update audio
SND_UpdateSound ( ) ;
// play avi-files
SCR_RunCinematic ( ) ;
// adjust client time
CL_AdjustClock ( ) ;
}
//============================================================================
/*
= = = = = = = = = = = = = = = = = = = =
CL_Init
= = = = = = = = = = = = = = = = = = = =
*/
void CL_Init ( void )
{
2019-07-01 05:50:04 +03:00
string libpath ;
2018-04-13 19:23:45 +03:00
if ( host . type = = HOST_DEDICATED )
return ; // nothing running on the client
CL_InitLocal ( ) ;
2019-03-06 16:23:33 +03:00
VID_Init ( ) ; // init video
2018-04-13 19:23:45 +03:00
S_Init ( ) ; // init sound
2024-01-24 20:52:21 +03:00
Voice_Init ( VOICE_DEFAULT_CODEC , 3 , true ) ; // init voice (do not open the device)
2018-04-13 19:23:45 +03:00
// unreliable buffer. unsed for unreliable commands and voice stream
MSG_Init ( & cls . datagram , " cls.datagram " , cls . datagram_buf , sizeof ( cls . datagram_buf ) ) ;
2019-02-18 21:25:26 +03:00
// IN_TouchInit();
2018-04-17 03:53:01 +03:00
2019-07-07 02:47:45 +03:00
COM_GetCommonLibraryPath ( LIBRARY_CLIENT , libpath , sizeof ( libpath ) ) ;
2019-07-01 05:50:04 +03:00
2024-11-05 21:57:31 +03:00
if ( ! CL_LoadProgs ( libpath ) )
Host_Error ( " can't initialize %s: %s \n " , libpath , COM_GetLibraryError ( ) ) ;
2018-04-13 19:23:45 +03:00
2025-01-25 17:50:43 +03:00
ID_Init ( ) ;
2024-11-05 21:57:31 +03:00
cls . build_num = 0 ;
2018-04-13 19:23:45 +03:00
cls . initialized = true ;
cl . maxclients = 1 ; // allow to drawing player in menu
cls . olddemonum = - 1 ;
cls . demonum = - 1 ;
2025-03-17 10:01:06 -04:00
//Get a new random name
CL_XrasherNewName ( ) ;
2018-04-13 19:23:45 +03:00
}
/*
= = = = = = = = = = = = = = =
CL_Shutdown
= = = = = = = = = = = = = = =
*/
void CL_Shutdown ( void )
{
2024-06-19 06:46:08 +03:00
Con_Printf ( " %s() \n " , __func__ ) ;
2018-04-13 19:23:45 +03:00
2022-06-14 03:30:10 +03:00
if ( ! host . crashed & & cls . initialized )
2018-04-21 00:13:20 +03:00
{
Host_WriteOpenGLConfig ( ) ;
Host_WriteVideoConfig ( ) ;
2019-09-27 02:14:47 +07:00
Touch_WriteConfig ( ) ;
2018-04-21 00:13:20 +03:00
}
2018-04-13 19:23:45 +03:00
2019-02-18 21:25:26 +03:00
// IN_TouchShutdown ();
2018-04-21 00:13:20 +03:00
Joy_Shutdown ( ) ;
CL_CloseDemoHeader ( ) ;
2018-04-13 19:23:45 +03:00
IN_Shutdown ( ) ;
2018-04-21 00:13:20 +03:00
Mobile_Shutdown ( ) ;
2018-04-13 19:23:45 +03:00
SCR_Shutdown ( ) ;
CL_UnloadProgs ( ) ;
2022-06-12 03:06:03 +03:00
cls . initialized = false ;
2022-11-14 20:39:01 +04:00
// for client-side VGUI support we use other order
2023-04-30 10:13:45 +03:00
if ( FI & & FI - > GameInfo & & ! FI - > GameInfo - > internal_vgui_support )
2022-11-14 20:39:01 +04:00
VGui_Shutdown ( ) ;
2018-04-13 19:23:45 +03:00
2023-04-30 10:13:45 +03:00
if ( g_fsapi . Delete )
g_fsapi . Delete ( " demoheader.tmp " ) ; // remove tmp file
2018-04-13 19:23:45 +03:00
SCR_FreeCinematic ( ) ; // release AVI's *after* client.dll because custom renderer may use them
S_Shutdown ( ) ;
R_Shutdown ( ) ;
Con_Shutdown ( ) ;
2022-06-12 03:06:03 +03:00
2018-04-13 19:58:17 +03:00
}