diff --git a/dCommon/dEnums/eGameMessageType.h b/dCommon/dEnums/eGameMessageType.h index 69f542e1..d5751b51 100644 --- a/dCommon/dEnums/eGameMessageType.h +++ b/dCommon/dEnums/eGameMessageType.h @@ -169,7 +169,7 @@ enum class eGameMessageType : uint16_t { MODIFY_ARMOR = 187, MODIFY_MAX_ARMOR = 188, POP_HEALTH_STATE = 190, - PUSH_EQUIPPED_ITEMS_STATE = 191, + PUSH_EQUIPPED_ITEMS_STATE = 191, POP_EQUIPPED_ITEMS_STATE = 192, SET_GM_LEVEL = 193, GET_GM_LEVEL = 194, @@ -358,7 +358,7 @@ enum class eGameMessageType : uint16_t { GET_POSSESSOR = 397, IS_POSSESSED = 398, ENABLE_ACTIVITY = 399, - SET_SHOOTING_GALLERY_PARAMS = 400, + SET_SHOOTING_GALLERY_PARAMS = 400, OPEN_ACTIVITY_START_DIALOG = 401, REQUEST_ACTIVITY_START_STOP = 402, REQUEST_ACTIVITY_ENTER = 403, diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 998541ad..e3bacb8b 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -51,6 +51,7 @@ #include "BuildBorderComponent.h" #include "MovementAIComponent.h" #include "VendorComponent.h" +#include "DonationVendorComponent.h" #include "RocketLaunchpadControlComponent.h" #include "PropertyComponent.h" #include "BaseCombatAIComponent.h" @@ -578,6 +579,9 @@ void Entity::Initialize() { if ((compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::VENDOR) > 0)) { VendorComponent* comp = new VendorComponent(this); m_Components.insert(std::make_pair(eReplicaComponentType::VENDOR, comp)); + } else if ((compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::DONATION_VENDOR, -1) != -1)) { + DonationVendorComponent* comp = new DonationVendorComponent(this); + m_Components.insert(std::make_pair(eReplicaComponentType::DONATION_VENDOR, comp)); } if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY_VENDOR, -1) != -1) { @@ -1160,6 +1164,11 @@ void Entity::WriteComponents(RakNet::BitStream* outBitStream, eReplicaPacketType vendorComponent->Serialize(outBitStream, bIsInitialUpdate, flags); } + DonationVendorComponent* donationVendorComponent; + if (TryGetComponent(eReplicaComponentType::DONATION_VENDOR, donationVendorComponent)) { + donationVendorComponent->Serialize(outBitStream, bIsInitialUpdate, flags); + } + BouncerComponent* bouncerComponent; if (TryGetComponent(eReplicaComponentType::BOUNCER, bouncerComponent)) { bouncerComponent->Serialize(outBitStream, bIsInitialUpdate, flags); diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index a4c6ec1a..0e64b6b3 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -131,8 +131,8 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { // Time:1 break; case Type::Donations: - entry.push_back(new LDFData(u"Points", rows->getInt("primaryScore"))); - // Score:1 + entry.push_back(new LDFData(u"Score", rows->getInt("primaryScore"))); + // Score:1 break; case Type::None: // This type is included here simply to resolve a compiler warning on mac about unused enum types @@ -170,32 +170,32 @@ void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t r resultEnd++; // We need everything except 1 column so i'm selecting * from leaderboard const std::string queryBase = - R"QUERY( - WITH leaderboardsRanked AS ( - SELECT leaderboard.*, charinfo.name, - RANK() OVER - ( + R"QUERY( + WITH leaderboardsRanked AS ( + SELECT leaderboard.*, charinfo.name, + RANK() OVER + ( ORDER BY %s, UNIX_TIMESTAMP(last_played) ASC, id DESC - ) AS ranking - FROM leaderboard JOIN charinfo on charinfo.id = leaderboard.character_id - WHERE game_id = ? %s - ), - myStanding AS ( - SELECT - ranking as myRank - FROM leaderboardsRanked - WHERE id = ? - ), - lowestRanking AS ( - SELECT MAX(ranking) AS lowestRank - FROM leaderboardsRanked - ) - SELECT leaderboardsRanked.*, character_id, UNIX_TIMESTAMP(last_played) as lastPlayed, leaderboardsRanked.name, leaderboardsRanked.ranking FROM leaderboardsRanked, myStanding, lowestRanking - WHERE leaderboardsRanked.ranking - BETWEEN - LEAST(GREATEST(CAST(myRank AS SIGNED) - 5, %i), lowestRanking.lowestRank - 9) - AND - LEAST(GREATEST(myRank + 5, %i), lowestRanking.lowestRank) + ) AS ranking + FROM leaderboard JOIN charinfo on charinfo.id = leaderboard.character_id + WHERE game_id = ? %s + ), + myStanding AS ( + SELECT + ranking as myRank + FROM leaderboardsRanked + WHERE id = ? + ), + lowestRanking AS ( + SELECT MAX(ranking) AS lowestRank + FROM leaderboardsRanked + ) + SELECT leaderboardsRanked.*, character_id, UNIX_TIMESTAMP(last_played) as lastPlayed, leaderboardsRanked.name, leaderboardsRanked.ranking FROM leaderboardsRanked, myStanding, lowestRanking + WHERE leaderboardsRanked.ranking + BETWEEN + LEAST(GREATEST(CAST(myRank AS SIGNED) - 5, %i), lowestRanking.lowestRank - 9) + AND + LEAST(GREATEST(myRank + 5, %i), lowestRanking.lowestRank) ORDER BY ranking ASC; )QUERY"; @@ -277,15 +277,15 @@ std::string FormatInsert(const Leaderboard::Type& type, const Score& score, cons if (useUpdate) { insertStatement = R"QUERY( - UPDATE leaderboard - SET primaryScore = %f, secondaryScore = %f, tertiaryScore = %f, + UPDATE leaderboard + SET primaryScore = %f, secondaryScore = %f, tertiaryScore = %f, timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?; )QUERY"; } else { insertStatement = R"QUERY( - INSERT leaderboard SET - primaryScore = %f, secondaryScore = %f, tertiaryScore = %f, + INSERT leaderboard SET + primaryScore = %f, secondaryScore = %f, tertiaryScore = %f, character_id = ?, game_id = ?; )QUERY"; } @@ -300,9 +300,8 @@ std::string FormatInsert(const Leaderboard::Type& type, const Score& score, cons void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore, const float tertiaryScore) { const Leaderboard::Type leaderboardType = GetLeaderboardType(activityId); - auto* lookup = "SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;"; - std::unique_ptr query(Database::CreatePreppedStmt(lookup)); + std::unique_ptr query(Database::CreatePreppedStmt("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;")); query->setInt(1, playerID); query->setInt(2, activityId); std::unique_ptr myScoreResult(query->executeQuery()); @@ -337,6 +336,7 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activi case Leaderboard::Type::UnusedLeaderboard4: case Leaderboard::Type::Donations: { oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore")); + newScore.SetPrimaryScore(oldScore.GetPrimaryScore() + newScore.GetPrimaryScore()); break; } case Leaderboard::Type::Racing: { @@ -382,7 +382,7 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activi saveStatement->setInt(1, playerID); saveStatement->setInt(2, activityId); saveStatement->execute(); - + // track wins separately if (leaderboardType == Leaderboard::Type::Racing && tertiaryScore != 0.0f) { std::unique_ptr winUpdate(Database::CreatePreppedStmt("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;")); diff --git a/dGame/dComponents/CMakeLists.txt b/dGame/dComponents/CMakeLists.txt index b396829a..67f4ece5 100644 --- a/dGame/dComponents/CMakeLists.txt +++ b/dGame/dComponents/CMakeLists.txt @@ -6,6 +6,7 @@ set(DGAME_DCOMPONENTS_SOURCES "BaseCombatAIComponent.cpp" "Component.cpp" "ControllablePhysicsComponent.cpp" "DestroyableComponent.cpp" + "DonationVendorComponent.cpp" "InventoryComponent.cpp" "LevelProgressionComponent.cpp" "LUPExhibitComponent.cpp" diff --git a/dGame/dComponents/CharacterComponent.h b/dGame/dComponents/CharacterComponent.h index e5ca6da5..c367aefc 100644 --- a/dGame/dComponents/CharacterComponent.h +++ b/dGame/dComponents/CharacterComponent.h @@ -276,6 +276,10 @@ public: */ void UpdateClientMinimap(bool showFaction, std::string ventureVisionType) const; + void SetCurrentInteracting(LWOOBJID objectID) {m_CurrentInteracting = objectID;}; + + LWOOBJID GetCurrentInteracting() {return m_CurrentInteracting;}; + /** * Character info regarding this character, including clothing styles, etc. */ @@ -560,6 +564,8 @@ private: * ID of the last rocket used */ LWOOBJID m_LastRocketItemID = LWOOBJID_EMPTY; + + LWOOBJID m_CurrentInteracting = LWOOBJID_EMPTY; }; #endif // CHARACTERCOMPONENT_H diff --git a/dGame/dComponents/DonationVendorComponent.cpp b/dGame/dComponents/DonationVendorComponent.cpp new file mode 100644 index 00000000..f19ba9b7 --- /dev/null +++ b/dGame/dComponents/DonationVendorComponent.cpp @@ -0,0 +1,50 @@ +#include "DonationVendorComponent.h" +#include "Database.h" + +DonationVendorComponent::DonationVendorComponent(Entity* parent) : VendorComponent(parent) { + //LoadConfigData + m_PercentComplete = 0.0; + m_TotalDonated = 0; + m_TotalRemaining = 0; + + // custom attribute to calculate other values + m_Goal = m_Parent->GetVar(u"donationGoal"); + if (m_Goal == 0) m_Goal = INT32_MAX; + + // Default to the nexus tower jawbox activity and setup settings + m_ActivityId = m_Parent->GetVar(u"activityID"); + if ((m_ActivityId == 0) || (m_ActivityId == 117)) { + m_ActivityId = 117; + m_PercentComplete = 1.0; + m_TotalDonated = INT32_MAX; + m_TotalRemaining = 0; + m_Goal = INT32_MAX; + return; + } + + std::unique_ptr query(Database::CreatePreppedStmt("SELECT SUM(primaryScore) as donation_total FROM leaderboard WHERE game_id = ?;")); + query->setInt(1, m_ActivityId); + std::unique_ptr donation_total(query->executeQuery()); + if (donation_total->next()) m_TotalDonated = donation_total->getInt("donation_total"); + m_TotalRemaining = m_Goal - m_TotalDonated; + m_PercentComplete = m_TotalDonated/static_cast(m_Goal); +} + +void DonationVendorComponent::SubmitDonation(uint32_t count) { + if (count <= 0 && ((m_TotalDonated + count) > 0)) return; + m_TotalDonated += count; + m_TotalRemaining = m_Goal - m_TotalDonated; + m_PercentComplete = m_TotalDonated/static_cast(m_Goal); + m_DirtyDonationVendor = true; +} + +void DonationVendorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { + VendorComponent::Serialize(outBitStream, bIsInitialUpdate, flags); + outBitStream->Write(bIsInitialUpdate || m_DirtyDonationVendor); + if (bIsInitialUpdate || m_DirtyDonationVendor) { + outBitStream->Write(m_PercentComplete); + outBitStream->Write(m_TotalDonated); + outBitStream->Write(m_TotalRemaining); + if (!bIsInitialUpdate) m_DirtyDonationVendor = false; + } +} diff --git a/dGame/dComponents/DonationVendorComponent.h b/dGame/dComponents/DonationVendorComponent.h new file mode 100644 index 00000000..6c706bf9 --- /dev/null +++ b/dGame/dComponents/DonationVendorComponent.h @@ -0,0 +1,27 @@ +#ifndef __DONATIONVENDORCOMPONENT__H__ +#define __DONATIONVENDORCOMPONENT__H__ + +#include "VendorComponent.h" +#include "eReplicaComponentType.h" + +class Entity; + +class DonationVendorComponent final : public VendorComponent { +public: + inline static const eReplicaComponentType ComponentType = eReplicaComponentType::DONATION_VENDOR; + DonationVendorComponent(Entity* parent); + void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); + uint32_t GetActivityID() {return m_ActivityId;}; + void SubmitDonation(uint32_t count); + +private: + bool m_DirtyDonationVendor = false; + float m_PercentComplete = 0.0; + int32_t m_TotalDonated = 0; + int32_t m_TotalRemaining = 0; + uint32_t m_ActivityId = 0; + int32_t m_Goal = 0; +}; + + +#endif //!__DONATIONVENDORCOMPONENT__H__ diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 66b47e52..8689c0bc 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -116,6 +116,9 @@ Inventory* InventoryComponent::GetInventory(const eInventoryType type) { case eInventoryType::VENDOR_BUYBACK: size = 27u; break; + case eInventoryType::DONATION: + size = 24u; + break; default: break; } diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index ba063d2d..99592ed2 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -48,10 +48,11 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System if (!entity) { Game::logger->Log("GameMessageHandler", "Failed to find associated entity (%llu), aborting GM (%X)!", objectID, messageID); - return; } + if (messageID != eGameMessageType::READY_FOR_UPDATES) Game::logger->LogDebug("GameMessageHandler", "received game message ID: %i", messageID); + switch (messageID) { case eGameMessageType::UN_USE_BBB_MODEL: { @@ -680,8 +681,20 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System case eGameMessageType::REQUEST_ACTIVITY_EXIT: GameMessages::HandleRequestActivityExit(inStream, entity); break; + case eGameMessageType::ADD_DONATION_ITEM: + GameMessages::HandleAddDonationItem(inStream, entity, sysAddr); + break; + case eGameMessageType::REMOVE_DONATION_ITEM: + GameMessages::HandleRemoveDonationItem(inStream, entity, sysAddr); + break; + case eGameMessageType::CONFIRM_DONATION_ON_PLAYER: + GameMessages::HandleConfirmDonationOnPlayer(inStream, entity); + break; + case eGameMessageType::CANCEL_DONATION_ON_PLAYER: + GameMessages::HandleCancelDonationOnPlayer(inStream, entity); + break; default: - // Game::logger->Log("GameMessageHandler", "Unknown game message ID: %i", messageID); + Game::logger->LogDebug("GameMessageHandler", "Unknown game message ID: %i", messageID); break; } } diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index e5567cfa..020e0abe 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -76,6 +76,7 @@ #include "RacingControlComponent.h" #include "RailActivatorComponent.h" #include "LevelProgressionComponent.h" +#include "DonationVendorComponent.h" // Message includes: #include "dZoneManager.h" @@ -1294,6 +1295,7 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s bitStream.Write(bUpdateOnly); bitStream.Write(vendorItems.size()); + for (const auto& item : vendorItems) { bitStream.Write(item.lot); bitStream.Write(item.sortPriority); @@ -6207,3 +6209,104 @@ void GameMessages::HandleRequestActivityExit(RakNet::BitStream* inStream, Entity if (!entity || !player) return; entity->RequestActivityExit(entity, player_id, canceled); } + +void GameMessages::HandleAddDonationItem(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { + uint32_t count = 1; + bool hasCount = false; + inStream->Read(hasCount); + if (hasCount) inStream->Read(count); + LWOOBJID itemId = LWOOBJID_EMPTY; + inStream->Read(itemId); + if (!itemId) return; + + auto* donationVendorComponent = entity->GetComponent(); + if (!donationVendorComponent) return; + if (donationVendorComponent->GetActivityID() == 0) { + Game::logger->Log("GameMessages", "WARNING: Trying to dontate to a vendor with no activity"); + return; + } + User* user = UserManager::Instance()->GetUser(sysAddr); + if (!user) return; + Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar()); + if (!player) return; + auto* characterComponent = player->GetComponent(); + if (!characterComponent) return; + auto* inventoryComponent = player->GetComponent(); + if (!inventoryComponent) return; + Item* item = inventoryComponent->FindItemById(itemId); + if (!item) return; + if (item->GetCount() < count) return; + characterComponent->SetCurrentInteracting(entity->GetObjectID()); + inventoryComponent->MoveItemToInventory(item, eInventoryType::DONATION, count, true, false, true); +} + +void GameMessages::HandleRemoveDonationItem(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { + bool confirmed = false; + inStream->Read(confirmed); + uint32_t count = 1; + bool hasCount = false; + inStream->Read(hasCount); + if (hasCount) inStream->Read(count); + LWOOBJID itemId = LWOOBJID_EMPTY; + inStream->Read(itemId); + if (!itemId) return; + + User* user = UserManager::Instance()->GetUser(sysAddr); + if (!user) return; + Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar()); + if (!player) return; + + auto* inventoryComponent = player->GetComponent(); + if (!inventoryComponent) return; + + Item* item = inventoryComponent->FindItemById(itemId); + if (!item) return; + if (item->GetCount() < count) return; + inventoryComponent->MoveItemToInventory(item, eInventoryType::BRICKS, count, true, false, true); +} + +void GameMessages::HandleConfirmDonationOnPlayer(RakNet::BitStream* inStream, Entity* entity) { + auto* inventoryComponent = entity->GetComponent(); + if (!inventoryComponent) return; + auto* missionComponent = entity->GetComponent(); + if (!missionComponent) return; + auto* characterComponent = entity->GetComponent(); + if (!characterComponent || !characterComponent->GetCurrentInteracting()) return; + auto* donationEntity = Game::entityManager->GetEntity(characterComponent->GetCurrentInteracting()); + if (!donationEntity) return; + auto* donationVendorComponent = donationEntity->GetComponent(); + if(!donationVendorComponent) return; + if (donationVendorComponent->GetActivityID() == 0) { + Game::logger->Log("GameMessages", "WARNING: Trying to dontate to a vendor with no activity"); + return; + } + auto* inventory = inventoryComponent->GetInventory(eInventoryType::DONATION); + if (!inventory) return; + auto items = inventory->GetItems(); + if (!items.empty()) { + uint32_t count = 0; + for (auto& [itemID, item] : items){ + count += item->GetCount(); + item->RemoveFromInventory(); + } + missionComponent->Progress(eMissionTaskType::DONATION, 0, LWOOBJID_EMPTY, "", count); + LeaderboardManager::SaveScore(entity->GetObjectID(), donationVendorComponent->GetActivityID(), count); + donationVendorComponent->SubmitDonation(count); + Game::entityManager->SerializeEntity(donationEntity); + } + characterComponent->SetCurrentInteracting(LWOOBJID_EMPTY); +} + +void GameMessages::HandleCancelDonationOnPlayer(RakNet::BitStream* inStream, Entity* entity) { + auto* inventoryComponent = entity->GetComponent(); + if (!inventoryComponent) return; + auto* inventory = inventoryComponent->GetInventory(eInventoryType::DONATION); + if (!inventory) return; + auto items = inventory->GetItems(); + for (auto& [itemID, item] : items){ + inventoryComponent->MoveItemToInventory(item, eInventoryType::BRICKS, item->GetCount(), false, false, true); + } + auto* characterComponent = entity->GetComponent(); + if (!characterComponent) return; + characterComponent->SetCurrentInteracting(LWOOBJID_EMPTY); +} diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index bd1224d3..3a9963b4 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -649,6 +649,12 @@ namespace GameMessages { void HandleZoneSummaryDismissed(RakNet::BitStream* inStream, Entity* entity); void HandleRequestActivityExit(RakNet::BitStream* inStream, Entity* entity); + + // Donation vendor + void HandleAddDonationItem(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr); + void HandleRemoveDonationItem(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr); + void HandleConfirmDonationOnPlayer(RakNet::BitStream* inStream, Entity* entity); + void HandleCancelDonationOnPlayer(RakNet::BitStream* inStream, Entity* entity); }; #endif // GAMEMESSAGES_H diff --git a/dGame/dMission/MissionTask.cpp b/dGame/dMission/MissionTask.cpp index 6bc12b72..997fd34e 100644 --- a/dGame/dMission/MissionTask.cpp +++ b/dGame/dMission/MissionTask.cpp @@ -449,6 +449,9 @@ void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string& AddProgress(count); break; } + case eMissionTaskType::DONATION: + AddProgress(count); + break; default: Game::logger->Log("MissionTask", "Invalid mission task type (%i)!", static_cast(type)); return;