engine: reject mbf21 and shit24 wads. there is no way to know if it is safe to ignore...
[k8vavoom.git] / source / client / cl_main.cpp
blob3a6085df83300071bef14c7899c8b3d86b5e56d7
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
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.
16 //**
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.
21 //**
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/>.
24 //**
25 //**************************************************************************
26 #include "../gamedefs.h"
27 #include "../host.h"
28 #include "../drawer.h"
29 #include "../text.h"
30 #include "../sbar.h"
31 #include "../menu.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"
39 #include "client.h"
40 #include "cl_local.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 *);
51 void CL_Clear ();
52 void CL_ReadFromServerInfo ();
55 client_static_t cls;
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 //==========================================================================
85 // CL_LoadMods
87 //==========================================================================
88 void CL_LoadMods () {
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 //==========================================================================
100 // CL_Init
102 //==========================================================================
103 void CL_Init () {
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);
119 R_FuckOffShitdoze();
123 //==========================================================================
125 // CL_ResetLastSong
127 //==========================================================================
128 static void CL_ResetLastSong () {
129 CurrentSongLump = NAME_None;
133 //==========================================================================
135 // CL_Ticker
137 //==========================================================================
138 static void CL_Ticker () {
139 // do main actions
140 if (!GClGame->InIntermission()) {
141 SB_Ticker();
142 AM_Ticker();
144 R_AnimateSurfaces();
148 //==========================================================================
150 // CL_Shutdown
152 //==========================================================================
153 void CL_Shutdown () {
154 if (cl) SV_ShutdownGame(); // disconnect
155 // free up memory
156 if (GClGame) GClGame->ConditionalDestroy();
157 if (GRoot) GRoot->ConditionalDestroy();
158 cls.userinfo.Clean();
159 delete ClientNetContext;
160 ClientNetContext = nullptr;
164 //==========================================================================
166 // CL_DecayLights
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 //==========================================================================
183 // CL_UpdateMobjs
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 ======================");
190 // animate decals
191 GClLevel->TickDecals(deltaTime);
192 if (GGameInfo->NetMode == NM_Client) {
193 // network client
194 // cannot use thinker iterator here, because detached thinker may remove itself...
195 VThinker *curr = GClLevel->ThinkerHead;
196 while (curr) {
197 VThinker *th = curr;
198 curr = curr->Next;
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;
208 th->Tick(deltaTime);
209 GClLevel->LevelFlags = oldLevelFlags;
210 if (th->IsGoingToDie()) continue; // just in case
213 th->eventClientTick(deltaTime);
215 } else {
216 // other game types; don't use iterator too, because it does excessive class checks
217 VThinker *curr = GClLevel->ThinkerHead;
218 while (curr) {
219 VThinker *th = curr;
220 curr = curr->Next;
221 if (!th->IsGoingToDie()) th->eventClientTick(deltaTime);
227 //==========================================================================
229 // CL_ReadFromServer
231 // Read all incoming data from the server
233 //==========================================================================
234 void CL_ReadFromServer (float deltaTime) {
235 if (!cl) return;
237 if (cl->Net) {
238 ClientLastKeepAliveTime = Sys_Time();
239 cl->Net->GetMessages();
240 if (cl->Net->IsClosed()) Host_EndGame("Server disconnected");
243 if (cls.signon) {
244 // for interpolator
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;
263 } else {
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)
271 if (deltaTime) {
272 cl->ClCurrGameTime += deltaTime;
273 cl->eventClientTick(deltaTime);
274 CL_Ticker();
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);
278 } else {
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");
326 SV_ShutdownGame();
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);
333 if (!Sock) {
334 GCon->Log(NAME_Error, "Failed to connect to the server");
335 return;
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
347 cls.signon = 0;
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
363 MN_DeactivateMenu();
364 Host_CollectGarbage(true, true); // force-collect garbage, and set new unique id
366 VBasePlayer *Player = GPlayersBase[0];
367 SV_ConnectClient(Player);
368 ++svs.num_connected;
370 cl = Player;
371 cl->ClGame = GClGame;
372 GClGame->cl = cl;
374 cl->eventServerSetUserInfo(cls.userinfo);
376 GClGame->eventConnected();
377 // need all the signon messages before playing
378 // but the map may be aloready loaded
379 cls.signon = 0;
381 //MN_DeactivateMenu();
383 CL_SetupStandaloneClient();
387 //==========================================================================
389 // CL_SetupStandaloneClient
391 //==========================================================================
392 void CL_SetupStandaloneClient () {
393 CL_Clear();
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());
406 GCon->Log("");
408 GClLevel = GLevel;
409 GClGame->GLevel = GClLevel;
411 R_Start(GClLevel);
412 GAudio->Start();
414 SB_Start();
416 GClLevel->Renderer->ResetStaticLights();
417 for (auto &&L : GClLevel->StaticLights) {
418 if (L.IsActive()) {
419 VLightParams lpar(L);
420 GClLevel->Renderer->AddStaticLightRGB(L.OwnerUId, lpar, L.Flags);
423 GClLevel->Renderer->PreRender();
425 cl->SpawnClient();
426 //cls.signon = 1;
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 //==========================================================================
449 // CL_GotNetOrigin
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;
477 cl->MO->Player = cl;
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 //==========================================================================
508 // CL_SendMove
510 //==========================================================================
511 void CL_SendMove () {
512 if (!cl) return;
514 if (cls.demoplayback || GGameInfo->NetMode == NM_TitleMap) return;
516 if (cls.signon) {
517 if (!GGameInfo->IsPaused()) cl->HandleInput();
518 if (cl->Net) {
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 //==========================================================================
532 // CL_NetInterframe
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
539 cl->Net->Tick();
543 //==========================================================================
545 // CL_IsInGame
547 //==========================================================================
548 bool CL_IsInGame () {
549 return (cl && GClGame && !GClGame->InIntermission());
553 //==========================================================================
555 // CL_IsInGame
557 //==========================================================================
558 int CL_IntermissionPhase () {
559 return (GClGame ? GClGame->intermissionPhase : -666);
563 //==========================================================================
565 // CL_GetNetState
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);
590 return true;
594 //==========================================================================
596 // CL_SetNetAbortCallback
598 //==========================================================================
599 void CL_SetNetAbortCallback (bool (*cb) (void *udata), void *udata) {
600 if (GNet) {
601 GNet->CheckUserAbortCB = cb;
602 GNet->CheckUserAbortUData = udata;
607 //==========================================================================
609 // CL_GetNetLag
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 //==========================================================================
639 // CL_Responder
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;
646 if (cl) {
647 // BadApple.wad hack
648 if (cl->MO && cl->MO->XLevel->IsBadApple()) return false;
649 return cl->Responder(ev);
651 return false;
655 //==========================================================================
657 // CL_Clear
659 //==========================================================================
660 void CL_Clear () {
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
665 //cls.signon = 0;
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"));
680 // setup flags
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);
686 } else {
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 //==========================================================================
696 // CL_DoLoadLevel
698 //==========================================================================
699 void CL_ParseServerInfo (const VNetClientServerInfo *sinfo) {
700 vassert(sinfo);
701 CL_Clear();
703 //msg << GClGame->serverinfo;
704 GClGame->serverinfo = sinfo->sinfo;
705 CL_ReadFromServerInfo();
707 //VStr TmpStr;
708 //msg << TmpStr;
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());
720 GCon->Log("");
722 CL_LoadLevel(MapName);
723 GClLevel->NetContext = ClientNetContext;
725 cl->Net->GetLevelChannel()->SetLevel(GClLevel);
726 cl->Net->GetLevelChannel()->SendMapLoaded();
728 R_Start(GClLevel);
729 GAudio->Start();
731 Host_ResetSkipFrames();
733 SB_Start();
735 GCon->Log(NAME_Dev, "Client level loaded");
737 if (GClLevel->MapHash != sinfo->maphash) Host_Error("Server has different map data");
739 cls.gotmap = 1;
743 //==========================================================================
745 // VClientNetContext::GetLevel
747 //==========================================================================
748 VLevel *VClientNetContext::GetLevel () {
749 return GClLevel;
753 //==========================================================================
755 // CL_SetupNetClient
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;
767 GClGame->cl = cl;
769 if (cls.demorecording) {
770 cl->Net = new VDemoRecordingNetConnection(Sock, ClientNetContext, cl);
771 } else {
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 //==========================================================================
783 // CL_PlayDemo
785 //==========================================================================
786 void CL_PlayDemo (VStr DemoName, bool IsTimeDemo) {
787 char magic[8];
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);
794 if (!Strm) {
795 GCon->Logf("ERROR: couldn't open '%s'.", *name);
796 return;
799 Strm->Serialise(magic, 4);
800 magic[4] = 0;
801 if (VStr::Cmp(magic, "VDEM")) {
802 VStream::Destroy(Strm);
803 GCon->Logf("ERROR: '%s' is not a k8vavoom demo.", *name);
804 return;
807 vuint32 ver = -1;
808 *Strm << ver;
809 if (ver != VAVOOM_DEMO_VERSION) {
810 VStream::Destroy(Strm);
811 GCon->Logf("ERROR: '%s' has invalid version.", *name);
812 return;
815 auto wadlist = FL_GetWadPk3List();
816 int wadlen = wadlist.length();
817 int dmwadlen = -1;
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);
822 return;
824 for (int f = 0; f < wadlen; ++f) {
825 VStr s;
826 *Strm << s;
827 if (s != wadlist[f]) {
828 VStream::Destroy(Strm);
829 GCon->Logf("ERROR: '%s' was recorded with differrent mod set.", *name);
830 return;
834 // disconnect from server
835 SV_ShutdownGame();
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;
843 GClGame->cl = cl;
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 //==========================================================================
856 // CL_StopRecording
858 //==========================================================================
859 void CL_StopRecording () {
860 // finish up
861 VStream::Destroy(cls.demofile);
862 cls.demorecording = false;
863 if (GDemoRecordingContext) {
864 delete GDemoRecordingContext;
865 GDemoRecordingContext = nullptr;
867 GCon->Log("Completed demo");
871 //==========================================================================
873 // NeedMapMarking
875 //==========================================================================
876 bool CL_NeedAutomapUpdates () noexcept {
877 return
878 dbg_always_mark_map.asBool() ||
879 (cl && cl->MO == cl->Camera &&
880 GGameInfo && GGameInfo->NetMode > NM_TitleMap &&
881 GGameInfo->NetMode != NM_DedicatedServer);
885 //==========================================================================
887 // COMMAND Connect
889 //==========================================================================
890 #ifdef CLIENT
891 COMMAND(Connect) {
892 CL_EstablishConnection(Args.length() > 1 ? *Args[1] : "");
896 //==========================================================================
898 // COMMAND Disconnect
900 //==========================================================================
901 COMMAND(Disconnect) {
902 CL_ResetLastSong();
903 SV_ShutdownGame();
907 //==========================================================================
909 // COMMAND StopDemo
911 // stop recording a demo
913 //==========================================================================
914 COMMAND(StopDemo) {
915 if (Source != SRC_Command) return;
916 if (!cls.demorecording) {
917 GCon->Log("Not recording a demo.");
918 return;
920 CL_StopRecording();
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>]");
937 return;
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>]");
942 return;
945 if (strstr(*Args[1], "..") || strstr(*Args[1], "/") || strstr(*Args[1], "\\")) {
946 GCon->Log("Relative pathnames are not allowed.");
947 return;
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.");
953 return;
956 if (cls.demorecording) {
957 GCon->Log("Already recording a demo.");
958 return;
961 VStr name = VStr("demos/")+Args[1].DefaultExtension(".dem");
963 // start the map up
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);
969 if (!cls.demofile) {
970 GCon->Logf("ERROR: couldn't create '%s'.", *name);
971 return;
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());
1010 if (aidx == 2) {
1011 TArray<VStr> list;
1012 // prefer pwad maps
1013 if (fsys_PWadMaps.length()) {
1014 list.resize(fsys_PWadMaps.length());
1015 for (auto &&lmp : fsys_PWadMaps) list.append(lmp.mapname);
1016 } else {
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
1036 // play [demoname]
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");
1043 return;
1045 CL_PlayDemo(Args[1], false);
1049 //==========================================================================
1051 // DoDemoCompletions
1053 //==========================================================================
1054 static VStr DoDemoCompletions (const TArray<VStr> &args, int aidx) {
1055 TArray<VStr> list;
1056 VStr prefix = (aidx < args.length() ? args[aidx] : VStr());
1057 if (aidx == 1) {
1058 void *dir = Sys_OpenDir(FL_GetConfigDir().appendPath("demos"));
1059 if (!dir) return VStr::EmptyString;
1060 for (;;) {
1061 VStr fname = Sys_ReadDir(dir);
1062 if (fname.isEmpty()) break;
1063 if (fname.endsWithCI(".dem")) list.append(fname);
1065 Sys_CloseDir(dir);
1066 return VCommand::AutoCompleteFromListCmd(prefix, list);
1067 } else {
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");
1094 return;
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
1126 R_Start(GClLevel);
1127 GClLevel->Renderer->ResetStaticLights();
1128 for (auto &&L : GClLevel->StaticLights) {
1129 if (L.IsActive()) {
1130 VLightParams lpar(L);
1131 GClLevel->Renderer->AddStaticLightRGB(L.OwnerUId, lpar, L.Flags);
1134 GClLevel->Renderer->PreRender();
1135 Host_ResetSkipFrames();
1137 #endif
1140 //native final bool IsSignedOn ();
1141 IMPLEMENT_FUNCTION(VClientGameBase, IsSignedOn) {
1142 vobjGetParamSelf();
1143 RET_BOOL(Self->cl && cls.signon);
1146 //native final bool IsPlayerMObjReady ();
1147 IMPLEMENT_FUNCTION(VClientGameBase, IsPlayerMObjReady) {
1148 vobjGetParamSelf();
1149 RET_BOOL(Self->cl && cls.signon && Self->cl->MO);
1152 //native final bool IsRecordingDemo ();
1153 IMPLEMENT_FUNCTION(VClientGameBase, IsRecordingDemo) {
1154 vobjGetParamSelf();
1155 (void)Self;
1156 RET_BOOL(cls.demorecording);
1159 //native final bool IsPlayingDemo ();
1160 IMPLEMENT_FUNCTION(VClientGameBase, IsPlayingDemo) {
1161 vobjGetParamSelf();
1162 (void)Self;
1163 RET_BOOL(cls.demoplayback);