2020#include " plugin.h"
2121#include " cdll_int.h"
2222#include " game_ui.h"
23+ #include " game/server/iplayerinfo.h"
24+
25+ #define ENTINDEX (index ) static_cast <edict_t *>(globalVars->pEdicts + (index))
26+ #define PLAYER_INFO_FROM_INDEX (index ) static_cast <IPlayerInfo *>(playerInfoManager->GetPlayerInfo (ENTINDEX(index)))
2327
2428using easywsclient::WebSocket;
2529using nlohmann::json;
@@ -49,6 +53,8 @@ EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerPlugin, IServerPluginCallbacks, INTERFA
4953void * client;
5054IVEngineClient14* engine = NULL ;
5155CGameUI* gameUi = NULL ;
56+ IPlayerInfoManager* playerInfoManager = NULL ;
57+ CGlobalVars* globalVars = NULL ;
5258FrameStageNotifyFn originalFrameStageNotify = NULL ;
5359thread* wsConnectionThread = NULL ;
5460WebSocket::pointer ws;
@@ -58,6 +64,7 @@ bool isPlayingDemo = false;
5864int mainMenuFrameCount = 0 ;
5965int currentTick = -1 ;
6066bool isQuitting = false ;
67+ bool forceSpectatorMode = false ;
6168std::queue<Sequence> sequences;
6269// Unlike CS2, executing client commands from a different thread than the main game thread may crash the game.
6370// As the WebSocket connection runs in a separate thread, we defer the possible command execution when we receive a
@@ -84,11 +91,15 @@ void ExecutePendingCommand()
8491 }
8592}
8693
94+ void SendMsg (json msg) {
95+ ws->send (msg.dump ());
96+ }
97+
8798void SendStatusOk () {
8899 json msg;
89100 msg[" name" ] = " status" ;
90101 msg[" payload" ] = " ok" ;
91- ws-> send (msg. dump () );
102+ SendMsg (msg);
92103}
93104
94105void LoadSequencesFile (string demoPath) {
@@ -138,6 +149,40 @@ void HandleWebSocketMessage(const string& message)
138149
139150 std::lock_guard<mutex> lock (pendingCmdMutex);
140151 pendingCmd = " playdemo \" " + demoPath + " \" " ;
152+ } else if (msg[" name" ] == " capture-player-view" ) {
153+ float x, y, z, pitch, yaw = 0 ;
154+ for (int i = 1 ; i <= engine->GetMaxClients (); i++) {
155+ IPlayerInfo* player = PLAYER_INFO_FROM_INDEX (i);
156+ if (player && player->IsConnected () && player->GetNetworkIDString () != " BOT" ) {
157+ auto position = player->GetAbsOrigin ();
158+ x = position.x ;
159+ y = position.y ;
160+ z = position.z ;
161+ // player->GetAbsAngles() is not reliable, use engine->GetViewAngles() instead.
162+ QAngle viewAngles;
163+ engine->GetViewAngles (viewAngles);
164+ pitch = viewAngles.x ;
165+ yaw = viewAngles.y ;
166+ break ;
167+ }
168+ }
169+
170+ // use the startmovie command to generate a screenshot like CS2 so we don't maintain two separate code paths.
171+ engine->ExecuteClientCmd (" hideconsole" );
172+ engine->ExecuteClientCmd (" startmovie csdmcamera jpg" );
173+ std::this_thread::sleep_for (std::chrono::milliseconds (1000 ));
174+ engine->ExecuteClientCmd (" endmovie" );
175+
176+ json msg;
177+ msg[" name" ] = " capture-player-view-result" ;
178+ msg[" payload" ] = {
179+ {" x" , x},
180+ {" y" , y},
181+ {" z" , z},
182+ {" pitch" , pitch},
183+ {" yaw" , yaw}
184+ };
185+ SendMsg (msg);
141186 }
142187}
143188
@@ -272,11 +317,34 @@ void NewFrameStageNotify(void* thisptr, CClientFrameStage stage)
272317#endif
273318}
274319
320+ void CServerPlugin::ClientFullyConnect (edict_t * pEntity) {
321+ if (!forceSpectatorMode) {
322+ return ;
323+ }
324+ auto player = playerInfoManager->GetPlayerInfo (pEntity);
325+ if (player) {
326+ player->ChangeTeam (1 );
327+ }
328+ }
329+
275330// Called when the plugin is loaded ONLY if the -insecure launch parameter is set.
276331bool CServerPlugin::Load (CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory)
277332{
278333 DeleteLogFile ();
279334
335+ playerInfoManager = (IPlayerInfoManager*)gameServerFactory (INTERFACEVERSION_PLAYERINFOMANAGER, NULL );
336+ if (!playerInfoManager)
337+ {
338+ Log (" Could not find IPlayerInfoManager : %s" , GetLastErrorString ());
339+ return false ;
340+ }
341+
342+ globalVars = playerInfoManager->GetGlobalVars ();
343+ if (!globalVars) {
344+ Log (" Could not find CGlobalVars : %s" , GetLastErrorString ());
345+ return false ;
346+ }
347+
280348 engine = (IVEngineClient14*)interfaceFactory (" VEngineClient014" , NULL );
281349 if (engine == NULL )
282350 {
@@ -368,6 +436,10 @@ bool CServerPlugin::Load(CreateInterfaceFn interfaceFactory, CreateInterfaceFn g
368436 LoadSequencesFile (demoPath);
369437 break ;
370438 }
439+
440+ if (strcmp (param, " forcespec" ) == 0 ) {
441+ forceSpectatorMode = true ;
442+ }
371443 }
372444
373445 wsConnectionThread = new thread (ConnectToWebsocketServerLoop);
@@ -402,6 +474,15 @@ const char *CServerPlugin::GetPluginDescription()
402474 return " CS Demo Manager plugin" ;
403475}
404476
477+ void LogPlayers () {
478+ for (int i = 1 ; i <= engine->GetMaxClients (); i++) {
479+ IPlayerInfo* player = PLAYER_INFO_FROM_INDEX (i);
480+ if (player && player->IsConnected ()) {
481+ Log (" %s - userID: %d SteamID: %s" , player->GetName (), player->GetUserID (), player->GetNetworkIDString ());
482+ }
483+ }
484+ }
485+
405486CON_COMMAND (csdm_info, " Show info" ){
406487 if (!demoPath.empty ()) {
407488 Log (" Demo path: %s" , demoPath.c_str ());
@@ -417,4 +498,6 @@ CON_COMMAND(csdm_info, "Show info"){
417498 else {
418499 Log (" WebSocket not connected" );
419500 }
501+
502+ LogPlayers ();
420503}
0 commit comments