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