Compare commits

..

13 Commits

Author SHA1 Message Date
Tristan 'Natrist' Cormier
33c440e523
Merge a82628adaa into 0bb3ac157f 2026-01-29 09:47:59 -05:00
fallenoak
0bb3ac157f
feat(client): implement ReceiveNewTimeSpeed 2026-01-29 08:47:19 -06:00
fallenoak
53b26169f3
feat(util): add CGameTime::GameTimeSetMinutesPerSecond 2026-01-29 08:46:57 -06:00
fallenoak
6e7c267d3e
feat(client): add ClientInitializeGameTime 2026-01-29 07:52:43 -06:00
fallenoak
50a24e8564
feat(ui): implement Script_GetGameTime 2026-01-29 07:37:43 -06:00
fallenoak
efd3f8e8f4
feat(client): add ClientGameTimeTickHandler 2026-01-29 07:33:50 -06:00
fallenoak
721ee527eb
feat(util): add CGameTime::GameTimeUpdate
Some checks are pending
Push / ${{ matrix.build.system_name }} / ${{ matrix.build.build_type }} / ${{ matrix.build.compiler_name }} (map[build_type:Release cc:cl compiler_name:MSVC cxx:cl os:windows-latest system_name:Windows test_path:WhoaTest]) (push) Waiting to run
Push / ${{ matrix.build.system_name }} / ${{ matrix.build.build_type }} / ${{ matrix.build.compiler_name }} (map[build_type:Release cc:clang compiler_name:Clang cxx:clang++ os:macos-latest system_name:macOS test_path:WhoaTest]) (push) Waiting to run
Push / ${{ matrix.build.system_name }} / ${{ matrix.build.build_type }} / ${{ matrix.build.compiler_name }} (map[build_type:Release cc:gcc compiler_name:GCC cxx:g++ os:ubuntu-latest system_name:Linux test_path:WhoaTest]) (push) Waiting to run
2026-01-28 22:01:23 -06:00
fallenoak
886ababae9
feat(util): add CGameTime::GameTimeSetTime
Some checks are pending
Push / ${{ matrix.build.system_name }} / ${{ matrix.build.build_type }} / ${{ matrix.build.compiler_name }} (map[build_type:Release cc:cl compiler_name:MSVC cxx:cl os:windows-latest system_name:Windows test_path:WhoaTest]) (push) Waiting to run
Push / ${{ matrix.build.system_name }} / ${{ matrix.build.build_type }} / ${{ matrix.build.compiler_name }} (map[build_type:Release cc:clang compiler_name:Clang cxx:clang++ os:macos-latest system_name:macOS test_path:WhoaTest]) (push) Waiting to run
Push / ${{ matrix.build.system_name }} / ${{ matrix.build.build_type }} / ${{ matrix.build.compiler_name }} (map[build_type:Release cc:gcc compiler_name:GCC cxx:g++ os:ubuntu-latest system_name:Linux test_path:WhoaTest]) (push) Waiting to run
2026-01-28 21:31:24 -06:00
fallenoak
1d91a49462
feat(util): add initial implementation of CGameTime 2026-01-28 20:39:49 -06:00
fallenoak
aed2aebb66
feat(util): add WowTime::AddDays 2026-01-28 20:13:11 -06:00
fallenoak
661b77091f
feat(util): add WowTime::SetHourAndMinute overload 2026-01-28 18:56:12 -06:00
fallenoak
335bd21a26
feat(util): add WowTime::SetHourAndMinutes 2026-01-28 18:50:12 -06:00
fallenoak
bfcceed8fd
feat(util): add initial implementation of WowTime 2026-01-28 15:44:42 -06:00
14 changed files with 766 additions and 8 deletions

View File

@ -30,6 +30,8 @@ CVar* Client::g_accountNameVar;
CVar* Client::g_accountListVar; CVar* Client::g_accountListVar;
HEVENTCONTEXT Client::g_clientEventContext; HEVENTCONTEXT Client::g_clientEventContext;
CGameTime g_clientGameTime;
static CVar* s_desktopGammaCvar; static CVar* s_desktopGammaCvar;
static CVar* s_gammaCvar; static CVar* s_gammaCvar;
static CVar* s_textureCacheSizeCvar; static CVar* s_textureCacheSizeCvar;
@ -70,10 +72,28 @@ void BaseInitializeGlobal() {
PropInitialize(); PropInitialize();
} }
int32_t ClientGameTimeTickHandler(const void* data, void* param) {
STORM_ASSERT(data);
g_clientGameTime.GameTimeUpdate(static_cast<const EVENT_DATA_IDLE*>(data)->elapsedSec);
return 1;
}
void ClientInitializeGameTime() {
ClientServices::SetMessageHandler(SMSG_GAME_SPEED_SET, &ReceiveNewGameSpeed, nullptr);
ClientServices::SetMessageHandler(SMSG_LOGIN_SET_TIME_SPEED, &ReceiveNewTimeSpeed, nullptr);
ClientServices::SetMessageHandler(SMSG_GAME_TIME_UPDATE, &ReceiveGameTimeUpdate, nullptr);
ClientServices::SetMessageHandler(SMSG_SERVERTIME, &ReceiveServerTime, nullptr);
ClientServices::SetMessageHandler(SMSG_GAME_TIME_SET, &ReceiveNewGameTime, nullptr);
// TODO initialize s_forcedChangeCallbacks
}
int32_t ClientIdle(const void* data, void* param) { int32_t ClientIdle(const void* data, void* param) {
// TODO ClientGameTimeTickHandler(data, nullptr);
// ClientGameTimeTickHandler(data, param);
// Player_C_ZoneUpdateHandler(data, param); // TODO Player_C_ZoneUpdateHandler(data, nullptr);
return 1; return 1;
} }
@ -91,6 +111,7 @@ void ClientInitializeGame(uint32_t mapId, C3Vector position) {
// TODO // TODO
EventRegister(EVENT_ID_IDLE, ClientIdle); EventRegister(EVENT_ID_IDLE, ClientIdle);
ClientInitializeGameTime();
// TODO // TODO

View File

@ -2,6 +2,7 @@
#define CLIENT_CLIENT_HPP #define CLIENT_CLIENT_HPP
#include "event/Event.hpp" #include "event/Event.hpp"
#include "util/Time.hpp"
#include <tempest/Vector.hpp> #include <tempest/Vector.hpp>
class CVar; class CVar;
@ -12,6 +13,8 @@ namespace Client {
extern HEVENTCONTEXT g_clientEventContext; extern HEVENTCONTEXT g_clientEventContext;
} }
extern CGameTime g_clientGameTime;
void ClientInitializeGame(uint32_t mapId, C3Vector position); void ClientInitializeGame(uint32_t mapId, C3Vector position);
void ClientPostClose(int32_t a1); void ClientPostClose(int32_t a1);

View File

@ -1,11 +1,14 @@
#include "client/ClientHandlers.hpp" #include "client/ClientHandlers.hpp"
#include "Client.hpp"
#include "console/Console.hpp" #include "console/Console.hpp"
#include "db/Db.hpp" #include "db/Db.hpp"
#include "object/Client.hpp" #include "object/Client.hpp"
#include "util/Time.hpp"
#include "util/Unimplemented.hpp"
#include "world/World.hpp" #include "world/World.hpp"
#include <common/DataStore.hpp> #include <common/DataStore.hpp>
#include <tempest/Vector.hpp>
#include <cstdint> #include <cstdint>
#include <tempest/Vector.hpp>
static float s_newFacing; static float s_newFacing;
static C3Vector s_newPosition; static C3Vector s_newPosition;
@ -77,6 +80,56 @@ int32_t PlayedTimeHandler(void* param, NETMESSAGE msgId, uint32_t time, CDataSto
return 0; return 0;
} }
int32_t ReceiveGameTimeUpdate(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg) {
WHOA_UNIMPLEMENTED(0);
}
int32_t ReceiveNewGameSpeed(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg) {
WHOA_UNIMPLEMENTED(0);
}
int32_t ReceiveNewGameTime(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg) {
WHOA_UNIMPLEMENTED(0);
}
int32_t ReceiveNewTimeSpeed(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg) {
uint32_t encodedTime;
msg->Get(encodedTime);
float newSpeed;
msg->Get(newSpeed);
uint32_t holidayOffset;
msg->Get(holidayOffset);
if (!msg->IsRead()) {
STORM_ASSERT(msg->IsFinal());
// TODO ConsoleWriteA("Malformed message received: Id = %d, Len = %d, Read = %d\n", DEFAULT_COLOR, msgId, msg->Size(), msg->Tell());
return 0;
}
WowTime newTime;
WowTime::WowDecodeTime(encodedTime, &newTime);
newTime.m_holidayOffset = holidayOffset;
g_clientGameTime.GameTimeSetTime(newTime, true);
// TODO UpdateTime();
auto oldSpeed = g_clientGameTime.GameTimeSetMinutesPerSecond(newSpeed);
char logStr[256];
SStrPrintf(logStr, sizeof(logStr), "Gamespeed set from %.03f to %.03f", oldSpeed, newSpeed);
ConsoleWrite(logStr, DEFAULT_COLOR);
return 1;
}
int32_t ReceiveServerTime(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg) {
WHOA_UNIMPLEMENTED(0);
}
int32_t TransferAbortedHandler(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg) { int32_t TransferAbortedHandler(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg) {
// TODO // TODO

View File

@ -14,6 +14,16 @@ int32_t NotifyHandler(void* param, NETMESSAGE msgId, uint32_t time, CDataStore*
int32_t PlayedTimeHandler(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg); int32_t PlayedTimeHandler(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg);
int32_t ReceiveGameTimeUpdate(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg);
int32_t ReceiveNewGameSpeed(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg);
int32_t ReceiveNewGameTime(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg);
int32_t ReceiveNewTimeSpeed(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg);
int32_t ReceiveServerTime(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg);
int32_t TransferAbortedHandler(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg); int32_t TransferAbortedHandler(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg);
int32_t TransferPendingHandler(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg); int32_t TransferPendingHandler(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg);

View File

@ -1,4 +1,5 @@
#include "ui/FrameScript.hpp" #include "ui/FrameScript.hpp"
#include "client/Client.hpp"
#include "ui/ScriptFunctionsShared.hpp" #include "ui/ScriptFunctionsShared.hpp"
#include "ui/Types.hpp" #include "ui/Types.hpp"
#include "util/Lua.hpp" #include "util/Lua.hpp"
@ -14,10 +15,10 @@ int32_t Script_GetTime(lua_State* L) {
} }
int32_t Script_GetGameTime(lua_State* L) { int32_t Script_GetGameTime(lua_State* L) {
// TODO real implementation lua_pushnumber(L, g_clientGameTime.m_hour);
lua_pushnumber(L, 1.0); lua_pushnumber(L, g_clientGameTime.m_minute);
lua_pushnumber(L, 15.0);
WHOA_UNIMPLEMENTED(2); return 2;
} }
int32_t Script_ConsoleExec(lua_State* L) { int32_t Script_ConsoleExec(lua_State* L) {

View File

@ -1,6 +1,7 @@
file(GLOB PRIVATE_SOURCES file(GLOB PRIVATE_SOURCES
"*.cpp" "*.cpp"
"guid/*.cpp" "guid/*.cpp"
"time/*.cpp"
) )
if(WHOA_SYSTEM_MAC) if(WHOA_SYSTEM_MAC)

7
src/util/Time.hpp Normal file
View File

@ -0,0 +1,7 @@
#ifndef UTIL_TIME_HPP
#define UTIL_TIME_HPP
#include "util/time/CGameTime.hpp"
#include "util/time/WowTime.hpp"
#endif

106
src/util/time/CGameTime.cpp Normal file
View File

@ -0,0 +1,106 @@
#include "util/time/CGameTime.hpp"
#include "common/Time.hpp"
float CGameTime::GameTimeSetMinutesPerSecond(float minutesPerSec) {
float oldMinutesPerSec = this->m_gameMinutesPerRealSecond;
if (minutesPerSec > CGameTime::MAX_SPEED) {
minutesPerSec = CGameTime::MAX_SPEED;
} else if (minutesPerSec < CGameTime::MIN_SPEED) {
minutesPerSec = CGameTime::MIN_SPEED;
}
this->m_gameMinutesPerRealSecond = minutesPerSec;
return oldMinutesPerSec;
}
void CGameTime::GameTimeSetTime(const WowTime& time, bool shouldTick) {
WowTime biasTime = time;
if (this->m_timeBias) {
auto minutes = biasTime.GetHourAndMinutes() + this->m_timeBias;
if (minutes < 0) {
minutes += 1440;
} else {
minutes %= 1440;
}
biasTime.SetHourAndMinutes(minutes);
}
if (this->m_dateBias) {
biasTime.AddDays(this->m_dateBias, false);
}
static_cast<WowTime&>(*this) = biasTime;
if (shouldTick) {
// Rewind time by exactly one minute
if (this->m_minute != 0) {
this->m_minute--;
} else {
this->m_minute = 59;
if (this->m_hour != 0) {
this->m_hour--;
} else {
this->m_hour = 23;
this->AddDays(-1, false);
}
}
// Tick ahead exactly one minute (ensures callbacks fire and various counters update)
this->TickMinute();
}
}
void CGameTime::GameTimeUpdate(float elapsedSec) {
this->m_gameMinutesThisTick += this->m_gameMinutesPerRealSecond * elapsedSec;
// Skip minute ticks for time differential
if (this->m_timeDifferential != 0 && this->m_gameMinutesThisTick >= 1.0f) {
auto minutesToConsume = static_cast<uint32_t>(this->m_gameMinutesThisTick);
// Clamp to differential
if (this->m_timeDifferential < minutesToConsume) {
minutesToConsume = this->m_timeDifferential;
}
this->m_timeDifferential -= minutesToConsume;
this->m_gameMinutesThisTick -= static_cast<float>(minutesToConsume);
}
while (this->m_gameMinutesThisTick >= 1.0f) {
this->m_gameMinutesThisTick -= 1.0f;
this->TickMinute();
}
}
void CGameTime::PerformCallbacks(int32_t minutes) {
// TODO
}
void CGameTime::TickMinute() {
// Increment minute
int32_t minutes = (this->GetHourAndMinutes() + 1) % 1440;
// Update hours and minutes
this->SetHourAndMinutes(minutes);
// Increment day if minute crossed day boundary
if (minutes == 0) {
this->AddDays(1, false);
}
this->m_gameMinutesElapsed++;
this->PerformCallbacks(minutes);
this->m_lastTickMinute = OsGetAsyncTimeMsPrecise();
this->uint40 = 1;
this->m_dayProgression = this->m_gameMinutesThisTick + static_cast<float>(minutes);
}

View File

@ -0,0 +1,38 @@
#ifndef UTIL_TIME_C_GAME_TIME_HPP
#define UTIL_TIME_C_GAME_TIME_HPP
#include "util/time/WowTime.hpp"
class CGameTime : public WowTime {
public:
// Public static variables
static constexpr float MIN_SPEED = 1.0f / 60.0f;
static constexpr float MAX_SPEED = 60.0f;
// Public member functions
float GameTimeSetMinutesPerSecond(float minutesPerSec);
void GameTimeSetTime(const WowTime& time, bool shouldTick);
void GameTimeUpdate(float elapsedSec);
private:
// Private member variables
uint32_t m_lastTick = 0;
int32_t m_timeBias = 0;
int32_t m_dateBias = 0;
uint32_t m_gameMinutesElapsed = 0;
float m_gameMinutesPerRealSecond = MIN_SPEED;
float m_gameMinutesThisTick = 0.0f;
uint32_t m_timeDifferential = 0;
uint32_t m_lastTickMinute = 0;
uint8_t uint40 = 0;
float m_dayProgression = 0.0f;
float float48 = 0.0f;
float float4C = 0.0f;
// TODO m_callbackLists
// Private member functions
void PerformCallbacks(int32_t minutes);
void TickMinute();
};
#endif

236
src/util/time/WowTime.cpp Normal file
View File

@ -0,0 +1,236 @@
#include "util/time/WowTime.hpp"
#include <storm/Error.hpp>
#include <storm/String.hpp>
#include <ctime>
static const char* s_weekdays[] = {
"Sun",
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
};
void WowTime::WowDecodeTime(uint32_t value, int32_t* minute, int32_t* hour, int32_t* weekday, int32_t* monthday, int32_t* month, int32_t* year, int32_t* flags) {
// Minute: bits 0-5 (6 bits, max 63)
if (minute) {
auto m = static_cast<int32_t>(value & 63);
*minute = (m == 63) ? -1 : m;
}
// Hour: bits 6-10 (5 bits, max 31)
if (hour) {
auto h = static_cast<int32_t>((value >> 6) & 31);
*hour = (h == 31) ? -1 : h;
}
// Weekday: bits 11-13 (3 bits, max 7)
if (weekday) {
auto wd = static_cast<int32_t>((value >> 11) & 7);
*weekday = (wd == 7) ? -1 : wd;
}
// Month day: bits 14-19 (6 bits, max 63)
if (monthday) {
auto md = static_cast<int32_t>((value >> 14) & 63);
*monthday = (md == 63) ? -1 : md;
}
// Month: bits 20-23 (4 bits, max 15)
if (month) {
auto mo = static_cast<int32_t>((value >> 20) & 15);
*month = (mo == 15) ? -1 : mo;
}
// Year: bits 24-28 (5 bits, max 31)
if (year) {
auto y = static_cast<int32_t>((value >> 24) & 31);
*year = (y == 31) ? -1 : y;
}
// Flags: bits 29-30 (2 bits, max 3)
if (flags) {
auto f = static_cast<int32_t>((value >> 29) & 3);
*flags = (f == 3) ? -1 : f;
}
}
void WowTime::WowDecodeTime(uint32_t value, WowTime* time) {
WowTime::WowDecodeTime(
value,
&time->m_minute,
&time->m_hour,
&time->m_weekday,
&time->m_monthday,
&time->m_month,
&time->m_year,
&time->m_flags
);
}
void WowTime::WowEncodeTime(uint32_t& value, int32_t minute, int32_t hour, int32_t weekday, int32_t monthday, int32_t month, int32_t year, int32_t flags) {
STORM_ASSERT(minute == -1 || (minute >= 0 && minute < 60));
STORM_ASSERT(hour == -1 || (hour >= 0 && hour < 24));
STORM_ASSERT(weekday == -1 || (weekday >= 0 && weekday < 7));
STORM_ASSERT(monthday == -1 || (monthday >= 0 && monthday < 32));
STORM_ASSERT(month == -1 || (month >= 0 && month < 12));
STORM_ASSERT(year == -1 || year >= 0 && year <= ((1 << 5) - 1));
STORM_ASSERT(flags >= 0 && flags <= ((1 << 2) - 1));
value = ((flags & 3) << 29) // Flags: bits 29-30 (2 bits, max 3)
| ((year & 31) << 24) // Year: bits 24-28 (5 bits, max 31)
| ((month & 15) << 20) // Month: bits 20-23 (4 bits, max 15)
| ((monthday & 63) << 14) // Month day: bits 14-19 (6 bits, max 63)
| ((weekday & 7) << 11) // Weekday: bits 11-13 (3 bits, max 7)
| ((hour & 31) << 6) // Hour: bits 6-10 (5 bits, max 31)
| (minute & 63); // Minute: bits 0-5 (6 bits, max 63)
}
void WowTime::WowEncodeTime(uint32_t& value, const WowTime* time) {
WowTime::WowEncodeTime(
value,
time->m_minute,
time->m_hour,
time->m_weekday,
time->m_monthday,
time->m_month,
time->m_year,
time->m_flags
);
}
char* WowTime::WowGetTimeString(WowTime* time, char* str, int32_t len) {
uint32_t encoded;
WowTime::WowEncodeTime(encoded, time);
if (encoded == 0) {
SStrPrintf(str, len, "Not Set");
return str;
}
char yearStr[8];
char monthStr[8];
char monthdayStr[8];
char weekdayStr[8];
char hourStr[8];
char minuteStr[8];
if (time->m_year >= 0) {
SStrPrintf(yearStr, sizeof(yearStr), "%i", time->m_year + 2000);
} else {
SStrPrintf(yearStr, sizeof(yearStr), "A");
}
if (time->m_month >= 0) {
SStrPrintf(monthStr, sizeof(monthStr), "%i", time->m_month + 1);
} else {
SStrPrintf(monthStr, sizeof(monthStr), "A");
}
if (time->m_monthday >= 0) {
SStrPrintf(monthdayStr, sizeof(monthdayStr), "%i", time->m_monthday + 1);
} else {
SStrPrintf(monthdayStr, sizeof(monthdayStr), "A");
}
if (time->m_weekday >= 0) {
SStrPrintf(weekdayStr, sizeof(weekdayStr), s_weekdays[time->m_weekday]);
} else {
SStrPrintf(weekdayStr, sizeof(weekdayStr), "Any");
}
if (time->m_hour >= 0) {
SStrPrintf(hourStr, sizeof(hourStr), "%i", time->m_hour);
} else {
SStrPrintf(hourStr, sizeof(hourStr), "A");
}
if (time->m_minute >= 0) {
SStrPrintf(minuteStr, sizeof(minuteStr), "%2.2i", time->m_minute);
} else {
SStrPrintf(minuteStr, sizeof(minuteStr), "A");
}
SStrPrintf(str, len, "%s/%s/%s (%s) %s:%s", monthStr, monthdayStr, yearStr, weekdayStr, hourStr, minuteStr);
return str;
}
void WowTime::AddDays(int32_t days, bool includeTime) {
// Validate date
if (this->m_year < 0 || this->m_month < 0 || this->m_monthday < 0) {
return;
}
// Convert WowTime to tm
tm t = {};
t.tm_year = this->m_year + 100; // WowTime year is years since 2000; tm_year is years since 1900
t.tm_mon = this->m_month; // WowTime month and tm_mon are both 0-based
t.tm_mday = this->m_monthday + 1; // WowTime monthday is 0-based; tm_mday is 1-based
t.tm_isdst = -1; // Let mktime determine DST
if (includeTime) {
t.tm_hour = this->m_hour;
t.tm_min = this->m_minute;
}
// Convert tm to time_t and add the specified days
auto time = mktime(&t);
time += days * 86400;
if (!includeTime) {
time += 3600; // Tack on hour to ensure DST boundaries don't muck with days added
}
// Convert adjusted time back to tm
auto t_ = localtime(&time);
if (t_) {
t = *t_;
} else {
t = {};
}
// Convert adjusted tm back to WowTime
this->m_year = t.tm_year - 100;
this->m_month = t.tm_mon;
this->m_monthday = t.tm_mday - 1;
this->m_weekday = t.tm_wday;
if (includeTime) {
this->m_hour = t.tm_hour;
this->m_minute = t.tm_min;
}
}
int32_t WowTime::GetHourAndMinutes() {
if (this->m_hour < 0 || this->m_minute < 0) {
return 0;
}
return this->m_hour * 60 + this->m_minute;
}
void WowTime::SetHourAndMinutes(int32_t minutes) {
this->m_hour = minutes / 60;
this->m_minute = minutes % 60;
}
int32_t WowTime::SetHourAndMinutes(uint32_t hour, uint32_t minutes) {
if (hour >= 24 || minutes >= 60) {
return 0;
}
this->m_hour = hour;
this->m_minute = minutes;
return 1;
}

32
src/util/time/WowTime.hpp Normal file
View File

@ -0,0 +1,32 @@
#ifndef UTIL_TIME_WOW_TIME_HPP
#define UTIL_TIME_WOW_TIME_HPP
#include <cstdint>
class WowTime {
public:
// Static functions
static void WowDecodeTime(uint32_t value, int32_t* minute, int32_t* hour, int32_t* weekday, int32_t* monthday, int32_t* month, int32_t* year, int32_t* flags);
static void WowDecodeTime(uint32_t value, WowTime* time);
static void WowEncodeTime(uint32_t& value, int32_t minute, int32_t hour, int32_t weekday, int32_t monthday, int32_t month, int32_t year, int32_t flags);
static void WowEncodeTime(uint32_t& value, const WowTime* time);
static char* WowGetTimeString(WowTime* time, char* str, int32_t len);
// Member variables
int32_t m_minute = -1;
int32_t m_hour = -1;
int32_t m_weekday = -1;
int32_t m_monthday = -1;
int32_t m_month = -1;
int32_t m_year = -1;
int32_t m_flags = 0x0;
int32_t m_holidayOffset = 0;
// Member functions
void AddDays(int32_t days, bool includeTime);
int32_t GetHourAndMinutes();
void SetHourAndMinutes(int32_t minutes);
int32_t SetHourAndMinutes(uint32_t hour, uint32_t minutes);
};
#endif

View File

@ -5,6 +5,7 @@ if(WHOA_SYSTEM_MAC)
"gx/*.cpp" "gx/*.cpp"
"gx/font/*.cpp" "gx/font/*.cpp"
"util/*.cpp" "util/*.cpp"
"util/time/*.cpp"
) )
set_source_files_properties(${PRIVATE_SOURCES} set_source_files_properties(${PRIVATE_SOURCES}

View File

@ -0,0 +1,59 @@
#include "util/time/CGameTime.hpp"
#include "catch.hpp"
TEST_CASE("CGameTime::CGameTime", "[util]") {
SECTION("constructs correctly") {
CGameTime time;
CHECK(time.m_minute == -1);
CHECK(time.m_hour == -1);
CHECK(time.m_weekday == -1);
CHECK(time.m_monthday == -1);
CHECK(time.m_month == -1);
CHECK(time.m_year == -1);
CHECK(time.m_flags == 0x0);
CHECK(time.m_holidayOffset == 0);
}
}
TEST_CASE("CGameTime::GameTimeSetTime", "[util]") {
SECTION("sets game time correctly") {
WowTime time;
time.m_minute = 18;
time.m_hour = 11;
time.m_weekday = 3;
time.m_monthday = 27;
time.m_month = 0;
time.m_year = 26;
CGameTime gameTime;
gameTime.GameTimeSetTime(time, true);
CHECK(gameTime.m_minute == 18);
CHECK(gameTime.m_hour == 11);
CHECK(gameTime.m_weekday == 3);
CHECK(gameTime.m_monthday == 27);
CHECK(gameTime.m_month == 0);
CHECK(gameTime.m_year == 26);
}
}
TEST_CASE("CGameTime::GameTimeUpdate", "[util]") {
SECTION("updates game time correctly") {
WowTime time;
time.m_minute = 18;
time.m_hour = 11;
time.m_weekday = 3;
time.m_monthday = 27;
time.m_month = 0;
time.m_year = 26;
CGameTime gameTime;
gameTime.GameTimeSetTime(time, true);
gameTime.GameTimeUpdate(60.0f);
CHECK(gameTime.m_hour == 11);
CHECK(gameTime.m_minute == 19);
}
}

190
test/util/time/WowTime.cpp Normal file
View File

@ -0,0 +1,190 @@
#include "util/time/WowTime.hpp"
#include "catch.hpp"
#include "storm/String.hpp"
TEST_CASE("WowTime::WowTime", "[util]") {
SECTION("constructs correctly") {
WowTime time;
CHECK(time.m_minute == -1);
CHECK(time.m_hour == -1);
CHECK(time.m_weekday == -1);
CHECK(time.m_monthday == -1);
CHECK(time.m_month == -1);
CHECK(time.m_year == -1);
CHECK(time.m_flags == 0x0);
CHECK(time.m_holidayOffset == 0);
}
}
TEST_CASE("WowTime::WowDecodeTime", "[util]") {
SECTION("decodes 1234567890 as expected") {
uint32_t value = 1234567890;
WowTime time;
WowTime::WowDecodeTime(value, &time.m_minute, &time.m_hour, &time.m_weekday, &time.m_monthday, &time.m_month, &time.m_year, &time.m_flags);
CHECK(time.m_minute == 18);
CHECK(time.m_hour == 11);
CHECK(time.m_weekday == 0);
CHECK(time.m_monthday == 24);
CHECK(time.m_month == 9);
CHECK(time.m_year == 9);
CHECK(time.m_flags == 0x2);
}
SECTION("decodes 0xFFFFFFFF as expected") {
uint32_t value = 0xFFFFFFFF;
WowTime time;
WowTime::WowDecodeTime(value, &time.m_minute, &time.m_hour, &time.m_weekday, &time.m_monthday, &time.m_month, &time.m_year, &time.m_flags);
CHECK(time.m_minute == -1);
CHECK(time.m_hour == -1);
CHECK(time.m_weekday == -1);
CHECK(time.m_monthday == -1);
CHECK(time.m_month == -1);
CHECK(time.m_year == -1);
CHECK(time.m_flags == -1);
}
}
TEST_CASE("WowTime::WowEncodeTime", "[util]") {
SECTION("encodes 10/25/2009 (Sun) 11:18 with flag 0x2 set as expected") {
uint32_t value = 0;
WowTime time;
time.m_minute = 18;
time.m_hour = 11;
time.m_weekday = 0;
time.m_monthday = 24;
time.m_month = 9;
time.m_year = 9;
time.m_flags = 0x2;
WowTime::WowEncodeTime(value, &time);
REQUIRE(value == 1234567890);
}
SECTION("encodes empty time as expected") {
uint32_t value = 0;
WowTime time;
WowTime::WowEncodeTime(value, &time);
REQUIRE(value == 0x1FFFFFFF);
}
SECTION("encodes, decodes, and reencodes as expected") {
uint32_t value = 0;
WowTime time1;
time1.m_minute = 18;
time1.m_hour = 11;
time1.m_weekday = 0;
time1.m_monthday = 24;
time1.m_month = 9;
time1.m_year = 9;
time1.m_flags = 0x2;
WowTime time2;
CHECK(time2.m_minute != time1.m_minute);
CHECK(time2.m_hour != time1.m_hour);
CHECK(time2.m_weekday != time1.m_weekday);
CHECK(time2.m_monthday != time1.m_monthday);
CHECK(time2.m_month != time1.m_month);
CHECK(time2.m_year != time1.m_year);
CHECK(time2.m_flags != time1.m_flags);
WowTime::WowEncodeTime(value, &time1);
WowTime::WowDecodeTime(value, &time2);
CHECK(time2.m_minute == time1.m_minute);
CHECK(time2.m_hour == time1.m_hour);
CHECK(time2.m_weekday == time1.m_weekday);
CHECK(time2.m_monthday == time1.m_monthday);
CHECK(time2.m_month == time1.m_month);
CHECK(time2.m_year == time1.m_year);
CHECK(time2.m_flags == time1.m_flags);
}
}
TEST_CASE("WowTime::WowGetTimeString", "[util]") {
SECTION("gets expected string") {
WowTime time;
time.m_minute = 18;
time.m_hour = 11;
time.m_weekday = 0;
time.m_monthday = 24;
time.m_month = 9;
time.m_year = 9;
time.m_flags = 0x2;
char buf[128];
auto timeStr = WowTime::WowGetTimeString(&time, buf, sizeof(buf));
REQUIRE(!SStrCmp(timeStr, "10/25/2009 (Sun) 11:18"));
}
}
TEST_CASE("WowTime::AddDays", "[util]") {
SECTION("adds 1 day to 1/28/2026") {
WowTime time;
time.m_minute = 18;
time.m_hour = 11;
time.m_weekday = 3;
time.m_monthday = 27;
time.m_month = 0;
time.m_year = 26;
time.AddDays(1, false);
CHECK(time.m_minute == 18);
CHECK(time.m_hour == 11);
CHECK(time.m_weekday == 4);
CHECK(time.m_monthday == 28);
CHECK(time.m_month == 0);
CHECK(time.m_year == 26);
}
}
TEST_CASE("WowTime::GetHourAndMinutes", "[util]") {
SECTION("gets expected hour and minutes for default constructed time") {
WowTime time;
REQUIRE(time.GetHourAndMinutes() == 0);
}
SECTION("gets expected hour and minutes for 11:18") {
WowTime time;
time.m_minute = 18;
time.m_hour = 11;
REQUIRE(time.GetHourAndMinutes() == 11 * 60 + 18);
}
}
TEST_CASE("WowTime::SetHourAndMinutes", "[util]") {
SECTION("sets expected hour and minutes for 11:18") {
WowTime time;
time.SetHourAndMinutes(11 * 60 + 18);
CHECK(time.m_hour == 11);
CHECK(time.m_minute == 18);
time.SetHourAndMinutes(11, 18);
CHECK(time.m_hour == 11);
CHECK(time.m_minute == 18);
}
SECTION("does not set invalid hour and minutes") {
WowTime time;
CHECK(time.SetHourAndMinutes(25, 61) == 0);
CHECK(time.m_hour == -1);
CHECK(time.m_minute == -1);
}
}