From f89878b92ce0ace6e1b3934fa922a94a173c51e9 Mon Sep 17 00:00:00 2001 From: tuxxi Date: Sun, 17 Apr 2016 20:32:40 -0700 Subject: [PATCH 001/101] basic replay recording and saving properly --- .gitignore | 3 +- mp/src/game/server/momentum/Timer.cpp | 15 ++++ mp/src/game/server/momentum/Timer.h | 2 +- mp/src/game/server/momentum/mom_replay.cpp | 91 ++++++++++++++++++++ mp/src/game/server/momentum/mom_replay.h | 50 +++++++++++ mp/src/game/server/momentum/mom_triggers.cpp | 8 +- mp/src/game/server/server_momentum.vpc | 2 + 7 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 mp/src/game/server/momentum/mom_replay.cpp create mode 100644 mp/src/game/server/momentum/mom_replay.h diff --git a/.gitignore b/.gitignore index 1196a9fe19..0cf3bd5d44 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,7 @@ voice_ban.dt # Momentum *.tim *.zon +*.momrec # Others *.cache @@ -77,4 +78,4 @@ voice_ban.dt *.prt *.vmx *.lin -*.vdf \ No newline at end of file +*.vdf diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index db403d3c81..af03c6d49f 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -240,6 +240,21 @@ void CTimer::Stop(bool endTrigger /* = false */) localTimes.AddToTail(t); SaveTime(); + /*@tuxxi: we cannot rename a file to a different location, and I don't know how to move a file using IFileStream. disabling this for now + char* recordingPath = ""; + //rename temp demo file to "playername_mapname_time" + char newRecordingName[MAX_PATH], newRecordingPath[MAX_PATH]; + Q_snprintf(newRecordingName, MAX_PATH, "%s_%s_%f.dem", pPlayer->GetPlayerName(), gpGlobals->mapname.ToCStr()); + V_ComposeFileName(recordingPath, newRecordingName, newRecordingPath, MAX_PATH); + V_FixSlashes(newRecordingName); + if (filesystem->FileExists("tempdemo.dem", "MOD")) + { + filesystem->RenameFile("tempdemo.dem", newRecordingPath, "MOD"); + } + else + Warning("Recording file doesn't exist, cannot rename!"); + */ + } else if (runSaveEvent) //reset run saved status to false if we cant or didn't save { diff --git a/mp/src/game/server/momentum/Timer.h b/mp/src/game/server/momentum/Timer.h index b0adccf611..e960f4aeae 100644 --- a/mp/src/game/server/momentum/Timer.h +++ b/mp/src/game/server/momentum/Timer.h @@ -128,7 +128,7 @@ class CTimer void SaveTime(); void OnMapEnd(const char *); void OnMapStart(const char *); - + float GetLastRunTime() { return (gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; } // Practice mode- noclip mode that stops timer void PracticeMove(); void EnablePractice(CBasePlayer *pPlayer); diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp new file mode 100644 index 0000000000..5f88a5d042 --- /dev/null +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -0,0 +1,91 @@ +#include "cbase.h" +#include "mom_replay.h" +#include "Timer.h" + +void CMomentumReplaySystem::BeginRecording(CBasePlayer *pPlayer) +{ + Reset(); + m_player = pPlayer; + //delete old temp recording + V_ComposeFileName(recordingPath, "temprecording.momrec", tempRecordingName, MAX_PATH); //we only need to do this once! + if (filesystem->FileExists(tempRecordingName, "MOD")) + filesystem->RemoveFile(tempRecordingName, "MOD"); + + Log("Recording began!\n"); + m_bIsRecording = true; + +} +void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer) +{ + Log("Recording Stopped! Ticks: %i\n", m_nRecordingTicks); + m_bIsRecording = false; + + CMomentumPlayer *mPlayer = ToCMOMPlayer(pPlayer); + char newRecordingName[MAX_PATH], newRecordingPath[MAX_PATH]; + Q_snprintf(newRecordingName, MAX_PATH, "%s_%s_%f.momrec", mPlayer->GetPlayerName(), gpGlobals->mapname.ToCStr(), g_Timer.GetLastRunTime()); + V_ComposeFileName(recordingPath, newRecordingName, newRecordingPath, MAX_PATH); + V_FixSlashes(newRecordingName); + if (filesystem->FileExists(tempRecordingName, "MOD")) + { + filesystem->RenameFile(tempRecordingName, newRecordingPath, "MOD"); + } + else + Warning("Recording file doesn't exist, cannot rename!"); +} +void CMomentumReplaySystem::UpdateRecordingParams() +{ + m_currentFrame.m_nPlayerButtons = m_player->m_nButtons; + m_currentFrame.m_qEyeAngles = m_player->EyeAngles(); + m_currentFrame.m_vPlayerVelocity = m_player->GetLocalVelocity(); + m_currentFrame.m_vPlayerOrigin = m_player->GetLocalOrigin(); + m_nRecordingTicks++; +} +void CMomentumReplaySystem::WriteRecordingToFile() +{ + FileHandle_t fh = filesystem->Open(tempRecordingName, "a", "MOD"); + if (fh) + { + //buttons, eyeangles XYZ, velocity XYZ, origin XYZ + filesystem->FPrintf(fh, "%i %f, %f, %f %f, %f, %f %f, %f, %f\n", + m_currentFrame.m_nPlayerButtons, + m_currentFrame.m_qEyeAngles.x, m_currentFrame.m_qEyeAngles.y, m_currentFrame.m_qEyeAngles.z, + m_currentFrame.m_vPlayerVelocity.x, m_currentFrame.m_vPlayerVelocity.y, m_currentFrame.m_vPlayerVelocity.z, + m_currentFrame.m_vPlayerOrigin.x, m_currentFrame.m_vPlayerOrigin.y, m_currentFrame.m_vPlayerOrigin.z + ); + filesystem->Close(fh); + } +} +class CMOMReplayCommands +{ +public: + static void PlayRecording(const CCommand &args) + { + char recordingName[BUFSIZELOCL]; + V_ComposeFileName("recordings", args.ArgS(), recordingName, MAX_PATH); + + if (Q_strlen(args.GetCommandString()) > 1) + { + CBasePlayer *pPlayer = UTIL_GetListenServerHost(); + FileHandle_t fh = filesystem->Open(recordingName, "r", "MOD"); + if (fh) + { + int file_len = filesystem->Size(fh); + char recordingLine[1024]; + for (int i = 1; i < file_len; i++) + { + filesystem->ReadLine(recordingLine, sizeof(recordingLine), fh); + pPlayer->m_nButtons = Q_atoi(recordingLine); + } + //pPlayer->SetLocalOrigin(Vector(, )); + //pPlayer->SetLocalVelocity(Vector()); + //pPlayer->SetLocalAngles(QAngle()); + filesystem->Close(fh); + } + } + } +}; + +static ConCommand playrecording("playrecording", CMOMReplayCommands::PlayRecording, "plays a recording", FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_SERVER_CAN_EXECUTE); + +static CMomentumReplaySystem s_ReplaySystem("MOMReplaySystem"); +CMomentumReplaySystem *g_ReplaySystem = &s_ReplaySystem; \ No newline at end of file diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h new file mode 100644 index 0000000000..ff21a4c994 --- /dev/null +++ b/mp/src/game/server/momentum/mom_replay.h @@ -0,0 +1,50 @@ +#ifndef MOM_REPLAY_H +#define MOM_REPLAY_H +#include "cbase.h" +#include "filesystem.h" + +#include "mom_player_shared.h" +#include "mom_shareddefs.h" + +class CMomentumReplaySystem : CAutoGameSystemPerFrame +{ +public: + CMomentumReplaySystem(const char *pName) : CAutoGameSystemPerFrame(pName) {} + virtual void FrameUpdatePostEntityThink() + { + if (m_bIsRecording) + { + UpdateRecordingParams(); + WriteRecordingToFile(); + } + } + void BeginRecording(CBasePlayer *pPlayer); + void StopRecording(CBasePlayer *pPlayer); + bool IsRecording(CBasePlayer *pPlayer) { return m_bIsRecording; } + void WriteRecordingToFile(); + +private: + void UpdateRecordingParams(); //called every game frame after entities think and update + void Reset() + { + m_nRecordingTicks = 0; + } + bool m_bIsRecording; + CBasePlayer *m_player; + struct replay_t + { + QAngle m_qEyeAngles; + Vector m_vPlayerOrigin; + Vector m_vPlayerVelocity; + int m_nPlayerButtons; + }; + replay_t m_currentFrame; + + char* recordingPath = "recordings"; + char tempRecordingName[BUFSIZELOCL]; + int m_nRecordingTicks; +}; + +extern CMomentumReplaySystem *g_ReplaySystem; + +#endif //MOM_REPLAY_H \ No newline at end of file diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index 7528d09fbf..c7ddb579c4 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -3,7 +3,7 @@ #include "in_buttons.h" #include "mom_triggers.h" #include "movevars_shared.h" - +#include "mom_replay.h" #include "tier0/memdbgon.h" @@ -150,6 +150,9 @@ void CTriggerTimerStart::StartTouch(CBaseEntity *pOther) g_Timer.DispatchResetMessage(); //lower the player's speed if they try to jump back into the start zone } + //begin recording demo + engine->ClientCommand(pPlayer->edict(), "record tempdemo"); + g_ReplaySystem->BeginRecording(pPlayer); } IGameEvent *mapZoneEvent = gameeventmanager->CreateEvent("player_inside_mapzone"); if (mapZoneEvent) @@ -247,6 +250,9 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) if (mapZoneEvent) mapZoneEvent->SetBool("map_finished", true); //broadcast that we finished the map with a timer running g_Timer.Stop(true); } + //stop demo recording + engine->ClientCommand(UTIL_GetLocalPlayer()->edict(), "stop"); + g_ReplaySystem->StopRecording(ToCMOMPlayer(pOther)); if (mapZoneEvent) { mapZoneEvent->SetBool("inside_endzone", true); diff --git a/mp/src/game/server/server_momentum.vpc b/mp/src/game/server/server_momentum.vpc index b71b74f5f4..a1b84196e8 100644 --- a/mp/src/game/server/server_momentum.vpc +++ b/mp/src/game/server/server_momentum.vpc @@ -117,6 +117,8 @@ $Project "Server (Momentum)" $File "momentum\mom_gameinterface.cpp" $File "momentum\mom_eventlog.cpp" $File "momentum\mom_playermove.cpp" + $File "momentum\mom_replay.cpp" + $File "momentum\mom_replay.h" } $Folder "JSON Parser" From 42cfd31818d6264062e0152dcb723c1b53e0ef3e Mon Sep 17 00:00:00 2001 From: RSTFS Date: Mon, 18 Apr 2016 22:29:12 -0400 Subject: [PATCH 002/101] Fix zone-based start/stop recording logic. --- mp/src/game/server/momentum/mom_replay.cpp | 7 ++++++- mp/src/game/server/momentum/mom_replay.h | 2 +- mp/src/game/server/momentum/mom_triggers.cpp | 15 ++++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 5f88a5d042..b7ab69455a 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -15,8 +15,13 @@ void CMomentumReplaySystem::BeginRecording(CBasePlayer *pPlayer) m_bIsRecording = true; } -void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer) +void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway) { + if (throwaway) + { + m_bIsRecording = false; + return; + } Log("Recording Stopped! Ticks: %i\n", m_nRecordingTicks); m_bIsRecording = false; diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index ff21a4c994..a2ef29bbc9 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -19,7 +19,7 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame } } void BeginRecording(CBasePlayer *pPlayer); - void StopRecording(CBasePlayer *pPlayer); + void StopRecording(CBasePlayer *pPlayer, bool throwaway); bool IsRecording(CBasePlayer *pPlayer) { return m_bIsRecording; } void WriteRecordingToFile(); diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index c7ddb579c4..fc10bcf209 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -151,8 +151,13 @@ void CTriggerTimerStart::StartTouch(CBaseEntity *pOther) //lower the player's speed if they try to jump back into the start zone } //begin recording demo - engine->ClientCommand(pPlayer->edict(), "record tempdemo"); - g_ReplaySystem->BeginRecording(pPlayer); + if (!g_ReplaySystem->IsRecording(pPlayer)) + g_ReplaySystem->BeginRecording(pPlayer); + else + { + g_ReplaySystem->StopRecording(pPlayer, true); + g_ReplaySystem->BeginRecording(pPlayer); + } } IGameEvent *mapZoneEvent = gameeventmanager->CreateEvent("player_inside_mapzone"); if (mapZoneEvent) @@ -249,10 +254,10 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) } if (mapZoneEvent) mapZoneEvent->SetBool("map_finished", true); //broadcast that we finished the map with a timer running g_Timer.Stop(true); + //stop demo recording + if (g_ReplaySystem->IsRecording(pPlayer)) + g_ReplaySystem->StopRecording(ToCMOMPlayer(pOther), false); } - //stop demo recording - engine->ClientCommand(UTIL_GetLocalPlayer()->edict(), "stop"); - g_ReplaySystem->StopRecording(ToCMOMPlayer(pOther)); if (mapZoneEvent) { mapZoneEvent->SetBool("inside_endzone", true); From 348d63f75b5ca456845d8750fbd045ad723054e1 Mon Sep 17 00:00:00 2001 From: RSTFS Date: Tue, 19 Apr 2016 17:50:27 -0400 Subject: [PATCH 003/101] Open/close recording file on recording start/top instead of every frame --- mp/src/game/server/momentum/mom_replay.cpp | 8 +++----- mp/src/game/server/momentum/mom_replay.h | 2 ++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index b7ab69455a..c785085906 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -8,9 +8,7 @@ void CMomentumReplaySystem::BeginRecording(CBasePlayer *pPlayer) m_player = pPlayer; //delete old temp recording V_ComposeFileName(recordingPath, "temprecording.momrec", tempRecordingName, MAX_PATH); //we only need to do this once! - if (filesystem->FileExists(tempRecordingName, "MOD")) - filesystem->RemoveFile(tempRecordingName, "MOD"); - + fh = filesystem->Open(tempRecordingName, "w+", "MOD"); Log("Recording began!\n"); m_bIsRecording = true; @@ -22,6 +20,7 @@ void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway) m_bIsRecording = false; return; } + filesystem->Close(fh); Log("Recording Stopped! Ticks: %i\n", m_nRecordingTicks); m_bIsRecording = false; @@ -47,7 +46,7 @@ void CMomentumReplaySystem::UpdateRecordingParams() } void CMomentumReplaySystem::WriteRecordingToFile() { - FileHandle_t fh = filesystem->Open(tempRecordingName, "a", "MOD"); + if (fh) { //buttons, eyeangles XYZ, velocity XYZ, origin XYZ @@ -57,7 +56,6 @@ void CMomentumReplaySystem::WriteRecordingToFile() m_currentFrame.m_vPlayerVelocity.x, m_currentFrame.m_vPlayerVelocity.y, m_currentFrame.m_vPlayerVelocity.z, m_currentFrame.m_vPlayerOrigin.x, m_currentFrame.m_vPlayerOrigin.y, m_currentFrame.m_vPlayerOrigin.z ); - filesystem->Close(fh); } } class CMOMReplayCommands diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index a2ef29bbc9..ef259519aa 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -43,6 +43,8 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame char* recordingPath = "recordings"; char tempRecordingName[BUFSIZELOCL]; int m_nRecordingTicks; + + FileHandle_t fh; }; extern CMomentumReplaySystem *g_ReplaySystem; From b26bcd6a19e7a6d21892c950a81a2f9e42db0fe9 Mon Sep 17 00:00:00 2001 From: RSTFS Date: Wed, 20 Apr 2016 00:54:26 -0400 Subject: [PATCH 004/101] Write replay data as binary, and only write at the end of the recording. TODO: figure out how to attempt rename after writing has completed --- mp/src/game/server/momentum/mom_replay.cpp | 38 +++++++++++++--------- mp/src/game/server/momentum/mom_replay.h | 17 +++------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index c785085906..efc13e1950 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -1,4 +1,5 @@ #include "cbase.h" +#include "utlbuffer.h" #include "mom_replay.h" #include "Timer.h" @@ -8,7 +9,7 @@ void CMomentumReplaySystem::BeginRecording(CBasePlayer *pPlayer) m_player = pPlayer; //delete old temp recording V_ComposeFileName(recordingPath, "temprecording.momrec", tempRecordingName, MAX_PATH); //we only need to do this once! - fh = filesystem->Open(tempRecordingName, "w+", "MOD"); + fh = filesystem->Open(tempRecordingName, "w+b", "MOD"); Log("Recording began!\n"); m_bIsRecording = true; @@ -20,6 +21,9 @@ void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway) m_bIsRecording = false; return; } + + WriteRecordingToFile(*buf); + filesystem->Close(fh); Log("Recording Stopped! Ticks: %i\n", m_nRecordingTicks); m_bIsRecording = false; @@ -28,7 +32,6 @@ void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway) char newRecordingName[MAX_PATH], newRecordingPath[MAX_PATH]; Q_snprintf(newRecordingName, MAX_PATH, "%s_%s_%f.momrec", mPlayer->GetPlayerName(), gpGlobals->mapname.ToCStr(), g_Timer.GetLastRunTime()); V_ComposeFileName(recordingPath, newRecordingName, newRecordingPath, MAX_PATH); - V_FixSlashes(newRecordingName); if (filesystem->FileExists(tempRecordingName, "MOD")) { filesystem->RenameFile(tempRecordingName, newRecordingPath, "MOD"); @@ -36,26 +39,31 @@ void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway) else Warning("Recording file doesn't exist, cannot rename!"); } -void CMomentumReplaySystem::UpdateRecordingParams() +CUtlBuffer *CMomentumReplaySystem::UpdateRecordingParams() { - m_currentFrame.m_nPlayerButtons = m_player->m_nButtons; - m_currentFrame.m_qEyeAngles = m_player->EyeAngles(); - m_currentFrame.m_vPlayerVelocity = m_player->GetLocalVelocity(); - m_currentFrame.m_vPlayerOrigin = m_player->GetLocalOrigin(); + //TODO: figure out why we have to declare the buffer in this scope, and maybe change it + static CUtlBuffer buf; + + buf.PutInt(m_player->m_nButtons); + buf.PutFloat(m_player->EyeAngles().x); + buf.PutFloat(m_player->EyeAngles().y); + buf.PutFloat(m_player->EyeAngles().z); + buf.PutFloat(m_player->GetLocalVelocity().x); + buf.PutFloat(m_player->GetLocalVelocity().y); + buf.PutFloat(m_player->GetLocalVelocity().z); + buf.PutFloat(m_player->GetLocalOrigin().x); + buf.PutFloat(m_player->GetLocalOrigin().y); + buf.PutFloat(m_player->GetLocalOrigin().z); m_nRecordingTicks++; + return &buf; } -void CMomentumReplaySystem::WriteRecordingToFile() +void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer &buf) { - if (fh) { //buttons, eyeangles XYZ, velocity XYZ, origin XYZ - filesystem->FPrintf(fh, "%i %f, %f, %f %f, %f, %f %f, %f, %f\n", - m_currentFrame.m_nPlayerButtons, - m_currentFrame.m_qEyeAngles.x, m_currentFrame.m_qEyeAngles.y, m_currentFrame.m_qEyeAngles.z, - m_currentFrame.m_vPlayerVelocity.x, m_currentFrame.m_vPlayerVelocity.y, m_currentFrame.m_vPlayerVelocity.z, - m_currentFrame.m_vPlayerOrigin.x, m_currentFrame.m_vPlayerOrigin.y, m_currentFrame.m_vPlayerOrigin.z - ); + filesystem->Write(buf.Base(), buf.TellPut(), fh); + buf.Purge(); } } class CMOMReplayCommands diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index ef259519aa..0b5e85c43b 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -2,6 +2,7 @@ #define MOM_REPLAY_H #include "cbase.h" #include "filesystem.h" +#include "utlbuffer.h" #include "mom_player_shared.h" #include "mom_shareddefs.h" @@ -14,37 +15,29 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame { if (m_bIsRecording) { - UpdateRecordingParams(); - WriteRecordingToFile(); + buf = UpdateRecordingParams(); } } void BeginRecording(CBasePlayer *pPlayer); void StopRecording(CBasePlayer *pPlayer, bool throwaway); bool IsRecording(CBasePlayer *pPlayer) { return m_bIsRecording; } - void WriteRecordingToFile(); + void WriteRecordingToFile(CUtlBuffer &buf); private: - void UpdateRecordingParams(); //called every game frame after entities think and update + CUtlBuffer *UpdateRecordingParams(); //called every game frame after entities think and update void Reset() { m_nRecordingTicks = 0; } bool m_bIsRecording; CBasePlayer *m_player; - struct replay_t - { - QAngle m_qEyeAngles; - Vector m_vPlayerOrigin; - Vector m_vPlayerVelocity; - int m_nPlayerButtons; - }; - replay_t m_currentFrame; char* recordingPath = "recordings"; char tempRecordingName[BUFSIZELOCL]; int m_nRecordingTicks; FileHandle_t fh; + CUtlBuffer *buf; }; extern CMomentumReplaySystem *g_ReplaySystem; From efd3869cf5359553459a36787c9c05370586cb2c Mon Sep 17 00:00:00 2001 From: tuxxi Date: Wed, 20 Apr 2016 00:00:17 -0700 Subject: [PATCH 005/101] functions for reading replay headers and files + added replay ghost entity. TODO: fix reading files, currently getting the wrong data due to the encoding I think. --- mp/src/game/server/momentum/mom_replay.cpp | 150 ++++++++++----- mp/src/game/server/momentum/mom_replay.h | 46 +++-- .../server/momentum/mom_replay_entity.cpp | 181 ++++++++++++++++++ .../game/server/momentum/mom_replay_entity.h | 37 ++++ mp/src/game/server/server_momentum.vpc | 8 + 5 files changed, 366 insertions(+), 56 deletions(-) create mode 100644 mp/src/game/server/momentum/mom_replay_entity.cpp create mode 100644 mp/src/game/server/momentum/mom_replay_entity.h diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index efc13e1950..215d6d9eca 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -1,18 +1,14 @@ #include "cbase.h" #include "utlbuffer.h" #include "mom_replay.h" +#include "mom_replay_entity.h" #include "Timer.h" void CMomentumReplaySystem::BeginRecording(CBasePlayer *pPlayer) { - Reset(); m_player = pPlayer; - //delete old temp recording - V_ComposeFileName(recordingPath, "temprecording.momrec", tempRecordingName, MAX_PATH); //we only need to do this once! - fh = filesystem->Open(tempRecordingName, "w+b", "MOD"); - Log("Recording began!\n"); m_bIsRecording = true; - + Log("Recording began!\n"); } void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway) { @@ -21,23 +17,22 @@ void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway) m_bIsRecording = false; return; } + CMomentumPlayer *pMOMPlayer = ToCMOMPlayer(pPlayer); + char newRecordingName[MAX_PATH], newRecordingPath[MAX_PATH]; + Q_snprintf(newRecordingName, MAX_PATH, "%s_%s_%.3f.momrec", pMOMPlayer->GetPlayerName(), gpGlobals->mapname.ToCStr(), g_Timer.GetLastRunTime()); + V_ComposeFileName(RECORDING_PATH, newRecordingName, newRecordingPath, MAX_PATH); //V_ComposeFileName calls all relevent filename functions for us! THANKS GABEN - WriteRecordingToFile(*buf); + V_FixSlashes(RECORDING_PATH); + filesystem->CreateDirHierarchy(RECORDING_PATH, "MOD"); //we have to create the directory here just in case it doesnt exist yet - filesystem->Close(fh); - Log("Recording Stopped! Ticks: %i\n", m_nRecordingTicks); - m_bIsRecording = false; + m_fhFileHandle = filesystem->Open(newRecordingPath, "w+b", "MOD"); - CMomentumPlayer *mPlayer = ToCMOMPlayer(pPlayer); - char newRecordingName[MAX_PATH], newRecordingPath[MAX_PATH]; - Q_snprintf(newRecordingName, MAX_PATH, "%s_%s_%f.momrec", mPlayer->GetPlayerName(), gpGlobals->mapname.ToCStr(), g_Timer.GetLastRunTime()); - V_ComposeFileName(recordingPath, newRecordingName, newRecordingPath, MAX_PATH); - if (filesystem->FileExists(tempRecordingName, "MOD")) - { - filesystem->RenameFile(tempRecordingName, newRecordingPath, "MOD"); - } - else - Warning("Recording file doesn't exist, cannot rename!"); + WriteRecordingToFile(*m_buf); + + filesystem->Close(m_fhFileHandle); + Log("Recording Stopped! Ticks: %i\n", m_currentFrame.m_nCurrentTick); + m_bIsRecording = false; + LoadRun(newRecordingName); //load the last run that we did in case we want to watch it } CUtlBuffer *CMomentumReplaySystem::UpdateRecordingParams() { @@ -45,6 +40,7 @@ CUtlBuffer *CMomentumReplaySystem::UpdateRecordingParams() static CUtlBuffer buf; buf.PutInt(m_player->m_nButtons); + buf.PutInt(++m_currentFrame.m_nCurrentTick); buf.PutFloat(m_player->EyeAngles().x); buf.PutFloat(m_player->EyeAngles().y); buf.PutFloat(m_player->EyeAngles().z); @@ -54,45 +50,109 @@ CUtlBuffer *CMomentumReplaySystem::UpdateRecordingParams() buf.PutFloat(m_player->GetLocalOrigin().x); buf.PutFloat(m_player->GetLocalOrigin().y); buf.PutFloat(m_player->GetLocalOrigin().z); - m_nRecordingTicks++; return &buf; } +replay_header_t CMomentumReplaySystem::CreateHeader() +{ + replay_header_t header; + header.interval_per_tick = gpGlobals->interval_per_tick; + header.mapName = gpGlobals->mapname.ToCStr(); + header.playerName = m_player->GetPlayerName(); + header.steamID64 = steamapicontext->SteamUser()->GetSteamID().ConvertToUint64(); + return header; +} void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer &buf) { - if (fh) + if (m_fhFileHandle) { + replay_header_t header = CreateHeader(); + //write header: Mapname, Playername, steam64, interval per tick + filesystem->FPrintf(m_fhFileHandle, "|| %s %s %llu %f\n", + header.mapName, + header.playerName, + header.steamID64, + header.interval_per_tick + ); //buttons, eyeangles XYZ, velocity XYZ, origin XYZ - filesystem->Write(buf.Base(), buf.TellPut(), fh); + filesystem->Write(buf.Base(), buf.TellPut(), m_fhFileHandle); buf.Purge(); } } -class CMOMReplayCommands +//read a single frame of a recording +replay_frame_t CMomentumReplaySystem::ReadSingleFrame(FileHandle_t file) { -public: - static void PlayRecording(const CCommand &args) + replay_frame_t frame; + + char cmp[256]; + filesystem->ReadLine(cmp, 256, file); + + if (Q_strncmp(cmp, "||", sizeof(cmp)) != 0) //check to see that we're not trying to read the header + { + filesystem->Read(&frame.m_nCurrentTick, sizeof(frame.m_nCurrentTick), file); + filesystem->Read(&frame.m_nPlayerButtons, sizeof(frame.m_nPlayerButtons), file); + + for (int i = 0; i < 2; i++) //loop through XYZ + filesystem->Read(&frame.m_qEyeAngles[i], sizeof(frame.m_qEyeAngles[i]), file); + for (int i = 0; i < 2; i++) + filesystem->Read(&frame.m_vPlayerVelocity[i], sizeof(frame.m_vPlayerVelocity[i]), file); + for (int i = 0; i < 2; i++) + filesystem->Read(&frame.m_vPlayerOrigin[i], sizeof(frame.m_vPlayerOrigin[i]), file); + } + return frame; +} +replay_header_t CMomentumReplaySystem::ReadHeader(FileHandle_t file) +{ + replay_header_t header; + + char cmp[256]; + filesystem->ReadLine(cmp, 256, file); + if (Q_strncmp(cmp, "||", sizeof(cmp)) == 0) { - char recordingName[BUFSIZELOCL]; - V_ComposeFileName("recordings", args.ArgS(), recordingName, MAX_PATH); + filesystem->Read(&header.interval_per_tick, sizeof(header.interval_per_tick), file); + filesystem->Read(&header.mapName, sizeof(header.mapName), file); + filesystem->Read(&header.playerName, sizeof(header.playerName), file); + filesystem->Read(&header.steamID64, sizeof(header.steamID64), file); + } + return header; +} +void CMomentumReplaySystem::LoadRun(const char* filename) +{ + m_vecRunData.RemoveAll(); + char recordingName[BUFSIZELOCL]; + V_ComposeFileName(RECORDING_PATH, filename, recordingName, MAX_PATH); + FileHandle_t replayFile = filesystem->Open(recordingName, "r+b", "MOD"); - if (Q_strlen(args.GetCommandString()) > 1) + if (replayFile != nullptr && filename != NULL) + { + //NNOM_TODO: Do something with the run header data + //replay_header_t header = CMomentumReplaySystem::ReadHeader(replayFile); + while (!filesystem->EndOfFile(replayFile)) + { + replay_frame_t frame = CMomentumReplaySystem::ReadSingleFrame(replayFile); + m_vecRunData.AddToTail(frame); + } + } + filesystem->Close(replayFile); +} +void CMomentumReplaySystem::StartRun() +{ + CMomentumReplayGhostEntity *ghost = static_cast(CreateEntityByName("mom_replay_ghost")); + if (ghost != nullptr) + { + FOR_EACH_VEC(m_vecRunData, i) { - CBasePlayer *pPlayer = UTIL_GetListenServerHost(); - FileHandle_t fh = filesystem->Open(recordingName, "r", "MOD"); - if (fh) - { - int file_len = filesystem->Size(fh); - char recordingLine[1024]; - for (int i = 1; i < file_len; i++) - { - filesystem->ReadLine(recordingLine, sizeof(recordingLine), fh); - pPlayer->m_nButtons = Q_atoi(recordingLine); - } - //pPlayer->SetLocalOrigin(Vector(, )); - //pPlayer->SetLocalVelocity(Vector()); - //pPlayer->SetLocalAngles(QAngle()); - filesystem->Close(fh); - } + ghost->m_entRunData[i] = m_vecRunData[i]; } + ghost->StartRun(); + } +} +class CMOMReplayCommands +{ +public: + static void PlayRecording(const CCommand &args) + { + g_ReplaySystem->LoadRun(args.ArgS()); + g_ReplaySystem->StartRun(); } }; diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index 0b5e85c43b..bc021c99ea 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -7,37 +7,61 @@ #include "mom_player_shared.h" #include "mom_shareddefs.h" +#define RECORDING_PATH "recordings" + +//describes a single frame of a replay +struct replay_frame_t +{ + QAngle m_qEyeAngles; + Vector m_vPlayerOrigin; + Vector m_vPlayerVelocity; + int m_nPlayerButtons; + int m_nCurrentTick; +}; +//the replay header +struct replay_header_t +{ + const char* mapName; + const char* playerName; + uint64 steamID64; + float interval_per_tick; +}; class CMomentumReplaySystem : CAutoGameSystemPerFrame { public: CMomentumReplaySystem(const char *pName) : CAutoGameSystemPerFrame(pName) {} - virtual void FrameUpdatePostEntityThink() + virtual void FrameUpdatePostEntityThink() //inherited member from CAutoGameSystemPerFrame { if (m_bIsRecording) { - buf = UpdateRecordingParams(); + m_buf = UpdateRecordingParams(); } } void BeginRecording(CBasePlayer *pPlayer); void StopRecording(CBasePlayer *pPlayer, bool throwaway); bool IsRecording(CBasePlayer *pPlayer) { return m_bIsRecording; } void WriteRecordingToFile(CUtlBuffer &buf); + replay_header_t CreateHeader(); + void WriteRecordingToFile(); + + static replay_frame_t ReadSingleFrame(FileHandle_t file); + static replay_header_t ReadHeader(FileHandle_t file); + + void StartRun(); + void EndRun(); + void LoadRun(const char* fileName); + CUtlVector m_vecRunData; + private: CUtlBuffer *UpdateRecordingParams(); //called every game frame after entities think and update - void Reset() - { - m_nRecordingTicks = 0; - } bool m_bIsRecording; CBasePlayer *m_player; - char* recordingPath = "recordings"; - char tempRecordingName[BUFSIZELOCL]; - int m_nRecordingTicks; + replay_frame_t m_currentFrame; - FileHandle_t fh; - CUtlBuffer *buf; + FileHandle_t m_fhFileHandle; + CUtlBuffer *m_buf; }; extern CMomentumReplaySystem *g_ReplaySystem; diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp new file mode 100644 index 0000000000..bd250ef58b --- /dev/null +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -0,0 +1,181 @@ +#include "cbase.h" +#include "mom_replay_entity.h" + + +#define MODEL "models/cone.mdl" + +LINK_ENTITY_TO_CLASS(mom_replay_ghost, CMomentumReplayGhostEntity); + +BEGIN_DATADESC(CMomentumReplayGhostEntity) +END_DATADESC() + +const char* CMomentumReplayGhostEntity::GetGhostModel() +{ + return m_pszModel; +} + +void CMomentumReplayGhostEntity::Precache(void) +{ + BaseClass::Precache(); + m_ghostColor = COLOR_BLUE; //default color +} + +//----------------------------------------------------------------------------- +// Purpose: Sets up the entity's initial state +//----------------------------------------------------------------------------- +void CMomentumReplayGhostEntity::Spawn(void) +{ + Precache(); + RemoveEffects(EF_NODRAW); + SetModel(MODEL); + SetSolid( SOLID_NONE ); + SetRenderMode(kRenderTransColor); + SetRenderColor(m_ghostColor.r(), m_ghostColor.g(), m_ghostColor.b()); + SetRenderColorA(75); + SetMoveType( MOVETYPE_NOCLIP ); + m_bIsActive = true; +} + +void CMomentumReplayGhostEntity::StartRun() +{ + //Msg("Starting run with Rundata: %i, Step %i, Name %s, Starttime: %f, This: %i\n", RunData.size(), step, m_gName, startTime, this); + m_nStartTick = gpGlobals->curtime; + m_bIsActive = true; + step = 0; + SetAbsOrigin(Vector(m_entRunData[0].m_vPlayerOrigin.x, + m_entRunData[0].m_vPlayerOrigin.y, + m_entRunData[0].m_vPlayerOrigin.z)); + + SetNextThink(gpGlobals->curtime); +} +void CMomentumReplayGhostEntity::updateStep() +{ + const size_t numTicks = m_entRunData.Count(); + if (step < 0 || step >= numTicks) + { + currentStep = nextStep = NULL; + return; + } + currentStep = &m_entRunData[step]; + int currentTick = gpGlobals->tickcount - m_nStartTick; + + //catching up to a fast ghost, you came in late + if (currentTick > currentStep->m_nCurrentTick) + { + unsigned int x = step + 1; + while (++x < numTicks) + { + if (currentTick < m_entRunData[x].m_nCurrentTick) { + break; + } + } + step = x - 1; + } + currentStep = &m_entRunData[step];//update it to the new step + currentTick = gpGlobals->tickcount - m_nStartTick;//update to new time + + if (step == (numTicks - 1)) //if it's on the last step + { + //end the run somehow + } + else + { + nextStep = &m_entRunData[step + 1]; + } +} +//----------------------------------------------------------------------------- +// Purpose: Think function to move the ghost +//----------------------------------------------------------------------------- +void CMomentumReplayGhostEntity::Think(void) +{ + BaseClass::Think(); + if (Q_strlen(m_pszPlayerName) != 0) + { + if (!IsEffectActive(EF_NODRAW)) + EntityText(0, m_pszPlayerName, 0); + updateStep(); + HandleGhost(); + } + else + EndRun(); + + SetNextThink(gpGlobals->curtime + gpGlobals->interval_per_tick); +} + +void CMomentumReplayGhostEntity::HandleGhost() { + if (currentStep != NULL) + { + if (!m_bIsActive) + { + if (!Q_strcmp(m_pszMapName, STRING(gpGlobals->mapname)) == 0) + DispatchSpawn(this); + } + else + { + float x = currentStep->m_vPlayerOrigin.x; + float y = currentStep->m_vPlayerOrigin.y; + float z = currentStep->m_vPlayerOrigin.z; + float angleX = currentStep->m_qEyeAngles.x; + float angleY = currentStep->m_qEyeAngles.y; + float angleZ = currentStep->m_qEyeAngles.z; + int t1 = currentStep->m_nCurrentTick; + + if (x == 0.0f) + return; + + if (nextStep != NULL) // we have to be at least 2 ticks into the replay to interpolate + { + if (IsEffectActive(EF_NODRAW)) + RemoveEffects(EF_NODRAW); + + float x2 = nextStep->m_vPlayerOrigin.x; + float y2 = nextStep->m_vPlayerOrigin.y; + float z2 = nextStep->m_vPlayerOrigin.z; + float angleX2 = nextStep->m_qEyeAngles.x; + float angleY2 = nextStep->m_qEyeAngles.y; + float angleZ2 = nextStep->m_qEyeAngles.z; + int t2 = nextStep->m_nCurrentTick; + + //interpolate position + float scalar = (((gpGlobals->tickcount - m_nStartTick) - t1) / (t2 - t1)); //time difference scalar value used to interpolate + + float xfinal = x + (scalar * (x2 - x)); + float yfinal = y + (scalar * (y2 - y)); + float zfinal = z + (scalar * (z2 - z)); + SetAbsOrigin(Vector(xfinal, yfinal, (zfinal - 15.0f))); //@Tuxxi: @gocnak, why are we subtracting 15.0 here? + float angleXFinal = angleX + (scalar * (angleX2 - angleX)); + float angleYFinal = angleY + (scalar * (angleY2 - angleY)); + float angleZFinal = angleZ + (scalar * (angleZ2 - angleZ)); + SetAbsAngles(QAngle(angleXFinal, angleYFinal, angleZFinal)); + } + else //we cant interpolate + { + SetAbsOrigin(Vector(x, y, (z - 15.0f))); + SetAbsAngles(QAngle(angleX, angleY, angleZ)); + } + } + } + else + { + // END RUN + } +} + +void CMomentumReplayGhostEntity::SetGhostModel(const char * newmodel) +{ + if (newmodel) { + Q_strcpy(m_pszModel, newmodel); + PrecacheModel(m_pszModel); + SetModel(m_pszModel); + } +} +void CMomentumReplayGhostEntity::EndRun() +{ + SetNextThink(0.0f); + Remove(); + m_bIsActive = false; +} +void CMomentumReplayGhostEntity::clearRunData() +{ + m_entRunData.RemoveAll(); +} \ No newline at end of file diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h new file mode 100644 index 0000000000..d2f2529c36 --- /dev/null +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -0,0 +1,37 @@ +#include "cbase.h" +#include "mom_replay.h" + +#pragma once +class CMomentumReplayGhostEntity : public CBaseAnimating +{ + DECLARE_CLASS(CMomentumReplayGhostEntity, CBaseAnimating); + DECLARE_DATADESC(); +public: + const char* GetGhostModel(); + void SetGhostModel( const char* ); + //Increments the steps intelligently. + void updateStep(); + + void EndRun(); + void StartRun(); + void HandleGhost(); + void clearRunData(); + + bool m_bIsActive; + CUtlVector m_entRunData; + int m_nStartTick; +protected: + virtual void Think( void ); + virtual void Spawn(void); + virtual void Precache(void); + +private: + char m_pszModel[256], m_pszPlayerName[256], m_pszMapName[256]; + replay_frame_t* currentStep; + replay_frame_t* nextStep; + + unsigned int step; + + Color m_ghostColor; + +}; diff --git a/mp/src/game/server/server_momentum.vpc b/mp/src/game/server/server_momentum.vpc index a1b84196e8..77ae36b456 100644 --- a/mp/src/game/server/server_momentum.vpc +++ b/mp/src/game/server/server_momentum.vpc @@ -49,6 +49,14 @@ $Project "Server (Momentum)" $File "momentum\Timer.h" $File "momentum\Timer.cpp" } + + $Folder "Replays" + { + $File "momentum\mom_replay.cpp" + $File "momentum\mom_replay.h" + $File "momentum\mom_replay_entity.cpp" + $File "momentum\mom_replay_entity.h" + } $File "momentum\mom_blockfix.h" $File "momentum\mom_blockfix.cpp" From 859ea1dc40c94b82c3b4388e107181ed4eeab15c Mon Sep 17 00:00:00 2001 From: tuxxi Date: Sat, 23 Apr 2016 00:07:56 -0700 Subject: [PATCH 006/101] properly writing and reading both replay header and replay ticks. TODO: fix stack overflow when reading file, apply info to ghost entity --- mp/src/game/server/momentum/mom_replay.cpp | 91 +++++++++------------- mp/src/game/server/momentum/mom_replay.h | 27 +++++-- 2 files changed, 60 insertions(+), 58 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 215d6d9eca..67c695040e 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -38,81 +38,65 @@ CUtlBuffer *CMomentumReplaySystem::UpdateRecordingParams() { //TODO: figure out why we have to declare the buffer in this scope, and maybe change it static CUtlBuffer buf; + m_currentFrame.m_nCurrentTick++; + m_currentFrame.m_nPlayerButtons = m_player->m_nButtons; + m_currentFrame.m_qEyeAngles = m_player->EyeAngles(); + m_currentFrame.m_vPlayerOrigin = m_player->GetAbsOrigin(); + m_currentFrame.m_vPlayerVelocity = m_player->GetAbsVelocity(); - buf.PutInt(m_player->m_nButtons); - buf.PutInt(++m_currentFrame.m_nCurrentTick); - buf.PutFloat(m_player->EyeAngles().x); - buf.PutFloat(m_player->EyeAngles().y); - buf.PutFloat(m_player->EyeAngles().z); - buf.PutFloat(m_player->GetLocalVelocity().x); - buf.PutFloat(m_player->GetLocalVelocity().y); - buf.PutFloat(m_player->GetLocalVelocity().z); - buf.PutFloat(m_player->GetLocalOrigin().x); - buf.PutFloat(m_player->GetLocalOrigin().y); - buf.PutFloat(m_player->GetLocalOrigin().z); + ByteSwap_replay_frame_t(m_currentFrame); //We need to byteswap all of our data first in order to write each byte in the correct order + + Assert(buf.IsValid()); + buf.Put(&m_currentFrame, sizeof(replay_frame_t)); //stick all the frame info into the buffer return &buf; } replay_header_t CMomentumReplaySystem::CreateHeader() { replay_header_t header; - header.interval_per_tick = gpGlobals->interval_per_tick; - header.mapName = gpGlobals->mapname.ToCStr(); - header.playerName = m_player->GetPlayerName(); + Q_strcpy(header.mapName, gpGlobals->mapname.ToCStr()); + Q_strcpy(header.playerName, m_player->GetPlayerName()); header.steamID64 = steamapicontext->SteamUser()->GetSteamID().ConvertToUint64(); + header.interval_per_tick = gpGlobals->interval_per_tick; return header; } void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer &buf) { if (m_fhFileHandle) { - replay_header_t header = CreateHeader(); //write header: Mapname, Playername, steam64, interval per tick - filesystem->FPrintf(m_fhFileHandle, "|| %s %s %llu %f\n", - header.mapName, - header.playerName, - header.steamID64, - header.interval_per_tick - ); - //buttons, eyeangles XYZ, velocity XYZ, origin XYZ + replay_header_t littleEndianHeader = CreateHeader(); + ByteSwap_replay_header_t(littleEndianHeader); //byteswap again + + filesystem->Seek(m_fhFileHandle, 0, FILESYSTEM_SEEK_HEAD); + filesystem->Write(&littleEndianHeader, sizeof(replay_header_t), m_fhFileHandle); + + Assert(buf.IsValid()); + //write write from the CUtilBuffer to our filehandle: filesystem->Write(buf.Base(), buf.TellPut(), m_fhFileHandle); buf.Purge(); } } -//read a single frame of a recording +//read a single frame (or tick) of a recording replay_frame_t CMomentumReplaySystem::ReadSingleFrame(FileHandle_t file) { replay_frame_t frame; + Assert(file != FILESYSTEM_INVALID_HANDLE); + filesystem->Read(&frame, sizeof(replay_frame_t), file); + ByteSwap_replay_frame_t(frame); - char cmp[256]; - filesystem->ReadLine(cmp, 256, file); - - if (Q_strncmp(cmp, "||", sizeof(cmp)) != 0) //check to see that we're not trying to read the header - { - filesystem->Read(&frame.m_nCurrentTick, sizeof(frame.m_nCurrentTick), file); - filesystem->Read(&frame.m_nPlayerButtons, sizeof(frame.m_nPlayerButtons), file); - - for (int i = 0; i < 2; i++) //loop through XYZ - filesystem->Read(&frame.m_qEyeAngles[i], sizeof(frame.m_qEyeAngles[i]), file); - for (int i = 0; i < 2; i++) - filesystem->Read(&frame.m_vPlayerVelocity[i], sizeof(frame.m_vPlayerVelocity[i]), file); - for (int i = 0; i < 2; i++) - filesystem->Read(&frame.m_vPlayerOrigin[i], sizeof(frame.m_vPlayerOrigin[i]), file); - } return frame; } replay_header_t CMomentumReplaySystem::ReadHeader(FileHandle_t file) { replay_header_t header; + Q_memset(&header, 0, sizeof(header)); + + Assert(file != FILESYSTEM_INVALID_HANDLE); + filesystem->Seek(file, 0, FILESYSTEM_SEEK_HEAD); + filesystem->Read(&header, sizeof(replay_header_t), file); + + ByteSwap_replay_header_t(header); - char cmp[256]; - filesystem->ReadLine(cmp, 256, file); - if (Q_strncmp(cmp, "||", sizeof(cmp)) == 0) - { - filesystem->Read(&header.interval_per_tick, sizeof(header.interval_per_tick), file); - filesystem->Read(&header.mapName, sizeof(header.mapName), file); - filesystem->Read(&header.playerName, sizeof(header.playerName), file); - filesystem->Read(&header.steamID64, sizeof(header.steamID64), file); - } return header; } void CMomentumReplaySystem::LoadRun(const char* filename) @@ -120,19 +104,20 @@ void CMomentumReplaySystem::LoadRun(const char* filename) m_vecRunData.RemoveAll(); char recordingName[BUFSIZELOCL]; V_ComposeFileName(RECORDING_PATH, filename, recordingName, MAX_PATH); - FileHandle_t replayFile = filesystem->Open(recordingName, "r+b", "MOD"); + m_fhFileHandle = filesystem->Open(recordingName, "r+b", "MOD"); - if (replayFile != nullptr && filename != NULL) + if (m_fhFileHandle != nullptr && filename != NULL) { //NNOM_TODO: Do something with the run header data - //replay_header_t header = CMomentumReplaySystem::ReadHeader(replayFile); - while (!filesystem->EndOfFile(replayFile)) + replay_header_t header = CMomentumReplaySystem::ReadHeader(m_fhFileHandle); + DevLog("playername: %s mapname: %s steamid: %llu tickrate: %f", header.playerName, header.mapName, header.steamID64, header.interval_per_tick); + while (!filesystem->EndOfFile(m_fhFileHandle)) { - replay_frame_t frame = CMomentumReplaySystem::ReadSingleFrame(replayFile); + replay_frame_t frame = CMomentumReplaySystem::ReadSingleFrame(m_fhFileHandle); m_vecRunData.AddToTail(frame); } } - filesystem->Close(replayFile); + filesystem->Close(m_fhFileHandle); } void CMomentumReplaySystem::StartRun() { diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index bc021c99ea..908465175a 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -8,7 +8,6 @@ #include "mom_shareddefs.h" #define RECORDING_PATH "recordings" - //describes a single frame of a replay struct replay_frame_t { @@ -18,14 +17,31 @@ struct replay_frame_t int m_nPlayerButtons; int m_nCurrentTick; }; +inline void ByteSwap_replay_frame_t(replay_frame_t &swap) +{ + for (int i = 0; i < 2; i++) { + LittleFloat(&swap.m_qEyeAngles[i], &swap.m_qEyeAngles[i]); + LittleFloat(&swap.m_vPlayerOrigin[i], &swap.m_vPlayerOrigin[i]); + LittleFloat(&swap.m_vPlayerVelocity[i], &swap.m_vPlayerVelocity[i]); + } + swap.m_nPlayerButtons = LittleDWord(swap.m_nPlayerButtons); + swap.m_nCurrentTick = LittleDWord(swap.m_nCurrentTick); +} //the replay header struct replay_header_t { - const char* mapName; - const char* playerName; + char mapName[MAX_PATH]; + char playerName[MAX_PATH]; uint64 steamID64; float interval_per_tick; }; +//byteswap for int and float members of header +inline void ByteSwap_replay_header_t(replay_header_t &swap) +{ + LittleFloat(&swap.interval_per_tick, &swap.interval_per_tick); + swap.steamID64 = LittleLong(swap.steamID64); +} + class CMomentumReplaySystem : CAutoGameSystemPerFrame { public: @@ -44,8 +60,8 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame replay_header_t CreateHeader(); void WriteRecordingToFile(); - static replay_frame_t ReadSingleFrame(FileHandle_t file); - static replay_header_t ReadHeader(FileHandle_t file); + replay_frame_t ReadSingleFrame(FileHandle_t file); + replay_header_t ReadHeader(FileHandle_t file); void StartRun(); void EndRun(); @@ -62,6 +78,7 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame FileHandle_t m_fhFileHandle; CUtlBuffer *m_buf; + const static char CONTROL_CHAR = '\u001F'; //CONTROL CHAR = UNICODE 001F (37) [00011111] }; extern CMomentumReplaySystem *g_ReplaySystem; From d8d0c8c2396f4162f338f4a38a4b4f2221d62e06 Mon Sep 17 00:00:00 2001 From: RSTFS Date: Sat, 23 Apr 2016 17:04:57 -0400 Subject: [PATCH 007/101] Fix stack buffer overflow on run loading --- mp/src/game/server/momentum/mom_replay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 67c695040e..3180dea317 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -103,7 +103,7 @@ void CMomentumReplaySystem::LoadRun(const char* filename) { m_vecRunData.RemoveAll(); char recordingName[BUFSIZELOCL]; - V_ComposeFileName(RECORDING_PATH, filename, recordingName, MAX_PATH); + V_ComposeFileName(RECORDING_PATH, filename, recordingName, BUFSIZELOCL); m_fhFileHandle = filesystem->Open(recordingName, "r+b", "MOD"); if (m_fhFileHandle != nullptr && filename != NULL) From ac235396a4cbff0ba708efc30d8239ae676e78fa Mon Sep 17 00:00:00 2001 From: tuxxi Date: Sat, 23 Apr 2016 14:37:19 -0700 Subject: [PATCH 008/101] added version info, replay ID string, date, and all run stats to replay header --- mp/src/game/server/momentum/Timer.cpp | 1 + mp/src/game/server/momentum/Timer.h | 5 +- mp/src/game/server/momentum/mom_replay.cpp | 77 ++++++++++++------- mp/src/game/server/momentum/mom_replay.h | 44 +++++++++-- .../server/momentum/mom_replay_entity.cpp | 12 ++- .../game/server/momentum/mom_replay_entity.h | 2 +- 6 files changed, 97 insertions(+), 44 deletions(-) diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index af03c6d49f..8d220b8f82 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -273,6 +273,7 @@ void CTimer::Stop(bool endTrigger /* = false */) gameeventmanager->FireEvent(mapZoneEvent); } SetRunning(false); + m_iEndTick = gpGlobals->tickcount; DispatchStateMessage(); } void CTimer::OnMapEnd(const char *pMapName) diff --git a/mp/src/game/server/momentum/Timer.h b/mp/src/game/server/momentum/Timer.h index e960f4aeae..cfdb8f252f 100644 --- a/mp/src/game/server/momentum/Timer.h +++ b/mp/src/game/server/momentum/Timer.h @@ -128,7 +128,8 @@ class CTimer void SaveTime(); void OnMapEnd(const char *); void OnMapStart(const char *); - float GetLastRunTime() { return (gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; } + // returns last runtime in ticks + int GetLastRunTimeTicks() { return m_iEndTick - m_iStartTick; } // Practice mode- noclip mode that stops timer void PracticeMove(); void EnablePractice(CBasePlayer *pPlayer); @@ -149,7 +150,7 @@ class CTimer private: int m_iStageCount; - int m_iStartTick; + int m_iStartTick, m_iEndTick; int m_iLastStage = 0; int m_iStageEnterTick[MAX_STAGES]; bool m_bIsRunning; diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 3180dea317..25a0fb8cf6 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -3,23 +3,24 @@ #include "mom_replay.h" #include "mom_replay_entity.h" #include "Timer.h" +#include "util/mom_util.h" void CMomentumReplaySystem::BeginRecording(CBasePlayer *pPlayer) { - m_player = pPlayer; + m_player = ToCMOMPlayer( pPlayer); m_bIsRecording = true; Log("Recording began!\n"); } void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway) { - if (throwaway) - { - m_bIsRecording = false; + m_bIsRecording = false; + if (throwaway) { return; } CMomentumPlayer *pMOMPlayer = ToCMOMPlayer(pPlayer); - char newRecordingName[MAX_PATH], newRecordingPath[MAX_PATH]; - Q_snprintf(newRecordingName, MAX_PATH, "%s_%s_%.3f.momrec", pMOMPlayer->GetPlayerName(), gpGlobals->mapname.ToCStr(), g_Timer.GetLastRunTime()); + char newRecordingName[MAX_PATH], newRecordingPath[MAX_PATH], runTime[BUFSIZETIME]; + mom_UTIL.FormatTime(g_Timer.GetLastRunTimeTicks(), gpGlobals->interval_per_tick, runTime); + Q_snprintf(newRecordingName, MAX_PATH, "%s_%s_%s.momrec", pMOMPlayer->GetPlayerName(), gpGlobals->mapname.ToCStr(), runTime); V_ComposeFileName(RECORDING_PATH, newRecordingName, newRecordingPath, MAX_PATH); //V_ComposeFileName calls all relevent filename functions for us! THANKS GABEN V_FixSlashes(RECORDING_PATH); @@ -31,7 +32,6 @@ void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway) filesystem->Close(m_fhFileHandle); Log("Recording Stopped! Ticks: %i\n", m_currentFrame.m_nCurrentTick); - m_bIsRecording = false; LoadRun(newRecordingName); //load the last run that we did in case we want to watch it } CUtlBuffer *CMomentumReplaySystem::UpdateRecordingParams() @@ -53,10 +53,27 @@ CUtlBuffer *CMomentumReplaySystem::UpdateRecordingParams() replay_header_t CMomentumReplaySystem::CreateHeader() { replay_header_t header; + Q_strcpy(header.demofilestamp, DEMO_HEADER_ID); + header.demoProtoVersion = DEMO_PROTOCOL_VERSION; Q_strcpy(header.mapName, gpGlobals->mapname.ToCStr()); Q_strcpy(header.playerName, m_player->GetPlayerName()); header.steamID64 = steamapicontext->SteamUser()->GetSteamID().ConvertToUint64(); header.interval_per_tick = gpGlobals->interval_per_tick; + header.runTimeTicks = g_Timer.GetLastRunTimeTicks(); + time(&header.unixEpocDate); + + // --- RUN STATS --- + header.m_flEndSpeed = m_player->m_flEndSpeed; + header.m_flStartSpeed = m_player->m_flStartSpeed; + for (int i = 0; i < MAX_STAGES; i++) { + header.m_flStageEnterVelocity[i] = m_player->m_flStageEnterVelocity[i]; + header.m_flStageVelocityAvg[i] = m_player->m_flStageVelocityAvg[i]; + header.m_flStageVelocityMax[i] = m_player->m_flStageVelocityMax[i]; + header.m_flStageStrafeSyncAvg[i] = m_player->m_flStageStrafeSyncAvg[i]; + header.m_flStageStrafeSync2Avg[i] = m_player->m_flStageStrafeSync2Avg[i]; + header.m_nStageJumps[i] = m_player->m_nStageJumps[i]; + header.m_nStageStrafes[i] = m_player->m_nStageStrafes[i]; + } return header; } void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer &buf) @@ -77,55 +94,61 @@ void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer &buf) } } //read a single frame (or tick) of a recording -replay_frame_t CMomentumReplaySystem::ReadSingleFrame(FileHandle_t file) +replay_frame_t* CMomentumReplaySystem::ReadSingleFrame(FileHandle_t file, const char* filename) { - replay_frame_t frame; Assert(file != FILESYSTEM_INVALID_HANDLE); - filesystem->Read(&frame, sizeof(replay_frame_t), file); - ByteSwap_replay_frame_t(frame); + filesystem->Read(&m_currentFrame, sizeof(replay_frame_t), file); + ByteSwap_replay_frame_t(m_currentFrame); - return frame; + return &m_currentFrame; } -replay_header_t CMomentumReplaySystem::ReadHeader(FileHandle_t file) +replay_header_t* CMomentumReplaySystem::ReadHeader(FileHandle_t file, const char* filename) { - replay_header_t header; - Q_memset(&header, 0, sizeof(header)); + Q_memset(&m_replayHeader, 0, sizeof(m_replayHeader)); Assert(file != FILESYSTEM_INVALID_HANDLE); filesystem->Seek(file, 0, FILESYSTEM_SEEK_HEAD); - filesystem->Read(&header, sizeof(replay_header_t), file); + filesystem->Read(&m_replayHeader, sizeof(replay_header_t), file); - ByteSwap_replay_header_t(header); + ByteSwap_replay_header_t(m_replayHeader); - return header; + if (Q_strcmp(m_replayHeader.demofilestamp, DEMO_HEADER_ID)) { + ConMsg("%s has invalid replay header ID.\n", filename); + return nullptr; + } + if ((m_replayHeader.demoProtoVersion > DEMO_PROTOCOL_VERSION) || (m_replayHeader.demoProtoVersion < 2)) { + ConMsg("ERROR: replay file protocol %i outdated, engine version is %i \n", + m_replayHeader.demoProtoVersion, DEMO_PROTOCOL_VERSION); + + return nullptr; + } + return &m_replayHeader; } void CMomentumReplaySystem::LoadRun(const char* filename) { m_vecRunData.RemoveAll(); - char recordingName[BUFSIZELOCL]; - V_ComposeFileName(RECORDING_PATH, filename, recordingName, BUFSIZELOCL); + char recordingName[MAX_PATH]; + V_ComposeFileName(RECORDING_PATH, filename, recordingName, MAX_PATH); m_fhFileHandle = filesystem->Open(recordingName, "r+b", "MOD"); if (m_fhFileHandle != nullptr && filename != NULL) { - //NNOM_TODO: Do something with the run header data - replay_header_t header = CMomentumReplaySystem::ReadHeader(m_fhFileHandle); - DevLog("playername: %s mapname: %s steamid: %llu tickrate: %f", header.playerName, header.mapName, header.steamID64, header.interval_per_tick); + //NOM_TODO: Do something with the run header data + //replay_header_t* header = ReadHeader(m_fhFileHandle, filename); while (!filesystem->EndOfFile(m_fhFileHandle)) { - replay_frame_t frame = CMomentumReplaySystem::ReadSingleFrame(m_fhFileHandle); + replay_frame_t* frame = ReadSingleFrame(m_fhFileHandle, filename); m_vecRunData.AddToTail(frame); } + filesystem->Close(m_fhFileHandle); } - filesystem->Close(m_fhFileHandle); } void CMomentumReplaySystem::StartRun() { CMomentumReplayGhostEntity *ghost = static_cast(CreateEntityByName("mom_replay_ghost")); if (ghost != nullptr) { - FOR_EACH_VEC(m_vecRunData, i) - { + FOR_EACH_VEC(m_vecRunData, i) { ghost->m_entRunData[i] = m_vecRunData[i]; } ghost->StartRun(); diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index 908465175a..57a4cbf51e 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -8,6 +8,9 @@ #include "mom_shareddefs.h" #define RECORDING_PATH "recordings" +#define DEMO_HEADER_ID "MOMREPLAY" +#define DEMO_PROTOCOL_VERSION 2 + //describes a single frame of a replay struct replay_frame_t { @@ -27,19 +30,45 @@ inline void ByteSwap_replay_frame_t(replay_frame_t &swap) swap.m_nPlayerButtons = LittleDWord(swap.m_nPlayerButtons); swap.m_nCurrentTick = LittleDWord(swap.m_nCurrentTick); } -//the replay header +//the replay header, stores a bunch of information about the replay as well as the run stats for that replay struct replay_header_t { + char demofilestamp[9]; //should be DEMO_HEADER_ID + int demoProtoVersion; //should be DEMO_PROTOCOL_VERSION + time_t unixEpocDate; //redundant date check char mapName[MAX_PATH]; char playerName[MAX_PATH]; uint64 steamID64; float interval_per_tick; + int runTimeTicks; //Total run time in ticks + + float m_flStartSpeed, m_flEndSpeed; + int m_nStageJumps[MAX_STAGES], m_nStageStrafes[MAX_STAGES]; + float m_flStageVelocityMax[MAX_STAGES], m_flStageVelocityAvg[MAX_STAGES], + m_flStageStrafeSyncAvg[MAX_STAGES], m_flStageStrafeSync2Avg[MAX_STAGES], m_flStageEnterVelocity[MAX_STAGES]; + }; -//byteswap for int and float members of header +//byteswap for int and float members of header, swaps the endianness (byte order) in order to read correctly inline void ByteSwap_replay_header_t(replay_header_t &swap) { - LittleFloat(&swap.interval_per_tick, &swap.interval_per_tick); + swap.demoProtoVersion = LittleDWord(swap.demoProtoVersion); + swap.runTimeTicks = LittleDWord(swap.runTimeTicks); + swap.unixEpocDate = LittleLong(swap.unixEpocDate); swap.steamID64 = LittleLong(swap.steamID64); + LittleFloat(&swap.interval_per_tick, &swap.interval_per_tick); + + // --- run stats --- + LittleFloat(&swap.m_flEndSpeed, &swap.m_flEndSpeed); + LittleFloat(&swap.m_flStartSpeed, &swap.m_flStartSpeed); + for (int i = 0; i < MAX_STAGES; i++) { + LittleFloat(&swap.m_flStageVelocityMax[i], &swap.m_flStageVelocityMax[i]); + LittleFloat(&swap.m_flStageVelocityAvg[i], &swap.m_flStageVelocityAvg[i]); + LittleFloat(&swap.m_flStageStrafeSyncAvg[i], &swap.m_flStageStrafeSyncAvg[i]); + LittleFloat(&swap.m_flStageStrafeSync2Avg[i], &swap.m_flStageStrafeSync2Avg[i]); + LittleFloat(&swap.m_flStageEnterVelocity[i], &swap.m_flStageEnterVelocity[i]); + swap.m_nStageJumps[i] = LittleDWord(swap.m_nStageJumps[i]); + swap.m_nStageStrafes[i] = LittleDWord(swap.m_nStageStrafes[i]); + } } class CMomentumReplaySystem : CAutoGameSystemPerFrame @@ -60,21 +89,22 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame replay_header_t CreateHeader(); void WriteRecordingToFile(); - replay_frame_t ReadSingleFrame(FileHandle_t file); - replay_header_t ReadHeader(FileHandle_t file); + replay_frame_t* ReadSingleFrame(FileHandle_t file, const char* filename); + replay_header_t* ReadHeader(FileHandle_t file, const char* filename); void StartRun(); void EndRun(); void LoadRun(const char* fileName); - CUtlVector m_vecRunData; + CUtlVector m_vecRunData; private: CUtlBuffer *UpdateRecordingParams(); //called every game frame after entities think and update bool m_bIsRecording; - CBasePlayer *m_player; + CMomentumPlayer *m_player; replay_frame_t m_currentFrame; + replay_header_t m_replayHeader; FileHandle_t m_fhFileHandle; CUtlBuffer *m_buf; diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index bd250ef58b..338a631ce4 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -42,9 +42,7 @@ void CMomentumReplayGhostEntity::StartRun() m_nStartTick = gpGlobals->curtime; m_bIsActive = true; step = 0; - SetAbsOrigin(Vector(m_entRunData[0].m_vPlayerOrigin.x, - m_entRunData[0].m_vPlayerOrigin.y, - m_entRunData[0].m_vPlayerOrigin.z)); + SetAbsOrigin(m_entRunData[0]->m_vPlayerOrigin); SetNextThink(gpGlobals->curtime); } @@ -56,7 +54,7 @@ void CMomentumReplayGhostEntity::updateStep() currentStep = nextStep = NULL; return; } - currentStep = &m_entRunData[step]; + currentStep = m_entRunData[step]; int currentTick = gpGlobals->tickcount - m_nStartTick; //catching up to a fast ghost, you came in late @@ -65,13 +63,13 @@ void CMomentumReplayGhostEntity::updateStep() unsigned int x = step + 1; while (++x < numTicks) { - if (currentTick < m_entRunData[x].m_nCurrentTick) { + if (currentTick < m_entRunData[x]->m_nCurrentTick) { break; } } step = x - 1; } - currentStep = &m_entRunData[step];//update it to the new step + currentStep = m_entRunData[step];//update it to the new step currentTick = gpGlobals->tickcount - m_nStartTick;//update to new time if (step == (numTicks - 1)) //if it's on the last step @@ -80,7 +78,7 @@ void CMomentumReplayGhostEntity::updateStep() } else { - nextStep = &m_entRunData[step + 1]; + nextStep = m_entRunData[step + 1]; } } //----------------------------------------------------------------------------- diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index d2f2529c36..77e204bacb 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -18,7 +18,7 @@ class CMomentumReplayGhostEntity : public CBaseAnimating void clearRunData(); bool m_bIsActive; - CUtlVector m_entRunData; + CUtlVector m_entRunData; int m_nStartTick; protected: virtual void Think( void ); From d3ce9a1ff660312158c9777bc75c70c4d02c0f00 Mon Sep 17 00:00:00 2001 From: tuxxi Date: Sat, 23 Apr 2016 20:32:09 -0700 Subject: [PATCH 009/101] ghost is sorta working, need to figure out why replay frame data doesnt update/change --- mp/src/game/server/momentum/mom_replay.cpp | 45 +++++---- mp/src/game/server/momentum/mom_replay.h | 73 ++------------- .../server/momentum/mom_replay_entity.cpp | 93 +++++++------------ .../game/server/momentum/mom_replay_entity.h | 12 +-- mp/src/game/server/momentum/replayformat.h | 77 +++++++++++++++ mp/src/game/server/server_momentum.vpc | 1 + 6 files changed, 148 insertions(+), 153 deletions(-) create mode 100644 mp/src/game/server/momentum/replayformat.h diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 25a0fb8cf6..03d00d8aeb 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -10,8 +10,9 @@ void CMomentumReplaySystem::BeginRecording(CBasePlayer *pPlayer) m_player = ToCMOMPlayer( pPlayer); m_bIsRecording = true; Log("Recording began!\n"); + m_nCurrentTick = 1; //recoring begins at 1 ;) } -void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway) +void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway, float delay) { m_bIsRecording = false; if (throwaway) { @@ -31,18 +32,18 @@ void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway) WriteRecordingToFile(*m_buf); filesystem->Close(m_fhFileHandle); - Log("Recording Stopped! Ticks: %i\n", m_currentFrame.m_nCurrentTick); - LoadRun(newRecordingName); //load the last run that we did in case we want to watch it + Log("Recording Stopped! Ticks: %i\n", m_nCurrentTick); + if( LoadRun(newRecordingName) ) //load the last run that we did in case we want to watch it + StartReplay(); } CUtlBuffer *CMomentumReplaySystem::UpdateRecordingParams() { - //TODO: figure out why we have to declare the buffer in this scope, and maybe change it + m_nCurrentTick++; //increment recording tick + static CUtlBuffer buf; - m_currentFrame.m_nCurrentTick++; m_currentFrame.m_nPlayerButtons = m_player->m_nButtons; m_currentFrame.m_qEyeAngles = m_player->EyeAngles(); m_currentFrame.m_vPlayerOrigin = m_player->GetAbsOrigin(); - m_currentFrame.m_vPlayerVelocity = m_player->GetAbsVelocity(); ByteSwap_replay_frame_t(m_currentFrame); //We need to byteswap all of our data first in order to write each byte in the correct order @@ -124,7 +125,7 @@ replay_header_t* CMomentumReplaySystem::ReadHeader(FileHandle_t file, const char } return &m_replayHeader; } -void CMomentumReplaySystem::LoadRun(const char* filename) +bool CMomentumReplaySystem::LoadRun(const char* filename) { m_vecRunData.RemoveAll(); char recordingName[MAX_PATH]; @@ -133,24 +134,29 @@ void CMomentumReplaySystem::LoadRun(const char* filename) if (m_fhFileHandle != nullptr && filename != NULL) { - //NOM_TODO: Do something with the run header data - //replay_header_t* header = ReadHeader(m_fhFileHandle, filename); - while (!filesystem->EndOfFile(m_fhFileHandle)) + replay_header_t* header = ReadHeader(m_fhFileHandle, filename); + if (header == nullptr) { + return false; + } + else { - replay_frame_t* frame = ReadSingleFrame(m_fhFileHandle, filename); - m_vecRunData.AddToTail(frame); + while (!filesystem->EndOfFile(m_fhFileHandle)) + { + replay_frame_t* frame = ReadSingleFrame(m_fhFileHandle, filename); + m_vecRunData.AddToTail(frame); + } + return true; } filesystem->Close(m_fhFileHandle); } + else + return false; } -void CMomentumReplaySystem::StartRun() +void CMomentumReplaySystem::StartReplay() { CMomentumReplayGhostEntity *ghost = static_cast(CreateEntityByName("mom_replay_ghost")); if (ghost != nullptr) { - FOR_EACH_VEC(m_vecRunData, i) { - ghost->m_entRunData[i] = m_vecRunData[i]; - } ghost->StartRun(); } } @@ -159,8 +165,11 @@ class CMOMReplayCommands public: static void PlayRecording(const CCommand &args) { - g_ReplaySystem->LoadRun(args.ArgS()); - g_ReplaySystem->StartRun(); + if (args.ArgC() > 1) { //we passed any argument at all + if (g_ReplaySystem->LoadRun(args.ArgS())) { + g_ReplaySystem->StartReplay(); + } + } } }; diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index 57a4cbf51e..4d280aa402 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -6,70 +6,9 @@ #include "mom_player_shared.h" #include "mom_shareddefs.h" +#include "replayformat.h" #define RECORDING_PATH "recordings" -#define DEMO_HEADER_ID "MOMREPLAY" -#define DEMO_PROTOCOL_VERSION 2 - -//describes a single frame of a replay -struct replay_frame_t -{ - QAngle m_qEyeAngles; - Vector m_vPlayerOrigin; - Vector m_vPlayerVelocity; - int m_nPlayerButtons; - int m_nCurrentTick; -}; -inline void ByteSwap_replay_frame_t(replay_frame_t &swap) -{ - for (int i = 0; i < 2; i++) { - LittleFloat(&swap.m_qEyeAngles[i], &swap.m_qEyeAngles[i]); - LittleFloat(&swap.m_vPlayerOrigin[i], &swap.m_vPlayerOrigin[i]); - LittleFloat(&swap.m_vPlayerVelocity[i], &swap.m_vPlayerVelocity[i]); - } - swap.m_nPlayerButtons = LittleDWord(swap.m_nPlayerButtons); - swap.m_nCurrentTick = LittleDWord(swap.m_nCurrentTick); -} -//the replay header, stores a bunch of information about the replay as well as the run stats for that replay -struct replay_header_t -{ - char demofilestamp[9]; //should be DEMO_HEADER_ID - int demoProtoVersion; //should be DEMO_PROTOCOL_VERSION - time_t unixEpocDate; //redundant date check - char mapName[MAX_PATH]; - char playerName[MAX_PATH]; - uint64 steamID64; - float interval_per_tick; - int runTimeTicks; //Total run time in ticks - - float m_flStartSpeed, m_flEndSpeed; - int m_nStageJumps[MAX_STAGES], m_nStageStrafes[MAX_STAGES]; - float m_flStageVelocityMax[MAX_STAGES], m_flStageVelocityAvg[MAX_STAGES], - m_flStageStrafeSyncAvg[MAX_STAGES], m_flStageStrafeSync2Avg[MAX_STAGES], m_flStageEnterVelocity[MAX_STAGES]; - -}; -//byteswap for int and float members of header, swaps the endianness (byte order) in order to read correctly -inline void ByteSwap_replay_header_t(replay_header_t &swap) -{ - swap.demoProtoVersion = LittleDWord(swap.demoProtoVersion); - swap.runTimeTicks = LittleDWord(swap.runTimeTicks); - swap.unixEpocDate = LittleLong(swap.unixEpocDate); - swap.steamID64 = LittleLong(swap.steamID64); - LittleFloat(&swap.interval_per_tick, &swap.interval_per_tick); - - // --- run stats --- - LittleFloat(&swap.m_flEndSpeed, &swap.m_flEndSpeed); - LittleFloat(&swap.m_flStartSpeed, &swap.m_flStartSpeed); - for (int i = 0; i < MAX_STAGES; i++) { - LittleFloat(&swap.m_flStageVelocityMax[i], &swap.m_flStageVelocityMax[i]); - LittleFloat(&swap.m_flStageVelocityAvg[i], &swap.m_flStageVelocityAvg[i]); - LittleFloat(&swap.m_flStageStrafeSyncAvg[i], &swap.m_flStageStrafeSyncAvg[i]); - LittleFloat(&swap.m_flStageStrafeSync2Avg[i], &swap.m_flStageStrafeSync2Avg[i]); - LittleFloat(&swap.m_flStageEnterVelocity[i], &swap.m_flStageEnterVelocity[i]); - swap.m_nStageJumps[i] = LittleDWord(swap.m_nStageJumps[i]); - swap.m_nStageStrafes[i] = LittleDWord(swap.m_nStageStrafes[i]); - } -} class CMomentumReplaySystem : CAutoGameSystemPerFrame { @@ -83,7 +22,7 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame } } void BeginRecording(CBasePlayer *pPlayer); - void StopRecording(CBasePlayer *pPlayer, bool throwaway); + void StopRecording(CBasePlayer *pPlayer, bool throwaway, float delay = 1.0f); bool IsRecording(CBasePlayer *pPlayer) { return m_bIsRecording; } void WriteRecordingToFile(CUtlBuffer &buf); replay_header_t CreateHeader(); @@ -92,15 +31,18 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame replay_frame_t* ReadSingleFrame(FileHandle_t file, const char* filename); replay_header_t* ReadHeader(FileHandle_t file, const char* filename); - void StartRun(); + void StartReplay(); void EndRun(); - void LoadRun(const char* fileName); + bool LoadRun(const char* fileName); CUtlVector m_vecRunData; private: CUtlBuffer *UpdateRecordingParams(); //called every game frame after entities think and update + bool m_bIsRecording; + int m_nCurrentTick; + CMomentumPlayer *m_player; replay_frame_t m_currentFrame; @@ -108,7 +50,6 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame FileHandle_t m_fhFileHandle; CUtlBuffer *m_buf; - const static char CONTROL_CHAR = '\u001F'; //CONTROL CHAR = UNICODE 001F (37) [00011111] }; extern CMomentumReplaySystem *g_ReplaySystem; diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 338a631ce4..9981f9e9ca 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -2,7 +2,7 @@ #include "mom_replay_entity.h" -#define MODEL "models/cone.mdl" +#define MODEL "models/alyx.mdl" LINK_ENTITY_TO_CLASS(mom_replay_ghost, CMomentumReplayGhostEntity); @@ -17,6 +17,7 @@ const char* CMomentumReplayGhostEntity::GetGhostModel() void CMomentumReplayGhostEntity::Precache(void) { BaseClass::Precache(); + PrecacheModel(MODEL); m_ghostColor = COLOR_BLUE; //default color } @@ -25,61 +26,33 @@ void CMomentumReplayGhostEntity::Precache(void) //----------------------------------------------------------------------------- void CMomentumReplayGhostEntity::Spawn(void) { + BaseClass::Spawn(); Precache(); RemoveEffects(EF_NODRAW); SetModel(MODEL); - SetSolid( SOLID_NONE ); + SetSolid(SOLID_NONE); SetRenderMode(kRenderTransColor); SetRenderColor(m_ghostColor.r(), m_ghostColor.g(), m_ghostColor.b()); SetRenderColorA(75); - SetMoveType( MOVETYPE_NOCLIP ); + SetMoveType(MOVETYPE_NOCLIP); m_bIsActive = true; } void CMomentumReplayGhostEntity::StartRun() { + Spawn(); //Msg("Starting run with Rundata: %i, Step %i, Name %s, Starttime: %f, This: %i\n", RunData.size(), step, m_gName, startTime, this); m_nStartTick = gpGlobals->curtime; m_bIsActive = true; - step = 0; - SetAbsOrigin(m_entRunData[0]->m_vPlayerOrigin); + step = 1; + SetAbsOrigin(g_ReplaySystem->m_vecRunData[0]->m_vPlayerOrigin); SetNextThink(gpGlobals->curtime); } void CMomentumReplayGhostEntity::updateStep() { - const size_t numTicks = m_entRunData.Count(); - if (step < 0 || step >= numTicks) - { - currentStep = nextStep = NULL; - return; - } - currentStep = m_entRunData[step]; - int currentTick = gpGlobals->tickcount - m_nStartTick; - - //catching up to a fast ghost, you came in late - if (currentTick > currentStep->m_nCurrentTick) - { - unsigned int x = step + 1; - while (++x < numTicks) - { - if (currentTick < m_entRunData[x]->m_nCurrentTick) { - break; - } - } - step = x - 1; - } - currentStep = m_entRunData[step];//update it to the new step - currentTick = gpGlobals->tickcount - m_nStartTick;//update to new time - - if (step == (numTicks - 1)) //if it's on the last step - { - //end the run somehow - } - else - { - nextStep = m_entRunData[step + 1]; - } + currentStep = g_ReplaySystem->m_vecRunData.Element(step); + step++; } //----------------------------------------------------------------------------- // Purpose: Think function to move the ghost @@ -87,26 +60,25 @@ void CMomentumReplayGhostEntity::updateStep() void CMomentumReplayGhostEntity::Think(void) { BaseClass::Think(); - if (Q_strlen(m_pszPlayerName) != 0) - { - if (!IsEffectActive(EF_NODRAW)) - EntityText(0, m_pszPlayerName, 0); - updateStep(); - HandleGhost(); + if (step < g_ReplaySystem->m_vecRunData.Count()) { + updateStep(); + HandleGhost(); } - else - EndRun(); - + else { + EndRun(); + } + DevLog("Ghost X: %f Y: %f Z: %f\n", + currentStep->m_vPlayerOrigin.x, currentStep->m_vPlayerOrigin.y, currentStep->m_vPlayerOrigin.z); SetNextThink(gpGlobals->curtime + gpGlobals->interval_per_tick); } void CMomentumReplayGhostEntity::HandleGhost() { if (currentStep != NULL) { - if (!m_bIsActive) - { - if (!Q_strcmp(m_pszMapName, STRING(gpGlobals->mapname)) == 0) + if (!m_bIsActive) { + if (!Q_strcmp(m_pszMapName, STRING(gpGlobals->mapname)) == 0) { DispatchSpawn(this); + } } else { @@ -116,11 +88,10 @@ void CMomentumReplayGhostEntity::HandleGhost() { float angleX = currentStep->m_qEyeAngles.x; float angleY = currentStep->m_qEyeAngles.y; float angleZ = currentStep->m_qEyeAngles.z; - int t1 = currentStep->m_nCurrentTick; if (x == 0.0f) return; - + /* if (nextStep != NULL) // we have to be at least 2 ticks into the replay to interpolate { if (IsEffectActive(EF_NODRAW)) @@ -132,7 +103,6 @@ void CMomentumReplayGhostEntity::HandleGhost() { float angleX2 = nextStep->m_qEyeAngles.x; float angleY2 = nextStep->m_qEyeAngles.y; float angleZ2 = nextStep->m_qEyeAngles.z; - int t2 = nextStep->m_nCurrentTick; //interpolate position float scalar = (((gpGlobals->tickcount - m_nStartTick) - t1) / (t2 - t1)); //time difference scalar value used to interpolate @@ -140,22 +110,21 @@ void CMomentumReplayGhostEntity::HandleGhost() { float xfinal = x + (scalar * (x2 - x)); float yfinal = y + (scalar * (y2 - y)); float zfinal = z + (scalar * (z2 - z)); - SetAbsOrigin(Vector(xfinal, yfinal, (zfinal - 15.0f))); //@Tuxxi: @gocnak, why are we subtracting 15.0 here? + SetAbsOrigin(Vector(xfinal, yfinal, (zfinal - 15.0f))); //@tuxxi: @Gocnak, why are we subtracting 15.0 here? float angleXFinal = angleX + (scalar * (angleX2 - angleX)); float angleYFinal = angleY + (scalar * (angleY2 - angleY)); float angleZFinal = angleZ + (scalar * (angleZ2 - angleZ)); SetAbsAngles(QAngle(angleXFinal, angleYFinal, angleZFinal)); } - else //we cant interpolate - { - SetAbsOrigin(Vector(x, y, (z - 15.0f))); - SetAbsAngles(QAngle(angleX, angleY, angleZ)); + else { //we cant interpolate } + */ + SetAbsOrigin(Vector(x, y, z)); + SetAbsAngles(QAngle(angleX, angleY, angleZ)); } } - else - { - // END RUN + else{ + //EndRun(); } } @@ -169,11 +138,11 @@ void CMomentumReplayGhostEntity::SetGhostModel(const char * newmodel) } void CMomentumReplayGhostEntity::EndRun() { - SetNextThink(0.0f); + SetNextThink(-1); Remove(); m_bIsActive = false; } void CMomentumReplayGhostEntity::clearRunData() { - m_entRunData.RemoveAll(); + g_ReplaySystem->m_vecRunData.RemoveAll(); } \ No newline at end of file diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index 77e204bacb..6cbd752606 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -8,7 +8,7 @@ class CMomentumReplayGhostEntity : public CBaseAnimating DECLARE_DATADESC(); public: const char* GetGhostModel(); - void SetGhostModel( const char* ); + void SetGhostModel(const char* model); //Increments the steps intelligently. void updateStep(); @@ -18,20 +18,18 @@ class CMomentumReplayGhostEntity : public CBaseAnimating void clearRunData(); bool m_bIsActive; - CUtlVector m_entRunData; int m_nStartTick; + protected: - virtual void Think( void ); + virtual void Think(void); virtual void Spawn(void); virtual void Precache(void); private: - char m_pszModel[256], m_pszPlayerName[256], m_pszMapName[256]; + char m_pszModel[256], m_pszMapName[256]; replay_frame_t* currentStep; replay_frame_t* nextStep; - unsigned int step; - + int step; Color m_ghostColor; - }; diff --git a/mp/src/game/server/momentum/replayformat.h b/mp/src/game/server/momentum/replayformat.h new file mode 100644 index 0000000000..b44dec985e --- /dev/null +++ b/mp/src/game/server/momentum/replayformat.h @@ -0,0 +1,77 @@ +#ifndef REPLAYFORMAT_H +#define REPLAYFORMAT_H + +#include "cbase.h" +#include "mom_shareddefs.h" + +#define DEMO_HEADER_ID "MOMREPLAY" +#define DEMO_PROTOCOL_VERSION 2 + +//describes a single frame of a replay +struct replay_frame_t +{ + QAngle m_qEyeAngles; + Vector m_vPlayerOrigin; + int m_nPlayerButtons; + + replay_frame_t& operator=(const replay_frame_t &src) + { + if (this == &src) + return *this; + m_qEyeAngles = src.m_qEyeAngles; + m_nPlayerButtons = src.m_nPlayerButtons; + m_vPlayerOrigin = src.m_vPlayerOrigin; + + return *this; + } +}; +inline void ByteSwap_replay_frame_t(replay_frame_t &swap) +{ + for (int i = 0; i < 2; i++) { + LittleFloat(&swap.m_qEyeAngles[i], &swap.m_qEyeAngles[i]); + LittleFloat(&swap.m_vPlayerOrigin[i], &swap.m_vPlayerOrigin[i]); + } + swap.m_nPlayerButtons = LittleDWord(swap.m_nPlayerButtons); +} + +//the replay header, stores a bunch of information about the replay as well as the run stats for that replay +struct replay_header_t +{ + char demofilestamp[9]; //should be DEMO_HEADER_ID + int demoProtoVersion; //should be DEMO_PROTOCOL_VERSION + time_t unixEpocDate; //redundant date check + char mapName[MAX_PATH]; + char playerName[MAX_PATH]; + uint64 steamID64; + float interval_per_tick; + int runTimeTicks; //Total run time in ticks + + float m_flStartSpeed, m_flEndSpeed; + int m_nStageJumps[MAX_STAGES], m_nStageStrafes[MAX_STAGES]; + float m_flStageVelocityMax[MAX_STAGES], m_flStageVelocityAvg[MAX_STAGES], + m_flStageStrafeSyncAvg[MAX_STAGES], m_flStageStrafeSync2Avg[MAX_STAGES], m_flStageEnterVelocity[MAX_STAGES]; + +}; +//byteswap for int and float members of header, swaps the endianness (byte order) in order to read correctly +inline void ByteSwap_replay_header_t(replay_header_t &swap) +{ + swap.demoProtoVersion = LittleDWord(swap.demoProtoVersion); + swap.runTimeTicks = LittleDWord(swap.runTimeTicks); + swap.unixEpocDate = LittleLong(swap.unixEpocDate); + swap.steamID64 = LittleLong(swap.steamID64); + LittleFloat(&swap.interval_per_tick, &swap.interval_per_tick); + + // --- run stats --- + LittleFloat(&swap.m_flEndSpeed, &swap.m_flEndSpeed); + LittleFloat(&swap.m_flStartSpeed, &swap.m_flStartSpeed); + for (int i = 0; i < MAX_STAGES; i++) { + LittleFloat(&swap.m_flStageVelocityMax[i], &swap.m_flStageVelocityMax[i]); + LittleFloat(&swap.m_flStageVelocityAvg[i], &swap.m_flStageVelocityAvg[i]); + LittleFloat(&swap.m_flStageStrafeSyncAvg[i], &swap.m_flStageStrafeSyncAvg[i]); + LittleFloat(&swap.m_flStageStrafeSync2Avg[i], &swap.m_flStageStrafeSync2Avg[i]); + LittleFloat(&swap.m_flStageEnterVelocity[i], &swap.m_flStageEnterVelocity[i]); + swap.m_nStageJumps[i] = LittleDWord(swap.m_nStageJumps[i]); + swap.m_nStageStrafes[i] = LittleDWord(swap.m_nStageStrafes[i]); + } +} +#endif //REPLAYFORMAT_H \ No newline at end of file diff --git a/mp/src/game/server/server_momentum.vpc b/mp/src/game/server/server_momentum.vpc index 77ae36b456..c7e8ff4078 100644 --- a/mp/src/game/server/server_momentum.vpc +++ b/mp/src/game/server/server_momentum.vpc @@ -56,6 +56,7 @@ $Project "Server (Momentum)" $File "momentum\mom_replay.h" $File "momentum\mom_replay_entity.cpp" $File "momentum\mom_replay_entity.h" + $File "momentum\replayformat.h" } $File "momentum\mom_blockfix.h" From 416a974fbfa9d21235837534ca617113e7b95fc9 Mon Sep 17 00:00:00 2001 From: tuxxi Date: Tue, 26 Apr 2016 15:44:57 -0700 Subject: [PATCH 010/101] ghosts now work! + added custom model with 14 different options (bodygroups) --- .../materials/dev_nyro/dev_blue_b-02.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_blue_b-04.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_blue_b-05.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-01.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-03.vmt | 4 + .../materials/dev_nyro/dev_gray-03.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-04.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-05.vmt | 4 + .../materials/dev_nyro/dev_gray-05.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-06.vmt | 4 + .../materials/dev_nyro/dev_gray-06.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-07.vmt | 4 + .../materials/dev_nyro/dev_gray-07.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-08.vmt | 4 + .../materials/dev_nyro/dev_gray-08.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-09.vmt | 4 + .../materials/dev_nyro/dev_gray-09.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-10.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-11.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-12.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-13.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-14.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-15.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-16.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-17.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-18.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-19.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-20.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_gray-21.vmt | 4 + .../materials/dev_nyro/dev_gray-21.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_green_a-03.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_green_a-04.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_magenta_b-04.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_orange_b-04.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_purple_a-04.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_red_a-01.vmt | 4 + .../materials/dev_nyro/dev_red_a-01.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_red_a-02.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_red_a-03.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_red_a-04.vtf | Bin 0 -> 22064 bytes .../materials/dev_nyro/dev_yellow_b-04.vtf | Bin 0 -> 22064 bytes .../models/props/dev_nyro/dev_blue_b-02.vmt | 5 + .../models/props/dev_nyro/dev_blue_b-05.vmt | 5 + .../models/props/dev_nyro/dev_gray-03.vmt | 5 + .../models/props/dev_nyro/dev_gray-05.vmt | 5 + .../models/props/dev_nyro/dev_gray-07.vmt | 5 + .../models/props/dev_nyro/dev_gray-09.vmt | 5 + .../models/props/dev_nyro/dev_gray-10.vmt | 5 + .../models/props/dev_nyro/dev_gray-11.vmt | 5 + .../models/props/dev_nyro/dev_gray-13.vmt | 5 + .../models/props/dev_nyro/dev_gray-15.vmt | 5 + .../models/props/dev_nyro/dev_gray-17.vmt | 5 + .../models/props/dev_nyro/dev_gray-19.vmt | 5 + .../models/props/dev_nyro/dev_gray-21.vmt | 5 + .../models/props/dev_nyro/dev_green_a-03.vmt | 5 + .../models/props/dev_nyro/dev_orange_b-04.vmt | 5 + .../models/props/dev_nyro/dev_wireframe.vmt | 6 + .../models/props/dev_nyro/glow_blue_b-04.vmt | 6 + .../models/props/dev_nyro/glow_gray-01.vmt | 6 + .../models/props/dev_nyro/glow_gray-03.vmt | 6 + .../models/props/dev_nyro/glow_gray-04.vmt | 6 + .../models/props/dev_nyro/glow_gray-06.vmt | 6 + .../models/props/dev_nyro/glow_gray-07.vmt | 6 + .../models/props/dev_nyro/glow_gray-09.vmt | 6 + .../models/props/dev_nyro/glow_gray-10.vmt | 6 + .../models/props/dev_nyro/glow_gray-12.vmt | 6 + .../models/props/dev_nyro/glow_gray-13.vmt | 6 + .../models/props/dev_nyro/glow_gray-15.vmt | 6 + .../models/props/dev_nyro/glow_gray-16.vmt | 6 + .../models/props/dev_nyro/glow_gray-18.vmt | 6 + .../models/props/dev_nyro/glow_gray-20.vmt | 6 + .../models/props/dev_nyro/glow_gray-21.vmt | 6 + .../models/props/dev_nyro/glow_green_a-04.vmt | 6 + .../props/dev_nyro/glow_magenta_b-04.vmt | 6 + .../props/dev_nyro/glow_orange_b-04.vmt | 6 + .../props/dev_nyro/glow_purple_a-04.vmt | 6 + .../models/props/dev_nyro/glow_red_a-04.vmt | 6 + .../props/dev_nyro/glow_yellow_b-04.vmt | 6 + .../models/player/player_shape_base.dx80.vtx | Bin 0 -> 54371 bytes .../models/player/player_shape_base.dx90.vtx | Bin 0 -> 54371 bytes .../models/player/player_shape_base.mdl | Bin 0 -> 22528 bytes .../models/player/player_shape_base.sw.vtx | Bin 0 -> 54251 bytes .../models/player/player_shape_base.vvd | Bin 0 -> 280000 bytes mp/src/game/server/momentum/mom_replay.cpp | 7 +- mp/src/game/server/momentum/mom_replay.h | 2 +- .../server/momentum/mom_replay_entity.cpp | 125 ++++++++++-------- .../game/server/momentum/mom_replay_entity.h | 30 ++++- mp/src/game/shared/momentum/mom_shareddefs.h | 2 +- 88 files changed, 341 insertions(+), 64 deletions(-) create mode 100644 mp/game/momentum/materials/dev_nyro/dev_blue_b-02.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_blue_b-04.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_blue_b-05.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-01.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-03.vmt create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-03.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-04.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-05.vmt create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-05.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-06.vmt create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-06.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-07.vmt create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-07.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-08.vmt create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-08.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-09.vmt create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-09.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-10.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-11.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-12.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-13.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-14.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-15.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-16.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-17.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-18.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-19.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-20.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-21.vmt create mode 100644 mp/game/momentum/materials/dev_nyro/dev_gray-21.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_green_a-03.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_green_a-04.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_magenta_b-04.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_orange_b-04.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_purple_a-04.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_red_a-01.vmt create mode 100644 mp/game/momentum/materials/dev_nyro/dev_red_a-01.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_red_a-02.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_red_a-03.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_red_a-04.vtf create mode 100644 mp/game/momentum/materials/dev_nyro/dev_yellow_b-04.vtf create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_blue_b-02.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_blue_b-05.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_gray-03.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_gray-05.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_gray-07.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_gray-09.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_gray-10.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_gray-11.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_gray-13.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_gray-15.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_gray-17.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_gray-19.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_gray-21.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_green_a-03.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_orange_b-04.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/dev_wireframe.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_blue_b-04.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_gray-01.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_gray-03.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_gray-04.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_gray-06.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_gray-07.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_gray-09.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_gray-10.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_gray-12.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_gray-13.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_gray-15.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_gray-16.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_gray-18.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_gray-20.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_gray-21.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_green_a-04.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_magenta_b-04.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_orange_b-04.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_purple_a-04.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_red_a-04.vmt create mode 100644 mp/game/momentum/materials/models/props/dev_nyro/glow_yellow_b-04.vmt create mode 100644 mp/game/momentum/models/player/player_shape_base.dx80.vtx create mode 100644 mp/game/momentum/models/player/player_shape_base.dx90.vtx create mode 100644 mp/game/momentum/models/player/player_shape_base.mdl create mode 100644 mp/game/momentum/models/player/player_shape_base.sw.vtx create mode 100644 mp/game/momentum/models/player/player_shape_base.vvd diff --git a/mp/game/momentum/materials/dev_nyro/dev_blue_b-02.vtf b/mp/game/momentum/materials/dev_nyro/dev_blue_b-02.vtf new file mode 100644 index 0000000000000000000000000000000000000000..3a7906b753b92400f02bb5592a49d12509abf5a4 GIT binary patch literal 22064 zcmeHNJ8s)R5M8f|xC!DeE>ovdnZmKF^l~Wa9%SJng<0hQp^-BPf|>wIom|2xa{v~u z1P1{GGBc!xyGtsf3Ce;%zBT-L-tX+p&U?G|#QUILD5Xy5^NK!0`cQ9jOfV~-e}BFB z_ILh1N8#lw`r-5?eK|WjiS)Hr>-G8ze^28$(!QQ~o)_s6;WXvnFBXfnuRrQJ6>jfh zU#rPvl8rSkNg_!oa{eqV?U*e2Mc8<*N ztv_22?Vr?tXFpi;bNkqt-`C_OPug$hFZPq$Q}<$j0veC~C++WPJ+sN0e%76<_P0RHQx6->koDKd-l0|7V%s@gK!UvHzQRvG9oP zclMvwU&MR0eHL$4{|sN+`*&*jpZC-D|279{8{_lxtpXU<=St@Hmp*u>BIU`RZ3;Q6iiWBZz&|K0mpzyBkBMMeB| zJ}Bq!w&OSJ5BuSN#J|Zu_2;+Q`KpeG^M8c-&(dGShl&+Te*XxVANPufj(&Sh{72{S zVDm?M$ol_>*vI^Bmn4zJHke+P>%8HeP7mpTK|MKky&;Px4`v zuiNr{xj*>-2>HLk{k!bXW|;Xmj^pC~&-_Hsch>t&v+>%->$^cf_kZ(#m|=?_dx3jy zKbddo&%?%F+xTtUpW_?$!~cil|KmDeh<}`a}p!|GT&_B>W&_B>W&_B>W&_B>Wb$X#| e_0T`iKhQtWKhQtWKhQtWKV8!|b@udC|NH|t)WQY; literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_blue_b-04.vtf b/mp/game/momentum/materials/dev_nyro/dev_blue_b-04.vtf new file mode 100644 index 0000000000000000000000000000000000000000..179a51be380e3e237fec0dea3f448f3ec82a7d9b GIT binary patch literal 22064 zcmeHNyKWjm6deRoQaFkWmywB-lqp@MFlmx609rSxXrt82Po#E>_9ys?v~g=8q%??X zwVQjEo#D=Uga}^mti-_r>+IZ_J9D3BjHlyo=9Mv~C+{!vp39GUzrzB}mqohC(5;!T#7xY*%5&$riuyg!@GCL@sp9uEHW z(==T|&d7W`$NbwXS#P@C;`w|(KZpEkwMy|k%d#Bn$^5h^uqR{gE{eX0%PWjW<@jGW zJ{%N%vtF#NJjP{TxyG`%lgIcYhO6~~ac+&Ve~lMn2aW6N!47sD`;qf`^W~fyJq~+W z4E?a1hZC7@&wnNR6FXWAdDv59ecnpHvnS+XXDg3!&))B_{&Z@`%l%CJ_01EUBv$?o z^T*?{#YfHm?XSO)mz`hVUkHnR#lTg3h<;J;^oQj2dF$iYZwK_(_wW2$?bpL{e9!)Y z^Lz4TdR6wS)>~@(8RdWY(@ZAf@x6Ty;row%{2ViXuGa5}{bR?Ut1tEV3}NxV%Wv3Q z{O|HNB!4LW??3teOKSdqX}pAfmA@~oFSLFjf1G{)*Ph47OaGkN{94{TuYMh)?~wmt z51#+@{&9-^>3_yQ^MA)LI$Hl6|Eco3LHvOGf#YxJe}VejpuUpt*Vp^y;!pHz{s(_( zzSlU0KE?lrUw!`3xW4*%M8D>r?0>=dBmeR5OO4ut{h$5c)nD@eQ2%Q*-#U2zap$Z0 z|H=3FQ|=$y9uGIefl>GG6081v7WmB1AK;%ZKA#(3U7wHm=K0(G`-iSSvEzgOXZ$n& zng5oL$F4_h>u3FA{bT)O{bT*xeIL;E5ldp%F|hsvHRezbq~VKP5f^Sg9@ zd*bgwdCK3cmZ$SI;*0%hp7(t`3C#Ssy5#sAjT-A2@ng&Phq)X_5zW};yC?oXb^QG` zf9-g!;y+dXV}FKWsQquxe+>5{I?u4qKduMn`5y+?^&`Hd`g3gkM*U&?+SHH7{A-W% z`F){>I=}dH#^`yb`s z@$^&wA^vx(`w!_i`5*TqVem8OnDs?<|Ie?R{L#6-J@%OV6a5eU5B(4QPt`;8|K|5o zuKnRD|M}|4{!>|8~Dea9v-0Uk~*k^&j;g^&j;g z^`AZ9d%tqMKlDHJKlDHJKlDHJKlDHJf7krN_w|_n;Q#-1|114*X1{*`|EbU4_k4A| zKiq$C|3UsE|8f7p{Rj6S+<#p23*XlR{{jC2{{jC2{{jC2{{jE;ov*mwANUXW5BLxG W5BLxG5BLxGk86J6`+DcffBpa_nC}h% literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_gray-01.vtf b/mp/game/momentum/materials/dev_nyro/dev_gray-01.vtf new file mode 100644 index 0000000000000000000000000000000000000000..80bc86de8a243410408f5a696d384dc14e2aa261 GIT binary patch literal 22064 zcmeHNJ5Iwu5Zy>fq(P*EGU>Pi6{O(=_PU0KBhW@11)3=07953+f+G;-8BM&|7!rH2 z){CDbCwkhQ{mjh1c}`-p8*^-o8OePu_d;G~`i%vei!*zvH?Z`$I+geFiM&lFqxsmF z&!Q-1^0~_Myu{X8#rPCxF}^gH?LJ1+nzQ@U~KM-IYAM5!I-%9(^X4#?r>&DCEQhR@W`!MDYvB!Es?0Yi*CO`K1 z%Wuc}$4-6$yYa5W3nxFGB!6CtDnIM``{TLx^Z()h*7^HK`)?h8#LwlIJHc+ey2j7v zGQV^FyPmIZ{E+kSpQonyRrViqe)uEi{M>(B|1;InQf9v|)wBGprQGfA( zqWT;8=+^%(u&e)$X50Fsi~0QhxbycU{;l<+AJ3+k`;W^%$oH7~i}8=^f5tzq|9g`E zoBG?7zeD@Kq23DtY5gy;^Zm{s@WA&Uv;KtUZ>T-;oBOZMf2coGvuFI7|2+RZ|Ehmd z8nu5N3b^%~DjOMi+1fj~oDqL*JmQ0tAL%qJ#SeMdl6( zkittBfm0#%P9mujH7HChH-bM0w(*5`B=2}XAC)AF&+4^O>V)2(=)IyJHK{Q{_3h`M z5BANNBtE{S&(k;b5k)7j-fiP`qBVbSmT6k!Jg@L=S@O5mI=P>0<1|f|jPpFNa9NhO zj8BdJiu9+1?R=|7zcBjMe57A|y-BW0est^B|+{I=2%gOV)#btSE^((9kE%Am% zCH}387Uq4*akgLLb&}^2Z^#~r*)JAb|5(iXVLw^SyttCeAd4foAurJ`An$|z3lkeEQ{8KOeoPYk;o@ZaY`uT0n-{o(ges?^J)I#Rp zbu0hRXZyyqo`|2TIn|$ou21%e`os2x#9!Bc?Rwfbem|-5mlC+n->doa>Q~=-{o}A7 z=P&a=ES~%0t34m$ANf!G|4{!>|LVWThStxq%cK6I{-ge*{-ge*{<8p3kBC$NdNQALKvsANL>J ze{lc7{U?+^j9ni52mA;82mA;82mA;82mEL3d?s}N;6LC$;6LC$;6LC$;6LC$q5NU& J^2f@5{sDu<VNS;~((-(i93%eT+3 z%?DU!ym=+xr!VE}?CfMVxfrW;Rq1IYvuVj7r!C<|<_`}EWi^Dtr7z}y%$zaICUj}0w zehYB!>^FY&$Kg+du^#;DWBAwLZhyP}{q$V<{(fkEHFxW4{Q^81tK5xWUycI*?9Uhb z0(rqdoIK{Qc_G7_u>!srPPnupO0aj-$%K8`;WPQtRL;?`5!a> z82`-wXITF-Q;$2oB^@x}Ur&m~e!Cv}pXA?B_ZR;6)!2vql{}{fShPDVcKv_yA>@x* zp9Amj^H26a_CNMN$Nyt~_|%yFpZ)(C_WwVA{!Yz@F8=NFd)NQ}`T0FnpVN1))yRvj4OHGyj?YeE-4sAAJA8_aCWzqHpr#Kjc5;Kjc5;Kjc5; lKjc4s<1eZDlK+tZkpGbXkpGbXkpGbXr1FWr$q$wP`~rGNB`yE} literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_gray-05.vmt b/mp/game/momentum/materials/dev_nyro/dev_gray-05.vmt new file mode 100644 index 0000000000..f9ed31da98 --- /dev/null +++ b/mp/game/momentum/materials/dev_nyro/dev_gray-05.vmt @@ -0,0 +1,4 @@ +"LightmappedGeneric" +{ +"$basetexture" "dev_nyro\dev_gray-05" +} diff --git a/mp/game/momentum/materials/dev_nyro/dev_gray-05.vtf b/mp/game/momentum/materials/dev_nyro/dev_gray-05.vtf new file mode 100644 index 0000000000000000000000000000000000000000..cb85ed6a610108ba88372002270e47e3a7717978 GIT binary patch literal 22064 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 literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_gray-09.vmt b/mp/game/momentum/materials/dev_nyro/dev_gray-09.vmt new file mode 100644 index 0000000000..4a56be738e --- /dev/null +++ b/mp/game/momentum/materials/dev_nyro/dev_gray-09.vmt @@ -0,0 +1,4 @@ +"LightmappedGeneric" +{ +"$basetexture" "dev_nyro\dev_gray-09" +} diff --git a/mp/game/momentum/materials/dev_nyro/dev_gray-09.vtf b/mp/game/momentum/materials/dev_nyro/dev_gray-09.vtf new file mode 100644 index 0000000000000000000000000000000000000000..7d208b3798d145de6e6f88f2a958f5b41b6d44b0 GIT binary patch literal 22064 zcmeHNJ8s)R5M3cf0$53O1RuafPLQNZr%qCnbgCl7l}i^?VFa$+xk?ToNQ6#cz$XY? z1nN@;f&mv1SP`V(P}=2krGySd3Jd7bkcj8qncey9%p>hy=Yx8#lxpDf9-k3@)bR=f zs?X0}z5R;M03X4a@$NOgU%bRutJV0@Kk1L7Bxw&%`jc)DoZ>!S#&L${X)5<9ih6Pn zf}ktoNs>f@(=?q6R*;wbBuLZK;n7DGhoR^lVS>zqe5N1rsr){UO{UXn0Z(Q#jYZGp?9$8^tm6CF9#=QvO##REV~u5hzoP^@TCFEo?AAEb7c3SU zXXEgL#@YC7zF)y%7%uJc#bN;um`Hnv1la`nHOI#Ye`*U#{ z^9Q)}d|Cg0butZ#ed4muMAu9 z-^9(n3i9~folAc9dIf#|lt=!~b9qnvZ~W)xhl|C(HRPYEFYbCSUaxP?_d5UodcIwG z7tfGC$~wOpw&GV0&g;j+d9y#>_xe`;iG91&57(b%xIJtZ^5+5Pr-MtMpECKX$7w&~ z&-{Nn|Je2X@%(#P|4jXr_!Rk9^%(pC9d^aiqIupX4{~XZ-io|9v|T%zvJL z)<4!i*1y&JIN$1X-TPtvXZ>gWXZ>gWXZ;rs)ZK4<)6f3L{>T2u{>T2u{>T2u{_h(< zse3=`N8Zu-^JrvD97_`DK2;e_x zr~ZTxAd8_%ka{PJ6lJ+(3R?zIJqNP!eB>Q@$Gdw{9E{$Gr$UGmx?a;2(VsZoV1nZP z&5M`%VoW1mJ*W4{*+RZd>#ft z@NOR7GL}4kK=b+gWIP@}l=||Vzn4W(upZJb?eRIi=B%GyMZs$l;`6N#a=BbKuvAK8 z70$yeb!Emg7TJAf%f(%M*TC8RLgODaev#(6#=PD|kfs{5T#jcmjW_c3TH{jwi#AlP zmvNe=fyRn(k|d4!>-D-Jr|quvc-B9#>@$um`w!*%0yCfGlKiXZSN6?*D*MJiJf7lY z=8xmkC$_(L=@+D3>*x7xT>Gb-KN^kxK7LD|{xSaDUf(w6{e7gDw#W9#ys^JM z-i1A}Z`RNIqxhKkw($kalY0NtNtR{Cf9>%*_1)C-DZbYEV1Ekhe6=yvhgluZvrEpO z`h2y=xAjGqeoy>w{O39^9i2Z@zaDAdJRVlU+Ry`pDwuyFStLL{C#`9U;T2~bIGxP#2@Fsu79q4*;kJI_p<(3{Hx?w zhW~QBh;JPX&%^T%Q52c_&;5_`?`Za`{j4Horgctv+|X9@Kx- zf7E}}f7E}}fA&Dv{l+)_=zr*c=zr*c=zr*c=zr+{zVVZ;*8~0o{saEgZ~lY-|JU*7 zdT9Utu@4yeU&p`ee(Rfl^ndh!%or literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_gray-11.vtf b/mp/game/momentum/materials/dev_nyro/dev_gray-11.vtf new file mode 100644 index 0000000000000000000000000000000000000000..c64ae3f5cc7956a5709911088370c36d6c2aa457 GIT binary patch literal 22064 zcmeHNv5wO~5Z$Y|1|^>WqM_sqfad6toO}Tl9i;S0PLbk@t4>EpNnveF3VM`3xD<$= zh$3ARd`)EYW}UUy%b~zIiDKnBa;%r#nVs3$oq687e(**;Qc9iRevNyGk9tyIKs9*w z?xoxxaSKL|R~>wxKE_wKd(t_dg(`{T?&vnuy(oHtXO?D}!9Vj{o@tu)!a*&6+9KbJsywq z+`)P>u~_#KX{x>xDHA4iefKMt0qfA{zI^kF@@@>mz;_`ZEUUi*g$uI6Wn z%VNL(CM@{Q= z+aNz={A>FU>+i4o(_g>-{+9S5{{JlR>-%sy3sq3-?xwc?|1$gf3AP-|AWhaK7T?5EcoS`k8Kgp zgrArE?W)NCdmP{J%TLxnsXs3NGZ^01+P8?sUxoKetAF{W&L4L@zUX)PC+i>U-#+;l z>p$y1>;J*k|ML9Ke?QtbUhn_%{a@-4^3Q|c-<#U^<8ybUf}rFd2IKtJ*5{+ZNA5q| zFXP{IJ+)0<);IlU{B6E#+xl&4FV{c!Kl2~+AM;;v-q$prwoRV-pZTBppZTBppZQ;A zpl$W5slBX!tbeS3tbeS3tbeS3tp829b^&{_;Kg>U;f!#K zx7#HGbI!B6fBDNZ!XC*>5;1Nr$Mb4@DQ~mcl$gfzq7ZX(oR^>P-s9`_TEjd|Ydm99 zc5#03NyXtf8EZI7A`Op|MB;#bd=oeN#C(5@;#h4j9N(8%v`31(fE9B&-x&*79LFQs zf69E{cO;%HqA1eZ$=tcx{zTJ1S>&32kMxg}@d3-P>TlLdtPkl|>m;Ry6zg(YYea-oqxUx^#A8$eG8~tYgeOSzg z)~7f9Vm&EL8vVjP+8@P#bo`Fmvn*@&ce(Ofjj!Y@{zq}D`4?e|L%E+i7W(x4|8}PP zkKVuJ0YhG{;eOY@@BOdzH~h5UU(NU~{lDRdedEpcq*&s{T+rf25~+{bv^odqeGf^Zc1!KibtdV^`&= z^(XQl@*naa@?Xt|w)4Fy5BYzP`Twua-{yX{#nt|c5~KWx{6y!!YoFiS>NoqB`BAw) z`(3`g$$DTA`VQRwKJOn)f9x7p@z>PfUF+{#AMEe<{tf%DzmMvAJ>dWFf8@XGzd!9d zUTy0`{3HGm|A_zkJZ_s`hAt2M2mS;9f&ajNm-n$l=WE;Z1OJ2n!T;cY@IUxptbpzO zGIV*!f5?A_p8rBxfSUhBJpA|X`-$f?dk@_HL-&8!FXMmc`P%mUkpGeY5&ww)x*o92 zFGH6H{saGk|GL1iUsDDuZp#DMqgZjsIeKB--sDDuZ Kp#C|$`sWwAK#Vy6 literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_gray-13.vtf b/mp/game/momentum/materials/dev_nyro/dev_gray-13.vtf new file mode 100644 index 0000000000000000000000000000000000000000..61f1149108e51a6cd404addf081351b469cb92fa GIT binary patch literal 22064 zcmeHNJ&)5s5MAQ}8bpNXu0eO5tNa3viXI8mFO%o|D(|}yM$>k{QNLQpiiSQ4Q zF1i+aT2~a7nMu5B?_Q!@&L3Hkms4_jTF=hRdgkr(CLce%6HkQ@4!z&fdq{uc^%@ft zpT1wck#ELy#rX^ReD<6^dOhbh{2FGn*=!ko8RtootZ^Jm%>QYa#^>Q;u~?1AalFEL zp8w(N2Q<$A@5p``U5p2_t@gQ`?>3x=WKS=SNr;o*r@e&c(QtBoEiwDQxx7iLICsxn z4UeWH4ZCM*KDN(BQ-#xLop<$mUwEFx>+v|2nC<;xqWPapCQfzS^E@Fj+w(d}%=WZA zLy38u*R6_q{Uqjf{QJb!^4s8Fi;w(Q?B^lHr^ZiyoxY^$)p$ztXYO<={c$|LHogIS zr9H=&xsJ;JLH5S{v_4zoGvnp;qx?J>FZ*}sC9!$^E?nH7L;Ih|_>1~b^<(0c@~8eY z^VjNwmUpXtF`ueGUcZ4`t*?u*PkmU%b06PnS#^HuFFS^q=d|9$)%Ipy&kB4)IavW$A5p?b-y<45B-n+NB^V$yE^}Bn!oDK z2mS;9f&ajN;6MKLDgFMm<@0ab<_G_S|H1#@fABx}ANwEnf7|p)+wnmE(C_U?07(Al z_4x0BaiquI#nJSp>QdnV;?ySoXfRu_a zu>1>rK%534V`lbzceAUA#L3z|<=#bS>3L^ocV=ha%w7C`=Z(6rl;Na}^w8XL5 zG+XMqD)A2CsgURKVbqPvamL@HNZ?8GG>WSEIxp-l<#k@zUt()#)a}-I)9_YH%<^nU zjbk$&kI!mc_^HOR(OU19nDze=`?LEY|CHm5UHc}(W)r+x2ycSw9U6``OEK z|D$BuJvou`(mtLqG4DtBeS_yz2GJ z`LQ|b|90}jmG|c}9y|JNe75%dZy0|fzis?q+kbxi@8w5*ng7N4y7{eDXk5pSU0?9W zO4dJc?V@I&U-P*eABg|3{y_dWlK-myiO)y& zU(3M6|5g9Q^^f>>rR$S@RsYB9yZztpq1*q3#dbZ>Kk9t@(^Nda#3t-|m+6!JC+Z*S zKl&f^Kj?poJ|}d)=j&@||C6rx{KfOTeg3=g^Skfg#WB@e=l$98>+drAi_7Z$mFu{Pd;CT z#^68jANUXa2mWIZfd50+m!|7O{X_jj{X_jLcnbXAbiNL~Kkz^JAN&vgZ!iDj|Nk=o z+xPni@V`9&(EUZz^`ZYk{3HI+|FpONLH;BEk^jhl_5j}h6>-p>d_ez;`iJ`0o_zQD o4f&7!NB$%K3tm9~=kw!T$KZePKlmT~5B_Hlp#I(UdRN%@56H`*i2wiq literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_gray-15.vtf b/mp/game/momentum/materials/dev_nyro/dev_gray-15.vtf new file mode 100644 index 0000000000000000000000000000000000000000..d657046a761c1232bbccd3e64837420ae86af2da GIT binary patch literal 22064 zcmeHMy^hmB5Vqq28pIV8S5l`AEurEGvUEH~`g;aMQl*I^7q;c=)<{T}k>Cwb$OF(I zD6fvp%*N|oFF`pg_MN4CJ|(B`c4uaPX6O6X_ipyqd*XSXPoFpRxuGBLDFikRCOb31ic{Yzy( z3)x<#uhM<6*>Jx?wIA&5{$#G=v3i5^{I7yNQj&E-@=J;{^Iks^U$4zY6bN|w5 z&y%Flf4?#DRR3|)-+moMc0AV~h1;0+rKI&48$3^=C|Ya0l#<5Z^e>$Fq7h%Hb=3Ae zzO7?V@$2CyJ?_+0!c zUvT~vx_>5TVH&O1fvaIvd#hF~n>2zA3mumj^wy*sCHtRPD-_t=f zcosJI?-;iC{{`)TJNqs6zuw;}|LXeU=BJCR{G${()XPi?&fou@Z`a<%yZmm&bz1xT zAt#Of?;3Nx5uV8(72h}Kzw3BM{aN|P?QPD#yMAr)E?>$8J$k(}dAIWE(YUR@bNgfd z*ni}|sXynQr&IPg|HG<(%tw^}b^Yc25kl1b+y|!eKi2i0`R^g)SNiXZ9bZ`d&wM1* zJxuG1ZZXf-=KoB7_}J8s-NrjMpRF}N@E`aO{0IKi_3#vbpW6=p9~S>t_5a@Y?{oK` z`Ip|`Soe2z|Eu4>oa?`>{Zf8k3G4o;u*HuTfiHaikNN(gZN7HhpYt2@f4uikUGHaG zf7pNIKh8hS|3m5jw)Ls+{-}Sbf2e<`f2eNX{R902{R902 T{R902{nNMJ7@Ge})jxj$2Qm7^ literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_gray-16.vtf b/mp/game/momentum/materials/dev_nyro/dev_gray-16.vtf new file mode 100644 index 0000000000000000000000000000000000000000..de4626c35981d4282cc0dae5b5e62e738c6a2733 GIT binary patch literal 22064 zcmeHMy^hmB5Vm6pt~kU=2+1XNYKrTED0l))$3sxi-%I2wnkb@hESKxf3JrdPvuN%G zXpwM%EHh(gy+4UCu(>FfKS#FpX?J#Kc4lY3b>6*w<308~&!_tp-8b~}UUZnC_w2)? z#dEs(&6s*Dp3?K;2|cc^{3k8(S3Ap z;JU6ACjDsKKVjBOZ?`e)YqrO+j_r1~L$y~quaCm5-F7>5Y0af|D#d$4z7%)H{F$iN z`uN>?>hcHX*T(PGS@v(5i`O)~W*?0otk<%g^@)u319)!9`>WMI$y?)dTuFIbKO3|E zw{*$=VUdXyT@u^p!;mGxj%yf1h85;@xf!i(3&H`%e>Q*elzuIrIvT#F z{J&f-d;Y}rkMi%y=HKzlFVa4q-{gOUjrxCF+|K8z`X3fQKGoWJFJyflaDE=&Qm(}KS#db^N$wOr%A~VCV?kz zKbhbA-!-1bCeQH=`{93?@5UCtuKFVWvHy|(kpGbXy8q){^Xb^+k^hnZk^hnZk^hnZ zc?HH+zg+c2{X_jj{X_jj{X_jj{X_kCQV< e^#%U{{{jC2{{jC2{{jC2|8eCLW0RjM|M?B+AQYtl literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_gray-17.vtf b/mp/game/momentum/materials/dev_nyro/dev_gray-17.vtf new file mode 100644 index 0000000000000000000000000000000000000000..a13265ffb2701870492bb70b6ae4e69d143bfb5d GIT binary patch literal 22064 zcmeHMv2NQi5S8GeU5aL_fi9hmd_X#>Q@@}pthxP#pm7)HC)~lCtH~0Y%^34`ZY7(=o?M_4l$Yf4?rq`E)uB#dw;= ze>R`)7+oAQvKPNc{P!>~2XV}CM)us#k}m!JG))tU8RKj6JjTvHBs5^)3>0&+@HNZ8-L!8b9%m%p;f+2iecWy-uB1W+s6Jt^ZRl?+&`6*%0C-Vil_*Jfa=89 z|Gn*Z{(kEAA=RmYjd%~Ocyg@A|Hrx<3+<}X)&;6Lym_z(OC{;STrUHP-L^C!@^{^hz}^gr}J z^gr}J^gr}J^gr}}S3S`;d#-=*|NpxGiRVE?Ap5~7VCbJZ{(Y-o*Y)E42k$?qf7HM1 zcdmTWH+%3O_z(OC{saHv{0HYhIRELJesNtd^bhn8^bhn8^bhn8^bhoptDfkaJ@gOs M5A+Z8&#CF3zvfpTHvj+t literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_gray-18.vtf b/mp/game/momentum/materials/dev_nyro/dev_gray-18.vtf new file mode 100644 index 0000000000000000000000000000000000000000..8894bb3d792f08c473d333208a6bc0295b921fe5 GIT binary patch literal 22064 zcmeHMyN=U96tyuDG>9!zBGIL!q6-y05=QVL6zTQ@_<>F5brlC~M}Z?0s+8_B1(8fo zLrD=u=3FQCJXjQ2b~bC}c;!SVGj|^M&YXK@_TAgp-XqWRe7vvlUgFPtw!?tl!{;wX z)hie^Mo;ni_z6BPF8owoWknDK5&HAv%?`3;QQJ_;iMe*iSGMmjToZQ@0c%7%JnE#xc zcx%*`^6hjwHOoV%d^Mfc?MwYIa_SG0dOYiVqPBh%S?$3ACPsI4`9{W96boHGQBf56 z8m9{8hW$i^@RKeFhW|7kXW~bVCu#+MYMd(cZ_U?jAN|+!-Twag*)IRL{m?Vpm+@%4 zfj`2biD7T~&#o{2v+c1?ocUvXwg24lxmf(Wz@z6s&YIY}?aBC>*mvE(`16+aQ^$9| zo-B;%*Xv`GkF%!sSf9Tt|A~DV>;1MCY{c8eWql+5>-;75c@R|Xx%F?u_WC`o|4$!( z@T|d4?)r4`e*GbYJH8bEV*UPif4lZB-sB&$(AWE~#76v&gEhZx^1;&N*W>hO@)hQL zY2zmQ-}PTpEb$xR(eWEBeLNLz+FslG;&0Ahum86GJY>)KbNzGw-#h-R`FF3ka1Pn! z*JEJB-)MiZ1pnS?{}2z~_521)`}_f1p2I4A(JhvIZ=Y|}7h6Ae+uyqEPtKKkJ}YB> z%zw=P)IZce6+ax(Z*9w||L&#!tMAX+#`lo@KK{n}y*&Tb_a}$!yDtBs{(K9K^Jj^T z{CF1l%;$f~`-iUAbJO-@eRKY&yMJmLudeGe{#^gu|J?s~rT@F;ueR-x|HyyjKk^^> zZ+Ac3mQVX;PyQ$WlmE&8WRMDi~iyN z|8@Q={Q)AV^1(S^>K`5dzU|+(=O6BW?*A&^wdIq(*^~dsf8;;%ANh~>KfM3p s{ZHTYOWW~M|4{!>|4{!>|4{!>|4{$5)f0WQr~aY-q5h%%IXC_D7r>(PcmMzZ literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_gray-19.vtf b/mp/game/momentum/materials/dev_nyro/dev_gray-19.vtf new file mode 100644 index 0000000000000000000000000000000000000000..4076b28419d6e2891fcd3fdbe327583cf0dd45da GIT binary patch literal 22064 zcmeHNJ#Q015Z&_?&>&JG#RW<_{{>D86$LemgcSb){sCQjYLGypuu}Ofr3-~A(owqe z6}EIKSE6wZmUYZrytlhUz;Sfg*7;p@wqAB;XFq1%zPv5353dKU zxaH$-t(J%9`CtW3%YKyIQHi1YaqP7}&t*9^=e6H9H{)>jWbWDjSzZM8^PH!`^?#Vj z{$2&;^ZX9}tNsoh4>G9(&`T2JAB6}_B3-&_;bRxZE)5AJkq|GTqKqs6BEpM~S=(PjH1=cjJW`TKz`>n}0yM(glj{r#C_7(dqE z#$(;_CEG*%BmZ}h|0?~-@u{`0^jF>Zi_mYp{}KOg%>QycZ*9J_e;uEnTg>}WsPF2< zr9b8N|5NkidwBk+*WYd9@%1O`8|;7B|7`rr{i<%v`L~1o2mQM|Pu0ENvOTQ-PR4(K z!}+hRzvcPw-sg82U+dz;ycucl`CEzUxq|B-l{X#l`S`c_{9)aAt-C(QJH~(a=kImT z|F+x1`Xm04|Hyx1OI{lz<=OBew)tc-thM^O~>GW@IUw;{15&I|6~8d z{@--HBgY5*1N{U21N{U21O2nF`Umg-9sjW&X#DRTD9|n6LH|JiK>xV>m*<0~W8^>b zANh~`w|b!Id?&{T{saGk|G8nu5T@XvU5aM!(m9XN8gQpRLDVVU`WOw6m+(-a7GQX?4CDb))a?^=bA|>` zMgd(kawr7$D2u-`qA0N`BmQK_!3Q4iNZ#@8Bb`rYAK44WSVZp!dN1k2-fl2K_HOxk zq+W?hV)UB6U%sNRlapv#oabV>TvmV6G~I@&ncBbP)LgYod{KPU*zN!RuAIC2e0duS zA;-j+5Z5;;H8?oEx>9(>FADLUKDE5Wr6vCu94{6NTVG7`b8t&LHS7Kf|53>GvHQF2 z(?mJ^pR@^wh}9{s zqx$>f^YMyLi=Xr;`4u1LUHUimjf*b*_hdgZ^O+Vx2rGV?n5OTnKaGFTr9bKaYvhOd z*zx}`cm3bQYi9hP@HcnmU|`vA%KQ4(@W18XD*kl-@Z;0Rb^MVFH~F%Tf1MwE`8F*3 z$C2586?XQ2yFY#Zwqp}N8UL4+q3t{I_q6=cw!VJ5^xudc<-dgAW)VFK`||t7PtBiQ z$A|gD|A>F=f0geJB>(CBEAx{qu4;U{#u-oeWHQnGP1Zl+Z^7)>^ZBLbC+)`;|C9cR zt8>_fU+fxJ_ALIm;$MH;{BhUzw&`12^F#du|DpXiQ~RMSpXn+O{saC4{&R5r=fUUi zuKV4WulK(`zsveW$3K04;>+(FC;ZP%Y@I(VY}Lo3z(+oRn!e_r`;NCm(=X#2^N0UK z@jt}B`}w*1`{D=IH{Ez&DwOaAL<|K zAL<|KAL<|KAL@VKd?d7e@E`D>W9L8k{on9EnGf9me|-M<*yA7bH}N03{`PGT^&j;g z`ycxs_aC_b!2JjAKlc{{jC2{{jC2{{jC2 N{{jE$n~#LD{~xXW)@T3# literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_gray-21.vmt b/mp/game/momentum/materials/dev_nyro/dev_gray-21.vmt new file mode 100644 index 0000000000..45b09ea3a2 --- /dev/null +++ b/mp/game/momentum/materials/dev_nyro/dev_gray-21.vmt @@ -0,0 +1,4 @@ +"LightmappedGeneric" +{ +"$basetexture" "dev_nyro\dev_gray-21" +} diff --git a/mp/game/momentum/materials/dev_nyro/dev_gray-21.vtf b/mp/game/momentum/materials/dev_nyro/dev_gray-21.vtf new file mode 100644 index 0000000000000000000000000000000000000000..6b47e257d716b6ce0c6689ca0e2906b5a97aeaae GIT binary patch literal 22064 zcmeI2!A`5-K^V5U6Qc4B%zNhya{iwUWCaE4@UPp(w(UdW|rO)dd`snwA zosM<3TCH|E(c?VN6aA5{!YDdubIs^wt`yEq{$jBx@{Kn6+b|5x@7K%aGSTnUzSO7M z=eg;VQpGijBBoWZSL#F$)L@@ij9cj+>rbcs7xQ=H&HXbr-mx}QVfW{_p6e_Mf}iJC?@#v5eB1tJG>_@I z4(L_-pUQXR#r*tr|3>|vvhknmF&poyUbFZt<@Hm|ckjpB|BLaLuD=HJKXvABmh$y} zX-)oY{a)IydjDqpad}c#Pd3f?RoBnWpQU*ICi{;&o^>_mANAkl{8G2x+DH~jp!P5Alo+Z!~Tc;@Auzd)aAXK z5B{})e}en}a5E+VmGwuON~BY|Tn3+7Ig=>mTbM>tC8fQvTo{_y_)he^UIB_67gIKkyIylje|=Kllg!fq&qi z6n~_B!9V={-{xQU{{I2{pFRK5Jo4=i{-OR+|2Y5o=1yDuz(4R0`~&~m!Xw}P0RO;0 r@DKd+&7HRRfq&p1_y_*Ag-5>o0seu1;2-$sn>%gs1OLFk?(y#%JJKg+ literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_green_a-03.vtf b/mp/game/momentum/materials/dev_nyro/dev_green_a-03.vtf new file mode 100644 index 0000000000000000000000000000000000000000..9244903f1ca2cc333d22fd054544745bb6a65e7f GIT binary patch literal 22064 zcmeHNJ8s)R5FHmH(**GiqML|SPC(HuYaJl1Sn2?A&r+ZOU%@VN1-oXIf(TL(tOPPM zD-XGp3~dO>u|Pc;68$vX*`3eMyrmt5-^eGT8-54;PWXqWJIv6$e*FA8)8F}y*%t<% zKJ&-z?M>p5x320s<|{3m#^7?hO}wd>JvWVfzuj)jfUjAW74m%;hEX8$EW4NciO0+L z$$UP4;_Et!BE@p8s;Un7x>zhS!7@Jf@(GX6nr64M*bBUDv)S};_V8fi)vwhb#iD0j z*L8;jZ~aof*mxCvZ{;cL|9uzTsXUeOyl%y!PwZE00s-i{CmN82>5GdA#^hvFH>3Dwg$>W!bLpb^g|$hw}%XtX{{HPprSH z6r1(1_TzZDRC&(pUx#I>_@BmG{*(K8{q6Xe?UC74-yt^nvrswt#=oKB!~2ixyO8;< z^_%=?;~TSoIA1JGz5L2|{u?mf=(|^c$#}BjFa9JezxJ`Yf8NDE_BsAme(vA@7Pj*@ zMf~^n@9}(%*sko zEBlj(TK;){oKBJ?DYX74|9yUA-%RJ1{%i9;=|?vIJ2w`4X|Mm={Nvp79qQ|!pV0r% z|G|I2f3zMB<{O+31CC}w~k9Gbu*n0kR z75K{S|H$VLuKav&x#TzO|G4K*?;T&4e#9U7kNuDRuYAl^FUBTE{X_jj{X_jj{o9?# z$5!86_MrZw{-ge*{-ge*{)-1({bFo#^gr}J^gr}J^gr}J^gs0fvGE(1J;HzR_y4Z{ z8Gl^a|385Lbn$oXm$Aul{=xYN`ycxs=O3JZaQ?yhXKeh&We@ld_z(CG_z(CG_z(CG j_>YS(j7<*y1O5a41O5a41O5a41O78Me&e#|TKUgkfohl_ literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_green_a-04.vtf b/mp/game/momentum/materials/dev_nyro/dev_green_a-04.vtf new file mode 100644 index 0000000000000000000000000000000000000000..b6d187bcde6194de2aeab1f2df3466c11c4d3eed GIT binary patch literal 22064 zcmeHPJx;?=41S%`1+`m8*ccg+x|9hnkS2#xoNLkhhTai--^m=t9@*)|b}|PNPsq+cpRCRwlb!Jg;SJ}n z$6GFWPR`9baWZ}>9(|3Zo#&myH@)IlCTD*!g%eXa2m{BXjQj zZFW6B?VsZ)=fm+#@s9r2>z}FlyI3P zrT(>j8|L}c!2h~Fwyjs&Ieu+XUyPlouOb`?JJ*k_&!Xe?c=G(3#NWF94$lAU)&BX@ z`8)A>?rGAd_S2X@H{Gs>LzIZ|LxAf2Pu=J1h+r}UHw~asQ-!}f%_0Rp!l7HF1 z6Z((qQTEqU-BXGmlZ>c|YJywmmzOnvn|5qJL2?b`#;q` z)_hd8fAAmp5BvxI1OKg_KT^-fRL#d#`vd=j|H1#@fABx}KY9KZ{V(_YZq@y`YJcee z=>O>d=>O>d=>O>d^!G;%)IYfYpYcEIq1^M24Z!ICS^k^2|B-*>ANmja5Bjf}`VagM z{s;eq|H1#@e_a3M|9?x}xc>wF2mJ^A2mJ^A2mM!fy`$F${R902{R92eH2w1n D9Z3lq-Ay-#}{%wX&6%<7ll+tRyDZP|}(B6a);>_#_rSg9)+F?leZ6 zxnq~P%_VyZoU-?6;PNrMJ3F)U^Q{<#M`~RuwMMT)dUfenZKs$ZecnBIe!1GaK0n=O zOd}5J^j_Pbw;))%c&)$oqbNG2=b+harZ@~W<|m1xfSx37w&@v$p}{=9MsohVOY%E< z-nM4gHGeYUJt?&l2k~Gy9A@xfG}3rCuE)Kz-kHV?!e6acD}!kt5)XvDP2)c;%ywtS z)BZK)@%<;Le zG+)}!>now(g?WE{GEnaCC-ZrInr`L?gr)ynnD;-={1w&T(f*#=Z{ow{FI&#%r$qem zdh-18u#C@>_iTLTjknj6^HmarHcs@5{9m@dy!Gcf zKRcg_;12!h__-wim#tscSN2EQ`e8rLAMu~|59ELO^`}7nGyHp6&liX{o`N#cN@;{D8$=4Q**c6SaMfkj2ugG}Bf92!f#MdJe{lc7{l~=@e3OI!fd7F1fd7F1fd7F1fdBZ$Z(R0( b|A7C1|A7C1|A7C1|A7Cv_=0cps`8(2$AlH~ literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_orange_b-04.vtf b/mp/game/momentum/materials/dev_nyro/dev_orange_b-04.vtf new file mode 100644 index 0000000000000000000000000000000000000000..5615a61bc384f0740c729d458d2975c99bd928f3 GIT binary patch literal 22064 zcmeHNy>8P`7`^R67DOMRB3mB;2?Ys>CvcoK0~0&y7!MTj7-q|mM_?ce5(^tUnUJcA zAw(G*tL7Xh*S@v_mnv~<$;VY}oqYH2`|kI1uG27hWgZ!0PVjkwPlP{nzQus}J{o-e z@#*=`o43!1(PHof_ugaN&dyF2%V;@W-`>vgG?VM=El$%MXIaMQY&KiPct%n5hVnE` z*Tfj%g1CqNd`6QjJN+FmXQMD&EYV^(9I`*!r)-b@hTjK45E7%m$ui!JnJ00A>2UoW z|GNBk>B^^J7*=sm!5Tz0(r`s$eaea2|2p6Q(m(6%_hHw6 zTVnr*_P-*(`22O}|3!S$zW<7E-*_)KS=j9RA08_;1?YZ5v3rL^@s?HZCXU$)dcAs1FW1+}s?PMLEX$iZt5nl8S^Yu(N|S`&8)D(VKhcxxOw)3^o+L?{ zt+RJqdYJERnx_2zjrhOTS^Z^EmX_vsTJG`7{S7OjXST^JmVc10bFClsqgy9h(YeOb zt2(cr7K?@DXPWJ{^o->R{oQ&|*!3mxtNDCxY366YSUM+~-0@&(-d_}jlW*3ya{Wr^ zXZ}ja->vi3kCI=mFFX0}d_MVq)Bk;)w{e&9rG0#!0j(uX=hGWfaK!$hJob;~;E#{5xBJJbxwr<9w9!KQztuO8l2^`}iZYKA$|PAMvL-SIimjq|Mx#H2`pQ|DpXGyFA1n`H%WX{agGKd%p^OKJ-8IKlDHJKlH!u z`1Mts2{;}6<==0(JgZmHaAN7y>5AHv>|KR=;8ehaN5BLZ82lxm02lxm0 n2lxm0Cw9CN`h37Yz(2r0z(2r0z(2r0z(1k!MeOn}761GOEunoq literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_red_a-01.vmt b/mp/game/momentum/materials/dev_nyro/dev_red_a-01.vmt new file mode 100644 index 0000000000..2269f0e51d --- /dev/null +++ b/mp/game/momentum/materials/dev_nyro/dev_red_a-01.vmt @@ -0,0 +1,4 @@ +"LightmappedGeneric" +{ +"$basetexture" "dev_nyro\dev_red_a-01" +} diff --git a/mp/game/momentum/materials/dev_nyro/dev_red_a-01.vtf b/mp/game/momentum/materials/dev_nyro/dev_red_a-01.vtf new file mode 100644 index 0000000000000000000000000000000000000000..b9e9474f81f55a164972f5784f12a51a33e06e41 GIT binary patch literal 22064 zcmeHNF>c#H5Ik3L6U2Fe^O7W0ULX=|0;mj9rF2J@D$@i=nNpw#y}*E8p+6XeOM(>< zX3muFc1Z<-V4XymLqihA$KAcX+nwE`6wkhxH^!JNyguWV;m>^NFrc~p`u?N+^jG%j z9l-TlyxrVfnQtaBO;J33wq>dCCeJM{ilV{iJWbO`;V7N#!_3XBs-FJu<(E78 zb|-IflH+RI-OQjrU#(UO=j*k^i}ga?Z<@zOj+^`9Ug73(=`iN?Q2)i0clLmnaa~KC zM`=Dm4ywH|~DezwX)3r2V_${Ia2aIONw4_Wk-m zKkTb72^vVpRtec!}9Zf z{2sf1uWwNUnj>1|J3P6{R97=)js^&Nc*b)!+w}3LI<$(|I`@%8hO6?)9W8kZEughO!~+A z$NG;1bn(vmAGY7b^B(rU?EhW9fuBck{yFOW{i5?$7ythJ-udsJpM>Who!`Il1Xi9u zEtd6h3V7oFUo`*D9q)a6h;RCT!RJr?{Wv$~{xkk1|DQYlE^UwT&-iEj%ja*pKfZK+ zhPKcAXZ|z)ng7qv`$O~1*z}YC$baNN@*nxH`@(l@{2Y2d(4^ZhyCB=`#;uy_CN0T*rEAm zZ2HN6d%0kLN$0|9JizTYnOIKJ0(k|FHjI|HJ-={SW&e_CKNfjj`#UX#evM DXRe#? literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_red_a-02.vtf b/mp/game/momentum/materials/dev_nyro/dev_red_a-02.vtf new file mode 100644 index 0000000000000000000000000000000000000000..dd86f25e39afbc1ed8bcacd88f5dc4a5cddb68c9 GIT binary patch literal 22064 zcmeHNJ8s)R5FJ%<6U0ZzN#ayF0Yz56KrSJcDpIfspfV*v7(v{*i^^QUK+Yhj6a)ky zq`W2NsBpYKm{5?q$_gw67 z9(A$B?%_zs2v_;uVHpkNund9l)P4{Cvm{ zU2phfzoF~TAV2c@)cWECcIS5hLw-ZA^$%JP{l9wtSTEYX90qqo-^1$spPYTh@&D&r ze(AHq*YW(oU z{-&vaW>vrQsu;&V@&5Ky<)4enhkkcnKJu@r|B#Oa{hOk{E{47`R{8&TtA0GUeDF`T ze$0Q&f6RZ(|F+*gXZ_9k=lbfOp#D3s-)rAzx${SXvAf@?`@iP?$x5;}Gz`_3!ll;nMcrmIwQ${vF;wwa??ynCDOX*YW?-_SdyM+CS}|_OI{XPOGQ8 z_UBOf^ndz4{h$8-avu>IKl(nO@sIJ3@sIJ3@vpv+?t45B-9O_$<3Hm+<3Hm+B z+F!3#-f_Hn9^wwq>v)VfwAo5lDdYq=eRw!xl_QVx7~rBP9M*YYV*kQL)L(3)=a+sy zI{#v`+XeEp`|o_UU)RB5UB9wiJBrb-y&bqQ2r@iE}ecIpKQE+YR|dxPoew`nv)mLzXh++-1E@ibv*VbSpTl$d(fXa zdm4NF{wmyq&wsoh9dT8ox%2WY zb%N&bx&69yKkeuK;{XikfAoKz|67ZHHvVdTWBJ#`F}C0HH{`#Fzg=8kWRKP-pWS(f z{(%1>9|`I;(|>6Uei7#XfPeI_zs{|F%zw;(%zw=P$NQ8^^XpT2&CQ9qT>K zx4vG7KP(mtkDL0>-=CcN%gOjy-hbg=xKXwBr(&BQr+_DJ|5fiFhW7VZAN-s4U-AAa z-jAU%_n-c6%?Q{%=12#Ce$7_a8uinE#po zng5yp$$w&gGH+lZY{7?Sl{@-Wp@h-NH@sIJ3^$+Vm)_<)3SpTv9i}}IWG5HVq Z5BU%I5BU%I5BU%I&)E4+Y~Mur&mXsflrI1P literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_red_a-04.vtf b/mp/game/momentum/materials/dev_nyro/dev_red_a-04.vtf new file mode 100644 index 0000000000000000000000000000000000000000..482b7326f8422cd24f928bdc831a2f7b09ab1083 GIT binary patch literal 22064 zcmeHNOHKko5basX#>f%$8g7tTKTCW`%!(XlcIv?Bmvkq_5VTUH?jcx`C^6D=Z~$xE%!gB{_yG3(01%^`oB5A&bF#j?CG5<0DG5^)yKMI|1QyZW8pZTBppZTBppZOmQq*k9ouZQ)I z^^f(B^^f(B^^f(B^*^-UNNs%RAO8P;Q~xSHTK4@1>Yq*gQ#+rb*Tee{?>{{MJpa7^ z@czU55AQ#r^+syrQ~yx^Q2$W>Q2$W>Q2$W>q^3_ouZQ}F`iJ_5`iJ_5`iJ_5`X{vB KNNxPK>Ys1PC(E}0 literal 0 HcmV?d00001 diff --git a/mp/game/momentum/materials/dev_nyro/dev_yellow_b-04.vtf b/mp/game/momentum/materials/dev_nyro/dev_yellow_b-04.vtf new file mode 100644 index 0000000000000000000000000000000000000000..a2e0b8908aa9baf33e2e69a805e1f32a0b91803d GIT binary patch literal 22064 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 RUNMX57Ww&qd+t8w{{>fc+(rNZ literal 0 HcmV?d00001 diff --git a/mp/game/momentum/models/player/player_shape_base.dx90.vtx b/mp/game/momentum/models/player/player_shape_base.dx90.vtx new file mode 100644 index 0000000000000000000000000000000000000000..622ca9de9523ce7344d2232ecc5d09b326b35a22 GIT binary patch literal 54371 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 RUNMX57Ww&qd+t8w{{;$c+-m>; literal 0 HcmV?d00001 diff --git a/mp/game/momentum/models/player/player_shape_base.mdl b/mp/game/momentum/models/player/player_shape_base.mdl new file mode 100644 index 0000000000000000000000000000000000000000..363a060dd2a690c78ed0083609f7f9b85b7d520c GIT binary patch literal 22528 zcmeGk3vd)gwiA9KgpdRl6Nwn)Ux%>y00FWy`3Z^SATAyVDlPd(y!s08Se1sDPOHj{4_THOM(mUDN%#z8~d6k#y z>F(FB-?zJ`r)M*{*)ty15<;#{A9%)YHCCD(V+u=bc7wItP-?V~;ZX)>iP3I06dIl8 z(WNFUko$+nIJ66{kg79Z{Wi&SVcVt#)1>~HUwggp_x-{9{xkgtc&@Jr&;0INZ$?-0 zWG2@`<}v{A8$$WU`Ei7VKrvz%1FTO_6MSDY}jtRgA z$D??hOQ^JD7-$NnfxcAes21PiOZj|V&NN?Yx+c+$va7BOZ`aqR9NI4I?BY2{`f^AP zC$#fa+*M*%1?a29t`pOFIyLRWSc!>>4XWwlbZtkx{w(Or^O3*C0v)a)9bs3>uX?H; zuMejwi`Qd7hoGiir~C8c@XX#^)$RJ`*UIdw0lKa#+a-=c%+(%;{T14EGLI0vAE_BH z(XN>tFb)~eSIN2&2XqK(+9j^@n5!MH;;G8)dJO1#sBD)QuZ|dps~=Wo*9M>q`vuwc zPOdV$P5@ocUyxmAbClT?3wDL8Y*!)dpKuHgy*^#_^{~4_yJi7hFO}^Q=U058`gV<; zqAXtPfllpsC00#MeQNiR)ViY$s;`GLCo8k-6wu+bmzwMRt()Vyak%o0b*hiUDtNwA za{n3#c15aempBePVx8Zft;{YX*xyHGy9zuxT)aBMuEX$rtR!Ar!G5*p*VH|arXG2{ zW9HZQnab?C1olU(952&rnOwZq&UsPw`880XT_eG+7?thn$oVxnLs`76V1Hkg?K<`P zln$F;W$DW7+70%1#J=}LpWM`=7N-68FMOD%U_G1yDOoqZN+(1E{nWgF)!NchN0mOo zopH4LIYnZZn^9($7VPS;vRz-DdpMP`Ug;3KF2h-?l6WnGxZb0(U8=4dl6XZbwCi1n zYlO;n;oL&o?mIYA_4zd}Oau=C% zQ9M+lU>>|LP%;ijaPI~P;a@p4%cD3c#Zg^^;xX_o0QIm zW`-?e$*J7>(G>&SBMYYMLSo|Fb83>9wZkhtYxbF$V|gdjTz!|aOP|s*8*2`_X&wEQ zlLg**2Jxq!4o}&ULk-X5ZfEvPNo2>qc_S_N^W~nxhKMwM{nPHUxa~~X+iTP6;~nhr zSL)m3eQsil#Lqro;Xb@+F`FKe)=+Ej%?v+fVzjTW(d|9I1jbEfL$9yxtl?@UQ(J9w z=Y0Nho4j8Tcyn`M*j(L}gA;V?=Pmct+V5j$mF#T%;%Jp;iF0b>wMUxVXZKfm7TaFe zEj#^iqrToP%#0S@q@{@hZ}iFP%=ehX=N|X`dsG6Wub;yf#!Y1l>*q5Ozm4{6tohb` zZN$HrRcCiGm%fsUK;$X?Hh8yagC&1D_)=i)GOYgi41xC-1oX|@A9eMt&-%eVZ}01FD6@4A!ra*L2~#_k zBMErHF@ZkVP*%+pM(uWA*f!U#3;!#-vE~T|aq|`Mz}qD9xcT!s7_X~19)(dm*xS2u z8Q{mnE8u~4@Os3!L2l#B$f2kQrvut^A_WNA0G#ud&9;DDOoRd+cz%mM*-#ea8QOhovbmVx zd;P1!X0x`Kp)8cyl?r&^`5lgtn?E0W59EU#mV*M43wF41w3%93OpM6?fIK~ z|E&`Dntd8}cl>H*X!i`}w(x!*$YcS}Z!o^%*Z}$NcfKoS(VwVGy#gM1R)Rb(Y&+dJ zZ*LsTmpJ!NReBb322;5L9yq6w2aJ!jHT&)Zzn*TyxmH~Kp$`3_Rlp+#`r|m0v*lVM zG`0cwT#7v8Y}u)RM?ZmwXbZ+xv<3L;ObU418xh*OOP{Ea$X=vdboajfy2kVf>lq%W ziM^*k$@$Fd&E&>9zqWDrpEm5m=2Rd=+j10ixOm3vy~vHh^mHW0=RYNI5hfSP$j4x| z0WOg73@77829n}4ixRj23jxZg$H^QFBp@-2G$x}|?503XkyK%hF;RBUkz!Zi=|5+C2hT$y}1s`l%J>pAL$If9t;wY$BxUoX^2OsEBx+Yjy6t62wO zv%_aeM5FPthw?VeHWw#3y~__jzp18Vd&*lOO@|L{Jh|X<%6Ehuil~Vz9+s5sWi_xM zEGO|H%R>%@cp)thfif^&;k|fg#hsKpA&^$wfwH$*no9P!@FUzjLx0>obk4QleAhYG zg7cRT^&s4#-01l@3-=$FZA6!h5Wv)Fubk|*_^X6o+#NlXqk((Wu-J@O@T|bq1uzF+ zz25()2le+)4rv zJ_+WJD?+3xK9{$QyPx+4jCt+xm65&lmvgkyqk3o~0rQ=A+bzqzY;yLv1mN69?Dcds z({Ex333EjOW>4PS<=z8z89z=4AwyizfZ4g@%C^hr`(5Jr)Wra1>mPn9-mvPQN!jCY z1JKup4{F#CFb2;DtI2lcgTL9(0OrQbXa9JsY3`u%_h;z!*E?a#K$ zooiX!KB6g(9`$<94=3dLAv~5on_dc=LAl?#&b3PqZoetq0svo5W|sj<5-NALCWxu2|1QL5e_J zAO9OUA5(O9Yqdm4s$W!?*{C6C?o@c+N53h|5(v#+o1}_ z5^X#KKN#sJ6(H2}L0@gUQIlV8YV&wN&?!+3of<$VJTGd31w36cezd{sl%$4En?WZ$ zzi5K9iFC<4jS60;WHoeJ3OdDtPAI<)Z*~;ifVLoGm!gJF1%gg%VG?P@1zgbUXz)6X zRYRvVkc&I>C>ZE<5I-8~g4b!B8al;;T%1&P7*t2`(3^s_!Rs_$4V`Yo!S_JWWBvd_ z67Ue50(H}w&2#ML{--D0wH$i4~LR1 z*L`(58oW-btmA1Qw-HoHI2B6f!zmmMb;0YT$~qnoatFh`ckR!iL@U|FuMI+{Mp#P_ z@b;7s=`E_h4(gMr`U$8HrRwn@e-%}q4|QC}Z~mOZVuGR%^!2IvR^;|LNQLjn2*{DEZqOSXVaZhwV)EScnwj}%uM zz_;S>rNlldY3lg)1CWV(9)BR&Qj?EBCej8<{`g377eOZOY5W210ca@DRcnwoP@?JO z%HIl$P=v5Kc>wb9l8wsJyv@?(UXC4y?;`0xL>FBDtFw?z2=|BEt8_@aA;p5d1)xa; zEXw(?NX~~rUJHZnigXYFZ8P9aL+ym#kS5ScJWs|DVlx7ux!;Ol8P_{pe{em(`HAxd za~w|?U);-K9UqS{Ea*n;gKIGEgYe#priyZ)*a@n6K>8lPv+nEK|22Pb+PNqlJIlN? zuF73F9MqfJ5DRsLW}J(wa+gkebPVs0PX7pF z1pLwL3v>uF73H#}7J&_eZCHgfRmC@bN|4{-OS7jdYfqzt%$*=8$mjn~UD(i{9U(i{9Unqjw;nOpPc z(1|kmBpG~4Yy32G(Lz6La?D2fSCv+7%QIwIjTM$6s~?T>48O74&5jI5slR@cZ4R^1 ze^ZXJ%+$II_(@ijK7c6nql_rMK`Lso*=V52v)ov2DKgj{Haih<3moP$qsgo#LLpHsB#DJ&Vz)TUZB}A;SW1nKN@6k>FLIg} zTFQyJ#AbDtn;oPmg;7w8L&T2LkTaC^VQsgXmSQeT|y2S+1OE0liK!KZwGs}=<3vl{ zE+&OGQ>DRfDK9GFi-_a8JB1JHjwx~)T7Es{fLV#3*YzbaE;Ja+ETv9EQF)+NzR_f^ nFf4EwD<4cs1iw@m7F*z_SPo;USzua#&UO}HQ(2|MRzUs(?QHA+ literal 0 HcmV?d00001 diff --git a/mp/game/momentum/models/player/player_shape_base.sw.vtx b/mp/game/momentum/models/player/player_shape_base.sw.vtx new file mode 100644 index 0000000000000000000000000000000000000000..95b83f34e81779dfebe6938a0200e4207b86e9cc GIT binary patch literal 54251 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

6=I7>N^N4xW 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 z zL3sShq+FizrpD#^XY*A>KiN>K2u1s!e1z}k($z5?L=D{U9X;v$)3UL38|~f1?`+ zR?yz2t900jj|aCgUSxOfMRBm!0yZ8zO}KXKLA7{ka^K%HRsmt5PUcq`sXH1pe&%5*YW&5Sa&RgZ{{ePh+c5y80&%YN{(L=-3WpKaUq@W<V^#dr_FBj9C-FVhNNSXhF-H6^5uQv? zmp-Msm(z@&nCmkt$?tFUl~qzD`Bg>#IUnPvaRq*K?paCKznPZvbMc$0)c79zt(X0= z^@Z^i5ofahSDMM|Rh_$K{ki>BMPI@g?L3ke_n;Gs=p+Ly@L2kGE#B^uc#H zGrcyGHy_{B)xKPQZ2rH|QaJ%9U0Nx3}#7h1c3ragj}#{UhI4BjYdh@xud; zs@{+5uPWN_P$Ou9Hrdd8F6W2w`w#Ma=GJj$kAVB1!1lk{F)5sWIzb)S8uHXgBAy^rx+Mr;4U|K>DD2t2}KL#aP| zf8Wsm_QSu?jGqy0+n3AlZ*-69!LYC0LHzd9e9rj$jkWXDa@r30?^uC<#$V7M{CO`M zSxLVYX?8vvdLW2 zdR)JMKEDUsyN*`JvktxA`9J1k@^QGwY8g=b-)JWPZsTIIRQ>t-u8RJN>v#5l>sMmu zn+p36qbr_=Bs>pQ+<*A`JovsAlOO)ke*QPQZ?*7pd9!D#qH%8**dDJMU&6he?>}WU z+Yc-3H`1T)!^ZCf$IJEqztBG)t*>OCRnhqEg$m~xsce0h@i$7fAC=K%`%xLq_;LTn zzjydgB)`AW?@zwQz4*WLVpVh#T)%jRDVHBx-xc;3=}-RI{`lp;^BC7}hOuX*{pWA= zx1Dp#?T@`vRW#cRnas-M!OmJ0`DJ?}+at@`|Ky*Ie|ngDCH?-5Ha_m(yQ2K6qE+XK zvh^;pZ%svh*tdp#bISN*-xemrGMe#s#ZbMH{Qji-{bIFE{?lhgnvKtFsBApd`Kp{| z<1d_3#NG%0$bRB}W#hMn(_DY_|Nqh1d0W_g_(%Oed21a}(Y$}di(zHY<9o0^Wq*WE z{v5wze|9!udqmlIY;R#}vW))! zSv%vW?X1ebm;H@4{<6Q!-nhJ~q8UFG&TA4o3$y*q)q~Ef?5xUky1<;ytAFyxeoxu& zuUn44|3 z%)IsAdA{^Y!%Ft|Cw+WDRwa2=q}hBQalVTsMrf0X65^kVcuT^)sv;g^`xVEJrEY|G z@EMpCXi#bXKk1?WyeR*EGWmTqX?(0q|9{f#``L2Pw{m_j;eLsGDC(L0CGx$q0v_XM zIra{FYS4L3wO>hdx6^r!J(C`2UbY|d_4GFyymc#mAFHCv@L%iVIs7~;eZhQa+pFUK z?tc8dq1s=i79V%;_EEKhA*I1VvFv>qe@EUxrSJFO=rOGpmdl4dUlpB;{B%WrN+T)0 zd^FC#zQX+f;CaVLt(|yi%q-5|c)U-2>4EWLjf3{(^fIJhnC0{LDeHfFF3(>Ec78Ig SaDJMtI&ZDqv6%nf@P7cwtKGi< literal 0 HcmV?d00001 diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 03d00d8aeb..42dfed2415 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -58,7 +58,10 @@ replay_header_t CMomentumReplaySystem::CreateHeader() header.demoProtoVersion = DEMO_PROTOCOL_VERSION; Q_strcpy(header.mapName, gpGlobals->mapname.ToCStr()); Q_strcpy(header.playerName, m_player->GetPlayerName()); + + Assert(steamapicontext); header.steamID64 = steamapicontext->SteamUser()->GetSteamID().ConvertToUint64(); + header.interval_per_tick = gpGlobals->interval_per_tick; header.runTimeTicks = g_Timer.GetLastRunTimeTicks(); time(&header.unixEpocDate); @@ -117,7 +120,7 @@ replay_header_t* CMomentumReplaySystem::ReadHeader(FileHandle_t file, const char ConMsg("%s has invalid replay header ID.\n", filename); return nullptr; } - if ((m_replayHeader.demoProtoVersion > DEMO_PROTOCOL_VERSION) || (m_replayHeader.demoProtoVersion < 2)) { + if (m_replayHeader.demoProtoVersion != DEMO_PROTOCOL_VERSION) { ConMsg("ERROR: replay file protocol %i outdated, engine version is %i \n", m_replayHeader.demoProtoVersion, DEMO_PROTOCOL_VERSION); @@ -143,7 +146,7 @@ bool CMomentumReplaySystem::LoadRun(const char* filename) while (!filesystem->EndOfFile(m_fhFileHandle)) { replay_frame_t* frame = ReadSingleFrame(m_fhFileHandle, filename); - m_vecRunData.AddToTail(frame); + m_vecRunData.AddToTail(*frame); } return true; } diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index 4d280aa402..55136109c7 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -34,7 +34,7 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame void StartReplay(); void EndRun(); bool LoadRun(const char* fileName); - CUtlVector m_vecRunData; + CUtlVector m_vecRunData; private: diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 9981f9e9ca..86526ba79d 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -1,9 +1,6 @@ #include "cbase.h" #include "mom_replay_entity.h" - -#define MODEL "models/alyx.mdl" - LINK_ENTITY_TO_CLASS(mom_replay_ghost, CMomentumReplayGhostEntity); BEGIN_DATADESC(CMomentumReplayGhostEntity) @@ -17,8 +14,8 @@ const char* CMomentumReplayGhostEntity::GetGhostModel() void CMomentumReplayGhostEntity::Precache(void) { BaseClass::Precache(); - PrecacheModel(MODEL); - m_ghostColor = COLOR_BLUE; //default color + PrecacheModel(GHOST_MODEL); + m_ghostColor = COLOR_GREEN; //default color } //----------------------------------------------------------------------------- @@ -29,13 +26,14 @@ void CMomentumReplayGhostEntity::Spawn(void) BaseClass::Spawn(); Precache(); RemoveEffects(EF_NODRAW); - SetModel(MODEL); SetSolid(SOLID_NONE); SetRenderMode(kRenderTransColor); SetRenderColor(m_ghostColor.r(), m_ghostColor.g(), m_ghostColor.b()); SetRenderColorA(75); SetMoveType(MOVETYPE_NOCLIP); - m_bIsActive = true; + m_bIsActive = true; + SetModel(GHOST_MODEL); + SetBodygroup(1, m_iBodyGroup); } void CMomentumReplayGhostEntity::StartRun() @@ -45,13 +43,13 @@ void CMomentumReplayGhostEntity::StartRun() m_nStartTick = gpGlobals->curtime; m_bIsActive = true; step = 1; - SetAbsOrigin(g_ReplaySystem->m_vecRunData[0]->m_vPlayerOrigin); + SetAbsOrigin(g_ReplaySystem->m_vecRunData[0].m_vPlayerOrigin); SetNextThink(gpGlobals->curtime); } void CMomentumReplayGhostEntity::updateStep() { - currentStep = g_ReplaySystem->m_vecRunData.Element(step); + currentStep = g_ReplaySystem->m_vecRunData[step]; step++; } //----------------------------------------------------------------------------- @@ -68,74 +66,87 @@ void CMomentumReplayGhostEntity::Think(void) EndRun(); } DevLog("Ghost X: %f Y: %f Z: %f\n", - currentStep->m_vPlayerOrigin.x, currentStep->m_vPlayerOrigin.y, currentStep->m_vPlayerOrigin.z); + currentStep.m_vPlayerOrigin.x, currentStep.m_vPlayerOrigin.y, currentStep.m_vPlayerOrigin.z); SetNextThink(gpGlobals->curtime + gpGlobals->interval_per_tick); } void CMomentumReplayGhostEntity::HandleGhost() { - if (currentStep != NULL) + if (!m_bIsActive) { + if (!Q_strcmp(m_pszMapName, STRING(gpGlobals->mapname)) == 0) { + DispatchSpawn(this); + } + } + else { - if (!m_bIsActive) { - if (!Q_strcmp(m_pszMapName, STRING(gpGlobals->mapname)) == 0) { - DispatchSpawn(this); - } - } - else - { - float x = currentStep->m_vPlayerOrigin.x; - float y = currentStep->m_vPlayerOrigin.y; - float z = currentStep->m_vPlayerOrigin.z; - float angleX = currentStep->m_qEyeAngles.x; - float angleY = currentStep->m_qEyeAngles.y; - float angleZ = currentStep->m_qEyeAngles.z; + float x = currentStep.m_vPlayerOrigin.x; + float y = currentStep.m_vPlayerOrigin.y; + float z = currentStep.m_vPlayerOrigin.z; + float angleX = currentStep.m_qEyeAngles.x; + float angleY = currentStep.m_qEyeAngles.y; + float angleZ = currentStep.m_qEyeAngles.z; - if (x == 0.0f) - return; - /* - if (nextStep != NULL) // we have to be at least 2 ticks into the replay to interpolate - { - if (IsEffectActive(EF_NODRAW)) - RemoveEffects(EF_NODRAW); + if (x == 0.0f) + return; + /* + if (nextStep != NULL) // we have to be at least 2 ticks into the replay to interpolate + { + if (IsEffectActive(EF_NODRAW)) + RemoveEffects(EF_NODRAW); - float x2 = nextStep->m_vPlayerOrigin.x; - float y2 = nextStep->m_vPlayerOrigin.y; - float z2 = nextStep->m_vPlayerOrigin.z; - float angleX2 = nextStep->m_qEyeAngles.x; - float angleY2 = nextStep->m_qEyeAngles.y; - float angleZ2 = nextStep->m_qEyeAngles.z; + float x2 = nextStep->m_vPlayerOrigin.x; + float y2 = nextStep->m_vPlayerOrigin.y; + float z2 = nextStep->m_vPlayerOrigin.z; + float angleX2 = nextStep->m_qEyeAngles.x; + float angleY2 = nextStep->m_qEyeAngles.y; + float angleZ2 = nextStep->m_qEyeAngles.z; - //interpolate position - float scalar = (((gpGlobals->tickcount - m_nStartTick) - t1) / (t2 - t1)); //time difference scalar value used to interpolate + //interpolate position + float scalar = (((gpGlobals->tickcount - m_nStartTick) - t1) / (t2 - t1)); //time difference scalar value used to interpolate - float xfinal = x + (scalar * (x2 - x)); - float yfinal = y + (scalar * (y2 - y)); - float zfinal = z + (scalar * (z2 - z)); - SetAbsOrigin(Vector(xfinal, yfinal, (zfinal - 15.0f))); //@tuxxi: @Gocnak, why are we subtracting 15.0 here? - float angleXFinal = angleX + (scalar * (angleX2 - angleX)); - float angleYFinal = angleY + (scalar * (angleY2 - angleY)); - float angleZFinal = angleZ + (scalar * (angleZ2 - angleZ)); - SetAbsAngles(QAngle(angleXFinal, angleYFinal, angleZFinal)); - } - else { //we cant interpolate - } - */ - SetAbsOrigin(Vector(x, y, z)); - SetAbsAngles(QAngle(angleX, angleY, angleZ)); + float xfinal = x + (scalar * (x2 - x)); + float yfinal = y + (scalar * (y2 - y)); + float zfinal = z + (scalar * (z2 - z)); + SetAbsOrigin(Vector(xfinal, yfinal, (zfinal - 15.0f))); //@tuxxi: @Gocnak, why are we subtracting 15.0 here? + float angleXFinal = angleX + (scalar * (angleX2 - angleX)); + float angleYFinal = angleY + (scalar * (angleY2 - angleY)); + float angleZFinal = angleZ + (scalar * (angleZ2 - angleZ)); + SetAbsAngles(QAngle(angleXFinal, angleYFinal, angleZFinal)); } + else { //we cant interpolate + } + */ + SetAbsOrigin(Vector(x, y, z)); + SetAbsAngles(QAngle(angleX, angleY, angleZ)); } - else{ - //EndRun(); - } } void CMomentumReplayGhostEntity::SetGhostModel(const char * newmodel) { - if (newmodel) { + if (newmodel) + { Q_strcpy(m_pszModel, newmodel); PrecacheModel(m_pszModel); SetModel(m_pszModel); } } +void CMomentumReplayGhostEntity::SetGhostBodyGroup(int bodyGroup) +{ + if (bodyGroup > sizeof(ghostModelBodyGroup) || bodyGroup < 0) + { + Msg("Error: Could not set bodygroup!"); + return; + } + else + { + m_iBodyGroup = bodyGroup; + SetBodygroup(1, bodyGroup); + } +} +void CMomentumReplayGhostEntity::SetGhostColor(Color newColor, int alpha) +{ + SetRenderColor(newColor.r(), newColor.g(), newColor.b()); + SetRenderColorA(alpha); +} void CMomentumReplayGhostEntity::EndRun() { SetNextThink(-1); diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index 6cbd752606..508797c634 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -1,7 +1,28 @@ #include "cbase.h" #include "mom_replay.h" - #pragma once + +#define GHOST_MODEL "models/player/player_shape_base.mdl" +enum ghostModelBodyGroup +{ + BODY_THREE_SIDED_PYRAMID = 0, + BODY_FOUR_SIDED_PYRAMID, + BODY_SIX_SIDED_PYRAMID, + BODY_CUBE, + BODY_FOUR_SIDED_PRISM, + BODY_THREE_SIDED_PRISM, + BODY_KITE, + BODY_FIVE_SIDED_PRISM, + BODY_SIX_SIDED_PRISM, + BODY_PENTAGON_BALL, + BODY_BALL, + BODY_PROLATE_ELLIPSE, + BODY_TRIANGLE_BALL, + BODY_CONE, + BODY_CYLINDER + +}; + class CMomentumReplayGhostEntity : public CBaseAnimating { DECLARE_CLASS(CMomentumReplayGhostEntity, CBaseAnimating); @@ -9,6 +30,8 @@ class CMomentumReplayGhostEntity : public CBaseAnimating public: const char* GetGhostModel(); void SetGhostModel(const char* model); + void SetGhostBodyGroup(int bodyGroup); + void SetGhostColor(Color newColor, int alpha = 75); //Increments the steps intelligently. void updateStep(); @@ -27,9 +50,10 @@ class CMomentumReplayGhostEntity : public CBaseAnimating private: char m_pszModel[256], m_pszMapName[256]; - replay_frame_t* currentStep; - replay_frame_t* nextStep; + replay_frame_t currentStep; + replay_frame_t nextStep; int step; + int m_iBodyGroup = BODY_PROLATE_ELLIPSE; Color m_ghostColor; }; diff --git a/mp/src/game/shared/momentum/mom_shareddefs.h b/mp/src/game/shared/momentum/mom_shareddefs.h index 9b3744a237..65ee66a9ee 100644 --- a/mp/src/game/shared/momentum/mom_shareddefs.h +++ b/mp/src/game/shared/momentum/mom_shareddefs.h @@ -30,7 +30,7 @@ typedef enum MOMGM //buffers for cstr variables #define BUFSIZETIME (sizeof("00:00:00.000")+1) -#define BUFSIZELOCL (73) +#define BUFSIZELOCL 73 #define BUFSIZESHORT 10 #define MAX_STAGES 64 From 5d410a143c7af5ad45f584d47a846ad03abf0d7a Mon Sep 17 00:00:00 2001 From: RabsRincon Date: Wed, 27 Apr 2016 01:03:50 +0200 Subject: [PATCH 011/101] We don't want assertion. Not for now at least. We want it to be runnable! --- mp/src/game/server/momentum/mom_replay.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 42dfed2415..4b3befe6aa 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -58,9 +58,8 @@ replay_header_t CMomentumReplaySystem::CreateHeader() header.demoProtoVersion = DEMO_PROTOCOL_VERSION; Q_strcpy(header.mapName, gpGlobals->mapname.ToCStr()); Q_strcpy(header.playerName, m_player->GetPlayerName()); - - Assert(steamapicontext); - header.steamID64 = steamapicontext->SteamUser()->GetSteamID().ConvertToUint64(); + + header.steamID64 = steamapicontext->SteamUser() ? steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() : 0; header.interval_per_tick = gpGlobals->interval_per_tick; header.runTimeTicks = g_Timer.GetLastRunTimeTicks(); From 2d2f024a0b6208ccaa733247a29e02275582ef42 Mon Sep 17 00:00:00 2001 From: tuxxi Date: Tue, 26 Apr 2016 20:27:12 -0700 Subject: [PATCH 012/101] ghost has firstperson mode, as well as commands to change color, bodygroup, alpha --- .../server/momentum/mom_replay_entity.cpp | 105 ++++++++++-------- .../game/server/momentum/mom_replay_entity.h | 7 +- mp/src/game/shared/momentum/util/mom_util.cpp | 18 +++ mp/src/game/shared/momentum/util/mom_util.h | 4 +- 4 files changed, 85 insertions(+), 49 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 86526ba79d..363cf9445f 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -1,11 +1,23 @@ #include "cbase.h" #include "mom_replay_entity.h" +#include "util/mom_util.h" + +static ConVar mom_replay_firstperson("mom_replay_firstperson", "0", + FCVAR_CLIENTCMD_CAN_EXECUTE, "Watch replay in first-person", true, 0, true, 1); +static ConVar mom_replay_ghost_bodygroup("mom_replay_ghost_bodygroup", "11", + FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE, "Replay ghost's body group (model)", true, 0, true, 14); +static ConCommand mom_replay_ghost_color("mom_replay_ghost_color", + CMomentumReplayGhostEntity::SetGhostColor, "Set the ghost's color. Accepts HEX color value in format RRGGBBAA", FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE); +static ConVar mom_replay_ghost_alpha("mom_replay_ghost_alpha", "75", + FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE, "Sets the ghost's transparency, integer between 0 and 255,", true, 0, true, 255); LINK_ENTITY_TO_CLASS(mom_replay_ghost, CMomentumReplayGhostEntity); BEGIN_DATADESC(CMomentumReplayGhostEntity) END_DATADESC() +Color CMomentumReplayGhostEntity::m_newGhostColor = COLOR_GREEN; + const char* CMomentumReplayGhostEntity::GetGhostModel() { return m_pszModel; @@ -33,7 +45,7 @@ void CMomentumReplayGhostEntity::Spawn(void) SetMoveType(MOVETYPE_NOCLIP); m_bIsActive = true; SetModel(GHOST_MODEL); - SetBodygroup(1, m_iBodyGroup); + SetBodygroup(1, mom_replay_ghost_bodygroup.GetInt()); } void CMomentumReplayGhostEntity::StartRun() @@ -65,59 +77,57 @@ void CMomentumReplayGhostEntity::Think(void) else { EndRun(); } - DevLog("Ghost X: %f Y: %f Z: %f\n", - currentStep.m_vPlayerOrigin.x, currentStep.m_vPlayerOrigin.y, currentStep.m_vPlayerOrigin.z); - SetNextThink(gpGlobals->curtime + gpGlobals->interval_per_tick); + SetNextThink(gpGlobals->curtime + gpGlobals->interval_per_tick); } void CMomentumReplayGhostEntity::HandleGhost() { - if (!m_bIsActive) { + if (!m_bIsActive) + { if (!Q_strcmp(m_pszMapName, STRING(gpGlobals->mapname)) == 0) { DispatchSpawn(this); } - } + } else { - float x = currentStep.m_vPlayerOrigin.x; - float y = currentStep.m_vPlayerOrigin.y; - float z = currentStep.m_vPlayerOrigin.z; - float angleX = currentStep.m_qEyeAngles.x; - float angleY = currentStep.m_qEyeAngles.y; - float angleZ = currentStep.m_qEyeAngles.z; - - if (x == 0.0f) + if (currentStep.m_vPlayerOrigin.x == 0.0f) return; - /* - if (nextStep != NULL) // we have to be at least 2 ticks into the replay to interpolate - { - if (IsEffectActive(EF_NODRAW)) - RemoveEffects(EF_NODRAW); - - float x2 = nextStep->m_vPlayerOrigin.x; - float y2 = nextStep->m_vPlayerOrigin.y; - float z2 = nextStep->m_vPlayerOrigin.z; - float angleX2 = nextStep->m_qEyeAngles.x; - float angleY2 = nextStep->m_qEyeAngles.y; - float angleZ2 = nextStep->m_qEyeAngles.z; - //interpolate position - float scalar = (((gpGlobals->tickcount - m_nStartTick) - t1) / (t2 - t1)); //time difference scalar value used to interpolate + if (mom_replay_firstperson.GetBool()) + { + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if (pPlayer) { + //MOM_TODO: interpolate eyeangles and origin somehow so playback doesn't look so jerky + pPlayer->SnapEyeAngles(currentStep.m_qEyeAngles); + pPlayer->SetAbsOrigin(currentStep.m_vPlayerOrigin); - float xfinal = x + (scalar * (x2 - x)); - float yfinal = y + (scalar * (y2 - y)); - float zfinal = z + (scalar * (z2 - z)); - SetAbsOrigin(Vector(xfinal, yfinal, (zfinal - 15.0f))); //@tuxxi: @Gocnak, why are we subtracting 15.0 here? - float angleXFinal = angleX + (scalar * (angleX2 - angleX)); - float angleYFinal = angleY + (scalar * (angleY2 - angleY)); - float angleZFinal = angleZ + (scalar * (angleZ2 - angleZ)); - SetAbsAngles(QAngle(angleXFinal, angleYFinal, angleZFinal)); - } - else { //we cant interpolate + pPlayer->m_nButtons &= currentStep.m_nPlayerButtons; // MOM_TODO: make this actually work + pPlayer->AddFlag(FL_ATCONTROLS); //prevent keypress from affecting the replay playback + } + } + else + { + SetAbsOrigin(currentStep.m_vPlayerOrigin); + SetAbsAngles(QAngle(currentStep.m_qEyeAngles.x / 10, //we divide x angle (pitch) by 10 so the ghost doesn't look really stupid + currentStep.m_qEyeAngles.y, currentStep.m_qEyeAngles.z)); } - */ - SetAbsOrigin(Vector(x, y, z)); - SetAbsAngles(QAngle(angleX, angleY, angleZ)); } + //update color, bodygroup, and other params if they change + if (mom_replay_ghost_bodygroup.GetInt() != m_iBodyGroup) + { + m_iBodyGroup = mom_replay_ghost_bodygroup.GetInt(); + SetBodygroup(1, m_iBodyGroup); + } + if (m_ghostColor != m_newGhostColor) + { + m_ghostColor = m_newGhostColor; + SetRenderColor(m_ghostColor.r(), m_ghostColor.g(), m_ghostColor.b()); + } + if (mom_replay_ghost_alpha.GetInt() != m_ghostColor.a()) + { + m_ghostColor.SetColor(m_ghostColor.r(), m_ghostColor.g(), m_ghostColor.b(), //we have to set the previous colors in order to change alpha... + mom_replay_ghost_alpha.GetInt()); + SetRenderColorA(mom_replay_ghost_alpha.GetInt()); + } } void CMomentumReplayGhostEntity::SetGhostModel(const char * newmodel) @@ -142,18 +152,23 @@ void CMomentumReplayGhostEntity::SetGhostBodyGroup(int bodyGroup) SetBodygroup(1, bodyGroup); } } -void CMomentumReplayGhostEntity::SetGhostColor(Color newColor, int alpha) +void CMomentumReplayGhostEntity::SetGhostColor(const CCommand &args) { - SetRenderColor(newColor.r(), newColor.g(), newColor.b()); - SetRenderColorA(alpha); + if (mom_UTIL.GetColorFromHex(args.ArgS())) + CMomentumReplayGhostEntity::m_newGhostColor = *mom_UTIL.GetColorFromHex(args.ArgS()); } void CMomentumReplayGhostEntity::EndRun() { SetNextThink(-1); Remove(); m_bIsActive = false; + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if (pPlayer) { + pPlayer->EnableControl(true); + } } void CMomentumReplayGhostEntity::clearRunData() { g_ReplaySystem->m_vecRunData.RemoveAll(); -} \ No newline at end of file +} + diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index 508797c634..d380d1c99c 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -1,8 +1,11 @@ #include "cbase.h" #include "mom_replay.h" +#include "in_buttons.h" + #pragma once #define GHOST_MODEL "models/player/player_shape_base.mdl" +#define ALL_BUTTONS IN_LEFT & IN_RIGHT & IN_MOVELEFT & IN_MOVERIGHT & IN_FORWARD & IN_BACK & IN_JUMP & IN_DUCK enum ghostModelBodyGroup { BODY_THREE_SIDED_PYRAMID = 0, @@ -20,7 +23,6 @@ enum ghostModelBodyGroup BODY_TRIANGLE_BALL, BODY_CONE, BODY_CYLINDER - }; class CMomentumReplayGhostEntity : public CBaseAnimating @@ -31,7 +33,7 @@ class CMomentumReplayGhostEntity : public CBaseAnimating const char* GetGhostModel(); void SetGhostModel(const char* model); void SetGhostBodyGroup(int bodyGroup); - void SetGhostColor(Color newColor, int alpha = 75); + static void SetGhostColor(const CCommand &args); //Increments the steps intelligently. void updateStep(); @@ -56,4 +58,5 @@ class CMomentumReplayGhostEntity : public CBaseAnimating int step; int m_iBodyGroup = BODY_PROLATE_ELLIPSE; Color m_ghostColor; + static Color m_newGhostColor; }; diff --git a/mp/src/game/shared/momentum/util/mom_util.cpp b/mp/src/game/shared/momentum/util/mom_util.cpp index 1f624a62fb..9e7f4e8c61 100644 --- a/mp/src/game/shared/momentum/util/mom_util.cpp +++ b/mp/src/game/shared/momentum/util/mom_util.cpp @@ -234,5 +234,23 @@ Color MomentumUtil::GetColorFromVariation(float variation, float deadZone, Color return pFinalColor; } +Color* MomentumUtil::GetColorFromHex(const char* hexColor) +{ + long hex = strtol(hexColor, NULL, 16); + int length = Q_strlen(hexColor); + if (length == 6) + { + int r = ((hex >> 16) & 0xFF); //extract RR byte + int g = ((hex >> 8) & 0xFF); //extract GG byte + int b = ((hex) & 0xFF); //extract BB byte + m_newColor.SetColor(r, g, b, 75); + return &m_newColor; + } + else { + Msg("Error: Color format incorrect! Use hex code in format \"RRGGBB\"\n"); + return nullptr; + } +} + MomentumUtil mom_UTIL; \ No newline at end of file diff --git a/mp/src/game/shared/momentum/util/mom_util.h b/mp/src/game/shared/momentum/util/mom_util.h index c297a52c1a..7d7da0a584 100644 --- a/mp/src/game/shared/momentum/util/mom_util.h +++ b/mp/src/game/shared/momentum/util/mom_util.h @@ -31,12 +31,12 @@ class MomentumUtil #endif Color GetColorFromVariation(float variation, float deadZone, Color normalcolor, Color increasecolor, Color decreasecolor); - + Color* GetColorFromHex(const char* hexColor); //in hex color format RRGGBB + Color m_newColor; //Formats time in ticks by a given tickrate into time. Includes minutes if time > minutes, hours if time > hours, etc //Precision is miliseconds by default void FormatTime(float ticks, float rate, char *pOut, int precision = 3); }; - extern MomentumUtil mom_UTIL; #endif //MOM_UTIL_H \ No newline at end of file From b17cffa2bf1f30baf799d20d0a15005a6e41a58b Mon Sep 17 00:00:00 2001 From: RSTFS Date: Wed, 27 Apr 2016 17:06:14 -0400 Subject: [PATCH 013/101] Properly dispose of previous run data and thrown away recordings --- mp/src/game/server/momentum/mom_replay.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 4b3befe6aa..2e5a76398c 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -16,6 +16,7 @@ void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway, { m_bIsRecording = false; if (throwaway) { + m_buf->Purge(); return; } CMomentumPlayer *pMOMPlayer = ToCMOMPlayer(pPlayer); @@ -129,7 +130,7 @@ replay_header_t* CMomentumReplaySystem::ReadHeader(FileHandle_t file, const char } bool CMomentumReplaySystem::LoadRun(const char* filename) { - m_vecRunData.RemoveAll(); + m_vecRunData.Purge(); char recordingName[MAX_PATH]; V_ComposeFileName(RECORDING_PATH, filename, recordingName, MAX_PATH); m_fhFileHandle = filesystem->Open(recordingName, "r+b", "MOD"); From 8a1f337c58f40fec22d42cafbca1c3b418d2117e Mon Sep 17 00:00:00 2001 From: tuxxi Date: Tue, 3 May 2016 15:30:59 -0700 Subject: [PATCH 014/101] fixed trigger bug after respawning (THANK YOU RAPTOR) added spec mode, currently doesnt follow correctly --- mp/src/game/server/momentum/mom_player.cpp | 1 + .../server/momentum/mom_replay_entity.cpp | 37 +++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/mp/src/game/server/momentum/mom_player.cpp b/mp/src/game/server/momentum/mom_player.cpp index e62bd40998..bb61119936 100644 --- a/mp/src/game/server/momentum/mom_player.cpp +++ b/mp/src/game/server/momentum/mom_player.cpp @@ -54,6 +54,7 @@ void CMomentumPlayer::Spawn() SetModel(ENTITY_MODEL); BaseClass::Spawn(); AddFlag(FL_GODMODE); + RemoveSolidFlags(FSOLID_NOT_SOLID); //this removes the flag that was added while switching to spectator mode which prevented the player from activating triggers // do this here because we can't get a local player in the timer class ConVarRef gm("mom_gamemode"); switch (gm.GetInt()) diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 363cf9445f..24d37f57c1 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -92,24 +92,31 @@ void CMomentumReplayGhostEntity::HandleGhost() { if (currentStep.m_vPlayerOrigin.x == 0.0f) return; - if (mom_replay_firstperson.GetBool()) - { - CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); - if (pPlayer) { - //MOM_TODO: interpolate eyeangles and origin somehow so playback doesn't look so jerky - pPlayer->SnapEyeAngles(currentStep.m_qEyeAngles); - pPlayer->SetAbsOrigin(currentStep.m_vPlayerOrigin); + SetAbsOrigin(currentStep.m_vPlayerOrigin); //always set our origin - pPlayer->m_nButtons &= currentStep.m_nPlayerButtons; // MOM_TODO: make this actually work - pPlayer->AddFlag(FL_ATCONTROLS); //prevent keypress from affecting the replay playback - } - } - else + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if (pPlayer) { - SetAbsOrigin(currentStep.m_vPlayerOrigin); - SetAbsAngles(QAngle(currentStep.m_qEyeAngles.x / 10, //we divide x angle (pitch) by 10 so the ghost doesn't look really stupid - currentStep.m_qEyeAngles.y, currentStep.m_qEyeAngles.z)); + if (mom_replay_firstperson.GetBool()) + { + SetAbsAngles(currentStep.m_qEyeAngles); + pPlayer->StartObserverMode(OBS_MODE_CHASE); + pPlayer->SetObserverTarget(this); + pPlayer->m_nButtons = currentStep.m_nPlayerButtons; + } + else + { + SetAbsAngles(QAngle(currentStep.m_qEyeAngles.x / 10, //we divide x angle (pitch) by 10 so the ghost doesn't look really stupid + currentStep.m_qEyeAngles.y, currentStep.m_qEyeAngles.z)); + if (pPlayer->IsObserver()) + { + pPlayer->StopObserverMode(); + pPlayer->ForceRespawn(); + pPlayer->SetMoveType(MOVETYPE_WALK); + } + } } + } //update color, bodygroup, and other params if they change if (mom_replay_ghost_bodygroup.GetInt() != m_iBodyGroup) From 6d911c9664ab6b28413556a9235828ad996ada61 Mon Sep 17 00:00:00 2001 From: tuxxi Date: Tue, 3 May 2016 16:14:16 -0700 Subject: [PATCH 015/101] proper first person spectating works, keypress display works --- mp/src/game/client/momentum/c_mom_player.cpp | 2 ++ mp/src/game/client/momentum/c_mom_player.h | 1 + .../game/client/momentum/ui/hud_keypress.cpp | 10 ++++++- mp/src/game/server/momentum/mom_player.cpp | 2 ++ mp/src/game/server/momentum/mom_player.h | 2 ++ .../server/momentum/mom_replay_entity.cpp | 29 ++++++++++++++----- mp/src/game/server/player.cpp | 5 ++-- 7 files changed, 41 insertions(+), 10 deletions(-) diff --git a/mp/src/game/client/momentum/c_mom_player.cpp b/mp/src/game/client/momentum/c_mom_player.cpp index 6a8c76b8dc..5a2fe16274 100644 --- a/mp/src/game/client/momentum/c_mom_player.cpp +++ b/mp/src/game/client/momentum/c_mom_player.cpp @@ -16,6 +16,8 @@ RecvPropInt(RECVINFO(m_iSuccessiveBhops)), RecvPropFloat(RECVINFO(m_flStrafeSync)), RecvPropFloat(RECVINFO(m_flStrafeSync2)), RecvPropFloat(RECVINFO(m_flLastJumpVel)), +RecvPropBool(RECVINFO(m_bIsWatchingReplay)), +RecvPropInt(RECVINFO(m_nButtons)), //RecvPropDataTable(RECVINFO_DT(m_HL2Local), 0, &REFERENCE_RECV_TABLE(DT_HL2Local)), //RecvPropBool(RECVINFO(m_fIsSprinting)), END_RECV_TABLE() diff --git a/mp/src/game/client/momentum/c_mom_player.h b/mp/src/game/client/momentum/c_mom_player.h index 3cbb740fd5..2e57f7e526 100644 --- a/mp/src/game/client/momentum/c_mom_player.h +++ b/mp/src/game/client/momentum/c_mom_player.h @@ -31,6 +31,7 @@ class C_MomentumPlayer : public C_BasePlayer int m_iLastZoom; bool m_bAutoBhop; bool m_bDidPlayerBhop; + bool m_bIsWatchingReplay; float m_flStrafeSync, m_flStrafeSync2; float m_flLastJumpVel; diff --git a/mp/src/game/client/momentum/ui/hud_keypress.cpp b/mp/src/game/client/momentum/ui/hud_keypress.cpp index 25c2cacefa..ab23fc057d 100644 --- a/mp/src/game/client/momentum/ui/hud_keypress.cpp +++ b/mp/src/game/client/momentum/ui/hud_keypress.cpp @@ -164,7 +164,15 @@ void CHudKeyPressDisplay::Paint() } void CHudKeyPressDisplay::OnThink() { - m_nButtons = ::input->GetButtonBits(0); + CMomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); + if (pPlayer->m_bIsWatchingReplay) + { + m_nButtons = pPlayer->m_nButtons; + } + else + { + m_nButtons = ::input->GetButtonBits(1); + } if (g_MOMEventListener) { //we should only draw the strafe/jump counters when the timer is running m_bShouldDrawCounts = g_MOMEventListener->m_bTimerIsRunning; diff --git a/mp/src/game/server/momentum/mom_player.cpp b/mp/src/game/server/momentum/mom_player.cpp index bb61119936..be3cb4396f 100644 --- a/mp/src/game/server/momentum/mom_player.cpp +++ b/mp/src/game/server/momentum/mom_player.cpp @@ -19,6 +19,8 @@ SendPropInt(SENDINFO(m_iSuccessiveBhops)), SendPropFloat(SENDINFO(m_flStrafeSync)), SendPropFloat(SENDINFO(m_flStrafeSync2)), SendPropFloat(SENDINFO(m_flLastJumpVel)), +SendPropBool(SENDINFO(m_bIsWatchingReplay)), +SendPropInt(SENDINFO(m_nButtons)), END_SEND_TABLE() BEGIN_DATADESC(CMomentumPlayer) diff --git a/mp/src/game/server/momentum/mom_player.h b/mp/src/game/server/momentum/mom_player.h index 5ff4431e60..97f9b7dc34 100644 --- a/mp/src/game/server/momentum/mom_player.h +++ b/mp/src/game/server/momentum/mom_player.h @@ -82,6 +82,8 @@ class CMomentumPlayer : public CBasePlayer CNetworkVar(float, m_flStrafeSync); //eyeangle based, perfect strafes / total strafes CNetworkVar(float, m_flStrafeSync2); //acceleration based, strafes speed gained / total strafes CNetworkVar(float, m_flLastJumpVel); + CNetworkVar(bool, m_bIsWatchingReplay); + CNetworkVar(int, m_nButtons); void GetBulletTypeParameters(int iBulletType, float &fPenetrationPower, float &flPenetrationDistance); diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 24d37f57c1..f7ca2e1481 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -94,17 +94,27 @@ void CMomentumReplayGhostEntity::HandleGhost() { SetAbsOrigin(currentStep.m_vPlayerOrigin); //always set our origin - CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); if (pPlayer) { if (mom_replay_firstperson.GetBool()) { + pPlayer->m_bIsWatchingReplay = true; + if (!pPlayer->IsObserver()) + { + pPlayer->SetObserverTarget(this); + pPlayer->StartObserverMode(OBS_MODE_IN_EYE); + } SetAbsAngles(currentStep.m_qEyeAngles); - pPlayer->StartObserverMode(OBS_MODE_CHASE); - pPlayer->SetObserverTarget(this); + SetAbsOrigin(Vector(currentStep.m_vPlayerOrigin.x, currentStep.m_vPlayerOrigin.y, currentStep.m_vPlayerOrigin.z + VEC_VIEW.z)); pPlayer->m_nButtons = currentStep.m_nPlayerButtons; + if (currentStep.m_nPlayerButtons & IN_DUCK) + { + //MOM_TODO: make this smoother. possibly inherit from NPC classes/CBaseCombatCharacter + SetAbsOrigin(Vector(currentStep.m_vPlayerOrigin.x, currentStep.m_vPlayerOrigin.y, currentStep.m_vPlayerOrigin.z + VEC_DUCK_VIEW.z)); + } } - else + else //we're watching/racing with a ghost { SetAbsAngles(QAngle(currentStep.m_qEyeAngles.x / 10, //we divide x angle (pitch) by 10 so the ghost doesn't look really stupid currentStep.m_qEyeAngles.y, currentStep.m_qEyeAngles.z)); @@ -169,9 +179,14 @@ void CMomentumReplayGhostEntity::EndRun() SetNextThink(-1); Remove(); m_bIsActive = false; - CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); - if (pPlayer) { - pPlayer->EnableControl(true); + CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); + + if (pPlayer->IsObserver() && pPlayer) + { + pPlayer->StopObserverMode(); + pPlayer->ForceRespawn(); + pPlayer->SetMoveType(MOVETYPE_WALK); + pPlayer->m_bIsWatchingReplay = false; } } void CMomentumReplayGhostEntity::clearRunData() diff --git a/mp/src/game/server/player.cpp b/mp/src/game/server/player.cpp index e0e7ccd280..956267033e 100644 --- a/mp/src/game/server/player.cpp +++ b/mp/src/game/server/player.cpp @@ -2661,6 +2661,7 @@ bool CBasePlayer::IsValidObserverTarget(CBaseEntity * target) // MOD AUTHORS: Add checks on target here or in derived method + /* if ( !target->IsPlayer() ) // only track players return false; @@ -2668,7 +2669,7 @@ bool CBasePlayer::IsValidObserverTarget(CBaseEntity * target) /* Don't spec observers or players who haven't picked a class yet if ( player->IsObserver() ) - return false; */ + return false; if( player == this ) return false; // We can't observe ourselves. @@ -2686,7 +2687,7 @@ bool CBasePlayer::IsValidObserverTarget(CBaseEntity * target) return false; // allow watching until 3 seconds after death to see death animation } } - + */ // check forcecamera settings for active players if ( GetTeamNumber() != TEAM_SPECTATOR ) { From 7df1cb6e2297062a9af81f554d9065253e2837ad Mon Sep 17 00:00:00 2001 From: tuxxi Date: Tue, 3 May 2016 18:20:00 -0700 Subject: [PATCH 016/101] properly override IsValidObeserverTarget, fix player buttons, added interpolated velocity, timer runs during replay getting a weird assert about UtilVectors comparing unsigned ints, but it doesnt break anything --- mp/src/game/client/momentum/c_mom_player.cpp | 2 +- mp/src/game/client/momentum/c_mom_player.h | 1 + .../game/client/momentum/ui/hud_keypress.cpp | 2 +- mp/src/game/server/momentum/mom_player.cpp | 45 ++++++++++++++++++- mp/src/game/server/momentum/mom_player.h | 3 +- mp/src/game/server/momentum/mom_replay.cpp | 10 +++-- mp/src/game/server/momentum/mom_replay.h | 6 +-- .../server/momentum/mom_replay_entity.cpp | 36 +++++++++------ .../game/server/momentum/mom_replay_entity.h | 7 ++- mp/src/game/server/player.cpp | 5 +-- 10 files changed, 87 insertions(+), 30 deletions(-) diff --git a/mp/src/game/client/momentum/c_mom_player.cpp b/mp/src/game/client/momentum/c_mom_player.cpp index 5a2fe16274..557881d7d4 100644 --- a/mp/src/game/client/momentum/c_mom_player.cpp +++ b/mp/src/game/client/momentum/c_mom_player.cpp @@ -17,7 +17,7 @@ RecvPropFloat(RECVINFO(m_flStrafeSync)), RecvPropFloat(RECVINFO(m_flStrafeSync2)), RecvPropFloat(RECVINFO(m_flLastJumpVel)), RecvPropBool(RECVINFO(m_bIsWatchingReplay)), -RecvPropInt(RECVINFO(m_nButtons)), +RecvPropInt(RECVINFO(m_nReplayButtons)), //RecvPropDataTable(RECVINFO_DT(m_HL2Local), 0, &REFERENCE_RECV_TABLE(DT_HL2Local)), //RecvPropBool(RECVINFO(m_fIsSprinting)), END_RECV_TABLE() diff --git a/mp/src/game/client/momentum/c_mom_player.h b/mp/src/game/client/momentum/c_mom_player.h index 2e57f7e526..77e7235ae5 100644 --- a/mp/src/game/client/momentum/c_mom_player.h +++ b/mp/src/game/client/momentum/c_mom_player.h @@ -32,6 +32,7 @@ class C_MomentumPlayer : public C_BasePlayer bool m_bAutoBhop; bool m_bDidPlayerBhop; bool m_bIsWatchingReplay; + int m_nReplayButtons; //networked var that allows the replay system to control keypress display on the client float m_flStrafeSync, m_flStrafeSync2; float m_flLastJumpVel; diff --git a/mp/src/game/client/momentum/ui/hud_keypress.cpp b/mp/src/game/client/momentum/ui/hud_keypress.cpp index ab23fc057d..40a46c8a7b 100644 --- a/mp/src/game/client/momentum/ui/hud_keypress.cpp +++ b/mp/src/game/client/momentum/ui/hud_keypress.cpp @@ -167,7 +167,7 @@ void CHudKeyPressDisplay::OnThink() CMomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); if (pPlayer->m_bIsWatchingReplay) { - m_nButtons = pPlayer->m_nButtons; + m_nButtons = pPlayer->m_nReplayButtons; } else { diff --git a/mp/src/game/server/momentum/mom_player.cpp b/mp/src/game/server/momentum/mom_player.cpp index be3cb4396f..5f537e8791 100644 --- a/mp/src/game/server/momentum/mom_player.cpp +++ b/mp/src/game/server/momentum/mom_player.cpp @@ -20,7 +20,7 @@ SendPropFloat(SENDINFO(m_flStrafeSync)), SendPropFloat(SENDINFO(m_flStrafeSync2)), SendPropFloat(SENDINFO(m_flLastJumpVel)), SendPropBool(SENDINFO(m_bIsWatchingReplay)), -SendPropInt(SENDINFO(m_nButtons)), +SendPropInt(SENDINFO(m_nReplayButtons)), END_SEND_TABLE() BEGIN_DATADESC(CMomentumPlayer) @@ -414,3 +414,46 @@ void CMomentumPlayer::LimitSpeedInStartZone() } SetNextThink(gpGlobals->curtime, "CURTIME_FOR_START"); } +//override of CBasePlayer::IsValidObserverTarget that allows us to spectate replay ghosts +bool CMomentumPlayer::IsValidObserverTarget(CBaseEntity *target) +{ + if (target == NULL) + return false; + + if (!target->IsPlayer()) + { + if (!Q_strcmp(target->GetClassname(), "mom_replay_ghost")) //target is a replay ghost + { + return true; + } + else + { + return false; + } + } + + CMomentumPlayer *player = ToCMOMPlayer( target ); + + /* Don't spec observers or players who haven't picked a class yet */ + if ( player->IsObserver() ) + return false; + + if( player == this ) + return false; // We can't observe ourselves. + + if ( player->IsEffectActive( EF_NODRAW ) ) // don't watch invisible players + return false; + + if ( player->m_lifeState == LIFE_RESPAWNABLE ) // target is dead, waiting for respawn + return false; + + if ( player->m_lifeState == LIFE_DEAD || player->m_lifeState == LIFE_DYING ) + { + if ( (player->m_flDeathTime + DEATH_ANIMATION_TIME ) < gpGlobals->curtime ) + { + return false; // allow watching until 3 seconds after death to see death animation + } + } + + return true; // passed all tests +} \ No newline at end of file diff --git a/mp/src/game/server/momentum/mom_player.h b/mp/src/game/server/momentum/mom_player.h index 97f9b7dc34..d5bddb5a53 100644 --- a/mp/src/game/server/momentum/mom_player.h +++ b/mp/src/game/server/momentum/mom_player.h @@ -83,7 +83,7 @@ class CMomentumPlayer : public CBasePlayer CNetworkVar(float, m_flStrafeSync2); //acceleration based, strafes speed gained / total strafes CNetworkVar(float, m_flLastJumpVel); CNetworkVar(bool, m_bIsWatchingReplay); - CNetworkVar(int, m_nButtons); + CNetworkVar(int, m_nReplayButtons); void GetBulletTypeParameters(int iBulletType, float &fPenetrationPower, float &flPenetrationDistance); @@ -97,6 +97,7 @@ class CMomentumPlayer : public CBasePlayer void SetPunishTime(float newTime) { m_flPunishTime = newTime; } void SetLastBlock(int lastBlock) { m_iLastBlock = lastBlock; } + bool IsValidObserverTarget(CBaseEntity *target) override; int GetLastBlock() { return m_iLastBlock; } float GetPunishTime() { return m_flPunishTime; } diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 2e5a76398c..3b18d38fac 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -8,9 +8,12 @@ void CMomentumReplaySystem::BeginRecording(CBasePlayer *pPlayer) { m_player = ToCMOMPlayer( pPlayer); - m_bIsRecording = true; - Log("Recording began!\n"); - m_nCurrentTick = 1; //recoring begins at 1 ;) + if (!m_bIsWatchingReplay) //don't record if we're watching a preexisting replay + { + m_bIsRecording = true; + Log("Recording began!\n"); + m_nCurrentTick = 1; //recoring begins at 1 ;) + } } void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway, float delay) { @@ -161,6 +164,7 @@ void CMomentumReplaySystem::StartReplay() if (ghost != nullptr) { ghost->StartRun(); + m_bIsWatchingReplay = true; } } class CMOMReplayCommands diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index 55136109c7..aa3a8830ae 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -23,7 +23,6 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame } void BeginRecording(CBasePlayer *pPlayer); void StopRecording(CBasePlayer *pPlayer, bool throwaway, float delay = 1.0f); - bool IsRecording(CBasePlayer *pPlayer) { return m_bIsRecording; } void WriteRecordingToFile(CUtlBuffer &buf); replay_header_t CreateHeader(); void WriteRecordingToFile(); @@ -32,15 +31,16 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame replay_header_t* ReadHeader(FileHandle_t file, const char* filename); void StartReplay(); - void EndRun(); bool LoadRun(const char* fileName); CUtlVector m_vecRunData; + bool IsWatchingReplay(bool isWatching) { return m_bIsWatchingReplay; } + bool IsRecording(CBasePlayer *pPlayer) { return m_bIsRecording; } private: CUtlBuffer *UpdateRecordingParams(); //called every game frame after entities think and update - bool m_bIsRecording; + bool m_bIsRecording, m_bIsWatchingReplay; int m_nCurrentTick; CMomentumPlayer *m_player; diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index f7ca2e1481..2cfe8150fd 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -1,6 +1,7 @@ #include "cbase.h" #include "mom_replay_entity.h" #include "util/mom_util.h" +#include "Timer.h" static ConVar mom_replay_firstperson("mom_replay_firstperson", "0", FCVAR_CLIENTCMD_CAN_EXECUTE, "Watch replay in first-person", true, 0, true, 1); @@ -56,13 +57,12 @@ void CMomentumReplayGhostEntity::StartRun() m_bIsActive = true; step = 1; SetAbsOrigin(g_ReplaySystem->m_vecRunData[0].m_vPlayerOrigin); - SetNextThink(gpGlobals->curtime); } void CMomentumReplayGhostEntity::updateStep() { currentStep = g_ReplaySystem->m_vecRunData[step]; - step++; + nextStep = g_ReplaySystem->m_vecRunData[++step]; } //----------------------------------------------------------------------------- // Purpose: Think function to move the ghost @@ -93,7 +93,6 @@ void CMomentumReplayGhostEntity::HandleGhost() { return; SetAbsOrigin(currentStep.m_vPlayerOrigin); //always set our origin - CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); if (pPlayer) { @@ -104,21 +103,34 @@ void CMomentumReplayGhostEntity::HandleGhost() { { pPlayer->SetObserverTarget(this); pPlayer->StartObserverMode(OBS_MODE_IN_EYE); + pPlayer->RemoveSolidFlags(FSOLID_NOT_SOLID); //allow the player to trigger the timer start/zones/etc } + pPlayer->ForceObserverMode(OBS_MODE_IN_EYE); //don't allow other observer modes... + + //interpolate vel from difference in origin + float distX = fabs(currentStep.m_vPlayerOrigin.x - nextStep.m_vPlayerOrigin.x); + float distY= fabs(currentStep.m_vPlayerOrigin.y - nextStep.m_vPlayerOrigin.y); + float distZ = fabs(currentStep.m_vPlayerOrigin.z - nextStep.m_vPlayerOrigin.z); + Vector interpolatedVel = Vector(distX, distY, distZ) / gpGlobals->interval_per_tick; + SetAbsAngles(currentStep.m_qEyeAngles); - SetAbsOrigin(Vector(currentStep.m_vPlayerOrigin.x, currentStep.m_vPlayerOrigin.y, currentStep.m_vPlayerOrigin.z + VEC_VIEW.z)); - pPlayer->m_nButtons = currentStep.m_nPlayerButtons; + SetAbsVelocity(interpolatedVel); + SetAbsOrigin(currentStep.m_vPlayerOrigin + VEC_VIEW); + + pPlayer->m_nReplayButtons = currentStep.m_nPlayerButtons; //networked var that allows the replay to control keypress display on the client + pPlayer->SnapEyeAngles(currentStep.m_qEyeAngles); + if (currentStep.m_nPlayerButtons & IN_DUCK) { //MOM_TODO: make this smoother. possibly inherit from NPC classes/CBaseCombatCharacter - SetAbsOrigin(Vector(currentStep.m_vPlayerOrigin.x, currentStep.m_vPlayerOrigin.y, currentStep.m_vPlayerOrigin.z + VEC_DUCK_VIEW.z)); + SetAbsOrigin(currentStep.m_vPlayerOrigin + VEC_DUCK_VIEW); } } else //we're watching/racing with a ghost { SetAbsAngles(QAngle(currentStep.m_qEyeAngles.x / 10, //we divide x angle (pitch) by 10 so the ghost doesn't look really stupid currentStep.m_qEyeAngles.y, currentStep.m_qEyeAngles.z)); - if (pPlayer->IsObserver()) + if (pPlayer->IsObserver()) //bring the player out of obs mode if theyre current observing { pPlayer->StopObserverMode(); pPlayer->ForceRespawn(); @@ -179,6 +191,8 @@ void CMomentumReplayGhostEntity::EndRun() SetNextThink(-1); Remove(); m_bIsActive = false; + g_ReplaySystem->IsWatchingReplay(false); + g_Timer.Stop(false); CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); if (pPlayer->IsObserver() && pPlayer) @@ -187,10 +201,6 @@ void CMomentumReplayGhostEntity::EndRun() pPlayer->ForceRespawn(); pPlayer->SetMoveType(MOVETYPE_WALK); pPlayer->m_bIsWatchingReplay = false; + pPlayer->m_flLastJumpVel = 0; } -} -void CMomentumReplayGhostEntity::clearRunData() -{ - g_ReplaySystem->m_vecRunData.RemoveAll(); -} - +} \ No newline at end of file diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index d380d1c99c..3e2a9a0c02 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -40,15 +40,14 @@ class CMomentumReplayGhostEntity : public CBaseAnimating void EndRun(); void StartRun(); void HandleGhost(); - void clearRunData(); bool m_bIsActive; int m_nStartTick; protected: - virtual void Think(void); - virtual void Spawn(void); - virtual void Precache(void); + void Think(void) override; + void Spawn(void) override; + void Precache(void) override; private: char m_pszModel[256], m_pszMapName[256]; diff --git a/mp/src/game/server/player.cpp b/mp/src/game/server/player.cpp index 956267033e..d64abc9dc3 100644 --- a/mp/src/game/server/player.cpp +++ b/mp/src/game/server/player.cpp @@ -2661,13 +2661,12 @@ bool CBasePlayer::IsValidObserverTarget(CBaseEntity * target) // MOD AUTHORS: Add checks on target here or in derived method - /* if ( !target->IsPlayer() ) // only track players return false; CBasePlayer * player = ToBasePlayer( target ); - /* Don't spec observers or players who haven't picked a class yet + /* Don't spec observers or players who haven't picked a class yet */ if ( player->IsObserver() ) return false; @@ -2687,7 +2686,7 @@ bool CBasePlayer::IsValidObserverTarget(CBaseEntity * target) return false; // allow watching until 3 seconds after death to see death animation } } - */ + // check forcecamera settings for active players if ( GetTeamNumber() != TEAM_SPECTATOR ) { From 59ab60c71f78786c9a47c84b92ed20850d653348 Mon Sep 17 00:00:00 2001 From: RabsRincon Date: Thu, 5 May 2016 06:37:33 +0200 Subject: [PATCH 017/101] KeyPresses now stick a litle bit after being released. Go figure out if there is a better timming --- .../game/client/momentum/ui/hud_keypress.cpp | 46 ++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/mp/src/game/client/momentum/ui/hud_keypress.cpp b/mp/src/game/client/momentum/ui/hud_keypress.cpp index 40a46c8a7b..b052de7faa 100644 --- a/mp/src/game/client/momentum/ui/hud_keypress.cpp +++ b/mp/src/game/client/momentum/ui/hud_keypress.cpp @@ -15,6 +15,8 @@ #include "mom_player_shared.h" #include "mom_event_listener.h" +#define KEYDRAW_MIN 0.05f + using namespace vgui; static ConVar showkeys("mom_showkeypresses", "1", FCVAR_CLIENTDLL | FCVAR_ARCHIVE | FCVAR_REPLICATED, @@ -72,6 +74,12 @@ class CHudKeyPressDisplay : public CHudElement, public Panel wchar_t m_pwright[BUFSIZESHORT]; wchar_t m_pwjump[BUFSIZELOCL]; wchar_t m_pwduck[BUFSIZELOCL]; + float m_fFwdColorUntil; + float m_fLeftColorUntil; + float m_fBackColorUntil; + float m_fRightColorUntil; + float m_fJumpColorUntil; + float m_fDuckColorUntil; }; DECLARE_HUDELEMENT(CHudKeyPressDisplay); @@ -97,6 +105,8 @@ void CHudKeyPressDisplay::Init() Q_wcsncpy(m_pwjump, uJumpUnicode, sizeof(m_pwjump)); //use buffer-safe wcscpy so we don't crash on startup if localization file is corrupted wchar_t *uDuckUnicode = g_pVGuiLocalize->Find("#MOM_Duck"); Q_wcsncpy(m_pwduck, uDuckUnicode, sizeof(m_pwduck)); + + m_fFwdColorUntil = m_fLeftColorUntil = m_fBackColorUntil = m_fRightColorUntil = m_fJumpColorUntil = m_fDuckColorUntil = 0; } void CHudKeyPressDisplay::Paint() { @@ -105,24 +115,40 @@ void CHudKeyPressDisplay::Paint() //then, color the key in if it's pressed surface()->DrawSetTextColor(m_Normal); surface()->DrawSetTextFont(m_hTextFont); - if (m_nButtons & IN_FORWARD) + if (m_nButtons & IN_FORWARD || gpGlobals->curtime < m_fFwdColorUntil) { + if (m_nButtons & IN_FORWARD) + { + m_fFwdColorUntil = gpGlobals->curtime + KEYDRAW_MIN; + } surface()->DrawSetTextPos(GetTextCenter(m_hTextFont, m_pwfwd), top_row_ypos); surface()->DrawPrintText(m_pwfwd, wcslen(m_pwfwd)); } - if (m_nButtons & IN_MOVELEFT) + if (m_nButtons & IN_MOVELEFT || gpGlobals->curtime < m_fLeftColorUntil) { + if (m_nButtons & IN_MOVELEFT) + { + m_fLeftColorUntil = gpGlobals->curtime + KEYDRAW_MIN; + } int text_left = GetTextCenter(m_hTextFont, m_pwleft) - UTIL_ComputeStringWidth(m_hTextFont, m_pwleft); surface()->DrawSetTextPos(text_left, mid_row_ypos); surface()->DrawPrintText(m_pwleft, wcslen(m_pwleft)); } - if (m_nButtons & IN_BACK) + if (m_nButtons & IN_BACK || gpGlobals->curtime < m_fBackColorUntil) { + if (m_nButtons & IN_BACK) + { + m_fBackColorUntil = gpGlobals->curtime + KEYDRAW_MIN; + } surface()->DrawSetTextPos(GetTextCenter(m_hTextFont, m_pwback), lower_row_ypos); surface()->DrawPrintText(m_pwback, wcslen(m_pwback)); } - if (m_nButtons & IN_MOVERIGHT) + if (m_nButtons & IN_MOVERIGHT || gpGlobals->curtime < m_fRightColorUntil) { + if (m_nButtons & IN_MOVERIGHT) + { + m_fRightColorUntil = gpGlobals->curtime + KEYDRAW_MIN; + } int text_right = GetTextCenter(m_hTextFont, m_pwright) + UTIL_ComputeStringWidth(m_hTextFont, m_pwright); surface()->DrawSetTextPos(text_right, mid_row_ypos); surface()->DrawPrintText(m_pwright, wcslen(m_pwright)); @@ -130,13 +156,21 @@ void CHudKeyPressDisplay::Paint() //reset text font for jump/duck surface()->DrawSetTextFont(m_hWordTextFont); - if (m_nButtons & IN_JUMP) + if (m_nButtons & IN_JUMP || gpGlobals->curtime < m_fJumpColorUntil) { + if (m_nButtons & IN_JUMP) + { + m_fJumpColorUntil = gpGlobals->curtime + KEYDRAW_MIN; + } surface()->DrawSetTextPos(GetTextCenter(m_hWordTextFont, m_pwjump), jump_row_ypos); surface()->DrawPrintText(m_pwjump, wcslen(m_pwjump)); } - if (m_nButtons & IN_DUCK) + if (m_nButtons & IN_DUCK || gpGlobals->curtime < m_fDuckColorUntil) { + if (m_nButtons & IN_DUCK) + { + m_fDuckColorUntil = gpGlobals->curtime + KEYDRAW_MIN; + } surface()->DrawSetTextPos(GetTextCenter(m_hWordTextFont, m_pwduck), duck_row_ypos); surface()->DrawPrintText(m_pwduck, wcslen(m_pwduck)); } From 0f594b29cdd0b92224085f31b7800f80b630e606 Mon Sep 17 00:00:00 2001 From: RabsRincon Date: Thu, 5 May 2016 06:59:35 +0200 Subject: [PATCH 018/101] Credits can now go faster if ATTACK button is pressed --- mp/src/game/client/hl2/hud_credits.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/mp/src/game/client/hl2/hud_credits.cpp b/mp/src/game/client/hl2/hud_credits.cpp index 6ad1488ba6..2eb6c54956 100644 --- a/mp/src/game/client/hl2/hud_credits.cpp +++ b/mp/src/game/client/hl2/hud_credits.cpp @@ -17,6 +17,8 @@ #include #include "KeyValues.h" #include "filesystem.h" +#include "in_buttons.h" +#include "input.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -272,6 +274,12 @@ void CHudCredits::DrawOutroCreditsName( void ) int iWidth, iTall; GetHudSize(iWidth, iTall); SetSize( iWidth, iTall ); + float m_flDesiredScrollTime = m_flScrollTime; + int m_nButtons = ::input->GetButtonBits(1); + if (m_nButtons & IN_ATTACK) + { + m_flDesiredScrollTime = m_flScrollTime * 0.4; + } for ( int i = 0; i < m_CreditsList.Count(); i++ ) { @@ -302,14 +310,14 @@ void CHudCredits::DrawOutroCreditsName( void ) { if ( m_bLastOneInPlace == false ) { - pCredit->flYPos -= gpGlobals->frametime * ( (float)g_iCreditsPixelHeight / m_flScrollTime ); + pCredit->flYPos -= gpGlobals->frametime * ((float)g_iCreditsPixelHeight / m_flDesiredScrollTime); if ( (int)pCredit->flYPos + ( iFontTall / 2 ) <= iTall / 2 ) { m_bLastOneInPlace = true; // 360 certification requires that we not hold a static image too long. - m_flFadeTime = gpGlobals->curtime + ( IsConsole() ? 2.0f : 10.0f ); + m_flFadeTime = gpGlobals->curtime + (IsConsole() ? 2.0f : ((m_nButtons & IN_ATTACK2) ? 4.0f : 10.0f)); } } else @@ -318,7 +326,7 @@ void CHudCredits::DrawOutroCreditsName( void ) { if ( m_Alpha > 0 ) { - m_Alpha -= gpGlobals->frametime * ( m_flScrollTime * 2 ); + m_Alpha -= gpGlobals->frametime * (m_flDesiredScrollTime * 2); if ( m_Alpha <= 0 ) { @@ -333,7 +341,7 @@ void CHudCredits::DrawOutroCreditsName( void ) } else { - pCredit->flYPos -= gpGlobals->frametime * ( (float)g_iCreditsPixelHeight / m_flScrollTime ); + pCredit->flYPos -= gpGlobals->frametime * ((float)g_iCreditsPixelHeight / m_flDesiredScrollTime); } if ( pCredit->bActive == false ) From f5af85fdf1e19800042c01328487e55f9c5ba9c8 Mon Sep 17 00:00:00 2001 From: RabsRincon Date: Thu, 5 May 2016 07:06:42 +0200 Subject: [PATCH 019/101] Only stick JUMP/DUCK texts. --- .../game/client/momentum/ui/hud_keypress.cpp | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/mp/src/game/client/momentum/ui/hud_keypress.cpp b/mp/src/game/client/momentum/ui/hud_keypress.cpp index b052de7faa..a0d525fdfd 100644 --- a/mp/src/game/client/momentum/ui/hud_keypress.cpp +++ b/mp/src/game/client/momentum/ui/hud_keypress.cpp @@ -74,10 +74,6 @@ class CHudKeyPressDisplay : public CHudElement, public Panel wchar_t m_pwright[BUFSIZESHORT]; wchar_t m_pwjump[BUFSIZELOCL]; wchar_t m_pwduck[BUFSIZELOCL]; - float m_fFwdColorUntil; - float m_fLeftColorUntil; - float m_fBackColorUntil; - float m_fRightColorUntil; float m_fJumpColorUntil; float m_fDuckColorUntil; }; @@ -115,40 +111,24 @@ void CHudKeyPressDisplay::Paint() //then, color the key in if it's pressed surface()->DrawSetTextColor(m_Normal); surface()->DrawSetTextFont(m_hTextFont); - if (m_nButtons & IN_FORWARD || gpGlobals->curtime < m_fFwdColorUntil) + if (m_nButtons & IN_FORWARD) { - if (m_nButtons & IN_FORWARD) - { - m_fFwdColorUntil = gpGlobals->curtime + KEYDRAW_MIN; - } surface()->DrawSetTextPos(GetTextCenter(m_hTextFont, m_pwfwd), top_row_ypos); surface()->DrawPrintText(m_pwfwd, wcslen(m_pwfwd)); } - if (m_nButtons & IN_MOVELEFT || gpGlobals->curtime < m_fLeftColorUntil) + if (m_nButtons & IN_MOVELEFT) { - if (m_nButtons & IN_MOVELEFT) - { - m_fLeftColorUntil = gpGlobals->curtime + KEYDRAW_MIN; - } int text_left = GetTextCenter(m_hTextFont, m_pwleft) - UTIL_ComputeStringWidth(m_hTextFont, m_pwleft); surface()->DrawSetTextPos(text_left, mid_row_ypos); surface()->DrawPrintText(m_pwleft, wcslen(m_pwleft)); } - if (m_nButtons & IN_BACK || gpGlobals->curtime < m_fBackColorUntil) + if (m_nButtons & IN_BACK) { - if (m_nButtons & IN_BACK) - { - m_fBackColorUntil = gpGlobals->curtime + KEYDRAW_MIN; - } surface()->DrawSetTextPos(GetTextCenter(m_hTextFont, m_pwback), lower_row_ypos); surface()->DrawPrintText(m_pwback, wcslen(m_pwback)); } - if (m_nButtons & IN_MOVERIGHT || gpGlobals->curtime < m_fRightColorUntil) + if (m_nButtons & IN_MOVERIGHT) { - if (m_nButtons & IN_MOVERIGHT) - { - m_fRightColorUntil = gpGlobals->curtime + KEYDRAW_MIN; - } int text_right = GetTextCenter(m_hTextFont, m_pwright) + UTIL_ComputeStringWidth(m_hTextFont, m_pwright); surface()->DrawSetTextPos(text_right, mid_row_ypos); surface()->DrawPrintText(m_pwright, wcslen(m_pwright)); From 1584cce85da443f1c3c9bbf0d99499e69d7e0a0e Mon Sep 17 00:00:00 2001 From: tuxxi Date: Wed, 4 May 2016 22:13:05 -0700 Subject: [PATCH 020/101] fixed strafe sync and key stats while watching a replay --- mp/src/game/server/momentum/mom_replay.cpp | 13 ++- mp/src/game/server/momentum/mom_replay.h | 5 +- .../server/momentum/mom_replay_entity.cpp | 103 ++++++++++++++---- .../game/server/momentum/mom_replay_entity.h | 6 + mp/src/game/server/momentum/replayformat.h | 1 - 5 files changed, 101 insertions(+), 27 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 3b18d38fac..0b43b7a407 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -8,7 +8,7 @@ void CMomentumReplaySystem::BeginRecording(CBasePlayer *pPlayer) { m_player = ToCMOMPlayer( pPlayer); - if (!m_bIsWatchingReplay) //don't record if we're watching a preexisting replay + if (!m_player->m_bIsWatchingReplay) //don't record if we're watching a preexisting replay { m_bIsRecording = true; Log("Recording began!\n"); @@ -146,6 +146,7 @@ bool CMomentumReplaySystem::LoadRun(const char* filename) } else { + Q_strncpy(loadedReplayMapName, header->mapName, MAX_MAP_NAME); while (!filesystem->EndOfFile(m_fhFileHandle)) { replay_frame_t* frame = ReadSingleFrame(m_fhFileHandle, filename); @@ -164,7 +165,6 @@ void CMomentumReplaySystem::StartReplay() if (ghost != nullptr) { ghost->StartRun(); - m_bIsWatchingReplay = true; } } class CMOMReplayCommands @@ -174,7 +174,14 @@ class CMOMReplayCommands { if (args.ArgC() > 1) { //we passed any argument at all if (g_ReplaySystem->LoadRun(args.ArgS())) { - g_ReplaySystem->StartReplay(); + if (!Q_strcmp(STRING(gpGlobals->mapname), g_ReplaySystem->loadedReplayMapName)) + { + g_ReplaySystem->StartReplay(); + } + else + { + Warning("Error: Tried to start replay on incorrect map! Please load map %s", g_ReplaySystem->loadedReplayMapName); + } } } } diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index aa3a8830ae..090487ba10 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -34,13 +34,12 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame bool LoadRun(const char* fileName); CUtlVector m_vecRunData; - bool IsWatchingReplay(bool isWatching) { return m_bIsWatchingReplay; } bool IsRecording(CBasePlayer *pPlayer) { return m_bIsRecording; } - + char loadedReplayMapName[MAX_MAP_NAME]; private: CUtlBuffer *UpdateRecordingParams(); //called every game frame after entities think and update - bool m_bIsRecording, m_bIsWatchingReplay; + bool m_bIsRecording, m_bTimerRunning; int m_nCurrentTick; CMomentumPlayer *m_player; diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 2cfe8150fd..d12db4f38a 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -5,10 +5,12 @@ static ConVar mom_replay_firstperson("mom_replay_firstperson", "0", FCVAR_CLIENTCMD_CAN_EXECUTE, "Watch replay in first-person", true, 0, true, 1); +static ConVar mom_replay_reverse("mom_replay_reverse", "0", + FCVAR_CLIENTCMD_CAN_EXECUTE, "Reverse playback of replay", true, 0, true, 1); static ConVar mom_replay_ghost_bodygroup("mom_replay_ghost_bodygroup", "11", FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE, "Replay ghost's body group (model)", true, 0, true, 14); static ConCommand mom_replay_ghost_color("mom_replay_ghost_color", - CMomentumReplayGhostEntity::SetGhostColor, "Set the ghost's color. Accepts HEX color value in format RRGGBBAA", FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE); + CMomentumReplayGhostEntity::SetGhostColor, "Set the ghost's color. Accepts HEX color value in format RRGGBB", FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE); static ConVar mom_replay_ghost_alpha("mom_replay_ghost_alpha", "75", FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE, "Sets the ghost's transparency, integer between 0 and 255,", true, 0, true, 255); @@ -39,12 +41,11 @@ void CMomentumReplayGhostEntity::Spawn(void) BaseClass::Spawn(); Precache(); RemoveEffects(EF_NODRAW); - SetSolid(SOLID_NONE); SetRenderMode(kRenderTransColor); - SetRenderColor(m_ghostColor.r(), m_ghostColor.g(), m_ghostColor.b()); - SetRenderColorA(75); + SetRenderColor(m_ghostColor.r(), m_ghostColor.g(), m_ghostColor.b(), 75); SetMoveType(MOVETYPE_NOCLIP); - m_bIsActive = true; + RemoveSolidFlags(FSOLID_NOT_SOLID); + AddSolidFlags(FSOLID_TRIGGER); SetModel(GHOST_MODEL); SetBodygroup(1, mom_replay_ghost_bodygroup.GetInt()); } @@ -52,7 +53,6 @@ void CMomentumReplayGhostEntity::Spawn(void) void CMomentumReplayGhostEntity::StartRun() { Spawn(); - //Msg("Starting run with Rundata: %i, Step %i, Name %s, Starttime: %f, This: %i\n", RunData.size(), step, m_gName, startTime, this); m_nStartTick = gpGlobals->curtime; m_bIsActive = true; step = 1; @@ -62,7 +62,15 @@ void CMomentumReplayGhostEntity::StartRun() void CMomentumReplayGhostEntity::updateStep() { currentStep = g_ReplaySystem->m_vecRunData[step]; - nextStep = g_ReplaySystem->m_vecRunData[++step]; + + if (mom_replay_reverse.GetBool()) + { + nextStep = g_ReplaySystem->m_vecRunData[--step]; + } + else + { + nextStep = g_ReplaySystem->m_vecRunData[++step]; + } } //----------------------------------------------------------------------------- // Purpose: Think function to move the ghost @@ -70,11 +78,13 @@ void CMomentumReplayGhostEntity::updateStep() void CMomentumReplayGhostEntity::Think(void) { BaseClass::Think(); - if (step < g_ReplaySystem->m_vecRunData.Count()) { + if (step < g_ReplaySystem->m_vecRunData.Count() && step >= 1) + { updateStep(); HandleGhost(); - } - else { + } + else + { EndRun(); } SetNextThink(gpGlobals->curtime + gpGlobals->interval_per_tick); @@ -92,7 +102,6 @@ void CMomentumReplayGhostEntity::HandleGhost() { if (currentStep.m_vPlayerOrigin.x == 0.0f) return; - SetAbsOrigin(currentStep.m_vPlayerOrigin); //always set our origin CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); if (pPlayer) { @@ -103,22 +112,35 @@ void CMomentumReplayGhostEntity::HandleGhost() { { pPlayer->SetObserverTarget(this); pPlayer->StartObserverMode(OBS_MODE_IN_EYE); - pPlayer->RemoveSolidFlags(FSOLID_NOT_SOLID); //allow the player to trigger the timer start/zones/etc } - pPlayer->ForceObserverMode(OBS_MODE_IN_EYE); //don't allow other observer modes... - + pPlayer->RemoveSolidFlags(FSOLID_NOT_SOLID); + + if (pPlayer->GetObserverMode() != (OBS_MODE_IN_EYE | OBS_MODE_CHASE)) { + //we don't want to allow any other obs modes, only IN EYE and CHASE + pPlayer->ForceObserverMode(OBS_MODE_IN_EYE); + } + if (pPlayer->GetObserverMode() == OBS_MODE_IN_EYE) { + //for some reason we can't change the view offset so we have to modify the origin when spectating in first person. + SetAbsOrigin(currentStep.m_vPlayerOrigin + VEC_VIEW); + SetAbsAngles(currentStep.m_qEyeAngles); + } + else { + SetAbsOrigin(currentStep.m_vPlayerOrigin); + SetAbsAngles(QAngle(currentStep.m_qEyeAngles.x / 10, //we divide x angle (pitch) by 10 so the ghost doesn't look really stupid + currentStep.m_qEyeAngles.y, currentStep.m_qEyeAngles.z)); + } + //interpolate vel from difference in origin float distX = fabs(currentStep.m_vPlayerOrigin.x - nextStep.m_vPlayerOrigin.x); float distY= fabs(currentStep.m_vPlayerOrigin.y - nextStep.m_vPlayerOrigin.y); float distZ = fabs(currentStep.m_vPlayerOrigin.z - nextStep.m_vPlayerOrigin.z); Vector interpolatedVel = Vector(distX, distY, distZ) / gpGlobals->interval_per_tick; - - SetAbsAngles(currentStep.m_qEyeAngles); SetAbsVelocity(interpolatedVel); - SetAbsOrigin(currentStep.m_vPlayerOrigin + VEC_VIEW); pPlayer->m_nReplayButtons = currentStep.m_nPlayerButtons; //networked var that allows the replay to control keypress display on the client - pPlayer->SnapEyeAngles(currentStep.m_qEyeAngles); + + if (g_Timer.IsRunning()) + UpdateStats(interpolatedVel, pPlayer); if (currentStep.m_nPlayerButtons & IN_DUCK) { @@ -128,10 +150,12 @@ void CMomentumReplayGhostEntity::HandleGhost() { } else //we're watching/racing with a ghost { + SetAbsOrigin(currentStep.m_vPlayerOrigin); SetAbsAngles(QAngle(currentStep.m_qEyeAngles.x / 10, //we divide x angle (pitch) by 10 so the ghost doesn't look really stupid currentStep.m_qEyeAngles.y, currentStep.m_qEyeAngles.z)); - if (pPlayer->IsObserver()) //bring the player out of obs mode if theyre current observing + if (pPlayer->IsObserver()) //bring the player out of obs mode if theyre currently observing { + pPlayer->m_bIsWatchingReplay = false; pPlayer->StopObserverMode(); pPlayer->ForceRespawn(); pPlayer->SetMoveType(MOVETYPE_WALK); @@ -158,7 +182,47 @@ void CMomentumReplayGhostEntity::HandleGhost() { SetRenderColorA(mom_replay_ghost_alpha.GetInt()); } } +void CMomentumReplayGhostEntity::UpdateStats(Vector ghostVel, CMomentumPlayer *pPlayer) +{ + // --- STRAFE SYNC --- + //calculate strafe sync based on replay ghost's movement, in order to update the player's HUD + + float SyncVelocity = ghostVel.Length2DSqr(); //we always want HVEL for checking velocity sync + if (!(pPlayer->GetFlags() & (FL_ONGROUND | FL_INWATER)) && pPlayer->GetMoveType() != MOVETYPE_LADDER) + { + if (EyeAngles().y > m_qLastEyeAngle.y) //player turned left + { + m_nStrafeTicks++; + if ((currentStep.m_nPlayerButtons & IN_MOVELEFT) && !(currentStep.m_nPlayerButtons & IN_MOVERIGHT)) + m_nPerfectSyncTicks++; + if (SyncVelocity > m_flLastSyncVelocity) + m_nAccelTicks++; + } + else if (EyeAngles().y < m_qLastEyeAngle.y) //player turned right + { + m_nStrafeTicks++; + if ((currentStep.m_nPlayerButtons & IN_MOVERIGHT) && !(currentStep.m_nPlayerButtons & IN_MOVELEFT)) + m_nPerfectSyncTicks++; + if (SyncVelocity > m_flLastSyncVelocity) + m_nAccelTicks++; + } + } + if (m_nStrafeTicks && m_nAccelTicks && m_nPerfectSyncTicks) + { + pPlayer->m_flStrafeSync = ((float)m_nPerfectSyncTicks / (float)m_nStrafeTicks) * 100; // ticks strafing perfectly / ticks strafing + pPlayer->m_flStrafeSync2 = ((float)m_nAccelTicks / (float)m_nStrafeTicks) * 100; // ticks gaining speed / ticks strafing + } + // --- JUMP AND STRAFE COUNTER --- + if (ghostVel.z == 0 && currentStep.m_nPlayerButtons & IN_JUMP) + pPlayer->m_nStageJumps[0]++; + if ((currentStep.m_nPlayerButtons & IN_MOVELEFT && !(m_nOldReplayButtons & IN_MOVELEFT)) + || (currentStep.m_nPlayerButtons & IN_MOVERIGHT && !(m_nOldReplayButtons & IN_MOVERIGHT)) ) + pPlayer->m_nStageStrafes[0]++; + m_flLastSyncVelocity = SyncVelocity; + m_qLastEyeAngle = EyeAngles(); + m_nOldReplayButtons = currentStep.m_nPlayerButtons; +} void CMomentumReplayGhostEntity::SetGhostModel(const char * newmodel) { if (newmodel) @@ -191,7 +255,6 @@ void CMomentumReplayGhostEntity::EndRun() SetNextThink(-1); Remove(); m_bIsActive = false; - g_ReplaySystem->IsWatchingReplay(false); g_Timer.Stop(false); CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index 3e2a9a0c02..cc7afd9906 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -40,6 +40,7 @@ class CMomentumReplayGhostEntity : public CBaseAnimating void EndRun(); void StartRun(); void HandleGhost(); + void UpdateStats(Vector ghostVel, CMomentumPlayer *pPlayer); //for hud display.. bool m_bIsActive; int m_nStartTick; @@ -58,4 +59,9 @@ class CMomentumReplayGhostEntity : public CBaseAnimating int m_iBodyGroup = BODY_PROLATE_ELLIPSE; Color m_ghostColor; static Color m_newGhostColor; + + //for faking strafe sync calculations + QAngle m_qLastEyeAngle; + float m_flLastSyncVelocity; + int m_nStrafeTicks, m_nPerfectSyncTicks, m_nAccelTicks, m_nOldReplayButtons; }; diff --git a/mp/src/game/server/momentum/replayformat.h b/mp/src/game/server/momentum/replayformat.h index b44dec985e..5147c35a71 100644 --- a/mp/src/game/server/momentum/replayformat.h +++ b/mp/src/game/server/momentum/replayformat.h @@ -50,7 +50,6 @@ struct replay_header_t int m_nStageJumps[MAX_STAGES], m_nStageStrafes[MAX_STAGES]; float m_flStageVelocityMax[MAX_STAGES], m_flStageVelocityAvg[MAX_STAGES], m_flStageStrafeSyncAvg[MAX_STAGES], m_flStageStrafeSync2Avg[MAX_STAGES], m_flStageEnterVelocity[MAX_STAGES]; - }; //byteswap for int and float members of header, swaps the endianness (byte order) in order to read correctly inline void ByteSwap_replay_header_t(replay_header_t &swap) From 59b624d166dde5da5415e5c7ccf843f4cc6d6f0c Mon Sep 17 00:00:00 2001 From: tuxxi Date: Wed, 4 May 2016 22:18:47 -0700 Subject: [PATCH 021/101] fix compile bug + tweak timings for keypres stick --- mp/src/game/client/momentum/ui/hud_keypress.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mp/src/game/client/momentum/ui/hud_keypress.cpp b/mp/src/game/client/momentum/ui/hud_keypress.cpp index a0d525fdfd..accd04e592 100644 --- a/mp/src/game/client/momentum/ui/hud_keypress.cpp +++ b/mp/src/game/client/momentum/ui/hud_keypress.cpp @@ -15,7 +15,7 @@ #include "mom_player_shared.h" #include "mom_event_listener.h" -#define KEYDRAW_MIN 0.05f +#define KEYDRAW_MIN 0.07f using namespace vgui; @@ -102,7 +102,7 @@ void CHudKeyPressDisplay::Init() wchar_t *uDuckUnicode = g_pVGuiLocalize->Find("#MOM_Duck"); Q_wcsncpy(m_pwduck, uDuckUnicode, sizeof(m_pwduck)); - m_fFwdColorUntil = m_fLeftColorUntil = m_fBackColorUntil = m_fRightColorUntil = m_fJumpColorUntil = m_fDuckColorUntil = 0; + m_fJumpColorUntil = m_fDuckColorUntil = 0; } void CHudKeyPressDisplay::Paint() { From beef06ef1573a5f89ed1f55aaf2dd5c57c0a3673 Mon Sep 17 00:00:00 2001 From: RabsRincon Date: Thu, 5 May 2016 19:52:19 +0200 Subject: [PATCH 022/101] replay entity quickfix --- mp/src/game/server/momentum/mom_replay_entity.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index d12db4f38a..f78cb47cdc 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -4,7 +4,7 @@ #include "Timer.h" static ConVar mom_replay_firstperson("mom_replay_firstperson", "0", - FCVAR_CLIENTCMD_CAN_EXECUTE, "Watch replay in first-person", true, 0, true, 1); + FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE, "Watch replay in first-person", true, 0, true, 1); static ConVar mom_replay_reverse("mom_replay_reverse", "0", FCVAR_CLIENTCMD_CAN_EXECUTE, "Reverse playback of replay", true, 0, true, 1); static ConVar mom_replay_ghost_bodygroup("mom_replay_ghost_bodygroup", "11", @@ -258,7 +258,7 @@ void CMomentumReplayGhostEntity::EndRun() g_Timer.Stop(false); CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); - if (pPlayer->IsObserver() && pPlayer) + if (pPlayer && pPlayer->IsObserver()) { pPlayer->StopObserverMode(); pPlayer->ForceRespawn(); From 59379f828c30eed6e20b31a6b0ca1f59b59040df Mon Sep 17 00:00:00 2001 From: RSTFS Date: Wed, 11 May 2016 00:19:01 -0400 Subject: [PATCH 023/101] Wait for a second on end touch to stop recording. The new function behaves as before if the delay arg is false. --- mp/src/game/server/momentum/mom_replay.cpp | 44 ++++++++++++------- mp/src/game/server/momentum/mom_replay.h | 10 ++++- .../server/momentum/mom_replay_entity.cpp | 5 ++- .../game/server/momentum/mom_replay_entity.h | 1 + mp/src/game/server/momentum/mom_triggers.cpp | 4 +- 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 0b43b7a407..3e50f98cee 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -15,30 +15,40 @@ void CMomentumReplaySystem::BeginRecording(CBasePlayer *pPlayer) m_nCurrentTick = 1; //recoring begins at 1 ;) } } -void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway, float delay) +void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway, bool delay) { - m_bIsRecording = false; if (throwaway) { + m_bIsRecording = false; m_buf->Purge(); return; } - CMomentumPlayer *pMOMPlayer = ToCMOMPlayer(pPlayer); - char newRecordingName[MAX_PATH], newRecordingPath[MAX_PATH], runTime[BUFSIZETIME]; - mom_UTIL.FormatTime(g_Timer.GetLastRunTimeTicks(), gpGlobals->interval_per_tick, runTime); - Q_snprintf(newRecordingName, MAX_PATH, "%s_%s_%s.momrec", pMOMPlayer->GetPlayerName(), gpGlobals->mapname.ToCStr(), runTime); - V_ComposeFileName(RECORDING_PATH, newRecordingName, newRecordingPath, MAX_PATH); //V_ComposeFileName calls all relevent filename functions for us! THANKS GABEN + if (delay) + { + m_bShouldStopRec = true; + m_fRecEndTime = gpGlobals->curtime; + } + else + { + m_bIsRecording = false; + m_bShouldStopRec = false; + CMomentumPlayer *pMOMPlayer = ToCMOMPlayer(pPlayer); + char newRecordingName[MAX_PATH], newRecordingPath[MAX_PATH], runTime[BUFSIZETIME]; + mom_UTIL.FormatTime(g_Timer.GetLastRunTimeTicks(), gpGlobals->interval_per_tick, runTime); + Q_snprintf(newRecordingName, MAX_PATH, "%s_%s_%s.momrec", pMOMPlayer->GetPlayerName(), gpGlobals->mapname.ToCStr(), runTime); + V_ComposeFileName(RECORDING_PATH, newRecordingName, newRecordingPath, MAX_PATH); //V_ComposeFileName calls all relevent filename functions for us! THANKS GABEN - V_FixSlashes(RECORDING_PATH); - filesystem->CreateDirHierarchy(RECORDING_PATH, "MOD"); //we have to create the directory here just in case it doesnt exist yet + V_FixSlashes(RECORDING_PATH); + filesystem->CreateDirHierarchy(RECORDING_PATH, "MOD"); //we have to create the directory here just in case it doesnt exist yet - m_fhFileHandle = filesystem->Open(newRecordingPath, "w+b", "MOD"); + m_fhFileHandle = filesystem->Open(newRecordingPath, "w+b", "MOD"); - WriteRecordingToFile(*m_buf); + WriteRecordingToFile(*m_buf); - filesystem->Close(m_fhFileHandle); - Log("Recording Stopped! Ticks: %i\n", m_nCurrentTick); - if( LoadRun(newRecordingName) ) //load the last run that we did in case we want to watch it - StartReplay(); + filesystem->Close(m_fhFileHandle); + Log("Recording Stopped! Ticks: %i\n", m_nCurrentTick); + if( LoadRun(newRecordingName) ) //load the last run that we did in case we want to watch it + StartReplay(); + } } CUtlBuffer *CMomentumReplaySystem::UpdateRecordingParams() { @@ -51,6 +61,10 @@ CUtlBuffer *CMomentumReplaySystem::UpdateRecordingParams() ByteSwap_replay_frame_t(m_currentFrame); //We need to byteswap all of our data first in order to write each byte in the correct order + if(m_bShouldStopRec) + if (gpGlobals->curtime - m_fRecEndTime >= END_RECORDING_PAUSE) + StopRecording(UTIL_GetLocalPlayer(), false, false); + Assert(buf.IsValid()); buf.Put(&m_currentFrame, sizeof(replay_frame_t)); //stick all the frame info into the buffer return &buf; diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index 090487ba10..02385cf9b2 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -9,6 +9,7 @@ #include "replayformat.h" #define RECORDING_PATH "recordings" +#define END_RECORDING_PAUSE 1.0 class CMomentumReplaySystem : CAutoGameSystemPerFrame { @@ -22,7 +23,7 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame } } void BeginRecording(CBasePlayer *pPlayer); - void StopRecording(CBasePlayer *pPlayer, bool throwaway, float delay = 1.0f); + void StopRecording(CBasePlayer *pPlayer, bool throwaway, bool delay); void WriteRecordingToFile(CUtlBuffer &buf); replay_header_t CreateHeader(); void WriteRecordingToFile(); @@ -35,12 +36,17 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame CUtlVector m_vecRunData; bool IsRecording(CBasePlayer *pPlayer) { return m_bIsRecording; } + char loadedReplayMapName[MAX_MAP_NAME]; + bool m_bIsWatchingReplay; + private: CUtlBuffer *UpdateRecordingParams(); //called every game frame after entities think and update - bool m_bIsRecording, m_bTimerRunning; + bool m_bIsRecording; + bool m_bShouldStopRec; int m_nCurrentTick; + float m_fRecEndTime; CMomentumPlayer *m_player; diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index f78cb47cdc..71c78a28f3 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -3,8 +3,9 @@ #include "util/mom_util.h" #include "Timer.h" -static ConVar mom_replay_firstperson("mom_replay_firstperson", "0", - FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE, "Watch replay in first-person", true, 0, true, 1); + +static ConVar mom_replay_firstperson("mom_replay_firstperson", "1", + FCVAR_CLIENTCMD_CAN_EXECUTE, "Watch replay in first-person", true, 0, true, 1); static ConVar mom_replay_reverse("mom_replay_reverse", "0", FCVAR_CLIENTCMD_CAN_EXECUTE, "Reverse playback of replay", true, 0, true, 1); static ConVar mom_replay_ghost_bodygroup("mom_replay_ghost_bodygroup", "11", diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index cc7afd9906..7de2169210 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -30,6 +30,7 @@ class CMomentumReplayGhostEntity : public CBaseAnimating DECLARE_CLASS(CMomentumReplayGhostEntity, CBaseAnimating); DECLARE_DATADESC(); public: + ~CMomentumReplayGhostEntity(){ g_ReplaySystem->m_bIsWatchingReplay = false;} const char* GetGhostModel(); void SetGhostModel(const char* model); void SetGhostBodyGroup(int bodyGroup); diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index fc10bcf209..3b382549d9 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -155,7 +155,7 @@ void CTriggerTimerStart::StartTouch(CBaseEntity *pOther) g_ReplaySystem->BeginRecording(pPlayer); else { - g_ReplaySystem->StopRecording(pPlayer, true); + g_ReplaySystem->StopRecording(pPlayer, true, false); g_ReplaySystem->BeginRecording(pPlayer); } } @@ -256,7 +256,7 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) g_Timer.Stop(true); //stop demo recording if (g_ReplaySystem->IsRecording(pPlayer)) - g_ReplaySystem->StopRecording(ToCMOMPlayer(pOther), false); + g_ReplaySystem->StopRecording(ToCMOMPlayer(pOther), false, true); } if (mapZoneEvent) { From e12244ebbd6dde3cd2692520d94cdc2fd1c0959f Mon Sep 17 00:00:00 2001 From: tuxxi Date: Thu, 12 May 2016 16:27:35 -0700 Subject: [PATCH 024/101] added filename autocomplete for replays, better commands for playing replays broken: we need a way to not draw the ghost entity when we are spectating in first person, because for some reason AddEffects(EF_NODRAW) makes it so the abs angles are never changed. I much perfer to have the view offset changed rather than change the origin, because changing the origin breaks a lot of other stuff, namely GetGroundEntity() --- mp/src/game/client/client_momentum.vpc | 3 + mp/src/game/server/momentum/mom_replay.cpp | 45 ++++++--- mp/src/game/server/momentum/mom_replay.h | 5 +- .../server/momentum/mom_replay_entity.cpp | 19 ++-- .../game/server/momentum/mom_replay_entity.h | 6 +- mp/src/game/server/server_momentum.vpc | 3 + .../util/baseautocompletefilelist.cpp | 98 +++++++++++++++++++ .../momentum/util/baseautocompletefilelist.h | 61 ++++++++++++ 8 files changed, 212 insertions(+), 28 deletions(-) create mode 100644 mp/src/game/shared/momentum/util/baseautocompletefilelist.cpp create mode 100644 mp/src/game/shared/momentum/util/baseautocompletefilelist.h diff --git a/mp/src/game/client/client_momentum.vpc b/mp/src/game/client/client_momentum.vpc index 01939fbaf9..1ff83efd87 100644 --- a/mp/src/game/client/client_momentum.vpc +++ b/mp/src/game/client/client_momentum.vpc @@ -132,6 +132,9 @@ $Project "Client (Momentum)" { $File "$SRCDIR\game\shared\momentum\util\mom_util.cpp" $File "$SRCDIR\game\shared\momentum\util\mom_util.h" + $File "$SRCDIR\game\shared\momentum\util\baseautocompletefilelist.cpp" + $File "$SRCDIR\game\shared\momentum\util\baseautocompletefilelist.h" + } $File "$SRCDIR\game\shared\momentum\mom_gamemovement.cpp" diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 3e50f98cee..b7fedb3a6b 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -4,6 +4,7 @@ #include "mom_replay_entity.h" #include "Timer.h" #include "util/mom_util.h" +#include "util/baseautocompletefilelist.h" void CMomentumReplaySystem::BeginRecording(CBasePlayer *pPlayer) { @@ -133,7 +134,7 @@ replay_header_t* CMomentumReplaySystem::ReadHeader(FileHandle_t file, const char ByteSwap_replay_header_t(m_replayHeader); - if (Q_strcmp(m_replayHeader.demofilestamp, DEMO_HEADER_ID)) { + if (Q_strcmp(m_replayHeader.demofilestamp, DEMO_HEADER_ID)) { //DEMO_HEADER_ID is __NOT__ the same as the stamp from the header we read from file ConMsg("%s has invalid replay header ID.\n", filename); return nullptr; } @@ -160,48 +161,68 @@ bool CMomentumReplaySystem::LoadRun(const char* filename) } else { - Q_strncpy(loadedReplayMapName, header->mapName, MAX_MAP_NAME); + m_loadedHeader = *header; while (!filesystem->EndOfFile(m_fhFileHandle)) { replay_frame_t* frame = ReadSingleFrame(m_fhFileHandle, filename); m_vecRunData.AddToTail(*frame); } - return true; } filesystem->Close(m_fhFileHandle); + return true; } else return false; } -void CMomentumReplaySystem::StartReplay() +void CMomentumReplaySystem::StartReplay(bool firstperson) { CMomentumReplayGhostEntity *ghost = static_cast(CreateEntityByName("mom_replay_ghost")); if (ghost != nullptr) { - ghost->StartRun(); + ghost->StartRun(firstperson); } } class CMOMReplayCommands { public: - static void PlayRecording(const CCommand &args) + static void StartReplay(const CCommand &args, bool firstperson) { - if (args.ArgC() > 1) { //we passed any argument at all - if (g_ReplaySystem->LoadRun(args.ArgS())) { - if (!Q_strcmp(STRING(gpGlobals->mapname), g_ReplaySystem->loadedReplayMapName)) + if (args.ArgC() > 0) //we passed any argument at all + { + char filename[MAX_PATH]; + if (Q_strstr(args.ArgS(), ".momrec")) + { + Q_snprintf(filename, MAX_PATH, "%s", args.ArgS()); + } + else + { + Q_snprintf(filename, MAX_PATH, "%s.momrec", args.ArgS()); + } + if (g_ReplaySystem->LoadRun(filename)) + { + if (!Q_strcmp(STRING(gpGlobals->mapname), g_ReplaySystem->m_loadedHeader.mapName)) { - g_ReplaySystem->StartReplay(); + g_ReplaySystem->StartReplay(firstperson); } else { - Warning("Error: Tried to start replay on incorrect map! Please load map %s", g_ReplaySystem->loadedReplayMapName); + Warning("Error: Tried to start replay on incorrect map! Please load map %s", g_ReplaySystem->m_loadedHeader.mapName); } } } } + static void PlayReplayGhost(const CCommand &args) + { + StartReplay(args, false); + } + static void PlayReplayFirstPerson(const CCommand &args) + { + StartReplay(args, true); + } }; -static ConCommand playrecording("playrecording", CMOMReplayCommands::PlayRecording, "plays a recording", FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_SERVER_CAN_EXECUTE); +CON_COMMAND_AUTOCOMPLETEFILE(playreplay_ghost, CMOMReplayCommands::PlayReplayGhost, "begins playback of a replay ghost", "recordings", momrec); +CON_COMMAND_AUTOCOMPLETEFILE(playreplay, CMOMReplayCommands::PlayReplayFirstPerson, "plays back a replay in first-person", "recordings", momrec); static CMomentumReplaySystem s_ReplaySystem("MOMReplaySystem"); CMomentumReplaySystem *g_ReplaySystem = &s_ReplaySystem; \ No newline at end of file diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index 02385cf9b2..0822cf5b5b 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -31,15 +31,14 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame replay_frame_t* ReadSingleFrame(FileHandle_t file, const char* filename); replay_header_t* ReadHeader(FileHandle_t file, const char* filename); - void StartReplay(); + void StartReplay(bool firstperson = false); bool LoadRun(const char* fileName); CUtlVector m_vecRunData; bool IsRecording(CBasePlayer *pPlayer) { return m_bIsRecording; } - char loadedReplayMapName[MAX_MAP_NAME]; + replay_header_t m_loadedHeader; bool m_bIsWatchingReplay; - private: CUtlBuffer *UpdateRecordingParams(); //called every game frame after entities think and update diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 71c78a28f3..5a818f546b 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -51,14 +51,16 @@ void CMomentumReplayGhostEntity::Spawn(void) SetBodygroup(1, mom_replay_ghost_bodygroup.GetInt()); } -void CMomentumReplayGhostEntity::StartRun() +void CMomentumReplayGhostEntity::StartRun(bool firstPerson) { + mom_replay_firstperson.SetValue(firstPerson ? "1" : "0"); Spawn(); m_nStartTick = gpGlobals->curtime; m_bIsActive = true; step = 1; SetAbsOrigin(g_ReplaySystem->m_vecRunData[0].m_vPlayerOrigin); SetNextThink(gpGlobals->curtime); + } void CMomentumReplayGhostEntity::updateStep() { @@ -114,19 +116,17 @@ void CMomentumReplayGhostEntity::HandleGhost() { pPlayer->SetObserverTarget(this); pPlayer->StartObserverMode(OBS_MODE_IN_EYE); } - pPlayer->RemoveSolidFlags(FSOLID_NOT_SOLID); if (pPlayer->GetObserverMode() != (OBS_MODE_IN_EYE | OBS_MODE_CHASE)) { //we don't want to allow any other obs modes, only IN EYE and CHASE pPlayer->ForceObserverMode(OBS_MODE_IN_EYE); } + pPlayer->SetViewOffset(VEC_VIEW); + SetAbsOrigin(currentStep.m_vPlayerOrigin); if (pPlayer->GetObserverMode() == OBS_MODE_IN_EYE) { - //for some reason we can't change the view offset so we have to modify the origin when spectating in first person. - SetAbsOrigin(currentStep.m_vPlayerOrigin + VEC_VIEW); SetAbsAngles(currentStep.m_qEyeAngles); } else { - SetAbsOrigin(currentStep.m_vPlayerOrigin); SetAbsAngles(QAngle(currentStep.m_qEyeAngles.x / 10, //we divide x angle (pitch) by 10 so the ghost doesn't look really stupid currentStep.m_qEyeAngles.y, currentStep.m_qEyeAngles.z)); } @@ -137,7 +137,6 @@ void CMomentumReplayGhostEntity::HandleGhost() { float distZ = fabs(currentStep.m_vPlayerOrigin.z - nextStep.m_vPlayerOrigin.z); Vector interpolatedVel = Vector(distX, distY, distZ) / gpGlobals->interval_per_tick; SetAbsVelocity(interpolatedVel); - pPlayer->m_nReplayButtons = currentStep.m_nPlayerButtons; //networked var that allows the replay to control keypress display on the client if (g_Timer.IsRunning()) @@ -146,8 +145,9 @@ void CMomentumReplayGhostEntity::HandleGhost() { if (currentStep.m_nPlayerButtons & IN_DUCK) { //MOM_TODO: make this smoother. possibly inherit from NPC classes/CBaseCombatCharacter - SetAbsOrigin(currentStep.m_vPlayerOrigin + VEC_DUCK_VIEW); + pPlayer->SetViewOffset(VEC_DUCK_VIEW); } + } else //we're watching/racing with a ghost { @@ -248,15 +248,15 @@ void CMomentumReplayGhostEntity::SetGhostBodyGroup(int bodyGroup) } void CMomentumReplayGhostEntity::SetGhostColor(const CCommand &args) { - if (mom_UTIL.GetColorFromHex(args.ArgS())) + if (mom_UTIL.GetColorFromHex(args.ArgS())) { CMomentumReplayGhostEntity::m_newGhostColor = *mom_UTIL.GetColorFromHex(args.ArgS()); + } } void CMomentumReplayGhostEntity::EndRun() { SetNextThink(-1); Remove(); m_bIsActive = false; - g_Timer.Stop(false); CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); if (pPlayer && pPlayer->IsObserver()) @@ -265,6 +265,5 @@ void CMomentumReplayGhostEntity::EndRun() pPlayer->ForceRespawn(); pPlayer->SetMoveType(MOVETYPE_WALK); pPlayer->m_bIsWatchingReplay = false; - pPlayer->m_flLastJumpVel = 0; } } \ No newline at end of file diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index 7de2169210..5097bfee02 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -25,9 +25,9 @@ enum ghostModelBodyGroup BODY_CYLINDER }; -class CMomentumReplayGhostEntity : public CBaseAnimating +class CMomentumReplayGhostEntity : public CBaseCombatCharacter { - DECLARE_CLASS(CMomentumReplayGhostEntity, CBaseAnimating); + DECLARE_CLASS(CMomentumReplayGhostEntity, CBaseCombatCharacter); DECLARE_DATADESC(); public: ~CMomentumReplayGhostEntity(){ g_ReplaySystem->m_bIsWatchingReplay = false;} @@ -39,7 +39,7 @@ class CMomentumReplayGhostEntity : public CBaseAnimating void updateStep(); void EndRun(); - void StartRun(); + void StartRun(bool firstPerson = false); void HandleGhost(); void UpdateStats(Vector ghostVel, CMomentumPlayer *pPlayer); //for hud display.. diff --git a/mp/src/game/server/server_momentum.vpc b/mp/src/game/server/server_momentum.vpc index c7e8ff4078..c853a15ffe 100644 --- a/mp/src/game/server/server_momentum.vpc +++ b/mp/src/game/server/server_momentum.vpc @@ -42,6 +42,9 @@ $Project "Server (Momentum)" $File "$SRCDIR\game\shared\momentum\util\mom_util.cpp" $File "$SRCDIR\game\shared\momentum\util\mom_util.h" + $File "$SRCDIR\game\shared\momentum\util\baseautocompletefilelist.cpp" + $File "$SRCDIR\game\shared\momentum\util\baseautocompletefilelist.h" + } $Folder "Timer" diff --git a/mp/src/game/shared/momentum/util/baseautocompletefilelist.cpp b/mp/src/game/shared/momentum/util/baseautocompletefilelist.cpp new file mode 100644 index 0000000000..fa8f221301 --- /dev/null +++ b/mp/src/game/shared/momentum/util/baseautocompletefilelist.cpp @@ -0,0 +1,98 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "filesystem.h" +#include "qlimits.h" +#include "baseautocompletefilelist.h" +#include "utlsymbol.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Fills in a list of commands based on specified subdirectory and extension into the format: +// commandname subdir/filename.ext +// commandname subdir/filename2.ext +// Returns number of files in list for autocompletion +//----------------------------------------------------------------------------- +int CBaseAutoCompleteFileList::AutoCompletionFunc( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) +{ + char const *cmdname = m_pszCommandName; + + char *substring = (char *)partial; + if ( Q_strstr( partial, cmdname ) ) + { + substring = (char *)partial + strlen( cmdname ) + 1; + } + + // Search the directory structure. + char searchpath[MAX_QPATH]; + if ( m_pszSubDir && m_pszSubDir[0] && Q_strcasecmp( m_pszSubDir, "NULL" ) ) + { + Q_snprintf(searchpath,sizeof(searchpath),"%s/*.%s", m_pszSubDir, m_pszExtension ); + } + else + { + Q_snprintf(searchpath,sizeof(searchpath),"*.%s", m_pszExtension ); + } + + CUtlSymbolTable entries( 0, 0, true ); + CUtlVector< CUtlSymbol > symbols; + FileFindHandle_t hfind = FILESYSTEM_INVALID_FIND_HANDLE; + const char* findfn = g_pFullFileSystem->FindFirst(searchpath, &hfind); + while ( findfn ) + { + char sz[ MAX_QPATH ]; + Q_snprintf( sz, sizeof( sz ), "%s", findfn ); + + bool add = false; + // Insert into lookup + if ( substring[0] ) + { + if ( !Q_strncasecmp( findfn, substring, strlen( substring ) ) ) + { + add = true; + } + } + else + { + add = true; + } + + if ( add ) + { + CUtlSymbol sym = entries.AddString( findfn ); + + int idx = symbols.Find( sym ); + if ( idx == symbols.InvalidIndex() ) + { + symbols.AddToTail( sym ); + } + } + + findfn = g_pFullFileSystem->FindNext(hfind); + + // Too many + if ( symbols.Count() >= COMMAND_COMPLETION_MAXITEMS ) + break; + } + + g_pFullFileSystem->FindClose(hfind); + hfind = FILESYSTEM_INVALID_FIND_HANDLE; + + for ( int i = 0; i < symbols.Count(); i++ ) + { + char const *filename = entries.String( symbols[ i ] ); + + Q_snprintf( commands[ i ], sizeof( commands[ i ] ), "%s %s", cmdname, filename ); + // Remove ".momrec" + commands[ i ][ strlen( commands[ i ] ) - 7 ] = 0; + } + + return symbols.Count(); +} diff --git a/mp/src/game/shared/momentum/util/baseautocompletefilelist.h b/mp/src/game/shared/momentum/util/baseautocompletefilelist.h new file mode 100644 index 0000000000..c8ec0e86ac --- /dev/null +++ b/mp/src/game/shared/momentum/util/baseautocompletefilelist.h @@ -0,0 +1,61 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#ifndef BASEAUTOCOMPLETEFILELIST_H +#define BASEAUTOCOMPLETEFILELIST_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/convar.h" + +//----------------------------------------------------------------------------- +// Purpose: Simple helper class for doing autocompletion of all files in a specific directory by extension +//----------------------------------------------------------------------------- +class CBaseAutoCompleteFileList +{ +public: + CBaseAutoCompleteFileList( const char *cmdname, const char *subdir, const char *extension ) + { + m_pszCommandName = cmdname; + m_pszSubDir = subdir; + m_pszExtension = extension; + } + + int AutoCompletionFunc( const char *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ); + +private: + const char *m_pszCommandName; + const char *m_pszSubDir; + const char *m_pszExtension; +}; + +#define DECLARE_AUTOCOMPLETION_FUNCTION( command, subdirectory, extension ) \ + static int g_##command##_CompletionFunc( const char *partial, \ + char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) \ + { \ + static CBaseAutoCompleteFileList command##Complete( #command, subdirectory, #extension ); \ + return command##Complete.AutoCompletionFunc( partial, commands ); \ + } + +#define AUTOCOMPLETION_FUNCTION( command ) \ + g_##command##_CompletionFunc + +//----------------------------------------------------------------------------- +// Purpose: Utility to quicky generate a simple console command with file name autocompletion +//----------------------------------------------------------------------------- +#if !defined( _XBOX ) +#define CON_COMMAND_AUTOCOMPLETEFILE( name, func, description, subdirectory, extension ) \ + DECLARE_AUTOCOMPLETION_FUNCTION( name, subdirectory, extension ) \ + static ConCommand name##_command( #name, func, description, 0, AUTOCOMPLETION_FUNCTION( name ) ); + +#else + #define CON_COMMAND_AUTOCOMPLETEFILE( name, func, description, subdirectory, extension ) \ + static ConCommand name##_command( #name, func, description ); + +#endif +#endif // BASEAUTOCOMPLETEFILELIST_H From 2712a1e9bec00a287f69786bd49af9a43327b417 Mon Sep 17 00:00:00 2001 From: tuxxi Date: Fri, 13 May 2016 14:51:03 -0700 Subject: [PATCH 025/101] cleaned up code for replays and added functionality to start hud timer from the replay system --- mp/src/game/server/momentum/mom_replay.cpp | 13 ++ mp/src/game/server/momentum/mom_replay.h | 1 + .../server/momentum/mom_replay_entity.cpp | 162 +++++++++--------- .../game/server/momentum/mom_replay_entity.h | 1 + mp/src/game/server/momentum/mom_triggers.cpp | 4 +- 5 files changed, 97 insertions(+), 84 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index b7fedb3a6b..fe49cc0293 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -179,9 +179,22 @@ void CMomentumReplaySystem::StartReplay(bool firstperson) CMomentumReplayGhostEntity *ghost = static_cast(CreateEntityByName("mom_replay_ghost")); if (ghost != nullptr) { + g_Timer.Stop(false); //stop the timer just in case we started a replay while it was running... ghost->StartRun(firstperson); } } +void CMomentumReplaySystem::DispatchTimerStateMessage(CBasePlayer *pPlayer, bool started) +{ + if (pPlayer) + { + CSingleUserRecipientFilter user(pPlayer); + user.MakeReliable(); + UserMessageBegin(user, "Timer_State"); + WRITE_BOOL(started); + WRITE_LONG(gpGlobals->tickcount); + MessageEnd(); + } +} class CMOMReplayCommands { public: diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index 0822cf5b5b..5be52bbc86 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -39,6 +39,7 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame replay_header_t m_loadedHeader; bool m_bIsWatchingReplay; + void DispatchTimerStateMessage(CBasePlayer *pPlayer, bool started); private: CUtlBuffer *UpdateRecordingParams(); //called every game frame after entities think and update diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 5a818f546b..f16c9bf12b 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -11,7 +11,8 @@ static ConVar mom_replay_reverse("mom_replay_reverse", "0", static ConVar mom_replay_ghost_bodygroup("mom_replay_ghost_bodygroup", "11", FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE, "Replay ghost's body group (model)", true, 0, true, 14); static ConCommand mom_replay_ghost_color("mom_replay_ghost_color", - CMomentumReplayGhostEntity::SetGhostColor, "Set the ghost's color. Accepts HEX color value in format RRGGBB", FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE); + CMomentumReplayGhostEntity::SetGhostColor, "Set the ghost's color. Accepts HEX color value in format RRGGBB", + FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE); static ConVar mom_replay_ghost_alpha("mom_replay_ghost_alpha", "75", FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE, "Sets the ghost's transparency, integer between 0 and 255,", true, 0, true, 255); @@ -46,7 +47,6 @@ void CMomentumReplayGhostEntity::Spawn(void) SetRenderColor(m_ghostColor.r(), m_ghostColor.g(), m_ghostColor.b(), 75); SetMoveType(MOVETYPE_NOCLIP); RemoveSolidFlags(FSOLID_NOT_SOLID); - AddSolidFlags(FSOLID_TRIGGER); SetModel(GHOST_MODEL); SetBodygroup(1, mom_replay_ghost_bodygroup.GetInt()); } @@ -75,96 +75,19 @@ void CMomentumReplayGhostEntity::updateStep() nextStep = g_ReplaySystem->m_vecRunData[++step]; } } -//----------------------------------------------------------------------------- -// Purpose: Think function to move the ghost -//----------------------------------------------------------------------------- void CMomentumReplayGhostEntity::Think(void) { BaseClass::Think(); if (step < g_ReplaySystem->m_vecRunData.Count() && step >= 1) { updateStep(); - HandleGhost(); + mom_replay_firstperson.GetBool() ? HandleGhostFirstPerson() : HandleGhost(); } else { EndRun(); } - SetNextThink(gpGlobals->curtime + gpGlobals->interval_per_tick); -} - -void CMomentumReplayGhostEntity::HandleGhost() { - if (!m_bIsActive) - { - if (!Q_strcmp(m_pszMapName, STRING(gpGlobals->mapname)) == 0) { - DispatchSpawn(this); - } - } - else - { - if (currentStep.m_vPlayerOrigin.x == 0.0f) - return; - - CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); - if (pPlayer) - { - if (mom_replay_firstperson.GetBool()) - { - pPlayer->m_bIsWatchingReplay = true; - if (!pPlayer->IsObserver()) - { - pPlayer->SetObserverTarget(this); - pPlayer->StartObserverMode(OBS_MODE_IN_EYE); - } - - if (pPlayer->GetObserverMode() != (OBS_MODE_IN_EYE | OBS_MODE_CHASE)) { - //we don't want to allow any other obs modes, only IN EYE and CHASE - pPlayer->ForceObserverMode(OBS_MODE_IN_EYE); - } - pPlayer->SetViewOffset(VEC_VIEW); - SetAbsOrigin(currentStep.m_vPlayerOrigin); - if (pPlayer->GetObserverMode() == OBS_MODE_IN_EYE) { - SetAbsAngles(currentStep.m_qEyeAngles); - } - else { - SetAbsAngles(QAngle(currentStep.m_qEyeAngles.x / 10, //we divide x angle (pitch) by 10 so the ghost doesn't look really stupid - currentStep.m_qEyeAngles.y, currentStep.m_qEyeAngles.z)); - } - - //interpolate vel from difference in origin - float distX = fabs(currentStep.m_vPlayerOrigin.x - nextStep.m_vPlayerOrigin.x); - float distY= fabs(currentStep.m_vPlayerOrigin.y - nextStep.m_vPlayerOrigin.y); - float distZ = fabs(currentStep.m_vPlayerOrigin.z - nextStep.m_vPlayerOrigin.z); - Vector interpolatedVel = Vector(distX, distY, distZ) / gpGlobals->interval_per_tick; - SetAbsVelocity(interpolatedVel); - pPlayer->m_nReplayButtons = currentStep.m_nPlayerButtons; //networked var that allows the replay to control keypress display on the client - if (g_Timer.IsRunning()) - UpdateStats(interpolatedVel, pPlayer); - - if (currentStep.m_nPlayerButtons & IN_DUCK) - { - //MOM_TODO: make this smoother. possibly inherit from NPC classes/CBaseCombatCharacter - pPlayer->SetViewOffset(VEC_DUCK_VIEW); - } - - } - else //we're watching/racing with a ghost - { - SetAbsOrigin(currentStep.m_vPlayerOrigin); - SetAbsAngles(QAngle(currentStep.m_qEyeAngles.x / 10, //we divide x angle (pitch) by 10 so the ghost doesn't look really stupid - currentStep.m_qEyeAngles.y, currentStep.m_qEyeAngles.z)); - if (pPlayer->IsObserver()) //bring the player out of obs mode if theyre currently observing - { - pPlayer->m_bIsWatchingReplay = false; - pPlayer->StopObserverMode(); - pPlayer->ForceRespawn(); - pPlayer->SetMoveType(MOVETYPE_WALK); - } - } - } - - } //update color, bodygroup, and other params if they change if (mom_replay_ghost_bodygroup.GetInt() != m_iBodyGroup) { @@ -182,6 +105,81 @@ void CMomentumReplayGhostEntity::HandleGhost() { mom_replay_ghost_alpha.GetInt()); SetRenderColorA(mom_replay_ghost_alpha.GetInt()); } + + SetNextThink(gpGlobals->curtime + gpGlobals->interval_per_tick); +} +//----------------------------------------------------------------------------- +// Purpose: called by the think function, moves and handles the ghost if we're spectating it +//----------------------------------------------------------------------------- + +void CMomentumReplayGhostEntity::HandleGhostFirstPerson() +{ + CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); + if (pPlayer) + { + pPlayer->m_bIsWatchingReplay = true; + if (!pPlayer->IsObserver()) + { + pPlayer->SetObserverTarget(this); + pPlayer->StartObserverMode(OBS_MODE_IN_EYE); + } + + if (pPlayer->GetObserverMode() != (OBS_MODE_IN_EYE | OBS_MODE_CHASE)) { + //we don't want to allow any other obs modes, only IN EYE and CHASE + pPlayer->ForceObserverMode(OBS_MODE_IN_EYE); + } + pPlayer->SetViewOffset(VEC_VIEW); + SetAbsOrigin(currentStep.m_vPlayerOrigin); + + if (pPlayer->GetObserverMode() == OBS_MODE_IN_EYE) { + SetAbsAngles(currentStep.m_qEyeAngles); + // don't render the model when we're in first person mode + SetRenderMode(kRenderNone); + AddEffects(EF_NOSHADOW); + } + else { + SetAbsAngles(QAngle(currentStep.m_qEyeAngles.x / 10, //we divide x angle (pitch) by 10 so the ghost doesn't look really stupid + currentStep.m_qEyeAngles.y, currentStep.m_qEyeAngles.z)); + //remove the nodraw effects + SetRenderMode(kRenderTransColor); + RemoveEffects(EF_NOSHADOW); + } + + //interpolate vel from difference in origin + float distX = fabs(currentStep.m_vPlayerOrigin.x - nextStep.m_vPlayerOrigin.x); + float distY = fabs(currentStep.m_vPlayerOrigin.y - nextStep.m_vPlayerOrigin.y); + float distZ = fabs(currentStep.m_vPlayerOrigin.z - nextStep.m_vPlayerOrigin.z); + Vector interpolatedVel = Vector(distX, distY, distZ) / gpGlobals->interval_per_tick; + SetAbsVelocity(interpolatedVel); + pPlayer->m_nReplayButtons = currentStep.m_nPlayerButtons; //networked var that allows the replay to control keypress display on the client + + if (g_Timer.IsRunning()) + UpdateStats(interpolatedVel, pPlayer); + + if (currentStep.m_nPlayerButtons & IN_DUCK) + { + //MOM_TODO: make this smoother. possibly inherit from NPC classes/CBaseCombatCharacter + pPlayer->SetViewOffset(VEC_DUCK_VIEW); + } + + } +} +void CMomentumReplayGhostEntity::HandleGhost() +{ + SetAbsOrigin(currentStep.m_vPlayerOrigin); + SetAbsAngles(QAngle(currentStep.m_qEyeAngles.x / 10, //we divide x angle (pitch) by 10 so the ghost doesn't look really stupid + currentStep.m_qEyeAngles.y, currentStep.m_qEyeAngles.z)); + //remove the nodraw effects + SetRenderMode(kRenderTransColor); + RemoveEffects(EF_NOSHADOW); + + CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); + if (pPlayer && pPlayer->IsObserver()) //bring the player out of obs mode if theyre currently observing + { + pPlayer->m_bIsWatchingReplay = false; + pPlayer->StopObserverMode(); + pPlayer->ForceRespawn(); + } } void CMomentumReplayGhostEntity::UpdateStats(Vector ghostVel, CMomentumPlayer *pPlayer) { @@ -189,7 +187,7 @@ void CMomentumReplayGhostEntity::UpdateStats(Vector ghostVel, CMomentumPlayer *p //calculate strafe sync based on replay ghost's movement, in order to update the player's HUD float SyncVelocity = ghostVel.Length2DSqr(); //we always want HVEL for checking velocity sync - if (!(pPlayer->GetFlags() & (FL_ONGROUND | FL_INWATER)) && pPlayer->GetMoveType() != MOVETYPE_LADDER) + if (GetGroundEntity() == NULL) { if (EyeAngles().y > m_qLastEyeAngle.y) //player turned left { @@ -214,7 +212,7 @@ void CMomentumReplayGhostEntity::UpdateStats(Vector ghostVel, CMomentumPlayer *p pPlayer->m_flStrafeSync2 = ((float)m_nAccelTicks / (float)m_nStrafeTicks) * 100; // ticks gaining speed / ticks strafing } // --- JUMP AND STRAFE COUNTER --- - if (ghostVel.z == 0 && currentStep.m_nPlayerButtons & IN_JUMP) + if (GetGroundEntity() != NULL && currentStep.m_nPlayerButtons & IN_JUMP) pPlayer->m_nStageJumps[0]++; if ((currentStep.m_nPlayerButtons & IN_MOVELEFT && !(m_nOldReplayButtons & IN_MOVELEFT)) || (currentStep.m_nPlayerButtons & IN_MOVERIGHT && !(m_nOldReplayButtons & IN_MOVERIGHT)) ) diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index 5097bfee02..d29ec9a100 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -41,6 +41,7 @@ class CMomentumReplayGhostEntity : public CBaseCombatCharacter void EndRun(); void StartRun(bool firstPerson = false); void HandleGhost(); + void HandleGhostFirstPerson(); void UpdateStats(Vector ghostVel, CMomentumPlayer *pPlayer); //for hud display.. bool m_bIsActive; diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index 3b382549d9..cacfdcb4b7 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -117,12 +117,12 @@ void CTriggerTimerStart::EndTouch(CBaseEntity *pOther) pOther->SetAbsVelocity(Vector(vel2D.x, vel2D.y, velocity.z)); } } - g_Timer.Start(gpGlobals->tickcount); } - + g_Timer.Start(gpGlobals->tickcount); } pPlayer->m_bInsideStartZone = false; } + IGameEvent *mapZoneEvent = gameeventmanager->CreateEvent("player_inside_mapzone"); if (mapZoneEvent) { From c570412bbb00a4aca9dc1be72ed2fed293bff3ae Mon Sep 17 00:00:00 2001 From: Nick K Date: Wed, 18 May 2016 20:06:48 -0400 Subject: [PATCH 026/101] Add run_stats.h Crash on boot? From node graph building? --- mp/src/game/client/client_momentum.vpc | 1 + .../client/momentum/mom_event_listener.cpp | 56 +++++++++---------- .../game/client/momentum/mom_event_listener.h | 17 ++---- .../client/momentum/ui/hud_comparisons.cpp | 20 +++---- .../game/client/momentum/ui/hud_keypress.cpp | 4 +- .../client/momentum/ui/hud_mapfinished.cpp | 16 +++--- mp/src/game/server/momentum/mom_replay.cpp | 24 ++++---- mp/src/game/server/momentum/replayformat.h | 36 ++++++++---- mp/src/game/server/server_momentum.vpc | 2 + mp/src/game/shared/momentum/util/run_stats.h | 32 +++++++++++ 10 files changed, 125 insertions(+), 83 deletions(-) create mode 100644 mp/src/game/shared/momentum/util/run_stats.h diff --git a/mp/src/game/client/client_momentum.vpc b/mp/src/game/client/client_momentum.vpc index 81a21686f0..686b99216b 100644 --- a/mp/src/game/client/client_momentum.vpc +++ b/mp/src/game/client/client_momentum.vpc @@ -137,6 +137,7 @@ $Project "Client (Momentum)" $File "$SRCDIR\game\shared\momentum\util\baseautocompletefilelist.cpp" $File "$SRCDIR\game\shared\momentum\util\baseautocompletefilelist.h" $File "$SRCDIR\game\shared\momentum\util\run_compare.h" + $File "$SRCDIR\game\shared\momentum\util\run_stats.h" } $File "$SRCDIR\game\shared\momentum\mom_gamemovement.cpp" diff --git a/mp/src/game/client/momentum/mom_event_listener.cpp b/mp/src/game/client/momentum/mom_event_listener.cpp index 0919e39e94..809e72fdb7 100644 --- a/mp/src/game/client/momentum/mom_event_listener.cpp +++ b/mp/src/game/client/momentum/mom_event_listener.cpp @@ -21,18 +21,18 @@ void C_Momentum_EventListener::FireGameEvent(IGameEvent *pEvent) { if (!Q_strcmp("timer_stopped", pEvent->GetName())) { - m_flStageStrafeSyncAvg[0] = pEvent->GetFloat("avg_sync"); - m_flStageStrafeSync2Avg[0] = pEvent->GetFloat("avg_sync2"); + stats.m_flStageStrafeSyncAvg[0] = pEvent->GetFloat("avg_sync"); + stats.m_flStageStrafeSync2Avg[0] = pEvent->GetFloat("avg_sync2"); //3D - m_flStageEnterSpeed[0][0] = pEvent->GetFloat("start_vel"); - m_flStageExitSpeed[0][0] = pEvent->GetFloat("end_vel"); - m_flStageVelocityAvg[0][0] = pEvent->GetFloat("avg_vel"); - m_flStageVelocityMax[0][0] = pEvent->GetFloat("max_vel"); + stats.m_flStageEnterSpeed[0][0] = pEvent->GetFloat("start_vel"); + stats.m_flStageExitSpeed[0][0] = pEvent->GetFloat("end_vel"); + stats.m_flStageVelocityAvg[0][0] = pEvent->GetFloat("avg_vel"); + stats.m_flStageVelocityMax[0][0] = pEvent->GetFloat("max_vel"); //2D - m_flStageEnterSpeed[0][1] = pEvent->GetFloat("start_vel_2D"); - m_flStageExitSpeed[0][1] = pEvent->GetFloat("end_vel_2D"); - m_flStageVelocityAvg[0][1] = pEvent->GetFloat("avg_vel_2D"); - m_flStageVelocityMax[0][1] = pEvent->GetFloat("max_vel_2D"); + stats.m_flStageEnterSpeed[0][1] = pEvent->GetFloat("start_vel_2D"); + stats.m_flStageExitSpeed[0][1] = pEvent->GetFloat("end_vel_2D"); + stats.m_flStageVelocityAvg[0][1] = pEvent->GetFloat("avg_vel_2D"); + stats.m_flStageVelocityMax[0][1] = pEvent->GetFloat("max_vel_2D"); } else if (!Q_strcmp("stage_enter", pEvent->GetName())) { @@ -41,37 +41,37 @@ void C_Momentum_EventListener::FireGameEvent(IGameEvent *pEvent) int currentStage = pEvent->GetInt("stage_num"); //Note: stage_enter_time will NOT change upon multiple entries to the same stage trigger (only set once per run) - m_flStageEnterTime[currentStage] = pEvent->GetFloat("stage_enter_time"); + stats.m_flStageEnterTime[currentStage] = pEvent->GetFloat("stage_enter_time"); //Reset the stage enter speed for the speedometer - m_flStageEnterSpeed[currentStage][0] = 0.0f; - m_flStageEnterSpeed[currentStage][1] = 0.0f; + stats.m_flStageEnterSpeed[currentStage][0] = 0.0f; + stats.m_flStageEnterSpeed[currentStage][1] = 0.0f; if (currentStage > 1) //MOM_TODO: || m_iStageCount < 2 (linear maps use checkpoints?) { //The first stage doesn't have its time yet, we calculate it upon going into stage 2+ - m_flStageTime[currentStage - 1] = m_flStageEnterTime[currentStage] - m_flStageEnterTime[currentStage - 1]; + stats.m_flStageTime[currentStage - 1] = stats.m_flStageEnterTime[currentStage] - stats.m_flStageEnterTime[currentStage - 1]; //And the rest of the stats are about the previous stage anyways, not calculated during stage 1 (start) - m_flStageStrafeSyncAvg[currentStage - 1] = pEvent->GetFloat("avg_sync"); - m_flStageStrafeSync2Avg[currentStage - 1] = pEvent->GetFloat("avg_sync2"); + stats.m_flStageStrafeSyncAvg[currentStage - 1] = pEvent->GetFloat("avg_sync"); + stats.m_flStageStrafeSync2Avg[currentStage - 1] = pEvent->GetFloat("avg_sync2"); - m_flStageExitSpeed[currentStage - 1][0] = pEvent->GetFloat("stage_exit_vel"); - m_flStageVelocityAvg[currentStage - 1][0] = pEvent->GetFloat("avg_vel"); - m_flStageVelocityMax[currentStage - 1][0] = pEvent->GetFloat("max_vel"); + stats.m_flStageExitSpeed[currentStage - 1][0] = pEvent->GetFloat("stage_exit_vel"); + stats.m_flStageVelocityAvg[currentStage - 1][0] = pEvent->GetFloat("avg_vel"); + stats.m_flStageVelocityMax[currentStage - 1][0] = pEvent->GetFloat("max_vel"); - m_flStageExitSpeed[currentStage - 1][1] = pEvent->GetFloat("stage_exit_vel_2D"); - m_flStageVelocityAvg[currentStage - 1][1] = pEvent->GetFloat("avg_vel_2D"); - m_flStageVelocityMax[currentStage - 1][1] = pEvent->GetFloat("max_vel_2D"); + stats.m_flStageExitSpeed[currentStage - 1][1] = pEvent->GetFloat("stage_exit_vel_2D"); + stats.m_flStageVelocityAvg[currentStage - 1][1] = pEvent->GetFloat("avg_vel_2D"); + stats.m_flStageVelocityMax[currentStage - 1][1] = pEvent->GetFloat("max_vel_2D"); - m_iStageJumps[currentStage - 1] = pEvent->GetInt("num_jumps"); - m_iStageStrafes[currentStage - 1] = pEvent->GetInt("num_strafes"); + stats.m_iStageJumps[currentStage - 1] = pEvent->GetInt("num_jumps"); + stats.m_iStageStrafes[currentStage - 1] = pEvent->GetInt("num_strafes"); } } else if (!Q_strcmp("stage_exit", pEvent->GetName())) { int currentStage = pEvent->GetInt("stage_num"); //Set the stage enter speed upon exiting the trigger - m_flStageEnterSpeed[currentStage][0] = pEvent->GetFloat("stage_enter_vel"); - m_flStageEnterSpeed[currentStage][1] = pEvent->GetFloat("stage_enter_vel_2D"); + stats.m_flStageEnterSpeed[currentStage][0] = pEvent->GetFloat("stage_enter_vel"); + stats.m_flStageEnterSpeed[currentStage][1] = pEvent->GetFloat("stage_enter_vel_2D"); } else if (!Q_strcmp("run_save", pEvent->GetName())) { @@ -93,8 +93,8 @@ void C_Momentum_EventListener::FireGameEvent(IGameEvent *pEvent) } else if (!Q_strcmp("keypress", pEvent->GetName())) { - m_iStageJumps[0] = pEvent->GetInt("num_jumps"); - m_iStageStrafes[0] = pEvent->GetInt("num_strafes"); + stats.m_iStageJumps[0] = pEvent->GetInt("num_jumps"); + stats.m_iStageStrafes[0] = pEvent->GetInt("num_strafes"); } else if (!Q_strcmp("map_init", pEvent->GetName())) { diff --git a/mp/src/game/client/momentum/mom_event_listener.h b/mp/src/game/client/momentum/mom_event_listener.h index 3ff3dc5890..b91bdc73ee 100644 --- a/mp/src/game/client/momentum/mom_event_listener.h +++ b/mp/src/game/client/momentum/mom_event_listener.h @@ -1,7 +1,7 @@ #pragma once #include "mom_shareddefs.h" - +#include "util/run_stats.h" class C_Momentum_EventListener : public CGameEventListener { @@ -10,7 +10,8 @@ class C_Momentum_EventListener : public CGameEventListener m_bTimerIsRunning(false), m_bTimeDidSave(false), m_bTimeDidUpload(false), - m_bPlayerHasPracticeMode(false) + m_bPlayerHasPracticeMode(false), + stats() { } void Init(); @@ -25,17 +26,7 @@ class C_Momentum_EventListener : public CGameEventListener bool m_bPlayerHasPracticeMode; - //MOM_TODO: We're going to hold an unbiased view at both - //checkpoint and stages. If a map is linear yet has checkpoints, - //it can be free to use these below to display stats for the player to compare against. - int m_iStageJumps[MAX_STAGES], m_iStageStrafes[MAX_STAGES]; - float m_flStageTime[MAX_STAGES], m_flStageEnterTime[MAX_STAGES], m_flStageStrafeSyncAvg[MAX_STAGES], - m_flStageStrafeSync2Avg[MAX_STAGES]; - - float m_flStageEnterSpeed[MAX_STAGES][2],//The velocity with which you started the stage (exit this stage's start trigger) - m_flStageVelocityMax[MAX_STAGES][2],//Max velocity for a stage - m_flStageVelocityAvg[MAX_STAGES][2],//Average velocity in a stage - m_flStageExitSpeed[MAX_STAGES][2];//The velocity with which you exit the stage (this stage -> next) + RunStats_t stats; char m_szRunUploadStatus[512];//MOM_TODO: determine best (max) size for this }; diff --git a/mp/src/game/client/momentum/ui/hud_comparisons.cpp b/mp/src/game/client/momentum/ui/hud_comparisons.cpp index 742845a07b..bfb15cad8c 100644 --- a/mp/src/game/client/momentum/ui/hud_comparisons.cpp +++ b/mp/src/game/client/momentum/ui/hud_comparisons.cpp @@ -271,8 +271,8 @@ void C_RunComparisons::GetComparisonString(ComparisonString_t type, int stage, c case TIME_OVERALL: case STAGE_TIME: // Get the time difference in seconds. - act = type == TIME_OVERALL ? g_MOMEventListener->m_flStageEnterTime[stage + 1] - : g_MOMEventListener->m_flStageTime[stage]; + act = type == TIME_OVERALL ? g_MOMEventListener->stats.m_flStageEnterTime[stage + 1] + : g_MOMEventListener->stats.m_flStageTime[stage]; if (m_bLoadedComparison) diff = act - (type == TIME_OVERALL ? m_rcCurrentComparison->overallSplits[stage] @@ -288,43 +288,43 @@ void C_RunComparisons::GetComparisonString(ComparisonString_t type, int stage, c break; case VELOCITY_AVERAGE: // Get the vel difference - act = g_MOMEventListener->m_flStageVelocityAvg[stage][velType]; + act = g_MOMEventListener->stats.m_flStageVelocityAvg[stage][velType]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageAvgVels[velType][stage - 1]; //- 1 due to array indexing (0 is stage 1) break; case VELOCITY_EXIT: - act = g_MOMEventListener->m_flStageExitSpeed[stage][velType]; + act = g_MOMEventListener->stats.m_flStageExitSpeed[stage][velType]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageExitVels[velType][stage - 1]; break; case VELOCITY_MAX: - act = g_MOMEventListener->m_flStageVelocityMax[stage][velType]; + act = g_MOMEventListener->stats.m_flStageVelocityMax[stage][velType]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageMaxVels[velType][stage - 1]; break; case VELOCITY_ENTER: - act = g_MOMEventListener->m_flStageEnterSpeed[stage][velType]; + act = g_MOMEventListener->stats.m_flStageEnterSpeed[stage][velType]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageEnterVels[velType][stage - 1]; break; case STAGE_SYNC1: - act = g_MOMEventListener->m_flStageStrafeSyncAvg[stage]; + act = g_MOMEventListener->stats.m_flStageStrafeSyncAvg[stage]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageAvgSync1[stage - 1]; break; case STAGE_SYNC2: - act = g_MOMEventListener->m_flStageStrafeSync2Avg[stage]; + act = g_MOMEventListener->stats.m_flStageStrafeSync2Avg[stage]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageAvgSync2[stage - 1]; break; case STAGE_JUMPS: - act = g_MOMEventListener->m_iStageJumps[stage]; + act = g_MOMEventListener->stats.m_iStageJumps[stage]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageJumps[stage - 1]; break; case STAGE_STRAFES: - act = g_MOMEventListener->m_iStageStrafes[stage]; + act = g_MOMEventListener->stats.m_iStageStrafes[stage]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageStrafes[stage - 1]; break; diff --git a/mp/src/game/client/momentum/ui/hud_keypress.cpp b/mp/src/game/client/momentum/ui/hud_keypress.cpp index 833ae98583..1ac1c1555e 100644 --- a/mp/src/game/client/momentum/ui/hud_keypress.cpp +++ b/mp/src/game/client/momentum/ui/hud_keypress.cpp @@ -195,8 +195,8 @@ void CHudKeyPressDisplay::OnThink() if (g_MOMEventListener) { //we should only draw the strafe/jump counters when the timer is running m_bShouldDrawCounts = g_MOMEventListener->m_bTimerIsRunning; - m_nStrafes = g_MOMEventListener->m_iStageStrafes[0]; - m_nJumps = g_MOMEventListener->m_iStageJumps[0]; + m_nStrafes = g_MOMEventListener->stats.m_iStageStrafes[0]; + m_nJumps = g_MOMEventListener->stats.m_iStageJumps[0]; } } void CHudKeyPressDisplay::Reset() diff --git a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp index 0111cc1f8a..2a622601dc 100644 --- a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp +++ b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp @@ -324,14 +324,14 @@ void CHudMapFinishedDialog::OnThink() //Is it going to be a localized string, except for errors that have to be specific? ConVarRef hvel("mom_speedometer_hvel"); - m_flAvgSpeed = g_MOMEventListener->m_flStageVelocityAvg[0][hvel.GetBool()]; - m_flMaxSpeed = g_MOMEventListener->m_flStageVelocityMax[0][hvel.GetBool()]; - m_flEndSpeed = g_MOMEventListener->m_flStageExitSpeed[0][hvel.GetBool()]; - m_flStartSpeed = g_MOMEventListener->m_flStageEnterSpeed[0][hvel.GetBool()]; - m_flAvgSync2 = g_MOMEventListener->m_flStageStrafeSyncAvg[0]; - m_flAvgSync = g_MOMEventListener->m_flStageStrafeSync2Avg[0]; - m_iTotalJumps = g_MOMEventListener->m_iStageJumps[0]; - m_iTotalStrafes = g_MOMEventListener->m_iStageStrafes[0]; + m_flAvgSpeed = g_MOMEventListener->stats.m_flStageVelocityAvg[0][hvel.GetBool()]; + m_flMaxSpeed = g_MOMEventListener->stats.m_flStageVelocityMax[0][hvel.GetBool()]; + m_flEndSpeed = g_MOMEventListener->stats.m_flStageExitSpeed[0][hvel.GetBool()]; + m_flStartSpeed = g_MOMEventListener->stats.m_flStageEnterSpeed[0][hvel.GetBool()]; + m_flAvgSync2 = g_MOMEventListener->stats.m_flStageStrafeSyncAvg[0]; + m_flAvgSync = g_MOMEventListener->stats.m_flStageStrafeSync2Avg[0]; + m_iTotalJumps = g_MOMEventListener->stats.m_iStageJumps[0]; + m_iTotalStrafes = g_MOMEventListener->stats.m_iStageStrafes[0]; } if (pPlayer != nullptr) mom_UTIL->FormatTime(pPlayer->m_flLastRunTime, m_pszRunTime); diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 14ea2eec1a..0013684879 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -85,21 +85,25 @@ replay_header_t CMomentumReplaySystem::CreateHeader() time(&header.unixEpocDate); // --- RUN STATS --- - //MOM_TODO: we just get one velocity here, for now. UPDATE THIS WHEN "runstats.h" is a thing! - header.m_flEndSpeed = m_player->m_flStageExitVelocity[0][0]; - header.m_flStartSpeed = m_player->m_flStageEnterVelocity[0][0]; + for (int i = 0; i < 2; i++) + { + header.stats.m_flStageExitSpeed[0][i] = m_player->m_flStageExitVelocity[0][i]; + header.stats.m_flStageEnterSpeed[0][i] = m_player->m_flStageEnterVelocity[0][i]; + } + for (int i = 0; i < MAX_STAGES; i++) { for (int k = 0; i < 2; k++) { - header.m_flStageEnterVelocity[i] = m_player->m_flStageEnterVelocity[i][k]; - header.m_flStageVelocityAvg[i] = m_player->m_flStageVelocityAvg[i][k]; - header.m_flStageVelocityMax[i] = m_player->m_flStageVelocityMax[i][k]; + header.stats.m_flStageEnterSpeed[i][k] = m_player->m_flStageEnterVelocity[i][k]; + header.stats.m_flStageExitSpeed[i][k] = m_player->m_flStageExitVelocity[i][k]; + header.stats.m_flStageVelocityAvg[i][k] = m_player->m_flStageVelocityAvg[i][k]; + header.stats.m_flStageVelocityMax[i][k] = m_player->m_flStageVelocityMax[i][k]; } - header.m_flStageStrafeSyncAvg[i] = m_player->m_flStageStrafeSyncAvg[i]; - header.m_flStageStrafeSync2Avg[i] = m_player->m_flStageStrafeSync2Avg[i]; - header.m_nStageJumps[i] = m_player->m_nStageJumps[i]; - header.m_nStageStrafes[i] = m_player->m_nStageStrafes[i]; + header.stats.m_flStageStrafeSyncAvg[i] = m_player->m_flStageStrafeSyncAvg[i]; + header.stats.m_flStageStrafeSync2Avg[i] = m_player->m_flStageStrafeSync2Avg[i]; + header.stats.m_iStageJumps[i] = m_player->m_nStageJumps[i]; + header.stats.m_iStageStrafes[i] = m_player->m_nStageStrafes[i]; } return header; } diff --git a/mp/src/game/server/momentum/replayformat.h b/mp/src/game/server/momentum/replayformat.h index 5147c35a71..363ee906ec 100644 --- a/mp/src/game/server/momentum/replayformat.h +++ b/mp/src/game/server/momentum/replayformat.h @@ -3,6 +3,7 @@ #include "cbase.h" #include "mom_shareddefs.h" +#include "util/run_stats.h" #define DEMO_HEADER_ID "MOMREPLAY" #define DEMO_PROTOCOL_VERSION 2 @@ -46,10 +47,11 @@ struct replay_header_t float interval_per_tick; int runTimeTicks; //Total run time in ticks - float m_flStartSpeed, m_flEndSpeed; + RunStats_t stats; + /*float m_flStartSpeed, m_flEndSpeed; int m_nStageJumps[MAX_STAGES], m_nStageStrafes[MAX_STAGES]; float m_flStageVelocityMax[MAX_STAGES], m_flStageVelocityAvg[MAX_STAGES], - m_flStageStrafeSyncAvg[MAX_STAGES], m_flStageStrafeSync2Avg[MAX_STAGES], m_flStageEnterVelocity[MAX_STAGES]; + m_flStageStrafeSyncAvg[MAX_STAGES], m_flStageStrafeSync2Avg[MAX_STAGES], m_flStageEnterVelocity[MAX_STAGES];*/ }; //byteswap for int and float members of header, swaps the endianness (byte order) in order to read correctly inline void ByteSwap_replay_header_t(replay_header_t &swap) @@ -59,18 +61,28 @@ inline void ByteSwap_replay_header_t(replay_header_t &swap) swap.unixEpocDate = LittleLong(swap.unixEpocDate); swap.steamID64 = LittleLong(swap.steamID64); LittleFloat(&swap.interval_per_tick, &swap.interval_per_tick); - + //MOM_TODO: Do we want to also have a float time? // --- run stats --- - LittleFloat(&swap.m_flEndSpeed, &swap.m_flEndSpeed); - LittleFloat(&swap.m_flStartSpeed, &swap.m_flStartSpeed); + for (int i = 0; i < 2; i++) + { + LittleFloat(&swap.stats.m_flStageExitSpeed[0][i], &swap.stats.m_flStageExitSpeed[0][i]); + LittleFloat(&swap.stats.m_flStageEnterSpeed[0][i], &swap.stats.m_flStageEnterSpeed[0][i]); + } + for (int i = 0; i < MAX_STAGES; i++) { - LittleFloat(&swap.m_flStageVelocityMax[i], &swap.m_flStageVelocityMax[i]); - LittleFloat(&swap.m_flStageVelocityAvg[i], &swap.m_flStageVelocityAvg[i]); - LittleFloat(&swap.m_flStageStrafeSyncAvg[i], &swap.m_flStageStrafeSyncAvg[i]); - LittleFloat(&swap.m_flStageStrafeSync2Avg[i], &swap.m_flStageStrafeSync2Avg[i]); - LittleFloat(&swap.m_flStageEnterVelocity[i], &swap.m_flStageEnterVelocity[i]); - swap.m_nStageJumps[i] = LittleDWord(swap.m_nStageJumps[i]); - swap.m_nStageStrafes[i] = LittleDWord(swap.m_nStageStrafes[i]); + + for (int k = 0; k < 2; k++) + { + LittleFloat(&swap.stats.m_flStageEnterSpeed[i][k], &swap.stats.m_flStageEnterSpeed[i][k]); + LittleFloat(&swap.stats.m_flStageExitSpeed[i][k], &swap.stats.m_flStageExitSpeed[i][k]); + LittleFloat(&swap.stats.m_flStageVelocityAvg[i][k], &swap.stats.m_flStageVelocityAvg[i][k]); + LittleFloat(&swap.stats.m_flStageVelocityMax[i][k], &swap.stats.m_flStageVelocityMax[i][k]); + } + + LittleFloat(&swap.stats.m_flStageStrafeSyncAvg[i], &swap.stats.m_flStageStrafeSyncAvg[i]); + LittleFloat(&swap.stats.m_flStageStrafeSync2Avg[i], &swap.stats.m_flStageStrafeSync2Avg[i]); + swap.stats.m_iStageJumps[i] = LittleDWord(swap.stats.m_iStageJumps[i]); + swap.stats.m_iStageStrafes[i] = LittleDWord(swap.stats.m_iStageStrafes[i]); } } #endif //REPLAYFORMAT_H \ No newline at end of file diff --git a/mp/src/game/server/server_momentum.vpc b/mp/src/game/server/server_momentum.vpc index c853a15ffe..44ded91312 100644 --- a/mp/src/game/server/server_momentum.vpc +++ b/mp/src/game/server/server_momentum.vpc @@ -44,6 +44,8 @@ $Project "Server (Momentum)" $File "$SRCDIR\game\shared\momentum\util\mom_util.h" $File "$SRCDIR\game\shared\momentum\util\baseautocompletefilelist.cpp" $File "$SRCDIR\game\shared\momentum\util\baseautocompletefilelist.h" + $File "$SRCDIR\game\shared\momentum\util\run_stats.h" + $File "$SRCDIR\game\shared\momentum\util\run_compare.h" } diff --git a/mp/src/game/shared/momentum/util/run_stats.h b/mp/src/game/shared/momentum/util/run_stats.h new file mode 100644 index 0000000000..2ca1852723 --- /dev/null +++ b/mp/src/game/shared/momentum/util/run_stats.h @@ -0,0 +1,32 @@ +#pragma once + +#include "cbase.h" +#include "mom_shareddefs.h" + +struct RunStats_t +{ + //MOM_TODO: We're going to hold an unbiased view at both + //checkpoint and stages. If a map is linear yet has checkpoints, + //it can be free to use these below to display stats for the player to compare against. + + //Note: Passing 0 as the index to any of these will return overall. + + //Keypress + int m_iStageJumps[MAX_STAGES],//Amount of jumps per stage + m_iStageStrafes[MAX_STAGES];//Amount of strafes per stage + + //Time + float m_flStageTime[MAX_STAGES], //The amount of time (seconds) you spent to accomplish (stage) -> (stage + 1) + m_flStageEnterTime[MAX_STAGES]; //The time in seconds that you entered the given stage + + //Sync + float m_flStageStrafeSyncAvg[MAX_STAGES],//The average sync1 you had over the given stage + m_flStageStrafeSync2Avg[MAX_STAGES];//The average sync2 you had over the given stage + + //Velocity + //Note: The secondary index is as follows: 0 = 3D Velocity (z included), 1 = Horizontal (XY) Velocity + float m_flStageEnterSpeed[MAX_STAGES][2],//The velocity with which you started the stage (exit this stage's start trigger) + m_flStageVelocityMax[MAX_STAGES][2],//Max velocity for a stage + m_flStageVelocityAvg[MAX_STAGES][2],//Average velocity in a stage + m_flStageExitSpeed[MAX_STAGES][2];//The velocity with which you exit the stage (this stage -> next) +}; \ No newline at end of file From 1db5d78b867888883a421107d1bf3936bc7c473a Mon Sep 17 00:00:00 2001 From: tuxxi Date: Wed, 18 May 2016 17:58:26 -0700 Subject: [PATCH 027/101] RunStats struct implemented on server classes; fixed saving replays --- mp/src/game/server/momentum/Timer.cpp | 142 +++++++----------- mp/src/game/server/momentum/Timer.h | 12 +- mp/src/game/server/momentum/mom_player.cpp | 85 +++++------ mp/src/game/server/momentum/mom_player.h | 12 +- mp/src/game/server/momentum/mom_replay.cpp | 37 ++--- .../server/momentum/mom_replay_entity.cpp | 4 +- mp/src/game/server/momentum/mom_triggers.cpp | 68 ++++----- mp/src/game/server/momentum/replayformat.h | 40 +++-- mp/src/game/shared/momentum/util/run_stats.h | 39 +++++ 9 files changed, 196 insertions(+), 243 deletions(-) diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index de16205664..d469b448f1 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -105,41 +105,41 @@ void CTimer::LoadLocalTimes(const char *szMapname) if (!Q_strnicmp(subKv->GetName(), "stage", strlen("stage"))) { int i = Q_atoi(subKv->GetName() + 6); //atoi will need to ignore "stage " and only return the stage number - t.stagejumps[i] = subKv->GetInt("num_jumps"); - t.stagestrafes[i] = subKv->GetInt("num_strafes"); - t.stagetime[i] = subKv->GetFloat("time"); - t.stageentertime[i] = subKv->GetFloat("enter_time"); - t.stageavgsync[i] = subKv->GetFloat("avg_sync"); - t.stageavgsync2[i] = subKv->GetFloat("avg_sync2"); + t.RunStats->m_iStageJumps[i] = subKv->GetInt("num_jumps"); + t.RunStats->m_iStageStrafes[i] = subKv->GetInt("num_strafes"); + t.RunStats->m_flStageTime[i] = subKv->GetFloat("time"); + t.RunStats->m_flStageEnterTime[i] = subKv->GetFloat("enter_time"); + t.RunStats->m_flStageStrafeSyncAvg[i] = subKv->GetFloat("avg_sync"); + t.RunStats->m_flStageStrafeSync2Avg[i] = subKv->GetFloat("avg_sync2"); //3D Velocity Stats - t.stageavgvel[i][0] = subKv->GetFloat("avg_vel"); - t.stagemaxvel[i][0] = subKv->GetFloat("max_vel"); - t.stagestartvel[i][0] = subKv->GetFloat("stage_enter_vel"); - t.stageexitvel[i][0] = subKv->GetFloat("stage_exit_vel"); + t.RunStats->m_flStageVelocityAvg[i][0] = subKv->GetFloat("avg_vel"); + t.RunStats->m_flStageVelocityMax[i][0] = subKv->GetFloat("max_vel"); + t.RunStats->m_flStageEnterSpeed[i][0] = subKv->GetFloat("stage_enter_vel"); + t.RunStats->m_flStageExitSpeed[i][0] = subKv->GetFloat("stage_exit_vel"); //2D Velocity Stats - t.stageavgvel[i][1] = subKv->GetFloat("avg_vel_2D"); - t.stagemaxvel[i][1] = subKv->GetFloat("max_vel_2D"); - t.stagestartvel[i][1] = subKv->GetFloat("stage_enter_vel_2D"); - t.stageexitvel[i][1] = subKv->GetFloat("stage_exit_vel_2D"); + t.RunStats->m_flStageVelocityAvg[i][1] = subKv->GetFloat("avg_vel_2D"); + t.RunStats->m_flStageVelocityMax[i][1] = subKv->GetFloat("max_vel_2D"); + t.RunStats->m_flStageEnterSpeed[i][1] = subKv->GetFloat("stage_enter_vel_2D"); + t.RunStats->m_flStageExitSpeed[i][1] = subKv->GetFloat("stage_exit_vel_2D"); } if (!Q_strcmp(subKv->GetName(), "total")) { - t.stagejumps[0] = subKv->GetInt("jumps"); - t.stagestrafes[0] = subKv->GetInt("strafes"); - t.stageavgsync[0] = subKv->GetFloat("avgsync"); - t.stageavgsync2[0] = subKv->GetFloat("avgsync2"); + t.RunStats->m_iStageJumps[0] = subKv->GetInt("jumps"); + t.RunStats->m_iStageStrafes[0] = subKv->GetInt("strafes"); + t.RunStats->m_flStageStrafeSyncAvg[0] = subKv->GetFloat("avgsync"); + t.RunStats->m_flStageStrafeSync2Avg[0] = subKv->GetFloat("avgsync2"); //3D - t.stageavgvel[0][0] = subKv->GetFloat("avg_vel"); - t.stagemaxvel[0][0] = subKv->GetFloat("max_vel"); - t.stagestartvel[0][0] = subKv->GetFloat("start_vel"); - t.stageexitvel[0][0] = subKv->GetFloat("end_vel"); + t.RunStats->m_flStageVelocityAvg[0][0] = subKv->GetFloat("avg_vel"); + t.RunStats->m_flStageVelocityMax[0][0] = subKv->GetFloat("max_vel"); + t.RunStats->m_flStageEnterSpeed[0][0] = subKv->GetFloat("start_vel"); + t.RunStats->m_flStageExitSpeed[0][0] = subKv->GetFloat("end_vel"); //2D - t.stageavgvel[0][1] = subKv->GetFloat("avg_vel_2D"); - t.stagemaxvel[0][1] = subKv->GetFloat("max_vel_2D"); - t.stagestartvel[0][1] = subKv->GetFloat("start_vel_2D"); - t.stageexitvel[0][1] = subKv->GetFloat("end_vel_2D"); + t.RunStats->m_flStageVelocityAvg[0][1] = subKv->GetFloat("avg_vel_2D"); + t.RunStats->m_flStageVelocityMax[0][1] = subKv->GetFloat("max_vel_2D"); + t.RunStats->m_flStageEnterSpeed[0][1] = subKv->GetFloat("start_vel_2D"); + t.RunStats->m_flStageExitSpeed[0][1] = subKv->GetFloat("end_vel_2D"); } } localTimes.AddToTail(t); @@ -172,20 +172,20 @@ void CTimer::SaveTime() pSubkey->SetInt("flags", t.flags); KeyValues *pOverallKey = new KeyValues("total"); - pOverallKey->SetInt("jumps", t.stagejumps[0]); - pOverallKey->SetInt("strafes", t.stagestrafes[0]); - pOverallKey->SetFloat("avgsync", t.stageavgsync[0]); - pOverallKey->SetFloat("avgsync2", t.stageavgsync2[0]); + pOverallKey->SetInt("jumps", t.RunStats->m_iStageJumps[0]); + pOverallKey->SetInt("strafes", t.RunStats->m_iStageStrafes[0]); + pOverallKey->SetFloat("avgsync", t.RunStats->m_flStageStrafeSyncAvg[0]); + pOverallKey->SetFloat("avgsync2", t.RunStats->m_flStageStrafeSync2Avg[0]); - pOverallKey->SetFloat("start_vel", t.stagestartvel[0][0]); - pOverallKey->SetFloat("end_vel", t.stageexitvel[0][0]); - pOverallKey->SetFloat("avg_vel", t.stageavgvel[0][0]); - pOverallKey->SetFloat("max_vel", t.stagemaxvel[0][0]); + pOverallKey->SetFloat("start_vel", t.RunStats->m_flStageEnterSpeed[0][0]); + pOverallKey->SetFloat("end_vel", t.RunStats->m_flStageExitSpeed[0][0]); + pOverallKey->SetFloat("avg_vel", t.RunStats->m_flStageVelocityAvg[0][0]); + pOverallKey->SetFloat("max_vel", t.RunStats->m_flStageVelocityMax[0][0]); - pOverallKey->SetFloat("start_vel_2D", t.stagestartvel[0][1]); - pOverallKey->SetFloat("end_vel_2D", t.stageexitvel[0][1]); - pOverallKey->SetFloat("avg_vel_2D", t.stageavgvel[0][1]); - pOverallKey->SetFloat("max_vel_2D", t.stagemaxvel[0][1]); + pOverallKey->SetFloat("start_vel_2D", t.RunStats->m_flStageEnterSpeed[0][1]); + pOverallKey->SetFloat("end_vel_2D", t.RunStats->m_flStageExitSpeed[0][1]); + pOverallKey->SetFloat("avg_vel_2D", t.RunStats->m_flStageVelocityAvg[0][1]); + pOverallKey->SetFloat("max_vel_2D", t.RunStats->m_flStageVelocityMax[0][1]); char stageName[9]; // "stage 64\0" if (GetStageCount() > 1) @@ -195,22 +195,22 @@ void CTimer::SaveTime() Q_snprintf(stageName, sizeof(stageName), "stage %d", i2); KeyValues *pStageKey = new KeyValues(stageName); - pStageKey->SetFloat("time", t.stagetime[i2]); - pStageKey->SetFloat("enter_time", t.stageentertime[i2]); - pStageKey->SetInt("num_jumps", t.stagejumps[i2]); - pStageKey->SetInt("num_strafes", t.stagestrafes[i2]); - pStageKey->SetFloat("avg_sync", t.stageavgsync[i2]); - pStageKey->SetFloat("avg_sync2", t.stageavgsync2[i2]); - - pStageKey->SetFloat("avg_vel", t.stageavgvel[i2][0]); - pStageKey->SetFloat("max_vel", t.stagemaxvel[i2][0]); - pStageKey->SetFloat("stage_enter_vel", t.stagestartvel[i2][0]); - pStageKey->SetFloat("stage_exit_vel", t.stageexitvel[i2][0]); - - pStageKey->SetFloat("avg_vel_2D", t.stageavgvel[i2][1]); - pStageKey->SetFloat("max_vel_2D", t.stagemaxvel[i2][1]); - pStageKey->SetFloat("stage_enter_vel_2D", t.stagestartvel[i2][1]); - pStageKey->SetFloat("stage_exit_vel_2D", t.stageexitvel[i2][1]); + pStageKey->SetFloat("time", t.RunStats->m_flStageTime[i2]); + pStageKey->SetFloat("enter_time", t.RunStats->m_flStageEnterTime[i2]); + pStageKey->SetInt("num_jumps", t.RunStats->m_iStageJumps[i2]); + pStageKey->SetInt("num_strafes", t.RunStats->m_iStageStrafes[i2]); + pStageKey->SetFloat("avg_sync", t.RunStats->m_flStageStrafeSyncAvg[i2]); + pStageKey->SetFloat("avg_sync2", t.RunStats->m_flStageStrafeSync2Avg[i2]); + + pStageKey->SetFloat("avg_vel", t.RunStats->m_flStageVelocityAvg[i2][0]); + pStageKey->SetFloat("max_vel", t.RunStats->m_flStageVelocityMax[i2][0]); + pStageKey->SetFloat("stage_enter_vel", t.RunStats->m_flStageEnterSpeed[i2][0]); + pStageKey->SetFloat("stage_exit_vel", t.RunStats->m_flStageExitSpeed[i2][0]); + + pStageKey->SetFloat("avg_vel_2D", t.RunStats->m_flStageVelocityAvg[i2][1]); + pStageKey->SetFloat("max_vel_2D", t.RunStats->m_flStageVelocityMax[i2][1]); + pStageKey->SetFloat("stage_enter_vel_2D", t.RunStats->m_flStageEnterSpeed[i2][1]); + pStageKey->SetFloat("stage_exit_vel_2D", t.RunStats->m_flStageExitSpeed[i2][1]); pSubkey->AddSubKey(pStageKey); } @@ -254,39 +254,7 @@ void CTimer::Stop(bool endTrigger /* = false */) t.flags = pPlayer->m_iRunFlags; time(&t.date); - //OVERALL STATS - STAGE 0 - t.stagejumps[0] = pPlayer->m_nStageJumps[0]; - t.stagestrafes[0] = pPlayer->m_nStageStrafes[0]; - t.stageavgsync[0] = pPlayer->m_flStageStrafeSyncAvg[0]; - t.stageavgsync2[0] = pPlayer->m_flStageStrafeSync2Avg[0]; - for (int j = 0; j < 2; j++) - { - t.stageavgvel[0][j] = pPlayer->m_flStageVelocityAvg[0][j]; - t.stagemaxvel[0][j] = pPlayer->m_flStageVelocityMax[0][j]; - t.stagestartvel[0][j] = pPlayer->m_flStageEnterVelocity[0][j]; - t.stageexitvel[0][j] = pPlayer->m_flStageExitVelocity[0][j]; - } - // -------- - if (GetStageCount() > 1) //don't save stage specific stats if we are on a linear map - { - for (int i = 1; i <= GetStageCount(); i++) //stages start at 1 since stage 0 is overall stats - { - t.stageentertime[i] = m_iStageEnterTime[i]; - t.stagetime[i] = i == GetStageCount() ? (t.time_sec - m_iStageEnterTime[i]) : - m_iStageEnterTime[i+1] - m_iStageEnterTime[i]; //each stage's total time is the time from the previous stage to this one - t.stagejumps[i] = pPlayer->m_nStageJumps[i]; - t.stagestrafes[i] = pPlayer->m_nStageStrafes[i]; - t.stageavgsync[i] = pPlayer->m_flStageStrafeSyncAvg[i]; - t.stageavgsync2[i] = pPlayer->m_flStageStrafeSync2Avg[i]; - for (int k = 0; k < 2; k++) - { - t.stageavgvel[i][k] = pPlayer->m_flStageVelocityAvg[i][k]; - t.stagemaxvel[i][k] = pPlayer->m_flStageVelocityMax[i][k]; - t.stagestartvel[i][k] = pPlayer->m_flStageEnterVelocity[i][k]; - t.stageexitvel[i][k] = pPlayer->m_flStageExitVelocity[i][k]; - } - } - } + t.RunStats = pPlayer->m_PlayerRunStats; //copy all the run stats localTimes.AddToTail(t); diff --git a/mp/src/game/server/momentum/Timer.h b/mp/src/game/server/momentum/Timer.h index 56d0013cf0..cad94997b4 100644 --- a/mp/src/game/server/momentum/Timer.h +++ b/mp/src/game/server/momentum/Timer.h @@ -135,8 +135,6 @@ class CTimer void SaveTime(); void OnMapEnd(const char *); void OnMapStart(const char *); - // returns last runtime in ticks - int GetLastRunTimeTicks() { return m_iEndTick - m_iStartTick; } void DispatchMapInfo(); // Practice mode- noclip mode that stops timer void PracticeMove(); @@ -179,15 +177,7 @@ class CTimer int flags; //stage specific stats: - float stagetime[MAX_STAGES], stageentertime[MAX_STAGES], stageavgsync[MAX_STAGES], stageavgsync2[MAX_STAGES]; - - //These members are 2D arrays which store the XYZ velocity length in index 0 and XY velocity in index 1 - float stagestartvel[MAX_STAGES][2], //The velocity that you start the stage with (exit the stage start trigger) - stageexitvel[MAX_STAGES][2], //The velocity with which you exit the stage (this stage -> next) - stageavgvel[MAX_STAGES][2], - stagemaxvel[MAX_STAGES][2]; - - int stagejumps[MAX_STAGES], stagestrafes[MAX_STAGES]; + RunStats_t *RunStats = new RunStats_t(); }; struct Checkpoint diff --git a/mp/src/game/server/momentum/mom_player.cpp b/mp/src/game/server/momentum/mom_player.cpp index 1d4ca034fb..b7853f36b5 100644 --- a/mp/src/game/server/momentum/mom_player.cpp +++ b/mp/src/game/server/momentum/mom_player.cpp @@ -46,7 +46,10 @@ CMomentumPlayer::CMomentumPlayer() m_iRunFlags = 0; } -CMomentumPlayer::~CMomentumPlayer() {} +CMomentumPlayer::~CMomentumPlayer() +{ + delete m_PlayerRunStats; +} void CMomentumPlayer::Precache() { @@ -244,8 +247,8 @@ void CMomentumPlayer::CheckForBhop() if (g_Timer->IsRunning()) { int currentStage = g_Timer->GetCurrentStageNumber(); - m_nStageJumps[0]++; - m_nStageJumps[currentStage]++; + m_PlayerRunStats->m_iStageJumps[0]++; + m_PlayerRunStats->m_iStageJumps[currentStage]++; } } } @@ -268,40 +271,40 @@ void CMomentumPlayer::UpdateRunStats() if (!m_bPrevTimerRunning) //timer started on this tick { //Reset old run stats -- moved to on start's touch - m_flStageEnterVelocity[0][0] = velocity; - m_flStageEnterVelocity[0][1] = velocity2D; + m_PlayerRunStats->m_flStageEnterSpeed[0][0] = velocity; + m_PlayerRunStats->m_flStageEnterSpeed[0][1] = velocity2D; //Compare against successive bhops to avoid incrimenting when the player was in the air without jumping (for surf) if (GetGroundEntity() == NULL && m_iSuccessiveBhops) { - m_nStageJumps[0]++; - m_nStageJumps[currentStage]++; + m_PlayerRunStats->m_iStageJumps[0]++; + m_PlayerRunStats->m_iStageJumps[currentStage]++; } if (m_nButtons & IN_MOVERIGHT || m_nButtons & IN_MOVELEFT) { - m_nStageStrafes[0]++; - m_nStageStrafes[currentStage]++; + m_PlayerRunStats->m_iStageStrafes[0]++; + m_PlayerRunStats->m_iStageStrafes[currentStage]++; } } if (m_nButtons & IN_MOVELEFT && !(m_nPrevButtons & IN_MOVELEFT)) { - m_nStageStrafes[0]++; - m_nStageStrafes[currentStage]++; + m_PlayerRunStats->m_iStageStrafes[0]++; + m_PlayerRunStats->m_iStageStrafes[currentStage]++; } else if (m_nButtons & IN_MOVERIGHT && !(m_nPrevButtons & IN_MOVERIGHT)) { - m_nStageStrafes[0]++; - m_nStageStrafes[currentStage]++; + m_PlayerRunStats->m_iStageStrafes[0]++; + m_PlayerRunStats->m_iStageStrafes[currentStage]++; } // ---- MAX VELOCITY ---- - if (velocity > m_flStageVelocityMax[0][0]) - m_flStageVelocityMax[0][0] = velocity; - if (velocity2D > m_flStageVelocityMax[0][1]) - m_flStageVelocityMax[0][1] = velocity; + if (velocity > m_PlayerRunStats->m_flStageVelocityMax[0][0]) + m_PlayerRunStats->m_flStageVelocityMax[0][0] = velocity; + if (velocity2D > m_PlayerRunStats->m_flStageVelocityMax[0][1]) + m_PlayerRunStats->m_flStageVelocityMax[0][1] = velocity; //also do max velocity per stage - if (velocity > m_flStageVelocityMax[currentStage][0]) - m_flStageVelocityMax[currentStage][0] = velocity; - if (velocity2D > m_flStageVelocityMax[currentStage][1]) - m_flStageVelocityMax[currentStage][1] = velocity2D; + if (velocity >m_PlayerRunStats->m_flStageVelocityMax[currentStage][0]) + m_PlayerRunStats->m_flStageVelocityMax[currentStage][0] = velocity; + if (velocity2D > m_PlayerRunStats->m_flStageVelocityMax[currentStage][1]) + m_PlayerRunStats->m_flStageVelocityMax[currentStage][1] = velocity2D; // ---------- // --- STAGE ENTER VELOCITY --- @@ -345,8 +348,8 @@ void CMomentumPlayer::UpdateRunStats() if (playerMoveEvent) { - playerMoveEvent->SetInt("num_strafes", m_nStageStrafes[0]); - playerMoveEvent->SetInt("num_jumps", m_nStageJumps[0]); + playerMoveEvent->SetInt("num_strafes", m_PlayerRunStats->m_iStageStrafes[0]); + playerMoveEvent->SetInt("num_jumps", m_PlayerRunStats->m_iStageJumps[0]); bool onGround = GetFlags() & FL_ONGROUND; if ((m_nButtons & IN_JUMP) && onGround || m_nButtons & (IN_MOVELEFT | IN_MOVERIGHT)) gameeventmanager->FireEvent(playerMoveEvent); @@ -363,24 +366,8 @@ void CMomentumPlayer::ResetRunStats() m_flStrafeSync = 0; m_flStrafeSync2 = 0; - for (int i = 0; i < MAX_STAGES; i++) - { - m_nStageAvgCount[i] = 0; - m_nStageJumps[i] = 0; - m_nStageStrafes[i] = 0; - m_flStageTotalSync[i] = 0; - m_flStageTotalSync2[i] = 0; - m_flStageStrafeSyncAvg[i] = 0; - m_flStageStrafeSync2Avg[i] = 0; - for (int k = 0; k < 2; k++) - { - m_flStageVelocityMax[i][k] = 0; - m_flStageVelocityAvg[i][k] = 0; - m_flStageEnterVelocity[i][k] = 0; - m_flStageExitVelocity[i][k] = 0; - m_flStageTotalVelocity[i][k] = 0; - } - } + delete m_PlayerRunStats; + m_PlayerRunStats = new RunStats_t(); } void CMomentumPlayer::CalculateAverageStats() { @@ -396,10 +383,10 @@ void CMomentumPlayer::CalculateAverageStats() m_nStageAvgCount[currentStage]++; - m_flStageStrafeSyncAvg[currentStage] = m_flStageTotalSync[currentStage] / float(m_nStageAvgCount[currentStage]); - m_flStageStrafeSync2Avg[currentStage] = m_flStageTotalSync2[currentStage] / float(m_nStageAvgCount[currentStage]); - m_flStageVelocityAvg[currentStage][0] = m_flStageTotalVelocity[currentStage][0] / float(m_nStageAvgCount[currentStage]); - m_flStageVelocityAvg[currentStage][1] = m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats->m_flStageStrafeSyncAvg[currentStage] = m_flStageTotalSync[currentStage] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats->m_flStageStrafeSync2Avg[currentStage] = m_flStageTotalSync2[currentStage] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats->m_flStageVelocityAvg[currentStage][0] = m_flStageTotalVelocity[currentStage][0] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats->m_flStageVelocityAvg[currentStage][1] = m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); //stage 0 is "overall" - also update these as well, no matter which stage we are on m_flStageTotalSync[0] += m_flStrafeSync; @@ -408,10 +395,10 @@ void CMomentumPlayer::CalculateAverageStats() m_flStageTotalVelocity[0][1] += GetLocalVelocity().Length2D(); m_nStageAvgCount[0]++; - m_flStageStrafeSyncAvg[0] = m_flStageTotalSync[currentStage] / float(m_nStageAvgCount[currentStage]); - m_flStageStrafeSync2Avg[0] = m_flStageTotalSync2[currentStage] / float(m_nStageAvgCount[currentStage]); - m_flStageVelocityAvg[0][0] = m_flStageTotalVelocity[currentStage][0] / float(m_nStageAvgCount[currentStage]); - m_flStageVelocityAvg[0][1] = m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats->m_flStageStrafeSyncAvg[0] = m_flStageTotalSync[currentStage] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats->m_flStageStrafeSync2Avg[0] = m_flStageTotalSync2[currentStage] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats->m_flStageVelocityAvg[0][0] = m_flStageTotalVelocity[currentStage][0] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats->m_flStageVelocityAvg[0][1] = m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); } // think once per 0.1 second interval so we avoid making the totals extremely large diff --git a/mp/src/game/server/momentum/mom_player.h b/mp/src/game/server/momentum/mom_player.h index 89da37434e..76c30916ce 100644 --- a/mp/src/game/server/momentum/mom_player.h +++ b/mp/src/game/server/momentum/mom_player.h @@ -8,6 +8,7 @@ #include "mom_blockfix.h" #include "momentum/mom_shareddefs.h" #include "player.h" +#include "util/run_stats.h" class CMomentumPlayer : public CBasePlayer { @@ -111,15 +112,8 @@ class CMomentumPlayer : public CBasePlayer int GetLastBlock() { return m_iLastBlock; } float GetPunishTime() { return m_flPunishTime; } - //stage stats. index 0 is overall stats - int m_nStageJumps[MAX_STAGES], m_nStageStrafes[MAX_STAGES]; - float m_flStageStrafeSyncAvg[MAX_STAGES], m_flStageStrafeSync2Avg[MAX_STAGES]; - - //These members are 2D arrays so we can store both 2D and 3D velocities in them. Index 0 is 3D and index 1 is 2D - float m_flStageVelocityMax[MAX_STAGES][2], - m_flStageVelocityAvg[MAX_STAGES][2], - m_flStageEnterVelocity[MAX_STAGES][2],//The velocity with which you enter the stage (leave the stage start trigger) - m_flStageExitVelocity[MAX_STAGES][2];//The velocity with which you exit this stage (this stage -> next) + //Run Stats + RunStats_t *m_PlayerRunStats = new RunStats_t(); //for calc avg int m_nStageAvgCount[MAX_STAGES]; diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 0013684879..62057de027 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -34,7 +34,7 @@ void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway, m_bShouldStopRec = false; CMomentumPlayer *pMOMPlayer = ToCMOMPlayer(pPlayer); char newRecordingName[MAX_PATH], newRecordingPath[MAX_PATH], runTime[BUFSIZETIME]; - mom_UTIL->FormatTime(g_Timer->GetLastRunTimeTicks() * gpGlobals->interval_per_tick, runTime); + mom_UTIL->FormatTime(g_Timer->GetLastRunTime(), runTime); Q_snprintf(newRecordingName, MAX_PATH, "%s_%s_%s.momrec", pMOMPlayer->GetPlayerName(), gpGlobals->mapname.ToCStr(), runTime); V_ComposeFileName(RECORDING_PATH, newRecordingName, newRecordingPath, MAX_PATH); //V_ComposeFileName calls all relevent filename functions for us! THANKS GABEN @@ -73,38 +73,18 @@ CUtlBuffer *CMomentumReplaySystem::UpdateRecordingParams() replay_header_t CMomentumReplaySystem::CreateHeader() { replay_header_t header; - Q_strcpy(header.demofilestamp, DEMO_HEADER_ID); - header.demoProtoVersion = DEMO_PROTOCOL_VERSION; + Q_strcpy(header.demofilestamp, REPLAY_HEADER_ID); + header.demoProtoVersion = REPLAY_PROTOCOL_VERSION; Q_strcpy(header.mapName, gpGlobals->mapname.ToCStr()); Q_strcpy(header.playerName, m_player->GetPlayerName()); header.steamID64 = steamapicontext->SteamUser() ? steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() : 0; header.interval_per_tick = gpGlobals->interval_per_tick; - header.runTimeTicks = g_Timer->GetLastRunTimeTicks(); + header.runTime = g_Timer->GetLastRunTime(); time(&header.unixEpocDate); - // --- RUN STATS --- - for (int i = 0; i < 2; i++) - { - header.stats.m_flStageExitSpeed[0][i] = m_player->m_flStageExitVelocity[0][i]; - header.stats.m_flStageEnterSpeed[0][i] = m_player->m_flStageEnterVelocity[0][i]; - } - - for (int i = 0; i < MAX_STAGES; i++) { - for (int k = 0; i < 2; k++) - { - header.stats.m_flStageEnterSpeed[i][k] = m_player->m_flStageEnterVelocity[i][k]; - header.stats.m_flStageExitSpeed[i][k] = m_player->m_flStageExitVelocity[i][k]; - header.stats.m_flStageVelocityAvg[i][k] = m_player->m_flStageVelocityAvg[i][k]; - header.stats.m_flStageVelocityMax[i][k] = m_player->m_flStageVelocityMax[i][k]; - } - - header.stats.m_flStageStrafeSyncAvg[i] = m_player->m_flStageStrafeSyncAvg[i]; - header.stats.m_flStageStrafeSync2Avg[i] = m_player->m_flStageStrafeSync2Avg[i]; - header.stats.m_iStageJumps[i] = m_player->m_nStageJumps[i]; - header.stats.m_iStageStrafes[i] = m_player->m_nStageStrafes[i]; - } + //header.stats = m_player->m_PlayerRunStats; //copy ALL run stats using operator overload return header; } void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer &buf) @@ -117,6 +97,7 @@ void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer &buf) filesystem->Seek(m_fhFileHandle, 0, FILESYSTEM_SEEK_HEAD); filesystem->Write(&littleEndianHeader, sizeof(replay_header_t), m_fhFileHandle); + DevLog("\n\nreplay header size: %i\n", sizeof(replay_header_t)); Assert(buf.IsValid()); //write write from the CUtilBuffer to our filehandle: @@ -143,13 +124,13 @@ replay_header_t* CMomentumReplaySystem::ReadHeader(FileHandle_t file, const char ByteSwap_replay_header_t(m_replayHeader); - if (Q_strcmp(m_replayHeader.demofilestamp, DEMO_HEADER_ID)) { //DEMO_HEADER_ID is __NOT__ the same as the stamp from the header we read from file + if (Q_strcmp(m_replayHeader.demofilestamp, REPLAY_HEADER_ID)) { //DEMO_HEADER_ID is __NOT__ the same as the stamp from the header we read from file ConMsg("%s has invalid replay header ID.\n", filename); return nullptr; } - if (m_replayHeader.demoProtoVersion != DEMO_PROTOCOL_VERSION) { + if (m_replayHeader.demoProtoVersion != REPLAY_PROTOCOL_VERSION) { ConMsg("ERROR: replay file protocol %i outdated, engine version is %i \n", - m_replayHeader.demoProtoVersion, DEMO_PROTOCOL_VERSION); + m_replayHeader.demoProtoVersion, REPLAY_PROTOCOL_VERSION); return nullptr; } diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index c4f50e6af3..76cf44f58c 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -213,10 +213,10 @@ void CMomentumReplayGhostEntity::UpdateStats(Vector ghostVel, CMomentumPlayer *p } // --- JUMP AND STRAFE COUNTER --- if (GetGroundEntity() != NULL && currentStep.m_nPlayerButtons & IN_JUMP) - pPlayer->m_nStageJumps[0]++; + pPlayer->m_PlayerRunStats->m_iStageJumps[0]++; if ((currentStep.m_nPlayerButtons & IN_MOVELEFT && !(m_nOldReplayButtons & IN_MOVELEFT)) || (currentStep.m_nPlayerButtons & IN_MOVERIGHT && !(m_nOldReplayButtons & IN_MOVERIGHT)) ) - pPlayer->m_nStageStrafes[0]++; + pPlayer->m_PlayerRunStats->m_iStageStrafes[0]++; m_flLastSyncVelocity = SyncVelocity; m_qLastEyeAngle = EyeAngles(); diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index d754f3ce81..d8b5207356 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -43,22 +43,22 @@ void CTriggerStage::StartTouch(CBaseEntity *pOther) { stageEvent->SetInt("stage_num", stageNum); stageEvent->SetFloat("stage_enter_time", g_Timer->CalculateStageTime(stageNum)); - stageEvent->SetInt("num_jumps", pPlayer->m_nStageJumps[stageNum - 1]); - stageEvent->SetFloat("num_strafes", pPlayer->m_nStageStrafes[stageNum - 1]); - stageEvent->SetFloat("avg_sync", pPlayer->m_flStageStrafeSyncAvg[stageNum - 1]); - stageEvent->SetFloat("avg_sync2", pPlayer->m_flStageStrafeSync2Avg[stageNum - 1]); + stageEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats->m_iStageJumps[stageNum - 1]); + stageEvent->SetFloat("num_strafes", pPlayer->m_PlayerRunStats->m_iStageStrafes[stageNum - 1]); + stageEvent->SetFloat("avg_sync", pPlayer->m_PlayerRunStats->m_flStageStrafeSyncAvg[stageNum - 1]); + stageEvent->SetFloat("avg_sync2", pPlayer->m_PlayerRunStats->m_flStageStrafeSync2Avg[stageNum - 1]); //3D VELOCITY - stageEvent->SetFloat("max_vel", pPlayer->m_flStageVelocityMax[stageNum - 1][0]); - stageEvent->SetFloat("avg_vel", pPlayer->m_flStageVelocityAvg[stageNum - 1][0]); - pPlayer->m_flStageExitVelocity[stageNum - 1][0] = pPlayer->GetLocalVelocity().Length(); - stageEvent->SetFloat("stage_exit_vel", pPlayer->m_flStageExitVelocity[stageNum - 1][0]); + stageEvent->SetFloat("max_vel", pPlayer->m_PlayerRunStats->m_flStageVelocityMax[stageNum - 1][0]); + stageEvent->SetFloat("avg_vel", pPlayer->m_PlayerRunStats->m_flStageVelocityAvg[stageNum - 1][0]); + pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum - 1][0] = pPlayer->GetLocalVelocity().Length(); + stageEvent->SetFloat("stage_exit_vel", pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum - 1][0]); //2D VELOCITY - stageEvent->SetFloat("max_vel_2D", pPlayer->m_flStageVelocityMax[stageNum - 1][1]); - stageEvent->SetFloat("avg_vel_2D", pPlayer->m_flStageVelocityAvg[stageNum - 1][1]); - pPlayer->m_flStageExitVelocity[stageNum - 1][1] = pPlayer->GetLocalVelocity().Length2D(); - stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_flStageExitVelocity[stageNum - 1][1]); + stageEvent->SetFloat("max_vel_2D", pPlayer->m_PlayerRunStats->m_flStageVelocityMax[stageNum - 1][1]); + stageEvent->SetFloat("avg_vel_2D", pPlayer->m_PlayerRunStats->m_flStageVelocityAvg[stageNum - 1][1]); + pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum - 1][1] = pPlayer->GetLocalVelocity().Length2D(); + stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum - 1][1]); gameeventmanager->FireEvent(stageEvent); } @@ -87,12 +87,12 @@ void CTriggerStage::EndTouch(CBaseEntity *pOther) stageEvent->SetInt("stage_num", stageNum); //3D VELOCITY - pPlayer->m_flStageEnterVelocity[stageNum][0] = pPlayer->GetLocalVelocity().Length(); - stageEvent->SetFloat("stage_enter_vel", pPlayer->m_flStageEnterVelocity[stageNum][0]); + pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); + stageEvent->SetFloat("stage_enter_vel", pPlayer->m_PlayerRunStats->m_flStageEnterSpeed[stageNum][0]); //2D VELOCITY - pPlayer->m_flStageEnterVelocity[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); - stageEvent->SetFloat("stage_enter_vel_2D", pPlayer->m_flStageEnterVelocity[stageNum][1]); + pPlayer->m_PlayerRunStats->m_flStageEnterSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); + stageEvent->SetFloat("stage_enter_vel_2D", pPlayer->m_PlayerRunStats->m_flStageEnterSpeed[stageNum][1]); gameeventmanager->FireEvent(stageEvent); } @@ -246,32 +246,32 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) //send run stats via GameEventManager if (timerStopEvent) { - timerStopEvent->SetFloat("avg_sync", pPlayer->m_flStageStrafeSyncAvg[0]); - timerStopEvent->SetFloat("avg_sync2", pPlayer->m_flStageStrafeSync2Avg[0]); - timerStopEvent->SetInt("num_strafes", pPlayer->m_nStageStrafes[0]); - timerStopEvent->SetInt("num_jumps", pPlayer->m_nStageJumps[0]); + timerStopEvent->SetFloat("avg_sync", pPlayer->m_PlayerRunStats->m_flStageStrafeSyncAvg[0]); + timerStopEvent->SetFloat("avg_sync2", pPlayer->m_PlayerRunStats->m_flStageStrafeSync2Avg[0]); + timerStopEvent->SetInt("num_strafes", pPlayer->m_PlayerRunStats->m_iStageStrafes[0]); + timerStopEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats->m_iStageJumps[0]); //3D VELCOCITY STATS - INDEX 0 - timerStopEvent->SetFloat("avg_vel", pPlayer->m_flStageVelocityAvg[0][0]); - timerStopEvent->SetFloat("start_vel", pPlayer->m_flStageEnterVelocity[0][0]); + timerStopEvent->SetFloat("avg_vel", pPlayer->m_PlayerRunStats->m_flStageVelocityAvg[0][0]); + timerStopEvent->SetFloat("start_vel", pPlayer->m_PlayerRunStats->m_flStageEnterSpeed[0][0]); float endvel = pPlayer->GetLocalVelocity().Length(); timerStopEvent->SetFloat("end_vel", endvel); - if (endvel > pPlayer->m_flStageVelocityMax[0][0]) + if (endvel > pPlayer->m_PlayerRunStats->m_flStageVelocityMax[0][0]) timerStopEvent->SetFloat("max_vel", endvel); else - timerStopEvent->SetFloat("max_vel", pPlayer->m_flStageVelocityMax[0][0]); - pPlayer->m_flStageExitVelocity[0][0] = endvel; //we have to set end speed here or else it will be saved as 0 + timerStopEvent->SetFloat("max_vel", pPlayer->m_PlayerRunStats->m_flStageVelocityMax[0][0]); + pPlayer->m_PlayerRunStats->m_flStageExitSpeed[0][0] = endvel; //we have to set end speed here or else it will be saved as 0 //2D VELOCITY STATS - INDEX 1 - timerStopEvent->SetFloat("avg_vel_2D", pPlayer->m_flStageVelocityAvg[0][1]); - timerStopEvent->SetFloat("start_vel_2D", pPlayer->m_flStageEnterVelocity[0][1]); + timerStopEvent->SetFloat("avg_vel_2D", pPlayer->m_PlayerRunStats->m_flStageVelocityAvg[0][1]); + timerStopEvent->SetFloat("start_vel_2D", pPlayer->m_PlayerRunStats->m_flStageEnterSpeed[0][1]); float endvel2D = pPlayer->GetLocalVelocity().Length2D(); timerStopEvent->SetFloat("end_vel_2D", endvel2D); - if (endvel2D > pPlayer->m_flStageVelocityMax[0][1]) + if (endvel2D > pPlayer->m_PlayerRunStats->m_flStageVelocityMax[0][1]) timerStopEvent->SetFloat("max_vel_2D", endvel2D); else - timerStopEvent->SetFloat("max_vel_2D", pPlayer->m_flStageVelocityMax[0][1]); - pPlayer->m_flStageExitVelocity[0][1] = endvel2D; + timerStopEvent->SetFloat("max_vel_2D", pPlayer->m_PlayerRunStats->m_flStageVelocityMax[0][1]); + pPlayer->m_PlayerRunStats->m_flStageExitSpeed[0][1] = endvel2D; gameeventmanager->FireEvent(timerStopEvent); } @@ -287,10 +287,10 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) stageEvent->SetFloat("stage_enter_time", g_Timer->GetLastRunTime()); //This is needed so we have an ending velocity. - pPlayer->m_flStageExitVelocity[stageNum][0] = pPlayer->GetLocalVelocity().Length(); - stageEvent->SetFloat("stage_exit_vel", pPlayer->m_flStageExitVelocity[stageNum][0]); - pPlayer->m_flStageExitVelocity[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); - stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_flStageExitVelocity[stageNum][1]); + pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); + stageEvent->SetFloat("stage_exit_vel", pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum][0]); + pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); + stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum][1]); gameeventmanager->FireEvent(stageEvent); } diff --git a/mp/src/game/server/momentum/replayformat.h b/mp/src/game/server/momentum/replayformat.h index 363ee906ec..d220f46fbd 100644 --- a/mp/src/game/server/momentum/replayformat.h +++ b/mp/src/game/server/momentum/replayformat.h @@ -5,8 +5,8 @@ #include "mom_shareddefs.h" #include "util/run_stats.h" -#define DEMO_HEADER_ID "MOMREPLAY" -#define DEMO_PROTOCOL_VERSION 2 +#define REPLAY_HEADER_ID "MOMREPLAY" +#define REPLAY_PROTOCOL_VERSION 2 //describes a single frame of a replay struct replay_frame_t @@ -38,38 +38,36 @@ inline void ByteSwap_replay_frame_t(replay_frame_t &swap) //the replay header, stores a bunch of information about the replay as well as the run stats for that replay struct replay_header_t { - char demofilestamp[9]; //should be DEMO_HEADER_ID - int demoProtoVersion; //should be DEMO_PROTOCOL_VERSION + char demofilestamp[9]; //should be REPLAY_HEADER_ID + int demoProtoVersion; //should be REPLAY_PROTOCOL_VERSION time_t unixEpocDate; //redundant date check char mapName[MAX_PATH]; char playerName[MAX_PATH]; uint64 steamID64; float interval_per_tick; - int runTimeTicks; //Total run time in ticks + float runTime; - RunStats_t stats; - /*float m_flStartSpeed, m_flEndSpeed; - int m_nStageJumps[MAX_STAGES], m_nStageStrafes[MAX_STAGES]; - float m_flStageVelocityMax[MAX_STAGES], m_flStageVelocityAvg[MAX_STAGES], - m_flStageStrafeSyncAvg[MAX_STAGES], m_flStageStrafeSync2Avg[MAX_STAGES], m_flStageEnterVelocity[MAX_STAGES];*/ + RunStats_t stats; //a massive ammount of run stats all stored in the header. }; //byteswap for int and float members of header, swaps the endianness (byte order) in order to read correctly inline void ByteSwap_replay_header_t(replay_header_t &swap) { swap.demoProtoVersion = LittleDWord(swap.demoProtoVersion); - swap.runTimeTicks = LittleDWord(swap.runTimeTicks); swap.unixEpocDate = LittleLong(swap.unixEpocDate); swap.steamID64 = LittleLong(swap.steamID64); LittleFloat(&swap.interval_per_tick, &swap.interval_per_tick); + LittleFloat(&swap.runTime, &swap.runTime); //MOM_TODO: Do we want to also have a float time? - // --- run stats --- - for (int i = 0; i < 2; i++) + + + for (int i = 0; i < MAX_STAGES; i++) { - LittleFloat(&swap.stats.m_flStageExitSpeed[0][i], &swap.stats.m_flStageExitSpeed[0][i]); - LittleFloat(&swap.stats.m_flStageEnterSpeed[0][i], &swap.stats.m_flStageEnterSpeed[0][i]); - } - - for (int i = 0; i < MAX_STAGES; i++) { + LittleFloat(&swap.stats.m_flStageEnterTime[i], &swap.stats.m_flStageEnterTime[i]); + LittleFloat(&swap.stats.m_flStageTime[i], &swap.stats.m_flStageTime[i]); + LittleFloat(&swap.stats.m_flStageStrafeSyncAvg[i], &swap.stats.m_flStageStrafeSyncAvg[i]); + LittleFloat(&swap.stats.m_flStageStrafeSync2Avg[i], &swap.stats.m_flStageStrafeSync2Avg[i]); + swap.stats.m_iStageJumps[i] = LittleDWord(swap.stats.m_iStageJumps[i]); + swap.stats.m_iStageStrafes[i] = LittleDWord(swap.stats.m_iStageStrafes[i]); for (int k = 0; k < 2; k++) { @@ -78,11 +76,7 @@ inline void ByteSwap_replay_header_t(replay_header_t &swap) LittleFloat(&swap.stats.m_flStageVelocityAvg[i][k], &swap.stats.m_flStageVelocityAvg[i][k]); LittleFloat(&swap.stats.m_flStageVelocityMax[i][k], &swap.stats.m_flStageVelocityMax[i][k]); } - - LittleFloat(&swap.stats.m_flStageStrafeSyncAvg[i], &swap.stats.m_flStageStrafeSyncAvg[i]); - LittleFloat(&swap.stats.m_flStageStrafeSync2Avg[i], &swap.stats.m_flStageStrafeSync2Avg[i]); - swap.stats.m_iStageJumps[i] = LittleDWord(swap.stats.m_iStageJumps[i]); - swap.stats.m_iStageStrafes[i] = LittleDWord(swap.stats.m_iStageStrafes[i]); } + } #endif //REPLAYFORMAT_H \ No newline at end of file diff --git a/mp/src/game/shared/momentum/util/run_stats.h b/mp/src/game/shared/momentum/util/run_stats.h index 2ca1852723..d879f4c3a5 100644 --- a/mp/src/game/shared/momentum/util/run_stats.h +++ b/mp/src/game/shared/momentum/util/run_stats.h @@ -5,6 +5,45 @@ struct RunStats_t { + RunStats_t() + { + for (int i = 0; i < MAX_STAGES; i++) + { + m_iStageJumps[i] = 0; + m_iStageStrafes[i] = 0; + m_flStageStrafeSyncAvg[i] = 0; + m_flStageStrafeSync2Avg[i] = 0; + m_flStageEnterTime[i] = 0; + m_flStageTime[i] = 0; + for (int k = 0; k < 2; k++) + { + m_flStageVelocityMax[i][k] = 0; + m_flStageVelocityAvg[i][k] = 0; + m_flStageEnterSpeed[i][k] = 0; + m_flStageExitSpeed[i][k] = 0; + } + } + } + RunStats_t& operator=(const RunStats_t& other) + { + for (int i = 0; i < MAX_STAGES; i++) + { + m_iStageJumps[i] = other.m_iStageJumps[i]; + m_iStageStrafes[i] = other.m_iStageStrafes[i]; + m_flStageStrafeSyncAvg[i] = other.m_flStageStrafeSyncAvg[i]; + m_flStageStrafeSync2Avg[i] = other.m_flStageStrafeSync2Avg[i]; + m_flStageEnterTime[i] = other.m_flStageEnterTime[i]; + m_flStageTime[i] = other.m_flStageTime[i]; + for (int k = 0; k < 2; k++) + { + m_flStageVelocityMax[i][k] = other.m_flStageVelocityMax[i][k]; + m_flStageVelocityAvg[i][k] = other.m_flStageVelocityAvg[i][k]; + m_flStageEnterSpeed[i][k] = other.m_flStageEnterSpeed[i][k]; + m_flStageExitSpeed[i][k] = other.m_flStageExitSpeed[i][k]; + } + } + return *this; + } //MOM_TODO: We're going to hold an unbiased view at both //checkpoint and stages. If a map is linear yet has checkpoints, //it can be free to use these below to display stats for the player to compare against. From 265dd369f279e1ee3f9c94905dbc5ccacffd28db Mon Sep 17 00:00:00 2001 From: tuxxi Date: Wed, 18 May 2016 18:13:30 -0700 Subject: [PATCH 028/101] fixed 0 stage enter vel, fixed not copying stats to header --- mp/src/game/server/momentum/mom_replay.cpp | 2 +- mp/src/game/server/momentum/mom_triggers.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 62057de027..f4d39145bd 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -84,7 +84,7 @@ replay_header_t CMomentumReplaySystem::CreateHeader() header.runTime = g_Timer->GetLastRunTime(); time(&header.unixEpocDate); - //header.stats = m_player->m_PlayerRunStats; //copy ALL run stats using operator overload + header.stats = *m_player->m_PlayerRunStats; //copy ALL run stats using operator overload return header; } void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer &buf) diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index d8b5207356..6b0f73f8da 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -87,7 +87,7 @@ void CTriggerStage::EndTouch(CBaseEntity *pOther) stageEvent->SetInt("stage_num", stageNum); //3D VELOCITY - pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); + pPlayer->m_PlayerRunStats->m_flStageEnterSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); stageEvent->SetFloat("stage_enter_vel", pPlayer->m_PlayerRunStats->m_flStageEnterSpeed[stageNum][0]); //2D VELOCITY From c8e32b28f7c2f858dbe99b108d12324af8d29f21 Mon Sep 17 00:00:00 2001 From: Nick K Date: Wed, 18 May 2016 21:34:35 -0400 Subject: [PATCH 029/101] Change runstats back to object from pointer --- .../client/momentum/mom_event_listener.cpp | 11 +- mp/src/game/server/momentum/Timer.cpp | 108 +++++++++--------- mp/src/game/server/momentum/Timer.h | 2 +- mp/src/game/server/momentum/mom_player.cpp | 71 ++++++------ mp/src/game/server/momentum/mom_player.h | 23 ++-- mp/src/game/server/momentum/mom_replay.cpp | 2 +- .../server/momentum/mom_replay_entity.cpp | 4 +- .../game/server/momentum/mom_replay_entity.h | 4 +- mp/src/game/server/momentum/mom_triggers.cpp | 68 +++++------ 9 files changed, 152 insertions(+), 141 deletions(-) diff --git a/mp/src/game/client/momentum/mom_event_listener.cpp b/mp/src/game/client/momentum/mom_event_listener.cpp index 809e72fdb7..622d0835b9 100644 --- a/mp/src/game/client/momentum/mom_event_listener.cpp +++ b/mp/src/game/client/momentum/mom_event_listener.cpp @@ -70,8 +70,15 @@ void C_Momentum_EventListener::FireGameEvent(IGameEvent *pEvent) { int currentStage = pEvent->GetInt("stage_num"); //Set the stage enter speed upon exiting the trigger - stats.m_flStageEnterSpeed[currentStage][0] = pEvent->GetFloat("stage_enter_vel"); - stats.m_flStageEnterSpeed[currentStage][1] = pEvent->GetFloat("stage_enter_vel_2D"); + float enterVel = pEvent->GetFloat("stage_enter_vel"); + float enterVel2D = pEvent->GetFloat("stage_enter_vel_2D"); + for (int i = 0; i < 2; i++) + { + float vel = i == 0 ? enterVel : enterVel2D; + stats.m_flStageEnterSpeed[currentStage][i] = vel; + if (currentStage == 1) + stats.m_flStageEnterSpeed[currentStage - 1][i] = vel;//Set overall enter vel + } } else if (!Q_strcmp("run_save", pEvent->GetName())) { diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index d469b448f1..9dfbf25538 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -105,41 +105,41 @@ void CTimer::LoadLocalTimes(const char *szMapname) if (!Q_strnicmp(subKv->GetName(), "stage", strlen("stage"))) { int i = Q_atoi(subKv->GetName() + 6); //atoi will need to ignore "stage " and only return the stage number - t.RunStats->m_iStageJumps[i] = subKv->GetInt("num_jumps"); - t.RunStats->m_iStageStrafes[i] = subKv->GetInt("num_strafes"); - t.RunStats->m_flStageTime[i] = subKv->GetFloat("time"); - t.RunStats->m_flStageEnterTime[i] = subKv->GetFloat("enter_time"); - t.RunStats->m_flStageStrafeSyncAvg[i] = subKv->GetFloat("avg_sync"); - t.RunStats->m_flStageStrafeSync2Avg[i] = subKv->GetFloat("avg_sync2"); + t.RunStats.m_iStageJumps[i] = subKv->GetInt("num_jumps"); + t.RunStats.m_iStageStrafes[i] = subKv->GetInt("num_strafes"); + t.RunStats.m_flStageTime[i] = subKv->GetFloat("time"); + t.RunStats.m_flStageEnterTime[i] = subKv->GetFloat("enter_time"); + t.RunStats.m_flStageStrafeSyncAvg[i] = subKv->GetFloat("avg_sync"); + t.RunStats.m_flStageStrafeSync2Avg[i] = subKv->GetFloat("avg_sync2"); //3D Velocity Stats - t.RunStats->m_flStageVelocityAvg[i][0] = subKv->GetFloat("avg_vel"); - t.RunStats->m_flStageVelocityMax[i][0] = subKv->GetFloat("max_vel"); - t.RunStats->m_flStageEnterSpeed[i][0] = subKv->GetFloat("stage_enter_vel"); - t.RunStats->m_flStageExitSpeed[i][0] = subKv->GetFloat("stage_exit_vel"); + t.RunStats.m_flStageVelocityAvg[i][0] = subKv->GetFloat("avg_vel"); + t.RunStats.m_flStageVelocityMax[i][0] = subKv->GetFloat("max_vel"); + t.RunStats.m_flStageEnterSpeed[i][0] = subKv->GetFloat("stage_enter_vel"); + t.RunStats.m_flStageExitSpeed[i][0] = subKv->GetFloat("stage_exit_vel"); //2D Velocity Stats - t.RunStats->m_flStageVelocityAvg[i][1] = subKv->GetFloat("avg_vel_2D"); - t.RunStats->m_flStageVelocityMax[i][1] = subKv->GetFloat("max_vel_2D"); - t.RunStats->m_flStageEnterSpeed[i][1] = subKv->GetFloat("stage_enter_vel_2D"); - t.RunStats->m_flStageExitSpeed[i][1] = subKv->GetFloat("stage_exit_vel_2D"); + t.RunStats.m_flStageVelocityAvg[i][1] = subKv->GetFloat("avg_vel_2D"); + t.RunStats.m_flStageVelocityMax[i][1] = subKv->GetFloat("max_vel_2D"); + t.RunStats.m_flStageEnterSpeed[i][1] = subKv->GetFloat("stage_enter_vel_2D"); + t.RunStats.m_flStageExitSpeed[i][1] = subKv->GetFloat("stage_exit_vel_2D"); } if (!Q_strcmp(subKv->GetName(), "total")) { - t.RunStats->m_iStageJumps[0] = subKv->GetInt("jumps"); - t.RunStats->m_iStageStrafes[0] = subKv->GetInt("strafes"); - t.RunStats->m_flStageStrafeSyncAvg[0] = subKv->GetFloat("avgsync"); - t.RunStats->m_flStageStrafeSync2Avg[0] = subKv->GetFloat("avgsync2"); + t.RunStats.m_iStageJumps[0] = subKv->GetInt("jumps"); + t.RunStats.m_iStageStrafes[0] = subKv->GetInt("strafes"); + t.RunStats.m_flStageStrafeSyncAvg[0] = subKv->GetFloat("avgsync"); + t.RunStats.m_flStageStrafeSync2Avg[0] = subKv->GetFloat("avgsync2"); //3D - t.RunStats->m_flStageVelocityAvg[0][0] = subKv->GetFloat("avg_vel"); - t.RunStats->m_flStageVelocityMax[0][0] = subKv->GetFloat("max_vel"); - t.RunStats->m_flStageEnterSpeed[0][0] = subKv->GetFloat("start_vel"); - t.RunStats->m_flStageExitSpeed[0][0] = subKv->GetFloat("end_vel"); + t.RunStats.m_flStageVelocityAvg[0][0] = subKv->GetFloat("avg_vel"); + t.RunStats.m_flStageVelocityMax[0][0] = subKv->GetFloat("max_vel"); + t.RunStats.m_flStageEnterSpeed[0][0] = subKv->GetFloat("start_vel"); + t.RunStats.m_flStageExitSpeed[0][0] = subKv->GetFloat("end_vel"); //2D - t.RunStats->m_flStageVelocityAvg[0][1] = subKv->GetFloat("avg_vel_2D"); - t.RunStats->m_flStageVelocityMax[0][1] = subKv->GetFloat("max_vel_2D"); - t.RunStats->m_flStageEnterSpeed[0][1] = subKv->GetFloat("start_vel_2D"); - t.RunStats->m_flStageExitSpeed[0][1] = subKv->GetFloat("end_vel_2D"); + t.RunStats.m_flStageVelocityAvg[0][1] = subKv->GetFloat("avg_vel_2D"); + t.RunStats.m_flStageVelocityMax[0][1] = subKv->GetFloat("max_vel_2D"); + t.RunStats.m_flStageEnterSpeed[0][1] = subKv->GetFloat("start_vel_2D"); + t.RunStats.m_flStageExitSpeed[0][1] = subKv->GetFloat("end_vel_2D"); } } localTimes.AddToTail(t); @@ -172,20 +172,20 @@ void CTimer::SaveTime() pSubkey->SetInt("flags", t.flags); KeyValues *pOverallKey = new KeyValues("total"); - pOverallKey->SetInt("jumps", t.RunStats->m_iStageJumps[0]); - pOverallKey->SetInt("strafes", t.RunStats->m_iStageStrafes[0]); - pOverallKey->SetFloat("avgsync", t.RunStats->m_flStageStrafeSyncAvg[0]); - pOverallKey->SetFloat("avgsync2", t.RunStats->m_flStageStrafeSync2Avg[0]); + pOverallKey->SetInt("jumps", t.RunStats.m_iStageJumps[0]); + pOverallKey->SetInt("strafes", t.RunStats.m_iStageStrafes[0]); + pOverallKey->SetFloat("avgsync", t.RunStats.m_flStageStrafeSyncAvg[0]); + pOverallKey->SetFloat("avgsync2", t.RunStats.m_flStageStrafeSync2Avg[0]); - pOverallKey->SetFloat("start_vel", t.RunStats->m_flStageEnterSpeed[0][0]); - pOverallKey->SetFloat("end_vel", t.RunStats->m_flStageExitSpeed[0][0]); - pOverallKey->SetFloat("avg_vel", t.RunStats->m_flStageVelocityAvg[0][0]); - pOverallKey->SetFloat("max_vel", t.RunStats->m_flStageVelocityMax[0][0]); + pOverallKey->SetFloat("start_vel", t.RunStats.m_flStageEnterSpeed[0][0]); + pOverallKey->SetFloat("end_vel", t.RunStats.m_flStageExitSpeed[0][0]); + pOverallKey->SetFloat("avg_vel", t.RunStats.m_flStageVelocityAvg[0][0]); + pOverallKey->SetFloat("max_vel", t.RunStats.m_flStageVelocityMax[0][0]); - pOverallKey->SetFloat("start_vel_2D", t.RunStats->m_flStageEnterSpeed[0][1]); - pOverallKey->SetFloat("end_vel_2D", t.RunStats->m_flStageExitSpeed[0][1]); - pOverallKey->SetFloat("avg_vel_2D", t.RunStats->m_flStageVelocityAvg[0][1]); - pOverallKey->SetFloat("max_vel_2D", t.RunStats->m_flStageVelocityMax[0][1]); + pOverallKey->SetFloat("start_vel_2D", t.RunStats.m_flStageEnterSpeed[0][1]); + pOverallKey->SetFloat("end_vel_2D", t.RunStats.m_flStageExitSpeed[0][1]); + pOverallKey->SetFloat("avg_vel_2D", t.RunStats.m_flStageVelocityAvg[0][1]); + pOverallKey->SetFloat("max_vel_2D", t.RunStats.m_flStageVelocityMax[0][1]); char stageName[9]; // "stage 64\0" if (GetStageCount() > 1) @@ -195,22 +195,22 @@ void CTimer::SaveTime() Q_snprintf(stageName, sizeof(stageName), "stage %d", i2); KeyValues *pStageKey = new KeyValues(stageName); - pStageKey->SetFloat("time", t.RunStats->m_flStageTime[i2]); - pStageKey->SetFloat("enter_time", t.RunStats->m_flStageEnterTime[i2]); - pStageKey->SetInt("num_jumps", t.RunStats->m_iStageJumps[i2]); - pStageKey->SetInt("num_strafes", t.RunStats->m_iStageStrafes[i2]); - pStageKey->SetFloat("avg_sync", t.RunStats->m_flStageStrafeSyncAvg[i2]); - pStageKey->SetFloat("avg_sync2", t.RunStats->m_flStageStrafeSync2Avg[i2]); - - pStageKey->SetFloat("avg_vel", t.RunStats->m_flStageVelocityAvg[i2][0]); - pStageKey->SetFloat("max_vel", t.RunStats->m_flStageVelocityMax[i2][0]); - pStageKey->SetFloat("stage_enter_vel", t.RunStats->m_flStageEnterSpeed[i2][0]); - pStageKey->SetFloat("stage_exit_vel", t.RunStats->m_flStageExitSpeed[i2][0]); - - pStageKey->SetFloat("avg_vel_2D", t.RunStats->m_flStageVelocityAvg[i2][1]); - pStageKey->SetFloat("max_vel_2D", t.RunStats->m_flStageVelocityMax[i2][1]); - pStageKey->SetFloat("stage_enter_vel_2D", t.RunStats->m_flStageEnterSpeed[i2][1]); - pStageKey->SetFloat("stage_exit_vel_2D", t.RunStats->m_flStageExitSpeed[i2][1]); + pStageKey->SetFloat("time", t.RunStats.m_flStageTime[i2]); + pStageKey->SetFloat("enter_time", t.RunStats.m_flStageEnterTime[i2]); + pStageKey->SetInt("num_jumps", t.RunStats.m_iStageJumps[i2]); + pStageKey->SetInt("num_strafes", t.RunStats.m_iStageStrafes[i2]); + pStageKey->SetFloat("avg_sync", t.RunStats.m_flStageStrafeSyncAvg[i2]); + pStageKey->SetFloat("avg_sync2", t.RunStats.m_flStageStrafeSync2Avg[i2]); + + pStageKey->SetFloat("avg_vel", t.RunStats.m_flStageVelocityAvg[i2][0]); + pStageKey->SetFloat("max_vel", t.RunStats.m_flStageVelocityMax[i2][0]); + pStageKey->SetFloat("stage_enter_vel", t.RunStats.m_flStageEnterSpeed[i2][0]); + pStageKey->SetFloat("stage_exit_vel", t.RunStats.m_flStageExitSpeed[i2][0]); + + pStageKey->SetFloat("avg_vel_2D", t.RunStats.m_flStageVelocityAvg[i2][1]); + pStageKey->SetFloat("max_vel_2D", t.RunStats.m_flStageVelocityMax[i2][1]); + pStageKey->SetFloat("stage_enter_vel_2D", t.RunStats.m_flStageEnterSpeed[i2][1]); + pStageKey->SetFloat("stage_exit_vel_2D", t.RunStats.m_flStageExitSpeed[i2][1]); pSubkey->AddSubKey(pStageKey); } diff --git a/mp/src/game/server/momentum/Timer.h b/mp/src/game/server/momentum/Timer.h index cad94997b4..d4644a9834 100644 --- a/mp/src/game/server/momentum/Timer.h +++ b/mp/src/game/server/momentum/Timer.h @@ -177,7 +177,7 @@ class CTimer int flags; //stage specific stats: - RunStats_t *RunStats = new RunStats_t(); + RunStats_t RunStats = RunStats_t(); }; struct Checkpoint diff --git a/mp/src/game/server/momentum/mom_player.cpp b/mp/src/game/server/momentum/mom_player.cpp index b7853f36b5..0da86b9d6a 100644 --- a/mp/src/game/server/momentum/mom_player.cpp +++ b/mp/src/game/server/momentum/mom_player.cpp @@ -48,7 +48,7 @@ CMomentumPlayer::CMomentumPlayer() CMomentumPlayer::~CMomentumPlayer() { - delete m_PlayerRunStats; + } void CMomentumPlayer::Precache() @@ -89,7 +89,7 @@ void CMomentumPlayer::Spawn() m_bIsInZone = false; m_bMapFinished = false; m_iCurrentStage = 0; - + ResetRunStats(); if (runSaveEvent) { runSaveEvent->SetBool("run_saved", false); @@ -247,8 +247,8 @@ void CMomentumPlayer::CheckForBhop() if (g_Timer->IsRunning()) { int currentStage = g_Timer->GetCurrentStageNumber(); - m_PlayerRunStats->m_iStageJumps[0]++; - m_PlayerRunStats->m_iStageJumps[currentStage]++; + m_PlayerRunStats.m_iStageJumps[0]++; + m_PlayerRunStats.m_iStageJumps[currentStage]++; } } } @@ -271,40 +271,40 @@ void CMomentumPlayer::UpdateRunStats() if (!m_bPrevTimerRunning) //timer started on this tick { //Reset old run stats -- moved to on start's touch - m_PlayerRunStats->m_flStageEnterSpeed[0][0] = velocity; - m_PlayerRunStats->m_flStageEnterSpeed[0][1] = velocity2D; + m_PlayerRunStats.m_flStageEnterSpeed[0][0] = velocity; + m_PlayerRunStats.m_flStageEnterSpeed[0][1] = velocity2D; //Compare against successive bhops to avoid incrimenting when the player was in the air without jumping (for surf) if (GetGroundEntity() == NULL && m_iSuccessiveBhops) { - m_PlayerRunStats->m_iStageJumps[0]++; - m_PlayerRunStats->m_iStageJumps[currentStage]++; + m_PlayerRunStats.m_iStageJumps[0]++; + m_PlayerRunStats.m_iStageJumps[currentStage]++; } if (m_nButtons & IN_MOVERIGHT || m_nButtons & IN_MOVELEFT) { - m_PlayerRunStats->m_iStageStrafes[0]++; - m_PlayerRunStats->m_iStageStrafes[currentStage]++; + m_PlayerRunStats.m_iStageStrafes[0]++; + m_PlayerRunStats.m_iStageStrafes[currentStage]++; } } if (m_nButtons & IN_MOVELEFT && !(m_nPrevButtons & IN_MOVELEFT)) { - m_PlayerRunStats->m_iStageStrafes[0]++; - m_PlayerRunStats->m_iStageStrafes[currentStage]++; + m_PlayerRunStats.m_iStageStrafes[0]++; + m_PlayerRunStats.m_iStageStrafes[currentStage]++; } else if (m_nButtons & IN_MOVERIGHT && !(m_nPrevButtons & IN_MOVERIGHT)) { - m_PlayerRunStats->m_iStageStrafes[0]++; - m_PlayerRunStats->m_iStageStrafes[currentStage]++; + m_PlayerRunStats.m_iStageStrafes[0]++; + m_PlayerRunStats.m_iStageStrafes[currentStage]++; } // ---- MAX VELOCITY ---- - if (velocity > m_PlayerRunStats->m_flStageVelocityMax[0][0]) - m_PlayerRunStats->m_flStageVelocityMax[0][0] = velocity; - if (velocity2D > m_PlayerRunStats->m_flStageVelocityMax[0][1]) - m_PlayerRunStats->m_flStageVelocityMax[0][1] = velocity; + if (velocity > m_PlayerRunStats.m_flStageVelocityMax[0][0]) + m_PlayerRunStats.m_flStageVelocityMax[0][0] = velocity; + if (velocity2D > m_PlayerRunStats.m_flStageVelocityMax[0][1]) + m_PlayerRunStats.m_flStageVelocityMax[0][1] = velocity; //also do max velocity per stage - if (velocity >m_PlayerRunStats->m_flStageVelocityMax[currentStage][0]) - m_PlayerRunStats->m_flStageVelocityMax[currentStage][0] = velocity; - if (velocity2D > m_PlayerRunStats->m_flStageVelocityMax[currentStage][1]) - m_PlayerRunStats->m_flStageVelocityMax[currentStage][1] = velocity2D; + if (velocity >m_PlayerRunStats.m_flStageVelocityMax[currentStage][0]) + m_PlayerRunStats.m_flStageVelocityMax[currentStage][0] = velocity; + if (velocity2D > m_PlayerRunStats.m_flStageVelocityMax[currentStage][1]) + m_PlayerRunStats.m_flStageVelocityMax[currentStage][1] = velocity2D; // ---------- // --- STAGE ENTER VELOCITY --- @@ -332,8 +332,8 @@ void CMomentumPlayer::UpdateRunStats() } if (m_nStrafeTicks && m_nAccelTicks && m_nPerfectSyncTicks) { - m_flStrafeSync = ((float)m_nPerfectSyncTicks / (float)m_nStrafeTicks) * 100; // ticks strafing perfectly / ticks strafing - m_flStrafeSync2 = ((float)m_nAccelTicks / (float)m_nStrafeTicks) * 100; // ticks gaining speed / ticks strafing + m_flStrafeSync = (float(m_nPerfectSyncTicks) / float(m_nStrafeTicks)) * 100; // ticks strafing perfectly / ticks strafing + m_flStrafeSync2 = (float(m_nAccelTicks) / float(m_nStrafeTicks)) * 100; // ticks gaining speed / ticks strafing } // ---------- @@ -348,8 +348,8 @@ void CMomentumPlayer::UpdateRunStats() if (playerMoveEvent) { - playerMoveEvent->SetInt("num_strafes", m_PlayerRunStats->m_iStageStrafes[0]); - playerMoveEvent->SetInt("num_jumps", m_PlayerRunStats->m_iStageJumps[0]); + playerMoveEvent->SetInt("num_strafes", m_PlayerRunStats.m_iStageStrafes[0]); + playerMoveEvent->SetInt("num_jumps", m_PlayerRunStats.m_iStageJumps[0]); bool onGround = GetFlags() & FL_ONGROUND; if ((m_nButtons & IN_JUMP) && onGround || m_nButtons & (IN_MOVELEFT | IN_MOVERIGHT)) gameeventmanager->FireEvent(playerMoveEvent); @@ -366,8 +366,7 @@ void CMomentumPlayer::ResetRunStats() m_flStrafeSync = 0; m_flStrafeSync2 = 0; - delete m_PlayerRunStats; - m_PlayerRunStats = new RunStats_t(); + m_PlayerRunStats = RunStats_t(); } void CMomentumPlayer::CalculateAverageStats() { @@ -383,10 +382,10 @@ void CMomentumPlayer::CalculateAverageStats() m_nStageAvgCount[currentStage]++; - m_PlayerRunStats->m_flStageStrafeSyncAvg[currentStage] = m_flStageTotalSync[currentStage] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats->m_flStageStrafeSync2Avg[currentStage] = m_flStageTotalSync2[currentStage] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats->m_flStageVelocityAvg[currentStage][0] = m_flStageTotalVelocity[currentStage][0] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats->m_flStageVelocityAvg[currentStage][1] = m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageStrafeSyncAvg[currentStage] = m_flStageTotalSync[currentStage] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageStrafeSync2Avg[currentStage] = m_flStageTotalSync2[currentStage] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageVelocityAvg[currentStage][0] = m_flStageTotalVelocity[currentStage][0] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageVelocityAvg[currentStage][1] = m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); //stage 0 is "overall" - also update these as well, no matter which stage we are on m_flStageTotalSync[0] += m_flStrafeSync; @@ -395,10 +394,10 @@ void CMomentumPlayer::CalculateAverageStats() m_flStageTotalVelocity[0][1] += GetLocalVelocity().Length2D(); m_nStageAvgCount[0]++; - m_PlayerRunStats->m_flStageStrafeSyncAvg[0] = m_flStageTotalSync[currentStage] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats->m_flStageStrafeSync2Avg[0] = m_flStageTotalSync2[currentStage] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats->m_flStageVelocityAvg[0][0] = m_flStageTotalVelocity[currentStage][0] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats->m_flStageVelocityAvg[0][1] = m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageStrafeSyncAvg[0] = m_flStageTotalSync[currentStage] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageStrafeSync2Avg[0] = m_flStageTotalSync2[currentStage] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageVelocityAvg[0][0] = m_flStageTotalVelocity[currentStage][0] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageVelocityAvg[0][1] = m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); } // think once per 0.1 second interval so we avoid making the totals extremely large diff --git a/mp/src/game/server/momentum/mom_player.h b/mp/src/game/server/momentum/mom_player.h index 76c30916ce..c0ba984835 100644 --- a/mp/src/game/server/momentum/mom_player.h +++ b/mp/src/game/server/momentum/mom_player.h @@ -20,22 +20,23 @@ class CMomentumPlayer : public CBasePlayer static CMomentumPlayer *CreatePlayer(const char *className, edict_t *ed) { - CMomentumPlayer::s_PlayerEdict = ed; - return (CMomentumPlayer *)CreateEntityByName(className); + s_PlayerEdict = ed; + return static_cast(CreateEntityByName(className)); } DECLARE_SERVERCLASS(); DECLARE_DATADESC(); - int FlashlightIsOn() { return IsEffectActive(EF_DIMLIGHT); } + int FlashlightIsOn() override + { return IsEffectActive(EF_DIMLIGHT); } - void FlashlightTurnOn() + void FlashlightTurnOn() override { AddEffects(EF_DIMLIGHT); EmitSound("HL2Player.FlashLightOn"); // MOM_TODO: change this? } - void FlashlightTurnOff() + void FlashlightTurnOff() override { RemoveEffects(EF_DIMLIGHT); EmitSound("HL2Player.FlashLightOff"); // MOM_TODO: change this? @@ -47,10 +48,14 @@ class CMomentumPlayer : public CBasePlayer void InitHUD() override; - virtual void CommitSuicide(bool bExplode = false, bool bForce = false){}; - virtual void CommitSuicide(const Vector &vecForce, bool bExplode = false, bool bForce = false){}; + void CommitSuicide(bool bExplode = false, bool bForce = false) override + {}; - bool CanBreatheUnderwater() const { return true; } + void CommitSuicide(const Vector &vecForce, bool bExplode = false, bool bForce = false) override + {}; + + bool CanBreatheUnderwater() const override + { return true; } // LADDERS void SurpressLadderChecks(const Vector &pos, const Vector &normal); @@ -113,7 +118,7 @@ class CMomentumPlayer : public CBasePlayer float GetPunishTime() { return m_flPunishTime; } //Run Stats - RunStats_t *m_PlayerRunStats = new RunStats_t(); + RunStats_t m_PlayerRunStats; //for calc avg int m_nStageAvgCount[MAX_STAGES]; diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 62057de027..d8d8e5a75d 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -84,7 +84,7 @@ replay_header_t CMomentumReplaySystem::CreateHeader() header.runTime = g_Timer->GetLastRunTime(); time(&header.unixEpocDate); - //header.stats = m_player->m_PlayerRunStats; //copy ALL run stats using operator overload + header.stats = m_player->m_PlayerRunStats; //copy ALL run stats using operator overload return header; } void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer &buf) diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 76cf44f58c..93f8ce5764 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -213,10 +213,10 @@ void CMomentumReplayGhostEntity::UpdateStats(Vector ghostVel, CMomentumPlayer *p } // --- JUMP AND STRAFE COUNTER --- if (GetGroundEntity() != NULL && currentStep.m_nPlayerButtons & IN_JUMP) - pPlayer->m_PlayerRunStats->m_iStageJumps[0]++; + pPlayer->m_PlayerRunStats.m_iStageJumps[0]++; if ((currentStep.m_nPlayerButtons & IN_MOVELEFT && !(m_nOldReplayButtons & IN_MOVELEFT)) || (currentStep.m_nPlayerButtons & IN_MOVERIGHT && !(m_nOldReplayButtons & IN_MOVERIGHT)) ) - pPlayer->m_PlayerRunStats->m_iStageStrafes[0]++; + pPlayer->m_PlayerRunStats.m_iStageStrafes[0]++; m_flLastSyncVelocity = SyncVelocity; m_qLastEyeAngle = EyeAngles(); diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index d29ec9a100..a7ecd4ebf0 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -25,9 +25,9 @@ enum ghostModelBodyGroup BODY_CYLINDER }; -class CMomentumReplayGhostEntity : public CBaseCombatCharacter +class CMomentumReplayGhostEntity : public CBaseAnimating { - DECLARE_CLASS(CMomentumReplayGhostEntity, CBaseCombatCharacter); + DECLARE_CLASS(CMomentumReplayGhostEntity, CBaseAnimating); DECLARE_DATADESC(); public: ~CMomentumReplayGhostEntity(){ g_ReplaySystem->m_bIsWatchingReplay = false;} diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index d8b5207356..f911948509 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -43,22 +43,22 @@ void CTriggerStage::StartTouch(CBaseEntity *pOther) { stageEvent->SetInt("stage_num", stageNum); stageEvent->SetFloat("stage_enter_time", g_Timer->CalculateStageTime(stageNum)); - stageEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats->m_iStageJumps[stageNum - 1]); - stageEvent->SetFloat("num_strafes", pPlayer->m_PlayerRunStats->m_iStageStrafes[stageNum - 1]); - stageEvent->SetFloat("avg_sync", pPlayer->m_PlayerRunStats->m_flStageStrafeSyncAvg[stageNum - 1]); - stageEvent->SetFloat("avg_sync2", pPlayer->m_PlayerRunStats->m_flStageStrafeSync2Avg[stageNum - 1]); + stageEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats.m_iStageJumps[stageNum - 1]); + stageEvent->SetFloat("num_strafes", pPlayer->m_PlayerRunStats.m_iStageStrafes[stageNum - 1]); + stageEvent->SetFloat("avg_sync", pPlayer->m_PlayerRunStats.m_flStageStrafeSyncAvg[stageNum - 1]); + stageEvent->SetFloat("avg_sync2", pPlayer->m_PlayerRunStats.m_flStageStrafeSync2Avg[stageNum - 1]); //3D VELOCITY - stageEvent->SetFloat("max_vel", pPlayer->m_PlayerRunStats->m_flStageVelocityMax[stageNum - 1][0]); - stageEvent->SetFloat("avg_vel", pPlayer->m_PlayerRunStats->m_flStageVelocityAvg[stageNum - 1][0]); - pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum - 1][0] = pPlayer->GetLocalVelocity().Length(); - stageEvent->SetFloat("stage_exit_vel", pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum - 1][0]); + stageEvent->SetFloat("max_vel", pPlayer->m_PlayerRunStats.m_flStageVelocityMax[stageNum - 1][0]); + stageEvent->SetFloat("avg_vel", pPlayer->m_PlayerRunStats.m_flStageVelocityAvg[stageNum - 1][0]); + pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum - 1][0] = pPlayer->GetLocalVelocity().Length(); + stageEvent->SetFloat("stage_exit_vel", pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum - 1][0]); //2D VELOCITY - stageEvent->SetFloat("max_vel_2D", pPlayer->m_PlayerRunStats->m_flStageVelocityMax[stageNum - 1][1]); - stageEvent->SetFloat("avg_vel_2D", pPlayer->m_PlayerRunStats->m_flStageVelocityAvg[stageNum - 1][1]); - pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum - 1][1] = pPlayer->GetLocalVelocity().Length2D(); - stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum - 1][1]); + stageEvent->SetFloat("max_vel_2D", pPlayer->m_PlayerRunStats.m_flStageVelocityMax[stageNum - 1][1]); + stageEvent->SetFloat("avg_vel_2D", pPlayer->m_PlayerRunStats.m_flStageVelocityAvg[stageNum - 1][1]); + pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum - 1][1] = pPlayer->GetLocalVelocity().Length2D(); + stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum - 1][1]); gameeventmanager->FireEvent(stageEvent); } @@ -87,12 +87,12 @@ void CTriggerStage::EndTouch(CBaseEntity *pOther) stageEvent->SetInt("stage_num", stageNum); //3D VELOCITY - pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); - stageEvent->SetFloat("stage_enter_vel", pPlayer->m_PlayerRunStats->m_flStageEnterSpeed[stageNum][0]); + pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); + stageEvent->SetFloat("stage_enter_vel", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][0]); //2D VELOCITY - pPlayer->m_PlayerRunStats->m_flStageEnterSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); - stageEvent->SetFloat("stage_enter_vel_2D", pPlayer->m_PlayerRunStats->m_flStageEnterSpeed[stageNum][1]); + pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); + stageEvent->SetFloat("stage_enter_vel_2D", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][1]); gameeventmanager->FireEvent(stageEvent); } @@ -246,32 +246,32 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) //send run stats via GameEventManager if (timerStopEvent) { - timerStopEvent->SetFloat("avg_sync", pPlayer->m_PlayerRunStats->m_flStageStrafeSyncAvg[0]); - timerStopEvent->SetFloat("avg_sync2", pPlayer->m_PlayerRunStats->m_flStageStrafeSync2Avg[0]); - timerStopEvent->SetInt("num_strafes", pPlayer->m_PlayerRunStats->m_iStageStrafes[0]); - timerStopEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats->m_iStageJumps[0]); + timerStopEvent->SetFloat("avg_sync", pPlayer->m_PlayerRunStats.m_flStageStrafeSyncAvg[0]); + timerStopEvent->SetFloat("avg_sync2", pPlayer->m_PlayerRunStats.m_flStageStrafeSync2Avg[0]); + timerStopEvent->SetInt("num_strafes", pPlayer->m_PlayerRunStats.m_iStageStrafes[0]); + timerStopEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats.m_iStageJumps[0]); //3D VELCOCITY STATS - INDEX 0 - timerStopEvent->SetFloat("avg_vel", pPlayer->m_PlayerRunStats->m_flStageVelocityAvg[0][0]); - timerStopEvent->SetFloat("start_vel", pPlayer->m_PlayerRunStats->m_flStageEnterSpeed[0][0]); + timerStopEvent->SetFloat("avg_vel", pPlayer->m_PlayerRunStats.m_flStageVelocityAvg[0][0]); + timerStopEvent->SetFloat("start_vel", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[0][0]); float endvel = pPlayer->GetLocalVelocity().Length(); timerStopEvent->SetFloat("end_vel", endvel); - if (endvel > pPlayer->m_PlayerRunStats->m_flStageVelocityMax[0][0]) + if (endvel > pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][0]) timerStopEvent->SetFloat("max_vel", endvel); else - timerStopEvent->SetFloat("max_vel", pPlayer->m_PlayerRunStats->m_flStageVelocityMax[0][0]); - pPlayer->m_PlayerRunStats->m_flStageExitSpeed[0][0] = endvel; //we have to set end speed here or else it will be saved as 0 + timerStopEvent->SetFloat("max_vel", pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][0]); + pPlayer->m_PlayerRunStats.m_flStageExitSpeed[0][0] = endvel; //we have to set end speed here or else it will be saved as 0 //2D VELOCITY STATS - INDEX 1 - timerStopEvent->SetFloat("avg_vel_2D", pPlayer->m_PlayerRunStats->m_flStageVelocityAvg[0][1]); - timerStopEvent->SetFloat("start_vel_2D", pPlayer->m_PlayerRunStats->m_flStageEnterSpeed[0][1]); + timerStopEvent->SetFloat("avg_vel_2D", pPlayer->m_PlayerRunStats.m_flStageVelocityAvg[0][1]); + timerStopEvent->SetFloat("start_vel_2D", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[0][1]); float endvel2D = pPlayer->GetLocalVelocity().Length2D(); timerStopEvent->SetFloat("end_vel_2D", endvel2D); - if (endvel2D > pPlayer->m_PlayerRunStats->m_flStageVelocityMax[0][1]) + if (endvel2D > pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][1]) timerStopEvent->SetFloat("max_vel_2D", endvel2D); else - timerStopEvent->SetFloat("max_vel_2D", pPlayer->m_PlayerRunStats->m_flStageVelocityMax[0][1]); - pPlayer->m_PlayerRunStats->m_flStageExitSpeed[0][1] = endvel2D; + timerStopEvent->SetFloat("max_vel_2D", pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][1]); + pPlayer->m_PlayerRunStats.m_flStageExitSpeed[0][1] = endvel2D; gameeventmanager->FireEvent(timerStopEvent); } @@ -287,10 +287,10 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) stageEvent->SetFloat("stage_enter_time", g_Timer->GetLastRunTime()); //This is needed so we have an ending velocity. - pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); - stageEvent->SetFloat("stage_exit_vel", pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum][0]); - pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); - stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_PlayerRunStats->m_flStageExitSpeed[stageNum][1]); + pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); + stageEvent->SetFloat("stage_exit_vel", pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum][0]); + pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); + stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum][1]); gameeventmanager->FireEvent(stageEvent); } From 1eed95ea4cacafa037d9b08c909fbc712a389ab2 Mon Sep 17 00:00:00 2001 From: Nick K Date: Wed, 18 May 2016 21:47:13 -0400 Subject: [PATCH 030/101] Rid mom_comparisons_vel_type Replaced with very identical hud_speedometer_hvel --- mp/src/game/client/momentum/ui/hud_comparisons.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mp/src/game/client/momentum/ui/hud_comparisons.cpp b/mp/src/game/client/momentum/ui/hud_comparisons.cpp index bfb15cad8c..0d4a034889 100644 --- a/mp/src/game/client/momentum/ui/hud_comparisons.cpp +++ b/mp/src/game/client/momentum/ui/hud_comparisons.cpp @@ -43,9 +43,6 @@ static MAKE_TOGGLE_CONVAR(mom_comparisons_time_show_perstage, "0", FLAG_HUD_CVAR // Velocity static MAKE_TOGGLE_CONVAR(mom_comparisons_vel_show, "1", FLAG_HUD_CVAR, "Toggle showing velocity comparisons: 0 = OFF, 1 = ON"); // Overall vis -static MAKE_TOGGLE_CONVAR(mom_comparisons_vel_type, "0", FLAG_HUD_CVAR, - "Velocity comparison type: \n0 = Velocity including Z-axis (3D)\n1 = Velocity without Z axis " - "(horizontal velocity)"); // Horizontal/3D static MAKE_TOGGLE_CONVAR(mom_comparisons_vel_show_avg, "1", FLAG_HUD_CVAR, "Toggle showing average velocity. 0 = OFF, 1 = ON"); // avg vel static MAKE_TOGGLE_CONVAR(mom_comparisons_vel_show_max, "1", FLAG_HUD_CVAR, @@ -259,7 +256,8 @@ void C_RunComparisons::GetDiffColor(float diff, Color *into, bool positiveIsGain void C_RunComparisons::GetComparisonString(ComparisonString_t type, int stage, char *ansiActualBufferOut, char *ansiCompareBufferOut, Color *compareColorOut) { - int velType = mom_comparisons_vel_type.GetInt(); // Type of velocity comparison we're making (3D vs Horizontal) + ConVarRef velTypeVar("mom_speedometer_hvel"); + int velType = velTypeVar.GetInt(); // Type of velocity comparison we're making (3D vs Horizontal) float diff = 0.0f; // Difference between the current and the compared-to. float act; // Actual value that the player has for this stage. char tempANSITimeOutput[BUFSIZETIME], From 708da56ea2c1af2900311054c59eccc60cf25617 Mon Sep 17 00:00:00 2001 From: Nick K Date: Wed, 18 May 2016 21:49:47 -0400 Subject: [PATCH 031/101] Fix bug with map end making other stuff colored --- mp/src/game/client/momentum/ui/hud_mapfinished.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp index 2a622601dc..52b87868b3 100644 --- a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp +++ b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp @@ -131,7 +131,7 @@ class CHudMapFinishedDialog : public CHudElement, public Panel bool m_bRunSaved, m_bRunUploaded; }; -DECLARE_HUDELEMENT(CHudMapFinishedDialog); +DECLARE_HUDELEMENT_DEPTH(CHudMapFinishedDialog, 70); CHudMapFinishedDialog::CHudMapFinishedDialog(const char *pElementName) : CHudElement(pElementName), Panel(g_pClientMode->GetViewport(), "CHudMapFinishedDialog") From e16f28281fd11c750f9fbfe3eb779bb19422a57c Mon Sep 17 00:00:00 2001 From: Nick K Date: Wed, 18 May 2016 23:11:19 -0400 Subject: [PATCH 032/101] Static buf no longer --- mp/src/game/server/momentum/mom_replay.cpp | 22 ++++++------ mp/src/game/server/momentum/mom_replay.h | 35 ++++++++++++------- .../server/momentum/mom_replay_entity.cpp | 28 ++++++++------- .../game/server/momentum/mom_replay_entity.h | 2 +- 4 files changed, 48 insertions(+), 39 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index d8d8e5a75d..d5c06ee81a 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -20,7 +20,7 @@ void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway, { if (throwaway) { m_bIsRecording = false; - m_buf->Purge(); + m_buf.Purge(); return; } if (delay) @@ -43,7 +43,7 @@ void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway, m_fhFileHandle = filesystem->Open(newRecordingPath, "w+b", "MOD"); - WriteRecordingToFile(*m_buf); + WriteRecordingToFile(&m_buf); filesystem->Close(m_fhFileHandle); Log("Recording Stopped! Ticks: %i\n", m_nCurrentTick); @@ -51,11 +51,10 @@ void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway, StartReplay(); } } -CUtlBuffer *CMomentumReplaySystem::UpdateRecordingParams() +void CMomentumReplaySystem::UpdateRecordingParams(CUtlBuffer *buf) { m_nCurrentTick++; //increment recording tick - static CUtlBuffer buf; m_currentFrame.m_nPlayerButtons = m_player->m_nButtons; m_currentFrame.m_qEyeAngles = m_player->EyeAngles(); m_currentFrame.m_vPlayerOrigin = m_player->GetAbsOrigin(); @@ -66,9 +65,8 @@ CUtlBuffer *CMomentumReplaySystem::UpdateRecordingParams() if (gpGlobals->curtime - m_fRecEndTime >= END_RECORDING_PAUSE) StopRecording(UTIL_GetLocalPlayer(), false, false); - Assert(buf.IsValid()); - buf.Put(&m_currentFrame, sizeof(replay_frame_t)); //stick all the frame info into the buffer - return &buf; + Assert(buf && buf->IsValid()); + buf->Put(&m_currentFrame, sizeof(replay_frame_t)); //stick all the frame info into the buffer } replay_header_t CMomentumReplaySystem::CreateHeader() { @@ -87,7 +85,7 @@ replay_header_t CMomentumReplaySystem::CreateHeader() header.stats = m_player->m_PlayerRunStats; //copy ALL run stats using operator overload return header; } -void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer &buf) +void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer *buf) { if (m_fhFileHandle) { @@ -99,10 +97,10 @@ void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer &buf) filesystem->Write(&littleEndianHeader, sizeof(replay_header_t), m_fhFileHandle); DevLog("\n\nreplay header size: %i\n", sizeof(replay_header_t)); - Assert(buf.IsValid()); + Assert(buf && buf->IsValid()); //write write from the CUtilBuffer to our filehandle: - filesystem->Write(buf.Base(), buf.TellPut(), m_fhFileHandle); - buf.Purge(); + filesystem->Write(buf->Base(), buf->TellPut(), m_fhFileHandle); + buf->Purge(); } } //read a single frame (or tick) of a recording @@ -143,7 +141,7 @@ bool CMomentumReplaySystem::LoadRun(const char* filename) V_ComposeFileName(RECORDING_PATH, filename, recordingName, MAX_PATH); m_fhFileHandle = filesystem->Open(recordingName, "r+b", "MOD"); - if (m_fhFileHandle != nullptr && filename != NULL) + if (m_fhFileHandle != nullptr && filename != nullptr) { replay_header_t* header = ReadHeader(m_fhFileHandle, filename); if (header == nullptr) { diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index 5be52bbc86..093293f002 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -1,5 +1,6 @@ #ifndef MOM_REPLAY_H #define MOM_REPLAY_H + #include "cbase.h" #include "filesystem.h" #include "utlbuffer.h" @@ -13,35 +14,43 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame { -public: - CMomentumReplaySystem(const char *pName) : CAutoGameSystemPerFrame(pName) {} - virtual void FrameUpdatePostEntityThink() //inherited member from CAutoGameSystemPerFrame + public: + CMomentumReplaySystem(const char *pName) + : CAutoGameSystemPerFrame(pName), m_bIsWatchingReplay(false), m_bIsRecording(false), m_bShouldStopRec(false), + m_nCurrentTick(0), m_fRecEndTime(0), m_player(nullptr), m_fhFileHandle(nullptr), m_buf() + { + } + + // inherited member from CAutoGameSystemPerFrame + void FrameUpdatePostEntityThink() override { if (m_bIsRecording) { - m_buf = UpdateRecordingParams(); + UpdateRecordingParams(&m_buf); } } + void BeginRecording(CBasePlayer *pPlayer); void StopRecording(CBasePlayer *pPlayer, bool throwaway, bool delay); - void WriteRecordingToFile(CUtlBuffer &buf); + void WriteRecordingToFile(CUtlBuffer *buf); replay_header_t CreateHeader(); - void WriteRecordingToFile(); - replay_frame_t* ReadSingleFrame(FileHandle_t file, const char* filename); - replay_header_t* ReadHeader(FileHandle_t file, const char* filename); + replay_frame_t *ReadSingleFrame(FileHandle_t file, const char *filename); + replay_header_t *ReadHeader(FileHandle_t file, const char *filename); void StartReplay(bool firstperson = false); - bool LoadRun(const char* fileName); + bool LoadRun(const char *fileName); CUtlVector m_vecRunData; + //MOM_TODO: Handle the pPlayer pointer passed here or get rid of it bool IsRecording(CBasePlayer *pPlayer) { return m_bIsRecording; } replay_header_t m_loadedHeader; bool m_bIsWatchingReplay; void DispatchTimerStateMessage(CBasePlayer *pPlayer, bool started); -private: - CUtlBuffer *UpdateRecordingParams(); //called every game frame after entities think and update + + private: + void UpdateRecordingParams(CUtlBuffer *); // called every game frame after entities think and update bool m_bIsRecording; bool m_bShouldStopRec; @@ -54,9 +63,9 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame replay_header_t m_replayHeader; FileHandle_t m_fhFileHandle; - CUtlBuffer *m_buf; + CUtlBuffer m_buf; }; extern CMomentumReplaySystem *g_ReplaySystem; -#endif //MOM_REPLAY_H \ No newline at end of file +#endif // MOM_REPLAY_H \ No newline at end of file diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 93f8ce5764..1e43cd862b 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -62,30 +62,33 @@ void CMomentumReplayGhostEntity::StartRun(bool firstPerson) SetNextThink(gpGlobals->curtime); } -void CMomentumReplayGhostEntity::updateStep() +void CMomentumReplayGhostEntity::UpdateStep() { currentStep = g_ReplaySystem->m_vecRunData[step]; - + ++step; if (mom_replay_reverse.GetBool()) { nextStep = g_ReplaySystem->m_vecRunData[--step]; } - else + else if (step < g_ReplaySystem->m_vecRunData.Size()) { - nextStep = g_ReplaySystem->m_vecRunData[++step]; + nextStep = g_ReplaySystem->m_vecRunData[step]; } } void CMomentumReplayGhostEntity::Think(void) { BaseClass::Think(); - if (step < g_ReplaySystem->m_vecRunData.Count() && step >= 1) - { - updateStep(); - mom_replay_firstperson.GetBool() ? HandleGhostFirstPerson() : HandleGhost(); - } - else + if (step >= 1) { - EndRun(); + if (step < g_ReplaySystem->m_vecRunData.Size()) + { + UpdateStep(); + mom_replay_firstperson.GetBool() ? HandleGhostFirstPerson() : HandleGhost(); + } + else + { + EndRun(); + } } //update color, bodygroup, and other params if they change @@ -161,7 +164,6 @@ void CMomentumReplayGhostEntity::HandleGhostFirstPerson() //MOM_TODO: make this smoother. possibly inherit from NPC classes/CBaseCombatCharacter pPlayer->SetViewOffset(VEC_DUCK_VIEW); } - } } void CMomentumReplayGhostEntity::HandleGhost() @@ -247,7 +249,7 @@ void CMomentumReplayGhostEntity::SetGhostBodyGroup(int bodyGroup) void CMomentumReplayGhostEntity::SetGhostColor(const CCommand &args) { if (mom_UTIL->GetColorFromHex(args.ArgS())) { - CMomentumReplayGhostEntity::m_newGhostColor = *mom_UTIL->GetColorFromHex(args.ArgS()); + m_newGhostColor = *mom_UTIL->GetColorFromHex(args.ArgS()); } } void CMomentumReplayGhostEntity::EndRun() diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index a7ecd4ebf0..88f5c03c2e 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -36,7 +36,7 @@ class CMomentumReplayGhostEntity : public CBaseAnimating void SetGhostBodyGroup(int bodyGroup); static void SetGhostColor(const CCommand &args); //Increments the steps intelligently. - void updateStep(); + void UpdateStep(); void EndRun(); void StartRun(bool firstPerson = false); From 972a48b3472d0fd2618b6f6a8cf2546ca08e3d98 Mon Sep 17 00:00:00 2001 From: tuxxi Date: Wed, 18 May 2016 21:52:38 -0700 Subject: [PATCH 033/101] added mom_replay_loop and stop_replay commands, fixed mom_replay_reverse --- mp/src/game/server/momentum/mom_replay.cpp | 18 ++++++++--- mp/src/game/server/momentum/mom_replay.h | 6 ++-- .../server/momentum/mom_replay_entity.cpp | 32 ++++++++++++------- .../game/server/momentum/mom_replay_entity.h | 13 ++++++-- 4 files changed, 48 insertions(+), 21 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index d5c06ee81a..7906bdabe8 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -164,11 +164,18 @@ bool CMomentumReplaySystem::LoadRun(const char* filename) } void CMomentumReplaySystem::StartReplay(bool firstperson) { - CMomentumReplayGhostEntity *ghost = static_cast(CreateEntityByName("mom_replay_ghost")); - if (ghost != nullptr) + m_CurrentReplayGhost = static_cast(CreateEntityByName("mom_replay_ghost")); + if (m_CurrentReplayGhost != nullptr) { g_Timer->Stop(false); //stop the timer just in case we started a replay while it was running... - ghost->StartRun(firstperson); + m_CurrentReplayGhost->StartRun(firstperson); + } +} +void CMomentumReplaySystem::EndReplay() +{ + if (m_CurrentReplayGhost != nullptr) + { + m_CurrentReplayGhost->EndRun(); } } void CMomentumReplaySystem::DispatchTimerStateMessage(CBasePlayer *pPlayer, bool started) @@ -224,6 +231,9 @@ class CMOMReplayCommands CON_COMMAND_AUTOCOMPLETEFILE(playreplay_ghost, CMOMReplayCommands::PlayReplayGhost, "begins playback of a replay ghost", "recordings", momrec); CON_COMMAND_AUTOCOMPLETEFILE(playreplay, CMOMReplayCommands::PlayReplayFirstPerson, "plays back a replay in first-person", "recordings", momrec); - +CON_COMMAND(stop_replay, "Stops playing the current replay") +{ + g_ReplaySystem->EndReplay(); +} static CMomentumReplaySystem s_ReplaySystem("MOMReplaySystem"); CMomentumReplaySystem *g_ReplaySystem = &s_ReplaySystem; \ No newline at end of file diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index 093293f002..1ec52575b1 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -5,9 +5,9 @@ #include "filesystem.h" #include "utlbuffer.h" -#include "mom_player_shared.h" -#include "mom_shareddefs.h" #include "replayformat.h" +#include "mom_player_shared.h" +#include "mom_replay_entity.h" #define RECORDING_PATH "recordings" #define END_RECORDING_PAUSE 1.0 @@ -39,6 +39,7 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame replay_header_t *ReadHeader(FileHandle_t file, const char *filename); void StartReplay(bool firstperson = false); + void EndReplay(); bool LoadRun(const char *fileName); CUtlVector m_vecRunData; @@ -58,6 +59,7 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame float m_fRecEndTime; CMomentumPlayer *m_player; + CMomentumReplayGhostEntity *m_CurrentReplayGhost; replay_frame_t m_currentFrame; replay_header_t m_replayHeader; diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 1e43cd862b..5e78bdcd07 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -2,12 +2,12 @@ #include "mom_replay_entity.h" #include "util/mom_util.h" #include "Timer.h" +#include "mom_replay.h" +#include "mom_shareddefs.h" - -static ConVar mom_replay_firstperson("mom_replay_firstperson", "1", - FCVAR_CLIENTCMD_CAN_EXECUTE, "Watch replay in first-person", true, 0, true, 1); -static ConVar mom_replay_reverse("mom_replay_reverse", "0", - FCVAR_CLIENTCMD_CAN_EXECUTE, "Reverse playback of replay", true, 0, true, 1); +MAKE_TOGGLE_CONVAR(mom_replay_firstperson, "1", FCVAR_CLIENTCMD_CAN_EXECUTE, "Watch replay in first-person"); +MAKE_TOGGLE_CONVAR(mom_replay_reverse, "0", FCVAR_CLIENTCMD_CAN_EXECUTE, "Reverse playback of replay"); +MAKE_TOGGLE_CONVAR(mom_replay_loop, "1", FCVAR_CLIENTCMD_CAN_EXECUTE, "Loop playback of replay ghost"); static ConVar mom_replay_ghost_bodygroup("mom_replay_ghost_bodygroup", "11", FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE, "Replay ghost's body group (model)", true, 0, true, 14); static ConCommand mom_replay_ghost_color("mom_replay_ghost_color", @@ -27,7 +27,10 @@ const char* CMomentumReplayGhostEntity::GetGhostModel() { return m_pszModel; } - +CMomentumReplayGhostEntity::~CMomentumReplayGhostEntity() +{ + g_ReplaySystem->m_bIsWatchingReplay = false; +} void CMomentumReplayGhostEntity::Precache(void) { BaseClass::Precache(); @@ -51,13 +54,15 @@ void CMomentumReplayGhostEntity::Spawn(void) SetBodygroup(1, mom_replay_ghost_bodygroup.GetInt()); } -void CMomentumReplayGhostEntity::StartRun(bool firstPerson) +void CMomentumReplayGhostEntity::StartRun(bool firstPerson, bool shouldLoop) { mom_replay_firstperson.SetValue(firstPerson ? "1" : "0"); + mom_replay_loop.SetValue(shouldLoop ? "1" : "0"); + Spawn(); m_nStartTick = gpGlobals->curtime; m_bIsActive = true; - step = 1; + step = 0; SetAbsOrigin(g_ReplaySystem->m_vecRunData[0].m_vPlayerOrigin); SetNextThink(gpGlobals->curtime); @@ -65,26 +70,29 @@ void CMomentumReplayGhostEntity::StartRun(bool firstPerson) void CMomentumReplayGhostEntity::UpdateStep() { currentStep = g_ReplaySystem->m_vecRunData[step]; - ++step; if (mom_replay_reverse.GetBool()) { nextStep = g_ReplaySystem->m_vecRunData[--step]; } else if (step < g_ReplaySystem->m_vecRunData.Size()) { - nextStep = g_ReplaySystem->m_vecRunData[step]; + nextStep = g_ReplaySystem->m_vecRunData[++step]; } } void CMomentumReplayGhostEntity::Think(void) { BaseClass::Think(); - if (step >= 1) + if (step >= 0) { - if (step < g_ReplaySystem->m_vecRunData.Size()) + if (step+1 < g_ReplaySystem->m_vecRunData.Size()) { UpdateStep(); mom_replay_firstperson.GetBool() ? HandleGhostFirstPerson() : HandleGhost(); } + else if (step+1 == g_ReplaySystem->m_vecRunData.Size() && mom_replay_loop.GetBool()) + { + step = 0; //reset us to the start + } else { EndRun(); diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index 88f5c03c2e..878a769102 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -1,6 +1,10 @@ +#ifndef MOM_REPLAY_GHOST_H +#define MOM_REPLAY_GHOST_H + #include "cbase.h" -#include "mom_replay.h" #include "in_buttons.h" +#include "replayformat.h" +#include "mom_player_shared.h" #pragma once @@ -30,7 +34,7 @@ class CMomentumReplayGhostEntity : public CBaseAnimating DECLARE_CLASS(CMomentumReplayGhostEntity, CBaseAnimating); DECLARE_DATADESC(); public: - ~CMomentumReplayGhostEntity(){ g_ReplaySystem->m_bIsWatchingReplay = false;} + ~CMomentumReplayGhostEntity(); const char* GetGhostModel(); void SetGhostModel(const char* model); void SetGhostBodyGroup(int bodyGroup); @@ -39,7 +43,7 @@ class CMomentumReplayGhostEntity : public CBaseAnimating void UpdateStep(); void EndRun(); - void StartRun(bool firstPerson = false); + void StartRun(bool firstPerson = false, bool shouldLoop = false); void HandleGhost(); void HandleGhostFirstPerson(); void UpdateStats(Vector ghostVel, CMomentumPlayer *pPlayer); //for hud display.. @@ -66,4 +70,7 @@ class CMomentumReplayGhostEntity : public CBaseAnimating QAngle m_qLastEyeAngle; float m_flLastSyncVelocity; int m_nStrafeTicks, m_nPerfectSyncTicks, m_nAccelTicks, m_nOldReplayButtons; + bool m_bReplayShouldLoop; }; + +#endif // MOM_REPLAY_GHOST_H From f7aa029c26ca0642484159f2900eac13e2efa9e1 Mon Sep 17 00:00:00 2001 From: tuxxi Date: Thu, 19 May 2016 14:48:59 -0700 Subject: [PATCH 034/101] version update quickfix --- mp/src/game/shared/momentum/mom_shareddefs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mp/src/game/shared/momentum/mom_shareddefs.h b/mp/src/game/shared/momentum/mom_shareddefs.h index 1cc89f87e9..54e1936406 100644 --- a/mp/src/game/shared/momentum/mom_shareddefs.h +++ b/mp/src/game/shared/momentum/mom_shareddefs.h @@ -23,7 +23,7 @@ typedef enum MOMGM // Main Version (0 is alpha, 1 is beta, 2 is release)​.Main feature push (increment by one for each)​.​Small commits or hotfixes​ // When editing this, remember to also edit version.txt on the main dir of the repo // If you have any doubts, please refer to http://semver.org/ -#define MOM_CURRENT_VERSION "0.3.0" +#define MOM_CURRENT_VERSION "0.3.3" #define MOM_COLORIZATION_CHECK_FREQUENCY 0.1f From b78e5755183e28f173598a6351fc6ac3d32ed0e2 Mon Sep 17 00:00:00 2001 From: Nick K Date: Fri, 20 May 2016 02:14:29 -0400 Subject: [PATCH 035/101] Network the replay entity Have some more stuff to do, namely getting triggers working --- mp/src/game/client/client_momentum.vpc | 4 ++ mp/src/game/client/momentum/c_mom_player.cpp | 16 +---- mp/src/game/client/momentum/c_mom_player.h | 30 ++++----- .../client/momentum/c_mom_replay_entity.cpp | 18 ++++++ .../client/momentum/c_mom_replay_entity.h | 19 ++++++ .../client/momentum/ui/hud_comparisons.cpp | 4 +- .../game/client/momentum/ui/hud_keypress.cpp | 42 ++++++++----- .../client/momentum/ui/hud_mapfinished.cpp | 3 +- .../game/client/momentum/ui/hud_mapinfo.cpp | 6 +- .../client/momentum/ui/hud_speedometer.cpp | 17 ++--- .../client/momentum/ui/hud_strafesync.cpp | 63 ++++++++++++++++--- mp/src/game/client/momentum/ui/hud_timer.cpp | 6 +- mp/src/game/server/momentum/Timer.cpp | 6 +- mp/src/game/server/momentum/mom_player.cpp | 44 ++++++------- mp/src/game/server/momentum/mom_player.h | 25 ++++---- mp/src/game/server/momentum/mom_replay.cpp | 4 +- .../server/momentum/mom_replay_entity.cpp | 44 +++++++++---- .../game/server/momentum/mom_replay_entity.h | 14 ++++- mp/src/game/server/momentum/mom_triggers.cpp | 24 +++---- mp/src/game/server/server_momentum.vpc | 2 + .../shared/momentum/mom_entity_run_data.cpp | 45 +++++++++++++ .../shared/momentum/mom_entity_run_data.h | 49 +++++++++++++++ .../game/shared/momentum/mom_player_shared.h | 2 +- 23 files changed, 344 insertions(+), 143 deletions(-) create mode 100644 mp/src/game/client/momentum/c_mom_replay_entity.cpp create mode 100644 mp/src/game/client/momentum/c_mom_replay_entity.h create mode 100644 mp/src/game/shared/momentum/mom_entity_run_data.cpp create mode 100644 mp/src/game/shared/momentum/mom_entity_run_data.h diff --git a/mp/src/game/client/client_momentum.vpc b/mp/src/game/client/client_momentum.vpc index 686b99216b..bc7af5480a 100644 --- a/mp/src/game/client/client_momentum.vpc +++ b/mp/src/game/client/client_momentum.vpc @@ -150,6 +150,8 @@ $Project "Client (Momentum)" $File "$SRCDIR\game\shared\momentum\mom_shareddefs.h" $File "momentum\c_mom_player.cpp" $File "momentum\c_mom_player.h" + $File "momentum\c_mom_replay_entity.h" + $File "momentum\c_mom_replay_entity.cpp" $File "momentum\c_te_shotgun_shot.cpp" $File "momentum\fx_cs_muzzleflash.cpp" $File "momentum\fx_cs_weaponfx.cpp" @@ -166,6 +168,8 @@ $Project "Client (Momentum)" $File "momentum\clientmode_mom_normal.cpp" $File "momentum\mom_event_listener.h" $File "momentum\mom_event_listener.cpp" + $File "$SRCDIR\game\shared\momentum\mom_entity_run_data.h" + $File "$SRCDIR\game\shared\momentum\mom_entity_run_data.cpp" } $Folder "JSON Parser" diff --git a/mp/src/game/client/momentum/c_mom_player.cpp b/mp/src/game/client/momentum/c_mom_player.cpp index d79599dff9..e287f8667e 100644 --- a/mp/src/game/client/momentum/c_mom_player.cpp +++ b/mp/src/game/client/momentum/c_mom_player.cpp @@ -4,27 +4,15 @@ #include "tier0/memdbgon.h" - IMPLEMENT_CLIENTCLASS_DT(C_MomentumPlayer, DT_MOM_Player, CMomentumPlayer) RecvPropInt(RECVINFO(m_iShotsFired)), RecvPropInt(RECVINFO(m_iDirection)), RecvPropBool(RECVINFO(m_bResumeZoom)), RecvPropInt(RECVINFO(m_iLastZoom)), -RecvPropBool(RECVINFO(m_bAutoBhop)), RecvPropBool(RECVINFO(m_bDidPlayerBhop)), RecvPropInt(RECVINFO(m_iSuccessiveBhops)), -RecvPropFloat(RECVINFO(m_flStrafeSync)), -RecvPropFloat(RECVINFO(m_flStrafeSync2)), -RecvPropFloat(RECVINFO(m_flLastJumpVel)), -RecvPropBool(RECVINFO(m_bIsWatchingReplay)), -RecvPropInt(RECVINFO(m_nReplayButtons)), -RecvPropInt(RECVINFO(m_iRunFlags)), -RecvPropBool(RECVINFO(m_bIsInZone)), -RecvPropInt(RECVINFO(m_iCurrentStage)), -RecvPropBool(RECVINFO(m_bMapFinished)), RecvPropFloat(RECVINFO(m_flLastJumpTime)), -//RecvPropDataTable(RECVINFO_DT(m_HL2Local), 0, &REFERENCE_RECV_TABLE(DT_HL2Local)), -//RecvPropBool(RECVINFO(m_fIsSprinting)), +RecvPropDataTable(RECVINFO_DT(m_RunData), SPROP_PROXY_ALWAYS_YES, &REFERENCE_RECV_TABLE(DT_MOM_RunEntData)), END_RECV_TABLE() @@ -32,7 +20,7 @@ C_MomentumPlayer::C_MomentumPlayer() { ConVarRef scissor("r_flashlightscissor"); scissor.SetValue("0"); - m_bMapFinished = false; + m_RunData.m_bMapFinished = false; m_flLastJumpTime = 0.0f; } diff --git a/mp/src/game/client/momentum/c_mom_player.h b/mp/src/game/client/momentum/c_mom_player.h index b1fc4f49c1..32bf6224e9 100644 --- a/mp/src/game/client/momentum/c_mom_player.h +++ b/mp/src/game/client/momentum/c_mom_player.h @@ -6,41 +6,41 @@ #include "cbase.h" #include "momentum/mom_shareddefs.h" +#include "c_mom_replay_entity.h" +#include "mom_entity_run_data.h" class C_MomentumPlayer : public C_BasePlayer { public: DECLARE_CLASS(C_MomentumPlayer, C_BasePlayer); - + DECLARE_CLIENTCLASS(); + C_MomentumPlayer(); ~C_MomentumPlayer(); - DECLARE_CLIENTCLASS(); - Vector m_lastStandingPos; // used by the gamemovement code for finding ladders void SurpressLadderChecks(const Vector& pos, const Vector& normal); bool CanGrabLadder(const Vector& pos, const Vector& normal); bool DidPlayerBhop() { return m_bDidPlayerBhop; } - bool HasAutoBhop() { return m_bAutoBhop; } + bool HasAutoBhop() { return m_RunData.m_bAutoBhop; } void ResetStrafeSync(); + bool IsWatchingReplay() + { + return m_hObserverTarget.Get() && dynamic_cast(m_hObserverTarget.Get()); + } int m_iShotsFired; int m_iDirection; bool m_bResumeZoom; int m_iLastZoom; - bool m_bAutoBhop; bool m_bDidPlayerBhop; - bool m_bIsWatchingReplay; - int m_nReplayButtons; //networked var that allows the replay system to control keypress display on the client - bool m_bIsInZone; - bool m_bMapFinished; - int m_iRunFlags; - int m_iCurrentStage; - float m_flLastJumpTime; - - float m_flStrafeSync, m_flStrafeSync2; - float m_flLastJumpVel; + float m_flLastJumpTime;//Used for the speedometer panel + + CMOMRunEntityData m_RunData; + + //float m_flStrafeSync, m_flStrafeSync2; + //float m_flLastJumpVel; float m_flLastRunTime; diff --git a/mp/src/game/client/momentum/c_mom_replay_entity.cpp b/mp/src/game/client/momentum/c_mom_replay_entity.cpp new file mode 100644 index 0000000000..22b69e69e1 --- /dev/null +++ b/mp/src/game/client/momentum/c_mom_replay_entity.cpp @@ -0,0 +1,18 @@ +#include "cbase.h" +#include "c_mom_replay_entity.h" + +#include "tier0/memdbgon.h" + + +IMPLEMENT_CLIENTCLASS_DT(C_MomentumReplayGhostEntity, DT_MOM_ReplayEnt, CMomentumReplayGhostEntity) +//MOM_TODO: Network the rest of the variables that the ghost entity will be sending +RecvPropInt(RECVINFO(m_nReplayButtons)), +RecvPropInt(RECVINFO(m_iTotalStrafes)), +RecvPropDataTable(RECVINFO_DT(m_RunData), 0, &REFERENCE_RECV_TABLE(DT_MOM_RunEntData)) +END_RECV_TABLE(); + +C_MomentumReplayGhostEntity::C_MomentumReplayGhostEntity() +{ + m_nReplayButtons = 0; + m_iTotalStrafes = 0; +} \ No newline at end of file diff --git a/mp/src/game/client/momentum/c_mom_replay_entity.h b/mp/src/game/client/momentum/c_mom_replay_entity.h new file mode 100644 index 0000000000..4441bfb0e0 --- /dev/null +++ b/mp/src/game/client/momentum/c_mom_replay_entity.h @@ -0,0 +1,19 @@ +#pragma once + +#include "cbase.h" +#include "mom_entity_run_data.h" + +class C_MomentumReplayGhostEntity : public C_BaseAnimating +{ + DECLARE_CLASS(C_MomentumReplayGhostEntity, C_BaseAnimating); + DECLARE_CLIENTCLASS(); + +public: + C_MomentumReplayGhostEntity(); + + CMOMRunEntityData m_RunData; + + int m_nReplayButtons; + int m_iTotalStrafes; + +}; \ No newline at end of file diff --git a/mp/src/game/client/momentum/ui/hud_comparisons.cpp b/mp/src/game/client/momentum/ui/hud_comparisons.cpp index 0d4a034889..ffdf0e7232 100644 --- a/mp/src/game/client/momentum/ui/hud_comparisons.cpp +++ b/mp/src/game/client/momentum/ui/hud_comparisons.cpp @@ -141,7 +141,7 @@ void C_RunComparisons::LoadComparisons() if (szMapName && pPlayer) { m_rcCurrentComparison = new RunCompare_t(); - m_bLoadedComparison = mom_UTIL->GetRunComparison(szMapName, gpGlobals->interval_per_tick, pPlayer->m_iRunFlags, + m_bLoadedComparison = mom_UTIL->GetRunComparison(szMapName, gpGlobals->interval_per_tick, pPlayer->m_RunData.m_iRunFlags, m_rcCurrentComparison); } } @@ -160,7 +160,7 @@ void C_RunComparisons::OnThink() { C_MomentumPlayer *pPlayer = ToCMOMPlayer(C_BasePlayer::GetLocalPlayer()); if (pPlayer) - m_iCurrentStage = pPlayer->m_iCurrentStage; + m_iCurrentStage = pPlayer->m_RunData.m_iCurrentZone; if (!mom_comparisons_time_show_overall.GetBool() && !mom_comparisons_time_show_perstage.GetBool()) { diff --git a/mp/src/game/client/momentum/ui/hud_keypress.cpp b/mp/src/game/client/momentum/ui/hud_keypress.cpp index 1ac1c1555e..5d4b648e93 100644 --- a/mp/src/game/client/momentum/ui/hud_keypress.cpp +++ b/mp/src/game/client/momentum/ui/hud_keypress.cpp @@ -12,9 +12,12 @@ #include #include "mom_shareddefs.h" +#include "c_mom_replay_entity.h" #include "mom_player_shared.h" #include "mom_event_listener.h" +#include "tier0/memdbgon.h" + #define KEYDRAW_MIN 0.07f using namespace vgui; @@ -33,7 +36,7 @@ class CHudKeyPressDisplay : public CHudElement, public Panel bool ShouldDraw() override { C_MomentumPlayer *pMom = ToCMOMPlayer(C_BasePlayer::GetLocalPlayer()); - return showkeys.GetBool() && pMom && !pMom->m_bMapFinished && CHudElement::ShouldDraw(); //don't show during map finished dialog + return showkeys.GetBool() && pMom && !pMom->m_RunData.m_bMapFinished && CHudElement::ShouldDraw(); //don't show during map finished dialog } void OnThink() override; @@ -183,21 +186,30 @@ void CHudKeyPressDisplay::Paint() void CHudKeyPressDisplay::OnThink() { CMomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); - if (pPlayer->m_bIsWatchingReplay) - { - m_nButtons = pPlayer->m_nReplayButtons; - } - else + if (pPlayer) { - m_nButtons = ::input->GetButtonBits(1); - } - - if (g_MOMEventListener) - { //we should only draw the strafe/jump counters when the timer is running - m_bShouldDrawCounts = g_MOMEventListener->m_bTimerIsRunning; - m_nStrafes = g_MOMEventListener->stats.m_iStageStrafes[0]; - m_nJumps = g_MOMEventListener->stats.m_iStageJumps[0]; - } + if (pPlayer->IsWatchingReplay()) + { + C_MomentumReplayGhostEntity *pReplayEnt = dynamic_cast(pPlayer->GetObserverTarget()); + if (pReplayEnt) + { + m_bShouldDrawCounts = true; + m_nButtons = pReplayEnt->m_nReplayButtons; + m_nStrafes = pReplayEnt->m_iTotalStrafes; + m_nJumps = 0;//MOM_TODO: Calculate jumps + } + } else + { + m_nButtons = ::input->GetButtonBits(1); + if (g_MOMEventListener) + { //we should only draw the strafe/jump counters when the timer is running + //MOM_TODO: Update this so that the replay ent also correctly sets these + m_bShouldDrawCounts = g_MOMEventListener->m_bTimerIsRunning; + m_nStrafes = g_MOMEventListener->stats.m_iStageStrafes[0]; + m_nJumps = g_MOMEventListener->stats.m_iStageJumps[0]; + } + } + } } void CHudKeyPressDisplay::Reset() { diff --git a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp index 52b87868b3..f99be2263d 100644 --- a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp +++ b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp @@ -36,7 +36,7 @@ class CHudMapFinishedDialog : public CHudElement, public Panel bool ShouldDraw() override { C_MomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); - return pPlayer && pPlayer->m_bMapFinished; + return pPlayer && pPlayer->m_RunData.m_bMapFinished; } void Paint() override; @@ -324,6 +324,7 @@ void CHudMapFinishedDialog::OnThink() //Is it going to be a localized string, except for errors that have to be specific? ConVarRef hvel("mom_speedometer_hvel"); + //MOM_TODO: Are we going to update to read replay file stats? m_flAvgSpeed = g_MOMEventListener->stats.m_flStageVelocityAvg[0][hvel.GetBool()]; m_flMaxSpeed = g_MOMEventListener->stats.m_flStageVelocityMax[0][hvel.GetBool()]; m_flEndSpeed = g_MOMEventListener->stats.m_flStageExitSpeed[0][hvel.GetBool()]; diff --git a/mp/src/game/client/momentum/ui/hud_mapinfo.cpp b/mp/src/game/client/momentum/ui/hud_mapinfo.cpp index 459e9694e0..1d631ff83e 100644 --- a/mp/src/game/client/momentum/ui/hud_mapinfo.cpp +++ b/mp/src/game/client/momentum/ui/hud_mapinfo.cpp @@ -102,9 +102,9 @@ void C_HudMapInfo::OnThink() C_MomentumPlayer *pLocal = ToCMOMPlayer(C_BasePlayer::GetLocalPlayer()); if (pLocal && g_MOMEventListener) { - m_iStageCurrent = pLocal->m_iCurrentStage; - m_bPlayerInZone = pLocal->m_bIsInZone; - m_bMapFinished = pLocal->m_bMapFinished; + m_iStageCurrent = pLocal->m_RunData.m_iCurrentZone; + m_bPlayerInZone = pLocal->m_RunData.m_bIsInZone; + m_bMapFinished = pLocal->m_RunData.m_bMapFinished; m_iStageCount = g_MOMEventListener->m_iMapCheckpointCount; m_bMapLinear = g_MOMEventListener->m_bMapIsLinear; } diff --git a/mp/src/game/client/momentum/ui/hud_speedometer.cpp b/mp/src/game/client/momentum/ui/hud_speedometer.cpp index 7b41b548f6..d2da24049d 100644 --- a/mp/src/game/client/momentum/ui/hud_speedometer.cpp +++ b/mp/src/game/client/momentum/ui/hud_speedometer.cpp @@ -152,8 +152,9 @@ void CHudSpeedMeter::OnThink() C_MomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); if (pPlayer) { + //MOM_TODO: Update to read replay ent's stuff velocity = pPlayer->GetLocalVelocity(); - float lastJumpVel = pPlayer->m_flLastJumpVel; + float lastJumpVel = pPlayer->m_RunData.m_flLastJumpVel; int velType = mom_speedometer_hvel.GetBool(); // 1 is horizontal velocity if (gpGlobals->curtime - pPlayer->m_flLastJumpTime > 5.0f) @@ -215,19 +216,19 @@ void CHudSpeedMeter::OnThink() m_flNextColorizeCheck = gpGlobals->curtime + MOM_COLORIZATION_CHECK_FREQUENCY; } // reset last jump velocity when we restart a run by entering the start zone - if (pPlayer->m_bIsInZone && pPlayer->m_iCurrentStage == 1) + if (pPlayer->m_RunData.m_bIsInZone && pPlayer->m_RunData.m_iCurrentZone == 1) m_flLastJumpVelocity = 0; - if (pPlayer->m_flLastJumpVel == 0) + if (pPlayer->m_RunData.m_flLastJumpVel == 0) { m_SecondaryValueColor = normalColor; } - else if (m_flLastJumpVelocity != pPlayer->m_flLastJumpVel) + else if (m_flLastJumpVelocity != pPlayer->m_RunData.m_flLastJumpVel) { m_SecondaryValueColor = - mom_UTIL->GetColorFromVariation(abs(pPlayer->m_flLastJumpVel) - abs(m_flLastJumpVelocity), 0.0f, + mom_UTIL->GetColorFromVariation(abs(pPlayer->m_RunData.m_flLastJumpVel) - abs(m_flLastJumpVelocity), 0.0f, normalColor, increaseColor, decreaseColor); - m_flLastJumpVelocity = pPlayer->m_flLastJumpVel; + m_flLastJumpVelocity = pPlayer->m_RunData.m_flLastJumpVel; } } else @@ -264,7 +265,7 @@ void CHudSpeedMeter::Paint() C_MomentumPlayer *pPlayer = ToCMOMPlayer(C_BasePlayer::GetLocalPlayer()); // Draw the enter speed split, if toggled on - if (mom_speedometer_showenterspeed.GetBool() && pPlayer && !pPlayer->m_bIsInZone && + if (mom_speedometer_showenterspeed.GetBool() && pPlayer && !pPlayer->m_RunData.m_bIsInZone && g_MOMEventListener->m_bTimerIsRunning) { int split_xpos; // Dynamically set @@ -282,7 +283,7 @@ void CHudSpeedMeter::Paint() Color fg = GetFgColor(); Color actualColorFade = Color(fg.r(), fg.g(), fg.b(), stageStartAlpha); - g_MOMRunCompare->GetComparisonString(VELOCITY_ENTER, pPlayer->m_iCurrentStage, enterVelANSITemp, + g_MOMRunCompare->GetComparisonString(VELOCITY_ENTER, pPlayer->m_RunData.m_iCurrentZone, enterVelANSITemp, enterVelANSICompTemp, &compareColor); Q_snprintf(enterVelANSI, BUFSIZELOCL, "%i", static_cast(round(atof(enterVelANSITemp)))); diff --git a/mp/src/game/client/momentum/ui/hud_strafesync.cpp b/mp/src/game/client/momentum/ui/hud_strafesync.cpp index 5727d94a5c..9b48e94b0a 100644 --- a/mp/src/game/client/momentum/ui/hud_strafesync.cpp +++ b/mp/src/game/client/momentum/ui/hud_strafesync.cpp @@ -5,6 +5,7 @@ #include "iclientmode.h" #include "mom_player_shared.h" #include "momentum/util/mom_util.h" +#include "c_mom_replay_entity.h" #include "vphysics_interface.h" #include "mom_event_listener.h" #include @@ -94,12 +95,33 @@ void CHudStrafeSyncDisplay::OnThink() C_MomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); if (!pPlayer) return; - if (strafesync_type.GetInt() == 1) // sync1 - m_localStrafeSync = pPlayer->m_flStrafeSync; - else if (strafesync_type.GetInt() == 2) // sync2 - m_localStrafeSync = pPlayer->m_flStrafeSync2; - else - m_localStrafeSync = 0; + m_localStrafeSync = 0; + + if (pPlayer->IsWatchingReplay()) + { + C_MomentumReplayGhostEntity *pReplayEnt = dynamic_cast(pPlayer->GetObserverTarget()); + if (pReplayEnt) + { + if (strafesync_type.GetInt() == 1) // sync1 + m_localStrafeSync = pReplayEnt->m_RunData.m_flStrafeSync; + else if (strafesync_type.GetInt() == 2) // sync2 + m_localStrafeSync = pReplayEnt->m_RunData.m_flStrafeSync2; + } + } else + { + if (strafesync_type.GetInt() == 1) // sync1 + m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync; + else if (strafesync_type.GetInt() == 2) // sync2 + m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync2; + } + + + //if (strafesync_type.GetInt() == 1) // sync1 + // m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync; + //else if (strafesync_type.GetInt() == 2) // sync2 + // m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync2; + //else + // m_localStrafeSync = 0; float clampedStrafeSync = clamp(m_localStrafeSync, 0, 100); @@ -235,10 +257,31 @@ void CHudStrafeSyncBar::OnThink() C_MomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); if (pPlayer == nullptr) return; - if (strafesync_type.GetInt() == 1) // sync1 - m_localStrafeSync = pPlayer->m_flStrafeSync; - else if (strafesync_type.GetInt() == 2) // sync2 - m_localStrafeSync = pPlayer->m_flStrafeSync2; + + if (pPlayer->IsWatchingReplay()) + { + C_MomentumReplayGhostEntity *pReplayEnt = dynamic_cast(pPlayer->GetObserverTarget()); + if (pReplayEnt) + { + if (strafesync_type.GetInt() == 1) // sync1 + m_localStrafeSync = pReplayEnt->m_RunData.m_flStrafeSync; + else if (strafesync_type.GetInt() == 2) // sync2 + m_localStrafeSync = pReplayEnt->m_RunData.m_flStrafeSync2; + } + } + else + { + if (strafesync_type.GetInt() == 1) // sync1 + m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync; + else if (strafesync_type.GetInt() == 2) // sync2 + m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync2; + } + + //if (strafesync_type.GetInt() == 1) // sync1 + // m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync; + //else if (strafesync_type.GetInt() == 2) // sync2 + // m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync2; + switch (strafesync_colorize.GetInt()) { case 1: diff --git a/mp/src/game/client/momentum/ui/hud_timer.cpp b/mp/src/game/client/momentum/ui/hud_timer.cpp index 5f98779597..e75ddd2821 100644 --- a/mp/src/game/client/momentum/ui/hud_timer.cpp +++ b/mp/src/game/client/momentum/ui/hud_timer.cpp @@ -231,9 +231,9 @@ void C_Timer::OnThink() C_MomentumPlayer *pLocal = ToCMOMPlayer(C_BasePlayer::GetLocalPlayer()); if (pLocal && g_MOMEventListener) { - m_iStageCurrent = pLocal->m_iCurrentStage; - m_bPlayerInZone = pLocal->m_bIsInZone; - m_bMapFinished = pLocal->m_bMapFinished; + m_iStageCurrent = pLocal->m_RunData.m_iCurrentZone; + m_bPlayerInZone = pLocal->m_RunData.m_bIsInZone; + m_bMapFinished = pLocal->m_RunData.m_bMapFinished; m_iStageCount = g_MOMEventListener->m_iMapCheckpointCount; } } diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index 9dfbf25538..b5a4f9fbac 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -251,7 +251,7 @@ void CTimer::Stop(bool endTrigger /* = false */) Time t = Time(); t.time_sec = static_cast(gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; t.tickrate = gpGlobals->interval_per_tick; - t.flags = pPlayer->m_iRunFlags; + t.flags = pPlayer->m_RunData.m_iRunFlags; time(&t.date); t.RunStats = pPlayer->m_PlayerRunStats; //copy all the run stats @@ -291,8 +291,8 @@ void CTimer::Stop(bool endTrigger /* = false */) if (pPlayer) { - pPlayer->m_bIsInZone = endTrigger; - pPlayer->m_bMapFinished = endTrigger; + pPlayer->m_RunData.m_bIsInZone = endTrigger; + pPlayer->m_RunData.m_bMapFinished = endTrigger; } SetRunning(false); m_iEndTick = gpGlobals->tickcount; diff --git a/mp/src/game/server/momentum/mom_player.cpp b/mp/src/game/server/momentum/mom_player.cpp index 0da86b9d6a..392c9afa8a 100644 --- a/mp/src/game/server/momentum/mom_player.cpp +++ b/mp/src/game/server/momentum/mom_player.cpp @@ -13,19 +13,10 @@ SendPropInt(SENDINFO(m_iShotsFired)), SendPropInt(SENDINFO(m_iDirection)), SendPropBool(SENDINFO(m_bResumeZoom)), SendPropInt(SENDINFO(m_iLastZoom)), -SendPropBool(SENDINFO(m_bAutoBhop)), SendPropBool(SENDINFO(m_bDidPlayerBhop)), SendPropInt(SENDINFO(m_iSuccessiveBhops)), -SendPropFloat(SENDINFO(m_flStrafeSync)), -SendPropFloat(SENDINFO(m_flStrafeSync2)), -SendPropFloat(SENDINFO(m_flLastJumpVel)), -SendPropBool(SENDINFO(m_bIsWatchingReplay)), -SendPropInt(SENDINFO(m_nReplayButtons)), -SendPropInt(SENDINFO(m_iRunFlags)), -SendPropBool(SENDINFO(m_bIsInZone)), -SendPropInt(SENDINFO(m_iCurrentStage)), -SendPropBool(SENDINFO(m_bMapFinished)), SendPropFloat(SENDINFO(m_flLastJumpTime)), +SendPropDataTable(SENDINFO_DT(m_RunData), &REFERENCE_SEND_TABLE(DT_MOM_RunEntData)), END_SEND_TABLE() BEGIN_DATADESC(CMomentumPlayer) @@ -43,7 +34,7 @@ CMomentumPlayer::CMomentumPlayer() { m_flPunishTime = -1; m_iLastBlock = -1; - m_iRunFlags = 0; + m_RunData.m_iRunFlags = 0; } CMomentumPlayer::~CMomentumPlayer() @@ -86,9 +77,9 @@ void CMomentumPlayer::Spawn() IGameEvent *runUploadEvent = gameeventmanager->CreateEvent("run_upload"); IGameEvent *timerStartEvent = gameeventmanager->CreateEvent("timer_state"); IGameEvent *practiceModeEvent = gameeventmanager->CreateEvent("practice_mode"); - m_bIsInZone = false; - m_bMapFinished = false; - m_iCurrentStage = 0; + m_RunData.m_bIsInZone = false; + m_RunData.m_bMapFinished = false; + m_RunData.m_iCurrentZone = 0; ResetRunStats(); if (runSaveEvent) { @@ -223,12 +214,12 @@ void CMomentumPlayer::InitHUD() void CMomentumPlayer::EnableAutoBhop() { - m_bAutoBhop = true; + m_RunData.m_bAutoBhop = true; DevLog("Enabled autobhop\n"); } void CMomentumPlayer::DisableAutoBhop() { - m_bAutoBhop = false; + m_RunData.m_bAutoBhop = false; DevLog("Disabled autobhop\n"); } void CMomentumPlayer::CheckForBhop() @@ -242,7 +233,7 @@ void CMomentumPlayer::CheckForBhop() m_iSuccessiveBhops = 0; if (m_nButtons & IN_JUMP) { - m_flLastJumpVel = GetLocalVelocity().Length2D(); + m_RunData.m_flLastJumpVel = GetLocalVelocity().Length2D(); m_iSuccessiveBhops++; if (g_Timer->IsRunning()) { @@ -332,8 +323,8 @@ void CMomentumPlayer::UpdateRunStats() } if (m_nStrafeTicks && m_nAccelTicks && m_nPerfectSyncTicks) { - m_flStrafeSync = (float(m_nPerfectSyncTicks) / float(m_nStrafeTicks)) * 100; // ticks strafing perfectly / ticks strafing - m_flStrafeSync2 = (float(m_nAccelTicks) / float(m_nStrafeTicks)) * 100; // ticks gaining speed / ticks strafing + m_RunData.m_flStrafeSync = (float(m_nPerfectSyncTicks) / float(m_nStrafeTicks)) * 100.0f; // ticks strafing perfectly / ticks strafing + m_RunData.m_flStrafeSync2 = (float(m_nAccelTicks) / float(m_nStrafeTicks)) * 100.0f; // ticks gaining speed / ticks strafing } // ---------- @@ -363,8 +354,8 @@ void CMomentumPlayer::ResetRunStats() m_nPerfectSyncTicks = 0; m_nStrafeTicks = 0; m_nAccelTicks = 0; - m_flStrafeSync = 0; - m_flStrafeSync2 = 0; + m_RunData.m_flStrafeSync = 0; + m_RunData.m_flStrafeSync2 = 0; m_PlayerRunStats = RunStats_t(); } @@ -375,8 +366,8 @@ void CMomentumPlayer::CalculateAverageStats() { int currentStage = g_Timer->GetCurrentStageNumber(); - m_flStageTotalSync[currentStage] += m_flStrafeSync; - m_flStageTotalSync2[currentStage] += m_flStrafeSync2; + m_flStageTotalSync[currentStage] += m_RunData.m_flStrafeSync; + m_flStageTotalSync2[currentStage] += m_RunData.m_flStrafeSync2; m_flStageTotalVelocity[currentStage][0] += GetLocalVelocity().Length(); m_flStageTotalVelocity[currentStage][1] += GetLocalVelocity().Length2D(); @@ -388,8 +379,8 @@ void CMomentumPlayer::CalculateAverageStats() m_PlayerRunStats.m_flStageVelocityAvg[currentStage][1] = m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); //stage 0 is "overall" - also update these as well, no matter which stage we are on - m_flStageTotalSync[0] += m_flStrafeSync; - m_flStageTotalSync2[0] += m_flStrafeSync2; + m_flStageTotalSync[0] += m_RunData.m_flStrafeSync; + m_flStageTotalSync2[0] += m_RunData.m_flStrafeSync2; m_flStageTotalVelocity[0][0] += GetLocalVelocity().Length(); m_flStageTotalVelocity[0][1] += GetLocalVelocity().Length2D(); m_nStageAvgCount[0]++; @@ -407,12 +398,13 @@ void CMomentumPlayer::CalculateAverageStats() //On surf/other, it only limits practice mode speed. On bhop/scroll, it limits the movement speed above a certain threshhold, and //clamps the player's velocity if they go above it. This is to prevent prespeeding and is different per gamemode due to the different //respective playstyles of surf and bhop. +//MOM_TODO: Update this to extend to start zones of stages (if doing ILs) void CMomentumPlayer::LimitSpeedInStartZone() { ConVarRef gm("mom_gamemode"); CTriggerTimerStart *startTrigger = g_Timer->GetStartTrigger(); bool bhopGameMode = (gm.GetInt() == MOMGM_BHOP || gm.GetInt() == MOMGM_SCROLL); - if (m_bIsInZone && m_iCurrentStage == 1) + if (m_RunData.m_bIsInZone && m_RunData.m_iCurrentZone == 1) { if (GetGroundEntity() == nullptr && !g_Timer->IsPracticeMode(this)) //don't count ticks in air if we're in practice mode m_nTicksInAir++; diff --git a/mp/src/game/server/momentum/mom_player.h b/mp/src/game/server/momentum/mom_player.h index c0ba984835..1a69239fb1 100644 --- a/mp/src/game/server/momentum/mom_player.h +++ b/mp/src/game/server/momentum/mom_player.h @@ -9,6 +9,8 @@ #include "momentum/mom_shareddefs.h" #include "player.h" #include "util/run_stats.h" +#include "mom_replay_entity.h" +#include "mom_entity_run_data.h" class CMomentumPlayer : public CBasePlayer { @@ -71,7 +73,7 @@ class CMomentumPlayer : public CBasePlayer void EnableAutoBhop(); void DisableAutoBhop(); - bool HasAutoBhop() { return m_bAutoBhop; } + bool HasAutoBhop() { return m_RunData.m_bAutoBhop; } bool DidPlayerBhop() { return m_bDidPlayerBhop; } // think function for detecting if player bhopped void CheckForBhop(); @@ -86,20 +88,13 @@ class CMomentumPlayer : public CBasePlayer CNetworkVar(bool, m_bResumeZoom); CNetworkVar(int, m_iLastZoom); - CNetworkVar(bool, m_bAutoBhop);// Is the player using auto bhop? CNetworkVar(bool, m_bDidPlayerBhop);// Did the player bunnyhop successfully? CNetworkVar(int, m_iSuccessiveBhops); //How many successive bhops this player has - CNetworkVar(float, m_flStrafeSync); //eyeangle based, perfect strafes / total strafes - CNetworkVar(float, m_flStrafeSync2); //acceleration based, strafes speed gained / total strafes - CNetworkVar(bool, m_bIsWatchingReplay); - CNetworkVar(int, m_nReplayButtons); - CNetworkVar(float, m_flLastJumpVel); //Last jump velocity of the player - CNetworkVar(int, m_iRunFlags);//The run flags (W only/HSW/Scroll etc) of the player - CNetworkVar(bool, m_bIsInZone);//This is true if the player is in a CTriggerTimerStage zone - CNetworkVar(bool, m_bMapFinished);//Did the player finish the map? - CNetworkVar(int, m_iCurrentStage);//Current stage the player is on + //CNetworkVar(bool, m_bIsWatchingReplay); CNetworkVar(float, m_flLastJumpTime);//The last time that the player jumped + CNetworkVarEmbedded(CMOMRunEntityData, m_RunData);//Current run data, used for hud elements + void GetBulletTypeParameters(int iBulletType, float &fPenetrationPower, float &flPenetrationDistance); void FireBullet(Vector vecSrc, const QAngle &shootAngles, float vecSpread, float flDistance, int iPenetration, @@ -110,13 +105,18 @@ class CMomentumPlayer : public CBasePlayer float lateral_max, int direction_change); void SetPunishTime(float newTime) { m_flPunishTime = newTime; } - + void SetLastBlock(int lastBlock) { m_iLastBlock = lastBlock; } bool IsValidObserverTarget(CBaseEntity *target) override; int GetLastBlock() { return m_iLastBlock; } float GetPunishTime() { return m_flPunishTime; } + bool IsWatchingReplay() + { + return m_hObserverTarget.Get() && dynamic_cast(m_hObserverTarget.Get()); + } + //Run Stats RunStats_t m_PlayerRunStats; @@ -125,7 +125,6 @@ class CMomentumPlayer : public CBasePlayer float m_flStageTotalSync[MAX_STAGES], m_flStageTotalSync2[MAX_STAGES], m_flStageTotalVelocity[MAX_STAGES][2]; - //bool m_bInsideStartZone; private: CountdownTimer m_ladderSurpressionTimer; Vector m_lastLadderNormal; diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 7906bdabe8..28fe2711c5 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -9,7 +9,7 @@ void CMomentumReplaySystem::BeginRecording(CBasePlayer *pPlayer) { m_player = ToCMOMPlayer( pPlayer); - if (!m_player->m_bIsWatchingReplay) //don't record if we're watching a preexisting replay + if (!m_player->IsWatchingReplay()) //don't record if we're watching a preexisting replay { m_bIsRecording = true; Log("Recording began!\n"); @@ -167,7 +167,7 @@ void CMomentumReplaySystem::StartReplay(bool firstperson) m_CurrentReplayGhost = static_cast(CreateEntityByName("mom_replay_ghost")); if (m_CurrentReplayGhost != nullptr) { - g_Timer->Stop(false); //stop the timer just in case we started a replay while it was running... + if (firstperson) g_Timer->Stop(false); //stop the timer just in case we started a replay while it was running... m_CurrentReplayGhost->StartRun(firstperson); } } diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 5e78bdcd07..19309dfbe0 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -18,12 +18,26 @@ static ConVar mom_replay_ghost_alpha("mom_replay_ghost_alpha", "75", LINK_ENTITY_TO_CLASS(mom_replay_ghost, CMomentumReplayGhostEntity); +IMPLEMENT_SERVERCLASS_ST(CMomentumReplayGhostEntity, DT_MOM_ReplayEnt) +//MOM_TODO: Network other variables that the UI will need to reference +SendPropInt(SENDINFO(m_nReplayButtons)), +SendPropInt(SENDINFO(m_iTotalStrafes)), +SendPropDataTable(SENDINFO_DT(m_RunData), &REFERENCE_SEND_TABLE(DT_MOM_RunEntData)), +END_SEND_TABLE() + BEGIN_DATADESC(CMomentumReplayGhostEntity) END_DATADESC() Color CMomentumReplayGhostEntity::m_newGhostColor = COLOR_GREEN; -const char* CMomentumReplayGhostEntity::GetGhostModel() +CMomentumReplayGhostEntity::CMomentumReplayGhostEntity() +{ + m_nReplayButtons = 0; + m_iTotalStrafes = 0; +} + + +const char* CMomentumReplayGhostEntity::GetGhostModel() const { return m_pszModel; } @@ -60,6 +74,7 @@ void CMomentumReplayGhostEntity::StartRun(bool firstPerson, bool shouldLoop) mom_replay_loop.SetValue(shouldLoop ? "1" : "0"); Spawn(); + m_iTotalStrafes = 0; m_nStartTick = gpGlobals->curtime; m_bIsActive = true; step = 0; @@ -128,7 +143,7 @@ void CMomentumReplayGhostEntity::HandleGhostFirstPerson() CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); if (pPlayer) { - pPlayer->m_bIsWatchingReplay = true; + //pPlayer->IsWatchingReplay() = true; if (!pPlayer->IsObserver()) { pPlayer->SetObserverTarget(this); @@ -162,10 +177,10 @@ void CMomentumReplayGhostEntity::HandleGhostFirstPerson() float distZ = fabs(currentStep.m_vPlayerOrigin.z - nextStep.m_vPlayerOrigin.z); Vector interpolatedVel = Vector(distX, distY, distZ) / gpGlobals->interval_per_tick; SetAbsVelocity(interpolatedVel); - pPlayer->m_nReplayButtons = currentStep.m_nPlayerButtons; //networked var that allows the replay to control keypress display on the client + m_nReplayButtons = currentStep.m_nPlayerButtons; //networked var that allows the replay to control keypress display on the client if (g_Timer->IsRunning()) - UpdateStats(interpolatedVel, pPlayer); + UpdateStats(interpolatedVel); if (currentStep.m_nPlayerButtons & IN_DUCK) { @@ -174,6 +189,7 @@ void CMomentumReplayGhostEntity::HandleGhostFirstPerson() } } } + void CMomentumReplayGhostEntity::HandleGhost() { SetAbsOrigin(currentStep.m_vPlayerOrigin); @@ -186,12 +202,13 @@ void CMomentumReplayGhostEntity::HandleGhost() CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); if (pPlayer && pPlayer->IsObserver()) //bring the player out of obs mode if theyre currently observing { - pPlayer->m_bIsWatchingReplay = false; + //pPlayer->m_bIsWatchingReplay = false; pPlayer->StopObserverMode(); pPlayer->ForceRespawn(); } } -void CMomentumReplayGhostEntity::UpdateStats(Vector ghostVel, CMomentumPlayer *pPlayer) + +void CMomentumReplayGhostEntity::UpdateStats(Vector ghostVel) { // --- STRAFE SYNC --- //calculate strafe sync based on replay ghost's movement, in order to update the player's HUD @@ -218,15 +235,18 @@ void CMomentumReplayGhostEntity::UpdateStats(Vector ghostVel, CMomentumPlayer *p } if (m_nStrafeTicks && m_nAccelTicks && m_nPerfectSyncTicks) { - pPlayer->m_flStrafeSync = ((float)m_nPerfectSyncTicks / (float)m_nStrafeTicks) * 100; // ticks strafing perfectly / ticks strafing - pPlayer->m_flStrafeSync2 = ((float)m_nAccelTicks / (float)m_nStrafeTicks) * 100; // ticks gaining speed / ticks strafing + m_RunData.m_flStrafeSync = (float(m_nPerfectSyncTicks) / float(m_nStrafeTicks)) * 100.0f; // ticks strafing perfectly / ticks strafing + m_RunData.m_flStrafeSync2 = (float(m_nAccelTicks) / float(m_nStrafeTicks)) * 100.0f; // ticks gaining speed / ticks strafing } + // --- JUMP AND STRAFE COUNTER --- - if (GetGroundEntity() != NULL && currentStep.m_nPlayerButtons & IN_JUMP) - pPlayer->m_PlayerRunStats.m_iStageJumps[0]++; + //MOM_TODO: GetGroundEntity is never not null (the replay ghost never "jumps") + //if (GetGroundEntity() != NULL && currentStep.m_nPlayerButtons & IN_JUMP) + // pPlayer->m_PlayerRunStats.m_iStageJumps[0]++; + if ((currentStep.m_nPlayerButtons & IN_MOVELEFT && !(m_nOldReplayButtons & IN_MOVELEFT)) || (currentStep.m_nPlayerButtons & IN_MOVERIGHT && !(m_nOldReplayButtons & IN_MOVERIGHT)) ) - pPlayer->m_PlayerRunStats.m_iStageStrafes[0]++; + m_iTotalStrafes++; m_flLastSyncVelocity = SyncVelocity; m_qLastEyeAngle = EyeAngles(); @@ -272,6 +292,6 @@ void CMomentumReplayGhostEntity::EndRun() pPlayer->StopObserverMode(); pPlayer->ForceRespawn(); pPlayer->SetMoveType(MOVETYPE_WALK); - pPlayer->m_bIsWatchingReplay = false; + //pPlayer->m_bIsWatchingReplay = false; } } \ No newline at end of file diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index 878a769102..975eaf1b49 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -4,7 +4,7 @@ #include "cbase.h" #include "in_buttons.h" #include "replayformat.h" -#include "mom_player_shared.h" +#include "mom_entity_run_data.h" #pragma once @@ -33,9 +33,11 @@ class CMomentumReplayGhostEntity : public CBaseAnimating { DECLARE_CLASS(CMomentumReplayGhostEntity, CBaseAnimating); DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); public: + CMomentumReplayGhostEntity(); ~CMomentumReplayGhostEntity(); - const char* GetGhostModel(); + const char* GetGhostModel() const; void SetGhostModel(const char* model); void SetGhostBodyGroup(int bodyGroup); static void SetGhostColor(const CCommand &args); @@ -46,11 +48,15 @@ class CMomentumReplayGhostEntity : public CBaseAnimating void StartRun(bool firstPerson = false, bool shouldLoop = false); void HandleGhost(); void HandleGhostFirstPerson(); - void UpdateStats(Vector ghostVel, CMomentumPlayer *pPlayer); //for hud display.. + void UpdateStats(Vector ghostVel); //for hud display.. bool m_bIsActive; int m_nStartTick; + CNetworkVarEmbedded(CMOMRunEntityData, m_RunData); + CNetworkVar(int, m_nReplayButtons); + CNetworkVar(int, m_iTotalStrafes); + protected: void Think(void) override; void Spawn(void) override; @@ -61,6 +67,8 @@ class CMomentumReplayGhostEntity : public CBaseAnimating replay_frame_t currentStep; replay_frame_t nextStep; + //MOM_TODO: CUtlVector spectators; + int step; int m_iBodyGroup = BODY_PROLATE_ELLIPSE; Color m_ghostColor; diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index f911948509..bc63bbca72 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -32,8 +32,8 @@ void CTriggerStage::StartTouch(CBaseEntity *pOther) CMomentumPlayer *pPlayer = ToCMOMPlayer(pOther); if (pPlayer) { - pPlayer->m_bIsInZone = true; - pPlayer->m_iCurrentStage = stageNum; + pPlayer->m_RunData.m_bIsInZone = true; + pPlayer->m_RunData.m_iCurrentZone = stageNum; } g_Timer->SetCurrentStage(this); IGameEvent *stageEvent = gameeventmanager->CreateEvent("stage_enter"); @@ -81,7 +81,7 @@ void CTriggerStage::EndTouch(CBaseEntity *pOther) if (stageEvent) { //Status - pPlayer->m_bIsInZone = false; + pPlayer->m_RunData.m_bIsInZone = false; //Stage num stageEvent->SetInt("stage_num", stageNum); @@ -135,8 +135,8 @@ void CTriggerTimerStart::EndTouch(CBaseEntity *pOther) } g_Timer->Start(gpGlobals->tickcount); } - pPlayer->m_bIsInZone = false; - pPlayer->m_bMapFinished = false; + pPlayer->m_RunData.m_bIsInZone = false; + pPlayer->m_RunData.m_bMapFinished = false; } IGameEvent *movementCountsResetEvent = gameeventmanager->CreateEvent("keypress"); @@ -156,9 +156,9 @@ void CTriggerTimerStart::StartTouch(CBaseEntity *pOther) if (pPlayer) { pPlayer->ResetRunStats();//Reset run stats - pPlayer->m_bIsInZone = true; - pPlayer->m_bMapFinished = false; - pPlayer->m_flLastJumpVel = 0; //also reset last jump velocity when we enter the start zone + pPlayer->m_RunData.m_bIsInZone = true; + pPlayer->m_RunData.m_bMapFinished = false; + pPlayer->m_RunData.m_flLastJumpVel = 0; //also reset last jump velocity when we enter the start zone if (g_Timer->IsRunning()) { @@ -295,7 +295,7 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) } g_Timer->Stop(true); - pPlayer->m_bMapFinished = true; + pPlayer->m_RunData.m_bMapFinished = true; //MOM_TODO: SLOW DOWN/STOP THE PLAYER HERE! } @@ -303,7 +303,7 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) if (g_ReplaySystem->IsRecording(pPlayer)) g_ReplaySystem->StopRecording(ToCMOMPlayer(pOther), false, true); - pPlayer->m_bIsInZone = true; + pPlayer->m_RunData.m_bIsInZone = true; } BaseClass::StartTouch(pOther); } @@ -312,8 +312,8 @@ void CTriggerTimerStop::EndTouch(CBaseEntity* pOther) CMomentumPlayer *pMomPlayer = ToCMOMPlayer(pOther); if (pMomPlayer) { - pMomPlayer->m_bMapFinished = false;//Close the hud_mapfinished panel - pMomPlayer->m_bIsInZone = false;//Update status + pMomPlayer->m_RunData.m_bMapFinished = false;//Close the hud_mapfinished panel + pMomPlayer->m_RunData.m_bIsInZone = false;//Update status } BaseClass::EndTouch(pOther); } diff --git a/mp/src/game/server/server_momentum.vpc b/mp/src/game/server/server_momentum.vpc index 44ded91312..bcc5210a9f 100644 --- a/mp/src/game/server/server_momentum.vpc +++ b/mp/src/game/server/server_momentum.vpc @@ -133,6 +133,8 @@ $Project "Server (Momentum)" $File "momentum\mom_playermove.cpp" $File "momentum\mom_replay.cpp" $File "momentum\mom_replay.h" + $File "$SRCDIR\game\shared\momentum\mom_entity_run_data.h" + $File "$SRCDIR\game\shared\momentum\mom_entity_run_data.cpp" } $Folder "JSON Parser" diff --git a/mp/src/game/shared/momentum/mom_entity_run_data.cpp b/mp/src/game/shared/momentum/mom_entity_run_data.cpp new file mode 100644 index 0000000000..c76a34a56d --- /dev/null +++ b/mp/src/game/shared/momentum/mom_entity_run_data.cpp @@ -0,0 +1,45 @@ +#include "cbase.h" +#include "mom_entity_run_data.h" + +#include "tier0/memdbgon.h" + + +#ifdef GAME_DLL +BEGIN_SEND_TABLE_NOBASE(CMOMRunEntityData, DT_MOM_RunEntData) +SendPropBool(SENDINFO(m_bAutoBhop)), +SendPropInt(SENDINFO(m_iSuccessiveBhops)), +SendPropFloat(SENDINFO(m_flStrafeSync)), +SendPropFloat(SENDINFO(m_flStrafeSync2)), +SendPropFloat(SENDINFO(m_flLastJumpVel)), +SendPropInt(SENDINFO(m_iRunFlags)), +SendPropBool(SENDINFO(m_bIsInZone)), +SendPropInt(SENDINFO(m_iCurrentZone)), +SendPropBool(SENDINFO(m_bMapFinished)), +END_SEND_TABLE() + +#elif defined CLIENT_DLL +BEGIN_RECV_TABLE_NOBASE(C_MOMRunEntityData, DT_MOM_RunEntData) +RecvPropBool(RECVINFO(m_bAutoBhop)), +RecvPropInt(RECVINFO(m_iSuccessiveBhops)), +RecvPropFloat(RECVINFO(m_flStrafeSync)), +RecvPropFloat(RECVINFO(m_flStrafeSync2)), +RecvPropFloat(RECVINFO(m_flLastJumpVel)), +RecvPropInt(RECVINFO(m_iRunFlags)), +RecvPropBool(RECVINFO(m_bIsInZone)), +RecvPropInt(RECVINFO(m_iCurrentZone)), +RecvPropBool(RECVINFO(m_bMapFinished)), +END_RECV_TABLE() +#endif + +CMOMRunEntityData::CMOMRunEntityData() +{ + m_bAutoBhop = false; + m_iSuccessiveBhops = 0; + m_flStrafeSync = 0.0f; + m_flStrafeSync2 = 0.0f; + m_flLastJumpVel = 0.0f; + m_iRunFlags = 0; + m_bIsInZone = false; + m_iCurrentZone = 0; + m_bMapFinished = false; +} \ No newline at end of file diff --git a/mp/src/game/shared/momentum/mom_entity_run_data.h b/mp/src/game/shared/momentum/mom_entity_run_data.h new file mode 100644 index 0000000000..86aee29805 --- /dev/null +++ b/mp/src/game/shared/momentum/mom_entity_run_data.h @@ -0,0 +1,49 @@ +#pragma once + +#include "cbase.h" + +//This class handles networking all of the overlap between players and +//replay files. From an OOP standpoint, this is more efficient than having +//two classes network the same variables. + +#ifdef CLIENT_DLL +#define CMOMRunEntityData C_MOMRunEntityData +EXTERN_RECV_TABLE(DT_MOM_RunEntData); +#endif + +class CMOMRunEntityData +{ + DECLARE_CLASS_NOBASE(CMOMRunEntityData); + DECLARE_EMBEDDED_NETWORKVAR(); +public: + + CMOMRunEntityData(); + +#ifdef GAME_DLL + //DECLARE_SERVERCLASS(); + + + CNetworkVar(bool, m_bAutoBhop);// Is the player using auto bhop? + CNetworkVar(int, m_iSuccessiveBhops); //How many successive bhops this player has + CNetworkVar(float, m_flStrafeSync); //eyeangle based, perfect strafes / total strafes + CNetworkVar(float, m_flStrafeSync2); //acceleration based, strafes speed gained / total strafes + CNetworkVar(float, m_flLastJumpVel); //Last jump velocity of the player + CNetworkVar(int, m_iRunFlags);//The run flags (W only/HSW/Scroll etc) of the player + CNetworkVar(bool, m_bIsInZone);//This is true if the player is in a CTriggerTimerStage zone + CNetworkVar(bool, m_bMapFinished);//Did the player finish the map? + CNetworkVar(int, m_iCurrentZone);//Current stage/checkpoint the player is on + + +#elif defined CLIENT_DLL + //DECLARE_CLIENTCLASS(); + + bool m_bAutoBhop, m_bIsInZone, m_bMapFinished; + float m_flStrafeSync, m_flStrafeSync2, m_flLastJumpVel; + int m_iSuccessiveBhops, m_iRunFlags, m_iCurrentZone; +#endif + +}; + +#ifdef GAME_DLL +EXTERN_SEND_TABLE(DT_MOM_RunEntData); +#endif \ No newline at end of file diff --git a/mp/src/game/shared/momentum/mom_player_shared.h b/mp/src/game/shared/momentum/mom_player_shared.h index e6e4cca6c7..48e05794ba 100644 --- a/mp/src/game/shared/momentum/mom_player_shared.h +++ b/mp/src/game/shared/momentum/mom_player_shared.h @@ -17,7 +17,7 @@ inline CMomentumPlayer *ToCMOMPlayer(CBaseEntity *pEntity) { if (!pEntity || !pEntity->IsPlayer()) - return NULL; + return nullptr; return dynamic_cast(pEntity); } From 10ab15c26f7403155a5f94408d0c5c6b45d52111 Mon Sep 17 00:00:00 2001 From: Nick K Date: Fri, 20 May 2016 07:23:10 -0400 Subject: [PATCH 036/101] Replay entities activate triggers now TODO: Make the triggers set stuff on the replay --- mp/src/game/client/momentum/ui/hud_timer.cpp | 2 +- mp/src/game/server/momentum/mom_replay_entity.cpp | 7 ++++++- mp/src/game/server/momentum/mom_triggers.cpp | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/mp/src/game/client/momentum/ui/hud_timer.cpp b/mp/src/game/client/momentum/ui/hud_timer.cpp index e75ddd2821..b833b4ea4d 100644 --- a/mp/src/game/client/momentum/ui/hud_timer.cpp +++ b/mp/src/game/client/momentum/ui/hud_timer.cpp @@ -55,7 +55,7 @@ class C_Timer : public CHudElement, public Panel void MsgFunc_Timer_Checkpoint(bf_read &msg); float GetCurrentTime(); bool m_bIsRunning; - bool m_bTimerRan; + bool m_bTimerRan;//MOM_TODO: What is this used for? int m_iStartTick; protected: diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 19309dfbe0..7cb9b8ce50 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -62,8 +62,13 @@ void CMomentumReplayGhostEntity::Spawn(void) RemoveEffects(EF_NODRAW); SetRenderMode(kRenderTransColor); SetRenderColor(m_ghostColor.r(), m_ghostColor.g(), m_ghostColor.b(), 75); - SetMoveType(MOVETYPE_NOCLIP); + //~~~The magic combo~~~ (collides with triggers, not with players) + ClearSolidFlags(); + SetCollisionGroup(COLLISION_GROUP_DEBRIS_TRIGGER); + SetMoveType(MOVETYPE_STEP); + SetSolid(SOLID_BBOX); RemoveSolidFlags(FSOLID_NOT_SOLID); + SetModel(GHOST_MODEL); SetBodygroup(1, mom_replay_ghost_bodygroup.GetInt()); } diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index bc63bbca72..d1adec65cb 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -110,7 +110,7 @@ END_DATADESC() void CTriggerTimerStart::EndTouch(CBaseEntity *pOther) { - if (pOther->IsPlayer()) + if (pOther->IsPlayer())//MOM_TODO: pOther->IsReplay or dynamic cast idk I'm tired { CMomentumPlayer *pPlayer = ToCMOMPlayer(pOther); @@ -151,6 +151,7 @@ void CTriggerTimerStart::EndTouch(CBaseEntity *pOther) void CTriggerTimerStart::StartTouch(CBaseEntity *pOther) { + //MOM_TODO: Set replay entity stuff too g_Timer->SetStartTrigger(this); CMomentumPlayer *pPlayer = ToCMOMPlayer(pOther); if (pPlayer) From d6da87edf8e43100dc3eb9cb4525e73a24c887b5 Mon Sep 17 00:00:00 2001 From: RSTFS Date: Fri, 20 May 2016 20:33:53 -0400 Subject: [PATCH 037/101] Don't do stat stuff in the start zone, especially sync. --- mp/src/game/server/momentum/mom_player.cpp | 62 +++++++++++----------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/mp/src/game/server/momentum/mom_player.cpp b/mp/src/game/server/momentum/mom_player.cpp index 392c9afa8a..7613ecf651 100644 --- a/mp/src/game/server/momentum/mom_player.cpp +++ b/mp/src/game/server/momentum/mom_player.cpp @@ -298,44 +298,42 @@ void CMomentumPlayer::UpdateRunStats() m_PlayerRunStats.m_flStageVelocityMax[currentStage][1] = velocity2D; // ---------- - // --- STAGE ENTER VELOCITY --- - } - // ---- STRAFE SYNC ----- - float SyncVelocity = GetLocalVelocity().Length2DSqr(); //we always want HVEL for checking velocity sync - if (!(GetFlags() & (FL_ONGROUND | FL_INWATER)) && GetMoveType() != MOVETYPE_LADDER) - { - if (EyeAngles().y > m_qangLastAngle.y) //player turned left + // ---- STRAFE SYNC ----- + float SyncVelocity = GetLocalVelocity().Length2DSqr(); //we always want HVEL for checking velocity sync + if (!(GetFlags() & (FL_ONGROUND | FL_INWATER)) && GetMoveType() != MOVETYPE_LADDER) { - m_nStrafeTicks++; - if ((m_nButtons & IN_MOVELEFT) && !(m_nButtons & IN_MOVERIGHT)) - m_nPerfectSyncTicks++; - if (SyncVelocity > m_flLastSyncVelocity) - m_nAccelTicks++; + if (EyeAngles().y > m_qangLastAngle.y) //player turned left + { + m_nStrafeTicks++; + if ((m_nButtons & IN_MOVELEFT) && !(m_nButtons & IN_MOVERIGHT)) + m_nPerfectSyncTicks++; + if (SyncVelocity > m_flLastSyncVelocity) + m_nAccelTicks++; + } + else if (EyeAngles().y < m_qangLastAngle.y) //player turned right + { + m_nStrafeTicks++; + if ((m_nButtons & IN_MOVERIGHT) && !(m_nButtons & IN_MOVELEFT)) + m_nPerfectSyncTicks++; + if (SyncVelocity > m_flLastSyncVelocity) + m_nAccelTicks++; + } } - else if (EyeAngles().y < m_qangLastAngle.y) //player turned right + if (m_nStrafeTicks && m_nAccelTicks && m_nPerfectSyncTicks) { - m_nStrafeTicks++; - if ((m_nButtons & IN_MOVERIGHT) && !(m_nButtons & IN_MOVELEFT)) - m_nPerfectSyncTicks++; - if (SyncVelocity > m_flLastSyncVelocity) - m_nAccelTicks++; + m_RunData.m_flStrafeSync = (float(m_nPerfectSyncTicks) / float(m_nStrafeTicks)) * 100.0f; // ticks strafing perfectly / ticks strafing + m_RunData.m_flStrafeSync2 = (float(m_nAccelTicks) / float(m_nStrafeTicks)) * 100.0f; // ticks gaining speed / ticks strafing } - } - if (m_nStrafeTicks && m_nAccelTicks && m_nPerfectSyncTicks) - { - m_RunData.m_flStrafeSync = (float(m_nPerfectSyncTicks) / float(m_nStrafeTicks)) * 100.0f; // ticks strafing perfectly / ticks strafing - m_RunData.m_flStrafeSync2 = (float(m_nAccelTicks) / float(m_nStrafeTicks)) * 100.0f; // ticks gaining speed / ticks strafing - } - // ---------- - + // ---------- - m_qangLastAngle = EyeAngles(); - m_flLastSyncVelocity = SyncVelocity; - //this might be used in a later update - //m_flLastVelocity = velocity; + m_qangLastAngle = EyeAngles(); + m_flLastSyncVelocity = SyncVelocity; + //this might be used in a later update + //m_flLastVelocity = velocity; - m_bPrevTimerRunning = g_Timer->IsRunning(); - m_nPrevButtons = m_nButtons; + m_bPrevTimerRunning = g_Timer->IsRunning(); + m_nPrevButtons = m_nButtons; + } if (playerMoveEvent) { From 9e0d867442beb307e5c56d6531040fa798bb9f56 Mon Sep 17 00:00:00 2001 From: Nick K Date: Sat, 21 May 2016 02:59:46 -0400 Subject: [PATCH 038/101] Fix interpolation for ghost replay --- mp/src/game/client/momentum/c_mom_replay_entity.h | 5 +++++ mp/src/game/server/momentum/mom_replay_entity.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/mp/src/game/client/momentum/c_mom_replay_entity.h b/mp/src/game/client/momentum/c_mom_replay_entity.h index 4441bfb0e0..4eef08db1c 100644 --- a/mp/src/game/client/momentum/c_mom_replay_entity.h +++ b/mp/src/game/client/momentum/c_mom_replay_entity.h @@ -16,4 +16,9 @@ class C_MomentumReplayGhostEntity : public C_BaseAnimating int m_nReplayButtons; int m_iTotalStrafes; + bool ShouldInterpolate() override + { + return true; + } + }; \ No newline at end of file diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 7cb9b8ce50..a11f122565 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -65,7 +65,7 @@ void CMomentumReplayGhostEntity::Spawn(void) //~~~The magic combo~~~ (collides with triggers, not with players) ClearSolidFlags(); SetCollisionGroup(COLLISION_GROUP_DEBRIS_TRIGGER); - SetMoveType(MOVETYPE_STEP); + SetMoveType(MOVETYPE_STEP); SetSolid(SOLID_BBOX); RemoveSolidFlags(FSOLID_NOT_SOLID); From 16960ad925d9c839899e86389dbd925b85cc40ed Mon Sep 17 00:00:00 2001 From: Nick K Date: Sat, 21 May 2016 05:17:11 -0400 Subject: [PATCH 039/101] Hook ghost ent up to most hud elements Fixed: crash with ending map while recording Fixed: potential null in saving ghost replay file Fixed: Weird recording/playback bug upon spawning in start zone Cleaned up some code TODO: Fix the jump counter/detection for the ghost entity --- mp/src/game/client/momentum/c_mom_player.cpp | 3 +- mp/src/game/client/momentum/c_mom_player.h | 18 +-- .../client/momentum/c_mom_replay_entity.cpp | 1 + .../client/momentum/c_mom_replay_entity.h | 1 + .../game/client/momentum/ui/hud_keypress.cpp | 5 +- .../game/client/momentum/ui/hud_mapinfo.cpp | 17 ++- .../client/momentum/ui/hud_speedometer.cpp | 41 +++++-- .../client/momentum/ui/hud_strafesync.cpp | 88 ++++++-------- mp/src/game/server/momentum/mom_player.cpp | 1 - mp/src/game/server/momentum/mom_player.h | 11 +- mp/src/game/server/momentum/mom_replay.cpp | 19 ++- mp/src/game/server/momentum/mom_replay.h | 10 +- .../server/momentum/mom_replay_entity.cpp | 18 +-- .../game/server/momentum/mom_replay_entity.h | 1 + mp/src/game/server/momentum/mom_triggers.cpp | 110 +++++++++++++----- mp/src/game/server/momentum/server_events.cpp | 1 + mp/src/game/server/momentum/server_events.h | 7 +- .../shared/momentum/mom_entity_run_data.cpp | 3 + .../shared/momentum/mom_entity_run_data.h | 9 +- .../game/shared/momentum/mom_gamemovement.cpp | 8 +- 20 files changed, 231 insertions(+), 141 deletions(-) diff --git a/mp/src/game/client/momentum/c_mom_player.cpp b/mp/src/game/client/momentum/c_mom_player.cpp index e287f8667e..a4010df113 100644 --- a/mp/src/game/client/momentum/c_mom_player.cpp +++ b/mp/src/game/client/momentum/c_mom_player.cpp @@ -11,7 +11,6 @@ RecvPropBool(RECVINFO(m_bResumeZoom)), RecvPropInt(RECVINFO(m_iLastZoom)), RecvPropBool(RECVINFO(m_bDidPlayerBhop)), RecvPropInt(RECVINFO(m_iSuccessiveBhops)), -RecvPropFloat(RECVINFO(m_flLastJumpTime)), RecvPropDataTable(RECVINFO_DT(m_RunData), SPROP_PROXY_ALWAYS_YES, &REFERENCE_RECV_TABLE(DT_MOM_RunEntData)), END_RECV_TABLE() @@ -21,7 +20,7 @@ C_MomentumPlayer::C_MomentumPlayer() ConVarRef scissor("r_flashlightscissor"); scissor.SetValue("0"); m_RunData.m_bMapFinished = false; - m_flLastJumpTime = 0.0f; + m_RunData.m_flLastJumpTime = 0.0f; } C_MomentumPlayer::~C_MomentumPlayer() diff --git a/mp/src/game/client/momentum/c_mom_player.h b/mp/src/game/client/momentum/c_mom_player.h index 32bf6224e9..a0af0ea1c4 100644 --- a/mp/src/game/client/momentum/c_mom_player.h +++ b/mp/src/game/client/momentum/c_mom_player.h @@ -24,10 +24,17 @@ class C_MomentumPlayer : public C_BasePlayer bool CanGrabLadder(const Vector& pos, const Vector& normal); bool DidPlayerBhop() { return m_bDidPlayerBhop; } bool HasAutoBhop() { return m_RunData.m_bAutoBhop; } - void ResetStrafeSync(); - bool IsWatchingReplay() + //void ResetStrafeSync(); + //Returns true if the player is watching a replay (first person) + bool IsWatchingReplay() const { - return m_hObserverTarget.Get() && dynamic_cast(m_hObserverTarget.Get()); + return m_hObserverTarget.Get() && GetReplayEnt(); + } + + //Returns the replay entity that the player is watching (first person only) + C_MomentumReplayGhostEntity *GetReplayEnt() const + { + return dynamic_cast(m_hObserverTarget.Get()); } int m_iShotsFired; @@ -35,12 +42,9 @@ class C_MomentumPlayer : public C_BasePlayer bool m_bResumeZoom; int m_iLastZoom; bool m_bDidPlayerBhop; - float m_flLastJumpTime;//Used for the speedometer panel CMOMRunEntityData m_RunData; - //float m_flStrafeSync, m_flStrafeSync2; - //float m_flLastJumpVel; float m_flLastRunTime; @@ -84,8 +88,6 @@ class C_MomentumPlayer : public C_BasePlayer bool m_duckUntilOnGround; float m_flStamina; - - friend class CMomentumGameMovement; }; diff --git a/mp/src/game/client/momentum/c_mom_replay_entity.cpp b/mp/src/game/client/momentum/c_mom_replay_entity.cpp index 22b69e69e1..e1257f297e 100644 --- a/mp/src/game/client/momentum/c_mom_replay_entity.cpp +++ b/mp/src/game/client/momentum/c_mom_replay_entity.cpp @@ -8,6 +8,7 @@ IMPLEMENT_CLIENTCLASS_DT(C_MomentumReplayGhostEntity, DT_MOM_ReplayEnt, CMomentu //MOM_TODO: Network the rest of the variables that the ghost entity will be sending RecvPropInt(RECVINFO(m_nReplayButtons)), RecvPropInt(RECVINFO(m_iTotalStrafes)), +RecvPropInt(RECVINFO(m_iTotalJumps)), RecvPropDataTable(RECVINFO_DT(m_RunData), 0, &REFERENCE_RECV_TABLE(DT_MOM_RunEntData)) END_RECV_TABLE(); diff --git a/mp/src/game/client/momentum/c_mom_replay_entity.h b/mp/src/game/client/momentum/c_mom_replay_entity.h index 4eef08db1c..715fc979ff 100644 --- a/mp/src/game/client/momentum/c_mom_replay_entity.h +++ b/mp/src/game/client/momentum/c_mom_replay_entity.h @@ -15,6 +15,7 @@ class C_MomentumReplayGhostEntity : public C_BaseAnimating int m_nReplayButtons; int m_iTotalStrafes; + int m_iTotalJumps; bool ShouldInterpolate() override { diff --git a/mp/src/game/client/momentum/ui/hud_keypress.cpp b/mp/src/game/client/momentum/ui/hud_keypress.cpp index 5d4b648e93..8fa563df72 100644 --- a/mp/src/game/client/momentum/ui/hud_keypress.cpp +++ b/mp/src/game/client/momentum/ui/hud_keypress.cpp @@ -196,9 +196,10 @@ void CHudKeyPressDisplay::OnThink() m_bShouldDrawCounts = true; m_nButtons = pReplayEnt->m_nReplayButtons; m_nStrafes = pReplayEnt->m_iTotalStrafes; - m_nJumps = 0;//MOM_TODO: Calculate jumps + m_nJumps = pReplayEnt->m_iTotalJumps; } - } else + } + else { m_nButtons = ::input->GetButtonBits(1); if (g_MOMEventListener) diff --git a/mp/src/game/client/momentum/ui/hud_mapinfo.cpp b/mp/src/game/client/momentum/ui/hud_mapinfo.cpp index 1d631ff83e..2af1345dbf 100644 --- a/mp/src/game/client/momentum/ui/hud_mapinfo.cpp +++ b/mp/src/game/client/momentum/ui/hud_mapinfo.cpp @@ -102,9 +102,20 @@ void C_HudMapInfo::OnThink() C_MomentumPlayer *pLocal = ToCMOMPlayer(C_BasePlayer::GetLocalPlayer()); if (pLocal && g_MOMEventListener) { - m_iStageCurrent = pLocal->m_RunData.m_iCurrentZone; - m_bPlayerInZone = pLocal->m_RunData.m_bIsInZone; - m_bMapFinished = pLocal->m_RunData.m_bMapFinished; + C_MomentumReplayGhostEntity *pGhost = pLocal->GetReplayEnt(); + if (pGhost) + { + m_iStageCurrent = pGhost->m_RunData.m_iCurrentZone; + m_bPlayerInZone = pGhost->m_RunData.m_bIsInZone; + m_bMapFinished = pGhost->m_RunData.m_bMapFinished; + } + else + { + m_iStageCurrent = pLocal->m_RunData.m_iCurrentZone; + m_bPlayerInZone = pLocal->m_RunData.m_bIsInZone; + m_bMapFinished = pLocal->m_RunData.m_bMapFinished; + } + m_iStageCount = g_MOMEventListener->m_iMapCheckpointCount; m_bMapLinear = g_MOMEventListener->m_bMapIsLinear; } diff --git a/mp/src/game/client/momentum/ui/hud_speedometer.cpp b/mp/src/game/client/momentum/ui/hud_speedometer.cpp index d2da24049d..b929d67ae6 100644 --- a/mp/src/game/client/momentum/ui/hud_speedometer.cpp +++ b/mp/src/game/client/momentum/ui/hud_speedometer.cpp @@ -18,6 +18,7 @@ #include "mom_event_listener.h" #include "mom_player_shared.h" +#include "c_mom_replay_entity.h" #include "momentum/util/mom_util.h" #include "vphysics_interface.h" #include @@ -152,12 +153,23 @@ void CHudSpeedMeter::OnThink() C_MomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); if (pPlayer) { - //MOM_TODO: Update to read replay ent's stuff + //This will be null if the player is not watching a replay first person + C_MomentumReplayGhostEntity *pGhost = pPlayer->GetReplayEnt(); + + //Note: Velocity is also set to the player when watching first person velocity = pPlayer->GetLocalVelocity(); - float lastJumpVel = pPlayer->m_RunData.m_flLastJumpVel; + + //The last jump velocity + float lastJumpVel = (pGhost ? pGhost->m_RunData.m_flLastJumpVel : + pPlayer->m_RunData.m_flLastJumpVel); + + //The last jump time is also important if the player is watching a replay + float lastJumpTime = (pGhost ? pGhost->m_RunData.m_flLastJumpTime : + pPlayer->m_RunData.m_flLastJumpTime); + int velType = mom_speedometer_hvel.GetBool(); // 1 is horizontal velocity - if (gpGlobals->curtime - pPlayer->m_flLastJumpTime > 5.0f) + if (gpGlobals->curtime - lastJumpTime > 5.0f) { if (!m_bRanFadeOutJumpSpeed) m_bRanFadeOutJumpSpeed = @@ -215,20 +227,29 @@ void CHudSpeedMeter::OnThink() m_flLastVelocity = vel; m_flNextColorizeCheck = gpGlobals->curtime + MOM_COLORIZATION_CHECK_FREQUENCY; } - // reset last jump velocity when we restart a run by entering the start zone - if (pPlayer->m_RunData.m_bIsInZone && pPlayer->m_RunData.m_iCurrentZone == 1) - m_flLastJumpVelocity = 0; + // reset last jump velocity when we (or a ghost ent) restart a run by entering the start zone + if (pGhost) + { + if (pGhost->m_RunData.m_bIsInZone && pGhost->m_RunData.m_iCurrentZone == 1) + m_flLastJumpVelocity = 0; + } + else + { + if (pPlayer->m_RunData.m_bIsInZone && pPlayer->m_RunData.m_iCurrentZone == 1) + m_flLastJumpVelocity = 0; + } + - if (pPlayer->m_RunData.m_flLastJumpVel == 0) + if (lastJumpVel == 0) { m_SecondaryValueColor = normalColor; } - else if (m_flLastJumpVelocity != pPlayer->m_RunData.m_flLastJumpVel) + else if (m_flLastJumpVelocity != lastJumpVel) { m_SecondaryValueColor = - mom_UTIL->GetColorFromVariation(abs(pPlayer->m_RunData.m_flLastJumpVel) - abs(m_flLastJumpVelocity), 0.0f, + mom_UTIL->GetColorFromVariation(abs(lastJumpVel) - abs(m_flLastJumpVelocity), 0.0f, normalColor, increaseColor, decreaseColor); - m_flLastJumpVelocity = pPlayer->m_RunData.m_flLastJumpVel; + m_flLastJumpVelocity = lastJumpVel; } } else diff --git a/mp/src/game/client/momentum/ui/hud_strafesync.cpp b/mp/src/game/client/momentum/ui/hud_strafesync.cpp index 9b48e94b0a..71d5efa466 100644 --- a/mp/src/game/client/momentum/ui/hud_strafesync.cpp +++ b/mp/src/game/client/momentum/ui/hud_strafesync.cpp @@ -1,13 +1,13 @@ #include "cbase.h" +#include "c_mom_replay_entity.h" #include "hud_fillablebar.h" #include "hud_numericdisplay.h" #include "hudelement.h" #include "iclientmode.h" +#include "mom_event_listener.h" #include "mom_player_shared.h" #include "momentum/util/mom_util.h" -#include "c_mom_replay_entity.h" #include "vphysics_interface.h" -#include "mom_event_listener.h" #include #include "tier0/memdbgon.h" @@ -17,8 +17,9 @@ using namespace vgui; static ConVar strafesync_draw("mom_strafesync_draw", "1", FCVAR_CLIENTDLL | FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE, "Toggles displaying the strafesync data.\n", true, 0, true, 1); -static ConVar strafesync_drawbar("mom_strafesync_drawbar", "1", FCVAR_CLIENTDLL | FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE, - "Toggles displaying the visual strafesync bar.\n", true, 0, true, 1); +static ConVar strafesync_drawbar("mom_strafesync_drawbar", "1", + FCVAR_CLIENTDLL | FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE, + "Toggles displaying the visual strafesync bar.\n", true, 0, true, 1); static ConVar strafesync_type( "mom_strafesync_type", "1", FCVAR_CLIENTDLL | FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_ARCHIVE, @@ -44,8 +45,8 @@ class CHudStrafeSyncDisplay : public CHudElement, public CHudNumericDisplay bool ShouldDraw() override { C_MomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); - return pPlayer && strafesync_draw.GetBool() && CHudElement::ShouldDraw() && g_MOMEventListener - && g_MOMEventListener->m_bTimerIsRunning; + return pPlayer && strafesync_draw.GetBool() && CHudElement::ShouldDraw() && g_MOMEventListener && + g_MOMEventListener->m_bTimerIsRunning; } void Reset() override @@ -56,7 +57,7 @@ class CHudStrafeSyncDisplay : public CHudElement, public CHudNumericDisplay m_lastColor = normalColor; m_currentColor = normalColor; } - void ApplySchemeSettings(IScheme *pScheme) + void ApplySchemeSettings(IScheme *pScheme) override { Panel::ApplySchemeSettings(pScheme); SetFgColor(GetSchemeColor("White", pScheme)); @@ -79,7 +80,8 @@ class CHudStrafeSyncDisplay : public CHudElement, public CHudNumericDisplay Color normalColor, increaseColor, decreaseColor; float digit_xpos_initial; -protected: + + protected: CPanelAnimationVar(Color, _bgColor, "BgColor", "Blank"); }; @@ -93,21 +95,20 @@ CHudStrafeSyncDisplay::CHudStrafeSyncDisplay(const char *pElementName) void CHudStrafeSyncDisplay::OnThink() { C_MomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); - if (!pPlayer) return; + if (!pPlayer) + return; m_localStrafeSync = 0; - if (pPlayer->IsWatchingReplay()) + C_MomentumReplayGhostEntity *pReplayEnt = pPlayer->GetReplayEnt(); + if (pReplayEnt) { - C_MomentumReplayGhostEntity *pReplayEnt = dynamic_cast(pPlayer->GetObserverTarget()); - if (pReplayEnt) - { - if (strafesync_type.GetInt() == 1) // sync1 - m_localStrafeSync = pReplayEnt->m_RunData.m_flStrafeSync; - else if (strafesync_type.GetInt() == 2) // sync2 - m_localStrafeSync = pReplayEnt->m_RunData.m_flStrafeSync2; - } - } else + if (strafesync_type.GetInt() == 1) // sync1 + m_localStrafeSync = pReplayEnt->m_RunData.m_flStrafeSync; + else if (strafesync_type.GetInt() == 2) // sync2 + m_localStrafeSync = pReplayEnt->m_RunData.m_flStrafeSync2; + } + else { if (strafesync_type.GetInt() == 1) // sync1 m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync; @@ -115,14 +116,6 @@ void CHudStrafeSyncDisplay::OnThink() m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync2; } - - //if (strafesync_type.GetInt() == 1) // sync1 - // m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync; - //else if (strafesync_type.GetInt() == 2) // sync2 - // m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync2; - //else - // m_localStrafeSync = 0; - float clampedStrafeSync = clamp(m_localStrafeSync, 0, 100); switch (strafesync_colorize.GetInt()) @@ -131,8 +124,9 @@ void CHudStrafeSyncDisplay::OnThink() if (m_flNextColorizeCheck <= gpGlobals->curtime) { m_flLastStrafeSync != 0 - ? m_currentColor = mom_UTIL->GetColorFromVariation(m_localStrafeSync - m_flLastStrafeSync, SYNC_COLORIZE_DEADZONE, - normalColor, increaseColor, decreaseColor) + ? m_currentColor = + mom_UTIL->GetColorFromVariation(m_localStrafeSync - m_flLastStrafeSync, SYNC_COLORIZE_DEADZONE, + normalColor, increaseColor, decreaseColor) : m_currentColor = normalColor; m_lastColor = m_currentColor; @@ -171,7 +165,7 @@ void CHudStrafeSyncDisplay::OnThink() if (clampedStrafeSync == 0) { - digit_xpos = GetWide() / 2 - UTIL_ComputeStringWidth(m_hNumberFont,"0") / 2; + digit_xpos = GetWide() / 2 - UTIL_ComputeStringWidth(m_hNumberFont, "0") / 2; } else { @@ -192,7 +186,7 @@ void CHudStrafeSyncDisplay::Paint() { SetLabelText(L"Sync"); } - text_xpos = GetWide() / 2 - UTIL_ComputeStringWidth(m_hTextFont, m_LabelText) / 2; + text_xpos = GetWide() / 2 - UTIL_ComputeStringWidth(m_hTextFont, m_LabelText) / 2; } ////////////////////////////////////////// // CHudStrafeSyncBar // @@ -207,8 +201,8 @@ class CHudStrafeSyncBar : public CHudFillableBar bool ShouldDraw() override { C_MomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); - return (pPlayer && strafesync_drawbar.GetBool() && CHudElement::ShouldDraw() && g_MOMEventListener - && g_MOMEventListener->m_bTimerIsRunning); + return (pPlayer && strafesync_drawbar.GetBool() && CHudElement::ShouldDraw() && g_MOMEventListener && + g_MOMEventListener->m_bTimerIsRunning); } void Reset() override @@ -238,7 +232,6 @@ class CHudStrafeSyncBar : public CHudFillableBar Color m_lastColor; Color m_currentColor; Color normalColor, increaseColor, decreaseColor; - }; DECLARE_HUDELEMENT(CHudStrafeSyncBar); @@ -258,16 +251,13 @@ void CHudStrafeSyncBar::OnThink() if (pPlayer == nullptr) return; - if (pPlayer->IsWatchingReplay()) + C_MomentumReplayGhostEntity *pReplayEnt = pPlayer->GetReplayEnt(); + if (pReplayEnt) { - C_MomentumReplayGhostEntity *pReplayEnt = dynamic_cast(pPlayer->GetObserverTarget()); - if (pReplayEnt) - { - if (strafesync_type.GetInt() == 1) // sync1 - m_localStrafeSync = pReplayEnt->m_RunData.m_flStrafeSync; - else if (strafesync_type.GetInt() == 2) // sync2 - m_localStrafeSync = pReplayEnt->m_RunData.m_flStrafeSync2; - } + if (strafesync_type.GetInt() == 1) // sync1 + m_localStrafeSync = pReplayEnt->m_RunData.m_flStrafeSync; + else if (strafesync_type.GetInt() == 2) // sync2 + m_localStrafeSync = pReplayEnt->m_RunData.m_flStrafeSync2; } else { @@ -277,19 +267,15 @@ void CHudStrafeSyncBar::OnThink() m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync2; } - //if (strafesync_type.GetInt() == 1) // sync1 - // m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync; - //else if (strafesync_type.GetInt() == 2) // sync2 - // m_localStrafeSync = pPlayer->m_RunData.m_flStrafeSync2; - switch (strafesync_colorize.GetInt()) { case 1: if (m_flNextColorizeCheck <= gpGlobals->curtime) { - m_flLastStrafeSync != 0 - ? m_currentColor = mom_UTIL->GetColorFromVariation(m_localStrafeSync - m_flLastStrafeSync, SYNC_COLORIZE_DEADZONE, - normalColor, increaseColor, decreaseColor) + m_flLastStrafeSync != 0 + ? m_currentColor = + mom_UTIL->GetColorFromVariation(m_localStrafeSync - m_flLastStrafeSync, SYNC_COLORIZE_DEADZONE, + normalColor, increaseColor, decreaseColor) : m_currentColor = normalColor; m_lastColor = m_currentColor; diff --git a/mp/src/game/server/momentum/mom_player.cpp b/mp/src/game/server/momentum/mom_player.cpp index 7613ecf651..c2caffda22 100644 --- a/mp/src/game/server/momentum/mom_player.cpp +++ b/mp/src/game/server/momentum/mom_player.cpp @@ -15,7 +15,6 @@ SendPropBool(SENDINFO(m_bResumeZoom)), SendPropInt(SENDINFO(m_iLastZoom)), SendPropBool(SENDINFO(m_bDidPlayerBhop)), SendPropInt(SENDINFO(m_iSuccessiveBhops)), -SendPropFloat(SENDINFO(m_flLastJumpTime)), SendPropDataTable(SENDINFO_DT(m_RunData), &REFERENCE_SEND_TABLE(DT_MOM_RunEntData)), END_SEND_TABLE() diff --git a/mp/src/game/server/momentum/mom_player.h b/mp/src/game/server/momentum/mom_player.h index 1a69239fb1..7de01d37ab 100644 --- a/mp/src/game/server/momentum/mom_player.h +++ b/mp/src/game/server/momentum/mom_player.h @@ -90,8 +90,6 @@ class CMomentumPlayer : public CBasePlayer CNetworkVar(bool, m_bDidPlayerBhop);// Did the player bunnyhop successfully? CNetworkVar(int, m_iSuccessiveBhops); //How many successive bhops this player has - //CNetworkVar(bool, m_bIsWatchingReplay); - CNetworkVar(float, m_flLastJumpTime);//The last time that the player jumped CNetworkVarEmbedded(CMOMRunEntityData, m_RunData);//Current run data, used for hud elements @@ -112,9 +110,14 @@ class CMomentumPlayer : public CBasePlayer int GetLastBlock() { return m_iLastBlock; } float GetPunishTime() { return m_flPunishTime; } - bool IsWatchingReplay() + bool IsWatchingReplay() const { - return m_hObserverTarget.Get() && dynamic_cast(m_hObserverTarget.Get()); + return m_hObserverTarget.Get() && GetReplayEnt(); + } + + CMomentumReplayGhostEntity *GetReplayEnt() const + { + return dynamic_cast(m_hObserverTarget.Get()); } //Run Stats diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 28fe2711c5..3e7cf0f316 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -35,7 +35,7 @@ void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway, CMomentumPlayer *pMOMPlayer = ToCMOMPlayer(pPlayer); char newRecordingName[MAX_PATH], newRecordingPath[MAX_PATH], runTime[BUFSIZETIME]; mom_UTIL->FormatTime(g_Timer->GetLastRunTime(), runTime); - Q_snprintf(newRecordingName, MAX_PATH, "%s_%s_%s.momrec", pMOMPlayer->GetPlayerName(), gpGlobals->mapname.ToCStr(), runTime); + Q_snprintf(newRecordingName, MAX_PATH, "%s_%s_%s.momrec", (pMOMPlayer ? pMOMPlayer->GetPlayerName() : "Unnamed"), gpGlobals->mapname.ToCStr(), runTime); V_ComposeFileName(RECORDING_PATH, newRecordingName, newRecordingPath, MAX_PATH); //V_ComposeFileName calls all relevent filename functions for us! THANKS GABEN V_FixSlashes(RECORDING_PATH); @@ -147,20 +147,18 @@ bool CMomentumReplaySystem::LoadRun(const char* filename) if (header == nullptr) { return false; } - else + + m_loadedHeader = *header; + while (!filesystem->EndOfFile(m_fhFileHandle)) { - m_loadedHeader = *header; - while (!filesystem->EndOfFile(m_fhFileHandle)) - { - replay_frame_t* frame = ReadSingleFrame(m_fhFileHandle, filename); - m_vecRunData.AddToTail(*frame); - } + replay_frame_t* frame = ReadSingleFrame(m_fhFileHandle, filename); + m_vecRunData.AddToTail(*frame); } filesystem->Close(m_fhFileHandle); return true; } - else - return false; + + return false; } void CMomentumReplaySystem::StartReplay(bool firstperson) { @@ -186,6 +184,7 @@ void CMomentumReplaySystem::DispatchTimerStateMessage(CBasePlayer *pPlayer, bool user.MakeReliable(); UserMessageBegin(user, "Timer_State"); WRITE_BOOL(started); + //MOM_TODO: This should be an offset # of ticks so the hud_timer can sync with the replay WRITE_LONG(gpGlobals->tickcount); MessageEnd(); } diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index 1ec52575b1..dccc410cb4 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -30,6 +30,13 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame } } + void LevelShutdownPostEntity() override + { + //Stop a recording if there is one while the level shuts down + if (m_bIsRecording) + StopRecording(nullptr, true, 0.0f); + } + void BeginRecording(CBasePlayer *pPlayer); void StopRecording(CBasePlayer *pPlayer, bool throwaway, bool delay); void WriteRecordingToFile(CUtlBuffer *buf); @@ -44,7 +51,8 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame CUtlVector m_vecRunData; //MOM_TODO: Handle the pPlayer pointer passed here or get rid of it - bool IsRecording(CBasePlayer *pPlayer) { return m_bIsRecording; } + bool IsRecording(CBasePlayer *pPlayer) const + { return m_bIsRecording; } replay_header_t m_loadedHeader; bool m_bIsWatchingReplay; diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index a11f122565..0e7fb5ea4e 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -22,6 +22,7 @@ IMPLEMENT_SERVERCLASS_ST(CMomentumReplayGhostEntity, DT_MOM_ReplayEnt) //MOM_TODO: Network other variables that the UI will need to reference SendPropInt(SENDINFO(m_nReplayButtons)), SendPropInt(SENDINFO(m_iTotalStrafes)), +SendPropInt(SENDINFO(m_iTotalJumps)), SendPropDataTable(SENDINFO_DT(m_RunData), &REFERENCE_SEND_TABLE(DT_MOM_RunEntData)), END_SEND_TABLE() @@ -57,8 +58,8 @@ void CMomentumReplayGhostEntity::Precache(void) //----------------------------------------------------------------------------- void CMomentumReplayGhostEntity::Spawn(void) { + Precache(); BaseClass::Spawn(); - Precache(); RemoveEffects(EF_NODRAW); SetRenderMode(kRenderTransColor); SetRenderColor(m_ghostColor.r(), m_ghostColor.g(), m_ghostColor.b(), 75); @@ -219,7 +220,7 @@ void CMomentumReplayGhostEntity::UpdateStats(Vector ghostVel) //calculate strafe sync based on replay ghost's movement, in order to update the player's HUD float SyncVelocity = ghostVel.Length2DSqr(); //we always want HVEL for checking velocity sync - if (GetGroundEntity() == NULL) + if (GetGroundEntity() == nullptr) { if (EyeAngles().y > m_qLastEyeAngle.y) //player turned left { @@ -245,9 +246,13 @@ void CMomentumReplayGhostEntity::UpdateStats(Vector ghostVel) } // --- JUMP AND STRAFE COUNTER --- - //MOM_TODO: GetGroundEntity is never not null (the replay ghost never "jumps") - //if (GetGroundEntity() != NULL && currentStep.m_nPlayerButtons & IN_JUMP) - // pPlayer->m_PlayerRunStats.m_iStageJumps[0]++; + //MOM_TODO: This needs to calculate better. It currently counts every other jump, and sometimes spams (player on ground for a while) + if (GetGroundEntity() != nullptr && currentStep.m_nPlayerButtons & IN_JUMP) + { + m_RunData.m_flLastJumpVel = GetLocalVelocity().Length2D(); + m_RunData.m_flLastJumpTime = gpGlobals->curtime; + m_iTotalJumps++; + } if ((currentStep.m_nPlayerButtons & IN_MOVELEFT && !(m_nOldReplayButtons & IN_MOVELEFT)) || (currentStep.m_nPlayerButtons & IN_MOVERIGHT && !(m_nOldReplayButtons & IN_MOVERIGHT)) ) @@ -270,8 +275,7 @@ void CMomentumReplayGhostEntity::SetGhostBodyGroup(int bodyGroup) { if (bodyGroup > sizeof(ghostModelBodyGroup) || bodyGroup < 0) { - Msg("Error: Could not set bodygroup!"); - return; + Warning("CMomentumReplayGhostEntity::SetGhostBodyGroup() Error: Could not set bodygroup!"); } else { diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index 975eaf1b49..ec43a88e52 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -56,6 +56,7 @@ class CMomentumReplayGhostEntity : public CBaseAnimating CNetworkVarEmbedded(CMOMRunEntityData, m_RunData); CNetworkVar(int, m_nReplayButtons); CNetworkVar(int, m_iTotalStrafes); + CNetworkVar(int, m_iTotalJumps); protected: void Think(void) override; diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index d1adec65cb..52287d7cd6 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -26,19 +26,18 @@ END_DATADESC() void CTriggerStage::StartTouch(CBaseEntity *pOther) { BaseClass::StartTouch(pOther); + int stageNum = GetStageNumber(); if (pOther->IsPlayer()) { - int stageNum = GetStageNumber(); CMomentumPlayer *pPlayer = ToCMOMPlayer(pOther); - if (pPlayer) - { - pPlayer->m_RunData.m_bIsInZone = true; - pPlayer->m_RunData.m_iCurrentZone = stageNum; - } - g_Timer->SetCurrentStage(this); IGameEvent *stageEvent = gameeventmanager->CreateEvent("stage_enter"); if (stageEvent && pPlayer) { + //Set the current stage to this + g_Timer->SetCurrentStage(this); + //Set player run data + pPlayer->m_RunData.m_bIsInZone = true; + pPlayer->m_RunData.m_iCurrentZone = stageNum; if (g_Timer->IsRunning()) { stageEvent->SetInt("stage_num", stageNum); @@ -69,32 +68,52 @@ void CTriggerStage::StartTouch(CBaseEntity *pOther) } } } + else + { + CMomentumReplayGhostEntity *pGhost = dynamic_cast(pOther); + if (pGhost) + { + pGhost->m_RunData.m_iCurrentZone = stageNum; + pGhost->m_RunData.m_bIsInZone = true; + } + } } void CTriggerStage::EndTouch(CBaseEntity *pOther) { BaseClass::EndTouch(pOther); int stageNum = this->GetStageNumber(); CMomentumPlayer *pPlayer = ToCMOMPlayer(pOther); - if (pPlayer && (stageNum == 1 || g_Timer->IsRunning()))//Timer won't be running if it's the start trigger - { - IGameEvent *stageEvent = gameeventmanager->CreateEvent("stage_exit"); - if (stageEvent) + if (pPlayer) + { + if (stageNum == 1 || g_Timer->IsRunning())//Timer won't be running if it's the start trigger { - //Status - pPlayer->m_RunData.m_bIsInZone = false; + IGameEvent *stageEvent = gameeventmanager->CreateEvent("stage_exit"); + if (stageEvent) + { + //Status + pPlayer->m_RunData.m_bIsInZone = false; - //Stage num - stageEvent->SetInt("stage_num", stageNum); + //Stage num + stageEvent->SetInt("stage_num", stageNum); - //3D VELOCITY - pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); - stageEvent->SetFloat("stage_enter_vel", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][0]); + //3D VELOCITY + pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); + stageEvent->SetFloat("stage_enter_vel", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][0]); - //2D VELOCITY - pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); - stageEvent->SetFloat("stage_enter_vel_2D", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][1]); + //2D VELOCITY + pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); + stageEvent->SetFloat("stage_enter_vel_2D", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][1]); - gameeventmanager->FireEvent(stageEvent); + gameeventmanager->FireEvent(stageEvent); + } + } + } + else + { + CMomentumReplayGhostEntity *pGhost = dynamic_cast(pOther); + if (pGhost) + { + pGhost->m_RunData.m_bIsInZone = false; } } } @@ -138,6 +157,17 @@ void CTriggerTimerStart::EndTouch(CBaseEntity *pOther) pPlayer->m_RunData.m_bIsInZone = false; pPlayer->m_RunData.m_bMapFinished = false; } + else + { + CMomentumReplayGhostEntity *pGhost = dynamic_cast(pOther); + if (pGhost) + { + pGhost->m_RunData.m_bIsInZone = false; + pGhost->m_RunData.m_bMapFinished = false; + //MOM_TODO: Make the spectator's timer start + //pGhost->SetStartTick(gpGlobals->tickcount) + } + } IGameEvent *movementCountsResetEvent = gameeventmanager->CreateEvent("keypress"); if (movementCountsResetEvent) @@ -176,6 +206,15 @@ void CTriggerTimerStart::StartTouch(CBaseEntity *pOther) g_ReplaySystem->BeginRecording(pPlayer); } } + else + { + CMomentumReplayGhostEntity *pGhost = dynamic_cast(pOther); + if (pGhost) + { + pGhost->m_RunData.m_bIsInZone = true; + pGhost->m_RunData.m_bMapFinished = false; + } + } // start thinking SetNextThink(gpGlobals->curtime); BaseClass::StartTouch(pOther); @@ -232,17 +271,16 @@ LINK_ENTITY_TO_CLASS(trigger_momentum_timer_stop, CTriggerTimerStop); void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) { - CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); + CMomentumPlayer *pPlayer = ToCMOMPlayer(pOther); IGameEvent *timerStopEvent = gameeventmanager->CreateEvent("timer_stopped"); IGameEvent *stageEvent = gameeventmanager->CreateEvent("stage_enter"); - g_Timer->SetEndTrigger(this); - // If timer is already stopped, there's nothing to stop (No run state effect to play) if (pPlayer) { - if (g_Timer->IsRunning()) + g_Timer->SetEndTrigger(this); + if (g_Timer->IsRunning() && !pPlayer->IsWatchingReplay()) { //send run stats via GameEventManager if (timerStopEvent) @@ -306,6 +344,17 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) pPlayer->m_RunData.m_bIsInZone = true; } + else + { + CMomentumReplayGhostEntity *pGhost = dynamic_cast(pOther); + if (pGhost) + { + pGhost->m_RunData.m_bMapFinished = true; + pGhost->m_RunData.m_bIsInZone = true; + //MOM_TODO: pGhost->EndRunHud(); //sends a hud timer state message to each spectator + //MOM_TODO: Maybe also play effects if the player is racing against us and lost? + } + } BaseClass::StartTouch(pOther); } void CTriggerTimerStop::EndTouch(CBaseEntity* pOther) @@ -316,6 +365,15 @@ void CTriggerTimerStop::EndTouch(CBaseEntity* pOther) pMomPlayer->m_RunData.m_bMapFinished = false;//Close the hud_mapfinished panel pMomPlayer->m_RunData.m_bIsInZone = false;//Update status } + else + { + CMomentumReplayGhostEntity *pGhost = dynamic_cast(pOther); + if (pGhost) + { + pGhost->m_RunData.m_bMapFinished = false; + pGhost->m_RunData.m_bIsInZone = false; + } + } BaseClass::EndTouch(pOther); } //---------------------------------------------------------------------------------------------- diff --git a/mp/src/game/server/momentum/server_events.cpp b/mp/src/game/server/momentum/server_events.cpp index 16cf912636..4e7eb5503e 100644 --- a/mp/src/game/server/momentum/server_events.cpp +++ b/mp/src/game/server/momentum/server_events.cpp @@ -29,6 +29,7 @@ namespace Momentum // This will only happen if the user didn't use the map selector to start a map //set gamemode depending on map name + //MOM_TODO: This needs to read map entity/momfile data and set accordingly if (gm.GetInt() == MOMGM_UNKNOWN) { if (!Q_strnicmp(pMapName, "surf_", strlen("surf_"))) diff --git a/mp/src/game/server/momentum/server_events.h b/mp/src/game/server/momentum/server_events.h index 9ade5986a5..a7eb0d796d 100644 --- a/mp/src/game/server/momentum/server_events.h +++ b/mp/src/game/server/momentum/server_events.h @@ -7,12 +7,7 @@ namespace Momentum { void OnServerDLLInit(); -void OnMapStart(const char *pMapName); -void OnMapEnd(const char *pMapName); -void OnGameFrameStart(); void GameInit(); -//void OnGameFrameEnd(); - } // namespace Momentum -#endif // SERVER_EVENTS_H +#endif // SERVER_EVENTS_H \ No newline at end of file diff --git a/mp/src/game/shared/momentum/mom_entity_run_data.cpp b/mp/src/game/shared/momentum/mom_entity_run_data.cpp index c76a34a56d..9fff819760 100644 --- a/mp/src/game/shared/momentum/mom_entity_run_data.cpp +++ b/mp/src/game/shared/momentum/mom_entity_run_data.cpp @@ -11,6 +11,7 @@ SendPropInt(SENDINFO(m_iSuccessiveBhops)), SendPropFloat(SENDINFO(m_flStrafeSync)), SendPropFloat(SENDINFO(m_flStrafeSync2)), SendPropFloat(SENDINFO(m_flLastJumpVel)), +SendPropFloat(SENDINFO(m_flLastJumpTime)), SendPropInt(SENDINFO(m_iRunFlags)), SendPropBool(SENDINFO(m_bIsInZone)), SendPropInt(SENDINFO(m_iCurrentZone)), @@ -24,6 +25,7 @@ RecvPropInt(RECVINFO(m_iSuccessiveBhops)), RecvPropFloat(RECVINFO(m_flStrafeSync)), RecvPropFloat(RECVINFO(m_flStrafeSync2)), RecvPropFloat(RECVINFO(m_flLastJumpVel)), +RecvPropFloat(RECVINFO(m_flLastJumpTime)), RecvPropInt(RECVINFO(m_iRunFlags)), RecvPropBool(RECVINFO(m_bIsInZone)), RecvPropInt(RECVINFO(m_iCurrentZone)), @@ -38,6 +40,7 @@ CMOMRunEntityData::CMOMRunEntityData() m_flStrafeSync = 0.0f; m_flStrafeSync2 = 0.0f; m_flLastJumpVel = 0.0f; + m_flLastJumpTime = 0.0f; m_iRunFlags = 0; m_bIsInZone = false; m_iCurrentZone = 0; diff --git a/mp/src/game/shared/momentum/mom_entity_run_data.h b/mp/src/game/shared/momentum/mom_entity_run_data.h index 86aee29805..1d943d1907 100644 --- a/mp/src/game/shared/momentum/mom_entity_run_data.h +++ b/mp/src/game/shared/momentum/mom_entity_run_data.h @@ -20,28 +20,25 @@ class CMOMRunEntityData CMOMRunEntityData(); #ifdef GAME_DLL - //DECLARE_SERVERCLASS(); - CNetworkVar(bool, m_bAutoBhop);// Is the player using auto bhop? CNetworkVar(int, m_iSuccessiveBhops); //How many successive bhops this player has CNetworkVar(float, m_flStrafeSync); //eyeangle based, perfect strafes / total strafes CNetworkVar(float, m_flStrafeSync2); //acceleration based, strafes speed gained / total strafes + CNetworkVar(float, m_flLastJumpTime); //The last time that the player jumped CNetworkVar(float, m_flLastJumpVel); //Last jump velocity of the player CNetworkVar(int, m_iRunFlags);//The run flags (W only/HSW/Scroll etc) of the player CNetworkVar(bool, m_bIsInZone);//This is true if the player is in a CTriggerTimerStage zone CNetworkVar(bool, m_bMapFinished);//Did the player finish the map? CNetworkVar(int, m_iCurrentZone);//Current stage/checkpoint the player is on - #elif defined CLIENT_DLL - //DECLARE_CLIENTCLASS(); bool m_bAutoBhop, m_bIsInZone, m_bMapFinished; - float m_flStrafeSync, m_flStrafeSync2, m_flLastJumpVel; + float m_flStrafeSync, m_flStrafeSync2, m_flLastJumpVel, m_flLastJumpTime; int m_iSuccessiveBhops, m_iRunFlags, m_iCurrentZone; -#endif +#endif }; #ifdef GAME_DLL diff --git a/mp/src/game/shared/momentum/mom_gamemovement.cpp b/mp/src/game/shared/momentum/mom_gamemovement.cpp index 8e5407d931..1dd583b529 100644 --- a/mp/src/game/shared/momentum/mom_gamemovement.cpp +++ b/mp/src/game/shared/momentum/mom_gamemovement.cpp @@ -568,12 +568,12 @@ bool CMomentumGameMovement::CheckJumpButton() } // In the air now. - SetGroundEntity(NULL); + SetGroundEntity(nullptr); //Set the last jump time - player->m_flLastJumpTime = gpGlobals->curtime; + player->m_RunData.m_flLastJumpTime = gpGlobals->curtime; - player->PlayStepSound((Vector &) mv->GetAbsOrigin(), player->m_pSurfaceData, 1.0, true); + player->PlayStepSound(const_cast(mv->GetAbsOrigin()), player->m_pSurfaceData, 1.0, true); //MoveHelper()->PlayerSetAnimation( PLAYER_JUMP ); //player->DoAnimationEvent(PLAYERANIMEVENT_JUMP); @@ -603,7 +603,7 @@ bool CMomentumGameMovement::CheckJumpButton() mv->m_vecVelocity[2] += flGroundFactor * sqrt(2 * 800 * 57.0); // 2 * gravity * height } - //stamina stuff (scroll gamemode only) + //stamina stuff (scroll/kz gamemode only) ConVarRef gm("mom_gamemode"); if (gm.GetInt() == MOMGM_SCROLL) { From f99c2248212150078c91f9c1525a028d30a9371e Mon Sep 17 00:00:00 2001 From: Nick K Date: Sun, 22 May 2016 07:12:54 -0400 Subject: [PATCH 040/101] Start fixing accurate timer calculations Rid the practice_mode event Cleaned up some code Fixed: Null pointer with saving maps longer than a minute (no colons in windows filenames!) Fixed: Replay recording while player changed TODO: Move run stats to a networked variable TODO: Fix the "player teleports into end trigger" bug --- mp/game/momentum/resource/modevents.res | 4 - mp/src/game/client/momentum/c_mom_player.cpp | 2 + mp/src/game/client/momentum/c_mom_player.h | 1 + .../client/momentum/mom_event_listener.cpp | 5 - .../game/client/momentum/mom_event_listener.h | 5 +- mp/src/game/client/momentum/ui/hud_timer.cpp | 8 +- mp/src/game/server/momentum/Timer.cpp | 123 +++++------ mp/src/game/server/momentum/Timer.h | 49 +++-- mp/src/game/server/momentum/mom_blockfix.h | 14 +- mp/src/game/server/momentum/mom_player.cpp | 195 +++++++++--------- mp/src/game/server/momentum/mom_player.h | 69 +++---- mp/src/game/server/momentum/mom_replay.cpp | 5 +- mp/src/game/server/momentum/mom_replay.h | 4 +- .../server/momentum/mom_replay_entity.cpp | 25 ++- .../game/server/momentum/mom_replay_entity.h | 2 +- mp/src/game/server/momentum/mom_triggers.cpp | 47 +++-- .../shared/momentum/mom_entity_run_data.h | 2 + mp/src/game/shared/momentum/util/mom_util.cpp | 20 +- mp/src/game/shared/momentum/util/mom_util.h | 2 +- 19 files changed, 284 insertions(+), 298 deletions(-) diff --git a/mp/game/momentum/resource/modevents.res b/mp/game/momentum/resource/modevents.res index 2923a99692..878fe9f717 100644 --- a/mp/game/momentum/resource/modevents.res +++ b/mp/game/momentum/resource/modevents.res @@ -84,10 +84,6 @@ "num_jumps" "short" "num_strafes" "short" } - "practice_mode" - { - "has_practicemode" "bool" - } "map_init" { "is_linear" "bool" diff --git a/mp/src/game/client/momentum/c_mom_player.cpp b/mp/src/game/client/momentum/c_mom_player.cpp index a4010df113..1635aa1fff 100644 --- a/mp/src/game/client/momentum/c_mom_player.cpp +++ b/mp/src/game/client/momentum/c_mom_player.cpp @@ -11,6 +11,7 @@ RecvPropBool(RECVINFO(m_bResumeZoom)), RecvPropInt(RECVINFO(m_iLastZoom)), RecvPropBool(RECVINFO(m_bDidPlayerBhop)), RecvPropInt(RECVINFO(m_iSuccessiveBhops)), +RecvPropBool(RECVINFO(m_bHasPracticeMode)), RecvPropDataTable(RECVINFO_DT(m_RunData), SPROP_PROXY_ALWAYS_YES, &REFERENCE_RECV_TABLE(DT_MOM_RunEntData)), END_RECV_TABLE() @@ -21,6 +22,7 @@ C_MomentumPlayer::C_MomentumPlayer() scissor.SetValue("0"); m_RunData.m_bMapFinished = false; m_RunData.m_flLastJumpTime = 0.0f; + m_bHasPracticeMode = false; } C_MomentumPlayer::~C_MomentumPlayer() diff --git a/mp/src/game/client/momentum/c_mom_player.h b/mp/src/game/client/momentum/c_mom_player.h index a0af0ea1c4..401d83ecd3 100644 --- a/mp/src/game/client/momentum/c_mom_player.h +++ b/mp/src/game/client/momentum/c_mom_player.h @@ -42,6 +42,7 @@ class C_MomentumPlayer : public C_BasePlayer bool m_bResumeZoom; int m_iLastZoom; bool m_bDidPlayerBhop; + bool m_bHasPracticeMode; CMOMRunEntityData m_RunData; diff --git a/mp/src/game/client/momentum/mom_event_listener.cpp b/mp/src/game/client/momentum/mom_event_listener.cpp index 622d0835b9..3ce956d8e8 100644 --- a/mp/src/game/client/momentum/mom_event_listener.cpp +++ b/mp/src/game/client/momentum/mom_event_listener.cpp @@ -12,7 +12,6 @@ void C_Momentum_EventListener::Init() ListenForGameEvent("run_save"); ListenForGameEvent("run_upload"); ListenForGameEvent("timer_state"); - ListenForGameEvent("practice_mode"); ListenForGameEvent("keypress"); ListenForGameEvent("map_init"); } @@ -94,10 +93,6 @@ void C_Momentum_EventListener::FireGameEvent(IGameEvent *pEvent) { m_bTimerIsRunning = pEvent->GetBool("is_running"); } - else if (!Q_strcmp("practice_mode", pEvent->GetName())) - { - m_bPlayerHasPracticeMode = pEvent->GetBool("has_practicemode"); - } else if (!Q_strcmp("keypress", pEvent->GetName())) { stats.m_iStageJumps[0] = pEvent->GetInt("num_jumps"); diff --git a/mp/src/game/client/momentum/mom_event_listener.h b/mp/src/game/client/momentum/mom_event_listener.h index b91bdc73ee..13cd637b84 100644 --- a/mp/src/game/client/momentum/mom_event_listener.h +++ b/mp/src/game/client/momentum/mom_event_listener.h @@ -10,7 +10,6 @@ class C_Momentum_EventListener : public CGameEventListener m_bTimerIsRunning(false), m_bTimeDidSave(false), m_bTimeDidUpload(false), - m_bPlayerHasPracticeMode(false), stats() { } @@ -24,9 +23,7 @@ class C_Momentum_EventListener : public CGameEventListener int m_iMapCheckpointCount; - bool m_bPlayerHasPracticeMode; - - RunStats_t stats; + RunStats_t stats;//MOM_TODO: Move this to the player and ghost ent send/recv table char m_szRunUploadStatus[512];//MOM_TODO: determine best (max) size for this }; diff --git a/mp/src/game/client/momentum/ui/hud_timer.cpp b/mp/src/game/client/momentum/ui/hud_timer.cpp index b833b4ea4d..07acce9ac7 100644 --- a/mp/src/game/client/momentum/ui/hud_timer.cpp +++ b/mp/src/game/client/momentum/ui/hud_timer.cpp @@ -103,7 +103,8 @@ class C_Timer : public CHudElement, public Panel int m_iTotalTicks; bool m_bPlayerInZone; - bool m_bWereCheatsActivated = false; + bool m_bWereCheatsActivated; + bool m_bPlayerHasPracticeMode; bool m_bShowCheckpoints; bool m_bMapFinished; int m_iCheckpointCount, m_iCheckpointCurrent; @@ -154,6 +155,8 @@ void C_Timer::Reset() m_iTotalTicks = 0; m_iStageCurrent = 1; m_bShowCheckpoints = false; + m_bWereCheatsActivated = false; + m_bPlayerHasPracticeMode = false; m_bPlayerInZone = false; m_bMapFinished = false; m_iCheckpointCount = 0; @@ -234,6 +237,7 @@ void C_Timer::OnThink() m_iStageCurrent = pLocal->m_RunData.m_iCurrentZone; m_bPlayerInZone = pLocal->m_RunData.m_bIsInZone; m_bMapFinished = pLocal->m_RunData.m_bMapFinished; + m_bPlayerHasPracticeMode = pLocal->m_bHasPracticeMode; m_iStageCount = g_MOMEventListener->m_iMapCheckpointCount; } } @@ -282,7 +286,7 @@ void C_Timer::Paint(void) // find out status of timer (no timer/practice mode) if (!m_bIsRunning) { - if (g_MOMEventListener->m_bPlayerHasPracticeMode) // In practice mode + if (m_bPlayerHasPracticeMode) // In practice mode { Q_snprintf(m_pszStringStatus, sizeof(m_pszStringStatus), practiceModeLocalized); } diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index b5a4f9fbac..a581528d7f 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -25,10 +25,6 @@ void CTimer::Start(int start) timeStartEvent->SetBool("is_running", true); gameeventmanager->FireEvent(timeStartEvent); } - CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); - if (pPlayer) { - m_flTickOffsetFix[1] = GetTickIntervalOffset(pPlayer->GetAbsVelocity(), pPlayer->GetAbsOrigin(), 1); - } } void CTimer::PostTime() @@ -253,7 +249,6 @@ void CTimer::Stop(bool endTrigger /* = false */) t.tickrate = gpGlobals->interval_per_tick; t.flags = pPlayer->m_RunData.m_iRunFlags; time(&t.date); - t.RunStats = pPlayer->m_PlayerRunStats; //copy all the run stats localTimes.AddToTail(t); @@ -274,9 +269,6 @@ void CTimer::Stop(bool endTrigger /* = false */) else Warning("Recording file doesn't exist, cannot rename!"); */ - - - m_flTickOffsetFix[0] = GetTickIntervalOffset(pPlayer->GetAbsVelocity(), pPlayer->GetAbsOrigin(), 0); } else if (runSaveEvent) //reset run saved status to false if we cant or didn't save { @@ -294,6 +286,11 @@ void CTimer::Stop(bool endTrigger /* = false */) pPlayer->m_RunData.m_bIsInZone = endTrigger; pPlayer->m_RunData.m_bMapFinished = endTrigger; } + + //stop replay recording + if (g_ReplaySystem->IsRecording(pPlayer)) + g_ReplaySystem->StopRecording(pPlayer, !endTrigger, true); + SetRunning(false); m_iEndTick = gpGlobals->tickcount; DispatchStateMessage(); @@ -355,10 +352,6 @@ float CTimer::CalculateStageTime(int stage) //If the stage is a new one, we store the time we entered this stage in m_iStageEnterTime[stage] = stage == 1 ? 0.0f : //Always returns 0 for first stage. static_cast(gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; - CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); - if (pPlayer) { - m_flTickOffsetFix[stage] = GetTickIntervalOffset(pPlayer->GetAbsVelocity(), pPlayer->GetAbsOrigin(), 2); - } } m_iLastStage = stage; return m_iStageEnterTime[stage]; @@ -403,47 +396,49 @@ void CTimer::DispatchCheckpointMessage() } } -float CTimer::GetTickIntervalOffset(const Vector velocity, const Vector origin, const int zoneType) +void CTimer::GetTickIntervalOffset(CMomentumPlayer* pPlayer, const int zoneType) { + if (!pPlayer) return; Ray_t ray; - Vector prevOrigin = Vector(origin.x - (velocity.x * gpGlobals->interval_per_tick), - origin.y - (velocity.y * gpGlobals->interval_per_tick), - origin.z - (velocity.z * gpGlobals->interval_per_tick)); - - DevLog("Origin X:%f Y:%f Z:%f\n", origin.x, origin.y, origin.z); - DevLog("Prev Origin: X:%f Y:%f Z:%f\n", prevOrigin[0], prevOrigin[1], prevOrigin[2]); - if (zoneType == 0){ + Vector vecForward, start, end, origin = pPlayer->EyePosition(), velocity = pPlayer->GetAbsVelocity(); + float len = velocity.Length2D();//Go forwards/backwards X units, not too far though (multiple triggers) + QAngle eyes = pPlayer->EyeAngles(); + eyes.x = 0;//We don't look at if they're looking up/down, we only care about horizontal direction here + AngleVectors(eyes, &vecForward); //Get the direction the player is looking + if (zoneType == 0) + { //endzone has to have the ray _start_ before we entered the end zone, hence why we start with prevOrigin //and trace "forwards" to our current origin, hitting the end trigger on the way. - CTriggerTraceEnum endTriggerTraceEnum(&ray, velocity, origin); - ray.Init(prevOrigin, origin); + start = Vector(origin.x - (velocity.x * gpGlobals->interval_per_tick), + origin.y - (velocity.y * gpGlobals->interval_per_tick), + origin.z - (velocity.z * gpGlobals->interval_per_tick)); + //MOM_TODO: Check to see if this start is still in the trigger or not, some maps teleport the player to the stop trigger! + end = start + vecForward * len;//Trace forward to the end trigger + ray.Init(start, end); + CTimeTriggerTraceEnum endTriggerTraceEnum(&ray, pPlayer->GetAbsVelocity(), zoneType); enginetrace->EnumerateEntities(ray, true, &endTriggerTraceEnum); } else if (zoneType == 1) { - //on the other hand, start zones start the ray _after_ we exited the start zone, - //so we start at our current origin and trace backwards to our prev origin - CTriggerTraceEnum startTriggerTraceEnum(&ray, velocity, origin); - ray.Init(origin, prevOrigin); + vecForward.Negate();//We want the opposite direction the player is facing, we're tracing backwards to the trigger! + start = origin;//The start for this is the eye pos + float len = pPlayer->GetAbsVelocity().Length2D();//Go backwards X units, not too far though (multiple triggers) + end = start + vecForward * len;//Trace backwards to the start/stage trigger + ray.Init(start, end); + CTimeTriggerTraceEnum startTriggerTraceEnum(&ray, pPlayer->GetAbsVelocity(), zoneType); enginetrace->EnumerateEntities(ray, true, &startTriggerTraceEnum); + //debugoverlay->AddLineOverlay(start, end, 255, 0, 0, true, 10.0f); } else if (zoneType == 2) { - //same as endzone here - CTriggerTraceEnum stageTriggerTraceEnum(&ray, velocity, origin); - ray.Init(prevOrigin, origin); - enginetrace->EnumerateEntities(ray, true, &stageTriggerTraceEnum); - } - else - { - Warning("CTimer::GetTickIntervalOffset: Incorrect Zone Type!"); - return -1; + //MOM_TODO: Shouldn't this be bundled with the start trigger logic? } - return m_flTickIntervalOffsetOut; //HACKHACK Lol + + } + // override of IEntityEnumerator's EnumEntity() in order for our trace to hit zone triggers -// member of CTimer to avoid linker errors -bool CTimer::CTriggerTraceEnum::EnumEntity(IHandleEntity *pHandleEntity) +bool CTimeTriggerTraceEnum::EnumEntity(IHandleEntity *pHandleEntity) { trace_t tr; // store entity that we found on the trace @@ -451,21 +446,24 @@ bool CTimer::CTriggerTraceEnum::EnumEntity(IHandleEntity *pHandleEntity) if (pEnt->IsSolid()) return false; + enginetrace->ClipRayToEntity(*m_pRay, MASK_ALL, pHandleEntity, &tr); if (tr.fraction < 1.0f) // tr.fraction = 1.0 means the trace completed { - float dist = m_currOrigin.DistTo(tr.endpos); + debugoverlay->AddLineOverlay(tr.startpos, tr.endpos, 255, 0, 0, true, 10.0f);//Draw a pretty line to further show + float dist = tr.startpos.DistTo(tr.endpos); DevLog("DIST: %f\n", dist); - DevLog("Time offset: %f\n", dist / m_currVelocity.Length()); //velocity = dist/time, so it follows that time = distance / velocity. - g_Timer->m_flTickIntervalOffsetOut = dist / m_currVelocity.Length(); + float offset = dist / m_currVelocity.Length2D();//velocity = dist/time, so it follows that time = distance / velocity. + DevLog("Time offset: %f\n", offset); + int stage = m_iZoneType; + if (m_iZoneType == 2) stage = g_Timer->GetCurrentStageNumber(); + g_Timer->SetIntervalOffset(stage, offset); return true; } - else - { - DevLog("Didn't hit a zone trigger.\n"); - return false; - } + + DevLog("Didn't hit a zone trigger.\n"); + return false; } //set ConVars according to Gamemode. Tickrate is by in tickset.h @@ -503,40 +501,23 @@ void CTimer::SetGameModeConVars() sv_maxvelocity.GetInt(), sv_airaccelerate.GetInt(), sv_maxspeed.GetInt()); } //Practice mode that stops the timer and allows the player to noclip. -void CTimer::EnablePractice(CBasePlayer *pPlayer) +void CTimer::EnablePractice(CMomentumPlayer *pPlayer) { pPlayer->SetParent(nullptr); pPlayer->SetMoveType(MOVETYPE_NOCLIP); ClientPrint(pPlayer, HUD_PRINTCONSOLE, "Practice mode ON!\n"); pPlayer->AddEFlags(EFL_NOCLIP_ACTIVE); - g_Timer->Stop(false); - - IGameEvent *pracModeEvent = gameeventmanager->CreateEvent("practice_mode"); - if (pracModeEvent) - { - pracModeEvent->SetBool("has_practicemode", true); - gameeventmanager->FireEvent(pracModeEvent); - } - + pPlayer->m_bHasPracticeMode = true; + Stop(false); } -void CTimer::DisablePractice(CBasePlayer *pPlayer) +void CTimer::DisablePractice(CMomentumPlayer *pPlayer) { pPlayer->RemoveEFlags(EFL_NOCLIP_ACTIVE); ClientPrint(pPlayer, HUD_PRINTCONSOLE, "Practice mode OFF!\n"); pPlayer->SetMoveType(MOVETYPE_WALK); - - IGameEvent *pracModeEvent = gameeventmanager->CreateEvent("practice_mode"); - if (pracModeEvent) - { - pracModeEvent->SetBool("has_practicemode", false); - gameeventmanager->FireEvent(pracModeEvent); - } - -} -bool CTimer::IsPracticeMode(CBaseEntity *pOther) -{ - return pOther->GetMoveType() == MOVETYPE_NOCLIP && (pOther->GetEFlags() & EFL_NOCLIP_ACTIVE); + pPlayer->m_bHasPracticeMode = false; } + //--------- CPMenu stuff -------------------------------- void CTimer::CreateCheckpoint(CBasePlayer *pPlayer) @@ -707,12 +688,12 @@ class CTimerCommands static void PracticeMove() { - CBasePlayer *pPlayer = UTIL_GetCommandClient(); + CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); if (!pPlayer) return; Vector velocity = pPlayer->GetAbsVelocity(); - if (!g_Timer->IsPracticeMode(pPlayer)) + if (!pPlayer->m_bHasPracticeMode) { if (velocity.Length2DSqr() != 0) DevLog("You cannot enable practice mode while moving!\n"); diff --git a/mp/src/game/server/momentum/Timer.h b/mp/src/game/server/momentum/Timer.h index d4644a9834..8ca24bac2e 100644 --- a/mp/src/game/server/momentum/Timer.h +++ b/mp/src/game/server/momentum/Timer.h @@ -14,6 +14,7 @@ #include "tier1/checksum_sha1.h" #include "momentum/mom_shareddefs.h" #include "momentum/mom_gamerules.h" +#include "mom_replay.h" #include "movevars_shared.h" #include @@ -137,10 +138,9 @@ class CTimer void OnMapStart(const char *); void DispatchMapInfo(); // Practice mode- noclip mode that stops timer - void PracticeMove(); - void EnablePractice(CBasePlayer *pPlayer); - void DisablePractice(CBasePlayer *pPlayer); - bool IsPracticeMode(CBaseEntity *pOther); + //void PracticeMove(); MOM_TODO: NOT IMPLEMENTED + void EnablePractice(CMomentumPlayer *pPlayer); + void DisablePractice(CMomentumPlayer *pPlayer); // Have the cheats been turned on in this session? bool GotCaughtCheating() const @@ -195,30 +195,37 @@ class CTimer bool m_bUsingCPMenu = false; //PRECISION FIX - float m_flTickOffsetFix[MAX_STAGES]; //index 0 = endzone, 1 = startzone, 2 = stage 2, 3 = stage3, etc - //creates fraction of a tick to be used as a time "offset" in precicely calculating the real run time. - //zone type: 0: endzone, 1: startzone, 2: stage - float GetTickIntervalOffset(const Vector velocity, const Vector origin, const int zoneType); + public: float m_flTickIntervalOffsetOut; - class CTriggerTraceEnum : public IEntityEnumerator + + //creates fraction of a tick to be used as a time "offset" in precicely calculating the real run time. + //zone type: 0: endzone, 1: startzone, 2: stage + //void GetTickIntervalOffset(const Vector velocity, const Vector origin, const int zoneType); + void GetTickIntervalOffset(CMomentumPlayer *pPlayer, const int zoneType); + + void SetIntervalOffset(int stage, float offset) { - public: - CTriggerTraceEnum(Ray_t *pRay, Vector velocity, Vector currOrigin) - : m_pRay(pRay), m_currVelocity(velocity), m_currOrigin(currOrigin) - { - } - - virtual bool EnumEntity(IHandleEntity *pHandleEntity); - private: - Ray_t *m_pRay; - Vector m_currOrigin; - Vector m_currVelocity; - }; + m_flTickOffsetFix[stage] = offset; + } }; +class CTimeTriggerTraceEnum : public IEntityEnumerator +{ +public: + CTimeTriggerTraceEnum(Ray_t *pRay, Vector velocity, int zoneType) + : m_iZoneType(zoneType), m_pRay(pRay), m_currVelocity(velocity) + { + } + + bool EnumEntity(IHandleEntity *pHandleEntity) override; +private: + int m_iZoneType; + Ray_t *m_pRay; + Vector m_currVelocity; +}; extern CTimer *g_Timer; diff --git a/mp/src/game/server/momentum/mom_blockfix.h b/mp/src/game/server/momentum/mom_blockfix.h index dede6e7248..bc22027af5 100644 --- a/mp/src/game/server/momentum/mom_blockfix.h +++ b/mp/src/game/server/momentum/mom_blockfix.h @@ -4,8 +4,8 @@ #pragma once #endif -#include "buttons.h" #include "cbase.h" +#include "buttons.h" #include "doors.h" #include "mom_player.h" @@ -19,13 +19,11 @@ class CMOMBhopBlockFixSystem : CAutoGameSystem public: CMOMBhopBlockFixSystem(const char *pName) : CAutoGameSystem(pName) {} - void LevelInitPostEntity() override - { FindBhopBlocks(); } + void LevelInitPostEntity() override { FindBhopBlocks(); } - void LevelShutdownPostEntity() override - { m_mapBlocks.RemoveAll(); } + void LevelShutdownPostEntity() override { m_mapBlocks.RemoveAll(); } - bool IsBhopBlock(int entIndex) { return (m_mapBlocks.Find(entIndex) != m_mapBlocks.InvalidIndex()); } + bool IsBhopBlock(int entIndex) const { return (m_mapBlocks.Find(entIndex) != m_mapBlocks.InvalidIndex()); } void PlayerTouch(CBaseEntity *pPlayerEnt, CBaseEntity *pBlock); @@ -58,11 +56,11 @@ class CTeleportTriggerTraceEnum : public IEntityEnumerator { public: CTeleportTriggerTraceEnum(Ray_t *pRay, CBaseEntity *block, bool isDoor) - : m_pRay(pRay), pEntBlock(block), bIsDoor(isDoor) + : bIsDoor(isDoor), pEntBlock(block), m_pRay(pRay) { } - virtual bool EnumEntity(IHandleEntity *pHandleEntity); + bool EnumEntity(IHandleEntity *pHandleEntity) override; private: bool bIsDoor; diff --git a/mp/src/game/server/momentum/mom_player.cpp b/mp/src/game/server/momentum/mom_player.cpp index c2caffda22..b251d8fa01 100644 --- a/mp/src/game/server/momentum/mom_player.cpp +++ b/mp/src/game/server/momentum/mom_player.cpp @@ -1,8 +1,8 @@ #include "cbase.h" +#include "Timer.h" +#include "in_buttons.h" #include "mom_player.h" #include "mom_triggers.h" -#include "in_buttons.h" -#include "Timer.h" #include "tier0/memdbgon.h" @@ -15,20 +15,20 @@ SendPropBool(SENDINFO(m_bResumeZoom)), SendPropInt(SENDINFO(m_iLastZoom)), SendPropBool(SENDINFO(m_bDidPlayerBhop)), SendPropInt(SENDINFO(m_iSuccessiveBhops)), +SendPropBool(SENDINFO(m_bHasPracticeMode)), SendPropDataTable(SENDINFO_DT(m_RunData), &REFERENCE_SEND_TABLE(DT_MOM_RunEntData)), -END_SEND_TABLE() +END_SEND_TABLE(); BEGIN_DATADESC(CMomentumPlayer) DEFINE_THINKFUNC(CheckForBhop), DEFINE_THINKFUNC(UpdateRunStats), DEFINE_THINKFUNC(CalculateAverageStats), DEFINE_THINKFUNC(LimitSpeedInStartZone), -END_DATADESC() +END_DATADESC(); LINK_ENTITY_TO_CLASS(player, CMomentumPlayer); PRECACHE_REGISTER(player); - CMomentumPlayer::CMomentumPlayer() { m_flPunishTime = -1; @@ -36,14 +36,12 @@ CMomentumPlayer::CMomentumPlayer() m_RunData.m_iRunFlags = 0; } -CMomentumPlayer::~CMomentumPlayer() -{ - -} +CMomentumPlayer::~CMomentumPlayer() {} void CMomentumPlayer::Precache() { - // Name of our entity's model +// Name of our entity's model + //MOM_TODO: Replace this with the custom player model #define ENTITY_MODEL "models/gibs/airboat_broken_engine.mdl" PrecacheModel(ENTITY_MODEL); @@ -53,10 +51,11 @@ void CMomentumPlayer::Precache() void CMomentumPlayer::Spawn() { SetModel(ENTITY_MODEL); - //BASECLASS SPAWN MUST BE AFTER SETTING THE MODEL, OTHERWISE A NULL HAPPENS! + // BASECLASS SPAWN MUST BE AFTER SETTING THE MODEL, OTHERWISE A NULL HAPPENS! BaseClass::Spawn(); AddFlag(FL_GODMODE); - RemoveSolidFlags(FSOLID_NOT_SOLID); //this removes the flag that was added while switching to spectator mode which prevented the player from activating triggers + RemoveSolidFlags(FSOLID_NOT_SOLID); // this removes the flag that was added while switching to spectator mode which + // prevented the player from activating triggers // do this here because we can't get a local player in the timer class ConVarRef gm("mom_gamemode"); switch (gm.GetInt()) @@ -71,14 +70,14 @@ void CMomentumPlayer::Spawn() DisableAutoBhop(); break; } - // Reset all bool gameevents + // Reset all bool gameevents IGameEvent *runSaveEvent = gameeventmanager->CreateEvent("run_save"); IGameEvent *runUploadEvent = gameeventmanager->CreateEvent("run_upload"); IGameEvent *timerStartEvent = gameeventmanager->CreateEvent("timer_state"); - IGameEvent *practiceModeEvent = gameeventmanager->CreateEvent("practice_mode"); m_RunData.m_bIsInZone = false; m_RunData.m_bMapFinished = false; m_RunData.m_iCurrentZone = 0; + m_bHasPracticeMode = false; ResetRunStats(); if (runSaveEvent) { @@ -96,21 +95,18 @@ void CMomentumPlayer::Spawn() timerStartEvent->SetBool("is_running", false); gameeventmanager->FireEvent(timerStartEvent); } - if (practiceModeEvent) - { - practiceModeEvent->SetBool("has_practicemode", false); - gameeventmanager->FireEvent(practiceModeEvent); - } - //Linear/etc map + // Linear/etc map g_Timer->DispatchMapInfo(); RegisterThinkContext("THINK_EVERY_TICK"); RegisterThinkContext("CURTIME"); RegisterThinkContext("THINK_AVERAGE_STATS"); RegisterThinkContext("CURTIME_FOR_START"); - SetContextThink(&CMomentumPlayer::UpdateRunStats, gpGlobals->curtime + gpGlobals->interval_per_tick, "THINK_EVERY_TICK"); + SetContextThink(&CMomentumPlayer::UpdateRunStats, gpGlobals->curtime + gpGlobals->interval_per_tick, + "THINK_EVERY_TICK"); SetContextThink(&CMomentumPlayer::CheckForBhop, gpGlobals->curtime, "CURTIME"); - SetContextThink(&CMomentumPlayer::CalculateAverageStats, gpGlobals->curtime + AVERAGE_STATS_INTERVAL, "THINK_AVERAGE_STATS"); + SetContextThink(&CMomentumPlayer::CalculateAverageStats, gpGlobals->curtime + AVERAGE_STATS_INTERVAL, + "THINK_AVERAGE_STATS"); SetContextThink(&CMomentumPlayer::LimitSpeedInStartZone, gpGlobals->curtime, "CURTIME_FOR_START"); SetNextThink(gpGlobals->curtime); DevLog("Finished spawn!\n"); @@ -146,36 +142,26 @@ bool CMomentumPlayer::CanGrabLadder(const Vector &pos, const Vector &normal) CBaseEntity *CMomentumPlayer::EntSelectSpawnPoint() { - CBaseEntity *pStart; - pStart = NULL; - if (SelectSpawnSpot("info_player_counterterrorist", pStart)) - { - return pStart; - } - else if (SelectSpawnSpot("info_player_terrorist", pStart)) - { - return pStart; - } - else if (SelectSpawnSpot("info_player_start", pStart)) - { - return pStart; - } - else + CBaseEntity *pStart = nullptr; + const char *spawns[] = {"info_player_counterterrorist", "info_player_terrorist", "info_player_start"}; + for (int i = 0; i < 3; i++) { - DevMsg("No valid spawn point found.\n"); - return BaseClass::Instance(INDEXENT(0)); + if (SelectSpawnSpot(spawns[i], pStart)) + return pStart; } + + DevMsg("No valid spawn point found.\n"); + return Instance(INDEXENT(0)); } bool CMomentumPlayer::SelectSpawnSpot(const char *pEntClassName, CBaseEntity *&pStart) { #define SF_PLAYER_START_MASTER 1 pStart = gEntList.FindEntityByClassname(pStart, pEntClassName); - if (pStart == NULL) // skip over the null point + if (pStart == nullptr) // skip over the null point pStart = gEntList.FindEntityByClassname(pStart, pEntClassName); - CBaseEntity *pLast; - pLast = NULL; - while (pStart != NULL) + CBaseEntity *pLast = nullptr; + while (pStart != nullptr) { if (g_pGameRules->IsSpawnPointValid(pStart, this)) { @@ -206,11 +192,6 @@ void CMomentumPlayer::Touch(CBaseEntity *pOther) g_MOMBlockFixer->PlayerTouch(this, pOther); } -void CMomentumPlayer::InitHUD() -{ - //g_Timer->DispatchStageCountMessage(); this was moved to spawn, under DispatchMapInfo -} - void CMomentumPlayer::EnableAutoBhop() { m_RunData.m_bAutoBhop = true; @@ -223,7 +204,7 @@ void CMomentumPlayer::DisableAutoBhop() } void CMomentumPlayer::CheckForBhop() { - if (GetGroundEntity() != NULL) + if (GetGroundEntity() != nullptr) { m_flTicksOnGround += gpGlobals->interval_per_tick; // true is player is on ground for less than 10 ticks, false if they are on ground for more s @@ -250,21 +231,22 @@ void CMomentumPlayer::CheckForBhop() void CMomentumPlayer::UpdateRunStats() { - //should velocity be XY or XYZ? + // should velocity be XY or XYZ? IGameEvent *playerMoveEvent = gameeventmanager->CreateEvent("keypress"); - float velocity = GetLocalVelocity().Length(); + float velocity = GetLocalVelocity().Length(); float velocity2D = GetLocalVelocity().Length2D(); if (g_Timer->IsRunning()) { int currentStage = g_Timer->GetCurrentStageNumber(); - if (!m_bPrevTimerRunning) //timer started on this tick + if (!m_bPrevTimerRunning) // timer started on this tick { - //Reset old run stats -- moved to on start's touch + // Reset old run stats -- moved to on start's touch m_PlayerRunStats.m_flStageEnterSpeed[0][0] = velocity; m_PlayerRunStats.m_flStageEnterSpeed[0][1] = velocity2D; - //Compare against successive bhops to avoid incrimenting when the player was in the air without jumping (for surf) - if (GetGroundEntity() == NULL && m_iSuccessiveBhops) + // Compare against successive bhops to avoid incrimenting when the player was in the air without jumping + // (for surf) + if (GetGroundEntity() == nullptr && m_iSuccessiveBhops) { m_PlayerRunStats.m_iStageJumps[0]++; m_PlayerRunStats.m_iStageJumps[currentStage]++; @@ -290,18 +272,18 @@ void CMomentumPlayer::UpdateRunStats() m_PlayerRunStats.m_flStageVelocityMax[0][0] = velocity; if (velocity2D > m_PlayerRunStats.m_flStageVelocityMax[0][1]) m_PlayerRunStats.m_flStageVelocityMax[0][1] = velocity; - //also do max velocity per stage - if (velocity >m_PlayerRunStats.m_flStageVelocityMax[currentStage][0]) + // also do max velocity per stage + if (velocity > m_PlayerRunStats.m_flStageVelocityMax[currentStage][0]) m_PlayerRunStats.m_flStageVelocityMax[currentStage][0] = velocity; if (velocity2D > m_PlayerRunStats.m_flStageVelocityMax[currentStage][1]) m_PlayerRunStats.m_flStageVelocityMax[currentStage][1] = velocity2D; // ---------- // ---- STRAFE SYNC ----- - float SyncVelocity = GetLocalVelocity().Length2DSqr(); //we always want HVEL for checking velocity sync + float SyncVelocity = GetLocalVelocity().Length2DSqr(); // we always want HVEL for checking velocity sync if (!(GetFlags() & (FL_ONGROUND | FL_INWATER)) && GetMoveType() != MOVETYPE_LADDER) { - if (EyeAngles().y > m_qangLastAngle.y) //player turned left + if (EyeAngles().y > m_qangLastAngle.y) // player turned left { m_nStrafeTicks++; if ((m_nButtons & IN_MOVELEFT) && !(m_nButtons & IN_MOVERIGHT)) @@ -309,7 +291,7 @@ void CMomentumPlayer::UpdateRunStats() if (SyncVelocity > m_flLastSyncVelocity) m_nAccelTicks++; } - else if (EyeAngles().y < m_qangLastAngle.y) //player turned right + else if (EyeAngles().y < m_qangLastAngle.y) // player turned right { m_nStrafeTicks++; if ((m_nButtons & IN_MOVERIGHT) && !(m_nButtons & IN_MOVELEFT)) @@ -320,19 +302,21 @@ void CMomentumPlayer::UpdateRunStats() } if (m_nStrafeTicks && m_nAccelTicks && m_nPerfectSyncTicks) { - m_RunData.m_flStrafeSync = (float(m_nPerfectSyncTicks) / float(m_nStrafeTicks)) * 100.0f; // ticks strafing perfectly / ticks strafing - m_RunData.m_flStrafeSync2 = (float(m_nAccelTicks) / float(m_nStrafeTicks)) * 100.0f; // ticks gaining speed / ticks strafing + m_RunData.m_flStrafeSync = (float(m_nPerfectSyncTicks) / float(m_nStrafeTicks)) * + 100.0f; // ticks strafing perfectly / ticks strafing + m_RunData.m_flStrafeSync2 = + (float(m_nAccelTicks) / float(m_nStrafeTicks)) * 100.0f; // ticks gaining speed / ticks strafing } // ---------- m_qangLastAngle = EyeAngles(); m_flLastSyncVelocity = SyncVelocity; - //this might be used in a later update - //m_flLastVelocity = velocity; + // this might be used in a later update + // m_flLastVelocity = velocity; m_bPrevTimerRunning = g_Timer->IsRunning(); m_nPrevButtons = m_nButtons; - } + } if (playerMoveEvent) { @@ -343,7 +327,7 @@ void CMomentumPlayer::UpdateRunStats() gameeventmanager->FireEvent(playerMoveEvent); } - //think once per tick + // think once per tick SetNextThink(gpGlobals->curtime + gpGlobals->interval_per_tick, "THINK_EVERY_TICK"); } void CMomentumPlayer::ResetRunStats() @@ -370,32 +354,40 @@ void CMomentumPlayer::CalculateAverageStats() m_nStageAvgCount[currentStage]++; - m_PlayerRunStats.m_flStageStrafeSyncAvg[currentStage] = m_flStageTotalSync[currentStage] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats.m_flStageStrafeSync2Avg[currentStage] = m_flStageTotalSync2[currentStage] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats.m_flStageVelocityAvg[currentStage][0] = m_flStageTotalVelocity[currentStage][0] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats.m_flStageVelocityAvg[currentStage][1] = m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageStrafeSyncAvg[currentStage] = + m_flStageTotalSync[currentStage] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageStrafeSync2Avg[currentStage] = + m_flStageTotalSync2[currentStage] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageVelocityAvg[currentStage][0] = + m_flStageTotalVelocity[currentStage][0] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageVelocityAvg[currentStage][1] = + m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); - //stage 0 is "overall" - also update these as well, no matter which stage we are on + // stage 0 is "overall" - also update these as well, no matter which stage we are on m_flStageTotalSync[0] += m_RunData.m_flStrafeSync; m_flStageTotalSync2[0] += m_RunData.m_flStrafeSync2; m_flStageTotalVelocity[0][0] += GetLocalVelocity().Length(); m_flStageTotalVelocity[0][1] += GetLocalVelocity().Length2D(); m_nStageAvgCount[0]++; - m_PlayerRunStats.m_flStageStrafeSyncAvg[0] = m_flStageTotalSync[currentStage] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats.m_flStageStrafeSync2Avg[0] = m_flStageTotalSync2[currentStage] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats.m_flStageVelocityAvg[0][0] = m_flStageTotalVelocity[currentStage][0] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats.m_flStageVelocityAvg[0][1] = m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageStrafeSyncAvg[0] = + m_flStageTotalSync[currentStage] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageStrafeSync2Avg[0] = + m_flStageTotalSync2[currentStage] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageVelocityAvg[0][0] = + m_flStageTotalVelocity[currentStage][0] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flStageVelocityAvg[0][1] = + m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); } // think once per 0.1 second interval so we avoid making the totals extremely large SetNextThink(gpGlobals->curtime + AVERAGE_STATS_INTERVAL, "THINK_AVERAGE_STATS"); } -//This limits the player's speed in the start zone, depending on which gamemode the player is currently playing. -//On surf/other, it only limits practice mode speed. On bhop/scroll, it limits the movement speed above a certain threshhold, and -//clamps the player's velocity if they go above it. This is to prevent prespeeding and is different per gamemode due to the different -//respective playstyles of surf and bhop. -//MOM_TODO: Update this to extend to start zones of stages (if doing ILs) +// This limits the player's speed in the start zone, depending on which gamemode the player is currently playing. +// On surf/other, it only limits practice mode speed. On bhop/scroll, it limits the movement speed above a certain +// threshhold, and clamps the player's velocity if they go above it. +// This is to prevent prespeeding and is different per gamemode due to the different respective playstyles of surf and bhop. +// MOM_TODO: Update this to extend to start zones of stages (if doing ILs) void CMomentumPlayer::LimitSpeedInStartZone() { ConVarRef gm("mom_gamemode"); @@ -403,20 +395,22 @@ void CMomentumPlayer::LimitSpeedInStartZone() bool bhopGameMode = (gm.GetInt() == MOMGM_BHOP || gm.GetInt() == MOMGM_SCROLL); if (m_RunData.m_bIsInZone && m_RunData.m_iCurrentZone == 1) { - if (GetGroundEntity() == nullptr && !g_Timer->IsPracticeMode(this)) //don't count ticks in air if we're in practice mode + if (GetGroundEntity() == nullptr && + !m_bHasPracticeMode) // don't count ticks in air if we're in practice mode m_nTicksInAir++; else m_nTicksInAir = 0; - //set bhop flag to true so we can't prespeed with practice mode - if (g_Timer->IsPracticeMode(this)) m_bDidPlayerBhop = true; + // set bhop flag to true so we can't prespeed with practice mode + if (m_bHasPracticeMode) + m_bDidPlayerBhop = true; - //depending on gamemode, limit speed outright when player exceeds punish vel + // depending on gamemode, limit speed outright when player exceeds punish vel if (bhopGameMode && ((!g_Timer->IsRunning() && m_nTicksInAir > MAX_AIRTIME_TICKS))) { Vector velocity = GetLocalVelocity(); - float PunishVelSquared = startTrigger->GetPunishSpeed()*startTrigger->GetPunishSpeed(); - if (velocity.Length2DSqr() > PunishVelSquared) //more efficent to check agaisnt the square of velocity + float PunishVelSquared = startTrigger->GetPunishSpeed() * startTrigger->GetPunishSpeed(); + if (velocity.Length2DSqr() > PunishVelSquared) // more efficent to check agaisnt the square of velocity { velocity = (velocity / velocity.Length()) * startTrigger->GetPunishSpeed(); SetAbsVelocity(Vector(velocity.x, velocity.y, velocity.z)); @@ -425,46 +419,43 @@ void CMomentumPlayer::LimitSpeedInStartZone() } SetNextThink(gpGlobals->curtime, "CURTIME_FOR_START"); } -//override of CBasePlayer::IsValidObserverTarget that allows us to spectate replay ghosts +// override of CBasePlayer::IsValidObserverTarget that allows us to spectate replay ghosts bool CMomentumPlayer::IsValidObserverTarget(CBaseEntity *target) { - if (target == NULL) + if (target == nullptr) return false; if (!target->IsPlayer()) { - if (!Q_strcmp(target->GetClassname(), "mom_replay_ghost")) //target is a replay ghost + if (!Q_strcmp(target->GetClassname(), "mom_replay_ghost")) // target is a replay ghost { return true; } - else - { - return false; - } + return false; } - CMomentumPlayer *player = ToCMOMPlayer( target ); + CMomentumPlayer *player = ToCMOMPlayer(target); /* Don't spec observers or players who haven't picked a class yet */ - if ( player->IsObserver() ) + if (player->IsObserver()) return false; - if( player == this ) + if (player == this) return false; // We can't observe ourselves. - if ( player->IsEffectActive( EF_NODRAW ) ) // don't watch invisible players + if (player->IsEffectActive(EF_NODRAW)) // don't watch invisible players return false; - if ( player->m_lifeState == LIFE_RESPAWNABLE ) // target is dead, waiting for respawn + if (player->m_lifeState == LIFE_RESPAWNABLE) // target is dead, waiting for respawn return false; - if ( player->m_lifeState == LIFE_DEAD || player->m_lifeState == LIFE_DYING ) + if (player->m_lifeState == LIFE_DEAD || player->m_lifeState == LIFE_DYING) { - if ( (player->m_flDeathTime + DEATH_ANIMATION_TIME ) < gpGlobals->curtime ) + if ((player->m_flDeathTime + DEATH_ANIMATION_TIME) < gpGlobals->curtime) { - return false; // allow watching until 3 seconds after death to see death animation + return false; // allow watching until 3 seconds after death to see death animation } } - return true; // passed all tests + return true; // passed all tests } \ No newline at end of file diff --git a/mp/src/game/server/momentum/mom_player.h b/mp/src/game/server/momentum/mom_player.h index 7de01d37ab..137d0b7038 100644 --- a/mp/src/game/server/momentum/mom_player.h +++ b/mp/src/game/server/momentum/mom_player.h @@ -6,11 +6,11 @@ #include "cbase.h" #include "mom_blockfix.h" +#include "mom_entity_run_data.h" +#include "mom_replay_entity.h" #include "momentum/mom_shareddefs.h" #include "player.h" #include "util/run_stats.h" -#include "mom_replay_entity.h" -#include "mom_entity_run_data.h" class CMomentumPlayer : public CBasePlayer { @@ -29,8 +29,7 @@ class CMomentumPlayer : public CBasePlayer DECLARE_SERVERCLASS(); DECLARE_DATADESC(); - int FlashlightIsOn() override - { return IsEffectActive(EF_DIMLIGHT); } + int FlashlightIsOn() override { return IsEffectActive(EF_DIMLIGHT); } void FlashlightTurnOn() override { @@ -46,18 +45,15 @@ class CMomentumPlayer : public CBasePlayer void Spawn() override; void Precache() override; - void Touch(CBaseEntity *) override; - void InitHUD() override; + //MOM_TODO: This is called when the player spawns so that HUD elements can be updated + //void InitHUD() override; - void CommitSuicide(bool bExplode = false, bool bForce = false) override - {}; + void CommitSuicide(bool bExplode = false, bool bForce = false) override{}; - void CommitSuicide(const Vector &vecForce, bool bExplode = false, bool bForce = false) override - {}; + void CommitSuicide(const Vector &vecForce, bool bExplode = false, bool bForce = false) override{}; - bool CanBreatheUnderwater() const override - { return true; } + bool CanBreatheUnderwater() const override { return true; } // LADDERS void SurpressLadderChecks(const Vector &pos, const Vector &normal); @@ -65,7 +61,7 @@ class CMomentumPlayer : public CBasePlayer Vector m_lastStandingPos; // used by the gamemovement code for finding ladders // SPAWNING - CBaseEntity *EntSelectSpawnPoint(); + CBaseEntity *EntSelectSpawnPoint() override; // used by CMomentumGameMovement bool m_duckUntilOnGround; @@ -73,8 +69,8 @@ class CMomentumPlayer : public CBasePlayer void EnableAutoBhop(); void DisableAutoBhop(); - bool HasAutoBhop() { return m_RunData.m_bAutoBhop; } - bool DidPlayerBhop() { return m_bDidPlayerBhop; } + bool HasAutoBhop() const { return m_RunData.m_bAutoBhop; } + bool DidPlayerBhop() const { return m_bDidPlayerBhop; } // think function for detecting if player bhopped void CheckForBhop(); void UpdateRunStats(); @@ -82,16 +78,17 @@ class CMomentumPlayer : public CBasePlayer void CalculateAverageStats(); void LimitSpeedInStartZone(); - //These are used for weapon code, MOM_TODO: potentially remove? + // These are used for weapon code, MOM_TODO: potentially remove? CNetworkVar(int, m_iShotsFired); CNetworkVar(int, m_iDirection); CNetworkVar(bool, m_bResumeZoom); CNetworkVar(int, m_iLastZoom); - CNetworkVar(bool, m_bDidPlayerBhop);// Did the player bunnyhop successfully? - CNetworkVar(int, m_iSuccessiveBhops); //How many successive bhops this player has + CNetworkVar(bool, m_bDidPlayerBhop); // Did the player bunnyhop successfully? + CNetworkVar(int, m_iSuccessiveBhops); // How many successive bhops this player has + CNetworkVar(bool, m_bHasPracticeMode); //Is the player in practice mode? - CNetworkVarEmbedded(CMOMRunEntityData, m_RunData);//Current run data, used for hud elements + CNetworkVarEmbedded(CMOMRunEntityData, m_RunData); // Current run data, used for hud elements void GetBulletTypeParameters(int iBulletType, float &fPenetrationPower, float &flPenetrationDistance); @@ -102,33 +99,30 @@ class CMomentumPlayer : public CBasePlayer void KickBack(float up_base, float lateral_base, float up_modifier, float lateral_modifier, float up_max, float lateral_max, int direction_change); - void SetPunishTime(float newTime) { m_flPunishTime = newTime; } - - void SetLastBlock(int lastBlock) { m_iLastBlock = lastBlock; } bool IsValidObserverTarget(CBaseEntity *target) override; - int GetLastBlock() { return m_iLastBlock; } - float GetPunishTime() { return m_flPunishTime; } + // Used by g_MOMBlockFix door/button fix code + void Touch(CBaseEntity *) override; + int GetLastBlock() const { return m_iLastBlock; } + float GetPunishTime() const { return m_flPunishTime; } + void SetPunishTime(float newTime) { m_flPunishTime = newTime; } + void SetLastBlock(int lastBlock) { m_iLastBlock = lastBlock; } - bool IsWatchingReplay() const - { - return m_hObserverTarget.Get() && GetReplayEnt(); - } + bool IsWatchingReplay() const { return m_hObserverTarget.Get() && GetReplayEnt(); } CMomentumReplayGhostEntity *GetReplayEnt() const { - return dynamic_cast(m_hObserverTarget.Get()); + return dynamic_cast(m_hObserverTarget.Get()); } - //Run Stats + // Run Stats RunStats_t m_PlayerRunStats; - //for calc avg + // for calc avg int m_nStageAvgCount[MAX_STAGES]; - float m_flStageTotalSync[MAX_STAGES], m_flStageTotalSync2[MAX_STAGES], - m_flStageTotalVelocity[MAX_STAGES][2]; + float m_flStageTotalSync[MAX_STAGES], m_flStageTotalSync2[MAX_STAGES], m_flStageTotalVelocity[MAX_STAGES][2]; -private: + private: CountdownTimer m_ladderSurpressionTimer; Vector m_lastLadderNormal; Vector m_lastLadderPos; @@ -142,7 +136,7 @@ class CMomentumPlayer : public CBasePlayer float m_flPunishTime; int m_iLastBlock; - //for strafe sync + // for strafe sync float m_flLastVelocity, m_flLastSyncVelocity; QAngle m_qangLastAngle; @@ -153,8 +147,9 @@ class CMomentumPlayer : public CBasePlayer bool m_bPrevTimerRunning; int m_nPrevButtons; - //Start zone thinkfunc + // Start zone thinkfunc int m_nTicksInAir; - const int MAX_AIRTIME_TICKS = 15; //The player can spend this many ticks in the air inside the start zone before their speed is limited + const int MAX_AIRTIME_TICKS = + 15; // The player can spend this many ticks in the air inside the start zone before their speed is limited }; #endif // MOMPLAYER_H \ No newline at end of file diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 3e7cf0f316..f28b30113e 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -9,7 +9,8 @@ void CMomentumReplaySystem::BeginRecording(CBasePlayer *pPlayer) { m_player = ToCMOMPlayer( pPlayer); - if (!m_player->IsWatchingReplay()) //don't record if we're watching a preexisting replay + //don't record if we're watching a preexisting replay or in practice mode + if (!m_player->IsWatchingReplay() && !m_player->m_bHasPracticeMode) { m_bIsRecording = true; Log("Recording began!\n"); @@ -34,7 +35,7 @@ void CMomentumReplaySystem::StopRecording(CBasePlayer *pPlayer, bool throwaway, m_bShouldStopRec = false; CMomentumPlayer *pMOMPlayer = ToCMOMPlayer(pPlayer); char newRecordingName[MAX_PATH], newRecordingPath[MAX_PATH], runTime[BUFSIZETIME]; - mom_UTIL->FormatTime(g_Timer->GetLastRunTime(), runTime); + mom_UTIL->FormatTime(g_Timer->GetLastRunTime(), runTime, 3, true); Q_snprintf(newRecordingName, MAX_PATH, "%s_%s_%s.momrec", (pMOMPlayer ? pMOMPlayer->GetPlayerName() : "Unnamed"), gpGlobals->mapname.ToCStr(), runTime); V_ComposeFileName(RECORDING_PATH, newRecordingName, newRecordingPath, MAX_PATH); //V_ComposeFileName calls all relevent filename functions for us! THANKS GABEN diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index dccc410cb4..83482df3c0 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -34,7 +34,7 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame { //Stop a recording if there is one while the level shuts down if (m_bIsRecording) - StopRecording(nullptr, true, 0.0f); + StopRecording(nullptr, true, false); } void BeginRecording(CBasePlayer *pPlayer); @@ -67,7 +67,7 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame float m_fRecEndTime; CMomentumPlayer *m_player; - CMomentumReplayGhostEntity *m_CurrentReplayGhost; + CMomentumReplayGhostEntity *m_CurrentReplayGhost;//MOM_TODO: Update this to be a CUtlVector so multiple ghosts can be kept track of replay_frame_t m_currentFrame; replay_header_t m_replayHeader; diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 0e7fb5ea4e..4992600970 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -35,6 +35,7 @@ CMomentumReplayGhostEntity::CMomentumReplayGhostEntity() { m_nReplayButtons = 0; m_iTotalStrafes = 0; + m_bHasJumped = false; } @@ -74,7 +75,7 @@ void CMomentumReplayGhostEntity::Spawn(void) SetBodygroup(1, mom_replay_ghost_bodygroup.GetInt()); } -void CMomentumReplayGhostEntity::StartRun(bool firstPerson, bool shouldLoop) +void CMomentumReplayGhostEntity::StartRun(bool firstPerson, bool shouldLoop /* = false */) { mom_replay_firstperson.SetValue(firstPerson ? "1" : "0"); mom_replay_loop.SetValue(shouldLoop ? "1" : "0"); @@ -83,11 +84,12 @@ void CMomentumReplayGhostEntity::StartRun(bool firstPerson, bool shouldLoop) m_iTotalStrafes = 0; m_nStartTick = gpGlobals->curtime; m_bIsActive = true; - step = 0; - SetAbsOrigin(g_ReplaySystem->m_vecRunData[0].m_vPlayerOrigin); + m_bHasJumped = false; + step = mom_replay_reverse.GetBool() ? g_ReplaySystem->m_vecRunData.Size() - 1 : 0; + SetAbsOrigin(g_ReplaySystem->m_vecRunData[step].m_vPlayerOrigin); SetNextThink(gpGlobals->curtime); - } + void CMomentumReplayGhostEntity::UpdateStep() { currentStep = g_ReplaySystem->m_vecRunData[step]; @@ -105,14 +107,14 @@ void CMomentumReplayGhostEntity::Think(void) BaseClass::Think(); if (step >= 0) { - if (step+1 < g_ReplaySystem->m_vecRunData.Size()) + if (step+1 < g_ReplaySystem->m_vecRunData.Size() || mom_replay_reverse.GetBool() && step - 1 > -1) { UpdateStep(); mom_replay_firstperson.GetBool() ? HandleGhostFirstPerson() : HandleGhost(); } else if (step+1 == g_ReplaySystem->m_vecRunData.Size() && mom_replay_loop.GetBool()) { - step = 0; //reset us to the start + step = mom_replay_reverse.GetBool() ? g_ReplaySystem->m_vecRunData.Size() - 1 : 0; //reset us to the start } else { @@ -149,7 +151,6 @@ void CMomentumReplayGhostEntity::HandleGhostFirstPerson() CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); if (pPlayer) { - //pPlayer->IsWatchingReplay() = true; if (!pPlayer->IsObserver()) { pPlayer->SetObserverTarget(this); @@ -161,7 +162,9 @@ void CMomentumReplayGhostEntity::HandleGhostFirstPerson() pPlayer->ForceObserverMode(OBS_MODE_IN_EYE); } pPlayer->SetViewOffset(VEC_VIEW); - SetAbsOrigin(currentStep.m_vPlayerOrigin); + Vector origin = currentStep.m_vPlayerOrigin; + origin.z -= 3.5f; + SetAbsOrigin(origin); if (pPlayer->GetObserverMode() == OBS_MODE_IN_EYE) { SetAbsAngles(currentStep.m_qEyeAngles); @@ -220,8 +223,9 @@ void CMomentumReplayGhostEntity::UpdateStats(Vector ghostVel) //calculate strafe sync based on replay ghost's movement, in order to update the player's HUD float SyncVelocity = ghostVel.Length2DSqr(); //we always want HVEL for checking velocity sync - if (GetGroundEntity() == nullptr) + if (GetGroundEntity() == nullptr)//The ghost is in the air { + m_bHasJumped = false; if (EyeAngles().y > m_qLastEyeAngle.y) //player turned left { m_nStrafeTicks++; @@ -247,8 +251,9 @@ void CMomentumReplayGhostEntity::UpdateStats(Vector ghostVel) // --- JUMP AND STRAFE COUNTER --- //MOM_TODO: This needs to calculate better. It currently counts every other jump, and sometimes spams (player on ground for a while) - if (GetGroundEntity() != nullptr && currentStep.m_nPlayerButtons & IN_JUMP) + if (!m_bHasJumped && GetGroundEntity() != nullptr && GetFlags() & FL_ONGROUND && currentStep.m_nPlayerButtons & IN_JUMP) { + m_bHasJumped = true; m_RunData.m_flLastJumpVel = GetLocalVelocity().Length2D(); m_RunData.m_flLastJumpTime = gpGlobals->curtime; m_iTotalJumps++; diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index ec43a88e52..35666b8b6a 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -74,7 +74,7 @@ class CMomentumReplayGhostEntity : public CBaseAnimating int m_iBodyGroup = BODY_PROLATE_ELLIPSE; Color m_ghostColor; static Color m_newGhostColor; - + bool m_bHasJumped; //for faking strafe sync calculations QAngle m_qLastEyeAngle; float m_flLastSyncVelocity; diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index 52287d7cd6..53b92777a0 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -40,6 +40,10 @@ void CTriggerStage::StartTouch(CBaseEntity *pOther) pPlayer->m_RunData.m_iCurrentZone = stageNum; if (g_Timer->IsRunning()) { + //MOM_TODO: Shouldn't this (also?) be called upon stage exit? + if (stageNum != 1) + g_Timer->GetTickIntervalOffset(pPlayer, 2); + stageEvent->SetInt("stage_num", stageNum); stageEvent->SetFloat("stage_enter_time", g_Timer->CalculateStageTime(stageNum)); stageEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats.m_iStageJumps[stageNum - 1]); @@ -129,12 +133,12 @@ END_DATADESC() void CTriggerTimerStart::EndTouch(CBaseEntity *pOther) { - if (pOther->IsPlayer())//MOM_TODO: pOther->IsReplay or dynamic cast idk I'm tired + if (pOther->IsPlayer()) { CMomentumPlayer *pPlayer = ToCMOMPlayer(pOther); //surf or other gamemodes has timer start on exiting zone, bhop timer starts when the player jumps - if (!g_Timer->IsPracticeMode(pOther) && !g_Timer->IsRunning()) // do not start timer if player is in practice mode or it's already running. + if (!pPlayer->m_bHasPracticeMode && !g_Timer->IsRunning()) // do not start timer if player is in practice mode or it's already running. { if (IsLimitingSpeed()) { @@ -153,6 +157,7 @@ void CTriggerTimerStart::EndTouch(CBaseEntity *pOther) } } g_Timer->Start(gpGlobals->tickcount); + g_Timer->GetTickIntervalOffset(pPlayer, 1); } pPlayer->m_RunData.m_bIsInZone = false; pPlayer->m_RunData.m_bMapFinished = false; @@ -181,7 +186,6 @@ void CTriggerTimerStart::EndTouch(CBaseEntity *pOther) void CTriggerTimerStart::StartTouch(CBaseEntity *pOther) { - //MOM_TODO: Set replay entity stuff too g_Timer->SetStartTrigger(this); CMomentumPlayer *pPlayer = ToCMOMPlayer(pOther); if (pPlayer) @@ -193,11 +197,11 @@ void CTriggerTimerStart::StartTouch(CBaseEntity *pOther) if (g_Timer->IsRunning()) { - g_Timer->Stop(false); + g_Timer->Stop(false);//Handles stopping replay recording as well g_Timer->DispatchResetMessage(); //lower the player's speed if they try to jump back into the start zone } - //begin recording demo + //begin recording replay if (!g_ReplaySystem->IsRecording(pPlayer)) g_ReplaySystem->BeginRecording(pPlayer); else @@ -332,16 +336,23 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum][1]); gameeventmanager->FireEvent(stageEvent); } + + //MOM_TODO: BUG: If we teleport into the stop trigger, this will still try to get the offset! We need some + //check or something, you can see my idea below: + // if (pPlayer->GetAbsOrigin().AsVector2D().DistTo(CollisionProp()->OBBCenter().AsVector2D()) > 5.0f) + //However if the ending trigger is very small, this may end up returning true! + //My idea was to check if the player is very close to the outside edges (using model/size bounds) + //Or to just check to see if the previous origin was even in the end trigger or not + //So I (or somebody who wants to) will probably implement that eventually + + g_Timer->GetTickIntervalOffset(pPlayer, 0); g_Timer->Stop(true); pPlayer->m_RunData.m_bMapFinished = true; + //MOM_TODO: SLOW DOWN/STOP THE PLAYER HERE! } - //stop demo recording - if (g_ReplaySystem->IsRecording(pPlayer)) - g_ReplaySystem->StopRecording(ToCMOMPlayer(pOther), false, true); - pPlayer->m_RunData.m_bIsInZone = true; } else @@ -427,7 +438,7 @@ void CTriggerTeleportEnt::StartTouch(CBaseEntity *pOther) if (!pDestinationEnt) { if (m_target != NULL_STRING) - pDestinationEnt = gEntList.FindEntityByName(NULL, m_target, NULL, pOther, pOther); + pDestinationEnt = gEntList.FindEntityByName(nullptr, m_target, nullptr, pOther, pOther); else { DevWarning("CTriggerTeleport cannot teleport, pDestinationEnt and m_target are null!\n"); @@ -471,7 +482,7 @@ END_DATADESC() void CTriggerOnehop::StartTouch(CBaseEntity *pOther) { - SetDestinationEnt(NULL); + SetDestinationEnt(nullptr); BaseClass::StartTouch(pOther); // The above is needed for the Think() function of this class, // it's very HACKHACK but it works @@ -494,7 +505,7 @@ void CTriggerOnehop::StartTouch(CBaseEntity *pOther) for (int iIndex = 0; iIndex < c_MaxCount; iIndex++) { CTriggerOnehop *thisOnehop = g_Timer->FindOnehopOnList(iIndex); - if (thisOnehop != NULL && thisOnehop->HasSpawnFlags(SF_TELEPORT_RESET_ONEHOP)) + if (thisOnehop != nullptr && thisOnehop->HasSpawnFlags(SF_TELEPORT_RESET_ONEHOP)) g_Timer->RemoveOnehopFromList(thisOnehop); } } @@ -506,7 +517,7 @@ void CTriggerOnehop::StartTouch(CBaseEntity *pOther) void CTriggerOnehop::Think() { CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); - if (pPlayer != NULL && m_fStartTouchedTime > 0) + if (pPlayer != nullptr && m_fStartTouchedTime > 0) { if (IsTouching(pPlayer) && (gpGlobals->realtime - m_fStartTouchedTime >= m_fMaxHoldSeconds)) { @@ -554,7 +565,7 @@ void CTriggerMultihop::EndTouch(CBaseEntity *pOther) void CTriggerMultihop::Think() { CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); - if (pPlayer != NULL && m_fStartTouchedTime > 0) + if (pPlayer != nullptr && m_fStartTouchedTime > 0) { if (IsTouching(pPlayer) && (gpGlobals->realtime - m_fStartTouchedTime >= m_fMaxHoldSeconds)) { @@ -576,7 +587,7 @@ END_DATADESC() void CTriggerUserInput::Think() { CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); - if (pPlayer != NULL && IsTouching(pPlayer) && (pPlayer->m_nButtons & m_ButtonRep)) + if (pPlayer != nullptr && IsTouching(pPlayer) && (pPlayer->m_nButtons & m_ButtonRep)) { m_OnKeyPressed.FireOutput(pPlayer, this); } @@ -634,7 +645,7 @@ void CTriggerLimitMovement::Think() { pPlayer->DisableButtons(IN_JUMP); // if player in air - if (pPlayer->GetGroundEntity() != NULL) + if (pPlayer->GetGroundEntity() != nullptr) { // only start timer if we havent already started if (!m_BhopTimer.HasStarted()) @@ -716,7 +727,7 @@ void CFuncShootBoost::Spawn() // temporary m_debugOverlays |= (OVERLAY_BBOX_BIT | OVERLAY_TEXT_BIT); if (m_target != NULL_STRING) - m_Destination = gEntList.FindEntityByName(NULL, m_target); + m_Destination = gEntList.FindEntityByName(nullptr, m_target); } int CFuncShootBoost::OnTakeDamage(const CTakeDamageInfo &info) @@ -750,7 +761,7 @@ int CFuncShootBoost::OnTakeDamage(const CTakeDamageInfo &info) } if (m_Destination) { - if (((CBaseTrigger *)m_Destination)->IsTouching(pInflictor)) + if (static_cast(m_Destination)->IsTouching(pInflictor)) { pInflictor->SetAbsVelocity(finalVel); } diff --git a/mp/src/game/shared/momentum/mom_entity_run_data.h b/mp/src/game/shared/momentum/mom_entity_run_data.h index 1d943d1907..83e2dcfe1d 100644 --- a/mp/src/game/shared/momentum/mom_entity_run_data.h +++ b/mp/src/game/shared/momentum/mom_entity_run_data.h @@ -32,6 +32,8 @@ class CMOMRunEntityData CNetworkVar(bool, m_bMapFinished);//Did the player finish the map? CNetworkVar(int, m_iCurrentZone);//Current stage/checkpoint the player is on + //MOM_TODO: CNetworkEmbedded(CMOMRunStats, m_RunStats); + #elif defined CLIENT_DLL bool m_bAutoBhop, m_bIsInZone, m_bMapFinished; diff --git a/mp/src/game/shared/momentum/util/mom_util.cpp b/mp/src/game/shared/momentum/util/mom_util.cpp index ff154fa2b3..d624451541 100644 --- a/mp/src/game/shared/momentum/util/mom_util.cpp +++ b/mp/src/game/shared/momentum/util/mom_util.cpp @@ -164,11 +164,11 @@ void MomentumUtil::VersionCallback(HTTPRequestCompleted_t *pCallback, bool bIOFa steamapicontext->SteamHTTP()->ReleaseHTTPRequest(pCallback->m_hRequest); } -void MomentumUtil::FormatTime(float m_flSecondsTime, char *pOut, int precision) const +void MomentumUtil::FormatTime(float m_flSecondsTime, char *pOut, int precision, bool fileName) const { // We want the absolute value to format! Negatives (if any) should be added post-format! m_flSecondsTime = abs(m_flSecondsTime); - + char separator = fileName ? '-' : ':';//MOM_TODO: Think of a better char? int hours = m_flSecondsTime / (60.0f * 60.0f); int minutes = fmod(m_flSecondsTime / 60.0f, 60.0f); int seconds = fmod(m_flSecondsTime, 60.0f); @@ -180,33 +180,33 @@ void MomentumUtil::FormatTime(float m_flSecondsTime, char *pOut, int precision) { case 0: if (hours > 0) - Q_snprintf(pOut, BUFSIZETIME, "%d:%02d:%02d", hours, minutes, seconds); + Q_snprintf(pOut, BUFSIZETIME, "%d%c%02d%c%02d", hours, separator, minutes, separator, seconds); else if (minutes > 0) - Q_snprintf(pOut, BUFSIZETIME, "%d:%02d", minutes, seconds); + Q_snprintf(pOut, BUFSIZETIME, "%d%c%02d", minutes, separator, seconds); else Q_snprintf(pOut, BUFSIZETIME, "%d", seconds); break; case 1: if (hours > 0) - Q_snprintf(pOut, BUFSIZETIME, "%d:%02d:%02d.%d", hours, minutes, seconds, tenths); + Q_snprintf(pOut, BUFSIZETIME, "%d%c%02d%c%02d.%d", hours, separator, minutes, separator, seconds, tenths); else if (minutes > 0) - Q_snprintf(pOut, BUFSIZETIME, "%d:%02d.%d", minutes, seconds, tenths); + Q_snprintf(pOut, BUFSIZETIME, "%d%c%02d.%d", minutes, separator, seconds, tenths); else Q_snprintf(pOut, BUFSIZETIME, "%d.%d", seconds, tenths); break; case 2: if (hours > 0) - Q_snprintf(pOut, BUFSIZETIME, "%d:%02d:%02d.%02d", hours, minutes, seconds, hundredths); + Q_snprintf(pOut, BUFSIZETIME, "%d%c%02d%c%02d.%02d", hours, separator, minutes, separator, seconds, hundredths); else if (minutes > 0) - Q_snprintf(pOut, BUFSIZETIME, "%d:%02d.%02d", minutes, seconds, hundredths); + Q_snprintf(pOut, BUFSIZETIME, "%d%c%02d.%02d", minutes, separator, seconds, hundredths); else Q_snprintf(pOut, BUFSIZETIME, "%d.%02d", seconds, hundredths); break; case 3: if (hours > 0) - Q_snprintf(pOut, BUFSIZETIME, "%d:%02d:%02d.%03d", hours, minutes, seconds, millis); + Q_snprintf(pOut, BUFSIZETIME, "%d%c%02d%c%02d.%03d", hours, separator, minutes, separator, seconds, millis); else if (minutes > 0) - Q_snprintf(pOut, BUFSIZETIME, "%d:%02d.%03d", minutes, seconds, millis); + Q_snprintf(pOut, BUFSIZETIME, "%d%c%02d.%03d", minutes, separator, seconds, millis); else Q_snprintf(pOut, BUFSIZETIME, "%d.%03d", seconds, millis); break; diff --git a/mp/src/game/shared/momentum/util/mom_util.h b/mp/src/game/shared/momentum/util/mom_util.h index 842ce9f519..edda3ac644 100644 --- a/mp/src/game/shared/momentum/util/mom_util.h +++ b/mp/src/game/shared/momentum/util/mom_util.h @@ -38,7 +38,7 @@ class MomentumUtil Color GetColorFromVariation(float variation, float deadZone, Color normalcolor, Color increasecolor, Color decreasecolor) const; //Formats time in ticks by a given tickrate into time. Includes minutes if time > minutes, hours if time > hours, etc //Precision is miliseconds by default - void FormatTime(float seconds, char *pOut, int precision = 3) const; + void FormatTime(float seconds, char *pOut, int precision = 3, bool fileName = false) const; KeyValues *GetBestTime(KeyValues *kvInput, const char *szMapName, float tickrate, int flags = 0); bool GetRunComparison(const char *szMapName, float tickRate, int flags, RunCompare_t *into); From cc5f15fce9320394c6b525113d175a0ec367fb01 Mon Sep 17 00:00:00 2001 From: Nick K Date: Sun, 22 May 2016 17:20:56 -0400 Subject: [PATCH 041/101] Fix offset calculation for end zone Stage offsets calculate like start triggers --- mp/src/game/server/momentum/Timer.cpp | 21 +++++-------- mp/src/game/server/momentum/mom_triggers.cpp | 33 +++++++++++--------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index a581528d7f..f8a0932ff4 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -400,7 +400,7 @@ void CTimer::GetTickIntervalOffset(CMomentumPlayer* pPlayer, const int zoneType) { if (!pPlayer) return; Ray_t ray; - Vector vecForward, start, end, origin = pPlayer->EyePosition(), velocity = pPlayer->GetAbsVelocity(); + Vector vecForward, start, end, origin = pPlayer->GetAbsOrigin(), velocity = pPlayer->GetAbsVelocity(); float len = velocity.Length2D();//Go forwards/backwards X units, not too far though (multiple triggers) QAngle eyes = pPlayer->EyeAngles(); eyes.x = 0;//We don't look at if they're looking up/down, we only care about horizontal direction here @@ -412,29 +412,22 @@ void CTimer::GetTickIntervalOffset(CMomentumPlayer* pPlayer, const int zoneType) start = Vector(origin.x - (velocity.x * gpGlobals->interval_per_tick), origin.y - (velocity.y * gpGlobals->interval_per_tick), origin.z - (velocity.z * gpGlobals->interval_per_tick)); - //MOM_TODO: Check to see if this start is still in the trigger or not, some maps teleport the player to the stop trigger! + end = start + vecForward * len;//Trace forward to the end trigger ray.Init(start, end); CTimeTriggerTraceEnum endTriggerTraceEnum(&ray, pPlayer->GetAbsVelocity(), zoneType); enginetrace->EnumerateEntities(ray, true, &endTriggerTraceEnum); } - else if (zoneType == 1) + else if (zoneType == 1 || zoneType == 2) { + //Start/stage zones trace from outside the trigger, backwards vecForward.Negate();//We want the opposite direction the player is facing, we're tracing backwards to the trigger! - start = origin;//The start for this is the eye pos - float len = pPlayer->GetAbsVelocity().Length2D();//Go backwards X units, not too far though (multiple triggers) + start = origin;//The start for this is the player pos end = start + vecForward * len;//Trace backwards to the start/stage trigger ray.Init(start, end); - CTimeTriggerTraceEnum startTriggerTraceEnum(&ray, pPlayer->GetAbsVelocity(), zoneType); - enginetrace->EnumerateEntities(ray, true, &startTriggerTraceEnum); - //debugoverlay->AddLineOverlay(start, end, 255, 0, 0, true, 10.0f); - } - else if (zoneType == 2) - { - //MOM_TODO: Shouldn't this be bundled with the start trigger logic? + CTimeTriggerTraceEnum stageTriggerTraceEnum(&ray, pPlayer->GetAbsVelocity(), zoneType); + enginetrace->EnumerateEntities(ray, true, &stageTriggerTraceEnum); } - - } // override of IEntityEnumerator's EnumEntity() in order for our trace to hit zone triggers diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index 53b92777a0..7bf1daca49 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -40,10 +40,6 @@ void CTriggerStage::StartTouch(CBaseEntity *pOther) pPlayer->m_RunData.m_iCurrentZone = stageNum; if (g_Timer->IsRunning()) { - //MOM_TODO: Shouldn't this (also?) be called upon stage exit? - if (stageNum != 1) - g_Timer->GetTickIntervalOffset(pPlayer, 2); - stageEvent->SetInt("stage_num", stageNum); stageEvent->SetFloat("stage_enter_time", g_Timer->CalculateStageTime(stageNum)); stageEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats.m_iStageJumps[stageNum - 1]); @@ -91,6 +87,9 @@ void CTriggerStage::EndTouch(CBaseEntity *pOther) { if (stageNum == 1 || g_Timer->IsRunning())//Timer won't be running if it's the start trigger { + //This handles both the start and stage triggers + g_Timer->GetTickIntervalOffset(pPlayer, stageNum); + IGameEvent *stageEvent = gameeventmanager->CreateEvent("stage_exit"); if (stageEvent) { @@ -157,7 +156,6 @@ void CTriggerTimerStart::EndTouch(CBaseEntity *pOther) } } g_Timer->Start(gpGlobals->tickcount); - g_Timer->GetTickIntervalOffset(pPlayer, 1); } pPlayer->m_RunData.m_bIsInZone = false; pPlayer->m_RunData.m_bMapFinished = false; @@ -337,15 +335,22 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) gameeventmanager->FireEvent(stageEvent); } - //MOM_TODO: BUG: If we teleport into the stop trigger, this will still try to get the offset! We need some - //check or something, you can see my idea below: - // if (pPlayer->GetAbsOrigin().AsVector2D().DistTo(CollisionProp()->OBBCenter().AsVector2D()) > 5.0f) - //However if the ending trigger is very small, this may end up returning true! - //My idea was to check if the player is very close to the outside edges (using model/size bounds) - //Or to just check to see if the previous origin was even in the end trigger or not - //So I (or somebody who wants to) will probably implement that eventually - - g_Timer->GetTickIntervalOffset(pPlayer, 0); + //Check to see if we should calculate the timer offset fix + Vector origin = pPlayer->GetAbsOrigin(), velocity = pPlayer->GetAbsVelocity(); + Vector prevOrigin = Vector(origin.x - (velocity.x * gpGlobals->interval_per_tick), + origin.y - (velocity.y * gpGlobals->interval_per_tick), + origin.z - (velocity.z * gpGlobals->interval_per_tick)); + Vector bottomLeft = GetAbsOrigin() + CollisionProp()->OBBMins(); + Vector topRight = GetAbsOrigin() + CollisionProp()->OBBMaxs(); + + if ((prevOrigin.x > bottomLeft.x && prevOrigin.x < topRight.x) && + (prevOrigin.y > bottomLeft.y && prevOrigin.y < topRight.y)) + DevLog("PrevOrigin inside of end trigger, not calculating offset!\n"); + else + { + DevLog("Previous origin is NOT inside the trigger, calculating offset...\n"); + g_Timer->GetTickIntervalOffset(pPlayer, 0); + } g_Timer->Stop(true); pPlayer->m_RunData.m_bMapFinished = true; From aefab547219085cd44c24946c51aa2800ff2ce36 Mon Sep 17 00:00:00 2001 From: Nick K Date: Mon, 23 May 2016 02:25:10 -0400 Subject: [PATCH 042/101] Add player state to checkpoints ClangFormat and cleanup mom_gamemovement TODO: Make fall damage and other sounds toggleable --- mp/src/game/server/momentum/Timer.cpp | 2 + mp/src/game/server/momentum/Timer.h | 1 + .../game/shared/momentum/mom_gamemovement.cpp | 128 +++++++++++++----- .../game/shared/momentum/mom_gamemovement.h | 68 ++++++---- mp/src/game/shared/momentum/mom_gamerules.h | 2 +- 5 files changed, 139 insertions(+), 62 deletions(-) diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index f8a0932ff4..2c7a9a3fc1 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -520,6 +520,7 @@ void CTimer::CreateCheckpoint(CBasePlayer *pPlayer) c.ang = pPlayer->GetAbsAngles(); c.pos = pPlayer->GetAbsOrigin(); c.vel = pPlayer->GetAbsVelocity(); + Q_strncpy(c.targetName, pPlayer->GetEntityName().ToCStr(), MAX_PLAYER_NAME_LENGTH); checkpoints.AddToTail(c); m_iCurrentStepCP++; } @@ -535,6 +536,7 @@ void CTimer::TeleportToCP(CBasePlayer* cPlayer, int cpNum) { if (checkpoints.IsEmpty() || !cPlayer) return; Checkpoint c = checkpoints[cpNum]; + cPlayer->SetName(MAKE_STRING(c.targetName)); cPlayer->Teleport(&c.pos, &c.ang, &c.vel); } diff --git a/mp/src/game/server/momentum/Timer.h b/mp/src/game/server/momentum/Timer.h index 8ca24bac2e..51059ba949 100644 --- a/mp/src/game/server/momentum/Timer.h +++ b/mp/src/game/server/momentum/Timer.h @@ -185,6 +185,7 @@ class CTimer Vector pos; Vector vel; QAngle ang; + char targetName[MAX_PLAYER_NAME_LENGTH]; }; CUtlVector checkpoints; CUtlVector onehops; diff --git a/mp/src/game/shared/momentum/mom_gamemovement.cpp b/mp/src/game/shared/momentum/mom_gamemovement.cpp index 1dd583b529..1a3fe6f432 100644 --- a/mp/src/game/shared/momentum/mom_gamemovement.cpp +++ b/mp/src/game/shared/momentum/mom_gamemovement.cpp @@ -3,22 +3,9 @@ #include "in_buttons.h" #include #include "movevars_shared.h" -#include "engine/IEngineTrace.h" -#include "SoundEmitterSystem/isoundemittersystembase.h" -#include "decals.h" -#include "coordsize.h" #include "tier0/memdbgon.h" -#define STOP_EPSILON 0.1 -#define MAX_CLIP_PLANES 5 - -#define STAMINA_MAX 100.0 -#define STAMINA_COST_JUMP 25.0 -#define STAMINA_COST_FALL 20.0 -#define STAMINA_RECOVER_RATE 19.0 -#define CS_WALK_SPEED 135.0f - extern bool g_bMovementOptimizations; // remove this eventually ConVar sv_ramp_fix("sv_ramp_fix", "1"); @@ -87,7 +74,7 @@ void CMomentumGameMovement::WalkMove() } BaseClass::WalkMove(); - CheckForLadders(player->GetGroundEntity() != NULL); + CheckForLadders(player->GetGroundEntity() != nullptr); } void CMomentumGameMovement::CheckForLadders(bool wasOnGround) @@ -192,7 +179,7 @@ bool CMomentumGameMovement::CanUnduck() VectorCopy(mv->GetAbsOrigin(), newOrigin); - if (player->GetGroundEntity() != NULL) + if (player->GetGroundEntity() != nullptr) { newOrigin += VEC_DUCK_HULL_MIN - VEC_HULL_MIN; } @@ -221,7 +208,7 @@ void CMomentumGameMovement::Duck(void) int buttonsReleased = buttonsChanged & mv->m_nOldButtons; // The changed ones which were previously down are "released" // Check to see if we are in the air. - bool bInAir = player->GetGroundEntity() == NULL && player->GetMoveType() != MOVETYPE_LADDER; + bool bInAir = player->GetGroundEntity() == nullptr && player->GetMoveType() != MOVETYPE_LADDER; if (mv->m_nButtons & IN_DUCK) { @@ -310,7 +297,7 @@ void CMomentumGameMovement::Duck(void) { // Finish ducking immediately if duck time is over or not on ground if ((duckseconds > TIME_TO_DUCK) || - (player->GetGroundEntity() == NULL) || + (player->GetGroundEntity() == nullptr) || alreadyDucked) { FinishDuck(); @@ -327,7 +314,7 @@ void CMomentumGameMovement::Duck(void) { // Try to unduck unless automovement is not allowed // NOTE: When not onground, you can always unduck - if (player->m_Local.m_bAllowAutoMovement || player->GetGroundEntity() == NULL) + if (player->m_Local.m_bAllowAutoMovement || player->GetGroundEntity() == nullptr) { if ((buttonsReleased & IN_DUCK) && (player->GetFlags() & FL_DUCKING)) { @@ -346,7 +333,7 @@ void CMomentumGameMovement::Duck(void) { // Finish ducking immediately if duck time is over or not on ground if ((duckseconds > TIME_TO_UNDUCK) || - (player->GetGroundEntity() == NULL)) + (player->GetGroundEntity() == nullptr)) { FinishUnDuck(); } @@ -379,7 +366,7 @@ void CMomentumGameMovement::FinishUnDuck(void) VectorCopy(mv->GetAbsOrigin(), newOrigin); - if (player->GetGroundEntity() != NULL) + if (player->GetGroundEntity() != nullptr) { newOrigin += VEC_DUCK_HULL_MIN - VEC_HULL_MIN; } @@ -427,7 +414,7 @@ void CMomentumGameMovement::FinishDuck(void) Vector org = mv->GetAbsOrigin(); - if (player->GetGroundEntity() != NULL) + if (player->GetGroundEntity() != nullptr) { org -= VEC_DUCK_HULL_MIN - VEC_HULL_MIN; } @@ -534,7 +521,7 @@ bool CMomentumGameMovement::CheckJumpButton() if (player->GetWaterLevel() >= 2) { // swimming, not jumping - SetGroundEntity(NULL); + SetGroundEntity(nullptr); if (player->GetWaterType() == CONTENTS_WATER) // We move up a certain amount mv->m_vecVelocity[2] = 100; @@ -553,7 +540,7 @@ bool CMomentumGameMovement::CheckJumpButton() } // No more effect - if (player->GetGroundEntity() == NULL) + if (player->GetGroundEntity() == nullptr) { mv->m_nOldButtons |= IN_JUMP; return false; // in air, so no effect @@ -692,7 +679,7 @@ void CMomentumGameMovement::CategorizePosition() if (bMovingUpRapidly || (bMovingUp && player->GetMoveType() == MOVETYPE_LADDER)) { - SetGroundEntity(NULL); + SetGroundEntity(nullptr); } else { @@ -707,7 +694,7 @@ void CMomentumGameMovement::CategorizePosition() if (!pm.m_pEnt || pm.plane.normal[2] < 0.7) { - SetGroundEntity(NULL); + SetGroundEntity(nullptr); // probably want to add a check for a +z velocity too! if ((mv->m_vecVelocity.z > 0.0f) && (player->GetMoveType() != MOVETYPE_NOCLIP)) @@ -813,7 +800,7 @@ void CMomentumGameMovement::FullWalkMove() CategorizePosition(); // If we are on ground, no downward velocity. - if (player->GetGroundEntity() != NULL) + if (player->GetGroundEntity() != nullptr) { mv->m_vecVelocity[2] = 0; } @@ -833,7 +820,7 @@ void CMomentumGameMovement::FullWalkMove() // Fricion is handled before we add in any base velocity. That way, if we are on a conveyor, // we don't slow when standing still, relative to the conveyor. - if (player->GetGroundEntity() != NULL) + if (player->GetGroundEntity() != nullptr) { mv->m_vecVelocity[2] = 0.0; Friction(); @@ -845,7 +832,7 @@ void CMomentumGameMovement::FullWalkMove() // By default assume we did the reflect for WalkMove() m_flReflectNormal = 1.0f; - if (player->GetGroundEntity() != NULL) + if (player->GetGroundEntity() != nullptr) { WalkMove(); } @@ -867,7 +854,7 @@ void CMomentumGameMovement::FullWalkMove() } // If we are on ground, no downward velocity. - if (player->GetGroundEntity() != NULL) + if (player->GetGroundEntity() != nullptr) { mv->m_vecVelocity[2] = 0; } @@ -928,7 +915,7 @@ void CMomentumGameMovement::AirMove(void) VectorAdd(mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity); m_flReflectNormal = NO_REFL_NORMAL_CHANGE; - TryPlayerMove(NULL, NULL); + TryPlayerMove(nullptr, nullptr); // Now pull the base velocity back out. Base velocity is set if you are on a moving object, like a conveyor (or maybe another monster?) VectorSubtract(mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity); @@ -944,7 +931,7 @@ void CMomentumGameMovement::DoLateReflect(void) m_flReflectNormal = 1.0f; - if (mv->m_vecVelocity.Length() == 0.0f || player->GetGroundEntity() != NULL) + if (mv->m_vecVelocity.Length() == 0.0f || player->GetGroundEntity() != nullptr) return; @@ -1129,7 +1116,7 @@ int CMomentumGameMovement::TryPlayerMove(Vector *pFirstDest, trace_t *pFirstTrac // and pressing forward and nobody was really using this bounce/reflection feature anyway... if (numplanes == 1 && player->GetMoveType() == MOVETYPE_WALK && - player->GetGroundEntity() == NULL) + player->GetGroundEntity() == nullptr) { //Vector cross = mv->m_vecVelocity.Cross(planes[0]); @@ -1284,11 +1271,10 @@ void CMomentumGameMovement::SetGroundEntity(trace_t *pm) void CMomentumGameMovement::CategorizeGroundSurface(trace_t &pm) { IPhysicsSurfaceProps *physprops = MoveHelper()->GetSurfaceProps(); - //CMomentumPlayer *player = GetMomentumPlayer(); player->m_surfaceProps = pm.surface.surfaceProps; player->m_pSurfaceData = physprops->GetSurfaceData(player->m_surfaceProps); - physprops->GetPhysicsProperties(player->m_surfaceProps, NULL, NULL, &player->m_surfaceFriction, NULL); + physprops->GetPhysicsProperties(player->m_surfaceProps, nullptr, nullptr, &player->m_surfaceFriction, nullptr); // HACKHACK: Scale this to fudge the relationship between vphysics friction values and player friction values. // A value of 0.8f feels pretty normal for vphysics, whereas 1.0f is normal for players. @@ -1330,9 +1316,81 @@ void CMomentumGameMovement::ReduceTimers(void) BaseClass::ReduceTimers(); } +//We're overriding this here so the game doesn't play any fall damage noises +void CMomentumGameMovement::CheckFalling(void) +{ + // this function really deals with landing, not falling, so early out otherwise + if (player->GetGroundEntity() == nullptr || player->m_Local.m_flFallVelocity <= 0) + return; + + if (!IsDead() && player->m_Local.m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHOLD) + { + bool bAlive = true; + float fvol = 0.5; + + if (player->GetWaterLevel() > 0) + { + // They landed in water. + } + else + { + // Scale it down if we landed on something that's floating... + if (player->GetGroundEntity()->IsFloating()) + { + player->m_Local.m_flFallVelocity -= PLAYER_LAND_ON_FLOATING_OBJECT; + } + + // + // They hit the ground. + // + if (player->GetGroundEntity()->GetAbsVelocity().z < 0.0f) + { + // Player landed on a descending object. Subtract the velocity of the ground entity. + player->m_Local.m_flFallVelocity += player->GetGroundEntity()->GetAbsVelocity().z; + player->m_Local.m_flFallVelocity = MAX(0.1f, player->m_Local.m_flFallVelocity); + } + + if (player->m_Local.m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED) + { + // + // If they hit the ground going this fast they may take damage (and die). + // + //NOTE: We override this here since this way we can play the noise without having to go to the MoveHelper + //MOM_TODO: Revisit if we want custom fall noises. + bAlive = true;//MoveHelper()->PlayerFallingDamage(); + fvol = 1.0; + } + else if (player->m_Local.m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2) + { + fvol = 0.85; + } + else if (player->m_Local.m_flFallVelocity < PLAYER_MIN_BOUNCE_SPEED) + { + fvol = 0; + } + } + + //MOM_TODO: This plays a step sound, revisit if we want to override + PlayerRoughLandingEffects(fvol); + + if (bAlive) + { + MoveHelper()->PlayerSetAnimation(PLAYER_WALK); + } + } + + // let any subclasses know that the player has landed and how hard + OnLand(player->m_Local.m_flFallVelocity); + + // + // Clear the fall velocity so the impact doesn't happen again. + // + player->m_Local.m_flFallVelocity = 0; +} + // Expose our interface. static CMomentumGameMovement g_GameMovement; -IGameMovement *g_pGameMovement = (IGameMovement *) &g_GameMovement; +IGameMovement *g_pGameMovement = static_cast(&g_GameMovement); EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CGameMovement, IGameMovement, INTERFACENAME_GAMEMOVEMENT, g_GameMovement); diff --git a/mp/src/game/shared/momentum/mom_gamemovement.h b/mp/src/game/shared/momentum/mom_gamemovement.h index b98ada9154..9d1b4f22a3 100644 --- a/mp/src/game/shared/momentum/mom_gamemovement.h +++ b/mp/src/game/shared/momentum/mom_gamemovement.h @@ -1,4 +1,6 @@ +#pragma once +#include "cbase.h" #include "gamemovement.h" #include "func_ladder.h" #include "mom_player_shared.h" @@ -10,6 +12,16 @@ struct surface_data_t; class CMomentumPlayer; #define NO_REFL_NORMAL_CHANGE -2.0f +#define STOP_EPSILON 0.1 +#define MAX_CLIP_PLANES 5 + +#define STAMINA_MAX 100.0 +#define STAMINA_COST_JUMP 25.0 +#define STAMINA_COST_FALL 20.0 +#define STAMINA_RECOVER_RATE 19.0 +#define CS_WALK_SPEED 135.0f + +#define DuckSpeedMultiplier 0.34f class CMomentumGameMovement : public CGameMovement { @@ -19,50 +31,54 @@ class CMomentumGameMovement : public CGameMovement CMomentumGameMovement(); // Overrides - virtual bool LadderMove(void); // REPLACED - virtual bool OnLadder(trace_t &trace); // REPLACED - virtual void SetGroundEntity(trace_t *pm); - virtual bool CanAccelerate(void) { BaseClass::CanAccelerate(); return true; }//C+P from HL2GM - virtual bool CheckJumpButton(void); - virtual void PlayerMove(void); - virtual void AirMove(void);//Overridden for rampboost fix - virtual void WalkMove(void); + bool LadderMove(void) override; // REPLACED + bool OnLadder(trace_t &trace) override; // REPLACED + void SetGroundEntity(trace_t *pm) override; + + bool CanAccelerate(void) override + { BaseClass::CanAccelerate(); return true; }//C+P from HL2GM + bool CheckJumpButton(void) override; + void PlayerMove(void) override; + void AirMove(void) override;//Overridden for rampboost fix + void WalkMove(void) override; virtual void CheckForLadders(bool); virtual void CategorizeGroundSurface(trace_t&); + //Override fall damage + void CheckFalling() override; //added ladder - virtual float LadderDistance(void) const + float LadderDistance(void) const override { if (player->GetMoveType() == MOVETYPE_LADDER) return 10.0f; return 2.0f; } - virtual unsigned int LadderMask(void) const + unsigned int LadderMask(void) const override { return MASK_PLAYERSOLID & (~CONTENTS_PLAYERCLIP); } - virtual float ClimbSpeed(void) const; - virtual float LadderLateralMultiplier(void) const; - const float DuckSpeedMultiplier = 0.34f; + float ClimbSpeed(void) const override; + float LadderLateralMultiplier(void) const override; + //const float DuckSpeedMultiplier = 0.34f; //Overrides for fixing rampboost - virtual int TryPlayerMove(Vector *pFirstDest = NULL, trace_t *pFirstTrace = NULL); - virtual void FullWalkMove(); + int TryPlayerMove(Vector *pFirstDest = nullptr, trace_t *pFirstTrace = nullptr) override; + void FullWalkMove() override; void DoLateReflect(); - void CategorizePosition(); + void CategorizePosition() override; // Duck - virtual void Duck(void); - virtual void FinishUnDuck(void); - virtual void FinishDuck(void); - virtual bool CanUnduck(); - virtual void HandleDuckingSpeedCrop(); - virtual void CheckParameters(void); - virtual void ReduceTimers(void); + void Duck(void) override; + void FinishUnDuck(void) override; + void FinishDuck(void) override; + bool CanUnduck() override; + void HandleDuckingSpeedCrop() override; + void CheckParameters(void) override; + void ReduceTimers(void) override; private: float m_flReflectNormal = NO_REFL_NORMAL_CHANGE;//Used by rampboost fix @@ -72,12 +88,12 @@ class CMomentumGameMovement : public CGameMovement // Debounce the +USE key void SwallowUseKey(); - CMomentumPlayer *GetMomentumPlayer(); + CMomentumPlayer *GetMomentumPlayer() const; }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -inline CMomentumPlayer *CMomentumGameMovement::GetMomentumPlayer() +inline CMomentumPlayer *CMomentumGameMovement::GetMomentumPlayer() const { return static_cast(player); -} \ No newline at end of file +} diff --git a/mp/src/game/shared/momentum/mom_gamerules.h b/mp/src/game/shared/momentum/mom_gamerules.h index 4b18fffbb8..a15b292b78 100644 --- a/mp/src/game/shared/momentum/mom_gamerules.h +++ b/mp/src/game/shared/momentum/mom_gamerules.h @@ -41,7 +41,7 @@ class CMomentum : public CSingleplayRules // virtual float GetAmmoDamage(CBaseEntity *pAttacker, CBaseEntity *pVictim, int nAmmoType); //Players take no damage - float FlPlayerFallDamage(CBasePlayer *pPlayer) override {return 0;} + float FlPlayerFallDamage(CBasePlayer *pPlayer) override {return 0.0f;} virtual bool AllowDamage( CBaseEntity *pVictim, const CTakeDamageInfo &info ) {return !pVictim->IsPlayer();} private: From cc34b6aff89a2d2d37c88b79ffae5a616cb227ea Mon Sep 17 00:00:00 2001 From: Nick K Date: Mon, 23 May 2016 02:30:24 -0400 Subject: [PATCH 043/101] Make crosshair off by default That was easy --- mp/src/game/client/hud_crosshair.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mp/src/game/client/hud_crosshair.cpp b/mp/src/game/client/hud_crosshair.cpp index fc7714fef3..cec7bd754c 100644 --- a/mp/src/game/client/hud_crosshair.cpp +++ b/mp/src/game/client/hud_crosshair.cpp @@ -29,8 +29,8 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" -ConVar crosshair( "crosshair", "1", FCVAR_ARCHIVE ); -ConVar cl_observercrosshair( "cl_observercrosshair", "1", FCVAR_ARCHIVE ); +ConVar crosshair( "crosshair", "0", FCVAR_ARCHIVE ); +ConVar cl_observercrosshair( "cl_observercrosshair", "0", FCVAR_ARCHIVE ); using namespace vgui; From 53ff2cf41e0d025d8fced64b595504c98217009b Mon Sep 17 00:00:00 2001 From: Nick K Date: Mon, 23 May 2016 04:06:04 -0400 Subject: [PATCH 044/101] +speed is not +walk Rid any reference to +speed --- mp/game/momentum/cfg/config_default.cfg | 5 ++--- mp/src/game/client/momentum/ui/hud_mapinfo.cpp | 3 +-- mp/src/game/shared/momentum/mom_gamemovement.cpp | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/mp/game/momentum/cfg/config_default.cfg b/mp/game/momentum/cfg/config_default.cfg index c84c0051cd..f92a2c3f18 100644 --- a/mp/game/momentum/cfg/config_default.cfg +++ b/mp/game/momentum/cfg/config_default.cfg @@ -15,8 +15,7 @@ bind "CTRL" "+duck" bind "e" "+use" bind "c" "impulse 50" bind "r" "+reload" -bind "ALT" "+walk" -bind "SHIFT" "+speed" +bind "SHIFT" "+walk" bind "MOUSE1" "+attack" bind "MOUSE2" "+attack2" bind "f" "impulse 100" @@ -39,7 +38,7 @@ bind "g" "phys_swap" bind "y" "messagemode" bind "u" "messagemode2" bind "k" "+voicerecord" -//Momentum releated +//Momentum related bind "F5" "mom_restart" bind "F6" "mom_reset" bind "z" "showCPmenu" diff --git a/mp/src/game/client/momentum/ui/hud_mapinfo.cpp b/mp/src/game/client/momentum/ui/hud_mapinfo.cpp index 2af1345dbf..4c36c52a96 100644 --- a/mp/src/game/client/momentum/ui/hud_mapinfo.cpp +++ b/mp/src/game/client/momentum/ui/hud_mapinfo.cpp @@ -79,8 +79,7 @@ class C_HudMapInfo : public CHudElement, public Panel bool m_bPlayerInZone, m_bMapFinished, m_bMapLinear; }; -// DECLARE_NAMED_HUDELEMENT(C_HudMapInfo, CHudMapInfo); - +//The below is basically DECLARE_NAMED_HUDELEMENT_DEPTH(C_HudMapInfo, CHudMapInfo, 10) static CHudElement *Create_C_HudMapInfo(void) { return new C_HudMapInfo("CHudMapInfo"); } static CHudElementHelper g_C_HudMapInfo_Helper(Create_C_HudMapInfo, 10); diff --git a/mp/src/game/shared/momentum/mom_gamemovement.cpp b/mp/src/game/shared/momentum/mom_gamemovement.cpp index 1a3fe6f432..2fc20ee802 100644 --- a/mp/src/game/shared/momentum/mom_gamemovement.cpp +++ b/mp/src/game/shared/momentum/mom_gamemovement.cpp @@ -1292,7 +1292,7 @@ void CMomentumGameMovement::CheckParameters(void) QAngle v_angle; //shift-walking useful for some maps with tight jumps - if (mv->m_nButtons & IN_SPEED) + if (mv->m_nButtons & IN_WALK) { mv->m_flClientMaxSpeed = CS_WALK_SPEED; } From 39dce6ea73e8e5375b3c76d948b32c5ab2bf6cc9 Mon Sep 17 00:00:00 2001 From: tuxxi Date: Mon, 23 May 2016 13:03:07 -0700 Subject: [PATCH 045/101] time offset works 100% of the time and is applied to server time --- mp/src/game/server/momentum/Timer.cpp | 93 +++++++++++-------- mp/src/game/server/momentum/Timer.h | 19 ++-- mp/src/game/server/momentum/mom_triggers.cpp | 6 +- mp/src/game/shared/momentum/util/mom_util.cpp | 2 +- 4 files changed, 67 insertions(+), 53 deletions(-) diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index 2c7a9a3fc1..795dc08878 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -245,7 +245,12 @@ void CTimer::Stop(bool endTrigger /* = false */) //Save times locally too, regardless of SteamAPI condition Time t = Time(); - t.time_sec = static_cast(gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; + + float originalTime = static_cast(gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; + //apply precision fix, adding offset from start as well as subtracting offset from end. + t.time_sec = originalTime + m_flTickOffsetFix[1] - m_flTickOffsetFix[0]; + DevLog("Original time: %f\n Precision-Fixed time: %f\n", originalTime, t.time_sec); + t.tickrate = gpGlobals->interval_per_tick; t.flags = pPlayer->m_RunData.m_iRunFlags; time(&t.date); @@ -255,20 +260,6 @@ void CTimer::Stop(bool endTrigger /* = false */) SaveTime(); - /*@tuxxi: we cannot rename a file to a different location, and I don't know how to move a file using IFileStream. disabling this for now - char* recordingPath = ""; - //rename temp demo file to "playername_mapname_time" - char newRecordingName[MAX_PATH], newRecordingPath[MAX_PATH]; - Q_snprintf(newRecordingName, MAX_PATH, "%s_%s_%f.dem", pPlayer->GetPlayerName(), gpGlobals->mapname.ToCStr()); - V_ComposeFileName(recordingPath, newRecordingName, newRecordingPath, MAX_PATH); - V_FixSlashes(newRecordingName); - if (filesystem->FileExists("tempdemo.dem", "MOD")) - { - filesystem->RenameFile("tempdemo.dem", newRecordingPath, "MOD"); - } - else - Warning("Recording file doesn't exist, cannot rename!"); - */ } else if (runSaveEvent) //reset run saved status to false if we cant or didn't save { @@ -396,37 +387,54 @@ void CTimer::DispatchCheckpointMessage() } } -void CTimer::GetTickIntervalOffset(CMomentumPlayer* pPlayer, const int zoneType) +void CTimer::CalculateTickIntervalOffset(CMomentumPlayer* pPlayer, const int zoneType) { if (!pPlayer) return; Ray_t ray; - Vector vecForward, start, end, origin = pPlayer->GetAbsOrigin(), velocity = pPlayer->GetAbsVelocity(); - float len = velocity.Length2D();//Go forwards/backwards X units, not too far though (multiple triggers) - QAngle eyes = pPlayer->EyeAngles(); - eyes.x = 0;//We don't look at if they're looking up/down, we only care about horizontal direction here - AngleVectors(eyes, &vecForward); //Get the direction the player is looking - if (zoneType == 0) + Vector prevOrigin, origin, velocity = pPlayer->GetLocalVelocity(); + // Because trigger touch is calculated using colission hull rather than the player's origin (which is their world space center, + // this origin is actually the player's local origin offset by their colission hull (depending on which direction they are moving), + // so that we trace from the point in space where the player actually exited touch with the trigger, rather than their world center. + + if (zoneType == ZONETYPE_END) //ending zone or ending a stage { - //endzone has to have the ray _start_ before we entered the end zone, hence why we start with prevOrigin - //and trace "forwards" to our current origin, hitting the end trigger on the way. - start = Vector(origin.x - (velocity.x * gpGlobals->interval_per_tick), + if (velocity.x > 0 || velocity.y > 0) + origin = Vector (pPlayer->GetLocalOrigin().x + pPlayer->CollisionProp()->OBBMaxs().x, + pPlayer->GetLocalOrigin().y + pPlayer->CollisionProp()->OBBMaxs().y, + pPlayer->GetLocalOrigin().z ); + else + origin = pPlayer->GetLocalOrigin() + pPlayer->CollisionProp()->OBBMins(); + + // The previous origin is the origin "rewound" in time a single tick, scaled by player's current velocity + prevOrigin = Vector(origin.x - (velocity.x * gpGlobals->interval_per_tick), origin.y - (velocity.y * gpGlobals->interval_per_tick), origin.z - (velocity.z * gpGlobals->interval_per_tick)); - end = start + vecForward * len;//Trace forward to the end trigger - ray.Init(start, end); + //ending zones have to have the ray start _before_ we entered the zone bbox, hence why we start with prevOrigin + //and trace "forwards" to our current origin, hitting the trigger on the way. + ray.Init(prevOrigin, origin); + debugoverlay->AddLineOverlay(prevOrigin, origin, 0, 255, 0, true, 10.0f); CTimeTriggerTraceEnum endTriggerTraceEnum(&ray, pPlayer->GetAbsVelocity(), zoneType); enginetrace->EnumerateEntities(ray, true, &endTriggerTraceEnum); } - else if (zoneType == 1 || zoneType == 2) + else if (zoneType == ZONETYPE_START )//start zone and stages { - //Start/stage zones trace from outside the trigger, backwards - vecForward.Negate();//We want the opposite direction the player is facing, we're tracing backwards to the trigger! - start = origin;//The start for this is the player pos - end = start + vecForward * len;//Trace backwards to the start/stage trigger - ray.Init(start, end); - CTimeTriggerTraceEnum stageTriggerTraceEnum(&ray, pPlayer->GetAbsVelocity(), zoneType); - enginetrace->EnumerateEntities(ray, true, &stageTriggerTraceEnum); + if (velocity.x > 0 || velocity.y > 0) + origin = pPlayer->GetLocalOrigin() + pPlayer->CollisionProp()->OBBMins(); + else + origin = Vector(pPlayer->GetLocalOrigin().x + pPlayer->CollisionProp()->OBBMaxs().x, + pPlayer->GetLocalOrigin().y + pPlayer->CollisionProp()->OBBMaxs().y, + pPlayer->GetLocalOrigin().z); + + // The previous origin is the origin "rewound" in time a single tick, scaled by player's current velocity + prevOrigin = Vector(origin.x - (velocity.x * gpGlobals->interval_per_tick), + origin.y - (velocity.y * gpGlobals->interval_per_tick), + origin.z - (velocity.z * gpGlobals->interval_per_tick)); + + //Start/stage zones trace from outside the trigger, backwards, hitting the zone along the way + ray.Init(origin, prevOrigin); + CTimeTriggerTraceEnum startTriggerTraceEnum(&ray, pPlayer->GetAbsVelocity(), zoneType); + enginetrace->EnumerateEntities(ray, true, &startTriggerTraceEnum); } } @@ -440,22 +448,25 @@ bool CTimeTriggerTraceEnum::EnumEntity(IHandleEntity *pHandleEntity) if (pEnt->IsSolid()) return false; + if (Q_strnicmp(pEnt->GetClassname(), "trigger_momentum_", Q_strlen("trigger_momentum_"))) //if we aren't hitting a momentum trigger + return false; + enginetrace->ClipRayToEntity(*m_pRay, MASK_ALL, pHandleEntity, &tr); if (tr.fraction < 1.0f) // tr.fraction = 1.0 means the trace completed { - debugoverlay->AddLineOverlay(tr.startpos, tr.endpos, 255, 0, 0, true, 10.0f);//Draw a pretty line to further show + debugoverlay->AddLineOverlay(tr.startpos, tr.endpos, 255, 0, 0, true, 10.0f); float dist = tr.startpos.DistTo(tr.endpos); - DevLog("DIST: %f\n", dist); - float offset = dist / m_currVelocity.Length2D();//velocity = dist/time, so it follows that time = distance / velocity. - DevLog("Time offset: %f\n", offset); + DevLog("Distance to zone: %f\n", dist); + float offset = dist / m_currVelocity.Length();//velocity = dist/time, so it follows that time = distance / velocity. + DevLog("Time offset: %f\n", offset); int stage = m_iZoneType; - if (m_iZoneType == 2) stage = g_Timer->GetCurrentStageNumber(); + if (m_iZoneType == g_Timer->ZONETYPE_START) stage = g_Timer->GetCurrentStageNumber(); g_Timer->SetIntervalOffset(stage, offset); return true; } - DevLog("Didn't hit a zone trigger.\n"); + DevWarning("Didn't hit a zone trigger.\n"); return false; } diff --git a/mp/src/game/server/momentum/Timer.h b/mp/src/game/server/momentum/Timer.h index 51059ba949..718fea97a7 100644 --- a/mp/src/game/server/momentum/Timer.h +++ b/mp/src/game/server/momentum/Timer.h @@ -195,22 +195,23 @@ class CTimer int m_iCurrentStepCP = 0; bool m_bUsingCPMenu = false; - //PRECISION FIX + //PRECISION FIX: + // this works by adding the starting offset to the final time, since the timer starts after we actually exit the start trigger + // also, subtract the ending offset from the time, since we end after we actually enter the ending trigger float m_flTickOffsetFix[MAX_STAGES]; //index 0 = endzone, 1 = startzone, 2 = stage 2, 3 = stage3, etc - public: - float m_flTickIntervalOffsetOut; - - //creates fraction of a tick to be used as a time "offset" in precicely calculating the real run time. - //zone type: 0: endzone, 1: startzone, 2: stage - //void GetTickIntervalOffset(const Vector velocity, const Vector origin, const int zoneType); - void GetTickIntervalOffset(CMomentumPlayer *pPlayer, const int zoneType); - + //creates fraction of a tick to be used as a time "offset" in precicely calculating the real run time. + void CalculateTickIntervalOffset(CMomentumPlayer *pPlayer, const int zoneType); void SetIntervalOffset(int stage, float offset) { m_flTickOffsetFix[stage] = offset; } + typedef enum + { + ZONETYPE_END, + ZONETYPE_START + } zoneType; }; class CTimeTriggerTraceEnum : public IEntityEnumerator diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index 7bf1daca49..16fe3ce099 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -60,6 +60,8 @@ void CTriggerStage::StartTouch(CBaseEntity *pOther) stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum - 1][1]); gameeventmanager->FireEvent(stageEvent); + + g_Timer->CalculateTickIntervalOffset(pPlayer, g_Timer->ZONETYPE_END); } else { @@ -88,7 +90,7 @@ void CTriggerStage::EndTouch(CBaseEntity *pOther) if (stageNum == 1 || g_Timer->IsRunning())//Timer won't be running if it's the start trigger { //This handles both the start and stage triggers - g_Timer->GetTickIntervalOffset(pPlayer, stageNum); + g_Timer->CalculateTickIntervalOffset(pPlayer, g_Timer->ZONETYPE_START); IGameEvent *stageEvent = gameeventmanager->CreateEvent("stage_exit"); if (stageEvent) @@ -349,7 +351,7 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) else { DevLog("Previous origin is NOT inside the trigger, calculating offset...\n"); - g_Timer->GetTickIntervalOffset(pPlayer, 0); + g_Timer->CalculateTickIntervalOffset(pPlayer, g_Timer->ZONETYPE_END); } g_Timer->Stop(true); diff --git a/mp/src/game/shared/momentum/util/mom_util.cpp b/mp/src/game/shared/momentum/util/mom_util.cpp index d624451541..32e41db9e3 100644 --- a/mp/src/game/shared/momentum/util/mom_util.cpp +++ b/mp/src/game/shared/momentum/util/mom_util.cpp @@ -58,7 +58,7 @@ void MomentumUtil::PostTimeCallback(HTTPRequestCompleted_t *pCallback, bool bIOF DevLog("Outer is JSON OBJECT!\n"); JsonNode *node = val.toNode(); - DevLog("Outer has key %s with value %s\n", node->key, node->value.toString()); + DevLog("Outer has key %s with value %s\n", node->key, node->value); // MOM_TODO: This doesn't work, even if node has tag 'true'. Something is wrong with the way we are parsing // the JSON From 1e79707fae164169a6caaa4a57c7230f842ccdac Mon Sep 17 00:00:00 2001 From: tuxxi Date: Mon, 23 May 2016 13:43:46 -0700 Subject: [PATCH 046/101] incorperate time fix into stages, fix end zone logic. TODO: hud elements like mapfinished need this new precision-fixed time --- mp/src/game/server/momentum/Timer.cpp | 10 ++++------ mp/src/game/server/momentum/Timer.h | 8 +++++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index 795dc08878..e319575851 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -245,11 +245,7 @@ void CTimer::Stop(bool endTrigger /* = false */) //Save times locally too, regardless of SteamAPI condition Time t = Time(); - - float originalTime = static_cast(gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; - //apply precision fix, adding offset from start as well as subtracting offset from end. - t.time_sec = originalTime + m_flTickOffsetFix[1] - m_flTickOffsetFix[0]; - DevLog("Original time: %f\n Precision-Fixed time: %f\n", originalTime, t.time_sec); + t.time_sec = GetLastRunTime(); t.tickrate = gpGlobals->interval_per_tick; t.flags = pPlayer->m_RunData.m_iRunFlags; @@ -340,9 +336,11 @@ float CTimer::CalculateStageTime(int stage) { if (stage > m_iLastStage) { + float originalTime = static_cast(gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; //If the stage is a new one, we store the time we entered this stage in m_iStageEnterTime[stage] = stage == 1 ? 0.0f : //Always returns 0 for first stage. - static_cast(gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; + originalTime + m_flTickOffsetFix[stage-1]; + DevLog("Original Time: %f\n New Time: %f", originalTime, m_iStageEnterTime[stage]); } m_iLastStage = stage; return m_iStageEnterTime[stage]; diff --git a/mp/src/game/server/momentum/Timer.h b/mp/src/game/server/momentum/Timer.h index 718fea97a7..60db3a4d95 100644 --- a/mp/src/game/server/momentum/Timer.h +++ b/mp/src/game/server/momentum/Timer.h @@ -76,7 +76,13 @@ class CTimer // Gets the total stage count int GetStageCount() { return m_iStageCount; }; float CalculateStageTime(int stageNum); - float GetLastRunTime() { return (m_iEndTick - m_iStartTick) * gpGlobals->interval_per_tick; } + float GetLastRunTime() + { + float originalTime = static_cast(gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; + // apply precision fix, adding offset from start as well as subtracting offset from end. + // offset from end is 1 tick - fraction offset, since we started trace outside of the end zone. + return originalTime + m_flTickOffsetFix[1] - (gpGlobals->interval_per_tick - m_flTickOffsetFix[0]); + } //--------- CheckpointMenu stuff -------------------------------- // Gets the current menu checkpoint index From c4c15849d9022721889d4b0d00d46b03bf4b3103 Mon Sep 17 00:00:00 2001 From: Nick K Date: Mon, 23 May 2016 18:15:12 -0400 Subject: [PATCH 047/101] Update propdata to be like CS Fixes some prop behavior --- mp/game/momentum/scripts/propdata.txt | 275 +++++++++++++++++++++++++- mp/src/game/server/momentum/Timer.cpp | 2 +- 2 files changed, 270 insertions(+), 7 deletions(-) diff --git a/mp/game/momentum/scripts/propdata.txt b/mp/game/momentum/scripts/propdata.txt index 48c7f584f1..369bdea023 100644 --- a/mp/game/momentum/scripts/propdata.txt +++ b/mp/game/momentum/scripts/propdata.txt @@ -54,6 +54,13 @@ "health" "10" } + // Cardboard trash from props_junk still uses this. + "Cardboard.physics" + { + "base" "Cardboard.Base" + "health" "15" + } + // Cardboard / Paper blocks, less than 3 foot cubed. // i.e. cardboard boxes. thick books "Cardboard.Medium" @@ -68,6 +75,31 @@ { "base" "Cardboard.Base" "health" "40" + "physicsmode" "1" + } + + // Cardboard Boxes full of paper + // i.e. fileboxes, boxes of paper + "Cardboard.break" + { + "base" "Cardboard.Base" + "health" "10" + "physicsmode" "1" + } + // Cardboard / Paper blocks, less than 1 foot cubed. + // i.e. small cardboard boxes, notepads, newspapers, thin books. + "Cardboard.Indestructable" + { + "base" "Cardboard.Base" + } + + // Cardboard Boxes full of paper + // i.e. fileboxes, boxes of paper + "Cardboard.breakclient" + { + "base" "Cardboard.Base" + "health" "10" + "physicsmode" "3" } //================================================================================= @@ -104,9 +136,32 @@ { "base" "Cloth.Base" "health" "100" + "physicsmode" "1" } + // Misc cloth objects + // i.e. stuffed animals, hats, etc. + "Cloth.Object" + { + "base" "Cloth.Base" + "physicsmode" "3" + } + +//================================================================================= +// DOORS +//================================================================================= + // Base damage modifiers for doors + // All doors have the same propdata - metal/wood/etc is handled in the door QC! + "Door.Standard" + { + "dmg.bullets" "1.0" + "dmg.club" "1.25" + "dmg.explosive" "1.5" + "health" "1000" + } + + //================================================================================= // WOOD //================================================================================= @@ -128,8 +183,10 @@ { "base" "Wooden.Base" "health" "6" + "physicsmode" "3" "breakable_count" "0" + } // Wooden blocks, less than 1 foot cubed. @@ -138,31 +195,49 @@ { "base" "Wooden.Base" "health" "20" - + "breakable_count" "2" } + // Wooden chairs from c17 still use this entity + "Wooden.chair" + { + "base" "Wooden.Base" + "health" "25" + + "breakable_count" "4" + } + // Wooden blocks, less than 3 foot cubed. // i.e boards, small crates, pallettes, ladders, chairs. "Wooden.Medium" { "base" "Wooden.Base" "health" "30" - + "breakable_count" "4" } + + // Wooden blocks, less than 3 foot cubed. + // Client side, No break + "Wooden.MediumNobreak" + { + "base" "Wooden.Base" + "physicsmode" "3" + } + // Large wooden blocks, less than 5 foot cubed. // i.e Crates, benches. "Wooden.Large" { "base" "Wooden.Base" "health" "50" - + "physicsmode" "1" "breakable_count" "6" } - - // Extra large wooden objects + + // Extra large wooden objects // i.e posts "Wooden.ExtraLarge" { @@ -178,11 +253,54 @@ { "base" "Wooden.Base" "health" "130" + "physicsmode" "1" "breakable_count" "10" } + // small wooden objects + // i.e sticks + "Wooden.sticks" + { + "base" "Wooden.Base" + "physicsmode" "1" + "breakable_count" "0" + + } + // Breakable Barrels + // i.e Wine Barrel in de_Inferno + "Wooden.Barrel" + { + "base" "Wooden.Base" + "health" "50" + "physicsmode" "1" + + "breakable_count" "0" + + } + + // Wooden blocks, less than 1 foot cubed. + // i.e pieces of board, branches. (Server side version) + "Wooden.Small2" + { + "base" "Wooden.Base" + "health" "1" + "physicsmode" "1" + "breakable_count" "2" + } + + // Breakable Barrels + // i.e Wine Barrel in de_Inferno + "Wooden.Barrel2" + { + "base" "Wooden.Base" + "health" "201" + "physicsmode" "1" + + "breakable_count" "0" + + } //================================================================================= @@ -203,6 +321,7 @@ { "base" "Stone.Base" "health" "50" + "physicsmode" "3" } // Medium stone blocks, less than 3 foot cubed @@ -219,6 +338,7 @@ { "base" "Stone.Base" "health" "200" + "physicsmode" "1" } // Huge stone blocks, less than 5 foot cubed @@ -227,6 +347,16 @@ { "base" "Stone.Base" "health" "400" + "physicsmode" "1" + } + + // Gigantic stone blocks, more than 5 foot cubed + // i.e. only damaged by the bomb + "Stone.Gigantic" + { + "base" "Stone.Base" + "health" "600" + "physicsmode" "1" } //================================================================================= @@ -247,6 +377,7 @@ { "base" "Glass.Base" "health" "5" + "physicsmode" "3" "damage_table" "glass" } @@ -255,7 +386,39 @@ "base" "Glass.Base" "dmg.explosive" "1.0" // Override base glass explosive behavior "dmg.bullets" "0.5" - "health" "15" + "health" "1" + "physicsmode" "1" + "damage_table" "glass" + } + + "Glass.CSWindow" + { + "base" "Glass.Window" + "health" "1" // Shatter immediately, even from grenades. + + } + + "Glass.picture" + { + "base" "Glass.Base" + "physicsmode" "1" + } + "Glass.CSWindow2" + { + "base" "Glass.Window" + "health" "1" // Shatter immediately, even from grenades. + "physicsmode" "1" + + } + + + "Glass.WindowStrong" + { + "base" "Glass.Base" + "dmg.explosive" "1.0" // Override base glass explosive behavior + "dmg.bullets" "0.5" + "health" "100" + "physicsmode" "3" "damage_table" "glass" } @@ -286,6 +449,7 @@ "Metal.Medium" { "base" "Metal.Base" + "physicsmode" "1" } // Large metal objects. @@ -293,6 +457,31 @@ "Metal.Large" { "base" "Metal.Base" + "physicsmode" "1" + } + + // Breakable Medium Metal objects + "Metal.break" + { + "base" "Metal.Base" + "physicsmode" "1" + "health" "10" + } + + // Breakable Medium Metal objects + "Metal.break2" + { + "base" "Metal.Base" + "physicsmode" "1" + "health" "100" + } + + // Medium metal objects (Client Side). + // i.e. wheelbarrows, metal boxes, bicycles, barrels, ladders, filing cabinets. + "Metal.MediumClient" + { + "base" "Metal.Base" + "physicsmode" "3" } @@ -330,6 +519,46 @@ "Plastic.Large" { "base" "Plastic.Base" + "physicsmode" "1" + } + + + // Breakable medium plastic objects + "Plastic.break" + { + "base" "Plastic.Base" + "physicsmode" "1" + "health" "10" + } + + // Breakable small plastic objects + "PlasticSmall.break" + { + "base" "Plastic.Base" + "physicsmode" "3" + "health" "10" + } + + "PlasticSmall.NoBreak" + { + "base" "Plastic.Base" + "physicsmode" "3" + } + + + // Breakable small plastic objects + "Plastic.Small2" + { + "base" "Plastic.Base" + "physicsmode" "1" + } + + // Plastic blocks, less than 2 foot cubed + // i.e. plastic cups, plastic milk crates, phones + "Plastic.SmallClient" + { + "base" "Plastic.Base" + "physicsmode" "3" } @@ -367,6 +596,7 @@ "Item.Large" { "base" "Item.Base" + "physicsmode" "1" } @@ -405,6 +635,7 @@ { "base" "Pottery.Base" "health" "70" + "physicsmode" "1" } // Huge pottery objects. @@ -413,6 +644,38 @@ { "base" "Pottery.Base" "health" "100" + "physicsmode" "1" + } + + // Breakable small pottery + "Pottery.break" + { + "base" "Pottery.Base" + "physicsmode" "3" + "health" "20" + } + + // small potted plants + "Pottery.Plant" + { + "base" "Pottery.Base" + "physicsmode" "3" + } + + // Breakable small pottery (Server Side) + "Pottery.break2" + { + "base" "Pottery.Base" + "physicsmode" "1" + "health" "20" + } + + // small potted plants (Breakable) + "Pottery.PlantBreak" + { + "base" "Pottery.Base" + "physicsmode" "3" + "health" "20" } diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index e319575851..f5d470f308 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -276,7 +276,7 @@ void CTimer::Stop(bool endTrigger /* = false */) //stop replay recording if (g_ReplaySystem->IsRecording(pPlayer)) - g_ReplaySystem->StopRecording(pPlayer, !endTrigger, true); + g_ReplaySystem->StopRecording(pPlayer, !endTrigger, endTrigger); SetRunning(false); m_iEndTick = gpGlobals->tickcount; From c882329f74188a1c9b9b6f10a9fcc9fbc2cc45b5 Mon Sep 17 00:00:00 2001 From: tuxxi Date: Mon, 23 May 2016 16:04:44 -0700 Subject: [PATCH 048/101] updated timer stop event to include fixed time, now all hud elements should have their respective "fixed" times --- mp/game/momentum/resource/modevents.res | 1 + mp/src/game/client/momentum/c_mom_player.h | 3 -- .../client/momentum/mom_event_listener.cpp | 2 + .../game/client/momentum/mom_event_listener.h | 1 + .../client/momentum/ui/hud_mapfinished.cpp | 5 +- mp/src/game/client/momentum/ui/hud_timer.cpp | 2 - mp/src/game/server/momentum/Timer.cpp | 46 ++++++++++++++++--- mp/src/game/server/momentum/mom_player.cpp | 4 -- mp/src/game/server/momentum/mom_triggers.cpp | 33 ------------- 9 files changed, 44 insertions(+), 53 deletions(-) diff --git a/mp/game/momentum/resource/modevents.res b/mp/game/momentum/resource/modevents.res index 878fe9f717..62dd694896 100644 --- a/mp/game/momentum/resource/modevents.res +++ b/mp/game/momentum/resource/modevents.res @@ -29,6 +29,7 @@ { "timer_stopped" { + "time" "float" "avg_sync" "float" "avg_sync2" "float" "num_strafes" "short" diff --git a/mp/src/game/client/momentum/c_mom_player.h b/mp/src/game/client/momentum/c_mom_player.h index 401d83ecd3..c33547ff1d 100644 --- a/mp/src/game/client/momentum/c_mom_player.h +++ b/mp/src/game/client/momentum/c_mom_player.h @@ -46,9 +46,6 @@ class C_MomentumPlayer : public C_BasePlayer CMOMRunEntityData m_RunData; - - float m_flLastRunTime; - void GetBulletTypeParameters( int iBulletType, float &fPenetrationPower, diff --git a/mp/src/game/client/momentum/mom_event_listener.cpp b/mp/src/game/client/momentum/mom_event_listener.cpp index 3ce956d8e8..ac58a3019c 100644 --- a/mp/src/game/client/momentum/mom_event_listener.cpp +++ b/mp/src/game/client/momentum/mom_event_listener.cpp @@ -32,6 +32,8 @@ void C_Momentum_EventListener::FireGameEvent(IGameEvent *pEvent) stats.m_flStageExitSpeed[0][1] = pEvent->GetFloat("end_vel_2D"); stats.m_flStageVelocityAvg[0][1] = pEvent->GetFloat("avg_vel_2D"); stats.m_flStageVelocityMax[0][1] = pEvent->GetFloat("max_vel_2D"); + + m_flLastRunTime = pEvent->GetFloat("time"); } else if (!Q_strcmp("stage_enter", pEvent->GetName())) { diff --git a/mp/src/game/client/momentum/mom_event_listener.h b/mp/src/game/client/momentum/mom_event_listener.h index 13cd637b84..c5322dc971 100644 --- a/mp/src/game/client/momentum/mom_event_listener.h +++ b/mp/src/game/client/momentum/mom_event_listener.h @@ -24,6 +24,7 @@ class C_Momentum_EventListener : public CGameEventListener int m_iMapCheckpointCount; RunStats_t stats;//MOM_TODO: Move this to the player and ghost ent send/recv table + float m_flLastRunTime; //this is the "adjusted" precision-fixed time value that was calculated on the server DLL char m_szRunUploadStatus[512];//MOM_TODO: determine best (max) size for this }; diff --git a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp index f99be2263d..13cd2dd170 100644 --- a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp +++ b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp @@ -314,8 +314,6 @@ void CHudMapFinishedDialog::Paint() } void CHudMapFinishedDialog::OnThink() { - C_MomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); - if (g_MOMEventListener) { m_bRunSaved = g_MOMEventListener->m_bTimeDidSave; @@ -333,7 +331,6 @@ void CHudMapFinishedDialog::OnThink() m_flAvgSync = g_MOMEventListener->stats.m_flStageStrafeSync2Avg[0]; m_iTotalJumps = g_MOMEventListener->stats.m_iStageJumps[0]; m_iTotalStrafes = g_MOMEventListener->stats.m_iStageStrafes[0]; + mom_UTIL->FormatTime(g_MOMEventListener->m_flLastRunTime, m_pszRunTime); } - if (pPlayer != nullptr) - mom_UTIL->FormatTime(pPlayer->m_flLastRunTime, m_pszRunTime); } \ No newline at end of file diff --git a/mp/src/game/client/momentum/ui/hud_timer.cpp b/mp/src/game/client/momentum/ui/hud_timer.cpp index 07acce9ac7..78da3d16d1 100644 --- a/mp/src/game/client/momentum/ui/hud_timer.cpp +++ b/mp/src/game/client/momentum/ui/hud_timer.cpp @@ -202,8 +202,6 @@ void C_Timer::MsgFunc_Timer_State(bf_read &msg) if (pPlayer != nullptr) { pPlayer->EmitSound("Momentum.StopTimer"); - pPlayer->m_flLastRunTime = - static_cast(gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; } // MOM_TODO: (Beta+) show scoreboard animation with new position on leaderboards? diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index f5d470f308..cac4e76117 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -173,12 +173,12 @@ void CTimer::SaveTime() pOverallKey->SetFloat("avgsync", t.RunStats.m_flStageStrafeSyncAvg[0]); pOverallKey->SetFloat("avgsync2", t.RunStats.m_flStageStrafeSync2Avg[0]); - pOverallKey->SetFloat("start_vel", t.RunStats.m_flStageEnterSpeed[0][0]); + pOverallKey->SetFloat("start_vel", t.RunStats.m_flStageEnterSpeed[1][0]); pOverallKey->SetFloat("end_vel", t.RunStats.m_flStageExitSpeed[0][0]); pOverallKey->SetFloat("avg_vel", t.RunStats.m_flStageVelocityAvg[0][0]); pOverallKey->SetFloat("max_vel", t.RunStats.m_flStageVelocityMax[0][0]); - pOverallKey->SetFloat("start_vel_2D", t.RunStats.m_flStageEnterSpeed[0][1]); + pOverallKey->SetFloat("start_vel_2D", t.RunStats.m_flStageEnterSpeed[1][1]); pOverallKey->SetFloat("end_vel_2D", t.RunStats.m_flStageExitSpeed[0][1]); pOverallKey->SetFloat("avg_vel_2D", t.RunStats.m_flStageVelocityAvg[0][1]); pOverallKey->SetFloat("max_vel_2D", t.RunStats.m_flStageVelocityMax[0][1]); @@ -234,7 +234,8 @@ void CTimer::Stop(bool endTrigger /* = false */) CMomentumPlayer *pPlayer = ToCMOMPlayer(UTIL_GetLocalPlayer()); IGameEvent *runSaveEvent = gameeventmanager->CreateEvent("run_save"); - IGameEvent *timeStopEvent = gameeventmanager->CreateEvent("timer_state"); + IGameEvent *timerStateEvent = gameeventmanager->CreateEvent("timer_state"); + IGameEvent *timerStopEvent = gameeventmanager->CreateEvent("timer_stopped"); if (endTrigger && !m_bWereCheatsActivated && pPlayer) { @@ -262,12 +263,43 @@ void CTimer::Stop(bool endTrigger /* = false */) runSaveEvent->SetBool("run_saved", false); gameeventmanager->FireEvent(runSaveEvent); } - if (timeStopEvent) + if (timerStateEvent) { - timeStopEvent->SetBool("is_running", false); - gameeventmanager->FireEvent(timeStopEvent); + timerStateEvent->SetBool("is_running", false); + gameeventmanager->FireEvent(timerStateEvent); + } + if (timerStopEvent && pPlayer) + { + timerStopEvent->SetFloat("avg_sync", pPlayer->m_PlayerRunStats.m_flStageStrafeSyncAvg[0]); + timerStopEvent->SetFloat("avg_sync2", pPlayer->m_PlayerRunStats.m_flStageStrafeSync2Avg[0]); + timerStopEvent->SetInt("num_strafes", pPlayer->m_PlayerRunStats.m_iStageStrafes[0]); + timerStopEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats.m_iStageJumps[0]); + + //3D VELCOCITY STATS - INDEX 0 + timerStopEvent->SetFloat("avg_vel", pPlayer->m_PlayerRunStats.m_flStageVelocityAvg[0][0]); + timerStopEvent->SetFloat("start_vel", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[1][0]); + float endvel = pPlayer->GetLocalVelocity().Length(); + timerStopEvent->SetFloat("end_vel", endvel); + if (endvel > pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][0]) + timerStopEvent->SetFloat("max_vel", endvel); + else + timerStopEvent->SetFloat("max_vel", pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][0]); + pPlayer->m_PlayerRunStats.m_flStageExitSpeed[0][0] = endvel; //we have to set end speed here or else it will be saved as 0 + + //2D VELOCITY STATS - INDEX 1 + timerStopEvent->SetFloat("avg_vel_2D", pPlayer->m_PlayerRunStats.m_flStageVelocityAvg[0][1]); + timerStopEvent->SetFloat("start_vel_2D", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[1][1]); + float endvel2D = pPlayer->GetLocalVelocity().Length2D(); + timerStopEvent->SetFloat("end_vel_2D", endvel2D); + if (endvel2D > pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][1]) + timerStopEvent->SetFloat("max_vel_2D", endvel2D); + else + timerStopEvent->SetFloat("max_vel_2D", pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][1]); + pPlayer->m_PlayerRunStats.m_flStageExitSpeed[0][1] = endvel2D; + + timerStopEvent->SetFloat("time", GetLastRunTime()); + gameeventmanager->FireEvent(timerStopEvent); } - if (pPlayer) { pPlayer->m_RunData.m_bIsInZone = endTrigger; diff --git a/mp/src/game/server/momentum/mom_player.cpp b/mp/src/game/server/momentum/mom_player.cpp index b251d8fa01..de843ca7a8 100644 --- a/mp/src/game/server/momentum/mom_player.cpp +++ b/mp/src/game/server/momentum/mom_player.cpp @@ -231,7 +231,6 @@ void CMomentumPlayer::CheckForBhop() void CMomentumPlayer::UpdateRunStats() { - // should velocity be XY or XYZ? IGameEvent *playerMoveEvent = gameeventmanager->CreateEvent("keypress"); float velocity = GetLocalVelocity().Length(); float velocity2D = GetLocalVelocity().Length2D(); @@ -241,9 +240,6 @@ void CMomentumPlayer::UpdateRunStats() int currentStage = g_Timer->GetCurrentStageNumber(); if (!m_bPrevTimerRunning) // timer started on this tick { - // Reset old run stats -- moved to on start's touch - m_PlayerRunStats.m_flStageEnterSpeed[0][0] = velocity; - m_PlayerRunStats.m_flStageEnterSpeed[0][1] = velocity2D; // Compare against successive bhops to avoid incrimenting when the player was in the air without jumping // (for surf) if (GetGroundEntity() == nullptr && m_iSuccessiveBhops) diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index 16fe3ce099..c86ac83808 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -277,7 +277,6 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) { CMomentumPlayer *pPlayer = ToCMOMPlayer(pOther); - IGameEvent *timerStopEvent = gameeventmanager->CreateEvent("timer_stopped"); IGameEvent *stageEvent = gameeventmanager->CreateEvent("stage_enter"); // If timer is already stopped, there's nothing to stop (No run state effect to play) @@ -286,38 +285,6 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) g_Timer->SetEndTrigger(this); if (g_Timer->IsRunning() && !pPlayer->IsWatchingReplay()) { - //send run stats via GameEventManager - if (timerStopEvent) - { - timerStopEvent->SetFloat("avg_sync", pPlayer->m_PlayerRunStats.m_flStageStrafeSyncAvg[0]); - timerStopEvent->SetFloat("avg_sync2", pPlayer->m_PlayerRunStats.m_flStageStrafeSync2Avg[0]); - timerStopEvent->SetInt("num_strafes", pPlayer->m_PlayerRunStats.m_iStageStrafes[0]); - timerStopEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats.m_iStageJumps[0]); - - //3D VELCOCITY STATS - INDEX 0 - timerStopEvent->SetFloat("avg_vel", pPlayer->m_PlayerRunStats.m_flStageVelocityAvg[0][0]); - timerStopEvent->SetFloat("start_vel", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[0][0]); - float endvel = pPlayer->GetLocalVelocity().Length(); - timerStopEvent->SetFloat("end_vel", endvel); - if (endvel > pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][0]) - timerStopEvent->SetFloat("max_vel", endvel); - else - timerStopEvent->SetFloat("max_vel", pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][0]); - pPlayer->m_PlayerRunStats.m_flStageExitSpeed[0][0] = endvel; //we have to set end speed here or else it will be saved as 0 - - //2D VELOCITY STATS - INDEX 1 - timerStopEvent->SetFloat("avg_vel_2D", pPlayer->m_PlayerRunStats.m_flStageVelocityAvg[0][1]); - timerStopEvent->SetFloat("start_vel_2D", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[0][1]); - float endvel2D = pPlayer->GetLocalVelocity().Length2D(); - timerStopEvent->SetFloat("end_vel_2D", endvel2D); - if (endvel2D > pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][1]) - timerStopEvent->SetFloat("max_vel_2D", endvel2D); - else - timerStopEvent->SetFloat("max_vel_2D", pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][1]); - pPlayer->m_PlayerRunStats.m_flStageExitSpeed[0][1] = endvel2D; - gameeventmanager->FireEvent(timerStopEvent); - } - if (stageEvent) { //The last stage is a bit of a doozy. From 37a1c8fdd08ee565b1bf83091ca7da82e4620b72 Mon Sep 17 00:00:00 2001 From: tuxxi Date: Wed, 25 May 2016 17:40:37 -0700 Subject: [PATCH 049/101] divided the header into a constant-size info table as well as a dynamic size run stats section. TODO: fix dynamic memory size allocation for run stats --- mp/src/game/server/momentum/mom_replay.cpp | 39 ++++++++++++++++++-- mp/src/game/server/momentum/mom_replay.h | 2 + mp/src/game/server/momentum/replayformat.h | 24 +++++++++--- mp/src/game/shared/momentum/util/run_stats.h | 7 ++-- 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index f28b30113e..96e1f562a7 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -72,6 +72,7 @@ void CMomentumReplaySystem::UpdateRecordingParams(CUtlBuffer *buf) replay_header_t CMomentumReplaySystem::CreateHeader() { replay_header_t header; + header.numZones = g_Timer->GetStageCount(); Q_strcpy(header.demofilestamp, REPLAY_HEADER_ID); header.demoProtoVersion = REPLAY_PROTOCOL_VERSION; Q_strcpy(header.mapName, gpGlobals->mapname.ToCStr()); @@ -83,9 +84,30 @@ replay_header_t CMomentumReplaySystem::CreateHeader() header.runTime = g_Timer->GetLastRunTime(); time(&header.unixEpocDate); - header.stats = m_player->m_PlayerRunStats; //copy ALL run stats using operator overload return header; } +replay_stats_t CMomentumReplaySystem::CreateStats() +{ + replay_stats_t runStats = replay_stats_t(g_Timer->GetStageCount()); + + for (int i = 0; i < runStats.arraySize; i++) + { + runStats.stats.m_iStageJumps[i] = m_player->m_PlayerRunStats.m_iStageJumps[i]; + runStats.stats.m_iStageStrafes[i] = m_player->m_PlayerRunStats.m_iStageStrafes[i]; + runStats.stats.m_flStageStrafeSyncAvg[i] = m_player->m_PlayerRunStats.m_flStageStrafeSyncAvg[i]; + runStats.stats.m_flStageStrafeSync2Avg[i] = m_player->m_PlayerRunStats.m_flStageStrafeSync2Avg[i]; + runStats.stats.m_flStageEnterTime[i] = m_player->m_PlayerRunStats.m_flStageEnterTime[i]; + runStats.stats.m_flStageTime[i] = m_player->m_PlayerRunStats.m_flStageTime[i]; + for (int k = 0; k < 2; k++) + { + runStats.stats.m_flStageVelocityMax[i][k] = m_player->m_PlayerRunStats.m_flStageVelocityMax[i][k]; + runStats.stats.m_flStageVelocityAvg[i][k] = m_player->m_PlayerRunStats.m_flStageVelocityAvg[i][k]; + runStats.stats.m_flStageEnterSpeed[i][k] = m_player->m_PlayerRunStats.m_flStageEnterSpeed[i][k]; + runStats.stats.m_flStageExitSpeed[i][k] = m_player->m_PlayerRunStats.m_flStageExitSpeed[i][k]; + } + } + return runStats; +} void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer *buf) { if (m_fhFileHandle) @@ -96,11 +118,17 @@ void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer *buf) filesystem->Seek(m_fhFileHandle, 0, FILESYSTEM_SEEK_HEAD); filesystem->Write(&littleEndianHeader, sizeof(replay_header_t), m_fhFileHandle); - DevLog("\n\nreplay header size: %i\n", sizeof(replay_header_t)); + DevLog("replay header size: %i\n", sizeof(replay_header_t)); + + replay_stats_t littleEndianStats = CreateStats(); + ByteSwap_replay_stats_t(littleEndianStats); + filesystem->Write(&littleEndianStats, sizeof(littleEndianStats), m_fhFileHandle); + DevLog("replay stats size: %i\n", sizeof(littleEndianStats)); Assert(buf && buf->IsValid()); //write write from the CUtilBuffer to our filehandle: filesystem->Write(buf->Base(), buf->TellPut(), m_fhFileHandle); + DevLog("replay frame data size: %i\n", buf->Size()); buf->Purge(); } } @@ -115,14 +143,17 @@ replay_frame_t* CMomentumReplaySystem::ReadSingleFrame(FileHandle_t file, const } replay_header_t* CMomentumReplaySystem::ReadHeader(FileHandle_t file, const char* filename) { - Q_memset(&m_replayHeader, 0, sizeof(m_replayHeader)); + V_memset(&m_replayHeader, 0, sizeof(m_replayHeader)); + V_memset(&m_replayStats, 0, sizeof(m_replayStats)); Assert(file != FILESYSTEM_INVALID_HANDLE); filesystem->Seek(file, 0, FILESYSTEM_SEEK_HEAD); filesystem->Read(&m_replayHeader, sizeof(replay_header_t), file); - ByteSwap_replay_header_t(m_replayHeader); + filesystem->Read(&m_replayStats, sizeof(RunStats_t(m_replayHeader.numZones)) + sizeof(int), file); + ByteSwap_replay_stats_t(m_replayStats); + if (Q_strcmp(m_replayHeader.demofilestamp, REPLAY_HEADER_ID)) { //DEMO_HEADER_ID is __NOT__ the same as the stamp from the header we read from file ConMsg("%s has invalid replay header ID.\n", filename); return nullptr; diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index 83482df3c0..1c8e9ec992 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -41,6 +41,7 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame void StopRecording(CBasePlayer *pPlayer, bool throwaway, bool delay); void WriteRecordingToFile(CUtlBuffer *buf); replay_header_t CreateHeader(); + replay_stats_t CreateStats(); replay_frame_t *ReadSingleFrame(FileHandle_t file, const char *filename); replay_header_t *ReadHeader(FileHandle_t file, const char *filename); @@ -71,6 +72,7 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame replay_frame_t m_currentFrame; replay_header_t m_replayHeader; + replay_stats_t m_replayStats; FileHandle_t m_fhFileHandle; CUtlBuffer m_buf; diff --git a/mp/src/game/server/momentum/replayformat.h b/mp/src/game/server/momentum/replayformat.h index d220f46fbd..bca8974404 100644 --- a/mp/src/game/server/momentum/replayformat.h +++ b/mp/src/game/server/momentum/replayformat.h @@ -6,7 +6,7 @@ #include "util/run_stats.h" #define REPLAY_HEADER_ID "MOMREPLAY" -#define REPLAY_PROTOCOL_VERSION 2 +#define REPLAY_PROTOCOL_VERSION 3 //describes a single frame of a replay struct replay_frame_t @@ -35,9 +35,10 @@ inline void ByteSwap_replay_frame_t(replay_frame_t &swap) swap.m_nPlayerButtons = LittleDWord(swap.m_nPlayerButtons); } -//the replay header, stores a bunch of information about the replay as well as the run stats for that replay +//the replay header, stores a bunch of information about the replay struct replay_header_t { + int numZones; //the number of zones, controls the size of the runstats struct array char demofilestamp[9]; //should be REPLAY_HEADER_ID int demoProtoVersion; //should be REPLAY_PROTOCOL_VERSION time_t unixEpocDate; //redundant date check @@ -46,8 +47,6 @@ struct replay_header_t uint64 steamID64; float interval_per_tick; float runTime; - - RunStats_t stats; //a massive ammount of run stats all stored in the header. }; //byteswap for int and float members of header, swaps the endianness (byte order) in order to read correctly inline void ByteSwap_replay_header_t(replay_header_t &swap) @@ -57,10 +56,23 @@ inline void ByteSwap_replay_header_t(replay_header_t &swap) swap.steamID64 = LittleLong(swap.steamID64); LittleFloat(&swap.interval_per_tick, &swap.interval_per_tick); LittleFloat(&swap.runTime, &swap.runTime); - //MOM_TODO: Do we want to also have a float time? +} +struct replay_stats_t +{ + replay_stats_t() {} + replay_stats_t(int size) + { + stats = RunStats_t(size); + arraySize = size; + } - for (int i = 0; i < MAX_STAGES; i++) + RunStats_t stats; + int arraySize; +}; +inline void ByteSwap_replay_stats_t(replay_stats_t &swap) +{ + for (int i = 0; i < swap.arraySize; i++) { LittleFloat(&swap.stats.m_flStageEnterTime[i], &swap.stats.m_flStageEnterTime[i]); LittleFloat(&swap.stats.m_flStageTime[i], &swap.stats.m_flStageTime[i]); diff --git a/mp/src/game/shared/momentum/util/run_stats.h b/mp/src/game/shared/momentum/util/run_stats.h index d879f4c3a5..228e128c03 100644 --- a/mp/src/game/shared/momentum/util/run_stats.h +++ b/mp/src/game/shared/momentum/util/run_stats.h @@ -5,9 +5,9 @@ struct RunStats_t { - RunStats_t() + RunStats_t(int size = MAX_STAGES) { - for (int i = 0; i < MAX_STAGES; i++) + for (int i = 0; i < size; i++) { m_iStageJumps[i] = 0; m_iStageStrafes[i] = 0; @@ -44,12 +44,13 @@ struct RunStats_t } return *this; } + float niggers[]; + niggers = float[MAX_STAGES]; //MOM_TODO: We're going to hold an unbiased view at both //checkpoint and stages. If a map is linear yet has checkpoints, //it can be free to use these below to display stats for the player to compare against. //Note: Passing 0 as the index to any of these will return overall. - //Keypress int m_iStageJumps[MAX_STAGES],//Amount of jumps per stage m_iStageStrafes[MAX_STAGES];//Amount of strafes per stage From 2080ff3b3428ab3fc21f65728cba6bd04bc5cc86 Mon Sep 17 00:00:00 2001 From: tuxxi Date: Thu, 26 May 2016 02:23:07 -0700 Subject: [PATCH 050/101] array members of RunStats_t now have dynamically allocated size, which should reduce filesize interestingly, it doesnt look like it is actually reducing filesize, even though the RunStats portion of the header is now 1/100th of the size it was before for most maps TODO: fix destructor and deletion --- mp/src/game/server/momentum/Timer.cpp | 4 +- mp/src/game/server/momentum/Timer.h | 2 +- mp/src/game/shared/momentum/util/run_stats.h | 91 ++++++++++++++++---- 3 files changed, 78 insertions(+), 19 deletions(-) diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index cac4e76117..6e44d63bf8 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -239,6 +239,8 @@ void CTimer::Stop(bool endTrigger /* = false */) if (endTrigger && !m_bWereCheatsActivated && pPlayer) { + m_iEndTick = gpGlobals->tickcount; + // Post time to leaderboards if they're online // and if cheats haven't been turned on this session if (SteamAPI_IsSteamRunning()) @@ -311,9 +313,7 @@ void CTimer::Stop(bool endTrigger /* = false */) g_ReplaySystem->StopRecording(pPlayer, !endTrigger, endTrigger); SetRunning(false); - m_iEndTick = gpGlobals->tickcount; DispatchStateMessage(); - m_iEndTick = gpGlobals->tickcount; } void CTimer::OnMapEnd(const char *pMapName) { diff --git a/mp/src/game/server/momentum/Timer.h b/mp/src/game/server/momentum/Timer.h index 60db3a4d95..5c0a87287e 100644 --- a/mp/src/game/server/momentum/Timer.h +++ b/mp/src/game/server/momentum/Timer.h @@ -78,7 +78,7 @@ class CTimer float CalculateStageTime(int stageNum); float GetLastRunTime() { - float originalTime = static_cast(gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; + float originalTime = static_cast(m_iEndTick - m_iStartTick) * gpGlobals->interval_per_tick; // apply precision fix, adding offset from start as well as subtracting offset from end. // offset from end is 1 tick - fraction offset, since we started trace outside of the end zone. return originalTime + m_flTickOffsetFix[1] - (gpGlobals->interval_per_tick - m_flTickOffsetFix[0]); diff --git a/mp/src/game/shared/momentum/util/run_stats.h b/mp/src/game/shared/momentum/util/run_stats.h index 228e128c03..8cdb5fb690 100644 --- a/mp/src/game/shared/momentum/util/run_stats.h +++ b/mp/src/game/shared/momentum/util/run_stats.h @@ -7,33 +7,89 @@ struct RunStats_t { RunStats_t(int size = MAX_STAGES) { + m_iTotalStages = size; + //dynamically allocate the size of the run stats + m_iStageJumps = new int[size]; + m_iStageStrafes = new int[size]; + + m_flStageStrafeSyncAvg = new float[size]; + m_flStageStrafeSync2Avg = new float[size]; + m_flStageEnterTime = new float[size]; + m_flStageTime = new float[size]; + + //the 2d arrays are basically an array of pointers to arrays. + m_flStageEnterSpeed = new float*[size]; + m_flStageExitSpeed = new float*[size]; + m_flStageVelocityMax = new float*[size]; + m_flStageVelocityAvg = new float*[size]; + + for (int m = 0; m < size; ++m) + { + m_flStageEnterSpeed[m] = new float[2]; + m_flStageExitSpeed[m] = new float[2]; + m_flStageVelocityMax[m] = new float[2]; + m_flStageVelocityAvg[m] = new float[2]; + } + + //initialize everything to 0 for (int i = 0; i < size; i++) { m_iStageJumps[i] = 0; m_iStageStrafes[i] = 0; + m_flStageStrafeSyncAvg[i] = 0; m_flStageStrafeSync2Avg[i] = 0; m_flStageEnterTime[i] = 0; m_flStageTime[i] = 0; for (int k = 0; k < 2; k++) { - m_flStageVelocityMax[i][k] = 0; - m_flStageVelocityAvg[i][k] = 0; m_flStageEnterSpeed[i][k] = 0; m_flStageExitSpeed[i][k] = 0; + m_flStageVelocityMax[i][k] = 0; + m_flStageVelocityAvg[i][k] = 0; } } } + /* + MOM_TODO: + @tuxxi: this somehow breaks future memory allocation and will cause issues when trying + to access m_flVelocityAvg in a newly created RunStats object. No idea why. Commenting out for now, FIX BEFORE RELEASE!!!! + + ~RunStats_t() + { + delete[] m_iStageJumps; + delete[] m_iStageStrafes; + + delete[] m_flStageEnterTime; + delete[] m_flStageTime; + delete[] m_flStageStrafeSyncAvg; + delete[] m_flStageStrafeSync2Avg; + + for (int i = 0; i < m_iTotalStages; ++i) + { + delete[] m_flStageEnterSpeed[i]; + delete[] m_flStageExitSpeed[i]; + delete[] m_flStageVelocityMax[i]; + delete[] m_flStageVelocityAvg[i]; + } + delete[] m_flStageEnterSpeed; + delete[] m_flStageExitSpeed; + delete[] m_flStageVelocityMax; + delete[] m_flStageVelocityAvg; + } + */ RunStats_t& operator=(const RunStats_t& other) { - for (int i = 0; i < MAX_STAGES; i++) + for (int i = 0; i < other.m_iTotalStages; i++) { m_iStageJumps[i] = other.m_iStageJumps[i]; m_iStageStrafes[i] = other.m_iStageStrafes[i]; + m_flStageStrafeSyncAvg[i] = other.m_flStageStrafeSyncAvg[i]; m_flStageStrafeSync2Avg[i] = other.m_flStageStrafeSync2Avg[i]; m_flStageEnterTime[i] = other.m_flStageEnterTime[i]; m_flStageTime[i] = other.m_flStageTime[i]; + for (int k = 0; k < 2; k++) { m_flStageVelocityMax[i][k] = other.m_flStageVelocityMax[i][k]; @@ -44,29 +100,32 @@ struct RunStats_t } return *this; } - float niggers[]; - niggers = float[MAX_STAGES]; //MOM_TODO: We're going to hold an unbiased view at both //checkpoint and stages. If a map is linear yet has checkpoints, //it can be free to use these below to display stats for the player to compare against. - //Note: Passing 0 as the index to any of these will return overall. + + //Note: These are by initally created as null pointers, but the constructor will by default create arrays of size MAX_STAGES + //Note: Passing 0 as the index to any of these will return the overall stat, i.e during the entire run. + + int m_iTotalStages = MAX_STAGES; //required for the operator= overload + //Keypress - int m_iStageJumps[MAX_STAGES],//Amount of jumps per stage - m_iStageStrafes[MAX_STAGES];//Amount of strafes per stage + int *m_iStageJumps = nullptr, //Amount of jumps per stage + *m_iStageStrafes = nullptr; //Amount of strafes per stage //Time - float m_flStageTime[MAX_STAGES], //The amount of time (seconds) you spent to accomplish (stage) -> (stage + 1) - m_flStageEnterTime[MAX_STAGES]; //The time in seconds that you entered the given stage + float *m_flStageTime = nullptr, //The amount of time (seconds) you spent to accomplish (stage) -> (stage + 1) + *m_flStageEnterTime = nullptr; //The time in seconds that you entered the given stage //Sync - float m_flStageStrafeSyncAvg[MAX_STAGES],//The average sync1 you had over the given stage - m_flStageStrafeSync2Avg[MAX_STAGES];//The average sync2 you had over the given stage + float *m_flStageStrafeSyncAvg = nullptr, //The average sync1 you had over the given stage + *m_flStageStrafeSync2Avg = nullptr; //The average sync2 you had over the given stage //Velocity //Note: The secondary index is as follows: 0 = 3D Velocity (z included), 1 = Horizontal (XY) Velocity - float m_flStageEnterSpeed[MAX_STAGES][2],//The velocity with which you started the stage (exit this stage's start trigger) - m_flStageVelocityMax[MAX_STAGES][2],//Max velocity for a stage - m_flStageVelocityAvg[MAX_STAGES][2],//Average velocity in a stage - m_flStageExitSpeed[MAX_STAGES][2];//The velocity with which you exit the stage (this stage -> next) + float **m_flStageEnterSpeed = nullptr, //The velocity with which you started the stage (exit this stage's start trigger) + **m_flStageVelocityMax = nullptr, //Max velocity for a stage + **m_flStageVelocityAvg = nullptr, //Average velocity in a stage + **m_flStageExitSpeed = nullptr; //The velocity with which you exit the stage (this stage -> next) }; \ No newline at end of file From 285ab58deadc47462f77be1aa861010dd36279a1 Mon Sep 17 00:00:00 2001 From: Nick K Date: Fri, 27 May 2016 16:38:30 -0400 Subject: [PATCH 051/101] Revert and refactor Reverted to static memory, but dynamically set Refactored RunStats_t to be for zones, for upcoming linear map checkpoints support --- .../client/momentum/mom_event_listener.cpp | 56 +++--- .../client/momentum/ui/hud_comparisons.cpp | 20 +- .../game/client/momentum/ui/hud_keypress.cpp | 4 +- .../client/momentum/ui/hud_mapfinished.cpp | 16 +- mp/src/game/server/momentum/Timer.cpp | 154 ++++++++------- mp/src/game/server/momentum/Timer.h | 6 +- mp/src/game/server/momentum/mom_player.cpp | 104 +++++----- mp/src/game/server/momentum/mom_player.h | 4 +- mp/src/game/server/momentum/mom_replay.cpp | 38 ++-- mp/src/game/server/momentum/mom_replay.h | 4 +- mp/src/game/server/momentum/mom_triggers.cpp | 42 ++-- mp/src/game/server/momentum/replayformat.h | 37 ++-- mp/src/game/shared/momentum/util/run_stats.h | 179 +++++++++--------- 13 files changed, 335 insertions(+), 329 deletions(-) diff --git a/mp/src/game/client/momentum/mom_event_listener.cpp b/mp/src/game/client/momentum/mom_event_listener.cpp index ac58a3019c..49405039ee 100644 --- a/mp/src/game/client/momentum/mom_event_listener.cpp +++ b/mp/src/game/client/momentum/mom_event_listener.cpp @@ -20,18 +20,18 @@ void C_Momentum_EventListener::FireGameEvent(IGameEvent *pEvent) { if (!Q_strcmp("timer_stopped", pEvent->GetName())) { - stats.m_flStageStrafeSyncAvg[0] = pEvent->GetFloat("avg_sync"); - stats.m_flStageStrafeSync2Avg[0] = pEvent->GetFloat("avg_sync2"); + stats.m_flZoneStrafeSyncAvg[0] = pEvent->GetFloat("avg_sync"); + stats.m_flZoneStrafeSync2Avg[0] = pEvent->GetFloat("avg_sync2"); //3D - stats.m_flStageEnterSpeed[0][0] = pEvent->GetFloat("start_vel"); - stats.m_flStageExitSpeed[0][0] = pEvent->GetFloat("end_vel"); - stats.m_flStageVelocityAvg[0][0] = pEvent->GetFloat("avg_vel"); - stats.m_flStageVelocityMax[0][0] = pEvent->GetFloat("max_vel"); + stats.m_flZoneEnterSpeed[0][0] = pEvent->GetFloat("start_vel"); + stats.m_flZoneExitSpeed[0][0] = pEvent->GetFloat("end_vel"); + stats.m_flZoneVelocityAvg[0][0] = pEvent->GetFloat("avg_vel"); + stats.m_flZoneVelocityMax[0][0] = pEvent->GetFloat("max_vel"); //2D - stats.m_flStageEnterSpeed[0][1] = pEvent->GetFloat("start_vel_2D"); - stats.m_flStageExitSpeed[0][1] = pEvent->GetFloat("end_vel_2D"); - stats.m_flStageVelocityAvg[0][1] = pEvent->GetFloat("avg_vel_2D"); - stats.m_flStageVelocityMax[0][1] = pEvent->GetFloat("max_vel_2D"); + stats.m_flZoneEnterSpeed[0][1] = pEvent->GetFloat("start_vel_2D"); + stats.m_flZoneExitSpeed[0][1] = pEvent->GetFloat("end_vel_2D"); + stats.m_flZoneVelocityAvg[0][1] = pEvent->GetFloat("avg_vel_2D"); + stats.m_flZoneVelocityMax[0][1] = pEvent->GetFloat("max_vel_2D"); m_flLastRunTime = pEvent->GetFloat("time"); } @@ -42,29 +42,29 @@ void C_Momentum_EventListener::FireGameEvent(IGameEvent *pEvent) int currentStage = pEvent->GetInt("stage_num"); //Note: stage_enter_time will NOT change upon multiple entries to the same stage trigger (only set once per run) - stats.m_flStageEnterTime[currentStage] = pEvent->GetFloat("stage_enter_time"); + stats.m_flZoneEnterTime[currentStage] = pEvent->GetFloat("stage_enter_time"); //Reset the stage enter speed for the speedometer - stats.m_flStageEnterSpeed[currentStage][0] = 0.0f; - stats.m_flStageEnterSpeed[currentStage][1] = 0.0f; + stats.m_flZoneEnterSpeed[currentStage][0] = 0.0f; + stats.m_flZoneEnterSpeed[currentStage][1] = 0.0f; if (currentStage > 1) //MOM_TODO: || m_iStageCount < 2 (linear maps use checkpoints?) { //The first stage doesn't have its time yet, we calculate it upon going into stage 2+ - stats.m_flStageTime[currentStage - 1] = stats.m_flStageEnterTime[currentStage] - stats.m_flStageEnterTime[currentStage - 1]; + stats.m_flZoneTime[currentStage - 1] = stats.m_flZoneEnterTime[currentStage] - stats.m_flZoneEnterTime[currentStage - 1]; //And the rest of the stats are about the previous stage anyways, not calculated during stage 1 (start) - stats.m_flStageStrafeSyncAvg[currentStage - 1] = pEvent->GetFloat("avg_sync"); - stats.m_flStageStrafeSync2Avg[currentStage - 1] = pEvent->GetFloat("avg_sync2"); + stats.m_flZoneStrafeSyncAvg[currentStage - 1] = pEvent->GetFloat("avg_sync"); + stats.m_flZoneStrafeSync2Avg[currentStage - 1] = pEvent->GetFloat("avg_sync2"); - stats.m_flStageExitSpeed[currentStage - 1][0] = pEvent->GetFloat("stage_exit_vel"); - stats.m_flStageVelocityAvg[currentStage - 1][0] = pEvent->GetFloat("avg_vel"); - stats.m_flStageVelocityMax[currentStage - 1][0] = pEvent->GetFloat("max_vel"); + stats.m_flZoneExitSpeed[currentStage - 1][0] = pEvent->GetFloat("stage_exit_vel"); + stats.m_flZoneVelocityAvg[currentStage - 1][0] = pEvent->GetFloat("avg_vel"); + stats.m_flZoneVelocityMax[currentStage - 1][0] = pEvent->GetFloat("max_vel"); - stats.m_flStageExitSpeed[currentStage - 1][1] = pEvent->GetFloat("stage_exit_vel_2D"); - stats.m_flStageVelocityAvg[currentStage - 1][1] = pEvent->GetFloat("avg_vel_2D"); - stats.m_flStageVelocityMax[currentStage - 1][1] = pEvent->GetFloat("max_vel_2D"); + stats.m_flZoneExitSpeed[currentStage - 1][1] = pEvent->GetFloat("stage_exit_vel_2D"); + stats.m_flZoneVelocityAvg[currentStage - 1][1] = pEvent->GetFloat("avg_vel_2D"); + stats.m_flZoneVelocityMax[currentStage - 1][1] = pEvent->GetFloat("max_vel_2D"); - stats.m_iStageJumps[currentStage - 1] = pEvent->GetInt("num_jumps"); - stats.m_iStageStrafes[currentStage - 1] = pEvent->GetInt("num_strafes"); + stats.m_iZoneJumps[currentStage - 1] = pEvent->GetInt("num_jumps"); + stats.m_iZoneStrafes[currentStage - 1] = pEvent->GetInt("num_strafes"); } } else if (!Q_strcmp("stage_exit", pEvent->GetName())) @@ -76,9 +76,9 @@ void C_Momentum_EventListener::FireGameEvent(IGameEvent *pEvent) for (int i = 0; i < 2; i++) { float vel = i == 0 ? enterVel : enterVel2D; - stats.m_flStageEnterSpeed[currentStage][i] = vel; + stats.m_flZoneEnterSpeed[currentStage][i] = vel; if (currentStage == 1) - stats.m_flStageEnterSpeed[currentStage - 1][i] = vel;//Set overall enter vel + stats.m_flZoneEnterSpeed[currentStage - 1][i] = vel;//Set overall enter vel } } else if (!Q_strcmp("run_save", pEvent->GetName())) @@ -97,8 +97,8 @@ void C_Momentum_EventListener::FireGameEvent(IGameEvent *pEvent) } else if (!Q_strcmp("keypress", pEvent->GetName())) { - stats.m_iStageJumps[0] = pEvent->GetInt("num_jumps"); - stats.m_iStageStrafes[0] = pEvent->GetInt("num_strafes"); + stats.m_iZoneJumps[0] = pEvent->GetInt("num_jumps"); + stats.m_iZoneStrafes[0] = pEvent->GetInt("num_strafes"); } else if (!Q_strcmp("map_init", pEvent->GetName())) { diff --git a/mp/src/game/client/momentum/ui/hud_comparisons.cpp b/mp/src/game/client/momentum/ui/hud_comparisons.cpp index ffdf0e7232..e59733123f 100644 --- a/mp/src/game/client/momentum/ui/hud_comparisons.cpp +++ b/mp/src/game/client/momentum/ui/hud_comparisons.cpp @@ -269,8 +269,8 @@ void C_RunComparisons::GetComparisonString(ComparisonString_t type, int stage, c case TIME_OVERALL: case STAGE_TIME: // Get the time difference in seconds. - act = type == TIME_OVERALL ? g_MOMEventListener->stats.m_flStageEnterTime[stage + 1] - : g_MOMEventListener->stats.m_flStageTime[stage]; + act = type == TIME_OVERALL ? g_MOMEventListener->stats.m_flZoneEnterTime[stage + 1] + : g_MOMEventListener->stats.m_flZoneTime[stage]; if (m_bLoadedComparison) diff = act - (type == TIME_OVERALL ? m_rcCurrentComparison->overallSplits[stage] @@ -286,43 +286,43 @@ void C_RunComparisons::GetComparisonString(ComparisonString_t type, int stage, c break; case VELOCITY_AVERAGE: // Get the vel difference - act = g_MOMEventListener->stats.m_flStageVelocityAvg[stage][velType]; + act = g_MOMEventListener->stats.m_flZoneVelocityAvg[stage][velType]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageAvgVels[velType][stage - 1]; //- 1 due to array indexing (0 is stage 1) break; case VELOCITY_EXIT: - act = g_MOMEventListener->stats.m_flStageExitSpeed[stage][velType]; + act = g_MOMEventListener->stats.m_flZoneExitSpeed[stage][velType]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageExitVels[velType][stage - 1]; break; case VELOCITY_MAX: - act = g_MOMEventListener->stats.m_flStageVelocityMax[stage][velType]; + act = g_MOMEventListener->stats.m_flZoneVelocityMax[stage][velType]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageMaxVels[velType][stage - 1]; break; case VELOCITY_ENTER: - act = g_MOMEventListener->stats.m_flStageEnterSpeed[stage][velType]; + act = g_MOMEventListener->stats.m_flZoneEnterSpeed[stage][velType]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageEnterVels[velType][stage - 1]; break; case STAGE_SYNC1: - act = g_MOMEventListener->stats.m_flStageStrafeSyncAvg[stage]; + act = g_MOMEventListener->stats.m_flZoneStrafeSyncAvg[stage]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageAvgSync1[stage - 1]; break; case STAGE_SYNC2: - act = g_MOMEventListener->stats.m_flStageStrafeSync2Avg[stage]; + act = g_MOMEventListener->stats.m_flZoneStrafeSync2Avg[stage]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageAvgSync2[stage - 1]; break; case STAGE_JUMPS: - act = g_MOMEventListener->stats.m_iStageJumps[stage]; + act = g_MOMEventListener->stats.m_iZoneJumps[stage]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageJumps[stage - 1]; break; case STAGE_STRAFES: - act = g_MOMEventListener->stats.m_iStageStrafes[stage]; + act = g_MOMEventListener->stats.m_iZoneStrafes[stage]; if (m_bLoadedComparison) diff = act - m_rcCurrentComparison->stageStrafes[stage - 1]; break; diff --git a/mp/src/game/client/momentum/ui/hud_keypress.cpp b/mp/src/game/client/momentum/ui/hud_keypress.cpp index 8fa563df72..d9d35aacd0 100644 --- a/mp/src/game/client/momentum/ui/hud_keypress.cpp +++ b/mp/src/game/client/momentum/ui/hud_keypress.cpp @@ -206,8 +206,8 @@ void CHudKeyPressDisplay::OnThink() { //we should only draw the strafe/jump counters when the timer is running //MOM_TODO: Update this so that the replay ent also correctly sets these m_bShouldDrawCounts = g_MOMEventListener->m_bTimerIsRunning; - m_nStrafes = g_MOMEventListener->stats.m_iStageStrafes[0]; - m_nJumps = g_MOMEventListener->stats.m_iStageJumps[0]; + m_nStrafes = g_MOMEventListener->stats.m_iZoneStrafes[0]; + m_nJumps = g_MOMEventListener->stats.m_iZoneJumps[0]; } } } diff --git a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp index 13cd2dd170..0545541540 100644 --- a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp +++ b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp @@ -323,14 +323,14 @@ void CHudMapFinishedDialog::OnThink() ConVarRef hvel("mom_speedometer_hvel"); //MOM_TODO: Are we going to update to read replay file stats? - m_flAvgSpeed = g_MOMEventListener->stats.m_flStageVelocityAvg[0][hvel.GetBool()]; - m_flMaxSpeed = g_MOMEventListener->stats.m_flStageVelocityMax[0][hvel.GetBool()]; - m_flEndSpeed = g_MOMEventListener->stats.m_flStageExitSpeed[0][hvel.GetBool()]; - m_flStartSpeed = g_MOMEventListener->stats.m_flStageEnterSpeed[0][hvel.GetBool()]; - m_flAvgSync2 = g_MOMEventListener->stats.m_flStageStrafeSyncAvg[0]; - m_flAvgSync = g_MOMEventListener->stats.m_flStageStrafeSync2Avg[0]; - m_iTotalJumps = g_MOMEventListener->stats.m_iStageJumps[0]; - m_iTotalStrafes = g_MOMEventListener->stats.m_iStageStrafes[0]; + m_flAvgSpeed = g_MOMEventListener->stats.m_flZoneVelocityAvg[0][hvel.GetBool()]; + m_flMaxSpeed = g_MOMEventListener->stats.m_flZoneVelocityMax[0][hvel.GetBool()]; + m_flEndSpeed = g_MOMEventListener->stats.m_flZoneExitSpeed[0][hvel.GetBool()]; + m_flStartSpeed = g_MOMEventListener->stats.m_flZoneEnterSpeed[0][hvel.GetBool()]; + m_flAvgSync2 = g_MOMEventListener->stats.m_flZoneStrafeSyncAvg[0]; + m_flAvgSync = g_MOMEventListener->stats.m_flZoneStrafeSync2Avg[0]; + m_iTotalJumps = g_MOMEventListener->stats.m_iZoneJumps[0]; + m_iTotalStrafes = g_MOMEventListener->stats.m_iZoneStrafes[0]; mom_UTIL->FormatTime(g_MOMEventListener->m_flLastRunTime, m_pszRunTime); } } \ No newline at end of file diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index 6e44d63bf8..79dd2cd97c 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -95,47 +95,48 @@ void CTimer::LoadLocalTimes(const char *szMapname) t.tickrate = kv->GetFloat("rate"); t.date = static_cast(kv->GetInt("date")); t.flags = kv->GetInt("flags"); + t.RunStats = RunStats_t(GetStageCount()); for (KeyValues *subKv = kv->GetFirstSubKey(); subKv; subKv = subKv->GetNextKey()) { if (!Q_strnicmp(subKv->GetName(), "stage", strlen("stage"))) { int i = Q_atoi(subKv->GetName() + 6); //atoi will need to ignore "stage " and only return the stage number - t.RunStats.m_iStageJumps[i] = subKv->GetInt("num_jumps"); - t.RunStats.m_iStageStrafes[i] = subKv->GetInt("num_strafes"); - t.RunStats.m_flStageTime[i] = subKv->GetFloat("time"); - t.RunStats.m_flStageEnterTime[i] = subKv->GetFloat("enter_time"); - t.RunStats.m_flStageStrafeSyncAvg[i] = subKv->GetFloat("avg_sync"); - t.RunStats.m_flStageStrafeSync2Avg[i] = subKv->GetFloat("avg_sync2"); + t.RunStats.m_iZoneJumps[i] = subKv->GetInt("num_jumps"); + t.RunStats.m_iZoneStrafes[i] = subKv->GetInt("num_strafes"); + t.RunStats.m_flZoneTime[i] = subKv->GetFloat("time"); + t.RunStats.m_flZoneEnterTime[i] = subKv->GetFloat("enter_time"); + t.RunStats.m_flZoneStrafeSyncAvg[i] = subKv->GetFloat("avg_sync"); + t.RunStats.m_flZoneStrafeSync2Avg[i] = subKv->GetFloat("avg_sync2"); //3D Velocity Stats - t.RunStats.m_flStageVelocityAvg[i][0] = subKv->GetFloat("avg_vel"); - t.RunStats.m_flStageVelocityMax[i][0] = subKv->GetFloat("max_vel"); - t.RunStats.m_flStageEnterSpeed[i][0] = subKv->GetFloat("stage_enter_vel"); - t.RunStats.m_flStageExitSpeed[i][0] = subKv->GetFloat("stage_exit_vel"); + t.RunStats.m_flZoneVelocityAvg[i][0] = subKv->GetFloat("avg_vel"); + t.RunStats.m_flZoneVelocityMax[i][0] = subKv->GetFloat("max_vel"); + t.RunStats.m_flZoneEnterSpeed[i][0] = subKv->GetFloat("stage_enter_vel"); + t.RunStats.m_flZoneExitSpeed[i][0] = subKv->GetFloat("stage_exit_vel"); //2D Velocity Stats - t.RunStats.m_flStageVelocityAvg[i][1] = subKv->GetFloat("avg_vel_2D"); - t.RunStats.m_flStageVelocityMax[i][1] = subKv->GetFloat("max_vel_2D"); - t.RunStats.m_flStageEnterSpeed[i][1] = subKv->GetFloat("stage_enter_vel_2D"); - t.RunStats.m_flStageExitSpeed[i][1] = subKv->GetFloat("stage_exit_vel_2D"); + t.RunStats.m_flZoneVelocityAvg[i][1] = subKv->GetFloat("avg_vel_2D"); + t.RunStats.m_flZoneVelocityMax[i][1] = subKv->GetFloat("max_vel_2D"); + t.RunStats.m_flZoneEnterSpeed[i][1] = subKv->GetFloat("stage_enter_vel_2D"); + t.RunStats.m_flZoneExitSpeed[i][1] = subKv->GetFloat("stage_exit_vel_2D"); } if (!Q_strcmp(subKv->GetName(), "total")) { - t.RunStats.m_iStageJumps[0] = subKv->GetInt("jumps"); - t.RunStats.m_iStageStrafes[0] = subKv->GetInt("strafes"); - t.RunStats.m_flStageStrafeSyncAvg[0] = subKv->GetFloat("avgsync"); - t.RunStats.m_flStageStrafeSync2Avg[0] = subKv->GetFloat("avgsync2"); + t.RunStats.m_iZoneJumps[0] = subKv->GetInt("jumps"); + t.RunStats.m_iZoneStrafes[0] = subKv->GetInt("strafes"); + t.RunStats.m_flZoneStrafeSyncAvg[0] = subKv->GetFloat("avgsync"); + t.RunStats.m_flZoneStrafeSync2Avg[0] = subKv->GetFloat("avgsync2"); //3D - t.RunStats.m_flStageVelocityAvg[0][0] = subKv->GetFloat("avg_vel"); - t.RunStats.m_flStageVelocityMax[0][0] = subKv->GetFloat("max_vel"); - t.RunStats.m_flStageEnterSpeed[0][0] = subKv->GetFloat("start_vel"); - t.RunStats.m_flStageExitSpeed[0][0] = subKv->GetFloat("end_vel"); + t.RunStats.m_flZoneVelocityAvg[0][0] = subKv->GetFloat("avg_vel"); + t.RunStats.m_flZoneVelocityMax[0][0] = subKv->GetFloat("max_vel"); + t.RunStats.m_flZoneEnterSpeed[0][0] = subKv->GetFloat("start_vel"); + t.RunStats.m_flZoneExitSpeed[0][0] = subKv->GetFloat("end_vel"); //2D - t.RunStats.m_flStageVelocityAvg[0][1] = subKv->GetFloat("avg_vel_2D"); - t.RunStats.m_flStageVelocityMax[0][1] = subKv->GetFloat("max_vel_2D"); - t.RunStats.m_flStageEnterSpeed[0][1] = subKv->GetFloat("start_vel_2D"); - t.RunStats.m_flStageExitSpeed[0][1] = subKv->GetFloat("end_vel_2D"); + t.RunStats.m_flZoneVelocityAvg[0][1] = subKv->GetFloat("avg_vel_2D"); + t.RunStats.m_flZoneVelocityMax[0][1] = subKv->GetFloat("max_vel_2D"); + t.RunStats.m_flZoneEnterSpeed[0][1] = subKv->GetFloat("start_vel_2D"); + t.RunStats.m_flZoneExitSpeed[0][1] = subKv->GetFloat("end_vel_2D"); } } localTimes.AddToTail(t); @@ -168,45 +169,45 @@ void CTimer::SaveTime() pSubkey->SetInt("flags", t.flags); KeyValues *pOverallKey = new KeyValues("total"); - pOverallKey->SetInt("jumps", t.RunStats.m_iStageJumps[0]); - pOverallKey->SetInt("strafes", t.RunStats.m_iStageStrafes[0]); - pOverallKey->SetFloat("avgsync", t.RunStats.m_flStageStrafeSyncAvg[0]); - pOverallKey->SetFloat("avgsync2", t.RunStats.m_flStageStrafeSync2Avg[0]); + pOverallKey->SetInt("jumps", t.RunStats.m_iZoneJumps[0]); + pOverallKey->SetInt("strafes", t.RunStats.m_iZoneStrafes[0]); + pOverallKey->SetFloat("avgsync", t.RunStats.m_flZoneStrafeSyncAvg[0]); + pOverallKey->SetFloat("avgsync2", t.RunStats.m_flZoneStrafeSync2Avg[0]); - pOverallKey->SetFloat("start_vel", t.RunStats.m_flStageEnterSpeed[1][0]); - pOverallKey->SetFloat("end_vel", t.RunStats.m_flStageExitSpeed[0][0]); - pOverallKey->SetFloat("avg_vel", t.RunStats.m_flStageVelocityAvg[0][0]); - pOverallKey->SetFloat("max_vel", t.RunStats.m_flStageVelocityMax[0][0]); + pOverallKey->SetFloat("start_vel", t.RunStats.m_flZoneEnterSpeed[1][0]); + pOverallKey->SetFloat("end_vel", t.RunStats.m_flZoneExitSpeed[0][0]); + pOverallKey->SetFloat("avg_vel", t.RunStats.m_flZoneVelocityAvg[0][0]); + pOverallKey->SetFloat("max_vel", t.RunStats.m_flZoneVelocityMax[0][0]); - pOverallKey->SetFloat("start_vel_2D", t.RunStats.m_flStageEnterSpeed[1][1]); - pOverallKey->SetFloat("end_vel_2D", t.RunStats.m_flStageExitSpeed[0][1]); - pOverallKey->SetFloat("avg_vel_2D", t.RunStats.m_flStageVelocityAvg[0][1]); - pOverallKey->SetFloat("max_vel_2D", t.RunStats.m_flStageVelocityMax[0][1]); + pOverallKey->SetFloat("start_vel_2D", t.RunStats.m_flZoneEnterSpeed[1][1]); + pOverallKey->SetFloat("end_vel_2D", t.RunStats.m_flZoneExitSpeed[0][1]); + pOverallKey->SetFloat("avg_vel_2D", t.RunStats.m_flZoneVelocityAvg[0][1]); + pOverallKey->SetFloat("max_vel_2D", t.RunStats.m_flZoneVelocityMax[0][1]); char stageName[9]; // "stage 64\0" if (GetStageCount() > 1) { for (int i2 = 1; i2 <= GetStageCount(); i2++) { - Q_snprintf(stageName, sizeof(stageName), "stage %d", i2); + Q_snprintf(stageName, sizeof(stageName), "stage %d", i2);//MOM_TODO: || checkpoint %d KeyValues *pStageKey = new KeyValues(stageName); - pStageKey->SetFloat("time", t.RunStats.m_flStageTime[i2]); - pStageKey->SetFloat("enter_time", t.RunStats.m_flStageEnterTime[i2]); - pStageKey->SetInt("num_jumps", t.RunStats.m_iStageJumps[i2]); - pStageKey->SetInt("num_strafes", t.RunStats.m_iStageStrafes[i2]); - pStageKey->SetFloat("avg_sync", t.RunStats.m_flStageStrafeSyncAvg[i2]); - pStageKey->SetFloat("avg_sync2", t.RunStats.m_flStageStrafeSync2Avg[i2]); - - pStageKey->SetFloat("avg_vel", t.RunStats.m_flStageVelocityAvg[i2][0]); - pStageKey->SetFloat("max_vel", t.RunStats.m_flStageVelocityMax[i2][0]); - pStageKey->SetFloat("stage_enter_vel", t.RunStats.m_flStageEnterSpeed[i2][0]); - pStageKey->SetFloat("stage_exit_vel", t.RunStats.m_flStageExitSpeed[i2][0]); - - pStageKey->SetFloat("avg_vel_2D", t.RunStats.m_flStageVelocityAvg[i2][1]); - pStageKey->SetFloat("max_vel_2D", t.RunStats.m_flStageVelocityMax[i2][1]); - pStageKey->SetFloat("stage_enter_vel_2D", t.RunStats.m_flStageEnterSpeed[i2][1]); - pStageKey->SetFloat("stage_exit_vel_2D", t.RunStats.m_flStageExitSpeed[i2][1]); + pStageKey->SetFloat("time", t.RunStats.m_flZoneTime[i2]); + pStageKey->SetFloat("enter_time", t.RunStats.m_flZoneEnterTime[i2]); + pStageKey->SetInt("num_jumps", t.RunStats.m_iZoneJumps[i2]); + pStageKey->SetInt("num_strafes", t.RunStats.m_iZoneStrafes[i2]); + pStageKey->SetFloat("avg_sync", t.RunStats.m_flZoneStrafeSyncAvg[i2]); + pStageKey->SetFloat("avg_sync2", t.RunStats.m_flZoneStrafeSync2Avg[i2]); + + pStageKey->SetFloat("avg_vel", t.RunStats.m_flZoneVelocityAvg[i2][0]); + pStageKey->SetFloat("max_vel", t.RunStats.m_flZoneVelocityMax[i2][0]); + pStageKey->SetFloat("stage_enter_vel", t.RunStats.m_flZoneEnterSpeed[i2][0]); + pStageKey->SetFloat("stage_exit_vel", t.RunStats.m_flZoneExitSpeed[i2][0]); + + pStageKey->SetFloat("avg_vel_2D", t.RunStats.m_flZoneVelocityAvg[i2][1]); + pStageKey->SetFloat("max_vel_2D", t.RunStats.m_flZoneVelocityMax[i2][1]); + pStageKey->SetFloat("stage_enter_vel_2D", t.RunStats.m_flZoneEnterSpeed[i2][1]); + pStageKey->SetFloat("stage_exit_vel_2D", t.RunStats.m_flZoneExitSpeed[i2][1]); pSubkey->AddSubKey(pStageKey); } @@ -220,12 +221,17 @@ void CTimer::SaveTime() V_ComposeFileName(MAP_FOLDER, UTIL_VarArgs("%s%s", szMapName, EXT_TIME_FILE), file, MAX_PATH); - if (timesKV->SaveToFile(filesystem, file, "MOD", true) && runSaveEvent) + bool saved = false; + if (timesKV->SaveToFile(filesystem, file, "MOD", true)) { - runSaveEvent->SetBool("run_saved", true); - gameeventmanager->FireEvent(runSaveEvent); + saved = true; Log("Successfully saved new time!\n"); } + if (runSaveEvent) + { + runSaveEvent->SetBool("run_saved", saved); + gameeventmanager->FireEvent(runSaveEvent); + } timesKV->deleteThis(); //We don't need to delete sub KV pointers e.g. pSubkey because this destructor deletes all child nodes } @@ -272,32 +278,32 @@ void CTimer::Stop(bool endTrigger /* = false */) } if (timerStopEvent && pPlayer) { - timerStopEvent->SetFloat("avg_sync", pPlayer->m_PlayerRunStats.m_flStageStrafeSyncAvg[0]); - timerStopEvent->SetFloat("avg_sync2", pPlayer->m_PlayerRunStats.m_flStageStrafeSync2Avg[0]); - timerStopEvent->SetInt("num_strafes", pPlayer->m_PlayerRunStats.m_iStageStrafes[0]); - timerStopEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats.m_iStageJumps[0]); + timerStopEvent->SetFloat("avg_sync", pPlayer->m_PlayerRunStats.m_flZoneStrafeSyncAvg[0]); + timerStopEvent->SetFloat("avg_sync2", pPlayer->m_PlayerRunStats.m_flZoneStrafeSync2Avg[0]); + timerStopEvent->SetInt("num_strafes", pPlayer->m_PlayerRunStats.m_iZoneStrafes[0]); + timerStopEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats.m_iZoneJumps[0]); //3D VELCOCITY STATS - INDEX 0 - timerStopEvent->SetFloat("avg_vel", pPlayer->m_PlayerRunStats.m_flStageVelocityAvg[0][0]); - timerStopEvent->SetFloat("start_vel", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[1][0]); + timerStopEvent->SetFloat("avg_vel", pPlayer->m_PlayerRunStats.m_flZoneVelocityAvg[0][0]); + timerStopEvent->SetFloat("start_vel", pPlayer->m_PlayerRunStats.m_flZoneEnterSpeed[1][0]); float endvel = pPlayer->GetLocalVelocity().Length(); timerStopEvent->SetFloat("end_vel", endvel); - if (endvel > pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][0]) + if (endvel > pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[0][0]) timerStopEvent->SetFloat("max_vel", endvel); else - timerStopEvent->SetFloat("max_vel", pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][0]); - pPlayer->m_PlayerRunStats.m_flStageExitSpeed[0][0] = endvel; //we have to set end speed here or else it will be saved as 0 + timerStopEvent->SetFloat("max_vel", pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[0][0]); + pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[0][0] = endvel; //we have to set end speed here or else it will be saved as 0 //2D VELOCITY STATS - INDEX 1 - timerStopEvent->SetFloat("avg_vel_2D", pPlayer->m_PlayerRunStats.m_flStageVelocityAvg[0][1]); - timerStopEvent->SetFloat("start_vel_2D", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[1][1]); + timerStopEvent->SetFloat("avg_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneVelocityAvg[0][1]); + timerStopEvent->SetFloat("start_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneEnterSpeed[1][1]); float endvel2D = pPlayer->GetLocalVelocity().Length2D(); timerStopEvent->SetFloat("end_vel_2D", endvel2D); - if (endvel2D > pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][1]) + if (endvel2D > pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[0][1]) timerStopEvent->SetFloat("max_vel_2D", endvel2D); else - timerStopEvent->SetFloat("max_vel_2D", pPlayer->m_PlayerRunStats.m_flStageVelocityMax[0][1]); - pPlayer->m_PlayerRunStats.m_flStageExitSpeed[0][1] = endvel2D; + timerStopEvent->SetFloat("max_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[0][1]); + pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[0][1] = endvel2D; timerStopEvent->SetFloat("time", GetLastRunTime()); gameeventmanager->FireEvent(timerStopEvent); @@ -491,7 +497,7 @@ bool CTimeTriggerTraceEnum::EnumEntity(IHandleEntity *pHandleEntity) float offset = dist / m_currVelocity.Length();//velocity = dist/time, so it follows that time = distance / velocity. DevLog("Time offset: %f\n", offset); int stage = m_iZoneType; - if (m_iZoneType == g_Timer->ZONETYPE_START) stage = g_Timer->GetCurrentStageNumber(); + if (m_iZoneType == g_Timer->ZONETYPE_START) stage = g_Timer->GetCurrentZoneNumber(); g_Timer->SetIntervalOffset(stage, offset); return true; } @@ -562,6 +568,7 @@ void CTimer::CreateCheckpoint(CBasePlayer *pPlayer) c.pos = pPlayer->GetAbsOrigin(); c.vel = pPlayer->GetAbsVelocity(); Q_strncpy(c.targetName, pPlayer->GetEntityName().ToCStr(), MAX_PLAYER_NAME_LENGTH); + Q_strncpy(c.targetClassName, pPlayer->GetClassname(), MAX_PLAYER_NAME_LENGTH); checkpoints.AddToTail(c); m_iCurrentStepCP++; } @@ -578,6 +585,7 @@ void CTimer::TeleportToCP(CBasePlayer* cPlayer, int cpNum) if (checkpoints.IsEmpty() || !cPlayer) return; Checkpoint c = checkpoints[cpNum]; cPlayer->SetName(MAKE_STRING(c.targetName)); + cPlayer->SetClassname(c.targetClassName); cPlayer->Teleport(&c.pos, &c.ang, &c.vel); } diff --git a/mp/src/game/server/momentum/Timer.h b/mp/src/game/server/momentum/Timer.h index 5c0a87287e..3c8569db9a 100644 --- a/mp/src/game/server/momentum/Timer.h +++ b/mp/src/game/server/momentum/Timer.h @@ -68,7 +68,8 @@ class CTimer m_pCurrentStage.Set(pTrigger); //DispatchStageMessage(); } - int GetCurrentStageNumber() { return m_pCurrentStage.Get()->GetStageNumber(); } + int GetCurrentZoneNumber() const + { return m_pCurrentStage.Get() && m_pCurrentStage.Get()->GetStageNumber(); } // Calculates the stage count // Stores the result on m_iStageCount @@ -183,7 +184,7 @@ class CTimer int flags; //stage specific stats: - RunStats_t RunStats = RunStats_t(); + RunStats_t RunStats; }; struct Checkpoint @@ -192,6 +193,7 @@ class CTimer Vector vel; QAngle ang; char targetName[MAX_PLAYER_NAME_LENGTH]; + char targetClassName[MAX_PLAYER_NAME_LENGTH]; }; CUtlVector checkpoints; CUtlVector onehops; diff --git a/mp/src/game/server/momentum/mom_player.cpp b/mp/src/game/server/momentum/mom_player.cpp index de843ca7a8..7e398e2c33 100644 --- a/mp/src/game/server/momentum/mom_player.cpp +++ b/mp/src/game/server/momentum/mom_player.cpp @@ -217,9 +217,9 @@ void CMomentumPlayer::CheckForBhop() m_iSuccessiveBhops++; if (g_Timer->IsRunning()) { - int currentStage = g_Timer->GetCurrentStageNumber(); - m_PlayerRunStats.m_iStageJumps[0]++; - m_PlayerRunStats.m_iStageJumps[currentStage]++; + int currentZone = g_Timer->GetCurrentZoneNumber(); + m_PlayerRunStats.m_iZoneJumps[0]++; + m_PlayerRunStats.m_iZoneJumps[currentZone]++; } } } @@ -237,42 +237,42 @@ void CMomentumPlayer::UpdateRunStats() if (g_Timer->IsRunning()) { - int currentStage = g_Timer->GetCurrentStageNumber(); + int currentZone = g_Timer->GetCurrentZoneNumber(); if (!m_bPrevTimerRunning) // timer started on this tick { // Compare against successive bhops to avoid incrimenting when the player was in the air without jumping // (for surf) if (GetGroundEntity() == nullptr && m_iSuccessiveBhops) { - m_PlayerRunStats.m_iStageJumps[0]++; - m_PlayerRunStats.m_iStageJumps[currentStage]++; + m_PlayerRunStats.m_iZoneJumps[0]++; + m_PlayerRunStats.m_iZoneJumps[currentZone]++; } if (m_nButtons & IN_MOVERIGHT || m_nButtons & IN_MOVELEFT) { - m_PlayerRunStats.m_iStageStrafes[0]++; - m_PlayerRunStats.m_iStageStrafes[currentStage]++; + m_PlayerRunStats.m_iZoneStrafes[0]++; + m_PlayerRunStats.m_iZoneStrafes[currentZone]++; } } if (m_nButtons & IN_MOVELEFT && !(m_nPrevButtons & IN_MOVELEFT)) { - m_PlayerRunStats.m_iStageStrafes[0]++; - m_PlayerRunStats.m_iStageStrafes[currentStage]++; + m_PlayerRunStats.m_iZoneStrafes[0]++; + m_PlayerRunStats.m_iZoneStrafes[currentZone]++; } else if (m_nButtons & IN_MOVERIGHT && !(m_nPrevButtons & IN_MOVERIGHT)) { - m_PlayerRunStats.m_iStageStrafes[0]++; - m_PlayerRunStats.m_iStageStrafes[currentStage]++; + m_PlayerRunStats.m_iZoneStrafes[0]++; + m_PlayerRunStats.m_iZoneStrafes[currentZone]++; } // ---- MAX VELOCITY ---- - if (velocity > m_PlayerRunStats.m_flStageVelocityMax[0][0]) - m_PlayerRunStats.m_flStageVelocityMax[0][0] = velocity; - if (velocity2D > m_PlayerRunStats.m_flStageVelocityMax[0][1]) - m_PlayerRunStats.m_flStageVelocityMax[0][1] = velocity; + if (velocity > m_PlayerRunStats.m_flZoneVelocityMax[0][0]) + m_PlayerRunStats.m_flZoneVelocityMax[0][0] = velocity; + if (velocity2D > m_PlayerRunStats.m_flZoneVelocityMax[0][1]) + m_PlayerRunStats.m_flZoneVelocityMax[0][1] = velocity; // also do max velocity per stage - if (velocity > m_PlayerRunStats.m_flStageVelocityMax[currentStage][0]) - m_PlayerRunStats.m_flStageVelocityMax[currentStage][0] = velocity; - if (velocity2D > m_PlayerRunStats.m_flStageVelocityMax[currentStage][1]) - m_PlayerRunStats.m_flStageVelocityMax[currentStage][1] = velocity2D; + if (velocity > m_PlayerRunStats.m_flZoneVelocityMax[currentZone][0]) + m_PlayerRunStats.m_flZoneVelocityMax[currentZone][0] = velocity; + if (velocity2D > m_PlayerRunStats.m_flZoneVelocityMax[currentZone][1]) + m_PlayerRunStats.m_flZoneVelocityMax[currentZone][1] = velocity2D; // ---------- // ---- STRAFE SYNC ----- @@ -316,8 +316,8 @@ void CMomentumPlayer::UpdateRunStats() if (playerMoveEvent) { - playerMoveEvent->SetInt("num_strafes", m_PlayerRunStats.m_iStageStrafes[0]); - playerMoveEvent->SetInt("num_jumps", m_PlayerRunStats.m_iStageJumps[0]); + playerMoveEvent->SetInt("num_strafes", m_PlayerRunStats.m_iZoneStrafes[0]); + playerMoveEvent->SetInt("num_jumps", m_PlayerRunStats.m_iZoneJumps[0]); bool onGround = GetFlags() & FL_ONGROUND; if ((m_nButtons & IN_JUMP) && onGround || m_nButtons & (IN_MOVELEFT | IN_MOVERIGHT)) gameeventmanager->FireEvent(playerMoveEvent); @@ -333,47 +333,47 @@ void CMomentumPlayer::ResetRunStats() m_nAccelTicks = 0; m_RunData.m_flStrafeSync = 0; m_RunData.m_flStrafeSync2 = 0; - - m_PlayerRunStats = RunStats_t(); + //(&m_PlayerRunStats)->~RunStats_t();//MOM_TODO: Free the old memory?? + m_PlayerRunStats = RunStats_t(g_Timer->GetStageCount()); } void CMomentumPlayer::CalculateAverageStats() { if (g_Timer->IsRunning()) { - int currentStage = g_Timer->GetCurrentStageNumber(); + int currentZone = g_Timer->GetCurrentZoneNumber(); - m_flStageTotalSync[currentStage] += m_RunData.m_flStrafeSync; - m_flStageTotalSync2[currentStage] += m_RunData.m_flStrafeSync2; - m_flStageTotalVelocity[currentStage][0] += GetLocalVelocity().Length(); - m_flStageTotalVelocity[currentStage][1] += GetLocalVelocity().Length2D(); + m_flZoneTotalSync[currentZone] += m_RunData.m_flStrafeSync; + m_flZoneTotalSync2[currentZone] += m_RunData.m_flStrafeSync2; + m_flZoneTotalVelocity[currentZone][0] += GetLocalVelocity().Length(); + m_flZoneTotalVelocity[currentZone][1] += GetLocalVelocity().Length2D(); - m_nStageAvgCount[currentStage]++; + m_nZoneAvgCount[currentZone]++; - m_PlayerRunStats.m_flStageStrafeSyncAvg[currentStage] = - m_flStageTotalSync[currentStage] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats.m_flStageStrafeSync2Avg[currentStage] = - m_flStageTotalSync2[currentStage] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats.m_flStageVelocityAvg[currentStage][0] = - m_flStageTotalVelocity[currentStage][0] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats.m_flStageVelocityAvg[currentStage][1] = - m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); + m_PlayerRunStats.m_flZoneStrafeSyncAvg[currentZone] = + m_flZoneTotalSync[currentZone] / float(m_nZoneAvgCount[currentZone]); + m_PlayerRunStats.m_flZoneStrafeSync2Avg[currentZone] = + m_flZoneTotalSync2[currentZone] / float(m_nZoneAvgCount[currentZone]); + m_PlayerRunStats.m_flZoneVelocityAvg[currentZone][0] = + m_flZoneTotalVelocity[currentZone][0] / float(m_nZoneAvgCount[currentZone]); + m_PlayerRunStats.m_flZoneVelocityAvg[currentZone][1] = + m_flZoneTotalVelocity[currentZone][1] / float(m_nZoneAvgCount[currentZone]); // stage 0 is "overall" - also update these as well, no matter which stage we are on - m_flStageTotalSync[0] += m_RunData.m_flStrafeSync; - m_flStageTotalSync2[0] += m_RunData.m_flStrafeSync2; - m_flStageTotalVelocity[0][0] += GetLocalVelocity().Length(); - m_flStageTotalVelocity[0][1] += GetLocalVelocity().Length2D(); - m_nStageAvgCount[0]++; - - m_PlayerRunStats.m_flStageStrafeSyncAvg[0] = - m_flStageTotalSync[currentStage] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats.m_flStageStrafeSync2Avg[0] = - m_flStageTotalSync2[currentStage] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats.m_flStageVelocityAvg[0][0] = - m_flStageTotalVelocity[currentStage][0] / float(m_nStageAvgCount[currentStage]); - m_PlayerRunStats.m_flStageVelocityAvg[0][1] = - m_flStageTotalVelocity[currentStage][1] / float(m_nStageAvgCount[currentStage]); + m_flZoneTotalSync[0] += m_RunData.m_flStrafeSync; + m_flZoneTotalSync2[0] += m_RunData.m_flStrafeSync2; + m_flZoneTotalVelocity[0][0] += GetLocalVelocity().Length(); + m_flZoneTotalVelocity[0][1] += GetLocalVelocity().Length2D(); + m_nZoneAvgCount[0]++; + + m_PlayerRunStats.m_flZoneStrafeSyncAvg[0] = + m_flZoneTotalSync[currentZone] / float(m_nZoneAvgCount[currentZone]); + m_PlayerRunStats.m_flZoneStrafeSync2Avg[0] = + m_flZoneTotalSync2[currentZone] / float(m_nZoneAvgCount[currentZone]); + m_PlayerRunStats.m_flZoneVelocityAvg[0][0] = + m_flZoneTotalVelocity[currentZone][0] / float(m_nZoneAvgCount[currentZone]); + m_PlayerRunStats.m_flZoneVelocityAvg[0][1] = + m_flZoneTotalVelocity[currentZone][1] / float(m_nZoneAvgCount[currentZone]); } // think once per 0.1 second interval so we avoid making the totals extremely large diff --git a/mp/src/game/server/momentum/mom_player.h b/mp/src/game/server/momentum/mom_player.h index 137d0b7038..e64aac98a4 100644 --- a/mp/src/game/server/momentum/mom_player.h +++ b/mp/src/game/server/momentum/mom_player.h @@ -119,8 +119,8 @@ class CMomentumPlayer : public CBasePlayer RunStats_t m_PlayerRunStats; // for calc avg - int m_nStageAvgCount[MAX_STAGES]; - float m_flStageTotalSync[MAX_STAGES], m_flStageTotalSync2[MAX_STAGES], m_flStageTotalVelocity[MAX_STAGES][2]; + int m_nZoneAvgCount[MAX_STAGES]; + float m_flZoneTotalSync[MAX_STAGES], m_flZoneTotalSync2[MAX_STAGES], m_flZoneTotalVelocity[MAX_STAGES][2]; private: CountdownTimer m_ladderSurpressionTimer; diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 96e1f562a7..47069c85dd 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -86,24 +86,24 @@ replay_header_t CMomentumReplaySystem::CreateHeader() return header; } -replay_stats_t CMomentumReplaySystem::CreateStats() +RunStats_t CMomentumReplaySystem::CreateStats() { - replay_stats_t runStats = replay_stats_t(g_Timer->GetStageCount()); + RunStats_t runStats = RunStats_t(g_Timer->GetStageCount()); - for (int i = 0; i < runStats.arraySize; i++) + for (int i = 0; i < runStats.m_iTotalZones; i++) { - runStats.stats.m_iStageJumps[i] = m_player->m_PlayerRunStats.m_iStageJumps[i]; - runStats.stats.m_iStageStrafes[i] = m_player->m_PlayerRunStats.m_iStageStrafes[i]; - runStats.stats.m_flStageStrafeSyncAvg[i] = m_player->m_PlayerRunStats.m_flStageStrafeSyncAvg[i]; - runStats.stats.m_flStageStrafeSync2Avg[i] = m_player->m_PlayerRunStats.m_flStageStrafeSync2Avg[i]; - runStats.stats.m_flStageEnterTime[i] = m_player->m_PlayerRunStats.m_flStageEnterTime[i]; - runStats.stats.m_flStageTime[i] = m_player->m_PlayerRunStats.m_flStageTime[i]; + runStats.m_iZoneJumps[i] = m_player->m_PlayerRunStats.m_iZoneJumps[i]; + runStats.m_iZoneStrafes[i] = m_player->m_PlayerRunStats.m_iZoneStrafes[i]; + runStats.m_flZoneStrafeSyncAvg[i] = m_player->m_PlayerRunStats.m_flZoneStrafeSyncAvg[i]; + runStats.m_flZoneStrafeSync2Avg[i] = m_player->m_PlayerRunStats.m_flZoneStrafeSync2Avg[i]; + runStats.m_flZoneEnterTime[i] = m_player->m_PlayerRunStats.m_flZoneEnterTime[i]; + runStats.m_flZoneTime[i] = m_player->m_PlayerRunStats.m_flZoneTime[i]; for (int k = 0; k < 2; k++) { - runStats.stats.m_flStageVelocityMax[i][k] = m_player->m_PlayerRunStats.m_flStageVelocityMax[i][k]; - runStats.stats.m_flStageVelocityAvg[i][k] = m_player->m_PlayerRunStats.m_flStageVelocityAvg[i][k]; - runStats.stats.m_flStageEnterSpeed[i][k] = m_player->m_PlayerRunStats.m_flStageEnterSpeed[i][k]; - runStats.stats.m_flStageExitSpeed[i][k] = m_player->m_PlayerRunStats.m_flStageExitSpeed[i][k]; + runStats.m_flZoneVelocityMax[i][k] = m_player->m_PlayerRunStats.m_flZoneVelocityMax[i][k]; + runStats.m_flZoneVelocityAvg[i][k] = m_player->m_PlayerRunStats.m_flZoneVelocityAvg[i][k]; + runStats.m_flZoneEnterSpeed[i][k] = m_player->m_PlayerRunStats.m_flZoneEnterSpeed[i][k]; + runStats.m_flZoneExitSpeed[i][k] = m_player->m_PlayerRunStats.m_flZoneExitSpeed[i][k]; } } return runStats; @@ -120,9 +120,10 @@ void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer *buf) filesystem->Write(&littleEndianHeader, sizeof(replay_header_t), m_fhFileHandle); DevLog("replay header size: %i\n", sizeof(replay_header_t)); - replay_stats_t littleEndianStats = CreateStats(); + RunStats_t littleEndianStats = CreateStats(); ByteSwap_replay_stats_t(littleEndianStats); - filesystem->Write(&littleEndianStats, sizeof(littleEndianStats), m_fhFileHandle); + littleEndianStats.WriteToFile(filesystem, m_fhFileHandle); + //filesystem->Write(&littleEndianStats, sizeof(littleEndianStats), m_fhFileHandle); DevLog("replay stats size: %i\n", sizeof(littleEndianStats)); Assert(buf && buf->IsValid()); @@ -150,8 +151,13 @@ replay_header_t* CMomentumReplaySystem::ReadHeader(FileHandle_t file, const char filesystem->Seek(file, 0, FILESYSTEM_SEEK_HEAD); filesystem->Read(&m_replayHeader, sizeof(replay_header_t), file); ByteSwap_replay_header_t(m_replayHeader); + + //Create and read into the replayStats + m_replayStats = RunStats_t(m_replayHeader.numZones); + + m_replayStats.ReadFromFile(filesystem, file); + //filesystem->Read(&m_replayStats, sizeof(replay_stats_t), file); - filesystem->Read(&m_replayStats, sizeof(RunStats_t(m_replayHeader.numZones)) + sizeof(int), file); ByteSwap_replay_stats_t(m_replayStats); if (Q_strcmp(m_replayHeader.demofilestamp, REPLAY_HEADER_ID)) { //DEMO_HEADER_ID is __NOT__ the same as the stamp from the header we read from file diff --git a/mp/src/game/server/momentum/mom_replay.h b/mp/src/game/server/momentum/mom_replay.h index 1c8e9ec992..0ad6b33fe9 100644 --- a/mp/src/game/server/momentum/mom_replay.h +++ b/mp/src/game/server/momentum/mom_replay.h @@ -41,7 +41,7 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame void StopRecording(CBasePlayer *pPlayer, bool throwaway, bool delay); void WriteRecordingToFile(CUtlBuffer *buf); replay_header_t CreateHeader(); - replay_stats_t CreateStats(); + RunStats_t CreateStats(); replay_frame_t *ReadSingleFrame(FileHandle_t file, const char *filename); replay_header_t *ReadHeader(FileHandle_t file, const char *filename); @@ -72,7 +72,7 @@ class CMomentumReplaySystem : CAutoGameSystemPerFrame replay_frame_t m_currentFrame; replay_header_t m_replayHeader; - replay_stats_t m_replayStats; + RunStats_t m_replayStats; FileHandle_t m_fhFileHandle; CUtlBuffer m_buf; diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index c86ac83808..fe024f0bd0 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -42,22 +42,22 @@ void CTriggerStage::StartTouch(CBaseEntity *pOther) { stageEvent->SetInt("stage_num", stageNum); stageEvent->SetFloat("stage_enter_time", g_Timer->CalculateStageTime(stageNum)); - stageEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats.m_iStageJumps[stageNum - 1]); - stageEvent->SetFloat("num_strafes", pPlayer->m_PlayerRunStats.m_iStageStrafes[stageNum - 1]); - stageEvent->SetFloat("avg_sync", pPlayer->m_PlayerRunStats.m_flStageStrafeSyncAvg[stageNum - 1]); - stageEvent->SetFloat("avg_sync2", pPlayer->m_PlayerRunStats.m_flStageStrafeSync2Avg[stageNum - 1]); + stageEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats.m_iZoneJumps[stageNum - 1]); + stageEvent->SetFloat("num_strafes", pPlayer->m_PlayerRunStats.m_iZoneStrafes[stageNum - 1]); + stageEvent->SetFloat("avg_sync", pPlayer->m_PlayerRunStats.m_flZoneStrafeSyncAvg[stageNum - 1]); + stageEvent->SetFloat("avg_sync2", pPlayer->m_PlayerRunStats.m_flZoneStrafeSync2Avg[stageNum - 1]); //3D VELOCITY - stageEvent->SetFloat("max_vel", pPlayer->m_PlayerRunStats.m_flStageVelocityMax[stageNum - 1][0]); - stageEvent->SetFloat("avg_vel", pPlayer->m_PlayerRunStats.m_flStageVelocityAvg[stageNum - 1][0]); - pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum - 1][0] = pPlayer->GetLocalVelocity().Length(); - stageEvent->SetFloat("stage_exit_vel", pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum - 1][0]); + stageEvent->SetFloat("max_vel", pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[stageNum - 1][0]); + stageEvent->SetFloat("avg_vel", pPlayer->m_PlayerRunStats.m_flZoneVelocityAvg[stageNum - 1][0]); + pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum - 1][0] = pPlayer->GetLocalVelocity().Length(); + stageEvent->SetFloat("stage_exit_vel", pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum - 1][0]); //2D VELOCITY - stageEvent->SetFloat("max_vel_2D", pPlayer->m_PlayerRunStats.m_flStageVelocityMax[stageNum - 1][1]); - stageEvent->SetFloat("avg_vel_2D", pPlayer->m_PlayerRunStats.m_flStageVelocityAvg[stageNum - 1][1]); - pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum - 1][1] = pPlayer->GetLocalVelocity().Length2D(); - stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum - 1][1]); + stageEvent->SetFloat("max_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[stageNum - 1][1]); + stageEvent->SetFloat("avg_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneVelocityAvg[stageNum - 1][1]); + pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum - 1][1] = pPlayer->GetLocalVelocity().Length2D(); + stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum - 1][1]); gameeventmanager->FireEvent(stageEvent); @@ -102,12 +102,12 @@ void CTriggerStage::EndTouch(CBaseEntity *pOther) stageEvent->SetInt("stage_num", stageNum); //3D VELOCITY - pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); - stageEvent->SetFloat("stage_enter_vel", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][0]); + pPlayer->m_PlayerRunStats.m_flZoneEnterSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); + stageEvent->SetFloat("stage_enter_vel", pPlayer->m_PlayerRunStats.m_flZoneEnterSpeed[stageNum][0]); //2D VELOCITY - pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); - stageEvent->SetFloat("stage_enter_vel_2D", pPlayer->m_PlayerRunStats.m_flStageEnterSpeed[stageNum][1]); + pPlayer->m_PlayerRunStats.m_flZoneEnterSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); + stageEvent->SetFloat("stage_enter_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneEnterSpeed[stageNum][1]); gameeventmanager->FireEvent(stageEvent); } @@ -291,16 +291,16 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) //We need to store it in totalstages + 1 so that comparisons can //call forward for determining time spent on the last stage. //We set the stage_num one higher so the last stage can still compare against it - int stageNum = g_Timer->GetCurrentStageNumber(); + int stageNum = g_Timer->GetCurrentZoneNumber(); stageEvent->SetInt("stage_num", stageNum + 1); //And then put the time we finished at as the enter time for the end trigger stageEvent->SetFloat("stage_enter_time", g_Timer->GetLastRunTime()); //This is needed so we have an ending velocity. - pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); - stageEvent->SetFloat("stage_exit_vel", pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum][0]); - pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); - stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_PlayerRunStats.m_flStageExitSpeed[stageNum][1]); + pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); + stageEvent->SetFloat("stage_exit_vel", pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum][0]); + pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); + stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum][1]); gameeventmanager->FireEvent(stageEvent); } diff --git a/mp/src/game/server/momentum/replayformat.h b/mp/src/game/server/momentum/replayformat.h index bca8974404..b833de409d 100644 --- a/mp/src/game/server/momentum/replayformat.h +++ b/mp/src/game/server/momentum/replayformat.h @@ -51,42 +51,31 @@ struct replay_header_t //byteswap for int and float members of header, swaps the endianness (byte order) in order to read correctly inline void ByteSwap_replay_header_t(replay_header_t &swap) { + swap.numZones = LittleDWord(swap.numZones); swap.demoProtoVersion = LittleDWord(swap.demoProtoVersion); swap.unixEpocDate = LittleLong(swap.unixEpocDate); swap.steamID64 = LittleLong(swap.steamID64); LittleFloat(&swap.interval_per_tick, &swap.interval_per_tick); LittleFloat(&swap.runTime, &swap.runTime); - } -struct replay_stats_t -{ - replay_stats_t() {} - replay_stats_t(int size) - { - stats = RunStats_t(size); - arraySize = size; - } - RunStats_t stats; - int arraySize; -}; -inline void ByteSwap_replay_stats_t(replay_stats_t &swap) +inline void ByteSwap_replay_stats_t(RunStats_t &swap) { - for (int i = 0; i < swap.arraySize; i++) + for (int i = 0; i < swap.m_iTotalZones; i++) { - LittleFloat(&swap.stats.m_flStageEnterTime[i], &swap.stats.m_flStageEnterTime[i]); - LittleFloat(&swap.stats.m_flStageTime[i], &swap.stats.m_flStageTime[i]); - LittleFloat(&swap.stats.m_flStageStrafeSyncAvg[i], &swap.stats.m_flStageStrafeSyncAvg[i]); - LittleFloat(&swap.stats.m_flStageStrafeSync2Avg[i], &swap.stats.m_flStageStrafeSync2Avg[i]); - swap.stats.m_iStageJumps[i] = LittleDWord(swap.stats.m_iStageJumps[i]); - swap.stats.m_iStageStrafes[i] = LittleDWord(swap.stats.m_iStageStrafes[i]); + LittleFloat(&swap.m_flZoneEnterTime[i], &swap.m_flZoneEnterTime[i]); + LittleFloat(&swap.m_flZoneTime[i], &swap.m_flZoneTime[i]); + LittleFloat(&swap.m_flZoneStrafeSyncAvg[i], &swap.m_flZoneStrafeSyncAvg[i]); + LittleFloat(&swap.m_flZoneStrafeSync2Avg[i], &swap.m_flZoneStrafeSync2Avg[i]); + swap.m_iZoneJumps[i] = LittleDWord(swap.m_iZoneJumps[i]); + swap.m_iZoneStrafes[i] = LittleDWord(swap.m_iZoneStrafes[i]); for (int k = 0; k < 2; k++) { - LittleFloat(&swap.stats.m_flStageEnterSpeed[i][k], &swap.stats.m_flStageEnterSpeed[i][k]); - LittleFloat(&swap.stats.m_flStageExitSpeed[i][k], &swap.stats.m_flStageExitSpeed[i][k]); - LittleFloat(&swap.stats.m_flStageVelocityAvg[i][k], &swap.stats.m_flStageVelocityAvg[i][k]); - LittleFloat(&swap.stats.m_flStageVelocityMax[i][k], &swap.stats.m_flStageVelocityMax[i][k]); + LittleFloat(&swap.m_flZoneEnterSpeed[i][k], &swap.m_flZoneEnterSpeed[i][k]); + LittleFloat(&swap.m_flZoneExitSpeed[i][k], &swap.m_flZoneExitSpeed[i][k]); + LittleFloat(&swap.m_flZoneVelocityAvg[i][k], &swap.m_flZoneVelocityAvg[i][k]); + LittleFloat(&swap.m_flZoneVelocityMax[i][k], &swap.m_flZoneVelocityMax[i][k]); } } diff --git a/mp/src/game/shared/momentum/util/run_stats.h b/mp/src/game/shared/momentum/util/run_stats.h index 8cdb5fb690..1a0f849bc9 100644 --- a/mp/src/game/shared/momentum/util/run_stats.h +++ b/mp/src/game/shared/momentum/util/run_stats.h @@ -1,131 +1,132 @@ #pragma once #include "cbase.h" +#include "filesystem.h" #include "mom_shareddefs.h" struct RunStats_t { RunStats_t(int size = MAX_STAGES) { - m_iTotalStages = size; - //dynamically allocate the size of the run stats - m_iStageJumps = new int[size]; - m_iStageStrafes = new int[size]; - - m_flStageStrafeSyncAvg = new float[size]; - m_flStageStrafeSync2Avg = new float[size]; - m_flStageEnterTime = new float[size]; - m_flStageTime = new float[size]; - - //the 2d arrays are basically an array of pointers to arrays. - m_flStageEnterSpeed = new float*[size]; - m_flStageExitSpeed = new float*[size]; - m_flStageVelocityMax = new float*[size]; - m_flStageVelocityAvg = new float*[size]; - - for (int m = 0; m < size; ++m) + // Set the total number of stages/checkpoints + m_iTotalZones = size; + + // initialize everything to 0 + for (int i = 0; i < m_iTotalZones; i++) { - m_flStageEnterSpeed[m] = new float[2]; - m_flStageExitSpeed[m] = new float[2]; - m_flStageVelocityMax[m] = new float[2]; - m_flStageVelocityAvg[m] = new float[2]; + m_iZoneJumps[i] = 0; + m_iZoneStrafes[i] = 0; + m_flZoneStrafeSyncAvg[i] = 0; + m_flZoneStrafeSync2Avg[i] = 0; + m_flZoneEnterTime[i] = 0; + m_flZoneTime[i] = 0; + for (int k = 0; k < 2; k++) + { + m_flZoneVelocityMax[i][k] = 0; + m_flZoneVelocityAvg[i][k] = 0; + m_flZoneEnterSpeed[i][k] = 0; + m_flZoneExitSpeed[i][k] = 0; + } } - - //initialize everything to 0 - for (int i = 0; i < size; i++) + } + + //Note: This needs updating every time the struct is updated!! + inline void ReadFromFile(IFileSystem *fs, FileHandle_t file) + { + fs->Read(&m_iTotalZones, sizeof(m_iTotalZones), file); + for (int i = 0; i < m_iTotalZones; i++) { - m_iStageJumps[i] = 0; - m_iStageStrafes[i] = 0; + fs->Read(&m_iZoneJumps[i], sizeof(m_iZoneJumps[i]), file); + fs->Read(&m_iZoneStrafes[i], sizeof(m_iZoneStrafes[i]), file); + fs->Read(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); + + fs->Read(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); + fs->Read(&m_flZoneStrafeSync2Avg[i], sizeof(m_flZoneStrafeSync2Avg[i]), file); + fs->Read(&m_flZoneEnterTime[i], sizeof(m_flZoneEnterTime[i]), file); + fs->Read(&m_flZoneTime[i], sizeof(m_flZoneTime[i]), file); - m_flStageStrafeSyncAvg[i] = 0; - m_flStageStrafeSync2Avg[i] = 0; - m_flStageEnterTime[i] = 0; - m_flStageTime[i] = 0; for (int k = 0; k < 2; k++) { - m_flStageEnterSpeed[i][k] = 0; - m_flStageExitSpeed[i][k] = 0; - m_flStageVelocityMax[i][k] = 0; - m_flStageVelocityAvg[i][k] = 0; + fs->Read(&m_flZoneVelocityMax[i][k], sizeof(m_flZoneVelocityMax[i][k]), file); + fs->Read(&m_flZoneVelocityAvg[i][k], sizeof(m_flZoneVelocityAvg[i][k]), file); + fs->Read(&m_flZoneEnterSpeed[i][k], sizeof(m_flZoneEnterSpeed[i][k]), file); + fs->Read(&m_flZoneExitSpeed[i][k], sizeof(m_flZoneExitSpeed[i][k]), file); } } } - /* - MOM_TODO: - @tuxxi: this somehow breaks future memory allocation and will cause issues when trying - to access m_flVelocityAvg in a newly created RunStats object. No idea why. Commenting out for now, FIX BEFORE RELEASE!!!! - ~RunStats_t() + inline void WriteToFile(IFileSystem *fs, FileHandle_t file) { - delete[] m_iStageJumps; - delete[] m_iStageStrafes; + fs->Write(&m_iTotalZones, sizeof(m_iTotalZones), file); + for (int i = 0; i < m_iTotalZones; i++) + { + fs->Write(&m_iZoneJumps[i], sizeof(m_iZoneJumps[i]), file); + fs->Write(&m_iZoneStrafes[i], sizeof(m_iZoneStrafes[i]), file); + fs->Write(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); - delete[] m_flStageEnterTime; - delete[] m_flStageTime; - delete[] m_flStageStrafeSyncAvg; - delete[] m_flStageStrafeSync2Avg; + fs->Write(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); + fs->Write(&m_flZoneStrafeSync2Avg[i], sizeof(m_flZoneStrafeSync2Avg[i]), file); + fs->Write(&m_flZoneEnterTime[i], sizeof(m_flZoneEnterTime[i]), file); + fs->Write(&m_flZoneTime[i], sizeof(m_flZoneTime[i]), file); - for (int i = 0; i < m_iTotalStages; ++i) - { - delete[] m_flStageEnterSpeed[i]; - delete[] m_flStageExitSpeed[i]; - delete[] m_flStageVelocityMax[i]; - delete[] m_flStageVelocityAvg[i]; + for (int k = 0; k < 2; k++) + { + fs->Write(&m_flZoneVelocityMax[i][k], sizeof(m_flZoneVelocityMax[i][k]), file); + fs->Write(&m_flZoneVelocityAvg[i][k], sizeof(m_flZoneVelocityAvg[i][k]), file); + fs->Write(&m_flZoneEnterSpeed[i][k], sizeof(m_flZoneEnterSpeed[i][k]), file); + fs->Write(&m_flZoneExitSpeed[i][k], sizeof(m_flZoneExitSpeed[i][k]), file); + } } - delete[] m_flStageEnterSpeed; - delete[] m_flStageExitSpeed; - delete[] m_flStageVelocityMax; - delete[] m_flStageVelocityAvg; } - */ - RunStats_t& operator=(const RunStats_t& other) + + RunStats_t &operator=(const RunStats_t &other) { - for (int i = 0; i < other.m_iTotalStages; i++) + m_iTotalZones = other.m_iTotalZones; + for (int i = 0; i < other.m_iTotalZones; i++) { - m_iStageJumps[i] = other.m_iStageJumps[i]; - m_iStageStrafes[i] = other.m_iStageStrafes[i]; + m_iZoneJumps[i] = other.m_iZoneJumps[i]; + m_iZoneStrafes[i] = other.m_iZoneStrafes[i]; - m_flStageStrafeSyncAvg[i] = other.m_flStageStrafeSyncAvg[i]; - m_flStageStrafeSync2Avg[i] = other.m_flStageStrafeSync2Avg[i]; - m_flStageEnterTime[i] = other.m_flStageEnterTime[i]; - m_flStageTime[i] = other.m_flStageTime[i]; + m_flZoneStrafeSyncAvg[i] = other.m_flZoneStrafeSyncAvg[i]; + m_flZoneStrafeSync2Avg[i] = other.m_flZoneStrafeSync2Avg[i]; + m_flZoneEnterTime[i] = other.m_flZoneEnterTime[i]; + m_flZoneTime[i] = other.m_flZoneTime[i]; for (int k = 0; k < 2; k++) { - m_flStageVelocityMax[i][k] = other.m_flStageVelocityMax[i][k]; - m_flStageVelocityAvg[i][k] = other.m_flStageVelocityAvg[i][k]; - m_flStageEnterSpeed[i][k] = other.m_flStageEnterSpeed[i][k]; - m_flStageExitSpeed[i][k] = other.m_flStageExitSpeed[i][k]; + m_flZoneVelocityMax[i][k] = other.m_flZoneVelocityMax[i][k]; + m_flZoneVelocityAvg[i][k] = other.m_flZoneVelocityAvg[i][k]; + m_flZoneEnterSpeed[i][k] = other.m_flZoneEnterSpeed[i][k]; + m_flZoneExitSpeed[i][k] = other.m_flZoneExitSpeed[i][k]; } } return *this; } - //MOM_TODO: We're going to hold an unbiased view at both - //checkpoint and stages. If a map is linear yet has checkpoints, - //it can be free to use these below to display stats for the player to compare against. + // MOM_TODO: We're going to hold an unbiased view at both + // checkpoint and stages. If a map is linear yet has checkpoints, + // it can be free to use these below to display stats for the player to compare against. - //Note: These are by initally created as null pointers, but the constructor will by default create arrays of size MAX_STAGES - //Note: Passing 0 as the index to any of these will return the overall stat, i.e during the entire run. + // Note: Passing 0 as the index to any of these will return the overall stat, i.e during the entire run. - int m_iTotalStages = MAX_STAGES; //required for the operator= overload + int m_iTotalZones; //Required for the operator= overload - //Keypress - int *m_iStageJumps = nullptr, //Amount of jumps per stage - *m_iStageStrafes = nullptr; //Amount of strafes per stage + // Keypress + int m_iZoneJumps[MAX_STAGES], // Amount of jumps per stage/checkpoint + m_iZoneStrafes[MAX_STAGES]; // Amount of strafes per stage/checkpoint - //Time - float *m_flStageTime = nullptr, //The amount of time (seconds) you spent to accomplish (stage) -> (stage + 1) - *m_flStageEnterTime = nullptr; //The time in seconds that you entered the given stage + // Time + float m_flZoneTime[MAX_STAGES], // The amount of time (seconds) you spent to accomplish (stage) -> (stage + 1) + m_flZoneEnterTime[MAX_STAGES]; // The time in seconds that you entered the given stage/checkpoint - //Sync - float *m_flStageStrafeSyncAvg = nullptr, //The average sync1 you had over the given stage - *m_flStageStrafeSync2Avg = nullptr; //The average sync2 you had over the given stage + // Sync + float m_flZoneStrafeSyncAvg[MAX_STAGES], // The average sync1 you had over the given stage/checkpoint + m_flZoneStrafeSync2Avg[MAX_STAGES]; // The average sync2 you had over the given stage/checkpoint - //Velocity - //Note: The secondary index is as follows: 0 = 3D Velocity (z included), 1 = Horizontal (XY) Velocity - float **m_flStageEnterSpeed = nullptr, //The velocity with which you started the stage (exit this stage's start trigger) - **m_flStageVelocityMax = nullptr, //Max velocity for a stage - **m_flStageVelocityAvg = nullptr, //Average velocity in a stage - **m_flStageExitSpeed = nullptr; //The velocity with which you exit the stage (this stage -> next) + // Velocity + // Note: The secondary index is as follows: 0 = 3D Velocity (z included), 1 = Horizontal (XY) Velocity + float m_flZoneEnterSpeed[MAX_STAGES][2], // The velocity with which you started the stage (exit this stage's start trigger) + m_flZoneVelocityMax[MAX_STAGES][2], // Max velocity for a stage/checkpoint + m_flZoneVelocityAvg[MAX_STAGES][2], // Average velocity in a stage/checkpoint + m_flZoneExitSpeed[MAX_STAGES][2]; // The velocity with which you exit the stage (this stage -> next) }; \ No newline at end of file From 2942a579d9dc5cc5837a1dc7b42c0c6f357b9d0a Mon Sep 17 00:00:00 2001 From: Nick K Date: Fri, 27 May 2016 18:56:26 -0400 Subject: [PATCH 052/101] Fix reading/writing RunStats_t data Fixed reading a .tim file TODO: Network run stats on the player/ghost --- .../momentum/ui/MapSelection/LocalMaps.cpp | 2 +- .../game/client/momentum/ui/hud_keypress.cpp | 4 +- .../client/momentum/ui/hud_mapfinished.cpp | 19 +++++- .../game/client/momentum/ui/hud_mapinfo.cpp | 29 ++++---- mp/src/game/server/momentum/Timer.cpp | 8 +-- mp/src/game/server/momentum/mapzones.cpp | 2 +- mp/src/game/server/momentum/mom_player.cpp | 1 - mp/src/game/server/momentum/mom_replay.cpp | 4 +- mp/src/game/shared/momentum/util/mom_util.cpp | 2 +- mp/src/game/shared/momentum/util/run_stats.h | 67 +++++++------------ 10 files changed, 71 insertions(+), 67 deletions(-) diff --git a/mp/src/game/client/momentum/ui/MapSelection/LocalMaps.cpp b/mp/src/game/client/momentum/ui/MapSelection/LocalMaps.cpp index d0bc4f5a31..530a4b773f 100644 --- a/mp/src/game/client/momentum/ui/MapSelection/LocalMaps.cpp +++ b/mp/src/game/client/momentum/ui/MapSelection/LocalMaps.cpp @@ -63,7 +63,7 @@ bool MapHasStages(const char* szMap) if (kvMap->LoadFromFile(filesystem, path, "MOD")) { - found = (kvMap->FindKey("stage") != NULL); + found = (kvMap->FindKey("zone") != NULL); } kvMap->deleteThis(); } diff --git a/mp/src/game/client/momentum/ui/hud_keypress.cpp b/mp/src/game/client/momentum/ui/hud_keypress.cpp index d9d35aacd0..6b6f892c68 100644 --- a/mp/src/game/client/momentum/ui/hud_keypress.cpp +++ b/mp/src/game/client/momentum/ui/hud_keypress.cpp @@ -203,8 +203,8 @@ void CHudKeyPressDisplay::OnThink() { m_nButtons = ::input->GetButtonBits(1); if (g_MOMEventListener) - { //we should only draw the strafe/jump counters when the timer is running - //MOM_TODO: Update this so that the replay ent also correctly sets these + { + //we should only draw the strafe/jump counters when the timer is running m_bShouldDrawCounts = g_MOMEventListener->m_bTimerIsRunning; m_nStrafes = g_MOMEventListener->stats.m_iZoneStrafes[0]; m_nJumps = g_MOMEventListener->stats.m_iZoneJumps[0]; diff --git a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp index 0545541540..824c93697e 100644 --- a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp +++ b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp @@ -35,8 +35,21 @@ class CHudMapFinishedDialog : public CHudElement, public Panel bool ShouldDraw() override { + bool shouldDrawLocal = false; C_MomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); - return pPlayer && pPlayer->m_RunData.m_bMapFinished; + if (pPlayer) + { + if (pPlayer->IsWatchingReplay()) + { + C_MomentumReplayGhostEntity *pEnt = pPlayer->GetReplayEnt(); + shouldDrawLocal = pEnt && pEnt->m_RunData.m_bMapFinished; + } + else + { + shouldDrawLocal = pPlayer->m_RunData.m_bMapFinished; + } + } + return CHudElement::ShouldDraw() && shouldDrawLocal; } void Paint() override; @@ -314,7 +327,8 @@ void CHudMapFinishedDialog::Paint() } void CHudMapFinishedDialog::OnThink() { - if (g_MOMEventListener) + C_MomentumPlayer * pPlayer = ToCMOMPlayer(C_BasePlayer::GetLocalPlayer()); + if (g_MOMEventListener && pPlayer) { m_bRunSaved = g_MOMEventListener->m_bTimeDidSave; m_bRunUploaded = g_MOMEventListener->m_bTimeDidUpload; @@ -323,6 +337,7 @@ void CHudMapFinishedDialog::OnThink() ConVarRef hvel("mom_speedometer_hvel"); //MOM_TODO: Are we going to update to read replay file stats? + //RunStats_t *stats = pPlayer->IsWatchingReplay() ? &g_MOMEventListener->stats : pPlayer->GetReplayEnt()->m_RunData.m_RunStats; m_flAvgSpeed = g_MOMEventListener->stats.m_flZoneVelocityAvg[0][hvel.GetBool()]; m_flMaxSpeed = g_MOMEventListener->stats.m_flZoneVelocityMax[0][hvel.GetBool()]; m_flEndSpeed = g_MOMEventListener->stats.m_flZoneExitSpeed[0][hvel.GetBool()]; diff --git a/mp/src/game/client/momentum/ui/hud_mapinfo.cpp b/mp/src/game/client/momentum/ui/hud_mapinfo.cpp index 4c36c52a96..524278016c 100644 --- a/mp/src/game/client/momentum/ui/hud_mapinfo.cpp +++ b/mp/src/game/client/momentum/ui/hud_mapinfo.cpp @@ -75,7 +75,7 @@ class C_HudMapInfo : public CHudElement, public Panel m_pszStringStages[BUFSIZELOCL], noStagesLocalized[BUFSIZELOCL], noCPLocalized[BUFSIZELOCL], mapNameLabelLocalized[BUFSIZELOCL], mapAuthorLabelLocalized[BUFSIZELOCL], mapDiffLabelLocalized[BUFSIZELOCL]; - int m_iStageCount, m_iStageCurrent; + int m_iZoneCount, m_iZoneCurrent; bool m_bPlayerInZone, m_bMapFinished, m_bMapLinear; }; @@ -91,7 +91,8 @@ C_HudMapInfo::C_HudMapInfo(const char *pElementName) SetKeyBoardInputEnabled(false); SetMouseInputEnabled(false); SetHiddenBits(HIDEHUD_WEAPONSELECTION); - m_iStageCurrent = 0; + m_iZoneCurrent = 0; + m_iZoneCount = 0; m_bPlayerInZone = false; m_bMapFinished = false; } @@ -104,18 +105,18 @@ void C_HudMapInfo::OnThink() C_MomentumReplayGhostEntity *pGhost = pLocal->GetReplayEnt(); if (pGhost) { - m_iStageCurrent = pGhost->m_RunData.m_iCurrentZone; + m_iZoneCurrent = pGhost->m_RunData.m_iCurrentZone; m_bPlayerInZone = pGhost->m_RunData.m_bIsInZone; m_bMapFinished = pGhost->m_RunData.m_bMapFinished; } else { - m_iStageCurrent = pLocal->m_RunData.m_iCurrentZone; + m_iZoneCurrent = pLocal->m_RunData.m_iCurrentZone; m_bPlayerInZone = pLocal->m_RunData.m_bIsInZone; m_bMapFinished = pLocal->m_RunData.m_bMapFinished; } - m_iStageCount = g_MOMEventListener->m_iMapCheckpointCount; + m_iZoneCount = g_MOMEventListener->m_iMapCheckpointCount; m_bMapLinear = g_MOMEventListener->m_bMapIsLinear; } } @@ -138,19 +139,22 @@ void C_HudMapInfo::Init() void C_HudMapInfo::Reset() { - // MOM_TODO: Reset all the numbers and stuff here? + m_iZoneCount = 0; + m_iZoneCurrent = 0; + m_bMapLinear = false; + m_bPlayerInZone = false; + m_bMapFinished = false; } void C_HudMapInfo::Paint() { - // MOM_TODO: this will change to be checkpoints - if (m_iStageCount > 0) + if (m_iZoneCount > 0) { // Current stage(checkpoint)/total stages(checkpoints) Q_snprintf(m_pszStringStages, sizeof(m_pszStringStages), "%s %i/%i", m_bMapLinear ? checkpointLocalized : stageLocalized, // "Stage" / "Checkpoint" - m_iStageCurrent, // Current stage/checkpoint - m_iStageCount // Total number of stages/checkpoints + m_iZoneCurrent, // Current stage/checkpoint + m_iZoneCount // Total number of stages/checkpoints ); } else @@ -163,7 +167,7 @@ void C_HudMapInfo::Paint() // No matter what, we always want the player's status printed out, if they're in a zone if (m_bPlayerInZone) { - if (m_iStageCurrent == 1) + if (m_iZoneCurrent == 1) { // Start zone Q_snprintf(m_pszStringStatus, sizeof(m_pszStringStatus), startZoneLocalized); @@ -177,10 +181,11 @@ void C_HudMapInfo::Paint() } else { + //Note: The player will never be inside a "checkpoint" zone // Stage # Start wchar_t stageCurrent[128]; // 00'\0' and max stages is 64 - V_snwprintf(stageCurrent, ARRAYSIZE(stageCurrent), L"%d", m_iStageCurrent); + V_snwprintf(stageCurrent, ARRAYSIZE(stageCurrent), L"%d", m_iZoneCurrent); // Fills the "Stage %s1 Start" string g_pVGuiLocalize->ConstructString(m_pwStageStartLabel, sizeof(m_pwStageStartLabel), m_pwStageStartString, 1, stageCurrent); diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index 79dd2cd97c..032e8629ad 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -99,9 +99,9 @@ void CTimer::LoadLocalTimes(const char *szMapname) for (KeyValues *subKv = kv->GetFirstSubKey(); subKv; subKv = subKv->GetNextKey()) { - if (!Q_strnicmp(subKv->GetName(), "stage", strlen("stage"))) + if (!Q_strnicmp(subKv->GetName(), "zone", strlen("zone"))) { - int i = Q_atoi(subKv->GetName() + 6); //atoi will need to ignore "stage " and only return the stage number + int i = Q_atoi(subKv->GetName() + 5); //atoi will need to ignore "zone " and only return the stage number t.RunStats.m_iZoneJumps[i] = subKv->GetInt("num_jumps"); t.RunStats.m_iZoneStrafes[i] = subKv->GetInt("num_strafes"); t.RunStats.m_flZoneTime[i] = subKv->GetFloat("time"); @@ -121,7 +121,7 @@ void CTimer::LoadLocalTimes(const char *szMapname) t.RunStats.m_flZoneEnterSpeed[i][1] = subKv->GetFloat("stage_enter_vel_2D"); t.RunStats.m_flZoneExitSpeed[i][1] = subKv->GetFloat("stage_exit_vel_2D"); } - if (!Q_strcmp(subKv->GetName(), "total")) + if (!Q_strncmp(subKv->GetName(), "total", Q_strlen("total"))) { t.RunStats.m_iZoneJumps[0] = subKv->GetInt("jumps"); t.RunStats.m_iZoneStrafes[0] = subKv->GetInt("strafes"); @@ -189,7 +189,7 @@ void CTimer::SaveTime() { for (int i2 = 1; i2 <= GetStageCount(); i2++) { - Q_snprintf(stageName, sizeof(stageName), "stage %d", i2);//MOM_TODO: || checkpoint %d + Q_snprintf(stageName, sizeof(stageName), "zone %d", i2);//MOM_TODO: || checkpoint %d KeyValues *pStageKey = new KeyValues(stageName); pStageKey->SetFloat("time", t.RunStats.m_flZoneTime[i2]); diff --git a/mp/src/game/server/momentum/mapzones.cpp b/mp/src/game/server/momentum/mapzones.cpp index 2180fce4c6..426e4d636d 100644 --- a/mp/src/game/server/momentum/mapzones.cpp +++ b/mp/src/game/server/momentum/mapzones.cpp @@ -404,7 +404,7 @@ bool CMapzoneData::LoadFromFile(const char *szMapName) //destinationIndex = cp->GetInt("destination", 1); linkedtrigger = cp->GetString("destinationname", NULL); } - else if (Q_strcmp(cp->GetName(), "stage") == 0) + else if (!Q_strcmp(cp->GetName(), "stage") || !Q_strcmp(cp->GetName(), "zone")) { zoneType = MOMZONETYPE_STAGE; index = cp->GetInt("number", 0); diff --git a/mp/src/game/server/momentum/mom_player.cpp b/mp/src/game/server/momentum/mom_player.cpp index 7e398e2c33..8899b32471 100644 --- a/mp/src/game/server/momentum/mom_player.cpp +++ b/mp/src/game/server/momentum/mom_player.cpp @@ -333,7 +333,6 @@ void CMomentumPlayer::ResetRunStats() m_nAccelTicks = 0; m_RunData.m_flStrafeSync = 0; m_RunData.m_flStrafeSync2 = 0; - //(&m_PlayerRunStats)->~RunStats_t();//MOM_TODO: Free the old memory?? m_PlayerRunStats = RunStats_t(g_Timer->GetStageCount()); } void CMomentumPlayer::CalculateAverageStats() diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 47069c85dd..673b39569e 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -122,7 +122,7 @@ void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer *buf) RunStats_t littleEndianStats = CreateStats(); ByteSwap_replay_stats_t(littleEndianStats); - littleEndianStats.WriteToFile(filesystem, m_fhFileHandle); + littleEndianStats.HandleFile(filesystem, m_fhFileHandle, false); //filesystem->Write(&littleEndianStats, sizeof(littleEndianStats), m_fhFileHandle); DevLog("replay stats size: %i\n", sizeof(littleEndianStats)); @@ -155,7 +155,7 @@ replay_header_t* CMomentumReplaySystem::ReadHeader(FileHandle_t file, const char //Create and read into the replayStats m_replayStats = RunStats_t(m_replayHeader.numZones); - m_replayStats.ReadFromFile(filesystem, file); + m_replayStats.HandleFile(filesystem, file, true); //filesystem->Read(&m_replayStats, sizeof(replay_stats_t), file); ByteSwap_replay_stats_t(m_replayStats); diff --git a/mp/src/game/shared/momentum/util/mom_util.cpp b/mp/src/game/shared/momentum/util/mom_util.cpp index 32e41db9e3..e15a717f83 100644 --- a/mp/src/game/shared/momentum/util/mom_util.cpp +++ b/mp/src/game/shared/momentum/util/mom_util.cpp @@ -292,7 +292,7 @@ bool MomentumUtil::GetRunComparison(const char *szMapName, float tickRate, int f { FOR_EACH_SUBKEY(bestRun, kv) { - if (!Q_strnicmp(kv->GetName(), "stage", strlen("stage"))) // MOM_TODO: or "checkpoint" (for linears) + if (!Q_strnicmp(kv->GetName(), "zone", strlen("zone"))) { // MOM_TODO: this may not be a PB, for now it is, but we'll load times from online. // I'm thinking the name could be like "(user): (Time)" diff --git a/mp/src/game/shared/momentum/util/run_stats.h b/mp/src/game/shared/momentum/util/run_stats.h index 1a0f849bc9..72774443bb 100644 --- a/mp/src/game/shared/momentum/util/run_stats.h +++ b/mp/src/game/shared/momentum/util/run_stats.h @@ -29,52 +29,42 @@ struct RunStats_t } } } - - //Note: This needs updating every time the struct is updated!! - inline void ReadFromFile(IFileSystem *fs, FileHandle_t file) - { - fs->Read(&m_iTotalZones, sizeof(m_iTotalZones), file); - for (int i = 0; i < m_iTotalZones; i++) - { - fs->Read(&m_iZoneJumps[i], sizeof(m_iZoneJumps[i]), file); - fs->Read(&m_iZoneStrafes[i], sizeof(m_iZoneStrafes[i]), file); - fs->Read(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); - fs->Read(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); - fs->Read(&m_flZoneStrafeSync2Avg[i], sizeof(m_flZoneStrafeSync2Avg[i]), file); - fs->Read(&m_flZoneEnterTime[i], sizeof(m_flZoneEnterTime[i]), file); - fs->Read(&m_flZoneTime[i], sizeof(m_flZoneTime[i]), file); - - for (int k = 0; k < 2; k++) - { - fs->Read(&m_flZoneVelocityMax[i][k], sizeof(m_flZoneVelocityMax[i][k]), file); - fs->Read(&m_flZoneVelocityAvg[i][k], sizeof(m_flZoneVelocityAvg[i][k]), file); - fs->Read(&m_flZoneEnterSpeed[i][k], sizeof(m_flZoneEnterSpeed[i][k]), file); - fs->Read(&m_flZoneExitSpeed[i][k], sizeof(m_flZoneExitSpeed[i][k]), file); - } - } + //Needed for HandleFile + static void Read(void const* pVoid, int size, FileHandle_t file) + { + filesystem->Read(&pVoid, size, file); } - inline void WriteToFile(IFileSystem *fs, FileHandle_t file) + //Needed for HandleFile + static void Write(void const* pVoid, int size, FileHandle_t file) + { + filesystem->Write(&pVoid, size, file); + } + + //Note: This needs updating every time the struct is updated!! + void HandleFile(IFileSystem *fs, FileHandle_t file, bool read) const { - fs->Write(&m_iTotalZones, sizeof(m_iTotalZones), file); + void (*handle)(void const*, int, FileHandle_t) = read ? &Read : &Write; + + handle(&m_iTotalZones, sizeof(m_iTotalZones), file); for (int i = 0; i < m_iTotalZones; i++) { - fs->Write(&m_iZoneJumps[i], sizeof(m_iZoneJumps[i]), file); - fs->Write(&m_iZoneStrafes[i], sizeof(m_iZoneStrafes[i]), file); - fs->Write(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); + handle(&m_iZoneJumps[i], sizeof(m_iZoneJumps[i]), file); + handle(&m_iZoneStrafes[i], sizeof(m_iZoneStrafes[i]), file); + handle(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); - fs->Write(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); - fs->Write(&m_flZoneStrafeSync2Avg[i], sizeof(m_flZoneStrafeSync2Avg[i]), file); - fs->Write(&m_flZoneEnterTime[i], sizeof(m_flZoneEnterTime[i]), file); - fs->Write(&m_flZoneTime[i], sizeof(m_flZoneTime[i]), file); + handle(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); + handle(&m_flZoneStrafeSync2Avg[i], sizeof(m_flZoneStrafeSync2Avg[i]), file); + handle(&m_flZoneEnterTime[i], sizeof(m_flZoneEnterTime[i]), file); + handle(&m_flZoneTime[i], sizeof(m_flZoneTime[i]), file); for (int k = 0; k < 2; k++) { - fs->Write(&m_flZoneVelocityMax[i][k], sizeof(m_flZoneVelocityMax[i][k]), file); - fs->Write(&m_flZoneVelocityAvg[i][k], sizeof(m_flZoneVelocityAvg[i][k]), file); - fs->Write(&m_flZoneEnterSpeed[i][k], sizeof(m_flZoneEnterSpeed[i][k]), file); - fs->Write(&m_flZoneExitSpeed[i][k], sizeof(m_flZoneExitSpeed[i][k]), file); + handle(&m_flZoneVelocityMax[i][k], sizeof(m_flZoneVelocityMax[i][k]), file); + handle(&m_flZoneVelocityAvg[i][k], sizeof(m_flZoneVelocityAvg[i][k]), file); + handle(&m_flZoneEnterSpeed[i][k], sizeof(m_flZoneEnterSpeed[i][k]), file); + handle(&m_flZoneExitSpeed[i][k], sizeof(m_flZoneExitSpeed[i][k]), file); } } } @@ -103,12 +93,7 @@ struct RunStats_t return *this; } - // MOM_TODO: We're going to hold an unbiased view at both - // checkpoint and stages. If a map is linear yet has checkpoints, - // it can be free to use these below to display stats for the player to compare against. - // Note: Passing 0 as the index to any of these will return the overall stat, i.e during the entire run. - int m_iTotalZones; //Required for the operator= overload // Keypress From 572dceec334ba34fb23e010947de669c313abdb1 Mon Sep 17 00:00:00 2001 From: Nick K Date: Sat, 28 May 2016 01:17:47 -0400 Subject: [PATCH 053/101] Cleanup HandleFile method --- mp/src/game/server/momentum/mom_replay.cpp | 8 ++------ mp/src/game/server/momentum/replayformat.h | 1 + mp/src/game/shared/momentum/util/run_stats.h | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 673b39569e..f23bb1debf 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -122,8 +122,7 @@ void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer *buf) RunStats_t littleEndianStats = CreateStats(); ByteSwap_replay_stats_t(littleEndianStats); - littleEndianStats.HandleFile(filesystem, m_fhFileHandle, false); - //filesystem->Write(&littleEndianStats, sizeof(littleEndianStats), m_fhFileHandle); + littleEndianStats.HandleFile(m_fhFileHandle, false); DevLog("replay stats size: %i\n", sizeof(littleEndianStats)); Assert(buf && buf->IsValid()); @@ -154,10 +153,7 @@ replay_header_t* CMomentumReplaySystem::ReadHeader(FileHandle_t file, const char //Create and read into the replayStats m_replayStats = RunStats_t(m_replayHeader.numZones); - - m_replayStats.HandleFile(filesystem, file, true); - //filesystem->Read(&m_replayStats, sizeof(replay_stats_t), file); - + m_replayStats.HandleFile(file, true); ByteSwap_replay_stats_t(m_replayStats); if (Q_strcmp(m_replayHeader.demofilestamp, REPLAY_HEADER_ID)) { //DEMO_HEADER_ID is __NOT__ the same as the stamp from the header we read from file diff --git a/mp/src/game/server/momentum/replayformat.h b/mp/src/game/server/momentum/replayformat.h index b833de409d..50509efc11 100644 --- a/mp/src/game/server/momentum/replayformat.h +++ b/mp/src/game/server/momentum/replayformat.h @@ -61,6 +61,7 @@ inline void ByteSwap_replay_header_t(replay_header_t &swap) inline void ByteSwap_replay_stats_t(RunStats_t &swap) { + swap.m_iTotalZones = LittleDWord(swap.m_iTotalZones); for (int i = 0; i < swap.m_iTotalZones; i++) { LittleFloat(&swap.m_flZoneEnterTime[i], &swap.m_flZoneEnterTime[i]); diff --git a/mp/src/game/shared/momentum/util/run_stats.h b/mp/src/game/shared/momentum/util/run_stats.h index 72774443bb..6f3a1fb8e5 100644 --- a/mp/src/game/shared/momentum/util/run_stats.h +++ b/mp/src/game/shared/momentum/util/run_stats.h @@ -43,7 +43,7 @@ struct RunStats_t } //Note: This needs updating every time the struct is updated!! - void HandleFile(IFileSystem *fs, FileHandle_t file, bool read) const + void HandleFile(FileHandle_t file, bool read) const { void (*handle)(void const*, int, FileHandle_t) = read ? &Read : &Write; From 64a0381af60e2020e88912863000cdb6a84ffea0 Mon Sep 17 00:00:00 2001 From: Nick K Date: Thu, 2 Jun 2016 04:39:15 -0400 Subject: [PATCH 054/101] Starting to hook up ghost to UI Refactored a lot of things to be "zone" Bug: 0s popping up in .tim file, causing every element to be inaccurate --- mp/game/momentum/resource/modevents.res | 24 +- mp/src/creategameprojects.ps1 | 24 +- .../client/momentum/c_mom_replay_entity.cpp | 1 - .../client/momentum/c_mom_replay_entity.h | 1 + .../client/momentum/mom_event_listener.cpp | 123 +++++---- .../game/client/momentum/mom_event_listener.h | 24 +- .../client/momentum/ui/hud_comparisons.cpp | 87 +++--- .../game/client/momentum/ui/hud_comparisons.h | 5 +- .../game/client/momentum/ui/hud_keypress.cpp | 9 +- .../client/momentum/ui/hud_mapfinished.cpp | 22 +- .../game/client/momentum/ui/hud_mapinfo.cpp | 2 +- .../client/momentum/ui/hud_speedometer.cpp | 29 +- .../client/momentum/ui/hud_strafesync.cpp | 38 ++- mp/src/game/client/momentum/ui/hud_timer.cpp | 38 ++- mp/src/game/server/momentum/Timer.cpp | 87 ++---- mp/src/game/server/momentum/Timer.h | 13 +- mp/src/game/server/momentum/mom_player.cpp | 9 +- mp/src/game/server/momentum/mom_replay.cpp | 12 +- .../server/momentum/mom_replay_entity.cpp | 10 +- .../game/server/momentum/mom_replay_entity.h | 5 + mp/src/game/server/momentum/mom_triggers.cpp | 258 ++++++++++++------ .../shared/momentum/mom_entity_run_data.cpp | 3 + .../shared/momentum/mom_entity_run_data.h | 5 +- mp/src/game/shared/momentum/util/mom_util.cpp | 4 +- mp/src/game/shared/momentum/util/run_stats.h | 61 +++-- 25 files changed, 551 insertions(+), 343 deletions(-) diff --git a/mp/game/momentum/resource/modevents.res b/mp/game/momentum/resource/modevents.res index 62dd694896..3470e07798 100644 --- a/mp/game/momentum/resource/modevents.res +++ b/mp/game/momentum/resource/modevents.res @@ -29,6 +29,7 @@ { "timer_stopped" { + "ent" "short" "time" "float" "avg_sync" "float" "avg_sync2" "float" @@ -44,10 +45,11 @@ "start_vel_2D" "float" "end_vel_2D" "float" } - "stage_enter" + "zone_enter"//When the player/ghost enters a checkpoint/stage trigger { - "stage_num" "byte" - "stage_enter_time" "float" //time is in seconds + "num" "byte"//Number of the zone + "ent" "short"//Ent index of the ent that touched me + "enter_time" "float" //time is in seconds "avg_sync" "float" "avg_sync2" "float" "num_strafes" "short" @@ -55,17 +57,18 @@ "avg_vel" "float" "max_vel" "float" - "stage_exit_vel" "float"//previous stage's exit velocity + "exit_vel" "float"//previous stage's exit velocity //we save both XY and XYZ, so we can look at both if need be... "avg_vel_2D" "float" "max_vel_2D" "float" - "stage_exit_vel_2D" "float"//previous stage's horizontal exit velocity + "exit_vel_2D" "float"//this stage's horizontal exit velocity } - "stage_exit"//When the player exits the start trigger for the stage + "zone_exit"//When the player exits the start trigger for the stage { - "stage_num" "byte" - "stage_enter_vel" "float"//velocity in which the player starts the stage (exits the stage trigger) - "stage_enter_vel_2D" "float" + "num" "byte" + "ent" "byte"//Ent index + "enter_vel" "float"//velocity in which the player starts the stage (exits the stage trigger) + "enter_vel_2D" "float" } "run_save" { @@ -82,12 +85,13 @@ } "keypress" { + "ent" "short" "num_jumps" "short" "num_strafes" "short" } "map_init" { "is_linear" "bool" - "num_checkpoints" "byte" + "num_zones" "byte" } } diff --git a/mp/src/creategameprojects.ps1 b/mp/src/creategameprojects.ps1 index 9b73bcc202..81c2ce3337 100644 --- a/mp/src/creategameprojects.ps1 +++ b/mp/src/creategameprojects.ps1 @@ -9,16 +9,24 @@ $path = (Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentV if (!$path) { - Write-Warning "You should install Source SDK Base 2013 Multiplayer.`nRequesting Steam to install the app..." - try { - $cmd ="cmd.exe" - &$cmd "/c start steam://install/243750/" + $path2 = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 243750" -Name InstallLocation).InstallLocation + if (!$path2) + { + Write-Warning "You should install Source SDK Base 2013 Multiplayer.`nRequesting Steam to install the app..." + try { + $cmd ="cmd.exe" + &$cmd "/c start steam://install/243750/" + } + catch { + Write-Warning "Steam is not running. Can not launch installation pop-up" + } + pause + exit } - catch { - Write-Warning "Steam is not running. Can not launch installation pop-up" + else + { + ($path = $path2) } - pause - exit } $hl2exe = Join-Path $path hl2.exe diff --git a/mp/src/game/client/momentum/c_mom_replay_entity.cpp b/mp/src/game/client/momentum/c_mom_replay_entity.cpp index e1257f297e..2d0e07ac6a 100644 --- a/mp/src/game/client/momentum/c_mom_replay_entity.cpp +++ b/mp/src/game/client/momentum/c_mom_replay_entity.cpp @@ -5,7 +5,6 @@ IMPLEMENT_CLIENTCLASS_DT(C_MomentumReplayGhostEntity, DT_MOM_ReplayEnt, CMomentumReplayGhostEntity) -//MOM_TODO: Network the rest of the variables that the ghost entity will be sending RecvPropInt(RECVINFO(m_nReplayButtons)), RecvPropInt(RECVINFO(m_iTotalStrafes)), RecvPropInt(RECVINFO(m_iTotalJumps)), diff --git a/mp/src/game/client/momentum/c_mom_replay_entity.h b/mp/src/game/client/momentum/c_mom_replay_entity.h index 715fc979ff..852540d3df 100644 --- a/mp/src/game/client/momentum/c_mom_replay_entity.h +++ b/mp/src/game/client/momentum/c_mom_replay_entity.h @@ -14,6 +14,7 @@ class C_MomentumReplayGhostEntity : public C_BaseAnimating CMOMRunEntityData m_RunData; int m_nReplayButtons; + //These are stored here because run stats already has the ones obtained from the run int m_iTotalStrafes; int m_iTotalJumps; diff --git a/mp/src/game/client/momentum/mom_event_listener.cpp b/mp/src/game/client/momentum/mom_event_listener.cpp index 49405039ee..2e67508834 100644 --- a/mp/src/game/client/momentum/mom_event_listener.cpp +++ b/mp/src/game/client/momentum/mom_event_listener.cpp @@ -7,78 +7,103 @@ void C_Momentum_EventListener::Init() { //add listeners for all of our custom events ListenForGameEvent("timer_stopped"); - ListenForGameEvent("stage_enter"); - ListenForGameEvent("stage_exit"); + ListenForGameEvent("zone_enter"); + ListenForGameEvent("zone_exit"); ListenForGameEvent("run_save"); ListenForGameEvent("run_upload"); ListenForGameEvent("timer_state"); ListenForGameEvent("keypress"); ListenForGameEvent("map_init"); + m_EntRunStats.SetLessFunc(DefLessFunc(int)); } void C_Momentum_EventListener::FireGameEvent(IGameEvent *pEvent) { if (!Q_strcmp("timer_stopped", pEvent->GetName())) { - stats.m_flZoneStrafeSyncAvg[0] = pEvent->GetFloat("avg_sync"); - stats.m_flZoneStrafeSync2Avg[0] = pEvent->GetFloat("avg_sync2"); + int entIndex = pEvent->GetInt("ent"); + RunStats_t *stats = GetRunStats(entIndex); + + stats->m_flZoneStrafeSyncAvg[0] = pEvent->GetFloat("avg_sync"); + stats->m_flZoneStrafeSync2Avg[0] = pEvent->GetFloat("avg_sync2"); //3D - stats.m_flZoneEnterSpeed[0][0] = pEvent->GetFloat("start_vel"); - stats.m_flZoneExitSpeed[0][0] = pEvent->GetFloat("end_vel"); - stats.m_flZoneVelocityAvg[0][0] = pEvent->GetFloat("avg_vel"); - stats.m_flZoneVelocityMax[0][0] = pEvent->GetFloat("max_vel"); + stats->m_flZoneEnterSpeed[0][0] = pEvent->GetFloat("start_vel"); + stats->m_flZoneExitSpeed[0][0] = pEvent->GetFloat("end_vel"); + stats->m_flZoneVelocityAvg[0][0] = pEvent->GetFloat("avg_vel"); + stats->m_flZoneVelocityMax[0][0] = pEvent->GetFloat("max_vel"); //2D - stats.m_flZoneEnterSpeed[0][1] = pEvent->GetFloat("start_vel_2D"); - stats.m_flZoneExitSpeed[0][1] = pEvent->GetFloat("end_vel_2D"); - stats.m_flZoneVelocityAvg[0][1] = pEvent->GetFloat("avg_vel_2D"); - stats.m_flZoneVelocityMax[0][1] = pEvent->GetFloat("max_vel_2D"); + stats->m_flZoneEnterSpeed[0][1] = pEvent->GetFloat("start_vel_2D"); + stats->m_flZoneExitSpeed[0][1] = pEvent->GetFloat("end_vel_2D"); + stats->m_flZoneVelocityAvg[0][1] = pEvent->GetFloat("avg_vel_2D"); + stats->m_flZoneVelocityMax[0][1] = pEvent->GetFloat("max_vel_2D"); m_flLastRunTime = pEvent->GetFloat("time"); } - else if (!Q_strcmp("stage_enter", pEvent->GetName())) + else if (!Q_strcmp("zone_enter", pEvent->GetName())) { - //NOTE: THE ONLY STAT BELOW THAT REQUIRES THE CURRENT STAGE GIVEN IN "stage_num" IS THE ENTER TIME! - //EVERYTHING ELSE IS m_iCurrentStage - 1 ! + //NOTE: THE ONLY STAT BELOW THAT REQUIRES THE CURRENT STAGE GIVEN IN "num" IS THE ENTER TIME! + //EVERYTHING ELSE IS m_iCurrentZone - 1 ! + + int currentZone = pEvent->GetInt("num"); + int entIndex = pEvent->GetInt("ent"); - int currentStage = pEvent->GetInt("stage_num"); - //Note: stage_enter_time will NOT change upon multiple entries to the same stage trigger (only set once per run) - stats.m_flZoneEnterTime[currentStage] = pEvent->GetFloat("stage_enter_time"); - //Reset the stage enter speed for the speedometer - stats.m_flZoneEnterSpeed[currentStage][0] = 0.0f; - stats.m_flZoneEnterSpeed[currentStage][1] = 0.0f; + RunStats_t *stats = GetRunStats(entIndex); - if (currentStage > 1) //MOM_TODO: || m_iStageCount < 2 (linear maps use checkpoints?) + if (currentZone <= m_iMapZoneCount) + { + //This if cheeck is needed for making sure the ending zone doesn't go out of bounds + //Since the only stats we care about that the ending trigger sends are not needed in this check + + //Note: enter_time will NOT change upon multiple entries to the same stage trigger (only set once per run) + stats->m_flZoneEnterTime[currentZone] = pEvent->GetFloat("enter_time"); + //Reset the stage enter speed for the speedometer + stats->m_flZoneEnterSpeed[currentZone][0] = 0.0f; + stats->m_flZoneEnterSpeed[currentZone][1] = 0.0f; + } + + if (currentZone > 1) //MOM_TODO: || m_iStageCount < 2 (linear maps use checkpoints?) { //The first stage doesn't have its time yet, we calculate it upon going into stage 2+ - stats.m_flZoneTime[currentStage - 1] = stats.m_flZoneEnterTime[currentStage] - stats.m_flZoneEnterTime[currentStage - 1]; + stats->m_flZoneTime[currentZone - 1] = stats->m_flZoneEnterTime[currentZone] - stats->m_flZoneEnterTime[currentZone - 1]; //And the rest of the stats are about the previous stage anyways, not calculated during stage 1 (start) - stats.m_flZoneStrafeSyncAvg[currentStage - 1] = pEvent->GetFloat("avg_sync"); - stats.m_flZoneStrafeSync2Avg[currentStage - 1] = pEvent->GetFloat("avg_sync2"); + stats->m_flZoneStrafeSyncAvg[currentZone - 1] = pEvent->GetFloat("avg_sync"); + stats->m_flZoneStrafeSync2Avg[currentZone - 1] = pEvent->GetFloat("avg_sync2"); - stats.m_flZoneExitSpeed[currentStage - 1][0] = pEvent->GetFloat("stage_exit_vel"); - stats.m_flZoneVelocityAvg[currentStage - 1][0] = pEvent->GetFloat("avg_vel"); - stats.m_flZoneVelocityMax[currentStage - 1][0] = pEvent->GetFloat("max_vel"); + stats->m_flZoneExitSpeed[currentZone - 1][0] = pEvent->GetFloat("exit_vel"); + stats->m_flZoneVelocityAvg[currentZone - 1][0] = pEvent->GetFloat("avg_vel"); + stats->m_flZoneVelocityMax[currentZone - 1][0] = pEvent->GetFloat("max_vel"); - stats.m_flZoneExitSpeed[currentStage - 1][1] = pEvent->GetFloat("stage_exit_vel_2D"); - stats.m_flZoneVelocityAvg[currentStage - 1][1] = pEvent->GetFloat("avg_vel_2D"); - stats.m_flZoneVelocityMax[currentStage - 1][1] = pEvent->GetFloat("max_vel_2D"); + stats->m_flZoneExitSpeed[currentZone - 1][1] = pEvent->GetFloat("exit_vel_2D"); + stats->m_flZoneVelocityAvg[currentZone - 1][1] = pEvent->GetFloat("avg_vel_2D"); + stats->m_flZoneVelocityMax[currentZone - 1][1] = pEvent->GetFloat("max_vel_2D"); - stats.m_iZoneJumps[currentStage - 1] = pEvent->GetInt("num_jumps"); - stats.m_iZoneStrafes[currentStage - 1] = pEvent->GetInt("num_strafes"); - } + stats->m_iZoneJumps[currentZone - 1] = pEvent->GetInt("num_jumps"); + stats->m_iZoneStrafes[currentZone - 1] = pEvent->GetInt("num_strafes"); + } } - else if (!Q_strcmp("stage_exit", pEvent->GetName())) + else if (!Q_strcmp("zone_exit", pEvent->GetName())) { - int currentStage = pEvent->GetInt("stage_num"); - //Set the stage enter speed upon exiting the trigger - float enterVel = pEvent->GetFloat("stage_enter_vel"); - float enterVel2D = pEvent->GetFloat("stage_enter_vel_2D"); - for (int i = 0; i < 2; i++) + int currentZone = pEvent->GetInt("num"); + int entIndex = pEvent->GetInt("ent"); + //This happens when the player/ghost exits the ending trigger (despawn or restart) + if (currentZone > m_iMapZoneCount) { - float vel = i == 0 ? enterVel : enterVel2D; - stats.m_flZoneEnterSpeed[currentStage][i] = vel; - if (currentStage == 1) - stats.m_flZoneEnterSpeed[currentStage - 1][i] = vel;//Set overall enter vel + m_EntRunStats.Remove(entIndex); + } + else + { + //Set the stage enter speed upon exiting the trigger + float enterVel = pEvent->GetFloat("enter_vel"); + float enterVel2D = pEvent->GetFloat("enter_vel_2D"); + RunStats_t *stats = GetRunStats(entIndex); + + for (int i = 0; i < 2; i++) + { + float vel = i == 0 ? enterVel : enterVel2D; + stats->m_flZoneEnterSpeed[currentZone][i] = vel; + if (currentZone == 1) + stats->m_flZoneEnterSpeed[currentZone - 1][i] = vel;//Set overall enter vel + } } } else if (!Q_strcmp("run_save", pEvent->GetName())) @@ -93,17 +118,19 @@ void C_Momentum_EventListener::FireGameEvent(IGameEvent *pEvent) } else if (!Q_strcmp("timer_state", pEvent->GetName())) { - m_bTimerIsRunning = pEvent->GetBool("is_running"); + //m_bTimerIsRunning = pEvent->GetBool("is_running"); } else if (!Q_strcmp("keypress", pEvent->GetName())) { - stats.m_iZoneJumps[0] = pEvent->GetInt("num_jumps"); - stats.m_iZoneStrafes[0] = pEvent->GetInt("num_strafes"); + int entIndex = pEvent->GetInt("ent"); + RunStats_t *stats = GetRunStats(entIndex); + stats->m_iZoneJumps[0] = pEvent->GetInt("num_jumps"); + stats->m_iZoneStrafes[0] = pEvent->GetInt("num_strafes"); } else if (!Q_strcmp("map_init", pEvent->GetName())) { m_bMapIsLinear = pEvent->GetBool("is_linear"); - m_iMapCheckpointCount = pEvent->GetInt("num_checkpoints"); + m_iMapZoneCount = pEvent->GetInt("num_zones"); } } diff --git a/mp/src/game/client/momentum/mom_event_listener.h b/mp/src/game/client/momentum/mom_event_listener.h index c5322dc971..d20ff270b8 100644 --- a/mp/src/game/client/momentum/mom_event_listener.h +++ b/mp/src/game/client/momentum/mom_event_listener.h @@ -6,24 +6,34 @@ class C_Momentum_EventListener : public CGameEventListener { public: - C_Momentum_EventListener() : - m_bTimerIsRunning(false), + C_Momentum_EventListener() : m_bTimeDidSave(false), - m_bTimeDidUpload(false), - stats() + m_bTimeDidUpload(false), m_bMapIsLinear(false), m_iMapZoneCount(0), + m_flLastRunTime(0) { } void Init(); void FireGameEvent(IGameEvent* pEvent) override; - bool m_bTimerIsRunning; + RunStats_t *GetRunStats(int entIndex) + { + unsigned short index; + if ((index = m_EntRunStats.Find(entIndex)) != m_EntRunStats.InvalidIndex()) + { + return &m_EntRunStats.Element(index); + } + RunStats_t temp(m_iMapZoneCount); + m_EntRunStats.Insert(entIndex, temp); + return GetRunStats(entIndex); + } + bool m_bTimeDidSave, m_bTimeDidUpload; bool m_bMapIsLinear; - int m_iMapCheckpointCount; + int m_iMapZoneCount; - RunStats_t stats;//MOM_TODO: Move this to the player and ghost ent send/recv table + CUtlMap m_EntRunStats; float m_flLastRunTime; //this is the "adjusted" precision-fixed time value that was calculated on the server DLL char m_szRunUploadStatus[512];//MOM_TODO: determine best (max) size for this diff --git a/mp/src/game/client/momentum/ui/hud_comparisons.cpp b/mp/src/game/client/momentum/ui/hud_comparisons.cpp index e59733123f..adffd17dfc 100644 --- a/mp/src/game/client/momentum/ui/hud_comparisons.cpp +++ b/mp/src/game/client/momentum/ui/hud_comparisons.cpp @@ -77,7 +77,7 @@ C_RunComparisons::C_RunComparisons(const char *pElementName) SetKeyBoardInputEnabled(false); // MOM_TODO: will we want keybinds? Hotkeys? SetMouseInputEnabled(false); SetHiddenBits(HIDEHUD_WEAPONSELECTION); - m_iCurrentStage = 0; + m_iCurrentZone = 0; m_bLoadedComparison = false; m_iWidestLabel = 0; m_iWidestValue = 0; @@ -104,8 +104,22 @@ void C_RunComparisons::Init() bool C_RunComparisons::ShouldDraw() { - return mom_comparisons.GetBool() && m_bLoadedComparison && CHudElement::ShouldDraw() && g_MOMEventListener && - g_MOMEventListener->m_bTimerIsRunning; + C_MomentumPlayer *pPlayer = ToCMOMPlayer(C_BasePlayer::GetLocalPlayer()); + bool shouldDrawLocal = false; + if (pPlayer) + { + if (pPlayer->IsWatchingReplay()) + { + //MOM_TODO: Should we have a convar against this? + C_MomentumReplayGhostEntity *pGhost = pPlayer->GetReplayEnt(); + shouldDrawLocal = pGhost->m_RunData.m_bTimerRunning && !pGhost->m_RunData.m_bMapFinished; + } + else + { + shouldDrawLocal = pPlayer->m_RunData.m_bTimerRunning && !pPlayer->m_RunData.m_bMapFinished; + } + } + return mom_comparisons.GetBool() && m_bLoadedComparison && CHudElement::ShouldDraw() && shouldDrawLocal; } void C_RunComparisons::Reset() @@ -114,6 +128,7 @@ void C_RunComparisons::Reset() m_iMaxWide = m_iDefaultWidth; m_iWidestLabel = 0; m_iWidestValue = 0; + m_iEntIndex = -1; } void C_RunComparisons::FireGameEvent(IGameEvent *event) @@ -160,7 +175,10 @@ void C_RunComparisons::OnThink() { C_MomentumPlayer *pPlayer = ToCMOMPlayer(C_BasePlayer::GetLocalPlayer()); if (pPlayer) - m_iCurrentStage = pPlayer->m_RunData.m_iCurrentZone; + { + m_iCurrentZone = pPlayer->m_RunData.m_iCurrentZone; + m_iEntIndex = pPlayer->IsWatchingReplay() ? pPlayer->GetReplayEnt()->entindex() : pPlayer->entindex(); + } if (!mom_comparisons_time_show_overall.GetBool() && !mom_comparisons_time_show_perstage.GetBool()) { @@ -184,16 +202,16 @@ int C_RunComparisons::GetMaximumTall() int fontTall = surface()->GetFontTall(m_hTextFont) + 2; // font tall and padding toReturn += fontTall; // Comparing against: (run) int stageBuffer = mom_comparisons_max_stages.GetInt(); - int lowerBound = m_iCurrentStage - stageBuffer; + int lowerBound = m_iCurrentZone - stageBuffer; - for (int i = 1; i < m_iCurrentStage; i++) + for (int i = 1; i < m_iCurrentZone; i++) { // Note: Say our current stage is 5 and our buffer is 4. We don't look at stage 5, // and the lower bound becomes 1. If the check was i > lowerBound, stage 1 would be ignored, // and the panel would thus only show 3 stages (2, 3, and 4). So it must be >=. if (i >= lowerBound) { - if (i == (m_iCurrentStage - 1)) + if (i == (m_iCurrentZone - 1)) { // Add everything that the user compares. // Time @@ -253,28 +271,31 @@ void C_RunComparisons::GetDiffColor(float diff, Color *into, bool positiveIsGain // If you pass null to any of the pointer args, they will not be touched. This allows for // only obtaining the actual, only obtaining the comparison, or only obtaining the color. -void C_RunComparisons::GetComparisonString(ComparisonString_t type, int stage, char *ansiActualBufferOut, +void C_RunComparisons::GetComparisonString(ComparisonString_t type, int entIndex, int zone, char *ansiActualBufferOut, char *ansiCompareBufferOut, Color *compareColorOut) { ConVarRef velTypeVar("mom_speedometer_hvel"); int velType = velTypeVar.GetInt(); // Type of velocity comparison we're making (3D vs Horizontal) float diff = 0.0f; // Difference between the current and the compared-to. - float act; // Actual value that the player has for this stage. + float act; // Actual value that the player has for this zone. char tempANSITimeOutput[BUFSIZETIME], tempANSITimeActual[BUFSIZETIME]; // Only used for time comparisons, ignored otherwise. char diffChar = '\0'; // The character used for showing the diff: + or - - // Calculate diffs only if we loaded a comparison + + //Get the run stats for the ent index, usually the player but can be the ghost the player is spectating + RunStats_t *stats = g_MOMEventListener->GetRunStats(entIndex); + switch (type) { case TIME_OVERALL: case STAGE_TIME: // Get the time difference in seconds. - act = type == TIME_OVERALL ? g_MOMEventListener->stats.m_flZoneEnterTime[stage + 1] - : g_MOMEventListener->stats.m_flZoneTime[stage]; + act = type == TIME_OVERALL ? stats->m_flZoneEnterTime[zone + 1] + : stats->m_flZoneTime[zone]; if (m_bLoadedComparison) - diff = act - (type == TIME_OVERALL ? m_rcCurrentComparison->overallSplits[stage] - : m_rcCurrentComparison->stageSplits[stage - 1]); + diff = act - (type == TIME_OVERALL ? m_rcCurrentComparison->overallSplits[zone] + : m_rcCurrentComparison->stageSplits[zone - 1]); // Are we losing time compared to the run? // If diff > 0, that means you're falling behind (losing time to) your PB! @@ -286,45 +307,45 @@ void C_RunComparisons::GetComparisonString(ComparisonString_t type, int stage, c break; case VELOCITY_AVERAGE: // Get the vel difference - act = g_MOMEventListener->stats.m_flZoneVelocityAvg[stage][velType]; + act = stats->m_flZoneVelocityAvg[zone][velType]; if (m_bLoadedComparison) diff = act - - m_rcCurrentComparison->stageAvgVels[velType][stage - 1]; //- 1 due to array indexing (0 is stage 1) + m_rcCurrentComparison->stageAvgVels[velType][zone - 1]; //- 1 due to array indexing (0 is zone 1) break; case VELOCITY_EXIT: - act = g_MOMEventListener->stats.m_flZoneExitSpeed[stage][velType]; + act = stats->m_flZoneExitSpeed[zone][velType]; if (m_bLoadedComparison) - diff = act - m_rcCurrentComparison->stageExitVels[velType][stage - 1]; + diff = act - m_rcCurrentComparison->stageExitVels[velType][zone - 1]; break; case VELOCITY_MAX: - act = g_MOMEventListener->stats.m_flZoneVelocityMax[stage][velType]; + act = stats->m_flZoneVelocityMax[zone][velType]; if (m_bLoadedComparison) - diff = act - m_rcCurrentComparison->stageMaxVels[velType][stage - 1]; + diff = act - m_rcCurrentComparison->stageMaxVels[velType][zone - 1]; break; case VELOCITY_ENTER: - act = g_MOMEventListener->stats.m_flZoneEnterSpeed[stage][velType]; + act = stats->m_flZoneEnterSpeed[zone][velType]; if (m_bLoadedComparison) - diff = act - m_rcCurrentComparison->stageEnterVels[velType][stage - 1]; + diff = act - m_rcCurrentComparison->stageEnterVels[velType][zone - 1]; break; case STAGE_SYNC1: - act = g_MOMEventListener->stats.m_flZoneStrafeSyncAvg[stage]; + act = stats->m_flZoneStrafeSyncAvg[zone]; if (m_bLoadedComparison) - diff = act - m_rcCurrentComparison->stageAvgSync1[stage - 1]; + diff = act - m_rcCurrentComparison->stageAvgSync1[zone - 1]; break; case STAGE_SYNC2: - act = g_MOMEventListener->stats.m_flZoneStrafeSync2Avg[stage]; + act = stats->m_flZoneStrafeSync2Avg[zone]; if (m_bLoadedComparison) - diff = act - m_rcCurrentComparison->stageAvgSync2[stage - 1]; + diff = act - m_rcCurrentComparison->stageAvgSync2[zone - 1]; break; case STAGE_JUMPS: - act = g_MOMEventListener->stats.m_iZoneJumps[stage]; + act = stats->m_iZoneJumps[zone]; if (m_bLoadedComparison) - diff = act - m_rcCurrentComparison->stageJumps[stage - 1]; + diff = act - m_rcCurrentComparison->stageJumps[zone - 1]; break; case STAGE_STRAFES: - act = g_MOMEventListener->stats.m_iZoneStrafes[stage]; + act = stats->m_iZoneStrafes[zone]; if (m_bLoadedComparison) - diff = act - m_rcCurrentComparison->stageStrafes[stage - 1]; + diff = act - m_rcCurrentComparison->stageStrafes[zone - 1]; break; default: return; @@ -423,7 +444,7 @@ void C_RunComparisons::DrawComparisonString(ComparisonString_t string, int stage } // Obtain the actual value, comparison string, and corresponding color - GetComparisonString(string, stage, actualValueANSI, compareValueANSI, &compareColor); + GetComparisonString(string, m_iEntIndex, stage, actualValueANSI, compareValueANSI, &compareColor); // Pad the compare type with a couple spaces in front. V_snprintf(compareTypeANSI, BUFSIZELOCL, " %s", localized); @@ -500,7 +521,7 @@ void C_RunComparisons::Paint() // MOM_TODO: Linear maps will have checkpoints, which rid the exit velocity stat? // Get player current stage - int currentStage = m_iCurrentStage; + int currentStage = m_iCurrentZone; // We want to create a "buffer" of stages. The very last stage should show // full comparisons, and be the most bottom one. However, the stages before that need @@ -647,7 +668,7 @@ void C_RunComparisons::Paint() + 2; // Padding // Get just the comparison value, no actual value needed as it clutters up the panel - GetComparisonString(timeType, i, nullptr, timeComparisonString, &comparisonColor); + GetComparisonString(timeType, m_iEntIndex, i, nullptr, timeComparisonString, &comparisonColor); // See if this updates our max width. SetMaxWide(newXPos + UTIL_ComputeStringWidth(m_hTextFont, timeComparisonString) + 2); diff --git a/mp/src/game/client/momentum/ui/hud_comparisons.h b/mp/src/game/client/momentum/ui/hud_comparisons.h index b1a4acf8c7..5dcd38e505 100644 --- a/mp/src/game/client/momentum/ui/hud_comparisons.h +++ b/mp/src/game/client/momentum/ui/hud_comparisons.h @@ -51,7 +51,7 @@ class C_RunComparisons : public CHudElement, public Panel } void UnloadComparisons(); void DrawComparisonString(ComparisonString_t, int stage, int Ypos); - void GetComparisonString(ComparisonString_t type, int stage, char *ansiActualBufferOut, char *ansiCompareBufferOut, Color *compareColorOut); + void GetComparisonString(ComparisonString_t type, int entIndex, int zone, char *ansiActualBufferOut, char *ansiCompareBufferOut, Color *compareColorOut); void GetDiffColor(float diff, Color *into, bool positiveIsGain); int GetMaximumTall(); void SetMaxWide(int); @@ -94,12 +94,13 @@ class C_RunComparisons : public CHudElement, public Panel int m_iDefaultWidth, m_iDefaultTall, m_iDefaultXPos, m_iDefaultYPos; int m_iMaxWide, m_iWidestLabel, m_iWidestValue; - int m_iCurrentStage; + int m_iCurrentZone, m_iEntIndex; bool m_bLoadedComparison; RunCompare_t *m_rcCurrentComparison; }; +//Really hacky way to interface this hud element, as opposed to calling the gHUD.FindElement everywhere static C_RunComparisons *GetComparisons() { static C_RunComparisons *s_runcompare; diff --git a/mp/src/game/client/momentum/ui/hud_keypress.cpp b/mp/src/game/client/momentum/ui/hud_keypress.cpp index 6b6f892c68..6db55ccd8b 100644 --- a/mp/src/game/client/momentum/ui/hud_keypress.cpp +++ b/mp/src/game/client/momentum/ui/hud_keypress.cpp @@ -193,7 +193,7 @@ void CHudKeyPressDisplay::OnThink() C_MomentumReplayGhostEntity *pReplayEnt = dynamic_cast(pPlayer->GetObserverTarget()); if (pReplayEnt) { - m_bShouldDrawCounts = true; + m_bShouldDrawCounts = pReplayEnt->m_RunData.m_bTimerRunning; m_nButtons = pReplayEnt->m_nReplayButtons; m_nStrafes = pReplayEnt->m_iTotalStrafes; m_nJumps = pReplayEnt->m_iTotalJumps; @@ -205,9 +205,10 @@ void CHudKeyPressDisplay::OnThink() if (g_MOMEventListener) { //we should only draw the strafe/jump counters when the timer is running - m_bShouldDrawCounts = g_MOMEventListener->m_bTimerIsRunning; - m_nStrafes = g_MOMEventListener->stats.m_iZoneStrafes[0]; - m_nJumps = g_MOMEventListener->stats.m_iZoneJumps[0]; + m_bShouldDrawCounts = pPlayer->m_RunData.m_bTimerRunning; + RunStats_t *stats = g_MOMEventListener->GetRunStats(pPlayer->entindex()); + m_nStrafes = stats->m_iZoneStrafes[0]; + m_nJumps = stats->m_iZoneJumps[0]; } } } diff --git a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp index 824c93697e..54bd2b10f1 100644 --- a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp +++ b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp @@ -336,16 +336,18 @@ void CHudMapFinishedDialog::OnThink() //Is it going to be a localized string, except for errors that have to be specific? ConVarRef hvel("mom_speedometer_hvel"); - //MOM_TODO: Are we going to update to read replay file stats? - //RunStats_t *stats = pPlayer->IsWatchingReplay() ? &g_MOMEventListener->stats : pPlayer->GetReplayEnt()->m_RunData.m_RunStats; - m_flAvgSpeed = g_MOMEventListener->stats.m_flZoneVelocityAvg[0][hvel.GetBool()]; - m_flMaxSpeed = g_MOMEventListener->stats.m_flZoneVelocityMax[0][hvel.GetBool()]; - m_flEndSpeed = g_MOMEventListener->stats.m_flZoneExitSpeed[0][hvel.GetBool()]; - m_flStartSpeed = g_MOMEventListener->stats.m_flZoneEnterSpeed[0][hvel.GetBool()]; - m_flAvgSync2 = g_MOMEventListener->stats.m_flZoneStrafeSyncAvg[0]; - m_flAvgSync = g_MOMEventListener->stats.m_flZoneStrafeSync2Avg[0]; - m_iTotalJumps = g_MOMEventListener->stats.m_iZoneJumps[0]; - m_iTotalStrafes = g_MOMEventListener->stats.m_iZoneStrafes[0]; + + RunStats_t *stats = g_MOMEventListener->GetRunStats(pPlayer->IsWatchingReplay() ? pPlayer->GetReplayEnt()->entindex() : + pPlayer->entindex()); + m_flAvgSpeed = stats->m_flZoneVelocityAvg[0][hvel.GetBool()]; + m_flMaxSpeed = stats->m_flZoneVelocityMax[0][hvel.GetBool()]; + m_flEndSpeed = stats->m_flZoneExitSpeed[0][hvel.GetBool()]; + m_flStartSpeed = stats->m_flZoneEnterSpeed[0][hvel.GetBool()]; + m_flAvgSync2 = stats->m_flZoneStrafeSyncAvg[0]; + m_flAvgSync = stats->m_flZoneStrafeSync2Avg[0]; + m_iTotalJumps = stats->m_iZoneJumps[0]; + m_iTotalStrafes = stats->m_iZoneStrafes[0]; + //MOM_TODO: This needs updating to show the run time of the ghost mom_UTIL->FormatTime(g_MOMEventListener->m_flLastRunTime, m_pszRunTime); } } \ No newline at end of file diff --git a/mp/src/game/client/momentum/ui/hud_mapinfo.cpp b/mp/src/game/client/momentum/ui/hud_mapinfo.cpp index 524278016c..d8dbad3b59 100644 --- a/mp/src/game/client/momentum/ui/hud_mapinfo.cpp +++ b/mp/src/game/client/momentum/ui/hud_mapinfo.cpp @@ -116,7 +116,7 @@ void C_HudMapInfo::OnThink() m_bMapFinished = pLocal->m_RunData.m_bMapFinished; } - m_iZoneCount = g_MOMEventListener->m_iMapCheckpointCount; + m_iZoneCount = g_MOMEventListener->m_iMapZoneCount; m_bMapLinear = g_MOMEventListener->m_bMapIsLinear; } } diff --git a/mp/src/game/client/momentum/ui/hud_speedometer.cpp b/mp/src/game/client/momentum/ui/hud_speedometer.cpp index b929d67ae6..908a507833 100644 --- a/mp/src/game/client/momentum/ui/hud_speedometer.cpp +++ b/mp/src/game/client/momentum/ui/hud_speedometer.cpp @@ -101,12 +101,12 @@ class CHudSpeedMeter : public CHudElement, public CHudNumericDisplay void FireGameEvent(IGameEvent *pEvent) override { - if (!Q_strcmp(pEvent->GetName(), "stage_exit")) + if (!Q_strcmp(pEvent->GetName(), "zone_exit")) { // Fade the enter speed after 5 seconds (in event) g_pClientMode->GetViewportAnimationController()->StartAnimationSequence("FadeOutEnterSpeed"); } - else if (!Q_strcmp(pEvent->GetName(), "stage_enter")) + else if (!Q_strcmp(pEvent->GetName(), "zone_enter")) { // Reset the alpha if we hit a stage enter again g_pClientMode->GetViewportAnimationController()->StartAnimationSequence("ResetEnterSpeed"); @@ -125,6 +125,8 @@ class CHudSpeedMeter : public CHudElement, public CHudNumericDisplay Color secondaryColor; bool m_bRanFadeOutJumpSpeed; + int m_iEntIndex, m_iCurrentZone; + bool m_bEntInZone, m_bTimerIsRunning; protected: CPanelAnimationVar(Color, _bgColor, "BgColor", "Blank"); @@ -138,13 +140,16 @@ DECLARE_NAMED_HUDELEMENT(CHudSpeedMeter, HudSpeedMeter); CHudSpeedMeter::CHudSpeedMeter(const char *pElementName) : CHudElement(pElementName), CHudNumericDisplay(g_pClientMode->GetViewport(), "HudSpeedMeter") { - ListenForGameEvent("stage_exit"); - ListenForGameEvent("stage_enter"); + ListenForGameEvent("zone_exit"); + ListenForGameEvent("zone_enter"); SetProportional(true); SetKeyBoardInputEnabled(false); SetMouseInputEnabled(false); SetHiddenBits(HIDEHUD_WEAPONSELECTION); m_bRanFadeOutJumpSpeed = false; + m_iEntIndex = -1; + m_iCurrentZone = 0; + m_bEntInZone = false; } void CHudSpeedMeter::OnThink() @@ -167,6 +172,14 @@ void CHudSpeedMeter::OnThink() float lastJumpTime = (pGhost ? pGhost->m_RunData.m_flLastJumpTime : pPlayer->m_RunData.m_flLastJumpTime); + m_iEntIndex = pGhost ? pGhost->entindex() : pPlayer->entindex(); + + m_bEntInZone = pGhost ? pGhost->m_RunData.m_bIsInZone : pPlayer->m_RunData.m_bIsInZone; + + m_iCurrentZone = pGhost ? pGhost->m_RunData.m_iCurrentZone : pPlayer->m_RunData.m_iCurrentZone; + + m_bTimerIsRunning = pGhost ? pGhost->m_RunData.m_bTimerRunning : pPlayer->m_RunData.m_bTimerRunning; + int velType = mom_speedometer_hvel.GetBool(); // 1 is horizontal velocity if (gpGlobals->curtime - lastJumpTime > 5.0f) @@ -283,11 +296,8 @@ void CHudSpeedMeter::Paint() BaseClass::Paint(); - C_MomentumPlayer *pPlayer = ToCMOMPlayer(C_BasePlayer::GetLocalPlayer()); - // Draw the enter speed split, if toggled on - if (mom_speedometer_showenterspeed.GetBool() && pPlayer && !pPlayer->m_RunData.m_bIsInZone && - g_MOMEventListener->m_bTimerIsRunning) + if (mom_speedometer_showenterspeed.GetBool() && m_bTimerIsRunning) { int split_xpos; // Dynamically set int split_ypos = mom_speedometer_showlastjumpvel.GetBool() @@ -304,7 +314,8 @@ void CHudSpeedMeter::Paint() Color fg = GetFgColor(); Color actualColorFade = Color(fg.r(), fg.g(), fg.b(), stageStartAlpha); - g_MOMRunCompare->GetComparisonString(VELOCITY_ENTER, pPlayer->m_RunData.m_iCurrentZone, enterVelANSITemp, + + g_MOMRunCompare->GetComparisonString(VELOCITY_ENTER, m_iEntIndex, m_iCurrentZone, enterVelANSITemp, enterVelANSICompTemp, &compareColor); Q_snprintf(enterVelANSI, BUFSIZELOCL, "%i", static_cast(round(atof(enterVelANSITemp)))); diff --git a/mp/src/game/client/momentum/ui/hud_strafesync.cpp b/mp/src/game/client/momentum/ui/hud_strafesync.cpp index 71d5efa466..15dd1bc378 100644 --- a/mp/src/game/client/momentum/ui/hud_strafesync.cpp +++ b/mp/src/game/client/momentum/ui/hud_strafesync.cpp @@ -44,9 +44,22 @@ class CHudStrafeSyncDisplay : public CHudElement, public CHudNumericDisplay void OnThink() override; bool ShouldDraw() override { - C_MomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); - return pPlayer && strafesync_draw.GetBool() && CHudElement::ShouldDraw() && g_MOMEventListener && - g_MOMEventListener->m_bTimerIsRunning; + C_MomentumPlayer *pPlayer = ToCMOMPlayer(C_BasePlayer::GetLocalPlayer()); + bool shouldDrawLocal = false; + if (pPlayer) + { + if (pPlayer->IsWatchingReplay()) + { + //MOM_TODO: Should we have a convar against this? + C_MomentumReplayGhostEntity *pGhost = pPlayer->GetReplayEnt(); + shouldDrawLocal = pGhost->m_RunData.m_bTimerRunning && !pGhost->m_RunData.m_bMapFinished; + } + else + { + shouldDrawLocal = pPlayer->m_RunData.m_bTimerRunning && !pPlayer->m_RunData.m_bMapFinished; + } + } + return strafesync_draw.GetBool() && CHudElement::ShouldDraw() && shouldDrawLocal; } void Reset() override @@ -200,9 +213,22 @@ class CHudStrafeSyncBar : public CHudFillableBar void OnThink() override; bool ShouldDraw() override { - C_MomentumPlayer *pPlayer = ToCMOMPlayer(CBasePlayer::GetLocalPlayer()); - return (pPlayer && strafesync_drawbar.GetBool() && CHudElement::ShouldDraw() && g_MOMEventListener && - g_MOMEventListener->m_bTimerIsRunning); + C_MomentumPlayer *pPlayer = ToCMOMPlayer(C_BasePlayer::GetLocalPlayer()); + bool shouldDrawLocal = false; + if (pPlayer) + { + if (pPlayer->IsWatchingReplay()) + { + //MOM_TODO: Should we have a convar against this? + C_MomentumReplayGhostEntity *pGhost = pPlayer->GetReplayEnt(); + shouldDrawLocal = pGhost->m_RunData.m_bTimerRunning && !pGhost->m_RunData.m_bMapFinished; + } + else + { + shouldDrawLocal = pPlayer->m_RunData.m_bTimerRunning && !pPlayer->m_RunData.m_bMapFinished; + } + } + return strafesync_drawbar.GetBool() && CHudElement::ShouldDraw() && shouldDrawLocal; } void Reset() override diff --git a/mp/src/game/client/momentum/ui/hud_timer.cpp b/mp/src/game/client/momentum/ui/hud_timer.cpp index 78da3d16d1..2771f6882d 100644 --- a/mp/src/game/client/momentum/ui/hud_timer.cpp +++ b/mp/src/game/client/momentum/ui/hud_timer.cpp @@ -80,7 +80,7 @@ class C_Timer : public CHudElement, public Panel CPanelAnimationVarAliasType(int, split_ypos, "split_ypos", "19", "proportional_ypos"); private: - int m_iStageCurrent, m_iStageCount; + int m_iZoneCurrent, m_iZoneCount; int initialTall; wchar_t m_pwCurrentTime[BUFSIZETIME]; @@ -107,7 +107,9 @@ class C_Timer : public CHudElement, public Panel bool m_bPlayerHasPracticeMode; bool m_bShowCheckpoints; bool m_bMapFinished; + bool m_bMapIsLinear; int m_iCheckpointCount, m_iCheckpointCurrent; + int m_iEntIndex; char stLocalized[BUFSIZELOCL], cpLocalized[BUFSIZELOCL], linearLocalized[BUFSIZELOCL], startZoneLocalized[BUFSIZELOCL], mapFinishedLocalized[BUFSIZELOCL], practiceModeLocalized[BUFSIZELOCL], noTimerLocalized[BUFSIZELOCL]; @@ -134,7 +136,8 @@ void C_Timer::Init() HOOK_HUD_MESSAGE(C_Timer, Timer_Checkpoint); initialTall = 48; m_iTotalTicks = 0; - m_iStageCount = 0; + m_iZoneCount = 0; + m_iEntIndex = -1; // Reset(); // cache localization strings @@ -153,14 +156,16 @@ void C_Timer::Reset() m_bIsRunning = false; m_bTimerRan = false; m_iTotalTicks = 0; - m_iStageCurrent = 1; + m_iZoneCurrent = 1; m_bShowCheckpoints = false; m_bWereCheatsActivated = false; m_bPlayerHasPracticeMode = false; m_bPlayerInZone = false; m_bMapFinished = false; + m_bMapIsLinear = false; m_iCheckpointCount = 0; m_iCheckpointCurrent = 0; + m_iEntIndex = -1; } void C_Timer::MsgFunc_Timer_State(bf_read &msg) @@ -210,6 +215,7 @@ void C_Timer::MsgFunc_Timer_State(bf_read &msg) void C_Timer::MsgFunc_Timer_Reset(bf_read &msg) { Reset(); } +//MOM_TODO: This should be moved to the player void C_Timer::MsgFunc_Timer_Checkpoint(bf_read &msg) { m_bShowCheckpoints = msg.ReadOneBit(); @@ -232,11 +238,15 @@ void C_Timer::OnThink() C_MomentumPlayer *pLocal = ToCMOMPlayer(C_BasePlayer::GetLocalPlayer()); if (pLocal && g_MOMEventListener) { - m_iStageCurrent = pLocal->m_RunData.m_iCurrentZone; - m_bPlayerInZone = pLocal->m_RunData.m_bIsInZone; - m_bMapFinished = pLocal->m_RunData.m_bMapFinished; - m_bPlayerHasPracticeMode = pLocal->m_bHasPracticeMode; - m_iStageCount = g_MOMEventListener->m_iMapCheckpointCount; + C_MomentumReplayGhostEntity *pGhost = pLocal->GetReplayEnt(); + C_MOMRunEntityData *runData = pGhost ? &pGhost->m_RunData : &pLocal->m_RunData; + m_iEntIndex = pGhost ? pGhost->entindex() : pLocal->entindex(); + m_iZoneCurrent = runData->m_iCurrentZone; + m_bPlayerInZone = runData->m_bIsInZone; + m_bMapFinished = runData->m_bMapFinished; + m_bPlayerHasPracticeMode = pGhost ? false : pLocal->m_bHasPracticeMode; + m_iZoneCount = g_MOMEventListener->m_iMapZoneCount; + m_bMapIsLinear = g_MOMEventListener->m_bMapIsLinear; } } @@ -263,19 +273,19 @@ void C_Timer::Paint(void) Color compareColor = GetFgColor(); // MOM_TODO: this will have to handle checkpoints as well! - if (m_iStageCurrent > 1) + if (m_iZoneCurrent > 1) { // MOM_TODO: m_bMapIsLinear needs to be passed here Q_snprintf(prevStageString, BUFSIZELOCL, "%s %i", - stLocalized, // Stage localization (MOM_TODO: "Checkpoint:" if linear) - m_iStageCurrent - 1); // Last stage number + m_bMapIsLinear ? cpLocalized : stLocalized, // Stage localization ("Checkpoint:" if linear) + m_iZoneCurrent - 1); // Last stage number ANSI_TO_UNICODE(prevStageString, prevStageStringUnicode); ConVarRef timeType("mom_comparisons_time_type"); // This void works even if there is no comparison loaded - g_MOMRunCompare->GetComparisonString(timeType.GetBool() ? STAGE_TIME : TIME_OVERALL, m_iStageCurrent - 1, - m_pszStageTimeString, comparisonANSI, &compareColor); + g_MOMRunCompare->GetComparisonString(timeType.GetBool() ? STAGE_TIME : TIME_OVERALL, m_iEntIndex, + m_iZoneCurrent - 1, m_pszStageTimeString, comparisonANSI, &compareColor); // Convert the split to Unicode ANSI_TO_UNICODE(m_pszStageTimeString, m_pwStageTimeLabel); @@ -337,7 +347,7 @@ void C_Timer::Paint(void) surface()->DrawPrintText(m_pwCurrentCheckpoints, wcslen(m_pwCurrentCheckpoints)); } // don't draw stages when drawing checkpoints, and vise versa. - else if (m_iStageCurrent > 1 && m_bIsRunning) + else if (m_iZoneCurrent > 1 && m_bIsRunning) { // only draw split timer if we are on stage/checkpoint 2 (not start, which is 1) or above. bool hasComparison = g_MOMRunCompare->LoadedComparison(); diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index 032e8629ad..9fdb5c72ee 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -6,6 +6,8 @@ void CTimer::Start(int start) { if (m_bUsingCPMenu) return; + ConVarRef zoneEdit("mom_zone_edit"); + if (zoneEdit.GetBool()) return; m_iStartTick = start; SetRunning(true); @@ -95,7 +97,7 @@ void CTimer::LoadLocalTimes(const char *szMapname) t.tickrate = kv->GetFloat("rate"); t.date = static_cast(kv->GetInt("date")); t.flags = kv->GetInt("flags"); - t.RunStats = RunStats_t(GetStageCount()); + t.RunStats = RunStats_t(GetZoneCount()); for (KeyValues *subKv = kv->GetFirstSubKey(); subKv; subKv = subKv->GetNextKey()) { @@ -112,14 +114,14 @@ void CTimer::LoadLocalTimes(const char *szMapname) //3D Velocity Stats t.RunStats.m_flZoneVelocityAvg[i][0] = subKv->GetFloat("avg_vel"); t.RunStats.m_flZoneVelocityMax[i][0] = subKv->GetFloat("max_vel"); - t.RunStats.m_flZoneEnterSpeed[i][0] = subKv->GetFloat("stage_enter_vel"); - t.RunStats.m_flZoneExitSpeed[i][0] = subKv->GetFloat("stage_exit_vel"); + t.RunStats.m_flZoneEnterSpeed[i][0] = subKv->GetFloat("enter_vel"); + t.RunStats.m_flZoneExitSpeed[i][0] = subKv->GetFloat("exit_vel"); //2D Velocity Stats t.RunStats.m_flZoneVelocityAvg[i][1] = subKv->GetFloat("avg_vel_2D"); t.RunStats.m_flZoneVelocityMax[i][1] = subKv->GetFloat("max_vel_2D"); - t.RunStats.m_flZoneEnterSpeed[i][1] = subKv->GetFloat("stage_enter_vel_2D"); - t.RunStats.m_flZoneExitSpeed[i][1] = subKv->GetFloat("stage_exit_vel_2D"); + t.RunStats.m_flZoneEnterSpeed[i][1] = subKv->GetFloat("enter_vel_2D"); + t.RunStats.m_flZoneExitSpeed[i][1] = subKv->GetFloat("exit_vel_2D"); } if (!Q_strncmp(subKv->GetName(), "total", Q_strlen("total"))) { @@ -185,11 +187,11 @@ void CTimer::SaveTime() pOverallKey->SetFloat("max_vel_2D", t.RunStats.m_flZoneVelocityMax[0][1]); char stageName[9]; // "stage 64\0" - if (GetStageCount() > 1) + if (GetZoneCount() > 1) { - for (int i2 = 1; i2 <= GetStageCount(); i2++) + for (int i2 = 1; i2 <= GetZoneCount(); i2++) { - Q_snprintf(stageName, sizeof(stageName), "zone %d", i2);//MOM_TODO: || checkpoint %d + Q_snprintf(stageName, sizeof(stageName), "zone %d", i2); KeyValues *pStageKey = new KeyValues(stageName); pStageKey->SetFloat("time", t.RunStats.m_flZoneTime[i2]); @@ -201,13 +203,13 @@ void CTimer::SaveTime() pStageKey->SetFloat("avg_vel", t.RunStats.m_flZoneVelocityAvg[i2][0]); pStageKey->SetFloat("max_vel", t.RunStats.m_flZoneVelocityMax[i2][0]); - pStageKey->SetFloat("stage_enter_vel", t.RunStats.m_flZoneEnterSpeed[i2][0]); - pStageKey->SetFloat("stage_exit_vel", t.RunStats.m_flZoneExitSpeed[i2][0]); + pStageKey->SetFloat("enter_vel", t.RunStats.m_flZoneEnterSpeed[i2][0]); + pStageKey->SetFloat("exit_vel", t.RunStats.m_flZoneExitSpeed[i2][0]); pStageKey->SetFloat("avg_vel_2D", t.RunStats.m_flZoneVelocityAvg[i2][1]); pStageKey->SetFloat("max_vel_2D", t.RunStats.m_flZoneVelocityMax[i2][1]); - pStageKey->SetFloat("stage_enter_vel_2D", t.RunStats.m_flZoneEnterSpeed[i2][1]); - pStageKey->SetFloat("stage_exit_vel_2D", t.RunStats.m_flZoneExitSpeed[i2][1]); + pStageKey->SetFloat("enter_vel_2D", t.RunStats.m_flZoneEnterSpeed[i2][1]); + pStageKey->SetFloat("exit_vel_2D", t.RunStats.m_flZoneExitSpeed[i2][1]); pSubkey->AddSubKey(pStageKey); } @@ -241,7 +243,6 @@ void CTimer::Stop(bool endTrigger /* = false */) IGameEvent *runSaveEvent = gameeventmanager->CreateEvent("run_save"); IGameEvent *timerStateEvent = gameeventmanager->CreateEvent("timer_state"); - IGameEvent *timerStopEvent = gameeventmanager->CreateEvent("timer_stopped"); if (endTrigger && !m_bWereCheatsActivated && pPlayer) { @@ -263,8 +264,7 @@ void CTimer::Stop(bool endTrigger /* = false */) localTimes.AddToTail(t); - SaveTime(); - + SaveTime(); } else if (runSaveEvent) //reset run saved status to false if we cant or didn't save { @@ -276,43 +276,6 @@ void CTimer::Stop(bool endTrigger /* = false */) timerStateEvent->SetBool("is_running", false); gameeventmanager->FireEvent(timerStateEvent); } - if (timerStopEvent && pPlayer) - { - timerStopEvent->SetFloat("avg_sync", pPlayer->m_PlayerRunStats.m_flZoneStrafeSyncAvg[0]); - timerStopEvent->SetFloat("avg_sync2", pPlayer->m_PlayerRunStats.m_flZoneStrafeSync2Avg[0]); - timerStopEvent->SetInt("num_strafes", pPlayer->m_PlayerRunStats.m_iZoneStrafes[0]); - timerStopEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats.m_iZoneJumps[0]); - - //3D VELCOCITY STATS - INDEX 0 - timerStopEvent->SetFloat("avg_vel", pPlayer->m_PlayerRunStats.m_flZoneVelocityAvg[0][0]); - timerStopEvent->SetFloat("start_vel", pPlayer->m_PlayerRunStats.m_flZoneEnterSpeed[1][0]); - float endvel = pPlayer->GetLocalVelocity().Length(); - timerStopEvent->SetFloat("end_vel", endvel); - if (endvel > pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[0][0]) - timerStopEvent->SetFloat("max_vel", endvel); - else - timerStopEvent->SetFloat("max_vel", pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[0][0]); - pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[0][0] = endvel; //we have to set end speed here or else it will be saved as 0 - - //2D VELOCITY STATS - INDEX 1 - timerStopEvent->SetFloat("avg_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneVelocityAvg[0][1]); - timerStopEvent->SetFloat("start_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneEnterSpeed[1][1]); - float endvel2D = pPlayer->GetLocalVelocity().Length2D(); - timerStopEvent->SetFloat("end_vel_2D", endvel2D); - if (endvel2D > pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[0][1]) - timerStopEvent->SetFloat("max_vel_2D", endvel2D); - else - timerStopEvent->SetFloat("max_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[0][1]); - pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[0][1] = endvel2D; - - timerStopEvent->SetFloat("time", GetLastRunTime()); - gameeventmanager->FireEvent(timerStopEvent); - } - if (pPlayer) - { - pPlayer->m_RunData.m_bIsInZone = endTrigger; - pPlayer->m_RunData.m_bMapFinished = endTrigger; - } //stop replay recording if (g_ReplaySystem->IsRecording(pPlayer)) @@ -341,8 +304,8 @@ void CTimer::DispatchMapInfo() { //MOM_TODO: for now it's assuming stages are on staged maps, load this from //either the RequestStageCount() method, or something else (map info file?) - mapInitEvent->SetBool("is_linear", m_iStageCount == 0); - mapInitEvent->SetInt("num_checkpoints", m_iStageCount); + mapInitEvent->SetBool("is_linear", m_iZoneCount == 0); + mapInitEvent->SetInt("num_zones", m_iZoneCount); gameeventmanager->FireEvent(mapInitEvent); } } @@ -351,14 +314,14 @@ void CTimer::OnMapStart(const char *pMapName) { SetGameModeConVars(); m_bWereCheatsActivated = false; - RequestStageCount(); + RequestZoneCount(); LoadLocalTimes(pMapName); //MOM_TODO: g_Timer->LoadOnlineTimes(); } //MOM_TODO: This needs to update to include checkpoint triggers placed in linear //maps to allow players to compare at certain points. -void CTimer::RequestStageCount() +void CTimer::RequestZoneCount() { CTriggerStage *stage = static_cast(gEntList.FindEntityByClassname(nullptr, "trigger_momentum_timer_stage")); int iCount = 1;//CTriggerStart counts as one @@ -367,7 +330,7 @@ void CTimer::RequestStageCount() iCount++; stage = static_cast(gEntList.FindEntityByClassname(stage, "trigger_momentum_timer_stage")); } - m_iStageCount = iCount; + m_iZoneCount = iCount; } //This function is called every time CTriggerStage::StartTouch is called float CTimer::CalculateStageTime(int stage) @@ -376,12 +339,12 @@ float CTimer::CalculateStageTime(int stage) { float originalTime = static_cast(gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; //If the stage is a new one, we store the time we entered this stage in - m_iStageEnterTime[stage] = stage == 1 ? 0.0f : //Always returns 0 for first stage. + m_iZoneEnterTime[stage] = stage == 1 ? 0.0f : //Always returns 0 for first stage. originalTime + m_flTickOffsetFix[stage-1]; - DevLog("Original Time: %f\n New Time: %f", originalTime, m_iStageEnterTime[stage]); + DevLog("Original Time: %f\n New Time: %f", originalTime, m_iZoneEnterTime[stage]); } m_iLastStage = stage; - return m_iStageEnterTime[stage]; + return m_iZoneEnterTime[stage]; } void CTimer::DispatchResetMessage() { @@ -403,7 +366,7 @@ void CTimer::DispatchStateMessage() user.MakeReliable(); UserMessageBegin(user, "Timer_State"); WRITE_BOOL(m_bIsRunning); - WRITE_LONG(m_iStartTick); + WRITE_LONG(m_iStartTick);//MOM_TODO: m_iStartTick is also MessageEnd(); } } @@ -428,7 +391,7 @@ void CTimer::CalculateTickIntervalOffset(CMomentumPlayer* pPlayer, const int zon if (!pPlayer) return; Ray_t ray; Vector prevOrigin, origin, velocity = pPlayer->GetLocalVelocity(); - // Because trigger touch is calculated using colission hull rather than the player's origin (which is their world space center, + // Because trigger touch is calculated using colission hull rather than the player's origin (which is their world space center), // this origin is actually the player's local origin offset by their colission hull (depending on which direction they are moving), // so that we trace from the point in space where the player actually exited touch with the trigger, rather than their world center. diff --git a/mp/src/game/server/momentum/Timer.h b/mp/src/game/server/momentum/Timer.h index 3c8569db9a..365dbdc9e6 100644 --- a/mp/src/game/server/momentum/Timer.h +++ b/mp/src/game/server/momentum/Timer.h @@ -49,7 +49,7 @@ class CTimer // Gets the current checkpoint CTriggerCheckpoint *GetCurrentCheckpoint() { return m_pCurrentCheckpoint.Get(); } - CTriggerTimerStop *GetEndTrtigger() { return m_pEndTrigger.Get(); } + CTriggerTimerStop *GetEndTrigger() { return m_pEndTrigger.Get(); } CTriggerStage *GetCurrentStage() { return m_pCurrentStage.Get(); } // Sets the given trigger as the start trigger @@ -73,9 +73,9 @@ class CTimer // Calculates the stage count // Stores the result on m_iStageCount - void RequestStageCount(); + void RequestZoneCount(); // Gets the total stage count - int GetStageCount() { return m_iStageCount; }; + int GetZoneCount() { return m_iZoneCount; }; float CalculateStageTime(int stageNum); float GetLastRunTime() { @@ -163,17 +163,18 @@ class CTimer private: - int m_iStageCount; + int m_iZoneCount; int m_iStartTick, m_iEndTick; int m_iLastStage = 0; - float m_iStageEnterTime[MAX_STAGES]; + float m_iZoneEnterTime[MAX_STAGES]; bool m_bIsRunning; bool m_bWereCheatsActivated; + bool m_bMapIsLinear; CHandle m_pStartTrigger; CHandle m_pEndTrigger; CHandle m_pCurrentCheckpoint; - CHandle m_pCurrentStage; + CHandle m_pCurrentStage;//MOM_TODO: Change to m_pCurrentZone struct Time { diff --git a/mp/src/game/server/momentum/mom_player.cpp b/mp/src/game/server/momentum/mom_player.cpp index 8899b32471..b89e5be980 100644 --- a/mp/src/game/server/momentum/mom_player.cpp +++ b/mp/src/game/server/momentum/mom_player.cpp @@ -217,7 +217,7 @@ void CMomentumPlayer::CheckForBhop() m_iSuccessiveBhops++; if (g_Timer->IsRunning()) { - int currentZone = g_Timer->GetCurrentZoneNumber(); + int currentZone = m_RunData.m_iCurrentZone;//g_Timer->GetCurrentZoneNumber(); m_PlayerRunStats.m_iZoneJumps[0]++; m_PlayerRunStats.m_iZoneJumps[currentZone]++; } @@ -237,7 +237,7 @@ void CMomentumPlayer::UpdateRunStats() if (g_Timer->IsRunning()) { - int currentZone = g_Timer->GetCurrentZoneNumber(); + int currentZone = m_RunData.m_iCurrentZone;//g_Timer->GetCurrentZoneNumber(); if (!m_bPrevTimerRunning) // timer started on this tick { // Compare against successive bhops to avoid incrimenting when the player was in the air without jumping @@ -316,6 +316,7 @@ void CMomentumPlayer::UpdateRunStats() if (playerMoveEvent) { + playerMoveEvent->SetInt("ent", entindex()); playerMoveEvent->SetInt("num_strafes", m_PlayerRunStats.m_iZoneStrafes[0]); playerMoveEvent->SetInt("num_jumps", m_PlayerRunStats.m_iZoneJumps[0]); bool onGround = GetFlags() & FL_ONGROUND; @@ -333,14 +334,14 @@ void CMomentumPlayer::ResetRunStats() m_nAccelTicks = 0; m_RunData.m_flStrafeSync = 0; m_RunData.m_flStrafeSync2 = 0; - m_PlayerRunStats = RunStats_t(g_Timer->GetStageCount()); + m_PlayerRunStats = RunStats_t(g_Timer->GetZoneCount()); } void CMomentumPlayer::CalculateAverageStats() { if (g_Timer->IsRunning()) { - int currentZone = g_Timer->GetCurrentZoneNumber(); + int currentZone = m_RunData.m_iCurrentZone;//g_Timer->GetCurrentZoneNumber(); m_flZoneTotalSync[currentZone] += m_RunData.m_flStrafeSync; m_flZoneTotalSync2[currentZone] += m_RunData.m_flStrafeSync2; diff --git a/mp/src/game/server/momentum/mom_replay.cpp b/mp/src/game/server/momentum/mom_replay.cpp index 673b39569e..2b9376346b 100644 --- a/mp/src/game/server/momentum/mom_replay.cpp +++ b/mp/src/game/server/momentum/mom_replay.cpp @@ -72,7 +72,7 @@ void CMomentumReplaySystem::UpdateRecordingParams(CUtlBuffer *buf) replay_header_t CMomentumReplaySystem::CreateHeader() { replay_header_t header; - header.numZones = g_Timer->GetStageCount(); + header.numZones = g_Timer->GetZoneCount(); Q_strcpy(header.demofilestamp, REPLAY_HEADER_ID); header.demoProtoVersion = REPLAY_PROTOCOL_VERSION; Q_strcpy(header.mapName, gpGlobals->mapname.ToCStr()); @@ -88,7 +88,7 @@ replay_header_t CMomentumReplaySystem::CreateHeader() } RunStats_t CMomentumReplaySystem::CreateStats() { - RunStats_t runStats = RunStats_t(g_Timer->GetStageCount()); + RunStats_t runStats = RunStats_t(g_Timer->GetZoneCount()); for (int i = 0; i < runStats.m_iTotalZones; i++) { @@ -122,8 +122,7 @@ void CMomentumReplaySystem::WriteRecordingToFile(CUtlBuffer *buf) RunStats_t littleEndianStats = CreateStats(); ByteSwap_replay_stats_t(littleEndianStats); - littleEndianStats.HandleFile(filesystem, m_fhFileHandle, false); - //filesystem->Write(&littleEndianStats, sizeof(littleEndianStats), m_fhFileHandle); + littleEndianStats.Write(m_fhFileHandle); DevLog("replay stats size: %i\n", sizeof(littleEndianStats)); Assert(buf && buf->IsValid()); @@ -155,8 +154,7 @@ replay_header_t* CMomentumReplaySystem::ReadHeader(FileHandle_t file, const char //Create and read into the replayStats m_replayStats = RunStats_t(m_replayHeader.numZones); - m_replayStats.HandleFile(filesystem, file, true); - //filesystem->Read(&m_replayStats, sizeof(replay_stats_t), file); + m_replayStats.Read(file); ByteSwap_replay_stats_t(m_replayStats); @@ -203,6 +201,8 @@ void CMomentumReplaySystem::StartReplay(bool firstperson) m_CurrentReplayGhost = static_cast(CreateEntityByName("mom_replay_ghost")); if (m_CurrentReplayGhost != nullptr) { + m_CurrentReplayGhost->SetRunStats(m_replayStats); + m_CurrentReplayGhost->m_flRunTime = m_replayHeader.runTime; if (firstperson) g_Timer->Stop(false); //stop the timer just in case we started a replay while it was running... m_CurrentReplayGhost->StartRun(firstperson); } diff --git a/mp/src/game/server/momentum/mom_replay_entity.cpp b/mp/src/game/server/momentum/mom_replay_entity.cpp index 4992600970..e92bc6d6a7 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.cpp +++ b/mp/src/game/server/momentum/mom_replay_entity.cpp @@ -31,11 +31,13 @@ END_DATADESC() Color CMomentumReplayGhostEntity::m_newGhostColor = COLOR_GREEN; -CMomentumReplayGhostEntity::CMomentumReplayGhostEntity() +CMomentumReplayGhostEntity::CMomentumReplayGhostEntity(): +m_bIsActive(false), m_nStartTick(0), step(0), m_flLastSyncVelocity(0) { m_nReplayButtons = 0; m_iTotalStrafes = 0; m_bHasJumped = false; + m_RunStats = RunStats_t(g_Timer->GetZoneCount()); } @@ -294,6 +296,12 @@ void CMomentumReplayGhostEntity::SetGhostColor(const CCommand &args) m_newGhostColor = *mom_UTIL->GetColorFromHex(args.ArgS()); } } + +void CMomentumReplayGhostEntity::SetRunStats(RunStats_t& stats) +{ + m_RunStats = stats; +} + void CMomentumReplayGhostEntity::EndRun() { SetNextThink(-1); diff --git a/mp/src/game/server/momentum/mom_replay_entity.h b/mp/src/game/server/momentum/mom_replay_entity.h index 35666b8b6a..17ec043833 100644 --- a/mp/src/game/server/momentum/mom_replay_entity.h +++ b/mp/src/game/server/momentum/mom_replay_entity.h @@ -49,9 +49,13 @@ class CMomentumReplayGhostEntity : public CBaseAnimating void HandleGhost(); void HandleGhostFirstPerson(); void UpdateStats(Vector ghostVel); //for hud display.. + void SetRunStats(RunStats_t &stats); + RunStats_t *GetRunStats() + { return &m_RunStats; } bool m_bIsActive; int m_nStartTick; + float m_flRunTime; CNetworkVarEmbedded(CMOMRunEntityData, m_RunData); CNetworkVar(int, m_nReplayButtons); @@ -67,6 +71,7 @@ class CMomentumReplayGhostEntity : public CBaseAnimating char m_pszModel[256], m_pszMapName[256]; replay_frame_t currentStep; replay_frame_t nextStep; + RunStats_t m_RunStats; //MOM_TODO: CUtlVector spectators; diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index fe024f0bd0..d1ef8a5780 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -27,47 +27,32 @@ void CTriggerStage::StartTouch(CBaseEntity *pOther) { BaseClass::StartTouch(pOther); int stageNum = GetStageNumber(); - if (pOther->IsPlayer()) + + IGameEvent *stageEvent = gameeventmanager->CreateEvent("zone_enter"); + RunStats_t *pStats = nullptr; + float enterTime = 0.0f; + CMomentumPlayer *pPlayer = ToCMOMPlayer(pOther); + if (pPlayer) { - CMomentumPlayer *pPlayer = ToCMOMPlayer(pOther); - IGameEvent *stageEvent = gameeventmanager->CreateEvent("stage_enter"); - if (stageEvent && pPlayer) + //Set the current stage to this + g_Timer->SetCurrentStage(this); + //Set player run data + pPlayer->m_RunData.m_bIsInZone = true; + pPlayer->m_RunData.m_iCurrentZone = stageNum; + if (g_Timer->IsRunning()) { - //Set the current stage to this - g_Timer->SetCurrentStage(this); - //Set player run data - pPlayer->m_RunData.m_bIsInZone = true; - pPlayer->m_RunData.m_iCurrentZone = stageNum; - if (g_Timer->IsRunning()) - { - stageEvent->SetInt("stage_num", stageNum); - stageEvent->SetFloat("stage_enter_time", g_Timer->CalculateStageTime(stageNum)); - stageEvent->SetInt("num_jumps", pPlayer->m_PlayerRunStats.m_iZoneJumps[stageNum - 1]); - stageEvent->SetFloat("num_strafes", pPlayer->m_PlayerRunStats.m_iZoneStrafes[stageNum - 1]); - stageEvent->SetFloat("avg_sync", pPlayer->m_PlayerRunStats.m_flZoneStrafeSyncAvg[stageNum - 1]); - stageEvent->SetFloat("avg_sync2", pPlayer->m_PlayerRunStats.m_flZoneStrafeSync2Avg[stageNum - 1]); - - //3D VELOCITY - stageEvent->SetFloat("max_vel", pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[stageNum - 1][0]); - stageEvent->SetFloat("avg_vel", pPlayer->m_PlayerRunStats.m_flZoneVelocityAvg[stageNum - 1][0]); - pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum - 1][0] = pPlayer->GetLocalVelocity().Length(); - stageEvent->SetFloat("stage_exit_vel", pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum - 1][0]); - - //2D VELOCITY - stageEvent->SetFloat("max_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[stageNum - 1][1]); - stageEvent->SetFloat("avg_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneVelocityAvg[stageNum - 1][1]); - pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum - 1][1] = pPlayer->GetLocalVelocity().Length2D(); - stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum - 1][1]); - - gameeventmanager->FireEvent(stageEvent); - - g_Timer->CalculateTickIntervalOffset(pPlayer, g_Timer->ZONETYPE_END); - } - else - { - stageEvent->SetInt("stage_num", stageNum);//It's 1, and this resets the stats - gameeventmanager->FireEvent(stageEvent); - } + pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum - 1][0] = pPlayer->GetLocalVelocity().Length(); + pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum - 1][1] = pPlayer->GetLocalVelocity().Length2D(); + g_Timer->CalculateTickIntervalOffset(pPlayer, g_Timer->ZONETYPE_END); + enterTime = g_Timer->CalculateStageTime(stageNum); + pStats = &pPlayer->m_PlayerRunStats; + } + else + { + stageEvent->SetInt("num", stageNum);//It's 1, and this resets the stats + stageEvent->SetInt("ent", pPlayer->entindex()); + gameeventmanager->FireEvent(stageEvent); + stageEvent = nullptr; } } else @@ -77,14 +62,41 @@ void CTriggerStage::StartTouch(CBaseEntity *pOther) { pGhost->m_RunData.m_iCurrentZone = stageNum; pGhost->m_RunData.m_bIsInZone = true; + enterTime = pGhost->GetRunStats()->m_flZoneEnterTime[stageNum]; + pStats = pGhost->GetRunStats(); } } + + if (stageEvent && pStats) + { + stageEvent->SetInt("num", stageNum); + stageEvent->SetInt("ent", pOther->entindex()); + stageEvent->SetFloat("enter_time", enterTime); + stageEvent->SetInt("num_jumps", pStats->m_iZoneJumps[stageNum - 1]); + stageEvent->SetFloat("num_strafes", pStats->m_iZoneStrafes[stageNum - 1]); + stageEvent->SetFloat("avg_sync", pStats->m_flZoneStrafeSyncAvg[stageNum - 1]); + stageEvent->SetFloat("avg_sync2", pStats->m_flZoneStrafeSync2Avg[stageNum - 1]); + + //3D VELOCITY + stageEvent->SetFloat("max_vel", pStats->m_flZoneVelocityMax[stageNum - 1][0]); + stageEvent->SetFloat("avg_vel", pStats->m_flZoneVelocityAvg[stageNum - 1][0]); + stageEvent->SetFloat("exit_vel", pStats->m_flZoneExitSpeed[stageNum - 1][0]); + + //2D VELOCITY + stageEvent->SetFloat("max_vel_2D", pStats->m_flZoneVelocityMax[stageNum - 1][1]); + stageEvent->SetFloat("avg_vel_2D", pStats->m_flZoneVelocityAvg[stageNum - 1][1]); + stageEvent->SetFloat("exit_vel_2D", pStats->m_flZoneExitSpeed[stageNum - 1][1]); + + gameeventmanager->FireEvent(stageEvent); + } } void CTriggerStage::EndTouch(CBaseEntity *pOther) { BaseClass::EndTouch(pOther); int stageNum = this->GetStageNumber(); CMomentumPlayer *pPlayer = ToCMOMPlayer(pOther); + IGameEvent *stageEvent = gameeventmanager->CreateEvent("zone_exit"); + RunStats_t *pStats = nullptr; if (pPlayer) { if (stageNum == 1 || g_Timer->IsRunning())//Timer won't be running if it's the start trigger @@ -92,25 +104,11 @@ void CTriggerStage::EndTouch(CBaseEntity *pOther) //This handles both the start and stage triggers g_Timer->CalculateTickIntervalOffset(pPlayer, g_Timer->ZONETYPE_START); - IGameEvent *stageEvent = gameeventmanager->CreateEvent("stage_exit"); - if (stageEvent) - { - //Status - pPlayer->m_RunData.m_bIsInZone = false; - - //Stage num - stageEvent->SetInt("stage_num", stageNum); - - //3D VELOCITY - pPlayer->m_PlayerRunStats.m_flZoneEnterSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); - stageEvent->SetFloat("stage_enter_vel", pPlayer->m_PlayerRunStats.m_flZoneEnterSpeed[stageNum][0]); - - //2D VELOCITY - pPlayer->m_PlayerRunStats.m_flZoneEnterSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); - stageEvent->SetFloat("stage_enter_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneEnterSpeed[stageNum][1]); - - gameeventmanager->FireEvent(stageEvent); - } + //Status + pPlayer->m_RunData.m_bIsInZone = false; + pPlayer->m_PlayerRunStats.m_flZoneEnterSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); + pPlayer->m_PlayerRunStats.m_flZoneEnterSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); + pStats = &pPlayer->m_PlayerRunStats; } } else @@ -119,8 +117,25 @@ void CTriggerStage::EndTouch(CBaseEntity *pOther) if (pGhost) { pGhost->m_RunData.m_bIsInZone = false; + pStats = pGhost->GetRunStats(); } } + if (stageEvent && pStats) + { + //Entity index + stageEvent->SetInt("ent", pOther->entindex()); + + //Stage num + stageEvent->SetInt("num", stageNum); + + //3D VELOCITY + stageEvent->SetFloat("enter_vel", pStats->m_flZoneEnterSpeed[stageNum][0]); + + //2D VELOCITY + stageEvent->SetFloat("enter_vel_2D", pStats->m_flZoneEnterSpeed[stageNum][1]); + + gameeventmanager->FireEvent(stageEvent); + } } //------------------------------------------------------------------------------------------ @@ -158,6 +173,7 @@ void CTriggerTimerStart::EndTouch(CBaseEntity *pOther) } } g_Timer->Start(gpGlobals->tickcount); + pPlayer->m_RunData.m_bTimerRunning = g_Timer->IsRunning(); } pPlayer->m_RunData.m_bIsInZone = false; pPlayer->m_RunData.m_bMapFinished = false; @@ -169,8 +185,9 @@ void CTriggerTimerStart::EndTouch(CBaseEntity *pOther) { pGhost->m_RunData.m_bIsInZone = false; pGhost->m_RunData.m_bMapFinished = false; + pGhost->m_RunData.m_bTimerRunning = true; //MOM_TODO: Make the spectator's timer start - //pGhost->SetStartTick(gpGlobals->tickcount) + //pGhost->StartTimer(gpGlobals->tickcount); } } @@ -193,6 +210,7 @@ void CTriggerTimerStart::StartTouch(CBaseEntity *pOther) pPlayer->ResetRunStats();//Reset run stats pPlayer->m_RunData.m_bIsInZone = true; pPlayer->m_RunData.m_bMapFinished = false; + pPlayer->m_RunData.m_bTimerRunning = false; pPlayer->m_RunData.m_flLastJumpVel = 0; //also reset last jump velocity when we enter the start zone if (g_Timer->IsRunning()) @@ -277,33 +295,31 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) { CMomentumPlayer *pPlayer = ToCMOMPlayer(pOther); - IGameEvent *stageEvent = gameeventmanager->CreateEvent("stage_enter"); - + IGameEvent *stageEvent = gameeventmanager->CreateEvent("zone_enter"); + IGameEvent *timerStopEvent = nullptr; + RunStats_t *pStats = nullptr; + int zoneNum = -1; + float lastRun = 0.0f; // If timer is already stopped, there's nothing to stop (No run state effect to play) if (pPlayer) { g_Timer->SetEndTrigger(this); if (g_Timer->IsRunning() && !pPlayer->IsWatchingReplay()) { - if (stageEvent) - { - //The last stage is a bit of a doozy. - //We need to store it in totalstages + 1 so that comparisons can - //call forward for determining time spent on the last stage. - //We set the stage_num one higher so the last stage can still compare against it - int stageNum = g_Timer->GetCurrentZoneNumber(); - stageEvent->SetInt("stage_num", stageNum + 1); - //And then put the time we finished at as the enter time for the end trigger - stageEvent->SetFloat("stage_enter_time", g_Timer->GetLastRunTime()); - - //This is needed so we have an ending velocity. - pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum][0] = pPlayer->GetLocalVelocity().Length(); - stageEvent->SetFloat("stage_exit_vel", pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum][0]); - pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum][1] = pPlayer->GetLocalVelocity().Length2D(); - stageEvent->SetFloat("stage_exit_vel_2D", pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum][1]); - gameeventmanager->FireEvent(stageEvent); - } - + //Only create this event if the player was running + timerStopEvent = gameeventmanager->CreateEvent("timer_stopped"); + // The last stage is a bit of a doozy (for the player) + // We need to store it in totalstages + 1 so that comparisons can + // call forward for determining time spent on the last stage. + // We set the num one higher so the last stage can still compare against it + zoneNum = pPlayer->m_RunData.m_iCurrentZone; + // And then put the time we finished at as the enter time for the end trigger + // stageEvent->SetFloat("enter_time", g_Timer->GetLastRunTime()); + + // This is needed so we have an ending velocity. + pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[zoneNum][0] = pPlayer->GetLocalVelocity().Length(); + pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[zoneNum][1] = pPlayer->GetLocalVelocity().Length2D(); + //Check to see if we should calculate the timer offset fix Vector origin = pPlayer->GetAbsOrigin(), velocity = pPlayer->GetAbsVelocity(); Vector prevOrigin = Vector(origin.x - (velocity.x * gpGlobals->interval_per_tick), @@ -319,10 +335,38 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) { DevLog("Previous origin is NOT inside the trigger, calculating offset...\n"); g_Timer->CalculateTickIntervalOffset(pPlayer, g_Timer->ZONETYPE_END); - } + } + + //Ending velocity checks + //3D Velocity + float endvel = pPlayer->GetLocalVelocity().Length(); + + //Update the ending velocity if it's the max velocity obtained + if (endvel > pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[0][0]) + pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[0][0] = endvel; + + //we have to set end speed here or else it will be saved as 0 + pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[0][0] = endvel; + + //2D Velocity + float endvel2D = pPlayer->GetLocalVelocity().Length2D(); + + //Update the ending velocity if it's the max velocity obtained + if (endvel2D > pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[0][1]) + pPlayer->m_PlayerRunStats.m_flZoneVelocityMax[0][1] = endvel2D; + pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[0][1] = endvel2D; + + //Set the pointer to send new stats + pStats = &pPlayer->m_PlayerRunStats; + + //Stop the timer g_Timer->Stop(true); + + lastRun = g_Timer->GetLastRunTime(); + //The map is now finished, show the mapfinished panel pPlayer->m_RunData.m_bMapFinished = true; + pPlayer->m_RunData.m_bTimerRunning = false; //MOM_TODO: SLOW DOWN/STOP THE PLAYER HERE! } @@ -334,21 +378,64 @@ void CTriggerTimerStop::StartTouch(CBaseEntity *pOther) CMomentumReplayGhostEntity *pGhost = dynamic_cast(pOther); if (pGhost) { + //Ghosts always are runs so create the event + timerStopEvent = gameeventmanager->CreateEvent("timer_stopped"); pGhost->m_RunData.m_bMapFinished = true; + pGhost->m_RunData.m_bTimerRunning = false; pGhost->m_RunData.m_bIsInZone = true; + zoneNum = pGhost->m_RunData.m_iCurrentZone; + pStats = pGhost->GetRunStats(); + lastRun = pGhost->m_flRunTime; //MOM_TODO: pGhost->EndRunHud(); //sends a hud timer state message to each spectator //MOM_TODO: Maybe also play effects if the player is racing against us and lost? } } + + if (stageEvent && pStats && zoneNum > -1) + { + stageEvent->SetInt("num", zoneNum + 1); + stageEvent->SetInt("ent", pOther->entindex()); + stageEvent->SetFloat("exit_vel", pStats->m_flZoneExitSpeed[zoneNum][0]); + stageEvent->SetFloat("exit_vel_2D", pStats->m_flZoneExitSpeed[zoneNum][1]); + gameeventmanager->FireEvent(stageEvent); + } + + if (timerStopEvent && pStats) + { + timerStopEvent->SetInt("ent", pOther->entindex()); + timerStopEvent->SetFloat("avg_sync", pStats->m_flZoneStrafeSyncAvg[0]); + timerStopEvent->SetFloat("avg_sync2", pStats->m_flZoneStrafeSync2Avg[0]); + timerStopEvent->SetInt("num_strafes", pStats->m_iZoneStrafes[0]); + timerStopEvent->SetInt("num_jumps", pStats->m_iZoneJumps[0]); + + //3D VELCOCITY STATS - INDEX 0 + timerStopEvent->SetFloat("avg_vel", pStats->m_flZoneVelocityAvg[0][0]); + timerStopEvent->SetFloat("start_vel", pStats->m_flZoneEnterSpeed[1][0]); + timerStopEvent->SetFloat("max_vel", pStats->m_flZoneVelocityMax[0][0]); + timerStopEvent->SetFloat("end_vel", pStats->m_flZoneExitSpeed[0][0]); + + //2D VELOCITY STATS - INDEX 1 + timerStopEvent->SetFloat("avg_vel_2D", pStats->m_flZoneVelocityAvg[0][1]); + timerStopEvent->SetFloat("start_vel_2D", pStats->m_flZoneEnterSpeed[1][1]); + timerStopEvent->SetFloat("max_vel_2D", pStats->m_flZoneVelocityMax[0][1]); + timerStopEvent->SetFloat("end_vel_2D", pStats->m_flZoneExitSpeed[0][1]); + + timerStopEvent->SetFloat("time", lastRun); + gameeventmanager->FireEvent(timerStopEvent); + } + BaseClass::StartTouch(pOther); } void CTriggerTimerStop::EndTouch(CBaseEntity* pOther) { CMomentumPlayer *pMomPlayer = ToCMOMPlayer(pOther); + IGameEvent *zoneExitEvent = gameeventmanager->CreateEvent("zone_exit"); + int lastZoneNumber = -1; if (pMomPlayer) { pMomPlayer->m_RunData.m_bMapFinished = false;//Close the hud_mapfinished panel pMomPlayer->m_RunData.m_bIsInZone = false;//Update status + lastZoneNumber = pMomPlayer->m_RunData.m_iCurrentZone; } else { @@ -357,8 +444,17 @@ void CTriggerTimerStop::EndTouch(CBaseEntity* pOther) { pGhost->m_RunData.m_bMapFinished = false; pGhost->m_RunData.m_bIsInZone = false; + lastZoneNumber = pGhost->m_RunData.m_iCurrentZone; } } + if (zoneExitEvent && lastZoneNumber > -1) + { + //This tells the event listener to remove/clear the stats for the given ent + zoneExitEvent->SetInt("num", lastZoneNumber + 1); + zoneExitEvent->SetInt("ent", pOther->entindex()); + + gameeventmanager->FireEvent(zoneExitEvent); + } BaseClass::EndTouch(pOther); } //---------------------------------------------------------------------------------------------- diff --git a/mp/src/game/shared/momentum/mom_entity_run_data.cpp b/mp/src/game/shared/momentum/mom_entity_run_data.cpp index 9fff819760..0cf22f26c2 100644 --- a/mp/src/game/shared/momentum/mom_entity_run_data.cpp +++ b/mp/src/game/shared/momentum/mom_entity_run_data.cpp @@ -16,6 +16,7 @@ SendPropInt(SENDINFO(m_iRunFlags)), SendPropBool(SENDINFO(m_bIsInZone)), SendPropInt(SENDINFO(m_iCurrentZone)), SendPropBool(SENDINFO(m_bMapFinished)), +SendPropBool(SENDINFO(m_bTimerRunning)), END_SEND_TABLE() #elif defined CLIENT_DLL @@ -30,6 +31,7 @@ RecvPropInt(RECVINFO(m_iRunFlags)), RecvPropBool(RECVINFO(m_bIsInZone)), RecvPropInt(RECVINFO(m_iCurrentZone)), RecvPropBool(RECVINFO(m_bMapFinished)), +RecvPropBool(RECVINFO(m_bTimerRunning)), END_RECV_TABLE() #endif @@ -45,4 +47,5 @@ CMOMRunEntityData::CMOMRunEntityData() m_bIsInZone = false; m_iCurrentZone = 0; m_bMapFinished = false; + m_bTimerRunning = false; } \ No newline at end of file diff --git a/mp/src/game/shared/momentum/mom_entity_run_data.h b/mp/src/game/shared/momentum/mom_entity_run_data.h index 83e2dcfe1d..2a0800739a 100644 --- a/mp/src/game/shared/momentum/mom_entity_run_data.h +++ b/mp/src/game/shared/momentum/mom_entity_run_data.h @@ -31,12 +31,11 @@ class CMOMRunEntityData CNetworkVar(bool, m_bIsInZone);//This is true if the player is in a CTriggerTimerStage zone CNetworkVar(bool, m_bMapFinished);//Did the player finish the map? CNetworkVar(int, m_iCurrentZone);//Current stage/checkpoint the player is on - - //MOM_TODO: CNetworkEmbedded(CMOMRunStats, m_RunStats); + CNetworkVar(bool, m_bTimerRunning);//Is the timer currently running for this ent? #elif defined CLIENT_DLL - bool m_bAutoBhop, m_bIsInZone, m_bMapFinished; + bool m_bAutoBhop, m_bIsInZone, m_bMapFinished, m_bTimerRunning; float m_flStrafeSync, m_flStrafeSync2, m_flLastJumpVel, m_flLastJumpTime; int m_iSuccessiveBhops, m_iRunFlags, m_iCurrentZone; diff --git a/mp/src/game/shared/momentum/util/mom_util.cpp b/mp/src/game/shared/momentum/util/mom_util.cpp index e15a717f83..820f279ae6 100644 --- a/mp/src/game/shared/momentum/util/mom_util.cpp +++ b/mp/src/game/shared/momentum/util/mom_util.cpp @@ -313,9 +313,9 @@ bool MomentumUtil::GetRunComparison(const char *szMapName, float tickRate, int f into->stageAvgVels[i].AddToTail(kv->GetFloat(horizontalVel ? "avg_vel_2D" : "avg_vel")); into->stageMaxVels[i].AddToTail(kv->GetFloat(horizontalVel ? "max_vel_2D" : "max_vel")); into->stageEnterVels[i].AddToTail( - kv->GetFloat(horizontalVel ? "stage_enter_vel_2D" : "stage_enter_vel")); + kv->GetFloat(horizontalVel ? "enter_vel_2D" : "enter_vel")); into->stageExitVels[i].AddToTail( - kv->GetFloat(horizontalVel ? "stage_exit_vel_2D" : "stage_exit_vel")); + kv->GetFloat(horizontalVel ? "exit_vel_2D" : "exit_vel")); } } } diff --git a/mp/src/game/shared/momentum/util/run_stats.h b/mp/src/game/shared/momentum/util/run_stats.h index 72774443bb..b85cad63a3 100644 --- a/mp/src/game/shared/momentum/util/run_stats.h +++ b/mp/src/game/shared/momentum/util/run_stats.h @@ -30,41 +30,52 @@ struct RunStats_t } } - //Needed for HandleFile - static void Read(void const* pVoid, int size, FileHandle_t file) + //Note: This needs updating every time the struct is updated! + void Read(FileHandle_t file) { - filesystem->Read(&pVoid, size, file); - } + filesystem->Read(&m_iTotalZones, sizeof(m_iTotalZones), file); + for (int i = 0; i < m_iTotalZones; i++) + { + filesystem->Read(&m_iZoneJumps[i], sizeof(m_iZoneJumps[i]), file); + filesystem->Read(&m_iZoneStrafes[i], sizeof(m_iZoneStrafes[i]), file); + filesystem->Read(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); - //Needed for HandleFile - static void Write(void const* pVoid, int size, FileHandle_t file) - { - filesystem->Write(&pVoid, size, file); + filesystem->Read(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); + filesystem->Read(&m_flZoneStrafeSync2Avg[i], sizeof(m_flZoneStrafeSync2Avg[i]), file); + filesystem->Read(&m_flZoneEnterTime[i], sizeof(m_flZoneEnterTime[i]), file); + filesystem->Read(&m_flZoneTime[i], sizeof(m_flZoneTime[i]), file); + + for (int k = 0; k < 2; k++) + { + filesystem->Read(&m_flZoneVelocityMax[i][k], sizeof(m_flZoneVelocityMax[i][k]), file); + filesystem->Read(&m_flZoneVelocityAvg[i][k], sizeof(m_flZoneVelocityAvg[i][k]), file); + filesystem->Read(&m_flZoneEnterSpeed[i][k], sizeof(m_flZoneEnterSpeed[i][k]), file); + filesystem->Read(&m_flZoneExitSpeed[i][k], sizeof(m_flZoneExitSpeed[i][k]), file); + } + } } - - //Note: This needs updating every time the struct is updated!! - void HandleFile(IFileSystem *fs, FileHandle_t file, bool read) const + + //Note: This needs updating every time the struct is updated! + void Write(FileHandle_t file) const { - void (*handle)(void const*, int, FileHandle_t) = read ? &Read : &Write; - - handle(&m_iTotalZones, sizeof(m_iTotalZones), file); + filesystem->Write(&m_iTotalZones, sizeof(m_iTotalZones), file); for (int i = 0; i < m_iTotalZones; i++) { - handle(&m_iZoneJumps[i], sizeof(m_iZoneJumps[i]), file); - handle(&m_iZoneStrafes[i], sizeof(m_iZoneStrafes[i]), file); - handle(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); + filesystem->Write(&m_iZoneJumps[i], sizeof(m_iZoneJumps[i]), file); + filesystem->Write(&m_iZoneStrafes[i], sizeof(m_iZoneStrafes[i]), file); + filesystem->Write(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); - handle(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); - handle(&m_flZoneStrafeSync2Avg[i], sizeof(m_flZoneStrafeSync2Avg[i]), file); - handle(&m_flZoneEnterTime[i], sizeof(m_flZoneEnterTime[i]), file); - handle(&m_flZoneTime[i], sizeof(m_flZoneTime[i]), file); + filesystem->Write(&m_flZoneStrafeSyncAvg[i], sizeof(m_flZoneStrafeSyncAvg[i]), file); + filesystem->Write(&m_flZoneStrafeSync2Avg[i], sizeof(m_flZoneStrafeSync2Avg[i]), file); + filesystem->Write(&m_flZoneEnterTime[i], sizeof(m_flZoneEnterTime[i]), file); + filesystem->Write(&m_flZoneTime[i], sizeof(m_flZoneTime[i]), file); for (int k = 0; k < 2; k++) { - handle(&m_flZoneVelocityMax[i][k], sizeof(m_flZoneVelocityMax[i][k]), file); - handle(&m_flZoneVelocityAvg[i][k], sizeof(m_flZoneVelocityAvg[i][k]), file); - handle(&m_flZoneEnterSpeed[i][k], sizeof(m_flZoneEnterSpeed[i][k]), file); - handle(&m_flZoneExitSpeed[i][k], sizeof(m_flZoneExitSpeed[i][k]), file); + filesystem->Write(&m_flZoneVelocityMax[i][k], sizeof(m_flZoneVelocityMax[i][k]), file); + filesystem->Write(&m_flZoneVelocityAvg[i][k], sizeof(m_flZoneVelocityAvg[i][k]), file); + filesystem->Write(&m_flZoneEnterSpeed[i][k], sizeof(m_flZoneEnterSpeed[i][k]), file); + filesystem->Write(&m_flZoneExitSpeed[i][k], sizeof(m_flZoneExitSpeed[i][k]), file); } } } From 5a3c8c6f4628df61167834feabe2c39d472ac0ef Mon Sep 17 00:00:00 2001 From: Nick K Date: Sat, 4 Jun 2016 18:34:14 -0400 Subject: [PATCH 055/101] Push changes from laptop To continue work on main PC --- mp/src/game/client/momentum/mom_event_listener.cpp | 3 ++- mp/src/game/client/momentum/mom_event_listener.h | 11 ++++++++--- mp/src/game/server/momentum/Timer.cpp | 8 ++++---- mp/src/game/server/momentum/Timer.h | 2 +- mp/src/game/server/momentum/mom_triggers.cpp | 7 +++++-- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/mp/src/game/client/momentum/mom_event_listener.cpp b/mp/src/game/client/momentum/mom_event_listener.cpp index 2e67508834..00f73855b2 100644 --- a/mp/src/game/client/momentum/mom_event_listener.cpp +++ b/mp/src/game/client/momentum/mom_event_listener.cpp @@ -88,7 +88,7 @@ void C_Momentum_EventListener::FireGameEvent(IGameEvent *pEvent) //This happens when the player/ghost exits the ending trigger (despawn or restart) if (currentZone > m_iMapZoneCount) { - m_EntRunStats.Remove(entIndex); + m_EntRunStats.Remove(entIndex); } else { @@ -129,6 +129,7 @@ void C_Momentum_EventListener::FireGameEvent(IGameEvent *pEvent) } else if (!Q_strcmp("map_init", pEvent->GetName())) { + m_EntRunStats.PurgeAndDeleteElements(); m_bMapIsLinear = pEvent->GetBool("is_linear"); m_iMapZoneCount = pEvent->GetInt("num_zones"); } diff --git a/mp/src/game/client/momentum/mom_event_listener.h b/mp/src/game/client/momentum/mom_event_listener.h index d20ff270b8..e26c786c7c 100644 --- a/mp/src/game/client/momentum/mom_event_listener.h +++ b/mp/src/game/client/momentum/mom_event_listener.h @@ -12,6 +12,11 @@ class C_Momentum_EventListener : public CGameEventListener m_flLastRunTime(0) { } + ~C_Momentum_EventListener() + { + m_EntRunStats.PurgeAndDeleteElements(); + } + void Init(); void FireGameEvent(IGameEvent* pEvent) override; @@ -21,9 +26,9 @@ class C_Momentum_EventListener : public CGameEventListener unsigned short index; if ((index = m_EntRunStats.Find(entIndex)) != m_EntRunStats.InvalidIndex()) { - return &m_EntRunStats.Element(index); + return m_EntRunStats.Element(index); } - RunStats_t temp(m_iMapZoneCount); + RunStats_t *temp = new RunStats_t(m_iMapZoneCount); m_EntRunStats.Insert(entIndex, temp); return GetRunStats(entIndex); } @@ -33,7 +38,7 @@ class C_Momentum_EventListener : public CGameEventListener int m_iMapZoneCount; - CUtlMap m_EntRunStats; + CUtlMap m_EntRunStats; float m_flLastRunTime; //this is the "adjusted" precision-fixed time value that was calculated on the server DLL char m_szRunUploadStatus[512];//MOM_TODO: determine best (max) size for this diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index 9fdb5c72ee..3ecfd8fd65 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -316,7 +316,7 @@ void CTimer::OnMapStart(const char *pMapName) m_bWereCheatsActivated = false; RequestZoneCount(); LoadLocalTimes(pMapName); - //MOM_TODO: g_Timer->LoadOnlineTimes(); + //MOM_TODO: LoadOnlineTimes(); } //MOM_TODO: This needs to update to include checkpoint triggers placed in linear @@ -339,12 +339,12 @@ float CTimer::CalculateStageTime(int stage) { float originalTime = static_cast(gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; //If the stage is a new one, we store the time we entered this stage in - m_iZoneEnterTime[stage] = stage == 1 ? 0.0f : //Always returns 0 for first stage. + m_flZoneEnterTime[stage] = stage == 1 ? 0.0f : //Always returns 0 for first stage. originalTime + m_flTickOffsetFix[stage-1]; - DevLog("Original Time: %f\n New Time: %f", originalTime, m_iZoneEnterTime[stage]); + DevLog("Original Time: %f\n New Time: %f", originalTime, m_flZoneEnterTime[stage]); } m_iLastStage = stage; - return m_iZoneEnterTime[stage]; + return m_flZoneEnterTime[stage]; } void CTimer::DispatchResetMessage() { diff --git a/mp/src/game/server/momentum/Timer.h b/mp/src/game/server/momentum/Timer.h index 365dbdc9e6..7f528b137a 100644 --- a/mp/src/game/server/momentum/Timer.h +++ b/mp/src/game/server/momentum/Timer.h @@ -166,7 +166,7 @@ class CTimer int m_iZoneCount; int m_iStartTick, m_iEndTick; int m_iLastStage = 0; - float m_iZoneEnterTime[MAX_STAGES]; + float m_flZoneEnterTime[MAX_STAGES]; bool m_bIsRunning; bool m_bWereCheatsActivated; bool m_bMapIsLinear; diff --git a/mp/src/game/server/momentum/mom_triggers.cpp b/mp/src/game/server/momentum/mom_triggers.cpp index d1ef8a5780..de3edbe40c 100644 --- a/mp/src/game/server/momentum/mom_triggers.cpp +++ b/mp/src/game/server/momentum/mom_triggers.cpp @@ -28,7 +28,7 @@ void CTriggerStage::StartTouch(CBaseEntity *pOther) BaseClass::StartTouch(pOther); int stageNum = GetStageNumber(); - IGameEvent *stageEvent = gameeventmanager->CreateEvent("zone_enter"); + IGameEvent *stageEvent = nullptr; RunStats_t *pStats = nullptr; float enterTime = 0.0f; CMomentumPlayer *pPlayer = ToCMOMPlayer(pOther); @@ -39,12 +39,14 @@ void CTriggerStage::StartTouch(CBaseEntity *pOther) //Set player run data pPlayer->m_RunData.m_bIsInZone = true; pPlayer->m_RunData.m_iCurrentZone = stageNum; + stageEvent = gameeventmanager->CreateEvent("zone_enter"); if (g_Timer->IsRunning()) - { + { pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum - 1][0] = pPlayer->GetLocalVelocity().Length(); pPlayer->m_PlayerRunStats.m_flZoneExitSpeed[stageNum - 1][1] = pPlayer->GetLocalVelocity().Length2D(); g_Timer->CalculateTickIntervalOffset(pPlayer, g_Timer->ZONETYPE_END); enterTime = g_Timer->CalculateStageTime(stageNum); + pPlayer->m_PlayerRunStats.m_flZoneEnterTime[stageNum] = enterTime; pStats = &pPlayer->m_PlayerRunStats; } else @@ -60,6 +62,7 @@ void CTriggerStage::StartTouch(CBaseEntity *pOther) CMomentumReplayGhostEntity *pGhost = dynamic_cast(pOther); if (pGhost) { + stageEvent = gameeventmanager->CreateEvent("zone_enter"); pGhost->m_RunData.m_iCurrentZone = stageNum; pGhost->m_RunData.m_bIsInZone = true; enterTime = pGhost->GetRunStats()->m_flZoneEnterTime[stageNum]; From ed658055efbfcf04c85311dcb0af5a4f9b5c111f Mon Sep 17 00:00:00 2001 From: Nick K Date: Sun, 5 Jun 2016 02:06:47 -0400 Subject: [PATCH 056/101] Fix 0s in time file Added player slowdown after run completion Bug: Game crashes if you load a map that has an existing tim file --- .../client/momentum/c_mom_replay_entity.cpp | 3 + .../client/momentum/c_mom_replay_entity.h | 2 + .../client/momentum/ui/hud_mapfinished.cpp | 6 +- mp/src/game/server/momentum/Timer.cpp | 31 ++-- mp/src/game/server/momentum/Timer.h | 80 ++++++--- mp/src/game/server/momentum/mom_player.cpp | 16 ++ mp/src/game/server/momentum/mom_player.h | 14 +- mp/src/game/server/momentum/mom_replay.cpp | 6 +- .../server/momentum/mom_replay_entity.cpp | 166 +++++++++--------- .../game/server/momentum/mom_replay_entity.h | 42 ++--- mp/src/game/server/momentum/mom_triggers.cpp | 20 ++- mp/src/game/server/momentum/mom_triggers.h | 83 +++++---- mp/src/game/server/momentum/replayformat.h | 3 +- mp/src/game/shared/momentum/util/mom_util.cpp | 15 ++ mp/src/game/shared/momentum/util/run_stats.h | 66 +++---- 15 files changed, 324 insertions(+), 229 deletions(-) diff --git a/mp/src/game/client/momentum/c_mom_replay_entity.cpp b/mp/src/game/client/momentum/c_mom_replay_entity.cpp index 2d0e07ac6a..99f5efe7d2 100644 --- a/mp/src/game/client/momentum/c_mom_replay_entity.cpp +++ b/mp/src/game/client/momentum/c_mom_replay_entity.cpp @@ -8,6 +8,7 @@ IMPLEMENT_CLIENTCLASS_DT(C_MomentumReplayGhostEntity, DT_MOM_ReplayEnt, CMomentu RecvPropInt(RECVINFO(m_nReplayButtons)), RecvPropInt(RECVINFO(m_iTotalStrafes)), RecvPropInt(RECVINFO(m_iTotalJumps)), +RecvPropFloat(RECVINFO(m_flRunTime)), RecvPropDataTable(RECVINFO_DT(m_RunData), 0, &REFERENCE_RECV_TABLE(DT_MOM_RunEntData)) END_RECV_TABLE(); @@ -15,4 +16,6 @@ C_MomentumReplayGhostEntity::C_MomentumReplayGhostEntity() { m_nReplayButtons = 0; m_iTotalStrafes = 0; + m_iTotalJumps = 0; + m_flRunTime = 0.0f; } \ No newline at end of file diff --git a/mp/src/game/client/momentum/c_mom_replay_entity.h b/mp/src/game/client/momentum/c_mom_replay_entity.h index 852540d3df..dabcdfd2c8 100644 --- a/mp/src/game/client/momentum/c_mom_replay_entity.h +++ b/mp/src/game/client/momentum/c_mom_replay_entity.h @@ -13,6 +13,8 @@ class C_MomentumReplayGhostEntity : public C_BaseAnimating CMOMRunEntityData m_RunData; + float m_flRunTime; + int m_nReplayButtons; //These are stored here because run stats already has the ones obtained from the run int m_iTotalStrafes; diff --git a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp index 54bd2b10f1..71c7e9821a 100644 --- a/mp/src/game/client/momentum/ui/hud_mapfinished.cpp +++ b/mp/src/game/client/momentum/ui/hud_mapfinished.cpp @@ -325,6 +325,8 @@ void CHudMapFinishedDialog::Paint() // ---------------- // ------------------------------ } + +//MOM_TODO: Do we want this to be a think method or update it to a usermsg/event, so it only calls once, and not every frame? void CHudMapFinishedDialog::OnThink() { C_MomentumPlayer * pPlayer = ToCMOMPlayer(C_BasePlayer::GetLocalPlayer()); @@ -339,6 +341,7 @@ void CHudMapFinishedDialog::OnThink() RunStats_t *stats = g_MOMEventListener->GetRunStats(pPlayer->IsWatchingReplay() ? pPlayer->GetReplayEnt()->entindex() : pPlayer->entindex()); + float lastRunTime = pPlayer->IsWatchingReplay() ? pPlayer->GetReplayEnt()->m_flRunTime : g_MOMEventListener->m_flLastRunTime; m_flAvgSpeed = stats->m_flZoneVelocityAvg[0][hvel.GetBool()]; m_flMaxSpeed = stats->m_flZoneVelocityMax[0][hvel.GetBool()]; m_flEndSpeed = stats->m_flZoneExitSpeed[0][hvel.GetBool()]; @@ -347,7 +350,6 @@ void CHudMapFinishedDialog::OnThink() m_flAvgSync = stats->m_flZoneStrafeSync2Avg[0]; m_iTotalJumps = stats->m_iZoneJumps[0]; m_iTotalStrafes = stats->m_iZoneStrafes[0]; - //MOM_TODO: This needs updating to show the run time of the ghost - mom_UTIL->FormatTime(g_MOMEventListener->m_flLastRunTime, m_pszRunTime); + mom_UTIL->FormatTime(lastRunTime, m_pszRunTime); } } \ No newline at end of file diff --git a/mp/src/game/server/momentum/Timer.cpp b/mp/src/game/server/momentum/Timer.cpp index 3ecfd8fd65..8082795c90 100644 --- a/mp/src/game/server/momentum/Timer.cpp +++ b/mp/src/game/server/momentum/Timer.cpp @@ -9,6 +9,7 @@ void CTimer::Start(int start) ConVarRef zoneEdit("mom_zone_edit"); if (zoneEdit.GetBool()) return; m_iStartTick = start; + m_iEndTick = 0; SetRunning(true); //MOM_TODO: IDEA START: @@ -335,15 +336,15 @@ void CTimer::RequestZoneCount() //This function is called every time CTriggerStage::StartTouch is called float CTimer::CalculateStageTime(int stage) { - if (stage > m_iLastStage) + if (stage > m_iLastZone) { - float originalTime = static_cast(gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; + float originalTime = GetCurrentTime(); //If the stage is a new one, we store the time we entered this stage in m_flZoneEnterTime[stage] = stage == 1 ? 0.0f : //Always returns 0 for first stage. originalTime + m_flTickOffsetFix[stage-1]; - DevLog("Original Time: %f\n New Time: %f", originalTime, m_flZoneEnterTime[stage]); + DevLog("Original Time: %f\n New Time: %f\n", originalTime, m_flZoneEnterTime[stage]); } - m_iLastStage = stage; + m_iLastZone = stage; return m_flZoneEnterTime[stage]; } void CTimer::DispatchResetMessage() @@ -391,13 +392,13 @@ void CTimer::CalculateTickIntervalOffset(CMomentumPlayer* pPlayer, const int zon if (!pPlayer) return; Ray_t ray; Vector prevOrigin, origin, velocity = pPlayer->GetLocalVelocity(); - // Because trigger touch is calculated using colission hull rather than the player's origin (which is their world space center), + // Because trigger touch is calculated using collision hull rather than the player's origin (which is their world space center), // this origin is actually the player's local origin offset by their colission hull (depending on which direction they are moving), // so that we trace from the point in space where the player actually exited touch with the trigger, rather than their world center. if (zoneType == ZONETYPE_END) //ending zone or ending a stage { - if (velocity.x > 0 || velocity.y > 0) + if (abs(velocity.x) > 0 || abs(velocity.y) > 0) origin = Vector (pPlayer->GetLocalOrigin().x + pPlayer->CollisionProp()->OBBMaxs().x, pPlayer->GetLocalOrigin().y + pPlayer->CollisionProp()->OBBMaxs().y, pPlayer->GetLocalOrigin().z ); @@ -405,20 +406,18 @@ void CTimer::CalculateTickIntervalOffset(CMomentumPlayer* pPlayer, const int zon origin = pPlayer->GetLocalOrigin() + pPlayer->CollisionProp()->OBBMins(); // The previous origin is the origin "rewound" in time a single tick, scaled by player's current velocity - prevOrigin = Vector(origin.x - (velocity.x * gpGlobals->interval_per_tick), - origin.y - (velocity.y * gpGlobals->interval_per_tick), - origin.z - (velocity.z * gpGlobals->interval_per_tick)); + prevOrigin = pPlayer->GetPrevOrigin(origin); //ending zones have to have the ray start _before_ we entered the zone bbox, hence why we start with prevOrigin //and trace "forwards" to our current origin, hitting the trigger on the way. ray.Init(prevOrigin, origin); - debugoverlay->AddLineOverlay(prevOrigin, origin, 0, 255, 0, true, 10.0f); + debugoverlay->AddLineOverlay(prevOrigin, origin, 0, 255, 0, true, 10.0f);//MOM_TODO: REMOVE ME CTimeTriggerTraceEnum endTriggerTraceEnum(&ray, pPlayer->GetAbsVelocity(), zoneType); enginetrace->EnumerateEntities(ray, true, &endTriggerTraceEnum); } else if (zoneType == ZONETYPE_START )//start zone and stages { - if (velocity.x > 0 || velocity.y > 0) + if (abs(velocity.x) > 0 || abs(velocity.y) > 0) origin = pPlayer->GetLocalOrigin() + pPlayer->CollisionProp()->OBBMins(); else origin = Vector(pPlayer->GetLocalOrigin().x + pPlayer->CollisionProp()->OBBMaxs().x, @@ -426,9 +425,7 @@ void CTimer::CalculateTickIntervalOffset(CMomentumPlayer* pPlayer, const int zon pPlayer->GetLocalOrigin().z); // The previous origin is the origin "rewound" in time a single tick, scaled by player's current velocity - prevOrigin = Vector(origin.x - (velocity.x * gpGlobals->interval_per_tick), - origin.y - (velocity.y * gpGlobals->interval_per_tick), - origin.z - (velocity.z * gpGlobals->interval_per_tick)); + prevOrigin = pPlayer->GetPrevOrigin(origin); //Start/stage zones trace from outside the trigger, backwards, hitting the zone along the way ray.Init(origin, prevOrigin); @@ -461,7 +458,11 @@ bool CTimeTriggerTraceEnum::EnumEntity(IHandleEntity *pHandleEntity) DevLog("Time offset: %f\n", offset); int stage = m_iZoneType; if (m_iZoneType == g_Timer->ZONETYPE_START) stage = g_Timer->GetCurrentZoneNumber(); - g_Timer->SetIntervalOffset(stage, offset); + + //MOM_TODO: If this was a ZONETYPE_END, don't we set the offset as (gpGlobals->interval_per_tick - offset) ? + + if (!mom_UTIL->FloatEquals(offset, 0.0f)) + g_Timer->SetIntervalOffset(stage, offset); return true; } diff --git a/mp/src/game/server/momentum/Timer.h b/mp/src/game/server/momentum/Timer.h index 7f528b137a..1da9071025 100644 --- a/mp/src/game/server/momentum/Timer.h +++ b/mp/src/game/server/momentum/Timer.h @@ -6,16 +6,14 @@ #include "utlvector.h" #include "momentum/tickset.h" -#include "KeyValues.h" -#include "momentum/util/mom_util.h" -#include "filesystem.h" #include "mom_triggers.h" -#include "GameEventListener.h" -#include "tier1/checksum_sha1.h" +#include "mom_player.h" +#include "tier1/checksum_sha1.h"//MOM_TODO: We'll need this #include "momentum/mom_shareddefs.h" -#include "momentum/mom_gamerules.h" #include "mom_replay.h" +#include "util/mom_util.h" #include "movevars_shared.h" +#include "filesystem.h" #include class CTriggerTimerStart; @@ -27,6 +25,23 @@ class CTimer { public: + CTimer() + : m_iZoneCount(0), + m_iStartTick(0), + m_iEndTick(0), + m_iLastZone(0), + m_bIsRunning(false), + m_bWereCheatsActivated(false), + m_bMapIsLinear(false), + m_pStartTrigger(nullptr), + m_pEndTrigger(nullptr), + m_pCurrentCheckpoint(nullptr), + m_pCurrentStage(nullptr), + m_iCurrentStepCP(0), + m_bUsingCPMenu(false) + { + } + //-------- HUD Messages -------------------- void DispatchStateMessage(); void DispatchResetMessage(); @@ -39,23 +54,28 @@ class CTimer // Stops the timer void Stop(bool = false); // Is the timer running? - bool IsRunning() { return m_bIsRunning; } + bool IsRunning() const + { return m_bIsRunning; } // Set the running status of the timer void SetRunning(bool running) { m_bIsRunning = running; } // ------------- Timer trigger related methods ---------------------------- // Gets the current starting trigger - CTriggerTimerStart *GetStartTrigger() { return m_pStartTrigger.Get(); } + CTriggerTimerStart *GetStartTrigger() const + { return m_pStartTrigger.Get(); } // Gets the current checkpoint - CTriggerCheckpoint *GetCurrentCheckpoint() { return m_pCurrentCheckpoint.Get(); } + CTriggerCheckpoint *GetCurrentCheckpoint() const + { return m_pCurrentCheckpoint.Get(); } - CTriggerTimerStop *GetEndTrigger() { return m_pEndTrigger.Get(); } - CTriggerStage *GetCurrentStage() { return m_pCurrentStage.Get(); } + CTriggerTimerStop *GetEndTrigger() const + { return m_pEndTrigger.Get(); } + CTriggerStage *GetCurrentStage() const + { return m_pCurrentStage.Get(); } // Sets the given trigger as the start trigger void SetStartTrigger(CTriggerTimerStart *pTrigger) { - m_iLastStage = 0;//Allows us to overwrite previous runs + m_iLastZone = 0;//Allows us to overwrite previous runs m_pStartTrigger.Set(pTrigger); } @@ -75,21 +95,33 @@ class CTimer // Stores the result on m_iStageCount void RequestZoneCount(); // Gets the total stage count - int GetZoneCount() { return m_iZoneCount; }; + int GetZoneCount() const + { return m_iZoneCount; }; float CalculateStageTime(int stageNum); + // Gets the time for the last run, if there was one float GetLastRunTime() { + if (m_iEndTick == 0) + return 0.0f; float originalTime = static_cast(m_iEndTick - m_iStartTick) * gpGlobals->interval_per_tick; // apply precision fix, adding offset from start as well as subtracting offset from end. // offset from end is 1 tick - fraction offset, since we started trace outside of the end zone. return originalTime + m_flTickOffsetFix[1] - (gpGlobals->interval_per_tick - m_flTickOffsetFix[0]); } + // Gets the current time for this timer + float GetCurrentTime() const + { + return float(gpGlobals->tickcount - m_iStartTick) * gpGlobals->interval_per_tick; + } + //--------- CheckpointMenu stuff -------------------------------- // Gets the current menu checkpoint index - int GetCurrentCPMenuStep() { return m_iCurrentStepCP; } + int GetCurrentCPMenuStep() const + { return m_iCurrentStepCP; } // MOM_TODO: For leaderboard use later on - bool IsUsingCPMenu() { return m_bUsingCPMenu; } + bool IsUsingCPMenu() const + { return m_bUsingCPMenu; } // Creates a checkpoint (menu) on the location of the given Entity void CreateCheckpoint(CBasePlayer*); // Removes last checkpoint (menu) form the checkpoint lists @@ -107,7 +139,8 @@ class CTimer // Sets the current checkpoint (menu) to the desired one with that index void SetCurrentCPMenuStep(int pNewNum); // Gets the total amount of menu checkpoints - int GetCPCount() { return checkpoints.Size(); } + int GetCPCount() const + { return checkpoints.Size(); } // Sets wheter or not we're using the CPMenu // WARNING! No verification is done. It is up to the caller to don't give false information void SetUsingCPMenu(bool pIsUsingCPMenu); @@ -125,7 +158,8 @@ class CTimer // Removes all onehops from the list void RemoveAllOnehopsFromList() { onehops.RemoveAll(); } // Returns the count for the onehop list - int GetOnehopListCount() { return onehops.Count(); } + int GetOnehopListCount() const + { return onehops.Count(); } // Finds the onehop with the given index on the list CTriggerOnehop* FindOnehopOnList(int pIndexOnList); @@ -165,8 +199,7 @@ class CTimer int m_iZoneCount; int m_iStartTick, m_iEndTick; - int m_iLastStage = 0; - float m_flZoneEnterTime[MAX_STAGES]; + int m_iLastZone; bool m_bIsRunning; bool m_bWereCheatsActivated; bool m_bMapIsLinear; @@ -186,6 +219,10 @@ class CTimer //stage specific stats: RunStats_t RunStats; + + Time(): time_sec(0), tickrate(0), date(0), flags(0) + { + } }; struct Checkpoint @@ -201,13 +238,14 @@ class CTimer CUtlVector