From 325598cd99b14fc3f0d82d27a4a94c6ccba9d8a7 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Sat, 6 Jan 2024 02:16:10 -0600 Subject: [PATCH 1/2] feat: fully reading auth packets and use stamps (#1398) * feat: fully reading auth packets and use stamps * fix the stupid define * Address feedback * no more Stamp(...) --- dNet/AuthPackets.cpp | 213 +++++++++++++++++++++++++++++-------------- dNet/AuthPackets.h | 84 ++++++++++++++++- dNet/dServer.h | 8 ++ 3 files changed, 238 insertions(+), 67 deletions(-) diff --git a/dNet/AuthPackets.cpp b/dNet/AuthPackets.cpp index 7feb0cc3..de8f5704 100644 --- a/dNet/AuthPackets.cpp +++ b/dNet/AuthPackets.cpp @@ -24,11 +24,17 @@ #include "eServerMessageType.h" #include "eMasterMessageType.h" #include "eGameMasterLevel.h" - +#include "StringifiedEnum.h" namespace { std::vector claimCodes; } +void Stamp::Serialize(RakNet::BitStream* outBitStream){ + outBitStream->Write(type); + outBitStream->Write(value); + outBitStream->Write(timestamp); +}; + void AuthPackets::LoadClaimCodes() { if(!claimCodes.empty()) return; auto rcstring = Game::config->GetValue("rewardcodes"); @@ -42,12 +48,26 @@ void AuthPackets::LoadClaimCodes() { } void AuthPackets::HandleHandshake(dServer* server, Packet* packet) { - RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header = inStream.Read(header); + CINSTREAM_SKIP_HEADER uint32_t clientVersion = 0; inStream.Read(clientVersion); + inStream.IgnoreBytes(4); + + ServiceId serviceId; + inStream.Read(serviceId); + if (serviceId != ServiceId::Client) LOG("WARNING: Service ID is not a Client!"); + + uint32_t processID; + inStream.Read(processID); + + uint16_t port; + inStream.Read(port); + if (port != packet->systemAddress.port) LOG("WARNING: Port written in packet does not match the port the client is connecting over!"); + + inStream.IgnoreBytes(33); + + LOG_DEBUG("Client Data [Version: %i, Service: %s, Process: %u, Port: %u, Sysaddr Port: %u]", clientVersion, StringifiedEnum::ToString(serviceId).data(), processID, port, packet->systemAddress.port); - LOG("Received client version: %i", clientVersion); SendHandshake(server, packet->systemAddress, server->GetIP(), server->GetPort(), server->GetServerType()); } @@ -60,42 +80,93 @@ void AuthPackets::SendHandshake(dServer* server, const SystemAddress& sysAddr, c if (!clientNetVersionString.empty()) GeneralUtils::TryParse(clientNetVersionString, clientNetVersion); bitStream.Write(clientNetVersion); - bitStream.Write(0x93); + bitStream.Write(861228100); - if (serverType == ServerType::Auth) bitStream.Write(uint32_t(1)); //Conn: auth - else bitStream.Write(4); //Conn: world - - bitStream.Write(0); //Server process ID - bitStream.Write(nextServerPort); + if (serverType == ServerType::Auth) bitStream.Write(ServiceId::Auth); + else if (serverType == ServerType::World) bitStream.Write(ServiceId::World); + else bitStream.Write(ServiceId::General); + bitStream.Write(774909490); server->Send(&bitStream, sysAddr, false); } void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) { - std::string username = PacketUtils::ReadString(8, packet, true); - std::string password = PacketUtils::ReadString(0x4A, packet, true); - const char* szUsername = username.c_str(); + CINSTREAM_SKIP_HEADER; + + std::vector stamps; + stamps.emplace_back(eStamps::PASSPORT_AUTH_START, 0); + + LUWString usernameLUString(33); + inStream.Read(usernameLUString); + const auto username = usernameLUString.GetAsString(); + + LUWString password(41); + inStream.Read(password); + + LanguageCodeID locale_id; + inStream.Read(locale_id); + LOG_DEBUG("Locale ID: %s", StringifiedEnum::ToString(locale_id).data()); + + ClientOS clientOS; + inStream.Read(clientOS); + LOG_DEBUG("Operating System: %s", StringifiedEnum::ToString(clientOS).data()); + stamps.emplace_back(eStamps::PASSPORT_AUTH_CLIENT_OS, 0); + + LUWString memoryStats(256); + inStream.Read(memoryStats); + LOG_DEBUG("Memory Stats [%s]", memoryStats.GetAsString().c_str()); + + LUWString videoCard(128); + inStream.Read(videoCard); + LOG_DEBUG("VideoCard Info: [%s]", videoCard.GetAsString().c_str()); + + // Processor/CPU info + uint32_t numOfProcessors; + inStream.Read(numOfProcessors); + uint32_t processorType; + inStream.Read(processorType); + uint16_t processorLevel; + inStream.Read(processorLevel); + uint16_t processorRevision; + inStream.Read(processorRevision); + LOG_DEBUG("CPU Info: [#Processors: %i, Processor Type: %i, Processor Level: %i, Processor Revision: %i]", numOfProcessors, processorType, processorLevel, processorRevision); + + // OS Info + uint32_t osVersionInfoSize; + inStream.Read(osVersionInfoSize); + uint32_t majorVersion; + inStream.Read(majorVersion); + uint32_t minorVersion; + inStream.Read(minorVersion); + uint32_t buildNumber; + inStream.Read(buildNumber); + uint32_t platformID; + inStream.Read(platformID); + LOG_DEBUG("OS Info: [Size: %i, Major: %i, Minor %i, Buid#: %i, platformID: %i]", osVersionInfoSize, majorVersion, minorVersion, buildNumber, platformID); // Fetch account details auto accountInfo = Database::Get()->GetAccountInfo(username); if (!accountInfo) { LOG("No user by name %s found!", username.c_str()); - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::INVALID_USER, "", "", 2001, username); + stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); + AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::INVALID_USER, "", "", 2001, username, stamps); return; } //If we aren't running in live mode, then only GMs are allowed to enter: const auto& closedToNonDevs = Game::config->GetValue("closed_to_non_devs"); if (closedToNonDevs.size() > 0 && bool(std::stoi(closedToNonDevs)) && accountInfo->maxGmLevel == eGameMasterLevel::CIVILIAN) { - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "The server is currently only open to developers.", "", 2001, username); + stamps.emplace_back(eStamps::GM_REQUIRED, 1); + AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "The server is currently only open to developers.", "", 2001, username, stamps); return; } if (Game::config->GetValue("dont_use_keys") != "1" && accountInfo->maxGmLevel == eGameMasterLevel::CIVILIAN) { //Check to see if we have a play key: if (accountInfo->playKeyId == 0) { - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a play key associated with it!", "", 2001, username); + stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); + AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a play key associated with it!", "", 2001, username, stamps); LOG("User %s tried to log in, but they don't have a play key.", username.c_str()); return; } @@ -104,40 +175,50 @@ void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) { auto playKeyStatus = Database::Get()->IsPlaykeyActive(accountInfo->playKeyId); if (!playKeyStatus) { - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a valid play key associated with it!", "", 2001, username); + stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); + AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a valid play key associated with it!", "", 2001, username, stamps); return; } if (!playKeyStatus.value()) { - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your play key has been disabled.", "", 2001, username); + stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); + AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your play key has been disabled.", "", 2001, username, stamps); LOG("User %s tried to log in, but their play key was disabled", username.c_str()); return; } + } else if (Game::config->GetValue("dont_use_keys") == "1" || accountInfo->maxGmLevel > eGameMasterLevel::CIVILIAN){ + stamps.emplace_back(eStamps::PASSPORT_AUTH_BYPASS, 1); } if (accountInfo->banned) { - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::BANNED, "", "", 2001, username); return; + stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); + AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::BANNED, "", "", 2001, username, stamps); + return; } if (accountInfo->locked) { - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::ACCOUNT_LOCKED, "", "", 2001, username); return; + stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); + AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::ACCOUNT_LOCKED, "", "", 2001, username, stamps); + return; } - bool loginSuccess = ::bcrypt_checkpw(password.c_str(), accountInfo->bcryptPassword.c_str()) == 0; + bool loginSuccess = ::bcrypt_checkpw(password.GetAsString().c_str(), accountInfo->bcryptPassword.c_str()) == 0; if (!loginSuccess) { - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::WRONG_PASS, "", "", 2001, username); + stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); + AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::WRONG_PASS, "", "", 2001, username, stamps); LOG("Wrong password used"); } else { SystemAddress system = packet->systemAddress; //Copy the sysAddr before the Packet gets destroyed from main if (!server->GetIsConnectedToMaster()) { - AuthPackets::SendLoginResponse(server, system, eLoginResponse::GENERAL_FAILED, "", "", 0, username); + stamps.emplace_back(eStamps::PASSPORT_AUTH_WORLD_DISCONNECT, 1); + AuthPackets::SendLoginResponse(server, system, eLoginResponse::GENERAL_FAILED, "", "", 0, username, stamps); return; } - - ZoneInstanceManager::Instance()->RequestZoneTransfer(server, 0, 0, false, [system, server, username](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string zoneIP, uint16_t zonePort) { - AuthPackets::SendLoginResponse(server, system, eLoginResponse::SUCCESS, "", zoneIP, zonePort, username); + stamps.emplace_back(eStamps::PASSPORT_AUTH_WORLD_SESSION_CONFIRM_TO_AUTH, 1); + ZoneInstanceManager::Instance()->RequestZoneTransfer(server, 0, 0, false, [system, server, username, stamps](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string zoneIP, uint16_t zonePort) mutable { + AuthPackets::SendLoginResponse(server, system, eLoginResponse::SUCCESS, "", zoneIP, zonePort, username, stamps); }); } @@ -146,21 +227,22 @@ void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) { } } -void AuthPackets::SendLoginResponse(dServer* server, const SystemAddress& sysAddr, eLoginResponse responseCode, const std::string& errorMsg, const std::string& wServerIP, uint16_t wServerPort, std::string username) { - RakNet::BitStream packet; - BitStreamUtils::WriteHeader(packet, eConnectionType::CLIENT, eClientMessageType::LOGIN_RESPONSE); +void AuthPackets::SendLoginResponse(dServer* server, const SystemAddress& sysAddr, eLoginResponse responseCode, const std::string& errorMsg, const std::string& wServerIP, uint16_t wServerPort, std::string username, std::vector& stamps) { + stamps.emplace_back(eStamps::PASSPORT_AUTH_IM_LOGIN_START, 1); + RakNet::BitStream loginResponse; + BitStreamUtils::WriteHeader(loginResponse, eConnectionType::CLIENT, eClientMessageType::LOGIN_RESPONSE); - packet.Write(GeneralUtils::CastUnderlyingType(responseCode)); + loginResponse.Write(GeneralUtils::CastUnderlyingType(responseCode)); // Event Gating - packet.Write(LUString(Game::config->GetValue("event_1"))); - packet.Write(LUString(Game::config->GetValue("event_2"))); - packet.Write(LUString(Game::config->GetValue("event_3"))); - packet.Write(LUString(Game::config->GetValue("event_4"))); - packet.Write(LUString(Game::config->GetValue("event_5"))); - packet.Write(LUString(Game::config->GetValue("event_6"))); - packet.Write(LUString(Game::config->GetValue("event_7"))); - packet.Write(LUString(Game::config->GetValue("event_8"))); + loginResponse.Write(LUString(Game::config->GetValue("event_1"))); + loginResponse.Write(LUString(Game::config->GetValue("event_2"))); + loginResponse.Write(LUString(Game::config->GetValue("event_3"))); + loginResponse.Write(LUString(Game::config->GetValue("event_4"))); + loginResponse.Write(LUString(Game::config->GetValue("event_5"))); + loginResponse.Write(LUString(Game::config->GetValue("event_6"))); + loginResponse.Write(LUString(Game::config->GetValue("event_7"))); + loginResponse.Write(LUString(Game::config->GetValue("event_8"))); uint16_t version_major = 1; uint16_t version_current = 10; @@ -169,53 +251,52 @@ void AuthPackets::SendLoginResponse(dServer* server, const SystemAddress& sysAdd GeneralUtils::TryParse(Game::config->GetValue("version_current"), version_current); GeneralUtils::TryParse(Game::config->GetValue("version_minor"), version_minor); - packet.Write(version_major); - packet.Write(version_current); - packet.Write(version_minor); + loginResponse.Write(version_major); + loginResponse.Write(version_current); + loginResponse.Write(version_minor); // Writes the user key uint32_t sessionKey = GeneralUtils::GenerateRandomNumber(); std::string userHash = std::to_string(sessionKey); userHash = md5(userHash); - packet.Write(LUWString(userHash)); + loginResponse.Write(LUWString(userHash)); - // Write the Character and Chat IPs - packet.Write(LUString(wServerIP)); - packet.Write(LUString("")); + // World Server IP + loginResponse.Write(LUString(wServerIP)); + // Chat Server IP (unused) + loginResponse.Write(LUString("")); - // Write the Character and Chat Ports - packet.Write(wServerPort); - packet.Write(0); + // World Server Redirect port + loginResponse.Write(wServerPort); + // Char Server Redirect port (unused) + loginResponse.Write(static_cast(0)); // CDN Key - packet.Write(LUString("")); + loginResponse.Write(LUString("")); // CDN Ticket - packet.Write(LUString("00000000-0000-0000-0000-000000000000", 37)); + loginResponse.Write(LUString("00000000-0000-0000-0000-000000000000", 37)); - packet.Write(0); // Language + // Language + loginResponse.Write(Language::en_US); // Write the localization - packet.Write(LUString("US", 3)); + loginResponse.Write(LUString("US", 3)); - packet.Write(false); // Just upgraded from F2P - packet.Write(false); // User is F2P - packet.Write(0); // Time Remaining in F2P + loginResponse.Write(false); // Just upgraded from F2P + loginResponse.Write(false); // User is F2P + loginResponse.Write(0); // Time Remaining in F2P // Write custom error message - packet.Write(errorMsg.length()); - packet.Write(LUWString(errorMsg, static_cast(errorMsg.length()))); + loginResponse.Write(errorMsg.length()); + loginResponse.Write(LUWString(errorMsg, static_cast(errorMsg.length()))); - // Here write auth logs - packet.Write(20); - for (uint32_t i = 0; i < 20; ++i) { - packet.Write(8); - packet.Write(44); - packet.Write(14000); - packet.Write(0); - } + stamps.emplace_back(eStamps::PASSPORT_AUTH_WORLD_COMMUNICATION_FINISH, 1); - server->Send(&packet, sysAddr, false); + loginResponse.Write((sizeof(Stamp) * stamps.size()) + sizeof(uint32_t)); + for (auto& stamp : stamps) stamp.Serialize(&loginResponse); + + server->Send(&loginResponse, sysAddr, false); //Inform the master server that we've created a session for this user: if (responseCode == eLoginResponse::SUCCESS) { diff --git a/dNet/AuthPackets.h b/dNet/AuthPackets.h index eb275a46..539bae75 100644 --- a/dNet/AuthPackets.h +++ b/dNet/AuthPackets.h @@ -4,17 +4,99 @@ #define _VARIADIC_MAX 10 #include "dCommonVars.h" #include "dNetCommon.h" +#include "magic_enum.hpp" enum class ServerType : uint32_t; enum class eLoginResponse : uint8_t; class dServer; +enum class eStamps : uint32_t { + PASSPORT_AUTH_START, + PASSPORT_AUTH_BYPASS, + PASSPORT_AUTH_ERROR, + PASSPORT_AUTH_DB_SELECT_START, + PASSPORT_AUTH_DB_SELECT_FINISH, + PASSPORT_AUTH_DB_INSERT_START, + PASSPORT_AUTH_DB_INSERT_FINISH, + PASSPORT_AUTH_LEGOINT_COMMUNICATION_START, + PASSPORT_AUTH_LEGOINT_RECEIVED, + PASSPORT_AUTH_LEGOINT_THREAD_SPAWN, + PASSPORT_AUTH_LEGOINT_WEBSERVICE_START, + PASSPORT_AUTH_LEGOINT_WEBSERVICE_FINISH, + PASSPORT_AUTH_LEGOINT_LEGOCLUB_START, + PASSPORT_AUTH_LEGOINT_LEGOCLUB_FINISH, + PASSPORT_AUTH_LEGOINT_THREAD_FINISH, + PASSPORT_AUTH_LEGOINT_REPLY, + PASSPORT_AUTH_LEGOINT_ERROR, + PASSPORT_AUTH_LEGOINT_COMMUNICATION_END, + PASSPORT_AUTH_LEGOINT_DISCONNECT, + PASSPORT_AUTH_WORLD_COMMUNICATION_START, + PASSPORT_AUTH_CLIENT_OS, + PASSPORT_AUTH_WORLD_PACKET_RECEIVED, + PASSPORT_AUTH_IM_COMMUNICATION_START, + PASSPORT_AUTH_IM_LOGIN_START, + PASSPORT_AUTH_IM_LOGIN_ALREADY_LOGGED_IN, + PASSPORT_AUTH_IM_OTHER_LOGIN_REMOVED, + PASSPORT_AUTH_IM_LOGIN_QUEUED, + PASSPORT_AUTH_IM_LOGIN_RESPONSE, + PASSPORT_AUTH_IM_COMMUNICATION_END, + PASSPORT_AUTH_WORLD_SESSION_CONFIRM_TO_AUTH, + PASSPORT_AUTH_WORLD_COMMUNICATION_FINISH, + PASSPORT_AUTH_WORLD_DISCONNECT, + NO_LEGO_INTERFACE, + DB_ERROR, + GM_REQUIRED, + NO_LEGO_WEBSERVICE_XML, + LEGO_WEBSERVICE_TIMEOUT, + LEGO_WEBSERVICE_ERROR, + NO_WORLD_SERVER +}; + +struct Stamp { + eStamps type; + uint32_t value; + uint64_t timestamp; + + Stamp(eStamps type, uint32_t value, uint64_t timestamp = time(nullptr)){ + this->type = type; + this->value = value; + this->timestamp = timestamp; + } + + void Serialize(RakNet::BitStream* outBitStream); +}; + +enum class ClientOS : uint8_t { + UNKNOWN, + WINDOWS, + MACOS +}; + +enum class LanguageCodeID : uint16_t { + de_DE = 0x0407, + en_US = 0x0409, + en_GB = 0x0809 +}; + +template <> +struct magic_enum::customize::enum_range { + static constexpr int min = 1031; + static constexpr int max = 2057; +}; + +enum class Language : uint32_t { + en_US, + pl_US, + de_DE, + en_GB, +}; + namespace AuthPackets { void HandleHandshake(dServer* server, Packet* packet); void SendHandshake(dServer* server, const SystemAddress& sysAddr, const std::string& nextServerIP, uint16_t nextServerPort, const ServerType serverType); void HandleLoginRequest(dServer* server, Packet* packet); - void SendLoginResponse(dServer* server, const SystemAddress& sysAddr, eLoginResponse responseCode, const std::string& errorMsg, const std::string& wServerIP, uint16_t wServerPort, std::string username); + void SendLoginResponse(dServer* server, const SystemAddress& sysAddr, eLoginResponse responseCode, const std::string& errorMsg, const std::string& wServerIP, uint16_t wServerPort, std::string username, std::vector& stamps); void LoadClaimCodes(); } diff --git a/dNet/dServer.h b/dNet/dServer.h index b0a3e11d..ef47eea4 100644 --- a/dNet/dServer.h +++ b/dNet/dServer.h @@ -16,6 +16,14 @@ enum class ServerType : uint32_t { World }; +enum class ServiceId : uint32_t{ + General = 0, + Auth = 1, + Chat = 2, + World = 4, + Client = 5, +}; + namespace Game { using signal_t = volatile std::sig_atomic_t; } From 14c20fbd6280b8f6e21f651d2855c3067e8e6fce Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 6 Jan 2024 01:45:23 -0800 Subject: [PATCH 2/2] change timers to not use ptrs (#1399) add comments as to why logic may seem confusing. --- dGame/Entity.cpp | 97 +++++++++++++-------------- dGame/Entity.h | 9 ++- dGame/dEntity/EntityCallbackTimer.cpp | 14 +--- dGame/dEntity/EntityCallbackTimer.h | 8 +-- dGame/dEntity/EntityTimer.cpp | 6 +- dGame/dEntity/EntityTimer.h | 11 ++- 6 files changed, 67 insertions(+), 78 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index aaa01e97..347b4caa 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -1222,39 +1222,56 @@ void Entity::UpdateXMLDoc(tinyxml2::XMLDocument* doc) { void Entity::Update(const float deltaTime) { uint32_t timerPosition; - timerPosition = 0; - while (timerPosition < m_Timers.size()) { - m_Timers[timerPosition]->Update(deltaTime); - if (m_Timers[timerPosition]->GetTime() <= 0) { - const auto timerName = m_Timers[timerPosition]->GetName(); - - delete m_Timers[timerPosition]; + for (timerPosition = 0; timerPosition < m_Timers.size();) { + auto& timer = m_Timers[timerPosition]; + timer.Update(deltaTime); + // If the timer is expired, erase it and dont increment the position because the next timer will be at the same position. + // Before: [0, 1, 2, 3, ..., n] + // timerPosition ^ + // After: [0, 1, 3, ..., n] + // timerPosition ^ + if (timer.GetTime() <= 0) { + // Remove the timer from the list of timers first so that scripts and events can remove timers without causing iterator invalidation + auto timerName = timer.GetName(); m_Timers.erase(m_Timers.begin() + timerPosition); - for (CppScripts::Script* script : CppScripts::GetEntityScripts(this)) { script->OnTimerDone(this, timerName); } + TriggerEvent(eTriggerEventType::TIMER_DONE, this); + } else { + // If the timer isnt expired, go to the next timer. + timerPosition++; + } + } + + for (timerPosition = 0; timerPosition < m_CallbackTimers.size(); ) { + // If the timer is expired, erase it and dont increment the position because the next timer will be at the same position. + // Before: [0, 1, 2, 3, ..., n] + // timerPosition ^ + // After: [0, 1, 3, ..., n] + // timerPosition ^ + auto& callbackTimer = m_CallbackTimers[timerPosition]; + callbackTimer.Update(deltaTime); + if (callbackTimer.GetTime() <= 0) { + // Remove the timer from the list of timers first so that callbacks can remove timers without causing iterator invalidation + auto callback = callbackTimer.GetCallback(); + m_CallbackTimers.erase(m_CallbackTimers.begin() + timerPosition); + callback(); } else { timerPosition++; } } - for (int i = 0; i < m_CallbackTimers.size(); i++) { - m_CallbackTimers[i]->Update(deltaTime); - if (m_CallbackTimers[i]->GetTime() <= 0) { - m_CallbackTimers[i]->GetCallback()(); - delete m_CallbackTimers[i]; - m_CallbackTimers.erase(m_CallbackTimers.begin() + i); - } + // Add pending timers to the list of timers so they start next tick. + if (!m_PendingTimers.empty()) { + m_Timers.insert(m_Timers.end(), m_PendingTimers.begin(), m_PendingTimers.end()); + m_PendingTimers.clear(); } - // Add pending timers to the list of timers so they start next tick. - if (m_PendingTimers.size() > 0) { - for (auto namedTimer : m_PendingTimers) { - m_Timers.push_back(namedTimer); - } - m_PendingTimers.clear(); + if (!m_PendingCallbackTimers.empty()) { + m_CallbackTimers.insert(m_CallbackTimers.end(), m_PendingCallbackTimers.begin(), m_PendingCallbackTimers.end()); + m_PendingCallbackTimers.clear(); } if (IsSleeping()) { @@ -1692,31 +1709,20 @@ void Entity::RemoveParent() { } void Entity::AddTimer(std::string name, float time) { - EntityTimer* timer = new EntityTimer(name, time); - m_PendingTimers.push_back(timer); + m_PendingTimers.emplace_back(name, time); } void Entity::AddCallbackTimer(float time, std::function callback) { - EntityCallbackTimer* timer = new EntityCallbackTimer(time, callback); - m_CallbackTimers.push_back(timer); + m_PendingCallbackTimers.emplace_back(time, callback); } bool Entity::HasTimer(const std::string& name) { - for (auto* timer : m_Timers) { - if (timer->GetName() == name) { - return true; - } - } - - return false; + return std::find(m_Timers.begin(), m_Timers.end(), name) != m_Timers.end(); } void Entity::CancelCallbackTimers() { - for (auto* callback : m_CallbackTimers) { - delete callback; - } - m_CallbackTimers.clear(); + m_PendingCallbackTimers.clear(); } void Entity::ScheduleKillAfterUpdate(Entity* murderer) { @@ -1728,8 +1734,8 @@ void Entity::ScheduleKillAfterUpdate(Entity* murderer) { void Entity::CancelTimer(const std::string& name) { for (int i = 0; i < m_Timers.size(); i++) { - if (m_Timers[i]->GetName() == name) { - delete m_Timers[i]; + auto& timer = m_Timers[i]; + if (timer == name) { m_Timers.erase(m_Timers.begin() + i); return; } @@ -1737,21 +1743,10 @@ void Entity::CancelTimer(const std::string& name) { } void Entity::CancelAllTimers() { - /*for (auto timer : m_Timers) { - if (timer) delete timer; - }*/ - - for (auto* timer : m_Timers) { - delete timer; - } - m_Timers.clear(); - - for (auto* callBackTimer : m_CallbackTimers) { - delete callBackTimer; - } - + m_PendingTimers.clear(); m_CallbackTimers.clear(); + m_PendingCallbackTimers.clear(); } bool Entity::IsPlayer() const { diff --git a/dGame/Entity.h b/dGame/Entity.h index 77d77b16..83e8dc3f 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -160,6 +160,8 @@ public: void AddChild(Entity* child); void RemoveChild(Entity* child); void RemoveParent(); + + // Adds a timer to start next frame with the given name and time. void AddTimer(std::string name, float time); void AddCallbackTimer(float time, std::function callback); bool HasTimer(const std::string& name); @@ -324,9 +326,10 @@ protected: std::vector> m_PhantomCollisionCallbacks; std::unordered_map m_Components; - std::vector m_Timers; - std::vector m_PendingTimers; - std::vector m_CallbackTimers; + std::vector m_Timers; + std::vector m_PendingTimers; + std::vector m_CallbackTimers; + std::vector m_PendingCallbackTimers; bool m_ShouldDestroyAfterUpdate = false; diff --git a/dGame/dEntity/EntityCallbackTimer.cpp b/dGame/dEntity/EntityCallbackTimer.cpp index e07c1189..95222c4e 100644 --- a/dGame/dEntity/EntityCallbackTimer.cpp +++ b/dGame/dEntity/EntityCallbackTimer.cpp @@ -1,22 +1,10 @@ #include "EntityCallbackTimer.h" -EntityCallbackTimer::EntityCallbackTimer(float time, std::function callback) { +EntityCallbackTimer::EntityCallbackTimer(const float time, const std::function callback) { m_Time = time; m_Callback = callback; } -EntityCallbackTimer::~EntityCallbackTimer() { - -} - -std::function EntityCallbackTimer::GetCallback() { - return m_Callback; -} - -float EntityCallbackTimer::GetTime() { - return m_Time; -} - void EntityCallbackTimer::Update(float deltaTime) { m_Time -= deltaTime; } diff --git a/dGame/dEntity/EntityCallbackTimer.h b/dGame/dEntity/EntityCallbackTimer.h index 2a7e58f0..9b37476b 100644 --- a/dGame/dEntity/EntityCallbackTimer.h +++ b/dGame/dEntity/EntityCallbackTimer.h @@ -5,11 +5,11 @@ class EntityCallbackTimer { public: - EntityCallbackTimer(float time, std::function callback); - ~EntityCallbackTimer(); + EntityCallbackTimer(const float time, const std::function callback); + + std::function GetCallback() const { return m_Callback; }; - std::function GetCallback(); - float GetTime(); + float GetTime() const { return m_Time; }; void Update(float deltaTime); diff --git a/dGame/dEntity/EntityTimer.cpp b/dGame/dEntity/EntityTimer.cpp index 0363fc5b..8ec666b6 100644 --- a/dGame/dEntity/EntityTimer.cpp +++ b/dGame/dEntity/EntityTimer.cpp @@ -1,14 +1,10 @@ #include "EntityTimer.h" -EntityTimer::EntityTimer(std::string name, float time) { +EntityTimer::EntityTimer(const std::string& name, const float time) { m_Name = name; m_Time = time; } -EntityTimer::~EntityTimer() { - -} - std::string EntityTimer::GetName() { return m_Name; } diff --git a/dGame/dEntity/EntityTimer.h b/dGame/dEntity/EntityTimer.h index 9de0345d..d5c4a19e 100644 --- a/dGame/dEntity/EntityTimer.h +++ b/dGame/dEntity/EntityTimer.h @@ -4,8 +4,15 @@ class EntityTimer { public: - EntityTimer(std::string name, float time); - ~EntityTimer(); + EntityTimer(const std::string& name, const float time); + + bool operator==(const EntityTimer& other) const { + return m_Name == other.m_Name; + } + + bool operator==(const std::string& other) const { + return m_Name == other; + } std::string GetName(); float GetTime();