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