ds@1952: /** ds@1952: * vim: set ts=4 : ds@1952: * ============================================================================= ds@1952: * SourceMod ds@1952: * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. ds@1952: * ============================================================================= ds@1952: * ds@1952: * This program is free software; you can redistribute it and/or modify it under ds@1952: * the terms of the GNU General Public License, version 3.0, as published by the ds@1952: * Free Software Foundation. ds@1952: * ds@1952: * This program is distributed in the hope that it will be useful, but WITHOUT ds@1952: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ds@1952: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ds@1952: * details. ds@1952: * ds@1952: * You should have received a copy of the GNU General Public License along with ds@1952: * this program. If not, see . ds@1952: * ds@1952: * As a special exception, AlliedModders LLC gives you permission to link the ds@1952: * code of this program (as well as its derivative works) to "Half-Life 2," the ds@1952: * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software ds@1952: * by the Valve Corporation. You must obey the GNU General Public License in ds@1952: * all respects for all other code used. Additionally, AlliedModders LLC grants ds@1952: * this exception to all derivative works. AlliedModders LLC defines further ds@1952: * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), ds@1952: * or . ds@1952: * ds@1952: * Version: $Id$ ds@1952: */ ds@1952: ds@1952: #include ds@1952: #include "TimerSys.h" ds@1952: #include "ForwardSys.h" ds@1952: #include "sourcemm_api.h" ds@1952: #include "frame_hooks.h" ds@1952: #include "ConVarManager.h" ds@1952: ds@1952: #define TIMER_MIN_ACCURACY 0.1 ds@1952: ds@1952: TimerSystem g_Timers; ds@1952: double g_fUniversalTime = 0.0f; ds@1952: float g_fGameStartTime = 0.0f; /* Game game start time, non-universal */ ds@1952: double g_fTimerThink = 0.0f; /* Timer's next think time */ ds@1952: const double *g_pUniversalTime = &g_fUniversalTime; ds@1952: ConVar *mp_timelimit = NULL; ds@1952: int g_TimeLeftMode = 0; ds@1952: ds@1952: ConVar sm_time_adjustment("sm_time_adjustment", "0", 0, "Adjusts the server time in seconds"); ds@1952: ds@1952: inline double GetSimulatedTime() ds@1952: { ds@1952: return g_fUniversalTime; ds@1952: } ds@1952: ds@1952: time_t GetAdjustedTime(time_t *buf) ds@1952: { ds@1952: time_t val = time(NULL) + sm_time_adjustment.GetInt(); ds@1952: if (buf) ds@1952: { ds@1952: *buf = val; ds@1952: } ds@1952: return val; ds@1952: } ds@1952: ds@1952: class DefaultMapTimer : ds@1952: public IMapTimer, ds@1952: public SMGlobalClass, ds@1952: public IConVarChangeListener ds@1952: { ds@1952: public: ds@1952: DefaultMapTimer() ds@1952: { ds@1952: m_bInUse = false; ds@1952: } ds@1952: ds@1952: void OnSourceModLevelChange(const char *mapName) ds@1952: { ds@1952: g_fGameStartTime = 0.0f; ds@1952: } ds@1952: ds@1952: int GetMapTimeLimit() ds@1952: { ds@1952: return mp_timelimit->GetInt(); ds@1952: } ds@1952: ds@1952: void SetMapTimerStatus(bool enabled) ds@1952: { ds@1952: if (enabled && !m_bInUse) ds@1952: { ds@1952: Enable(); ds@1952: } ds@1952: else if (!enabled && m_bInUse) ds@1952: { ds@1952: Disable(); ds@1952: } ds@1952: m_bInUse = enabled; ds@1952: } ds@1952: ds@1952: void ExtendMapTimeLimit(int extra_time) ds@1952: { ds@1952: if (extra_time == 0) ds@1952: { ds@1952: mp_timelimit->SetValue(0); ds@1952: return; ds@1952: } ds@1952: ds@1952: extra_time /= 60; ds@1952: ds@1952: mp_timelimit->SetValue(mp_timelimit->GetInt() + extra_time); ds@1952: } ds@1952: ds@1952: void OnConVarChanged(ConVar *pConVar, const char *oldValue, float flOldValue) ds@1952: { ds@1952: g_Timers.MapTimeLeftChanged(); ds@1952: } ds@1952: ds@1952: private: ds@1952: void Enable() ds@1952: { ds@1952: g_ConVarManager.AddConVarChangeListener("mp_timelimit", this); ds@1952: } ds@1952: ds@1952: void Disable() ds@1952: { ds@1952: g_ConVarManager.RemoveConVarChangeListener("mp_timelimit", this); ds@1952: } ds@1952: ds@1952: private: ds@1952: bool m_bInUse; ds@1952: } s_DefaultMapTimer; ds@1952: ds@1952: /** ds@1952: * If the ticking process has run amok (should be impossible), we ds@1952: * take care of this by "skipping" the in-between time, to prevent ds@1952: * a bazillion times from firing on accident. This has the result ds@1952: * that a drastic jump in time will continue acting normally. Users ds@1952: * may not expect this, but... I think it is the best solution. ds@1952: */ ds@1952: inline double CalcNextThink(double last, float interval) ds@1952: { ds@1952: if (g_fUniversalTime - last - interval <= TIMER_MIN_ACCURACY) ds@1952: { ds@1952: return last + interval; ds@1952: } ds@1952: else ds@1952: { ds@1952: return g_fUniversalTime + interval; ds@1952: } ds@1952: } ds@1952: ds@1952: void ITimer::Initialize(ITimedEvent *pCallbacks, float fInterval, float fToExec, void *pData, int flags) ds@1952: { ds@1952: m_Listener = pCallbacks; ds@1952: m_Interval = fInterval; ds@1952: m_ToExec = fToExec; ds@1952: m_pData = pData; ds@1952: m_Flags = flags; ds@1952: m_InExec = false; ds@1952: m_KillMe = false; ds@1952: } ds@1952: ds@1952: TimerSystem::TimerSystem() ds@1952: { ds@1952: m_pMapTimer = NULL; ds@1952: m_bHasMapTickedYet = false; ds@1952: m_bHasMapSimulatedYet = false; ds@1952: m_fLastTickedTime = 0.0f; ds@1952: } ds@1952: ds@1952: TimerSystem::~TimerSystem() ds@1952: { ds@1952: CStack::iterator iter; ds@1952: for (iter=m_FreeTimers.begin(); iter!=m_FreeTimers.end(); iter++) ds@1952: { ds@1952: delete (*iter); ds@1952: } ds@1952: m_FreeTimers.popall(); ds@1952: } ds@1952: ds@1952: void TimerSystem::OnSourceModAllInitialized() ds@1952: { ds@1952: g_ShareSys.AddInterface(NULL, this); ds@1952: m_pOnGameFrame = g_Forwards.CreateForward("OnGameFrame", ET_Ignore, 0, NULL); ds@1952: m_pOnMapTimeLeftChanged = g_Forwards.CreateForward("OnMapTimeLeftChanged", ET_Ignore, 0, NULL); ds@1952: } ds@1952: ds@1952: void TimerSystem::OnSourceModGameInitialized() ds@1952: { ds@1952: mp_timelimit = icvar->FindVar("mp_timelimit"); ds@1952: ds@1952: if (m_pMapTimer == NULL && mp_timelimit != NULL) ds@1952: { ds@1952: SetMapTimer(&s_DefaultMapTimer); ds@1952: } ds@1952: } ds@1952: ds@1952: void TimerSystem::OnSourceModShutdown() ds@1952: { ds@1952: SetMapTimer(NULL); ds@1952: g_Forwards.ReleaseForward(m_pOnGameFrame); ds@1952: g_Forwards.ReleaseForward(m_pOnMapTimeLeftChanged); ds@1952: } ds@1952: ds@1952: void TimerSystem::OnSourceModLevelEnd() ds@1952: { ds@1952: m_bHasMapTickedYet = false; ds@1952: m_bHasMapSimulatedYet = false; ds@1952: } ds@1952: ds@1952: void TimerSystem::GameFrame(bool simulating) ds@1952: { ds@1952: if (simulating && m_bHasMapTickedYet) ds@1952: { ds@1952: g_fUniversalTime += gpGlobals->curtime - m_fLastTickedTime; ds@1952: if (!m_bHasMapSimulatedYet) ds@1952: { ds@1952: m_bHasMapSimulatedYet = true; ds@1952: MapTimeLeftChanged(); ds@1952: } ds@1952: } ds@1952: else ds@1952: { ds@1952: g_fUniversalTime += gpGlobals->interval_per_tick; ds@1952: } ds@1952: ds@1952: m_fLastTickedTime = gpGlobals->curtime; ds@1952: m_bHasMapTickedYet = true; ds@1952: ds@1952: if (g_fUniversalTime >= g_fTimerThink) ds@1952: { ds@1952: RunFrame(); ds@1952: ds@1952: g_fTimerThink = CalcNextThink(g_fTimerThink, TIMER_MIN_ACCURACY); ds@1952: } ds@1952: ds@1952: RunFrameHooks(simulating); ds@1952: ds@1952: if (m_pOnGameFrame->GetFunctionCount()) ds@1952: { ds@1952: m_pOnGameFrame->Execute(NULL); ds@1952: } ds@1952: } ds@1952: ds@1952: void TimerSystem::RunFrame() ds@1952: { ds@1952: ITimer *pTimer; ds@1952: TimerIter iter; ds@1952: ds@1952: double curtime = GetSimulatedTime(); ds@1952: for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); ) ds@1952: { ds@1952: pTimer = (*iter); ds@1952: if (curtime >= pTimer->m_ToExec) ds@1952: { ds@1952: pTimer->m_InExec = true; ds@1952: pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData); ds@1952: pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData); ds@1952: iter = m_SingleTimers.erase(iter); ds@1952: m_FreeTimers.push(pTimer); ds@1952: } ds@1952: else ds@1952: { ds@1952: break; ds@1952: } ds@1952: } ds@1952: ds@1952: ResultType res; ds@1952: for (iter=m_LoopTimers.begin(); iter!=m_LoopTimers.end(); ) ds@1952: { ds@1952: pTimer = (*iter); ds@1952: if (curtime >= pTimer->m_ToExec) ds@1952: { ds@1952: pTimer->m_InExec = true; ds@1952: res = pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData); ds@1952: if (pTimer->m_KillMe || (res == Pl_Stop)) ds@1952: { ds@1952: pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData); ds@1952: iter = m_LoopTimers.erase(iter); ds@1952: m_FreeTimers.push(pTimer); ds@1952: continue; ds@1952: } ds@1952: pTimer->m_InExec = false; ds@1952: pTimer->m_ToExec = CalcNextThink(pTimer->m_ToExec, pTimer->m_Interval); ds@1952: } ds@1952: iter++; ds@1952: } ds@1952: } ds@1952: ds@1952: ITimer *TimerSystem::CreateTimer(ITimedEvent *pCallbacks, float fInterval, void *pData, int flags) ds@1952: { ds@1952: ITimer *pTimer; ds@1952: TimerIter iter; ds@1952: float to_exec = GetSimulatedTime() + fInterval; ds@1952: ds@1952: if (m_FreeTimers.empty()) ds@1952: { ds@1952: pTimer = new ITimer; ds@1952: } else { ds@1952: pTimer = m_FreeTimers.front(); ds@1952: m_FreeTimers.pop(); ds@1952: } ds@1952: ds@1952: pTimer->Initialize(pCallbacks, fInterval, to_exec, pData, flags); ds@1952: ds@1952: if (flags & TIMER_FLAG_REPEAT) ds@1952: { ds@1952: m_LoopTimers.push_back(pTimer); ds@1952: goto return_timer; ds@1952: } ds@1952: ds@1952: if (m_SingleTimers.size() >= 1) ds@1952: { ds@1952: iter = --m_SingleTimers.end(); ds@1952: if ((*iter)->m_ToExec <= to_exec) ds@1952: { ds@1952: goto normal_insert_end; ds@1952: } ds@1952: } ds@1952: ds@1952: for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); iter++) ds@1952: { ds@1952: if ((*iter)->m_ToExec >= to_exec) ds@1952: { ds@1952: m_SingleTimers.insert(iter, pTimer); ds@1952: goto return_timer; ds@1952: } ds@1952: } ds@1952: ds@1952: normal_insert_end: ds@1952: m_SingleTimers.push_back(pTimer); ds@1952: ds@1952: return_timer: ds@1952: return pTimer; ds@1952: } ds@1952: ds@1952: void TimerSystem::FireTimerOnce(ITimer *pTimer, bool delayExec) ds@1952: { ds@1952: ResultType res; ds@1952: ds@1952: if (pTimer->m_InExec) ds@1952: { ds@1952: return; ds@1952: } ds@1952: ds@1952: pTimer->m_InExec = true; ds@1952: res = pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData); ds@1952: ds@1952: if (!(pTimer->m_Flags & TIMER_FLAG_REPEAT)) ds@1952: { ds@1952: pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData); ds@1952: m_SingleTimers.remove(pTimer); ds@1952: m_FreeTimers.push(pTimer); ds@1952: } ds@1952: else ds@1952: { ds@1952: if ((res != Pl_Stop) && !pTimer->m_KillMe) ds@1952: { ds@1952: if (delayExec) ds@1952: { ds@1952: pTimer->m_ToExec = GetSimulatedTime() + pTimer->m_Interval; ds@1952: } ds@1952: pTimer->m_InExec = false; ds@1952: return; ds@1952: } ds@1952: pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData); ds@1952: m_LoopTimers.remove(pTimer); ds@1952: m_FreeTimers.push(pTimer); ds@1952: } ds@1952: } ds@1952: ds@1952: void TimerSystem::KillTimer(ITimer *pTimer) ds@1952: { ds@1952: if (pTimer->m_KillMe) ds@1952: { ds@1952: return; ds@1952: } ds@1952: ds@1952: if (pTimer->m_InExec) ds@1952: { ds@1952: pTimer->m_KillMe = true; ds@1952: return; ds@1952: } ds@1952: ds@1952: pTimer->m_InExec = true; /* The timer it's not really executed but this check needs to be done */ ds@1952: pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData); ds@1952: ds@1952: if (pTimer->m_Flags & TIMER_FLAG_REPEAT) ds@1952: { ds@1952: m_LoopTimers.remove(pTimer); ds@1952: } else { ds@1952: m_SingleTimers.remove(pTimer); ds@1952: } ds@1952: ds@1952: m_FreeTimers.push(pTimer); ds@1952: } ds@1952: ds@1952: CStack s_tokill; ds@1952: void TimerSystem::RemoveMapChangeTimers() ds@1952: { ds@1952: ITimer *pTimer; ds@1952: TimerIter iter; ds@1952: ds@1952: for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); iter++) ds@1952: { ds@1952: pTimer = (*iter); ds@1952: if (pTimer->m_Flags & TIMER_FLAG_NO_MAPCHANGE) ds@1952: { ds@1952: s_tokill.push(pTimer); ds@1952: } ds@1952: } ds@1952: ds@1952: for (iter=m_LoopTimers.begin(); iter!=m_LoopTimers.end(); iter++) ds@1952: { ds@1952: pTimer = (*iter); ds@1952: if (pTimer->m_Flags & TIMER_FLAG_NO_MAPCHANGE) ds@1952: { ds@1952: s_tokill.push(pTimer); ds@1952: } ds@1952: } ds@1952: ds@1952: while (!s_tokill.empty()) ds@1952: { ds@1952: KillTimer(s_tokill.front()); ds@1952: s_tokill.pop(); ds@1952: } ds@1952: } ds@1952: ds@1952: IMapTimer *TimerSystem::SetMapTimer(IMapTimer *pTimer) ds@1952: { ds@1952: IMapTimer *old = m_pMapTimer; ds@1952: ds@1952: m_pMapTimer = pTimer; ds@1952: ds@1952: if (m_pMapTimer) ds@1952: { ds@1952: m_pMapTimer->SetMapTimerStatus(true); ds@1952: } ds@1952: ds@1952: if (old) ds@1952: { ds@1952: old->SetMapTimerStatus(false); ds@1952: } ds@1952: ds@1952: return old; ds@1952: } ds@1952: ds@1952: IMapTimer *TimerSystem::GetMapTimer() ds@1952: { ds@1952: return m_pMapTimer; ds@1952: } ds@1952: ds@1952: void TimerSystem::MapTimeLeftChanged() ds@1952: { ds@1952: m_pOnMapTimeLeftChanged->Execute(NULL); ds@1952: } ds@1952: ds@1952: void TimerSystem::NotifyOfGameStart(float offset) ds@1952: { ds@1952: g_fGameStartTime = gpGlobals->curtime + offset; ds@1952: } ds@1952: ds@1952: float TimerSystem::GetTickedTime() ds@1952: { ds@1952: return g_fUniversalTime; ds@1952: } ds@1952: ds@1952: bool TimerSystem::GetMapTimeLeft(float *time_left) ds@1952: { ds@1952: if (!m_pMapTimer) ds@1952: { ds@1952: return false; ds@1952: } ds@1952: ds@1952: int time_limit; ds@1952: if (!m_bHasMapSimulatedYet || (time_limit = m_pMapTimer->GetMapTimeLimit()) < 1) ds@1952: { ds@1952: *time_left = -1.0f; ds@1952: } ds@1952: else ds@1952: { ds@1952: *time_left = (g_fGameStartTime + time_limit * 60.0f) - gpGlobals->curtime; ds@1952: } ds@1952: ds@1952: return true; ds@1952: } ds@1952: