1 //**************************************************************************
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
25 //**************************************************************************
26 #include "../gamedefs.h"
28 #include "../drawer.h"
32 #include "../infostr.h"
33 #include "../net/network.h"
34 #include "../psim/p_entity.h"
35 #include "../psim/p_levelinfo.h"
36 #include "../psim/p_player.h"
37 #include "../filesys/files.h"
38 #include "../sound/sound.h"
41 #include "../widgets/ui.h"
42 #include "../server/server.h"
43 #include "../server/sv_local.h"
44 #include "../automap.h"
46 #define VAVOOM_DEMO_VERSION (1)
49 void CL_SetupNetClient (VSocketPublic
*);
50 void SV_ConnectClient (VBasePlayer
*);
52 void CL_ReadFromServerInfo ();
56 VBasePlayer
*cl
= nullptr;
57 float clWipeTimer
= -1.0f
;
58 VClientNetContext
*ClientNetContext
= nullptr;
60 VClientGameBase
*GClGame
= nullptr;
62 bool UserInfoSent
= false;
64 VCvarS
cl_name("name", "PLAYER", "Player name.", CVAR_Archive
|CVAR_UserInfo
|CVAR_NoShadow
);
65 VCvarS
cl_color("color", "#00ff00", "Player color.", CVAR_Archive
|CVAR_UserInfo
|CVAR_NoShadow
);
66 VCvarI
cl_class("class", "0", "Player class.", /*CVAR_Archive|*/CVAR_UserInfo
|CVAR_NoShadow
); // do not save it, because it may interfere with other games
67 VCvarS
cl_model("model", "", "Player model.", CVAR_Archive
|CVAR_UserInfo
|CVAR_NoShadow
);
69 static VCvarB
cl_autonomous_proxy("cl_autonomous_proxy", false, "Is our client an autonomous proxy?", CVAR_PreInit
|CVAR_NoShadow
);
71 static VCvarB
d_attraction_mode("d_attraction_mode", false, "Allow demo playback (won't work with non-k8vavoom demos)?", CVAR_Archive
|CVAR_NoShadow
);
73 static VCvarB
dbg_always_mark_map("dbg_always_mark_map", false, "Always mark lines on automap?", /*CVAR_Archive|*/CVAR_Hidden
|CVAR_NoShadow
);
75 extern VCvarB r_wipe_enabled
;
77 IMPLEMENT_CLASS(V
, ClientGameBase
);
79 static VName CurrentSongLump
;
80 static double ClientLastKeepAliveTime
= 0.0;
83 //==========================================================================
87 //==========================================================================
89 VMemberBase::StaticLoadPackage(NAME_cgame
, TLocation());
90 // load user-specified Vavoom C script files
91 G_LoadVCMods(VCMODS_CLIENT
);
92 // this emits code for all `PackagesToEmit()`
93 VPackage::StaticEmitPackages();
94 //!TLocation::ClearSourceFiles();
98 //==========================================================================
102 //==========================================================================
104 cl_name
.Set(Sys_GetUserName()); // why not?
105 cl_name
.SetDefault(cl_name
.asStr());
106 if (developer
) GCon
->Logf(NAME_Dev
, "Default user name is '%s'", *cl_name
.asStr());
108 ClientNetContext
= new VClientNetContext();
109 GClGame
= (VClientGameBase
*)VObject::StaticSpawnWithReplace(VClass::FindClass("ClientGame"));
110 GClGame
->Game
= GGameInfo
;
111 GClGame
->eventPostSpawn();
112 CurrentSongLump
= NAME_None
;
113 // preload crosshairs, why not?
114 for (int cnum
= 1; cnum
< 16; ++cnum
) {
115 int handle
= GTextureManager
.AddPatch(VName(va("croshai%x", cnum
), VName::AddLower8
), TEXTYPE_Pic
, true/*silent*/);
116 if (handle
> 0 && developer
) GCon
->Logf(NAME_Debug
, "found crosshair #%d", cnum
);
123 //==========================================================================
127 //==========================================================================
128 static void CL_ResetLastSong () {
129 CurrentSongLump
= NAME_None
;
133 //==========================================================================
137 //==========================================================================
138 static void CL_Ticker () {
140 if (!GClGame
->InIntermission()) {
148 //==========================================================================
152 //==========================================================================
153 void CL_Shutdown () {
154 if (cl
) SV_ShutdownGame(); // disconnect
156 if (GClGame
) GClGame
->ConditionalDestroy();
157 if (GRoot
) GRoot
->ConditionalDestroy();
158 cls
.userinfo
.Clean();
159 delete ClientNetContext
;
160 ClientNetContext
= nullptr;
164 //==========================================================================
168 //==========================================================================
169 void CL_DecayLights () {
170 if (GClLevel
&& GClLevel
->Renderer
) {
171 if (!GGameInfo
|| !GGameInfo
->IsPaused()) {
172 if (host_frametime
> 0.0f
) {
173 //static int cnt = 0; GCon->Logf(NAME_Debug, "decay lights(%d): %g", cnt++, host_frametime);
174 GClLevel
->Renderer
->DecayLights(host_frametime
);
181 //==========================================================================
185 //==========================================================================
186 static void CL_UpdateMobjs (float deltaTime
) {
187 if (!GClLevel
) return; //k8: this is wrong!
188 if (!deltaTime
) return;
189 //GCon->Log(NAME_Debug, "====================== CL_UpdateMobjs ======================");
191 GClLevel
->TickDecals(deltaTime
);
192 if (GGameInfo
->NetMode
== NM_Client
) {
194 // cannot use thinker iterator here, because detached thinker may remove itself...
195 VThinker
*curr
= GClLevel
->ThinkerHead
;
199 if (th
->IsGoingToDie() || !th
->Level
|| th
->Level
->IsGoingToDie()) continue;
200 if (th
->Role
!= ROLE_DumbProxy
) {
201 //GCon->Logf(NAME_Debug, "%s:%u: client-local!", th->GetClass()->GetName(), th->GetUniqueId());
202 // for local thinkers, call their ticker method first
203 if (th
->ThinkerFlags
&VThinker::TF_DetachComplete
) {
204 //GCon->Logf(NAME_Debug, "%s:%u: client-local TICK!", th->GetClass()->GetName(), th->GetUniqueId());
205 // need to set this flag, so spawning could work
206 const vuint32 oldLevelFlags
= GClLevel
->LevelFlags
;
207 GClLevel
->LevelFlags
|= VLevel::LF_ForServer
;
209 GClLevel
->LevelFlags
= oldLevelFlags
;
210 if (th
->IsGoingToDie()) continue; // just in case
213 th
->eventClientTick(deltaTime
);
216 // other game types; don't use iterator too, because it does excessive class checks
217 VThinker
*curr
= GClLevel
->ThinkerHead
;
221 if (!th
->IsGoingToDie()) th
->eventClientTick(deltaTime
);
227 //==========================================================================
231 // Read all incoming data from the server
233 //==========================================================================
234 void CL_ReadFromServer (float deltaTime
) {
238 ClientLastKeepAliveTime
= Sys_Time();
239 cl
->Net
->GetMessages();
240 if (cl
->Net
->IsClosed()) Host_EndGame("Server disconnected");
245 if (GGameInfo
->NetMode
== NM_Client
&& deltaTime
) {
246 GClLevel
->Time
+= deltaTime
;
247 GClLevel
->TicTime
= (int)(GClLevel
->Time
*35.0f
);
250 // bad for interpolator
252 if (GGameInfo->NetMode == NM_Client) {
253 GClLevel->Time = cl->GameTime;
254 GClLevel->TicTime = (int)(GClLevel->Time*35.0f);
259 if (cl->ClCurrGameTime < cl->ClLastGameTime) {
260 GCon->Logf(NAME_Debug, ":RECV: WARP: GT=%g (d:%g); cltime=%g; svtime=%g; d=%g; deltaTime=%g; MO=%g (%g)", cl->GameTime, cl->LastDeltaTime, cl->ClCurrGameTime, cl->ClLastGameTime, cl->ClLastGameTime-cl->ClCurrGameTime, deltaTime,
261 (cl->MO ? cl->MO->DataGameTime : 0), (cl->MO ? cl->ClLastGameTime-cl->MO->DataGameTime : 0));
262 cl->ClCurrGameTime = cl->ClLastGameTime;
264 GCon->Logf(NAME_Debug, ":RECV: REPREDICT: GT=%g (d:%g); cltime=%g; svtime=%g; d=%g; deltaTime=%g MO=%g (%g)", cl->GameTime, cl->LastDeltaTime, cl->ClCurrGameTime, cl->ClLastGameTime, cl->ClCurrGameTime-cl->ClLastGameTime, deltaTime,
265 (cl->MO ? cl->MO->DataGameTime : 0), (cl->MO ? cl->ClLastGameTime-cl->MO->DataGameTime : 0));
269 CL_UpdateMobjs(deltaTime
);
270 // update world tick for client network games (copy from the server tic)
272 cl
->ClCurrGameTime
+= deltaTime
;
273 cl
->eventClientTick(deltaTime
);
277 //GCon->Logf(NAME_Debug, ":RECV: camera is MO:%d; GameTime=%g; ClLastGameTime=%g; ClCurrGameTime=%g", (cl->Camera == cl->MO ? 1 : 0), cl->GameTime, cl->ClLastGameTime, cl->ClCurrGameTime);
280 if (GGameInfo->NetMode == NM_Client && deltaTime) {
281 GCon->Logf(NAME_Debug, "client is not signed on yet (%g)", Sys_Time());
286 if (deltaTime
&& GClLevel
&& GClLevel
->LevelInfo
) {
287 if (CurrentSongLump
!= GClLevel
->LevelInfo
->SongLump
) {
288 CurrentSongLump
= GClLevel
->LevelInfo
->SongLump
;
289 GAudio
->MusicChanged(true/*allowrandom*/);
295 //==========================================================================
297 // CL_NetworkHeartbeat
299 // when the client is taking a long time to load stuff, send keepalive
300 // messages so the server doesn't disconnect
302 //==========================================================================
303 void CL_NetworkHeartbeat (bool forced
) {
304 if (GGameInfo
->NetMode
!= NM_Client
) return; // no need if server is local
305 if (cls
.demorecording
|| cls
.demoplayback
) return;
306 if (!cl
->Net
) return;
307 const double currTime
= Sys_Time();
308 if (ClientLastKeepAliveTime
> currTime
) ClientLastKeepAliveTime
= currTime
; // wtf?!
309 if (!forced
&& currTime
-ClientLastKeepAliveTime
< 1.0/60.0) return;
310 ClientLastKeepAliveTime
= currTime
;
311 cl
->Net
->KeepaliveTick();
315 //==========================================================================
317 // CL_EstablishConnection
319 // host should be a net address to be passed on
321 //==========================================================================
322 void CL_EstablishConnection (const char *host
) {
323 if (GGameInfo
->NetMode
== NM_DedicatedServer
) return;
325 GCon
->Log("shutting down current game");
328 //R_OSDMsgReset(OSD_Network);
329 //R_OSDMsgShow(va("initiating connection to [%s]", (host ? host : "")));
331 GCon
->Logf("connecting to '%s'", (host
? host
: ""));
332 VSocketPublic
*Sock
= GNet
->Connect(host
);
334 GCon
->Log(NAME_Error
, "Failed to connect to the server");
338 GCon
->Log("initialising net client");
339 CL_SetupNetClient(Sock
);
340 GCon
->Logf(NAME_Dev
, "CL_EstablishConnection: connected to '%s'", host
);
341 GGameInfo
->NetMode
= NM_Client
;
343 UserInfoSent
= false;
345 GClGame
->eventConnected();
346 // need all the signon messages before playing
348 ClientLastKeepAliveTime
= Sys_Time();
350 //MN_DeactivateMenu();
354 //==========================================================================
356 // CL_SetupLocalPlayer
358 //==========================================================================
359 void CL_SetupLocalPlayer () {
360 if (GGameInfo
->NetMode
== NM_DedicatedServer
) return;
362 // so we could minimize uniqueid
364 Host_CollectGarbage(true, true); // force-collect garbage, and set new unique id
366 VBasePlayer
*Player
= GPlayersBase
[0];
367 SV_ConnectClient(Player
);
371 cl
->ClGame
= GClGame
;
374 cl
->eventServerSetUserInfo(cls
.userinfo
);
376 GClGame
->eventConnected();
377 // need all the signon messages before playing
378 // but the map may be aloready loaded
381 //MN_DeactivateMenu();
383 CL_SetupStandaloneClient();
387 //==========================================================================
389 // CL_SetupStandaloneClient
391 //==========================================================================
392 void CL_SetupStandaloneClient () {
395 GClGame
->serverinfo
= svs
.serverinfo
;
396 CL_ReadFromServerInfo();
397 // cheating is always enabled in standalone client
398 if (GGameInfo
->NetMode
== NM_TitleMap
|| GGameInfo
->NetMode
== NM_Standalone
) VCvar::SetCheating(true);
400 GClGame
->maxclients
= svs
.max_clients
;
401 GClGame
->deathmatch
= svs
.deathmatch
;
403 const VMapInfo
&LInfo
= P_GetMapInfo(*GLevel
->MapName
);
404 GCon
->Log("---------------------------------------");
405 GCon
->Log(LInfo
.GetName());
409 GClGame
->GLevel
= GClLevel
;
416 GClLevel
->Renderer
->ResetStaticLights();
417 for (auto &&L
: GClLevel
->StaticLights
) {
419 VLightParams
lpar(L
);
420 GClLevel
->Renderer
->AddStaticLightRGB(L
.OwnerUId
, lpar
, L
.Flags
);
423 GClLevel
->Renderer
->PreRender();
427 cls
.clearForStandalone();
429 // if wipe is enabled, tick the world once, so spawned things will fix themselves
430 if (r_wipe_enabled
) {
431 if (developer
) GCon
->Log(NAME_Debug
, "****** WIPE TICK ******");
432 GLevel
->TickWorld(SV_GetFrameTimeConstant(), /*allowVCPause*/false);
433 if (CL_IntermissionPhase()) serverStartRenderFramesTic
= -1;
434 while (GLevel
->TicTime
< serverStartRenderFramesTic
) {
435 if (developer
) GCon
->Logf(NAME_Debug
, "****** WIPE EXTRA TICK (%d : %d) ******", GLevel
->TicTime
, serverStartRenderFramesTic
);
436 GLevel
->TickWorld(SV_GetFrameTimeConstant(), /*allowVCPause*/false);
440 GCon
->Log(NAME_Dev
, "Client level loaded");
441 GCmdBuf
<< "HideConsole\n";
443 Host_ResetSkipFrames();
447 //==========================================================================
451 // returns `true` if not a network client, or if network
452 // client got MO origin
454 //==========================================================================
455 bool CL_GotNetOrigin () {
456 if (!cl
->Net
) return true;
457 if (!cl
->MO
) return false;
458 return cl
->Net
->GetPlayerChannel()->GotMOOrigin
;
462 extern VCvarI sv_maxmove
;
465 //==========================================================================
467 // CL_RunSimulatedPlayerTick
469 //==========================================================================
470 static void CL_RunSimulatedPlayerTick (float deltaTime
) {
471 if (!cl
|| !cl
->Net
|| !cl
->MO
|| !cl
->Net
->GetPlayerChannel()->GotMOOrigin
) return;
472 //if (cl->MO->Role != ROLE_AutonomousProxy) return;
473 if (!cl
->isAutonomousProxy()) return;
475 auto oldplr
= cl
->MO
->Player
;
476 auto oldRole
= cl
->MO
->Role
;
478 cl
->MO
->Role
= ROLE_AutonomousProxy
; // force it, why not?
480 cl
->ForwardMove
= cl
->ClientForwardMove
;
481 cl
->SideMove
= cl
->ClientSideMove
;
483 // client movement handling should take care of this
484 // don't move faster than maxmove (halved, if running is disabled)
485 //FIXME: this should not assume anything, but take walking speed from cvars!
487 const float maxmove = sv_maxmove.asFloat()*(oldplr ? !oldplr->IsRunEnabled() ? 0.5f : 1.0f);
488 if (cl->ForwardMove > maxmove) cl->ForwardMove = maxmove;
489 else if (cl->ForwardMove < -maxmove) cl->ForwardMove = -maxmove;
490 if (cl->SideMove > maxmove) cl->SideMove = maxmove;
491 else if (cl->SideMove < -maxmove) cl->SideMove = -maxmove;
494 //GCon->Logf(NAME_Debug, "SIMPL:000: org=(%g,%g,%g); vel=(%g,%g,%g); fwd=%g; side=%g", cl->MO->Origin.x, cl->MO->Origin.y, cl->MO->Origin.z, cl->MO->Velocity.x, cl->MO->Velocity.y, cl->MO->Velocity.z, cl->ForwardMove, cl->SideMove);
495 //GCon->Logf(NAME_Debug, "000: ViewHeight=%g", cl->ViewHeight);
496 cl
->MO
->Tick(deltaTime
);
497 cl
->eventPlayerTick(deltaTime
);
498 cl
->eventSetViewPos();
499 //GCon->Logf(NAME_Debug, "001: ViewHeight=%g", cl->ViewHeight);
500 //GCon->Logf(NAME_Debug, "SIMPL:001: org=(%g,%g,%g); vel=(%g,%g,%g); fwd=%g; side=%g", cl->MO->Origin.x, cl->MO->Origin.y, cl->MO->Origin.z, cl->MO->Velocity.x, cl->MO->Velocity.y, cl->MO->Velocity.z, cl->ForwardMove, cl->SideMove);
501 cl
->MO
->Player
= oldplr
;
502 cl
->MO
->Role
= oldRole
;
506 //==========================================================================
510 //==========================================================================
511 void CL_SendMove () {
514 if (cls
.demoplayback
|| GGameInfo
->NetMode
== NM_TitleMap
) return;
517 if (!GGameInfo
->IsPaused()) cl
->HandleInput();
519 VPlayerChannel
*pc
= cl
->Net
->GetPlayerChannel();
520 CL_RunSimulatedPlayerTick(host_frametime
);
521 if (pc
) pc
->Update();
522 //GCon->Logf(NAME_Debug, ":SEND: camera is MO:%d; GameTime=%g; ClLastGameTime=%g; ClCurrGameTime=%g", (cl->Camera == cl->MO ? 1 : 0), cl->GameTime, cl->ClLastGameTime, cl->ClCurrGameTime);
526 if (cl
->Net
) cl
->Net
->Tick();
530 //==========================================================================
534 //==========================================================================
535 void CL_NetInterframe () {
536 if (!cl
|| !cl
->Net
) return;
537 if (cls
.demoplayback
|| GGameInfo
->NetMode
== NM_TitleMap
) return;
538 // no need to update channels here
543 //==========================================================================
547 //==========================================================================
548 bool CL_IsInGame () {
549 return (cl
&& GClGame
&& !GClGame
->InIntermission());
553 //==========================================================================
557 //==========================================================================
558 int CL_IntermissionPhase () {
559 return (GClGame
? GClGame
->intermissionPhase
: -666);
563 //==========================================================================
567 // returns `CLState_XXX`
569 //==========================================================================
570 int CL_GetNetState () {
571 if (GGameInfo
->NetMode
!= NM_Client
|| !cl
|| !cl
->Net
) return CLState_None
;
572 if (!cl
->Net
->ObjMapSent
) return CLState_Init
;
573 if (!cls
.signon
) return CLState_Init
;
574 if (cls
.gotmap
< 2) return CLState_Init
; //not sure
575 return CLState_InGame
;
579 //==========================================================================
581 // CL_SendCommandToServer
583 // returns `false` if client networking is not active
585 //==========================================================================
586 bool CL_SendCommandToServer (VStr cmd
) {
587 if (!cl
|| !cl
->Net
) return false;
588 //GCon->Logf(NAME_Debug, "*** sending command over the network: <%s>\n", *Original);
589 cl
->Net
->SendCommand(cmd
);
594 //==========================================================================
596 // CL_SetNetAbortCallback
598 //==========================================================================
599 void CL_SetNetAbortCallback (bool (*cb
) (void *udata
), void *udata
) {
601 GNet
->CheckUserAbortCB
= cb
;
602 GNet
->CheckUserAbortUData
= udata
;
607 //==========================================================================
611 //==========================================================================
612 int CL_GetNetLag () {
613 return (cl
&& cl
->Net
? (int)((cl
->Net
->PrevLag
+1.2*(max2(cl
->Net
->InLoss
, cl
->Net
->OutLoss
)*0.01))*1000) : 0);
617 //==========================================================================
619 // CL_IsDangerousTimeout
621 //==========================================================================
622 bool CL_IsDangerousTimeout () {
623 return (cl
&& cl
->Net
? cl
->Net
->IsDangerousTimeout() : false);
627 //==========================================================================
629 // CL_GetNumberOfChannels
631 //==========================================================================
632 int CL_GetNumberOfChannels () {
633 return (cl
&& cl
->Net
? cl
->Net
->OpenChannels
.length() : 0);
637 //==========================================================================
641 // get info needed to make ticcmd_ts for the players
643 //==========================================================================
644 bool CL_Responder (event_t
*ev
) {
645 if (GGameInfo
->NetMode
== NM_TitleMap
) return false;
648 if (cl
->MO
&& cl
->MO
->XLevel
->IsBadApple()) return false;
649 return cl
->Responder(ev
);
655 //==========================================================================
659 //==========================================================================
661 GClGame
->serverinfo
.Clean();
662 GClGame
->ResetIntermission();
663 if (cl
) cl
->ClearInput();
664 if (GGameInfo
->NetMode
== NM_None
|| GGameInfo
->NetMode
== NM_Client
) GAudio
->StopAllSound(); // make sure all sounds are stopped
666 cls
.clearForClient();
670 //==========================================================================
672 // CL_ReadFromServerInfo
674 //==========================================================================
675 void CL_ReadFromServerInfo () {
676 VCvar::SetCheating(!!VStr::atoi(*Info_ValueForKey(GClGame
->serverinfo
, "sv_cheats")));
677 GGameInfo
->clientFlags
= 0;
678 if (GGameInfo
->NetMode
== NM_Client
) {
679 GGameInfo
->deathmatch
= VStr::atoi(*Info_ValueForKey(GClGame
->serverinfo
, "DeathMatch"));
681 if (VStr::atoi(*Info_ValueForKey(GClGame
->serverinfo
, "sv_disable_run"))) GGameInfo
->clientFlags
|= VGameInfo::CLF_RUN_DISABLED
;
682 if (VStr::atoi(*Info_ValueForKey(GClGame
->serverinfo
, "sv_disable_crouch"))) GGameInfo
->clientFlags
|= VGameInfo::CLF_CROUCH_DISABLED
;
683 if (VStr::atoi(*Info_ValueForKey(GClGame
->serverinfo
, "sv_disable_jump"))) GGameInfo
->clientFlags
|= VGameInfo::CLF_JUMP_DISABLED
;
684 if (VStr::atoi(*Info_ValueForKey(GClGame
->serverinfo
, "sv_disable_mlook"))) GGameInfo
->clientFlags
|= VGameInfo::CLF_MLOOK_DISABLED
;
685 //GCon->Logf(NAME_Debug, "*** client: deathmatch mode is %u", GGameInfo->deathmatch);
687 // do nothing here, because the proper DM mode is set `SV_SpawnServer()`
688 //GCon->Logf(NAME_Debug, "*** other: deathmatch mode is %u (%u)", GGameInfo->deathmatch, svs.deathmatch);
689 //GGameInfo->deathmatch = svs.deathmatch;
694 //==========================================================================
698 //==========================================================================
699 void CL_ParseServerInfo (const VNetClientServerInfo
*sinfo
) {
703 //msg << GClGame->serverinfo;
704 GClGame
->serverinfo
= sinfo
->sinfo
;
705 CL_ReadFromServerInfo();
709 //VName MapName = *TmpStr;
710 VName MapName
= *sinfo
->mapname
;
712 //GClGame->maxclients = msg.ReadInt(/*MAXPLAYERS + 1*/);
713 //GClGame->deathmatch = msg.ReadInt(/*256*/);
714 GClGame
->maxclients
= sinfo
->maxclients
;
715 GClGame
->deathmatch
= sinfo
->deathmatch
;
717 const VMapInfo
&LInfo
= P_GetMapInfo(MapName
);
718 GCon
->Log("---------------------------------------");
719 GCon
->Log(LInfo
.GetName());
722 CL_LoadLevel(MapName
);
723 GClLevel
->NetContext
= ClientNetContext
;
725 cl
->Net
->GetLevelChannel()->SetLevel(GClLevel
);
726 cl
->Net
->GetLevelChannel()->SendMapLoaded();
731 Host_ResetSkipFrames();
735 GCon
->Log(NAME_Dev
, "Client level loaded");
737 if (GClLevel
->MapHash
!= sinfo
->maphash
) Host_Error("Server has different map data");
743 //==========================================================================
745 // VClientNetContext::GetLevel
747 //==========================================================================
748 VLevel
*VClientNetContext::GetLevel () {
753 //==========================================================================
757 //==========================================================================
758 void CL_SetupNetClient (VSocketPublic
*Sock
) {
759 //k8: i absolutely don't understand why it tries to create a writing demo connection in client
760 //k8: let's just crash here for now
761 if (cls
.demorecording
) Sys_Error("SOMETHING WICKED THIS WAY COMES...");
763 // create player structure
764 cl
= (VBasePlayer
*)VObject::StaticSpawnWithReplace(VClass::FindClass("K8VPlayer"));
765 cl
->PlayerFlags
|= VBasePlayer::PF_IsClient
;
766 cl
->ClGame
= GClGame
;
769 if (cls
.demorecording
) {
770 cl
->Net
= new VDemoRecordingNetConnection(Sock
, ClientNetContext
, cl
);
772 cl
->Net
= new VNetConnection(Sock
, ClientNetContext
, cl
);
774 ClientNetContext
->ServerConnection
= cl
->Net
;
775 cl
->Net
->GetPlayerChannel()->SetPlayer(cl
);
776 cl
->eventClientSetAutonomousProxy(cl_autonomous_proxy
.asBool());
777 cl
->setAutonomousProxy(cl_autonomous_proxy
.asBool());
781 //==========================================================================
785 //==========================================================================
786 void CL_PlayDemo (VStr DemoName
, bool IsTimeDemo
) {
789 // open the demo file
790 VStr name
= VStr("demos/")+DemoName
.DefaultExtension(".dem");
792 GCon
->Logf("Playing demo from '%s'", *name
);
793 VStream
*Strm
= FL_OpenFileReadInCfgDir(name
);
795 GCon
->Logf("ERROR: couldn't open '%s'.", *name
);
799 Strm
->Serialise(magic
, 4);
801 if (VStr::Cmp(magic
, "VDEM")) {
802 VStream::Destroy(Strm
);
803 GCon
->Logf("ERROR: '%s' is not a k8vavoom demo.", *name
);
809 if (ver
!= VAVOOM_DEMO_VERSION
) {
810 VStream::Destroy(Strm
);
811 GCon
->Logf("ERROR: '%s' has invalid version.", *name
);
815 auto wadlist
= FL_GetWadPk3List();
816 int wadlen
= wadlist
.length();
818 *Strm
<< STRM_INDEX(dmwadlen
);
819 if (dmwadlen
!= wadlen
) {
820 VStream::Destroy(Strm
);
821 GCon
->Logf("ERROR: '%s' was recorded with differrent mod set.", *name
);
824 for (int f
= 0; f
< wadlen
; ++f
) {
827 if (s
!= wadlist
[f
]) {
828 VStream::Destroy(Strm
);
829 GCon
->Logf("ERROR: '%s' was recorded with differrent mod set.", *name
);
834 // disconnect from server
837 cls
.demoplayback
= true;
839 // create player structure
840 cl
= (VBasePlayer
*)VObject::StaticSpawnWithReplace(VClass::FindClass("K8VPlayer"));
841 cl
->PlayerFlags
|= VBasePlayer::PF_IsClient
;
842 cl
->ClGame
= GClGame
;
845 cl
->Net
= new VDemoPlaybackNetConnection(ClientNetContext
, cl
, Strm
, IsTimeDemo
);
846 ClientNetContext
->ServerConnection
= cl
->Net
;
847 cl
->Net
->GetPlayerChannel()->SetPlayer(cl
);
849 GGameInfo
->NetMode
= NM_Client
;
850 GClGame
->eventDemoPlaybackStarted();
854 //==========================================================================
858 //==========================================================================
859 void CL_StopRecording () {
861 VStream::Destroy(cls
.demofile
);
862 cls
.demorecording
= false;
863 if (GDemoRecordingContext
) {
864 delete GDemoRecordingContext
;
865 GDemoRecordingContext
= nullptr;
867 GCon
->Log("Completed demo");
871 //==========================================================================
875 //==========================================================================
876 bool CL_NeedAutomapUpdates () noexcept
{
878 dbg_always_mark_map
.asBool() ||
879 (cl
&& cl
->MO
== cl
->Camera
&&
880 GGameInfo
&& GGameInfo
->NetMode
> NM_TitleMap
&&
881 GGameInfo
->NetMode
!= NM_DedicatedServer
);
885 //==========================================================================
889 //==========================================================================
892 CL_EstablishConnection(Args
.length() > 1 ? *Args
[1] : "");
896 //==========================================================================
898 // COMMAND Disconnect
900 //==========================================================================
901 COMMAND(Disconnect
) {
907 //==========================================================================
911 // stop recording a demo
913 //==========================================================================
915 if (Source
!= SRC_Command
) return;
916 if (!cls
.demorecording
) {
917 GCon
->Log("Not recording a demo.");
924 //==========================================================================
926 // COMMAND_WITH_AC RecordDemo
928 // RecordDemo <demoname> <map>
930 //==========================================================================
931 COMMAND_WITH_AC(RecordDemo
) {
932 if (Source
!= SRC_Command
) return;
934 int c
= Args
.length();
935 if (c
!= 2 && c
!= 3) {
936 GCon
->Log("RecordDemo <demoname> [<map>]");
940 if (Args
[1] == "?" || Args
[1].ICmp("-h") == 0 || Args
[1].ICmp("-help") == 0 || Args
[1].ICmp("--help") == 0) {
941 GCon
->Log("RecordDemo <demoname> [<map>]");
945 if (strstr(*Args
[1], "..") || strstr(*Args
[1], "/") || strstr(*Args
[1], "\\")) {
946 GCon
->Log("Relative pathnames are not allowed.");
950 if (c
== 2 && GGameInfo
->NetMode
== NM_Client
) {
951 GCon
->Log("Can not record demo -- already connected to server.");
952 GCon
->Log("Client demo recording must be started before connecting.");
956 if (cls
.demorecording
) {
957 GCon
->Log("Already recording a demo.");
961 VStr name
= VStr("demos/")+Args
[1].DefaultExtension(".dem");
964 if (c
> 2) VCommand::ExecuteString(VStr("map ")+Args
[2], SRC_Command
, nullptr);
966 // open the demo file
967 GCon
->Logf("recording to '%s'", *name
);
968 cls
.demofile
= FL_OpenFileWriteInCfgDir(name
);
970 GCon
->Logf("ERROR: couldn't create '%s'.", *name
);
974 cls
.demofile
->Serialise(const_cast<char *>("VDEM"), 4);
976 vuint32 ver
= VAVOOM_DEMO_VERSION
;
977 *cls
.demofile
<< ver
;
979 auto wadlist
= FL_GetWadPk3List();
980 int wadlen
= wadlist
.length();
981 *cls
.demofile
<< STRM_INDEX(wadlen
);
982 for (int f
= 0; f
< wadlen
; ++f
) *cls
.demofile
<< wadlist
[f
];
984 cls
.demorecording
= true;
986 if (GGameInfo
->NetMode
== NM_Standalone
|| GGameInfo
->NetMode
== NM_ListenServer
) {
987 GDemoRecordingContext
= new VServerNetContext();
988 VSocketPublic
*Sock
= new VDemoRecordingSocket();
989 //VNetConnection *Conn = new VNetConnection(Sock, GDemoRecordingContext, cl);
990 VNetConnection
*Conn
= new VDemoRecordingNetConnection(Sock
, GDemoRecordingContext
, cl
);
991 Conn
->AutoAck
= true;
992 GDemoRecordingContext
->ClientConnections
.Append(Conn
);
993 Conn
->ObjMap
->SetupClassLookup();
994 (void)Conn
->CreateChannel(CHANNEL_ObjectMap
, -1, true); // local
995 while (!Conn
->ObjMapSent
) Conn
->Tick();
996 Conn
->LoadedNewLevel();
997 Conn
->SendServerInfo();
998 Conn
->GetPlayerChannel()->SetPlayer(cl
);
1003 //==========================================================================
1005 // COMMAND_AC RecordDemo
1007 //==========================================================================
1008 COMMAND_AC(RecordDemo
) {
1009 VStr prefix
= (aidx
< args
.length() ? args
[aidx
] : VStr());
1013 if (fsys_PWadMaps
.length()) {
1014 list
.resize(fsys_PWadMaps
.length());
1015 for (auto &&lmp
: fsys_PWadMaps
) list
.append(lmp
.mapname
);
1017 int mapcount
= P_GetNumMaps();
1018 list
.resize(mapcount
);
1019 for (int f
= 0; f
< mapcount
; ++f
) {
1020 VName mlump
= P_GetMapLumpName(f
);
1021 if (mlump
!= NAME_None
) list
.append(VStr(mlump
));
1024 if (list
.length()) return AutoCompleteFromListCmd(prefix
, list
);
1025 } else if (aidx
< 2) {
1026 GCon
->Log("RecordDemo <demoname> [<map>]");
1028 return VStr::EmptyString
;
1032 //==========================================================================
1034 // COMMAND_WITH_AC PlayDemo
1038 //==========================================================================
1039 COMMAND_WITH_AC(PlayDemo
) {
1040 if (Source
!= SRC_Command
) return;
1041 if (Args
.length() != 2) {
1042 GCon
->Log("play <demoname> : plays a demo");
1045 CL_PlayDemo(Args
[1], false);
1049 //==========================================================================
1051 // DoDemoCompletions
1053 //==========================================================================
1054 static VStr
DoDemoCompletions (const TArray
<VStr
> &args
, int aidx
) {
1056 VStr prefix
= (aidx
< args
.length() ? args
[aidx
] : VStr());
1058 void *dir
= Sys_OpenDir(FL_GetConfigDir().appendPath("demos"));
1059 if (!dir
) return VStr::EmptyString
;
1061 VStr fname
= Sys_ReadDir(dir
);
1062 if (fname
.isEmpty()) break;
1063 if (fname
.endsWithCI(".dem")) list
.append(fname
);
1066 return VCommand::AutoCompleteFromListCmd(prefix
, list
);
1068 return VStr::EmptyString
;
1073 //==========================================================================
1075 // COMMAND_AC PlayDemo
1077 //==========================================================================
1078 COMMAND_AC(PlayDemo
) {
1079 return DoDemoCompletions(args
, aidx
);
1083 //==========================================================================
1085 // COMMAND_WITH_AC TimeDemo
1087 // timedemo [demoname]
1089 //==========================================================================
1090 COMMAND_WITH_AC(TimeDemo
) {
1091 if (Source
!= SRC_Command
) return;
1092 if (Args
.length() != 2) {
1093 GCon
->Log("timedemo <demoname> : gets demo speeds");
1096 CL_PlayDemo(Args
[1], true);
1100 //==========================================================================
1102 // COMMAND_AC TimeDemo
1104 //==========================================================================
1105 COMMAND_AC(TimeDemo
) {
1106 return DoDemoCompletions(args
, aidx
);
1110 //==========================================================================
1112 // COMMAND VidRendererRestart
1114 // VidRendererRestart
1116 //==========================================================================
1117 COMMAND(VidRendererRestart
) {
1118 if (Source
!= SRC_Command
) return;
1119 if (!GClLevel
) return;
1120 if (!GClLevel
->Renderer
) return;
1121 R_OSDMsgReset(OSD_MapLoading
);
1122 Drawer
->SetMainFBO(); // just in case
1123 delete GClLevel
->Renderer
;
1124 GClLevel
->Renderer
= nullptr;
1125 GClLevel
->cacheFileBase
.clear(); // so we won't store stale lightmaps
1127 GClLevel
->Renderer
->ResetStaticLights();
1128 for (auto &&L
: GClLevel
->StaticLights
) {
1130 VLightParams
lpar(L
);
1131 GClLevel
->Renderer
->AddStaticLightRGB(L
.OwnerUId
, lpar
, L
.Flags
);
1134 GClLevel
->Renderer
->PreRender();
1135 Host_ResetSkipFrames();
1140 //native final bool IsSignedOn ();
1141 IMPLEMENT_FUNCTION(VClientGameBase
, IsSignedOn
) {
1143 RET_BOOL(Self
->cl
&& cls
.signon
);
1146 //native final bool IsPlayerMObjReady ();
1147 IMPLEMENT_FUNCTION(VClientGameBase
, IsPlayerMObjReady
) {
1149 RET_BOOL(Self
->cl
&& cls
.signon
&& Self
->cl
->MO
);
1152 //native final bool IsRecordingDemo ();
1153 IMPLEMENT_FUNCTION(VClientGameBase
, IsRecordingDemo
) {
1156 RET_BOOL(cls
.demorecording
);
1159 //native final bool IsPlayingDemo ();
1160 IMPLEMENT_FUNCTION(VClientGameBase
, IsPlayingDemo
) {
1163 RET_BOOL(cls
.demoplayback
);