From df83f0d847ac8b15a046612a622a008515754a93 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 21 Nov 2023 20:05:15 -0600 Subject: [PATCH] feat: Reward codes (#1308) * feat: reward codes this is for giving rewards across characters as the did in live. Tested that the default config works Tested that all claim codes work Tested that saving and loading claim codes work Tested that mail sends correctly * newlines * include array * delete cascade * newline * address feedback --- dAuthServer/AuthServer.cpp | 2 + .../CDClientDatabase/CDClientManager.cpp | 2 + .../CDClientTables/CDRewardCodesTable.cpp | 47 ++++++++++++++++ .../CDClientTables/CDRewardCodesTable.h | 25 +++++++++ .../CDClientDatabase/CDClientTables/CDTable.h | 1 + .../CDClientTables/CMakeLists.txt | 1 + dDatabase/GameDatabase/GameDatabase.h | 3 +- .../ITables/IAccountsRewardCodes.h | 13 +++++ dDatabase/GameDatabase/MySQL/MySQLDatabase.h | 2 + .../MySQL/Tables/AccountsRewardCodes.cpp | 17 ++++++ .../GameDatabase/MySQL/Tables/CMakeLists.txt | 1 + dGame/dComponents/CharacterComponent.cpp | 56 +++++++++++++++++-- dGame/dComponents/CharacterComponent.h | 5 ++ dGame/dUtilities/SlashCommandHandler.cpp | 8 +++ dNet/AuthPackets.cpp | 20 +++++++ dNet/AuthPackets.h | 2 + dWorldServer/WorldServer.cpp | 5 +- docs/Commands.md | 1 + migrations/dlu/14_reward_codes.sql | 5 ++ resources/authconfig.ini | 8 +++ 20 files changed, 216 insertions(+), 8 deletions(-) create mode 100644 dDatabase/CDClientDatabase/CDClientTables/CDRewardCodesTable.cpp create mode 100644 dDatabase/CDClientDatabase/CDClientTables/CDRewardCodesTable.h create mode 100644 dDatabase/GameDatabase/ITables/IAccountsRewardCodes.h create mode 100644 dDatabase/GameDatabase/MySQL/Tables/AccountsRewardCodes.cpp create mode 100644 migrations/dlu/14_reward_codes.sql diff --git a/dAuthServer/AuthServer.cpp b/dAuthServer/AuthServer.cpp index c6d434b3..7cc28263 100644 --- a/dAuthServer/AuthServer.cpp +++ b/dAuthServer/AuthServer.cpp @@ -94,6 +94,8 @@ int main(int argc, char** argv) { uint32_t framesSinceMasterDisconnect = 0; uint32_t framesSinceLastSQLPing = 0; + AuthPackets::LoadClaimCodes(); + while (!Game::shouldShutdown) { //Check if we're still connected to master: if (!Game::server->GetIsConnectedToMaster()) { diff --git a/dDatabase/CDClientDatabase/CDClientManager.cpp b/dDatabase/CDClientDatabase/CDClientManager.cpp index 465fd4e6..7c2f3953 100644 --- a/dDatabase/CDClientDatabase/CDClientManager.cpp +++ b/dDatabase/CDClientDatabase/CDClientManager.cpp @@ -37,6 +37,7 @@ #include "CDPropertyTemplateTable.h" #include "CDFeatureGatingTable.h" #include "CDRailActivatorComponent.h" +#include "CDRewardCodesTable.h" // Uncomment this to cache the full cdclient database into memory. This will make the server load faster, but will use more memory. // A vanilla CDClient takes about 46MB of memory + the regular world data. @@ -82,6 +83,7 @@ CDClientManager::CDClientManager() { CDRailActivatorComponentTable::Instance().LoadValuesFromDatabase(); CDRarityTableTable::Instance().LoadValuesFromDatabase(); CDRebuildComponentTable::Instance().LoadValuesFromDatabase(); + CDRewardCodesTable::Instance().LoadValuesFromDatabase(); CDRewardsTable::Instance().LoadValuesFromDatabase(); CDScriptComponentTable::Instance().LoadValuesFromDatabase(); CDSkillBehaviorTable::Instance().LoadValuesFromDatabase(); diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDRewardCodesTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDRewardCodesTable.cpp new file mode 100644 index 00000000..2bda73f4 --- /dev/null +++ b/dDatabase/CDClientDatabase/CDClientTables/CDRewardCodesTable.cpp @@ -0,0 +1,47 @@ +#include "CDRewardCodesTable.h" + +void CDRewardCodesTable::LoadValuesFromDatabase() { + + // First, get the size of the table + unsigned int size = 0; + auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM RewardCodes"); + while (!tableSize.eof()) { + size = tableSize.getIntField(0, 0); + + tableSize.nextRow(); + } + + tableSize.finalize(); + + // Reserve the size + this->entries.reserve(size); + + // Now get the data + auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM RewardCodes"); + while (!tableData.eof()) { + CDRewardCode entry; + entry.id = tableData.getIntField("id", -1); + entry.code = tableData.getStringField("code", ""); + entry.attachmentLOT = tableData.getIntField("attachmentLOT", -1); + UNUSED_COLUMN(entry.locStatus = tableData.getIntField("locStatus", -1)); + UNUSED_COLUMN(entry.gate_version = tableData.getStringField("gate_version", "")); + + this->entries.push_back(entry); + tableData.nextRow(); + } +} + +LOT CDRewardCodesTable::GetAttachmentLOT(uint32_t rewardCodeId) const { + for (auto const &entry : this->entries){ + if (rewardCodeId == entry.id) return entry.attachmentLOT; + } + return LOT_NULL; +} + +uint32_t CDRewardCodesTable::GetCodeID(std::string code) const { + for (auto const &entry : this->entries){ + if (code == entry.code) return entry.id; + } + return -1; +} + diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDRewardCodesTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDRewardCodesTable.h new file mode 100644 index 00000000..1010a572 --- /dev/null +++ b/dDatabase/CDClientDatabase/CDClientTables/CDRewardCodesTable.h @@ -0,0 +1,25 @@ +#pragma once + +// Custom Classes +#include "CDTable.h" + + +struct CDRewardCode { + uint32_t id; + std::string code; + LOT attachmentLOT; + UNUSED(uint32_t locStatus); + UNUSED(std::string gate_version); +}; + + +class CDRewardCodesTable : public CDTable { +private: + std::vector entries; + +public: + void LoadValuesFromDatabase(); + const std::vector& GetEntries() const; + LOT GetAttachmentLOT(uint32_t rewardCodeId) const; + uint32_t GetCodeID(std::string code) const; +}; diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDTable.h index e4c11fb9..0a8f29ad 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDTable.h +++ b/dDatabase/CDClientDatabase/CDClientTables/CDTable.h @@ -8,6 +8,7 @@ #include #include #include +#include // CPPLinq #ifdef _WIN32 diff --git a/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt b/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt index 43ff52b2..b2551efa 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt +++ b/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt @@ -31,6 +31,7 @@ set(DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES "CDActivitiesTable.cpp" "CDRailActivatorComponent.cpp" "CDRarityTableTable.cpp" "CDRebuildComponentTable.cpp" + "CDRewardCodesTable.cpp" "CDRewardsTable.cpp" "CDScriptComponentTable.cpp" "CDSkillBehaviorTable.cpp" diff --git a/dDatabase/GameDatabase/GameDatabase.h b/dDatabase/GameDatabase/GameDatabase.h index 7d8c7de9..d874a488 100644 --- a/dDatabase/GameDatabase/GameDatabase.h +++ b/dDatabase/GameDatabase/GameDatabase.h @@ -21,6 +21,7 @@ #include "ICharInfo.h" #include "IAccounts.h" #include "IActivityLog.h" +#include "IAccountsRewardCodes.h" namespace sql { class Statement; @@ -38,7 +39,7 @@ class GameDatabase : public IMail, public ICommandLog, public IPlayerCheatDetections, public IBugReports, public IPropertyContents, public IProperty, public IPetNames, public ICharXml, public IMigrationHistory, public IUgc, public IFriends, public ICharInfo, - public IAccounts, public IActivityLog { + public IAccounts, public IActivityLog, public IAccountsRewardCodes { public: virtual ~GameDatabase() = default; // TODO: These should be made private. diff --git a/dDatabase/GameDatabase/ITables/IAccountsRewardCodes.h b/dDatabase/GameDatabase/ITables/IAccountsRewardCodes.h new file mode 100644 index 00000000..2fcb9d2a --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IAccountsRewardCodes.h @@ -0,0 +1,13 @@ +#ifndef __IACCOUNTSREWARDCODES__H__ +#define __IACCOUNTSREWARDCODES__H__ + +#include +#include + +class IAccountsRewardCodes { +public: + virtual void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) = 0; + virtual std::vector GetRewardCodesByAccountID(const uint32_t account_id) = 0; +}; + +#endif //!__IACCOUNTSREWARDCODES__H__ diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index bed79bb7..7b4e21e4 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -103,6 +103,8 @@ public: std::optional GetDonationTotal(const uint32_t activityId) override; std::optional IsPlaykeyActive(const int32_t playkeyId) override; std::vector GetUgcModels(const LWOOBJID& propertyId) override; + void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override; + std::vector GetRewardCodesByAccountID(const uint32_t account_id) override; private: // Generic query functions that can be used for any query. diff --git a/dDatabase/GameDatabase/MySQL/Tables/AccountsRewardCodes.cpp b/dDatabase/GameDatabase/MySQL/Tables/AccountsRewardCodes.cpp new file mode 100644 index 00000000..7243ce3c --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/AccountsRewardCodes.cpp @@ -0,0 +1,17 @@ +#include "MySQLDatabase.h" + +void MySQLDatabase::InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) { + ExecuteInsert("INSERT IGNORE INTO accounts_rewardcodes (account_id, rewardcode) VALUES (?, ?);", account_id, reward_code); +} + +std::vector MySQLDatabase::GetRewardCodesByAccountID(const uint32_t account_id) { + auto result = ExecuteSelect("SELECT rewardcode FROM accounts_rewardcodes WHERE account_id = ?;", account_id); + + std::vector toReturn; + toReturn.reserve(result->rowsCount()); + while (result->next()) { + toReturn.push_back(result->getUInt("rewardcode")); + } + + return toReturn; +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt b/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt index e9593ba9..c45510b9 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt +++ b/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt @@ -1,5 +1,6 @@ set(DDATABASES_DATABASES_MYSQL_TABLES_SOURCES "Accounts.cpp" + "AccountsRewardCodes.cpp" "ActivityLog.cpp" "BugReports.cpp" "CharInfo.cpp" diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index 8c59b28e..d1b7ee21 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -16,6 +16,10 @@ #include "Amf3.h" #include "eGameMasterLevel.h" #include "eGameActivity.h" +#include "User.h" +#include "Database.h" +#include "CDRewardCodesTable.h" +#include "Mail.h" #include CharacterComponent::CharacterComponent(Entity* parent, Character* character) : Component(parent) { @@ -74,10 +78,14 @@ CharacterComponent::~CharacterComponent() { void CharacterComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) { if (bIsInitialUpdate) { - outBitStream->Write0(); - outBitStream->Write0(); - outBitStream->Write0(); - outBitStream->Write0(); + outBitStream->Write(m_ClaimCodes[0] != 0); + if (m_ClaimCodes[0] != 0) outBitStream->Write(m_ClaimCodes[0]); + outBitStream->Write(m_ClaimCodes[1] != 0); + if (m_ClaimCodes[1] != 0) outBitStream->Write(m_ClaimCodes[1]); + outBitStream->Write(m_ClaimCodes[2] != 0); + if (m_ClaimCodes[2] != 0) outBitStream->Write(m_ClaimCodes[2]); + outBitStream->Write(m_ClaimCodes[3] != 0); + if (m_ClaimCodes[3] != 0) outBitStream->Write(m_ClaimCodes[3]); outBitStream->Write(m_Character->GetHairColor()); outBitStream->Write(m_Character->GetHairStyle()); @@ -186,6 +194,13 @@ void CharacterComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { SetReputation(0); } + character->QueryUnsigned64Attribute("co", &m_ClaimCodes[0]); + character->QueryUnsigned64Attribute("co1", &m_ClaimCodes[1]); + character->QueryUnsigned64Attribute("co2", &m_ClaimCodes[2]); + character->QueryUnsigned64Attribute("co3", &m_ClaimCodes[3]); + + AwardClaimCodes(); + character->QueryInt64Attribute("ls", &m_Uscore); // Load the statistics @@ -308,6 +323,11 @@ void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) { return; } + if (m_ClaimCodes[0] != 0) character->SetAttribute("co", m_ClaimCodes[0]); + if (m_ClaimCodes[1] != 0) character->SetAttribute("co1", m_ClaimCodes[1]); + if (m_ClaimCodes[2] != 0) character->SetAttribute("co2", m_ClaimCodes[2]); + if (m_ClaimCodes[3] != 0) character->SetAttribute("co3", m_ClaimCodes[3]); + character->SetAttribute("ls", m_Uscore); // Custom attribute to keep track of reputation. character->SetAttribute("rpt", GetReputation()); @@ -738,3 +758,31 @@ void CharacterComponent::UpdateClientMinimap(bool showFaction, std::string ventu arrayToSend.Insert(ventureVisionType, showFaction); GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent ? m_Parent->GetSystemAddress() : UNASSIGNED_SYSTEM_ADDRESS, "SetFactionVisibility", arrayToSend); } + +void CharacterComponent::AwardClaimCodes() { + if (!m_Parent) return; + auto* user = m_Parent->GetParentUser(); + if (!user) return; + + auto rewardCodes = Database::Get()->GetRewardCodesByAccountID(user->GetAccountID()); + if (rewardCodes.empty()) return; + + auto* cdrewardCodes = CDClientManager::Instance().GetTable(); + for (auto const rewardCode: rewardCodes){ + LOG_DEBUG("Processing RewardCode %i", rewardCode); + const uint32_t rewardCodeIndex = rewardCode >> 6; + const uint32_t bitIndex = rewardCode % 64; + if (GeneralUtils::CheckBit(m_ClaimCodes[rewardCodeIndex], bitIndex)) continue; + m_ClaimCodes[rewardCodeIndex] = GeneralUtils::SetBit(m_ClaimCodes[rewardCodeIndex], bitIndex); + + // Don't send it on this one since it's default and the mail doesn't make sense + if (rewardCode == 30) continue; + + auto attachmentLOT = cdrewardCodes->GetAttachmentLOT(rewardCode); + std::ostringstream subject; + subject << "%[RewardCodes_" << rewardCode << "_subjectText]"; + std::ostringstream body; + body << "%[RewardCodes_" << rewardCode << "_bodyText]"; + Mail::SendMail(LWOOBJID_EMPTY, "%[MAIL_SYSTEM_NOTIFICATION]", m_Parent, subject.str(), body.str(), attachmentLOT, 1); + } +} diff --git a/dGame/dComponents/CharacterComponent.h b/dGame/dComponents/CharacterComponent.h index 5bafb3df..4222ef4a 100644 --- a/dGame/dComponents/CharacterComponent.h +++ b/dGame/dComponents/CharacterComponent.h @@ -10,6 +10,7 @@ #include "CDMissionsTable.h" #include "tinyxml2.h" #include "eReplicaComponentType.h" +#include enum class eGameActivity : uint32_t; @@ -566,6 +567,10 @@ private: LWOOBJID m_LastRocketItemID = LWOOBJID_EMPTY; LWOOBJID m_CurrentInteracting = LWOOBJID_EMPTY; + + std::array m_ClaimCodes{}; + + void AwardClaimCodes(); }; #endif // CHARACTERCOMPONENT_H diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 8444bba7..13fc3ded 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -83,6 +83,7 @@ #include "eChatInternalMessageType.h" #include "eMasterMessageType.h" +#include "CDRewardCodesTable.h" #include "CDObjectsTable.h" #include "CDZoneTableTable.h" #include "ePlayerFlag.h" @@ -1911,6 +1912,13 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } + if (chatCommand == "setrewardcode" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { + auto* cdrewardCodes = CDClientManager::Instance().GetTable(); + + auto id = cdrewardCodes->GetCodeID(args[0]); + if (id != -1) Database::Get()->InsertRewardCode(user->GetAccountID(), id); + } + if (chatCommand == "inspect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { Entity* closest = nullptr; diff --git a/dNet/AuthPackets.cpp b/dNet/AuthPackets.cpp index f0b22b4e..6ee38028 100644 --- a/dNet/AuthPackets.cpp +++ b/dNet/AuthPackets.cpp @@ -29,6 +29,22 @@ #include "eMasterMessageType.h" #include "eGameMasterLevel.h" +namespace { + std::vector claimCodes; +} + +void AuthPackets::LoadClaimCodes() { + if(!claimCodes.empty()) return; + auto rcstring = Game::config->GetValue("rewardcodes"); + auto codestrings = GeneralUtils::SplitString(rcstring, ','); + for(auto const &codestring: codestrings){ + uint32_t code = -1; + if(GeneralUtils::TryParse(codestring, code) && code != -1){ + claimCodes.push_back(code); + } + } +} + void AuthPackets::HandleHandshake(dServer* server, Packet* packet) { RakNet::BitStream inStream(packet->data, packet->length, false); uint64_t header = inStream.Read(header); @@ -129,6 +145,10 @@ void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) { AuthPackets::SendLoginResponse(server, system, eLoginResponse::SUCCESS, "", zoneIP, zonePort, username); }); } + + for(auto const code: claimCodes){ + Database::Get()->InsertRewardCode(accountInfo->id, code); + } } void AuthPackets::SendLoginResponse(dServer* server, const SystemAddress& sysAddr, eLoginResponse responseCode, const std::string& errorMsg, const std::string& wServerIP, uint16_t wServerPort, std::string username) { diff --git a/dNet/AuthPackets.h b/dNet/AuthPackets.h index 0f004ca4..eb275a46 100644 --- a/dNet/AuthPackets.h +++ b/dNet/AuthPackets.h @@ -15,6 +15,8 @@ namespace AuthPackets { 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 LoadClaimCodes(); + } #endif // AUTHPACKETS_H diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index cfd7d157..f916e40c 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -1033,9 +1033,8 @@ void HandlePacket(Packet* packet) { Game::entityManager->ConstructAllEntities(packet->systemAddress); auto* characterComponent = player->GetComponent(); - if (characterComponent) { - player->GetComponent()->RocketUnEquip(player); - } + if (!characterComponent) return; + characterComponent->RocketUnEquip(player); // Do charxml fixes here auto* levelComponent = player->GetComponent(); diff --git a/docs/Commands.md b/docs/Commands.md index 16202056..0ba7d86e 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -113,6 +113,7 @@ These commands are primarily for development and testing. The usage of many of t |setfaction|`/setfaction `|Clears the users current factions and sets it|8| |addfaction|`/addfaction `|Add the faction to the users list of factions|8| |getfactions|`/getfactions`|Shows the player's factions|8| +|setrewardcode|`/setrewardcode `|Sets the rewardcode for the account you are logged into if it's a valid rewardcode, See cdclient table `RewardCodes`|8| ## Detailed `/inspect` Usage `/inspect (-m | -a | -s | -p | -f (faction) | -t)` diff --git a/migrations/dlu/14_reward_codes.sql b/migrations/dlu/14_reward_codes.sql new file mode 100644 index 00000000..a439df2e --- /dev/null +++ b/migrations/dlu/14_reward_codes.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS accounts_rewardcodes ( + account_id INT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + rewardcode INT NOT NULL, + PRIMARY KEY (account_id, rewardcode) +); diff --git a/resources/authconfig.ini b/resources/authconfig.ini index ec414bc0..62a5c6de 100644 --- a/resources/authconfig.ini +++ b/resources/authconfig.ini @@ -4,3 +4,11 @@ port=1001 # 0 or 1, should ignore playkeys # If 1 everyone with an account will be able to login, regardless of if they have a key or not dont_use_keys=0 + +# list of rewardcodes to set on the accounts by default +# ex: 30,1,0,3 +# See RewardCodes in the CDclient for what codes exist +# Default 4,30 +# 4 allows LEGOClub access +# 30 makes the client not consume bricks when in bbb mode +rewardcodes=4,30