//========= Copyright Valve Corporation, All rights reserved. ============// #include "cbase.h" #include "rtime.h" #include "econ_holidays.h" //----------------------------------------------------------------------------- // Purpose: Interface that answers the simple question "on the passed-in time, // would this holiday be active?". Any caching of calculations is left // up to subclasses. //----------------------------------------------------------------------------- class IIsHolidayActive { public: IIsHolidayActive( const char *pszHolidayName ) : m_pszHolidayName( pszHolidayName ) { } virtual ~IIsHolidayActive ( ) { } virtual bool IsActive( const CRTime& timeCurrent ) = 0; const char *GetHolidayName() const { return m_pszHolidayName; } private: const char *m_pszHolidayName; }; //----------------------------------------------------------------------------- // Purpose: Always-disabled. Dummy event needed to map to slot zero for "disabled // holiday". //----------------------------------------------------------------------------- class CNoHoliday : public IIsHolidayActive { public: CNoHoliday() : IIsHolidayActive( "none" ) { } virtual bool IsActive( const CRTime& timeCurrent ) { return false; } }; //----------------------------------------------------------------------------- // Purpose: A holiday that lasts exactly one and only one day. //----------------------------------------------------------------------------- class CSingleDayHoliday : public IIsHolidayActive { public: CSingleDayHoliday( const char *pszName, int iMonth, int iDay ) : IIsHolidayActive( pszName ) , m_iMonth( iMonth ) , m_iDay( iDay ) { // } virtual bool IsActive( const CRTime& timeCurrent ) { return m_iMonth == timeCurrent.GetMonth() && m_iDay == timeCurrent.GetDayOfMonth(); } private: int m_iMonth; int m_iDay; }; //----------------------------------------------------------------------------- // Purpose: We want "week long" holidays to encompass at least two weekends, // so that players get plenty of time interacting with the holiday // features. //----------------------------------------------------------------------------- class CWeeksBasedHoliday : public IIsHolidayActive { public: CWeeksBasedHoliday( const char *pszName, int iMonth, int iDay, int iExtraWeeks ) : IIsHolidayActive( pszName ) , m_iMonth( iMonth ) , m_iDay( iDay ) , m_iExtraWeeks( iExtraWeeks ) , m_iCachedCalculatedYear( 0 ) { // We'll calculate the interval the first time we call IsActive(). } void RecalculateTimeActiveInterval( int iYear ) { // Get the date of the holiday. tm holiday_tm = { }; holiday_tm.tm_mday = m_iDay; holiday_tm.tm_mon = m_iMonth - 1; holiday_tm.tm_year = iYear - 1900; // convert to years since 1900 mktime( &holiday_tm ); // The event starts on the first Friday at least four days prior to the holiday. tm start_time_tm( holiday_tm ); start_time_tm.tm_mday -= 4; // Move back four days. mktime( &start_time_tm ); int days_offset = start_time_tm.tm_wday - kFriday; // Find the nearest prior Friday. if ( days_offset < 0 ) days_offset += 7; start_time_tm.tm_mday -= days_offset; time_t start_time = mktime( &start_time_tm ); // The event ends on the first Monday after the holiday, maybe plus some additional fudge // time. tm end_time_tm( holiday_tm ); days_offset = 7 - (end_time_tm.tm_wday - kMonday); if ( days_offset >= 7 ) days_offset -= 7; end_time_tm.tm_mday += days_offset + 7 * m_iExtraWeeks; time_t end_time = mktime( &end_time_tm ); #ifdef GC_DLL char rgchDateStartBuf[ 128 ]; BGetLocalFormattedDate( start_time, rgchDateStartBuf, sizeof( rgchDateStartBuf) ); char rgchDateEndBuf[ 128 ]; BGetLocalFormattedDate( end_time, rgchDateEndBuf, sizeof( rgchDateEndBuf ) ); EmitInfo( GCSDK::SPEW_GC, 4, LOG_ALWAYS, "Holiday - '%s' event starts on '%s' and ends on '%s'.\n", GetHolidayName(), rgchDateStartBuf, rgchDateEndBuf ); #endif // GC_DLL m_timeStart = start_time; m_timeEnd = end_time; // We're done and our interval data is cached. m_iCachedCalculatedYear = iYear; } virtual bool IsActive( const CRTime& timeCurrent ) { const int iCurrentYear = timeCurrent.GetYear(); if ( m_iCachedCalculatedYear != iCurrentYear ) RecalculateTimeActiveInterval( iCurrentYear ); return timeCurrent.GetRTime32() > m_timeStart && timeCurrent.GetRTime32() < m_timeEnd; } private: static const int kMonday = 1; static const int kFriday = 5; int m_iMonth; int m_iDay; int m_iExtraWeeks; // Filled out from RecalculateTimeActiveInterval(). int m_iCachedCalculatedYear; RTime32 m_timeStart; RTime32 m_timeEnd; }; //----------------------------------------------------------------------------- // Purpose: A holiday that repeats on a certain time interval, like "every N days" // or "once every two months" or, uh, "any time there's a full moon". //----------------------------------------------------------------------------- class CCyclicalHoliday : public IIsHolidayActive { public: CCyclicalHoliday( const char *pszName, int iMonth, int iDay, int iYear, float fCycleLengthInDays, float fBonusTimeInDays ) : IIsHolidayActive( pszName ) , m_fCycleLengthInDays( fCycleLengthInDays ) , m_fBonusTimeInDays( fBonusTimeInDays ) { // When is our initial interval? tm holiday_tm = { }; holiday_tm.tm_mday = iDay; holiday_tm.tm_mon = iMonth - 1; holiday_tm.tm_year = iYear - 1900; // convert to years since 1900 m_timeInitial = mktime( &holiday_tm ); } virtual bool IsActive( const CRTime& timeCurrent ) { // Days-to-seconds conversion. const int iSecondsPerDay = 24 * 60 * 60; // Convert our cycle/buffer times to seconds. const int iCycleLengthInSeconds = (int)(m_fCycleLengthInDays * iSecondsPerDay); const int iBufferTimeInSeconds = (int)(m_fBonusTimeInDays * iSecondsPerDay); // How long has it been since we started this cycle? int iSecondsIntoCycle = (timeCurrent.GetRTime32() - m_timeInitial) % iCycleLengthInSeconds; // If we're within the buffer period right after the start of a cycle, we're active. if ( iSecondsIntoCycle < iBufferTimeInSeconds ) return true; // If we're within the buffer period towards the end of a cycle, we're active. if ( iSecondsIntoCycle > iCycleLengthInSeconds - iBufferTimeInSeconds ) return true; // Alas, normal mode for us. return false; } private: time_t m_timeInitial ; float m_fCycleLengthInDays; float m_fBonusTimeInDays; }; //----------------------------------------------------------------------------- // Purpose: A pseudo-holiday that is active when either of its child holidays // is active. Works through pointers but does not manage memory. //----------------------------------------------------------------------------- class COrHoliday : public IIsHolidayActive { public: COrHoliday( const char *pszName, IIsHolidayActive *pA, IIsHolidayActive *pB ) : IIsHolidayActive( pszName ) , m_pA( pA ) , m_pB( pB ) { Assert( pA ); Assert( pB ); Assert( pA != pB ); } virtual bool IsActive( const CRTime& timeCurrent ) { return m_pA->IsActive( timeCurrent ) || m_pB->IsActive( timeCurrent ); } private: IIsHolidayActive *m_pA; IIsHolidayActive *m_pB; }; //----------------------------------------------------------------------------- // Purpose: Holiday that is defined by a start and end date //----------------------------------------------------------------------------- class CDateBasedHoliday : public IIsHolidayActive { public: CDateBasedHoliday( const char *pszName, const char *pszStartTime, const char *pszEndTime ) : IIsHolidayActive( pszName ) { m_rtStartTime = CRTime::RTime32FromString( pszStartTime ); m_rtEndTime = CRTime::RTime32FromString( pszEndTime ); } virtual bool IsActive( const CRTime& timeCurrent ) { return ( ( timeCurrent >= m_rtStartTime ) && ( timeCurrent <= m_rtEndTime ) ); } RTime32 GetEndRTime() const { return m_rtEndTime.GetRTime32(); } private: CRTime m_rtStartTime; CRTime m_rtEndTime; }; //----------------------------------------------------------------------------- // Purpose: Holiday that is defined by a start and end date with no year specified //----------------------------------------------------------------------------- class CDateBasedHolidayNoSpecificYear : public IIsHolidayActive { public: CDateBasedHolidayNoSpecificYear( const char *pszName, const char *pszStartTime, const char *pszEndTime ) : IIsHolidayActive( pszName ) , m_pszStartTime( pszStartTime ) , m_pszEndTime( pszEndTime ) , m_iCachedYear( -1 ) { } virtual bool IsActive( const CRTime& timeCurrent ) { const int iYear = timeCurrent.GetYear(); if ( iYear != m_iCachedYear ) { char m_szStartTime[k_RTimeRenderBufferSize]; char m_szEndTime[k_RTimeRenderBufferSize]; V_sprintf_safe( m_szStartTime, "%d-%s", iYear, m_pszStartTime ); V_sprintf_safe( m_szEndTime, "%d-%s", iYear, m_pszEndTime ); m_iCachedYear = iYear; m_rtCachedStartTime = CRTime::RTime32FromString( m_szStartTime ); m_rtCachedEndTime = CRTime::RTime32FromString( m_szEndTime ); } return ( ( timeCurrent >= m_rtCachedStartTime ) && ( timeCurrent <= m_rtCachedEndTime ) ); } private: const char *m_pszStartTime; const char *m_pszEndTime; int m_iCachedYear; CRTime m_rtCachedStartTime; CRTime m_rtCachedEndTime; }; //----------------------------------------------------------------------------- // Purpose: Actual holiday implementation objects. //----------------------------------------------------------------------------- static CNoHoliday g_Holiday_NoHoliday; static CDateBasedHolidayNoSpecificYear g_Holiday_TF2Birthday ( "birthday", "08-23", "08-25" ); static CDateBasedHoliday g_Holiday_Halloween ( "halloween", "2016-10-19", "2016-11-18" ); static CDateBasedHoliday g_Holiday_Christmas ( "christmas", "2016-11-28", "2017-01-12" ); static CDateBasedHolidayNoSpecificYear g_Holiday_ValentinesDay ( "valentines", "02-13", "02-15" ); static CDateBasedHoliday g_Holiday_MeetThePyro ( "meet_the_pyro", "2012-06-26", "2012-07-05" ); /* starting date cycle length in days bonus time in days on both sides */ static CCyclicalHoliday g_Holiday_FullMoon ( "fullmoon", 5, 21, 2016, 29.53f, 1.0f ); // note: the cycle length is 29.5 instead of 29.53 so that the time calculations always start at noon based on the way CCyclicalHoliday works static COrHoliday g_Holiday_HalloweenOrFullMoon ( "halloween_or_fullmoon", &g_Holiday_Halloween, &g_Holiday_FullMoon ); static COrHoliday g_Holiday_HalloweenOrFullMoonOrValentines ( "halloween_or_fullmoon_or_valentines", &g_Holiday_HalloweenOrFullMoon, &g_Holiday_ValentinesDay ); static CDateBasedHolidayNoSpecificYear g_Holiday_AprilFools ( "april_fools", "03-31", "04-02" ); static CDateBasedHoliday g_Holiday_EndOfTheLine ( "eotl_launch", "2014-12-03", "2015-01-05" ); static CDateBasedHoliday g_Holiday_CommunityUpdate ( "community_update", "2015-09-01", "2015-11-05" ); // ORDER NEEDS TO MATCH enum EHoliday static IIsHolidayActive *s_HolidayChecks[] = { &g_Holiday_NoHoliday, // kHoliday_None &g_Holiday_TF2Birthday, // kHoliday_TFBirthday &g_Holiday_Halloween, // kHoliday_Halloween &g_Holiday_Christmas, // kHoliday_Christmas &g_Holiday_CommunityUpdate, // kHoliday_CommunityUpdate &g_Holiday_EndOfTheLine, // kHoliday_EOTL &g_Holiday_ValentinesDay, // kHoliday_Valentines &g_Holiday_MeetThePyro, // kHoliday_MeetThePyro &g_Holiday_FullMoon, // kHoliday_FullMoon &g_Holiday_HalloweenOrFullMoon, // kHoliday_HalloweenOrFullMoon &g_Holiday_HalloweenOrFullMoonOrValentines, // kHoliday_HalloweenOrFullMoonOrValentines &g_Holiday_AprilFools, // kHoliday_AprilFools }; COMPILE_TIME_ASSERT( ARRAYSIZE( s_HolidayChecks ) == kHolidayCount ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool EconHolidays_IsHolidayActive( int iHolidayIndex, const CRTime& timeCurrent ) { if ( iHolidayIndex < 0 || iHolidayIndex >= kHolidayCount ) return false; Assert( s_HolidayChecks[iHolidayIndex] ); if ( !s_HolidayChecks[iHolidayIndex] ) return false; return s_HolidayChecks[iHolidayIndex]->IsActive( timeCurrent ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int EconHolidays_GetHolidayForString( const char* pszHolidayName ) { for ( int iHoliday = 0; iHoliday < kHolidayCount; ++iHoliday ) { Assert( s_HolidayChecks[iHoliday] ); if ( s_HolidayChecks[iHoliday] && 0 == Q_stricmp( pszHolidayName, s_HolidayChecks[iHoliday]->GetHolidayName() ) ) { return iHoliday; } } return kHoliday_None; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *EconHolidays_GetActiveHolidayString() { CRTime timeNow; timeNow.SetToCurrentTime(); timeNow.SetToGMT( true ); for ( int iHoliday = 0; iHoliday < kHolidayCount; iHoliday++ ) { if ( EconHolidays_IsHolidayActive( iHoliday, timeNow ) ) { Assert( s_HolidayChecks[iHoliday] ); return s_HolidayChecks[iHoliday]->GetHolidayName(); } } // No holidays currently active. return NULL; } #if defined(TF_CLIENT_DLL) || defined(TF_DLL) || defined(TF_GC_DLL) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- RTime32 EconHolidays_TerribleHack_GetHalloweenEndData() { return g_Holiday_Halloween.GetEndRTime(); } #endif // defined(TF_CLIENT_DLL) || defined(TF_DLL) || defined(TF_GC_DLL)