diff --git a/common/ComputationalTools.h b/common/ComputationalTools.h new file mode 100644 index 0000000..9ccf8c0 --- /dev/null +++ b/common/ComputationalTools.h @@ -0,0 +1,41 @@ +#ifndef COMPUTATIONAL_TOOLS +#define COMPUTATIONAL_TOOLS + +#include + +template +class MovingAverage +{ + enum { val = (N >= 1) & !(N & (N - 1)) }; + static_assert(val, "The Sample size template parameter should be a power of 2"); + + public: + MovingAverage() : num_samples_(0), total_(0) {} + + void Reset() + { + num_samples_ = 0; + total_ = 0; + } + + void AddNumber( T sample ) + { + if (num_samples_ < N) + { + samples_[num_samples_++] = sample; + total_ += sample; + } else { + T& oldest = samples_[num_samples_++ % N]; + total_ += sample - oldest; + oldest = sample; + } + } + + operator double() const { return total_ / min(num_samples_, N); } + + private: + T samples_[N]; + int num_samples_; + T total_; +}; +#endif diff --git a/sizzlinglib/timedeventmgr.cpp b/sizzlinglib/timedeventmgr.cpp new file mode 100644 index 0000000..745ac3c --- /dev/null +++ b/sizzlinglib/timedeventmgr.cpp @@ -0,0 +1,153 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "edict.h" +#include "timedeventmgr.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern CGlobalVars *gpGlobals; + +// ------------------------------------------------------------------------------------------ // +// CEventRegister. +// ------------------------------------------------------------------------------------------ // + +CEventRegister::CEventRegister() +{ + m_bRegistered = false; + m_pEventMgr = NULL; +} + + +CEventRegister::~CEventRegister() +{ + Term(); +} + + +void CEventRegister::Init( CTimedEventMgr *pMgr, IEventRegisterCallback *pCallback ) +{ + Term(); + m_pEventMgr = pMgr; + m_pCallback = pCallback; +} + + +void CEventRegister::Term() +{ + // Unregister. + if ( m_pEventMgr && m_bRegistered ) + { + m_pEventMgr->RemoveEvent( this ); + } +} + + +void CEventRegister::SetUpdateInterval( float interval ) +{ + Assert( m_pEventMgr ); + + if ( m_pEventMgr ) + { + // Register for this event. + m_flUpdateInterval = interval; + m_flNextEventTime = gpGlobals->curtime + m_flUpdateInterval; + + m_pEventMgr->RegisterForNextEvent( this ); + } +} + + +void CEventRegister::StopUpdates() +{ + if ( m_pEventMgr ) + { + // Unregister our next event. + m_pEventMgr->RemoveEvent( this ); + } +} + + +void CEventRegister::Reregister() +{ + if ( m_flUpdateInterval > 1e-6 && m_pEventMgr ) + { + while ( m_flNextEventTime <= gpGlobals->curtime ) + { + m_flNextEventTime += m_flUpdateInterval; + } + + m_pEventMgr->RegisterForNextEvent( this ); + } +} + + +// ------------------------------------------------------------------------------------------ // +// CTimedEventMgr. +// ------------------------------------------------------------------------------------------ // + +bool TimedEventMgr_LessFunc( CEventRegister* const &a, CEventRegister* const &b ) +{ + return a->m_flNextEventTime > b->m_flNextEventTime; +} + + +CTimedEventMgr::CTimedEventMgr() +{ + m_Events.SetLessFunc( TimedEventMgr_LessFunc ); +} + + +void CTimedEventMgr::FireEvents() +{ + VPROF( "CTimedEventMgr::FireEvents" ); + while ( m_Events.Count() ) + { + // Fire the top element, then break out. + CEventRegister *pEvent = m_Events.ElementAtHead(); + if ( gpGlobals->curtime >= pEvent->m_flNextEventTime ) + { + // Reregister for the timed event, then fire the callback for the event. + m_Events.RemoveAtHead(); + pEvent->m_bRegistered = false; + pEvent->Reregister(); + + pEvent->m_pCallback->FireEvent(); + } + else + { + break; + } + } +} + + +void CTimedEventMgr::RegisterForNextEvent( CEventRegister *pEvent ) +{ + RemoveEvent( pEvent ); + m_Events.Insert( pEvent ); + pEvent->m_bRegistered = true; +} + + +void CTimedEventMgr::RemoveEvent( CEventRegister *pEvent ) +{ + if ( pEvent->m_bRegistered ) + { + // Find the event in the list and remove it. + int cnt = m_Events.Count(); + for ( int i=0; i < cnt; i++ ) + { + if ( m_Events.Element( i ) == pEvent ) + { + m_Events.RemoveAt( i ); + break; + } + } + } +} diff --git a/sizzlingstats/SSTeamData.cpp b/sizzlingstats/SSTeamData.cpp new file mode 100644 index 0000000..93ed22a --- /dev/null +++ b/sizzlingstats/SSTeamData.cpp @@ -0,0 +1,92 @@ + +#include "SSTeamData.h" + +tfteam to_tfteam( int teamid ) +{ + + switch (teamid) { + TFTEAM_SPECTATOR: + return TFTEAM_SPECTATOR; + TFTEAM_RED: + return TFTEAM_RED; + TFTEAM_BLUE: + return TFTEAM_BLUE; + default: + return TFTEAM_UNASSIGNED; + } +} + +void TeamScoreData::Reset() +{ + m_AverageMedicDistance.Reset(); +} + +CTeamDataManager::CTeamDataManager() +{ + +} + +CTeamDataManager::~CTeamDataManager() +{ +} + +void CTeamDataManager::Reset() +{ + TeamScoreData *tm_data = nullptr; + + if ((tm_data = GetTeamStats(TFTEAM_RED)) != nullptr) + { + tm_data->Reset(); + } + + if ((tm_data = GetTeamStats(TFTEAM_BLUE)) != nullptr) + { + tm_data->Reset(); + } + +} + +void CTeamDataManager::ResetTeamStatsData() +{ +} + +void CTeamDataManager::AddStatSample( tfteam team, TeamStat stat, float sample_point ) +{ + + if (stat == TeamStat::MedicCohesion) + { + TeamScoreData *team_data = GetTeamStats(team); + if (!team_data) + return; + + team_data->m_AverageMedicDistance.AddNumber(sample_point); + } +} + +double CTeamDataManager::GetStat( tfteam team, TeamStat stat ) +{ + if (stat == TeamStat::MedicCohesion) + { + TeamScoreData *team_data = GetTeamStats(team); + if (!team_data) + return 0.0; + return team_data->m_AverageMedicDistance; + } + + return 0.0; +} + + +TeamScoreData *CTeamDataManager::GetTeamStats( tfteam team ) +{ + switch (team) + { + case TFTEAM_RED: + return &m_TeamScoreData[0]; + case TFTEAM_BLUE: + return &m_TeamScoreData[1]; + default: + return nullptr; + } +} + diff --git a/sizzlingstats/SSTeamData.h b/sizzlingstats/SSTeamData.h new file mode 100644 index 0000000..8a52c1c --- /dev/null +++ b/sizzlingstats/SSTeamData.h @@ -0,0 +1,53 @@ +#ifndef SS_TEAM_DATA +#define SS_TEAM_DATA + +#include "ComputationalTools.h" + +class CSizzPluginContext; +/** + * Maintains the round statistics for the team as a whole. + */ + + +struct TeamScoreData +{ + MovingAverage m_AverageMedicDistance; + void Reset(); +}; + +enum tfteam +{ + TFTEAM_UNASSIGNED = 0, + TFTEAM_SPECTATOR = 1, + TFTEAM_RED = 2, + TFTEAM_BLUE = 3 +}; + +enum class TeamStat +{ + MedicCohesion +}; + + +tfteam to_tfteam( int ); + +class CTeamDataManager +{ + public: + CTeamDataManager(); + ~CTeamDataManager(); + + void AddStatSample( tfteam team, TeamStat stat, float sample_point ); + double GetStat( tfteam team, TeamStat stat ); + + void ResetTeamStatsData(); + void Reset(); + + private: + TeamScoreData *GetTeamStats( tfteam team ); + TeamScoreData m_TeamScoreData[2]; +}; + + + +#endif diff --git a/sizzlingstats/SizzlingStats.cpp b/sizzlingstats/SizzlingStats.cpp index 8504d69..5f12ac5 100644 --- a/sizzlingstats/SizzlingStats.cpp +++ b/sizzlingstats/SizzlingStats.cpp @@ -50,7 +50,10 @@ SizzlingStats::SizzlingStats(): m_pBluTeam(NULL), m_iOldRedScore(0), m_iOldBluScore(0), + m_plugin_context(NULL), + m_TimedEventMgr(), m_PlayerDataManager(), + m_TeamDataManager(), m_refHostIP((IConVar*)NULL), m_refIP((IConVar*)NULL), m_refHostPort((IConVar*)NULL), @@ -63,6 +66,8 @@ SizzlingStats::SizzlingStats(): m_pHostInfo = new hostInfo_t(); m_pWebStatsHandler = new CWebStatsHandler(); m_pS3UploaderThread = new CS3UploaderThread(); + m_EventRegister.Init( &m_TimedEventMgr, this ); + } #pragma warning( pop ) @@ -84,6 +89,9 @@ void SizzlingStats::Load( CSizzPluginContext *pPluginContext ) m_pWebStatsHandler->Initialize(); m_STVRecorder.Load(); + m_plugin_context = pPluginContext; + + using namespace std::placeholders; #ifdef _WIN32 // this hack makes it so that VC++11 outputs 2 moves instead of a copy and a move when calling the std::functions @@ -104,6 +112,9 @@ void SizzlingStats::Unload( CSizzPluginContext *pPluginContext ) m_STVRecorder.Unload(pPluginContext->GetEngine()); m_pS3UploaderThread->ShutDown(); m_pWebStatsHandler->Shutdown(); + + m_EventRegister.StopUpdates(); + m_plugin_context = nullptr; } void SizzlingStats::LevelInit( CSizzPluginContext *pPluginContext, const char *pMapName ) @@ -118,6 +129,7 @@ void SizzlingStats::ServerActivate( CSizzPluginContext *pPluginContext ) void SizzlingStats::GameFrame() { + m_TimedEventMgr.FireEvents(); } void SizzlingStats::LoadConfig( CSizzPluginContext *pPluginContext ) @@ -249,6 +261,92 @@ void SizzlingStats::GiveUber( CSizzPluginContext *pPluginContext, int entindex ) player.SetChargeLevel(pPluginContext, 1.0f); } +void SizzlingStats::SS_PingMedics( CSizzPluginContext *pPluginContext ) +{ + int max_clients = pPluginContext->GetMaxClients(); + + bool found_red_medic = false; + bool found_blu_medic = false; + + for (int i = 0; i < m_vecMedics.Count(); ++i) + { + int medIndex = m_vecMedics[i]; + SS_PlayerData *pMedData = m_PlayerDataManager.GetPlayerData(medIndex).m_pPlayerData; + CTFPlayerWrapper medic(pMedData->GetBaseEntity()); + + IPlayerInfo *pMedPlayerInfo = pPluginContext->GetPlayerInfo(medIndex); + + if( pMedPlayerInfo->IsPlayer() && pMedPlayerInfo->IsDead() ) + continue; + + Vector *medPos = medic.GetPlayerOrigin(); + + vec_t total_distance = 0; + int nteammates = 0; + + int medic_team_id = pMedPlayerInfo->GetTeamIndex(); + tfteam medic_team; + + // Only compute distance from the first medic found + if(medic_team_id == 2) + { + if(found_red_medic) + continue; + found_red_medic = true; + medic_team = TFTEAM_RED; + } else if(medic_team_id == 3) + { + if(found_blu_medic) + continue; + found_blu_medic = true; + medic_team = TFTEAM_BLUE; + } + + for ( int victimIndex = 1; victimIndex <= max_clients; ++victimIndex ) + { + if(medIndex == victimIndex) + continue; + + if(!m_PlayerDataManager.IsValidPlayer(victimIndex)) + continue; + + SS_PlayerData *pVictimData = m_PlayerDataManager.GetPlayerData(victimIndex).m_pPlayerData; + IPlayerInfo *pVictimPlayerInfo = pPluginContext->GetPlayerInfo(victimIndex); + + + if ( medic_team_id != pVictimPlayerInfo->GetTeamIndex() ) + continue; + + if( pVictimPlayerInfo->IsPlayer() && pVictimPlayerInfo->IsDead() ) + continue; + + CTFPlayerWrapper victim(pVictimData->GetBaseEntity()); + + Vector *victimPos = victim.GetPlayerOrigin(); + + //vec_t distance = victimPos->DistTo( *medPos ); + //vec_t distance = victimPos->DistToSqr( *medPos ); + vec_t distance = sqrt(victimPos->DistToSqr( *medPos )); + total_distance += distance; + ++nteammates; + } + + if(nteammates > 0) + { + vec_t average_distance = total_distance/static_cast(nteammates); + + m_TeamDataManager.AddStatSample(medic_team,TeamStat::MedicCohesion,average_distance); +#ifdef DEV_COMMANDS_ON + SS_AllUserChatMessage( pPluginContext, UTIL_VarArgs("avg medic distance: %.2f\n", average_distance) ); + double moving_average_distance = m_TeamDataManager.GetStat( medic_team, TeamStat::MedicCohesion ); + SS_AllUserChatMessage( pPluginContext, UTIL_VarArgs("moving-avg medic distance: %.2f\n", moving_average_distance) ); +#endif + } + } + +} + + void SizzlingStats::CheckPlayerDropped( CSizzPluginContext *pPluginContext, int victimIndex ) { for (int i = 0; i < m_vecMedics.Count(); ++i) @@ -406,6 +504,8 @@ void SizzlingStats::SS_AllUserChatMessage( CSizzPluginContext *pPluginContext, c void SizzlingStats::SS_TournamentMatchStarted( CSizzPluginContext *pPluginContext ) { Msg( "tournament match started\n" ); + + m_EventRegister.SetUpdateInterval(3.0f); m_bTournamentMatchRunning = true; m_flMatchDuration = Plat_FloatTime(); @@ -460,6 +560,8 @@ void SizzlingStats::SS_TournamentMatchEnded( CSizzPluginContext *pPluginContext // the value of the recording cvar m_STVRecorder.StopRecording(pPluginContext->GetEngine()); //SetTeamScores(0, 0); + + m_EventRegister.StopUpdates(); } void SizzlingStats::SS_PreRoundFreeze( CSizzPluginContext *pPluginContext ) @@ -512,10 +614,13 @@ void SizzlingStats::SS_DisplayStats( CSizzPluginContext *pPluginContext, int ent int ubers = playerData.GetStat(Invulns); int drops = playerData.GetStat(UbersDropped); int medpicks = playerData.GetStat(MedPicks); + IPlayerInfo *pPlayerInfo = pPluginContext->GetPlayerInfo(ent_index); CTFPlayerWrapper player(pPluginContext->BaseEntityFromEntIndex(ent_index)); + int iTeam = pPlayerInfo->GetTeamIndex(); + V_snprintf( pText, 64, "\x04[\x05SizzlingStats\x04]\x06: \x03%s\n", pPlayerInfo->GetName() ); SS_SingleUserChatMessage(pPluginContext, ent_index, pText); @@ -582,6 +687,21 @@ void SizzlingStats::SS_DisplayStats( CSizzPluginContext *pPluginContext, int ent memset( pText, 0, sizeof(pText) ); V_snprintf( pText, 64, "Round Score: %i\n", points ); SS_SingleUserChatMessage(pPluginContext, ent_index, pText); + + tfteam team = to_tfteam(iTeam); + + if (team != TFTEAM_RED && team != TFTEAM_BLUE) + return; + + double team_cohesion = m_TeamDataManager.GetStat( team, TeamStat::MedicCohesion ); + + if( team_cohesion > 0.0 ) + { + memset( pText, 0, sizeof(pText) ); + V_snprintf( pText, 64, "Team Cohesion: %f\n", team_cohesion); + SS_SingleUserChatMessage(pPluginContext, ent_index, pText); + } + } void SizzlingStats::SS_EndOfRound( CSizzPluginContext *pPluginContext ) @@ -635,6 +755,7 @@ void SizzlingStats::SS_ResetData( CSizzPluginContext *pPluginContext ) data.m_pExtraData->operator=(0); } } + m_TeamDataManager.Reset(); } void SizzlingStats::SS_Credits( CSizzPluginContext *pPluginContext, int entindex, const char *pszVersion ) @@ -781,3 +902,9 @@ void SizzlingStats::OnS3UploadReturn( const sizz::CString &sessionid, bool bUplo Msg("[SizzlingStats]: S3Upload failed\n"); } } + +void SizzlingStats::FireEvent() +{ + if(m_plugin_context != nullptr) + SS_PingMedics(m_plugin_context); +} diff --git a/sizzlingstats/SizzlingStats.h b/sizzlingstats/SizzlingStats.h index 54bbc3c..c585195 100644 --- a/sizzlingstats/SizzlingStats.h +++ b/sizzlingstats/SizzlingStats.h @@ -19,17 +19,22 @@ #include "convar.h" #include "PluginDefines.h" #include "PlayerDataManager.h" +#include "SSTeamData.h" #include "sizzstring.h" #include "STVRecorder.h" #include "S3Uploader.h" +#include "timedeventmgr.h" #include "tier1/utlvector.h" class CFuncQueueThread; class CSizzPluginContext; struct edict_t; -class SizzlingStats +extern CGlobalVars *gpGlobals; + +class SizzlingStats : public IEventRegisterCallback + { public: SizzlingStats(void); @@ -49,6 +54,9 @@ class SizzlingStats void GameFrame(); void LoadConfig( CSizzPluginContext *pPluginContext ); + + // IEventRegister Callback + virtual void FireEvent(); void PlayerHealed( int entindex, int amount ); void MedPick( int entindex ); @@ -113,6 +121,8 @@ class SizzlingStats void SetTeamScores( int redscore, int bluscore ); + void SS_PingMedics( CSizzPluginContext *pPluginContext ); + void SS_TestThreading(); void SS_UploadStats(); @@ -147,9 +157,14 @@ class SizzlingStats uint16 m_iOldBluScore; CUtlVector m_vecMedics; //ent index of medics + + CSizzPluginContext *m_plugin_context; + CTimedEventMgr m_TimedEventMgr; + CEventRegister m_EventRegister; private: CS3UploaderThread *m_pS3UploaderThread; CPlayerDataManager m_PlayerDataManager; + CTeamDataManager m_TeamDataManager; CWebStatsHandler *m_pWebStatsHandler; CSTVRecorder m_STVRecorder; ConVarRef m_refHostIP; diff --git a/sizzlingstats/serverplugin_empty.cpp b/sizzlingstats/serverplugin_empty.cpp index dca683e..1d2b141 100644 --- a/sizzlingstats/serverplugin_empty.cpp +++ b/sizzlingstats/serverplugin_empty.cpp @@ -60,6 +60,7 @@ IEngineTrace *enginetrace = NULL; IServerGameDLL *pServerDLL = NULL; IFileSystem *g_pFullFileSystem = NULL; +CGlobalVars *gpGlobals = NULL; //===========================================================================// @@ -255,12 +256,14 @@ bool CEmptyServerPlugin::Load( CreateInterfaceFn interfaceFactory, CreateInterfa return false; } + plugin_context_init_t init; init.pEngine = (IVEngineServer*)interfaceFactory(INTERFACEVERSION_VENGINESERVER, NULL); init.pPlayerInfoManager = (IPlayerInfoManager *)gameServerFactory(INTERFACEVERSION_PLAYERINFOMANAGER,NULL); init.pHelpers = (IServerPluginHelpers*)interfaceFactory(INTERFACEVERSION_ISERVERPLUGINHELPERS, NULL); init.pGameEventManager = (IGameEventManager2*)interfaceFactory(INTERFACEVERSION_GAMEEVENTSMANAGER2, NULL); init.pServerGameDLL = (IServerGameDLL *)gameServerFactory(INTERFACEVERSION_SERVERGAMEDLL, NULL); + gpGlobals = init.pPlayerInfoManager->GetGlobalVars(); if (!m_plugin_context.Initialize(init)) { @@ -652,6 +655,10 @@ PLUGIN_RESULT CEmptyServerPlugin::ClientCommand( edict_t *pEntity, const CComman { m_SizzlingStats.SS_HideHtmlStats(&m_plugin_context, entindex); } + else if ( FStrEq( pcmd, "pingmedic" ) ) + { + m_SizzlingStats.SS_PingMedics(&m_plugin_context); + } #ifdef DEV_COMMANDS_ON else if ( FStrEq( pcmd, "gibuber" ) ) {