diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 1865c679..b91a920d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -18,6 +18,14 @@ body: - label: > I have pulled the latest version of the main branch of DarkflameServer and have confirmed that the issue exists there. required: true + - type: input + id: server-version + attributes: + label: DarkflameServer Version + description: > + DarkflameServer version or commit SHA (can be obtained with `git rev-parse --short HEAD`) + validations: + required: true - type: textarea id: problem attributes: @@ -29,7 +37,7 @@ body: - type: textarea id: reproduction attributes: - label: Reproduction steps + label: Reproduction Steps description: > Please provide a concise list of steps needed to reproduce this issue. validations: diff --git a/.github/ISSUE_TEMPLATE/installation_issue.yaml b/.github/ISSUE_TEMPLATE/installation_issue.yaml index 2fd9a84f..43211e62 100644 --- a/.github/ISSUE_TEMPLATE/installation_issue.yaml +++ b/.github/ISSUE_TEMPLATE/installation_issue.yaml @@ -12,6 +12,14 @@ body: - label: > I have read the [installation guide](https://github.com/DarkflameUniverse/DarkflameServer/blob/main/README.md). required: true + - type: input + id: server-version + attributes: + label: DarkflameServer Version + description: > + DarkflameServer version or commit SHA (can be obtained with `git rev-parse --short HEAD`) + validations: + required: true - type: dropdown id: platform attributes: diff --git a/.github/ISSUE_TEMPLATE/performance_issue.yaml b/.github/ISSUE_TEMPLATE/performance_issue.yaml index 420a5381..b5ec2bcb 100644 --- a/.github/ISSUE_TEMPLATE/performance_issue.yaml +++ b/.github/ISSUE_TEMPLATE/performance_issue.yaml @@ -15,6 +15,22 @@ body: - label: > I have pulled the latest version of the main branch of DarkflameServer and have confirmed that the issue exists there. required: true + - type: input + id: server-version + attributes: + label: DarkflameServer Version + description: > + DarkflameServer version or commit SHA (can be obtained with `git rev-parse --short HEAD`) + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: > + Please include the environment you're running DarkflameServer on (for example: Windows, macOS, Ubuntu, WSL, etc), available memory, number of CPU cores. + validations: + required: true - type: textarea id: example attributes: diff --git a/CMakeLists.txt b/CMakeLists.txt index 64bf5c22..9492abcb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,7 +53,7 @@ FetchContent_Declare( FetchContent_Declare( zlib - URL http://github.com/madler/zlib/archive/refs/tags/v1.2.11.zip + URL https://github.com/madler/zlib/archive/refs/tags/v1.2.11.zip URL_HASH MD5=9d6a627693163bbbf3f26403a3a0b0b1 ) diff --git a/CMakeVariables.txt b/CMakeVariables.txt index 01e03d87..b50b4446 100644 --- a/CMakeVariables.txt +++ b/CMakeVariables.txt @@ -1,6 +1,6 @@ PROJECT_VERSION_MAJOR=1 PROJECT_VERSION_MINOR=0 -PROJECT_VERSION_PATCH=0 +PROJECT_VERSION_PATCH=2 # LICENSE LICENSE=AGPL-3.0 # The network version. diff --git a/README.md b/README.md index 47f116db..42848839 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,7 @@ The client script for the survival minigame has a bug in it which can cause the * Change `PlayerReady(self)` to `onPlayerReady(self)` * Save the file, overriding readonly mode if required -If you still experience the bug, try deleting/renaming `res/pack/scripts.pak`. +If you still experience the bug, try deleting/renaming `res/pack/scripts.pk`. ### Brick-By-Brick building diff --git a/dAuthServer/AuthServer.cpp b/dAuthServer/AuthServer.cpp index 67590fa0..fef3124b 100644 --- a/dAuthServer/AuthServer.cpp +++ b/dAuthServer/AuthServer.cpp @@ -57,7 +57,7 @@ int main(int argc, char** argv) { Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password); } catch (sql::SQLException& ex) { Game::logger->Log("AuthServer", "Got an error while connecting to the database: %s\n", ex.what()); - Database::Destroy(); + Database::Destroy("AuthServer"); delete Game::server; delete Game::logger; return 0; @@ -143,11 +143,12 @@ int main(int argc, char** argv) { } //Delete our objects here: - Database::Destroy(); + Database::Destroy("AuthServer"); delete Game::server; delete Game::logger; - return 0; + exit(EXIT_SUCCESS); + return EXIT_SUCCESS; } dLogger * SetupLogger() { diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index 81904d41..9ba3ba1b 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -61,7 +61,7 @@ int main(int argc, char** argv) { } catch (sql::SQLException& ex) { Game::logger->Log("ChatServer", "Got an error while connecting to the database: %s\n", ex.what()); - Database::Destroy(); + Database::Destroy("ChatServer"); delete Game::server; delete Game::logger; return 0; @@ -150,11 +150,12 @@ int main(int argc, char** argv) { } //Delete our objects here: - Database::Destroy(); + Database::Destroy("ChatServer"); delete Game::server; delete Game::logger; - return 0; + exit(EXIT_SUCCESS); + return EXIT_SUCCESS; } dLogger * SetupLogger() { diff --git a/dCommon/dCommonVars.h b/dCommon/dCommonVars.h index 4920249d..cbecdd37 100644 --- a/dCommon/dCommonVars.h +++ b/dCommon/dCommonVars.h @@ -469,6 +469,9 @@ enum eRebuildState : uint32_t { REBUILD_INCOMPLETE }; +/** + * The loot source's type. + */ enum eLootSourceType : int32_t { LOOT_SOURCE_NONE = 0, LOOT_SOURCE_CHEST, @@ -490,7 +493,7 @@ enum eLootSourceType : int32_t { LOOT_SOURCE_CLAIMCODE, LOOT_SOURCE_CONSUMPTION, LOOT_SOURCE_CRAFTING, - LOOT_SOURCE_LEVELREWARD, + LOOT_SOURCE_LEVEL_REWARD, LOOT_SOURCE_RELOCATE }; diff --git a/dDatabase/Database.cpp b/dDatabase/Database.cpp index cdadbbaa..7ba138cf 100644 --- a/dDatabase/Database.cpp +++ b/dDatabase/Database.cpp @@ -26,9 +26,10 @@ void Database::Connect(const string& host, const string& database, const string& con->setClientOption("MYSQL_OPT_RECONNECT", &myTrue); } //Connect -void Database::Destroy() { +void Database::Destroy(std::string source) { if (!con) return; - Game::logger->Log("Database", "Destroying MySQL connection!\n"); + if (source != "") Game::logger->Log("Database", "Destroying MySQL connection from %s!\n", source.c_str()); + else Game::logger->Log("Database", "Destroying MySQL connection!\n"); con->close(); delete con; } //Destroy diff --git a/dDatabase/Database.h b/dDatabase/Database.h index 8e4cf5dc..6e458065 100644 --- a/dDatabase/Database.h +++ b/dDatabase/Database.h @@ -22,7 +22,7 @@ private: public: static void Connect(const std::string& host, const std::string& database, const std::string& username, const std::string& password); - static void Destroy(); + static void Destroy(std::string source=""); static sql::Statement* CreateStmt(); static sql::PreparedStatement* CreatePreppedStmt(const std::string& query); }; diff --git a/dDatabase/Tables/CDMissionsTable.h b/dDatabase/Tables/CDMissionsTable.h index c083bd31..c961bb80 100644 --- a/dDatabase/Tables/CDMissionsTable.h +++ b/dDatabase/Tables/CDMissionsTable.h @@ -3,6 +3,7 @@ // Custom Classes #include "CDTable.h" #include +#include /*! \file CDMissionsTable.hpp @@ -17,9 +18,9 @@ struct CDMissions { int UISortOrder; //!< The UI Sort Order for the mission int offer_objectID; //!< The LOT of the mission giver int target_objectID; //!< The LOT of the mission's target - __int64 reward_currency; //!< The amount of currency to reward the player + int64_t reward_currency; //!< The amount of currency to reward the player int LegoScore; //!< The amount of LEGO Score to reward the player - __int64 reward_reputation; //!< The reputation to award the player + int64_t reward_reputation; //!< The reputation to award the player bool isChoiceReward; //!< Whether or not the user has the option to choose their loot int reward_item1; //!< The first rewarded item int reward_item1_count; //!< The count of the first item to be rewarded @@ -40,7 +41,7 @@ struct CDMissions { int reward_maxwidget; //!< ??? int reward_maxwallet; //!< ??? bool repeatable; //!< Whether or not this mission can be repeated (for instance, is it a daily mission) - __int64 reward_currency_repeatable; //!< The repeatable reward + int64_t reward_currency_repeatable; //!< The repeatable reward int reward_item1_repeatable; //!< The first rewarded item int reward_item1_repeat_count; //!< The count of the first item to be rewarded int reward_item2_repeatable; //!< The second rewarded item @@ -55,7 +56,7 @@ struct CDMissions { std::string prereqMissionID; //!< A '|' seperated list of prerequisite missions bool localize; //!< Whether or not to localize the mission bool inMOTD; //!< In Match of the Day(?) - __int64 cooldownTime; //!< The mission cooldown time + int64_t cooldownTime; //!< The mission cooldown time bool isRandom; //!< ??? std::string randomPool; //!< ??? int UIPrereqID; //!< ??? diff --git a/dGame/Character.cpp b/dGame/Character.cpp index d396382c..ce61ae70 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -534,7 +534,7 @@ void Character::OnZoneLoad() */ if (HasPermission(PermissionMap::Old)) { if (GetCoins() > 1000000) { - SetCoins(1000000, LOOT_SOURCE_NONE); + SetCoins(1000000, eLootSourceType::LOOT_SOURCE_NONE); } } diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index dd6df1d4..132a0eb7 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -22,6 +22,7 @@ #include "Component.h" #include "ControllablePhysicsComponent.h" #include "RenderComponent.h" +#include "RocketLaunchLupComponent.h" #include "CharacterComponent.h" #include "DestroyableComponent.h" #include "BuffComponent.h" @@ -231,7 +232,8 @@ void Entity::Initialize() m_Components.insert(std::make_pair(COMPONENT_TYPE_RACING_STATS, nullptr)); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_ITEM) > 0) { + PetComponent* petComponent; + if (compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_ITEM) > 0 && !TryGetComponent(COMPONENT_TYPE_PET, petComponent)) { m_Components.insert(std::make_pair(COMPONENT_TYPE_ITEM, nullptr)); } @@ -346,7 +348,7 @@ void Entity::Initialize() } /** - * Multiple components require te destructible component. + * Multiple components require the destructible component. */ int buffComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_BUFF); @@ -455,9 +457,10 @@ void Entity::Initialize() else comp = new InventoryComponent(this); m_Components.insert(std::make_pair(COMPONENT_TYPE_INVENTORY, comp)); } - - if (compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_ROCKET_LAUNCH_LUP) > 0) { - m_Components.insert(std::make_pair(COMPONENT_TYPE_ROCKET_LAUNCH_LUP, nullptr)); + // if this component exists, then we initialize it. it's value is always 0 + if (compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_ROCKET_LAUNCH_LUP, -1) != -1) { + auto comp = new RocketLaunchLupComponent(this); + m_Components.insert(std::make_pair(COMPONENT_TYPE_ROCKET_LAUNCH_LUP, comp)); } /** @@ -494,13 +497,6 @@ void Entity::Initialize() std::string customScriptServer; bool hasCustomServerScript = false; - // Custom script for the LUP teleporter - if (m_TemplateID == 14333) - { - hasCustomServerScript = true; - customScriptServer = "scripts\\02_server\\DLU\\L_SB_LUP_TELEPORT.lua"; - } - const auto customScriptServerName = GetVarAsString(u"custom_script_server"); const auto customScriptClientName = GetVarAsString(u"custom_script_client"); @@ -1636,7 +1632,7 @@ void Entity::PickupItem(const LWOOBJID& objectID) { } } else { - inv->AddItem(p.second.lot, p.second.count, INVALID, {}, LWOOBJID_EMPTY, true, false, LWOOBJID_EMPTY, INVALID, 1); + inv->AddItem(p.second.lot, p.second.count, eLootSourceType::LOOT_SOURCE_PICKUP, eInventoryType::INVALID, {}, LWOOBJID_EMPTY, true, false, LWOOBJID_EMPTY, eInventoryType::INVALID, 1); } } } @@ -2172,3 +2168,15 @@ void Entity::AddToGroup(const std::string& group) { m_Groups.push_back(group); } } + +void Entity::RetroactiveVaultSize() { + auto inventoryComponent = GetComponent(); + if (!inventoryComponent) return; + + auto itemsVault = inventoryComponent->GetInventory(eInventoryType::VAULT_ITEMS); + auto modelVault = inventoryComponent->GetInventory(eInventoryType::VAULT_MODELS); + + if (itemsVault->GetSize() == modelVault->GetSize()) return; + + modelVault->SetSize(itemsVault->GetSize()); +} diff --git a/dGame/Entity.h b/dGame/Entity.h index 31b2b303..cef7b97f 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -220,7 +220,11 @@ public: /* * Utility */ - + /** + * Retroactively corrects the model vault size due to incorrect initialization in a previous patch. + * + */ + void RetroactiveVaultSize(); bool GetBoolean(const std::u16string& name) const; int32_t GetI32(const std::u16string& name) const; int64_t GetI64(const std::u16string& name) const; diff --git a/dGame/EntityManager.cpp b/dGame/EntityManager.cpp index b9c8763b..aacb2bc8 100644 --- a/dGame/EntityManager.cpp +++ b/dGame/EntityManager.cpp @@ -24,12 +24,13 @@ EntityManager* EntityManager::m_Address = nullptr; std::vector EntityManager::m_GhostingExcludedZones = { // Small zones 1000, - + // Racing zones 1203, + 1261, 1303, 1403, - + // Property zones 1150, 1151, @@ -45,7 +46,7 @@ std::vector EntityManager::m_GhostingExcludedLOTs = { 9524, 12408, - // AG - Fotrace + // AG - Footrace 4967 }; diff --git a/dGame/TradingManager.cpp b/dGame/TradingManager.cpp index d0ec08d8..04a0501d 100644 --- a/dGame/TradingManager.cpp +++ b/dGame/TradingManager.cpp @@ -60,7 +60,7 @@ void Trade::SetCoins(LWOOBJID participant, uint64_t coins) { m_CoinsA = coins; } - else if (participant = m_ParticipantB) + else if (participant == m_ParticipantB) { m_CoinsB = coins; } @@ -72,7 +72,7 @@ void Trade::SetItems(LWOOBJID participant, std::vector items) { m_ItemsA = items; } - else if (participant = m_ParticipantB) + else if (participant == m_ParticipantB) { m_ItemsB = items; } @@ -151,8 +151,8 @@ void Trade::Complete() if (inventoryA == nullptr || inventoryB == nullptr || characterA == nullptr || characterB == nullptr || missionsA == nullptr || missionsB == nullptr) return; - characterA->SetCoins(characterA->GetCoins() - m_CoinsA + m_CoinsB, LOOT_SOURCE_TRADE); - characterB->SetCoins(characterB->GetCoins() - m_CoinsB + m_CoinsA, LOOT_SOURCE_TRADE); + characterA->SetCoins(characterA->GetCoins() - m_CoinsA + m_CoinsB, eLootSourceType::LOOT_SOURCE_TRADE); + characterB->SetCoins(characterB->GetCoins() - m_CoinsB + m_CoinsA, eLootSourceType::LOOT_SOURCE_TRADE); for (const auto& tradeItem : m_ItemsA) { @@ -170,12 +170,12 @@ void Trade::Complete() for (const auto& tradeItem : m_ItemsA) { - inventoryB->AddItem(tradeItem.itemLot, tradeItem.itemCount); + inventoryB->AddItem(tradeItem.itemLot, tradeItem.itemCount, eLootSourceType::LOOT_SOURCE_TRADE); } for (const auto& tradeItem : m_ItemsB) { - inventoryA->AddItem(tradeItem.itemLot, tradeItem.itemCount); + inventoryA->AddItem(tradeItem.itemLot, tradeItem.itemCount, eLootSourceType::LOOT_SOURCE_TRADE); } TradingManager::Instance()->CancelTrade(m_TradeId); diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 1535364c..1d14cb0a 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -6,6 +6,7 @@ #include "Database.h" #include "Game.h" +#include "dLogger.h" #include "User.h" #include #include "Character.h" @@ -68,16 +69,6 @@ void UserManager::Initialize() { StripCR(line); m_PreapprovedNames.push_back(line); } - - //Load custom ones from MySQL too: - /*sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT name FROM approvedNames;"); - sql::ResultSet* res = stmt->executeQuery(); - while (res->next()) { - m_PreapprovedNames.push_back(res->getString(1)); - } - - delete res; - delete stmt;*/ } UserManager::~UserManager() { @@ -244,7 +235,6 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) uint32_t middleNameIndex = PacketUtils::ReadPacketU32(78, packet); uint32_t lastNameIndex = PacketUtils::ReadPacketU32(82, packet); std::string predefinedName = GetPredefinedName(firstNameIndex, middleNameIndex, lastNameIndex); - Game::logger->Log("UserManager", "Got predefined name: %s\n", predefinedName.c_str()); uint32_t shirtColor = PacketUtils::ReadPacketU32(95, packet); uint32_t shirtStyle = PacketUtils::ReadPacketU32(99, packet); @@ -261,16 +251,23 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) LOT pantsLOT = FindCharPantsID(pantsColor); if (name != "" && !UserManager::IsNameAvailable(name)) { + Game::logger->Log("UserManager", "AccountID: %i chose unavailable name: %s\n", u->GetAccountID(), name.c_str()); WorldPackets::SendCharacterCreationResponse(sysAddr, CREATION_RESPONSE_CUSTOM_NAME_IN_USE); return; } if (!IsNameAvailable(predefinedName)) { + Game::logger->Log("UserManager", "AccountID: %i chose unavailable predefined name: %s\n", u->GetAccountID(), predefinedName.c_str()); WorldPackets::SendCharacterCreationResponse(sysAddr, CREATION_RESPONSE_PREDEFINED_NAME_IN_USE); return; } - Game::logger->Log("UserManager", "AccountID: %i is creating a character with name: %s\n", u->GetAccountID(), name.c_str()); + if (name == "") { + Game::logger->Log("UserManager", "AccountID: %i is creating a character with predefined name: %s\n", u->GetAccountID(), predefinedName.c_str()); + } + else { + Game::logger->Log("UserManager", "AccountID: %i is creating a character with name: %s (temporary: %s)\n", u->GetAccountID(), name.c_str(), predefinedName.c_str()); + } //Now that the name is ok, we can get an objectID from Master: ObjectIDManager::Instance()->RequestPersistentID([=](uint32_t objectID) { @@ -294,7 +291,7 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) xml << "ls=\"0\" lzx=\"-626.5847\" lzy=\"613.3515\" lzz=\"-28.6374\" lzrx=\"0.0\" lzry=\"0.7015\" lzrz=\"0.0\" lzrw=\"0.7126\" "; xml << "stt=\"0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;\">"; xml << ""; - xml << ""; + xml << ""; std::string xmlSave1 = xml.str(); ObjectIDManager::Instance()->RequestPersistentID([=](uint32_t idforshirt) { @@ -566,122 +563,38 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID } } -uint32_t GetShirtColorId(uint32_t color) { - - // get the index of the color in shirtColorVector - auto colorId = std::find(shirtColorVector.begin(), shirtColorVector.end(), color); - return color = std::distance(shirtColorVector.begin(), colorId); -} - uint32_t FindCharShirtID(uint32_t shirtColor, uint32_t shirtStyle) { - - shirtStyle--; // to start at 0 instead of 1 - uint32_t stylesCount = 34; - uint32_t colorId = GetShirtColorId(shirtColor); - - uint32_t startID = 4049; // item ID of the shirt with color 0 (red) and style 0 (plain) - - // For some reason, if the shirt style is 34 - 39, - // The ID is different than the original... Was this because - // these shirts were added later? - if (shirtStyle >= 34) { - startID = 5730; // item ID of the shirt with color 0 (red) and style 34 (butterflies) - shirtStyle -= stylesCount; //change style from range 35-40 to range 0-5 - stylesCount = 6; + try { + std::string shirtQuery = "select obj.id from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == \"character create shirt\" AND icc.color1 == "; + shirtQuery += std::to_string(shirtColor); + shirtQuery += " AND icc.decal == "; + shirtQuery = shirtQuery + std::to_string(shirtStyle); + auto tableData = CDClientDatabase::ExecuteQuery(shirtQuery); + auto shirtLOT = tableData.getIntField(0, -1); + tableData.finalize(); + return shirtLOT; + } + catch (const std::exception&){ + Game::logger->Log("Character Create", "Failed to execute query! Using backup..."); + // in case of no shirt found in CDServer, return problematic red vest. + return 4069; } - - // Get the final ID of the shirt - uint32_t shirtID = startID + (colorId * stylesCount) + shirtStyle; - - return shirtID; } uint32_t FindCharPantsID(uint32_t pantsColor) { - uint32_t pantsID = 2508; - - switch (pantsColor) { - case 0: { - pantsID = PANTS_BRIGHT_RED; - break; + try { + std::string pantsQuery = "select obj.id from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == \"cc pants\" AND icc.color1 == "; + pantsQuery += std::to_string(pantsColor); + auto tableData = CDClientDatabase::ExecuteQuery(pantsQuery); + auto pantsLOT = tableData.getIntField(0, -1); + tableData.finalize(); + return pantsLOT; } - - case 1: { - pantsID = PANTS_BRIGHT_BLUE; - break; + catch (const std::exception&){ + Game::logger->Log("Character Create", "Failed to execute query! Using backup..."); + // in case of no pants color found in CDServer, return red pants. + return 2508; } - - case 3: { - pantsID = PANTS_DARK_GREEN; - break; - } - - case 5: { - pantsID = PANTS_BRIGHT_ORANGE; - break; - } - - case 6: { - pantsID = PANTS_BLACK; - break; - } - - case 7: { - pantsID = PANTS_DARK_STONE_GRAY; - break; - } - - case 8: { - pantsID = PANTS_MEDIUM_STONE_GRAY; - break; - } - - case 9: { - pantsID = PANTS_REDDISH_BROWN; - break; - } - - case 10: { - pantsID = PANTS_WHITE; - break; - } - - case 11: { - pantsID = PANTS_MEDIUM_BLUE; - break; - } - - case 13: { - pantsID = PANTS_DARK_RED; - break; - } - - case 14: { - pantsID = PANTS_EARTH_BLUE; - break; - } - - case 15: { - pantsID = PANTS_EARTH_GREEN; - break; - } - - case 16: { - pantsID = PANTS_BRICK_YELLOW; - break; - } - - case 84: { - pantsID = PANTS_SAND_BLUE; - break; - } - - case 96: { - pantsID = PANTS_SAND_GREEN; - break; - } - } - - return pantsID; } void UserManager::SaveAllActiveCharacters() { diff --git a/dGame/UserManager.h b/dGame/UserManager.h index b29cf501..a5db6979 100644 --- a/dGame/UserManager.h +++ b/dGame/UserManager.h @@ -44,7 +44,6 @@ public: private: static UserManager* m_Address; //Singleton - //std::vector m_Users; std::map m_Users; std::vector m_UsersToDelete; @@ -54,43 +53,4 @@ private: std::vector m_PreapprovedNames; }; -enum CharCreatePantsColor : uint32_t { - PANTS_BRIGHT_RED = 2508, - PANTS_BRIGHT_ORANGE = 2509, - PANTS_BRICK_YELLOW = 2511, - PANTS_MEDIUM_BLUE = 2513, - PANTS_SAND_GREEN = 2514, - PANTS_DARK_GREEN = 2515, - PANTS_EARTH_GREEN = 2516, - PANTS_EARTH_BLUE = 2517, - PANTS_BRIGHT_BLUE = 2519, - PANTS_SAND_BLUE = 2520, - PANTS_DARK_STONE_GRAY = 2521, - PANTS_MEDIUM_STONE_GRAY = 2522, - PANTS_WHITE = 2523, - PANTS_BLACK = 2524, - PANTS_REDDISH_BROWN = 2526, - PANTS_DARK_RED = 2527 -}; - -const std::vector shirtColorVector { - 0, // BRIGHT_RED - 1, // BRIGHT_BLUE - 2, // BRIGHT_YELLOW - 3, // DARK_GREEN - 5, // BRIGHT_ORANGE - 6, // BLACK - 7, // DARK_STONE_GRAY - 8, // MEDIUM_STONE_GRAY - 9, // REDDISH_BROWN - 10, // WHITE - 11, // MEDIUM_BLUE - 13, // DARK_RED - 14, // EARTH_BLUE - 15, // EARTH_GREEN - 16, // BRICK_YELLOW - 84, // SAND_BLUE - 96 // SAND_GREEN -}; - #endif // USERMANAGER_H diff --git a/dGame/dBehaviors/AndBehavior.cpp b/dGame/dBehaviors/AndBehavior.cpp index 231b39c9..5fc1e113 100644 --- a/dGame/dBehaviors/AndBehavior.cpp +++ b/dGame/dBehaviors/AndBehavior.cpp @@ -19,6 +19,12 @@ void AndBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStre } } +void AndBehavior::UnCast(BehaviorContext* context, const BehaviorBranchContext branch) { + for (auto behavior : this->m_behaviors) { + behavior->UnCast(context, branch); + } +} + void AndBehavior::Load() { const auto parameters = GetParameterNames(); diff --git a/dGame/dBehaviors/AndBehavior.h b/dGame/dBehaviors/AndBehavior.h index 2b7d95e6..9cbce569 100644 --- a/dGame/dBehaviors/AndBehavior.h +++ b/dGame/dBehaviors/AndBehavior.h @@ -20,5 +20,7 @@ public: void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; + void UnCast(BehaviorContext* context, BehaviorBranchContext branch) override; + void Load() override; }; diff --git a/dGame/dBehaviors/BasicAttackBehavior.cpp b/dGame/dBehaviors/BasicAttackBehavior.cpp index 399efec1..a9a58245 100644 --- a/dGame/dBehaviors/BasicAttackBehavior.cpp +++ b/dGame/dBehaviors/BasicAttackBehavior.cpp @@ -14,7 +14,7 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi auto* destroyableComponent = entity->GetComponent(); if (destroyableComponent != nullptr) { PlayFx(u"onhit", entity->GetObjectID()); - destroyableComponent->Damage(this->m_maxDamage, context->originator); + destroyableComponent->Damage(this->m_maxDamage, context->originator, context->skillID); } this->m_onSuccess->Handle(context, bitStream, branch); @@ -56,7 +56,7 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi auto* destroyableComponent = entity->GetComponent(); if (destroyableComponent != nullptr) { PlayFx(u"onhit", entity->GetObjectID()); - destroyableComponent->Damage(damageDealt, context->originator); + destroyableComponent->Damage(damageDealt, context->originator, context->skillID); } } } @@ -113,7 +113,7 @@ void BasicAttackBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* auto* destroyableComponent = entity->GetComponent(); if (damage != 0 && destroyableComponent != nullptr) { PlayFx(u"onhit", entity->GetObjectID(), 1); - destroyableComponent->Damage(damage, context->originator, false); + destroyableComponent->Damage(damage, context->originator, context->skillID, false); context->ScheduleUpdate(branch.target); } } diff --git a/dGame/dBehaviors/Behavior.cpp b/dGame/dBehaviors/Behavior.cpp index 7f00f6be..df24af92 100644 --- a/dGame/dBehaviors/Behavior.cpp +++ b/dGame/dBehaviors/Behavior.cpp @@ -18,6 +18,7 @@ #include "AreaOfEffectBehavior.h" #include "DurationBehavior.h" #include "TacArcBehavior.h" +#include "LootBuffBehavior.h" #include "AttackDelayBehavior.h" #include "BasicAttackBehavior.h" #include "ChainBehavior.h" @@ -172,7 +173,9 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId) behavior = new SpeedBehavior(behaviorId); break; case BehaviorTemplates::BEHAVIOR_DARK_INSPIRATION: break; - case BehaviorTemplates::BEHAVIOR_LOOT_BUFF: break; + case BehaviorTemplates::BEHAVIOR_LOOT_BUFF: + behavior = new LootBuffBehavior(behaviorId); + break; case BehaviorTemplates::BEHAVIOR_VENTURE_VISION: break; case BehaviorTemplates::BEHAVIOR_SPAWN_OBJECT: behavior = new SpawnBehavior(behaviorId); diff --git a/dGame/dBehaviors/BehaviorContext.cpp b/dGame/dBehaviors/BehaviorContext.cpp index fc67a82d..c7bf912f 100644 --- a/dGame/dBehaviors/BehaviorContext.cpp +++ b/dGame/dBehaviors/BehaviorContext.cpp @@ -64,8 +64,8 @@ void BehaviorContext::RegisterSyncBehavior(const uint32_t syncId, Behavior* beha void BehaviorContext::RegisterTimerBehavior(Behavior* behavior, const BehaviorBranchContext& branchContext, const LWOOBJID second) { - BehaviorTimerEntry entry -; + BehaviorTimerEntry entry; + entry.time = branchContext.duration; entry.behavior = behavior; entry.branchContext = branchContext; diff --git a/dGame/dBehaviors/BehaviorContext.h b/dGame/dBehaviors/BehaviorContext.h index 9f1d1621..f27889f1 100644 --- a/dGame/dBehaviors/BehaviorContext.h +++ b/dGame/dBehaviors/BehaviorContext.h @@ -58,6 +58,8 @@ struct BehaviorContext float skillTime = 0; + uint32_t skillID = 0; + uint32_t skillUId = 0; bool failed = false; diff --git a/dGame/dBehaviors/LootBuffBehavior.cpp b/dGame/dBehaviors/LootBuffBehavior.cpp new file mode 100644 index 00000000..fe46f7bb --- /dev/null +++ b/dGame/dBehaviors/LootBuffBehavior.cpp @@ -0,0 +1,38 @@ +#include "LootBuffBehavior.h" + +void LootBuffBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) { + auto target = EntityManager::Instance()->GetEntity(context->caster); + if (!target) return; + + auto controllablePhysicsComponent = target->GetComponent(); + if (!controllablePhysicsComponent) return; + + controllablePhysicsComponent->AddPickupRadiusScale(m_Scale); + EntityManager::Instance()->SerializeEntity(target); + + if (branch.duration > 0) context->RegisterTimerBehavior(this, branch); + +} + +void LootBuffBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) { + Handle(context, bitStream, branch); +} + +void LootBuffBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branch) { + auto target = EntityManager::Instance()->GetEntity(context->caster); + if (!target) return; + + auto controllablePhysicsComponent = target->GetComponent(); + if (!controllablePhysicsComponent) return; + + controllablePhysicsComponent->RemovePickupRadiusScale(m_Scale); + EntityManager::Instance()->SerializeEntity(target); +} + +void LootBuffBehavior::Timer(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) { + UnCast(context, branch); +} + +void LootBuffBehavior::Load() { + this->m_Scale = GetFloat("scale"); +} \ No newline at end of file diff --git a/dGame/dBehaviors/LootBuffBehavior.h b/dGame/dBehaviors/LootBuffBehavior.h new file mode 100644 index 00000000..0f85b3b0 --- /dev/null +++ b/dGame/dBehaviors/LootBuffBehavior.h @@ -0,0 +1,32 @@ +#pragma once +#include "Behavior.h" +#include "BehaviorBranchContext.h" +#include "BehaviorContext.h" +#include "ControllablePhysicsComponent.h" + +/** + * @brief This is the behavior class to be used for all Loot Buff behavior nodes in the Behavior tree. + * + */ +class LootBuffBehavior final : public Behavior +{ +public: + + float m_Scale; + + /* + * Inherited + */ + + explicit LootBuffBehavior(const uint32_t behaviorId) : Behavior(behaviorId) {} + + void Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; + + void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; + + void UnCast(BehaviorContext* context, BehaviorBranchContext branch) override; + + void Timer(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) override; + + void Load() override; +}; diff --git a/dGame/dBehaviors/OverTimeBehavior.cpp b/dGame/dBehaviors/OverTimeBehavior.cpp index dbee4c39..9e8618df 100644 --- a/dGame/dBehaviors/OverTimeBehavior.cpp +++ b/dGame/dBehaviors/OverTimeBehavior.cpp @@ -7,62 +7,26 @@ #include "SkillComponent.h" #include "DestroyableComponent.h" -/** - * The OverTime behavior is very inconsistent in how it appears in the skill tree vs. how it should behave. - * - * Items like "Doc in a Box" use an overtime behavior which you would expect have health & armor regen, but is only fallowed by a stun. - * - * Due to this inconsistency, we have to implement a special case for some items. - */ - void OverTimeBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) { const auto originator = context->originator; auto* entity = EntityManager::Instance()->GetEntity(originator); - if (entity == nullptr) - { - return; - } + if (entity == nullptr) return; for (size_t i = 0; i < m_NumIntervals; i++) { entity->AddCallbackTimer((i + 1) * m_Delay, [originator, branch, this]() { auto* entity = EntityManager::Instance()->GetEntity(originator); - if (entity == nullptr) - { - return; - } + if (entity == nullptr) return; auto* skillComponent = entity->GetComponent(); - if (skillComponent == nullptr) - { - return; - } + if (skillComponent == nullptr) return; - skillComponent->CalculateBehavior(0, m_Action->m_behaviorId, branch.target, true, true); - - auto* destroyableComponent = entity->GetComponent(); - - if (destroyableComponent == nullptr) - { - return; - } - - /** - * Special cases for inconsistent behavior. - */ - - switch (m_behaviorId) - { - case 26253: // "Doc in a Box", heal up to 6 health and regen up to 18 armor. - destroyableComponent->Heal(1); - destroyableComponent->Repair(3); - break; - } + skillComponent->CalculateBehavior(m_Action, m_ActionBehaviorId, branch.target, true, true); }); } } @@ -74,7 +38,12 @@ void OverTimeBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bi void OverTimeBehavior::Load() { - m_Action = GetAction("action"); + m_Action = GetInt("action"); + // Since m_Action is a skillID and not a behavior, get is correlated behaviorID. + + CDSkillBehaviorTable* skillTable = CDClientManager::Instance()->GetTable("SkillBehavior"); + m_ActionBehaviorId = skillTable->GetSkillByID(m_Action).behaviorID; + m_Delay = GetFloat("delay"); m_NumIntervals = GetInt("num_intervals"); } diff --git a/dGame/dBehaviors/OverTimeBehavior.h b/dGame/dBehaviors/OverTimeBehavior.h index 6be675aa..5c177926 100644 --- a/dGame/dBehaviors/OverTimeBehavior.h +++ b/dGame/dBehaviors/OverTimeBehavior.h @@ -4,7 +4,8 @@ class OverTimeBehavior final : public Behavior { public: - Behavior* m_Action; + uint32_t m_Action; + uint32_t m_ActionBehaviorId; float m_Delay; int32_t m_NumIntervals; diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 4d82036f..0903e621 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -191,7 +191,7 @@ void BaseCombatAIComponent::Update(const float deltaTime) { return; } - if (m_Stunned || m_SkillTime > 0) { + if (m_Stunned) { m_MovementAI->Stop(); return; @@ -357,7 +357,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { return; } - m_Downtime = 0.5f; // TODO: find out if this is necessary + m_Downtime = 0.5f; auto* target = GetTargetEntity(); diff --git a/dGame/dComponents/BuffComponent.h b/dGame/dComponents/BuffComponent.h index 68cd5309..ba22c08b 100644 --- a/dGame/dComponents/BuffComponent.h +++ b/dGame/dComponents/BuffComponent.h @@ -51,7 +51,7 @@ public: void LoadFromXML(tinyxml2::XMLDocument* doc); - void UpdateXml(tinyxml2::XMLDocument* doc); + void UpdateXml(tinyxml2::XMLDocument* doc) override; void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index 9337c324..f7941af8 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -12,6 +12,8 @@ #include "EntityManager.h" #include "PossessorComponent.h" #include "VehiclePhysicsComponent.h" +#include "GameMessages.h" +#include "Item.h" CharacterComponent::CharacterComponent(Entity* parent, Character* character) : Component(parent) { m_Character = character; @@ -31,6 +33,7 @@ CharacterComponent::CharacterComponent(Entity* parent, Character* character) : C m_EditorEnabled = false; m_EditorLevel = m_GMLevel; + m_Reputation = 0; m_CurrentActivity = 0; m_CountryCode = 0; @@ -42,7 +45,7 @@ CharacterComponent::CharacterComponent(Entity* parent, Character* character) : C if (character->GetZoneID() != Game::server->GetZoneID()) { m_IsLanding = true; } - + if (LandingAnimDisabled(character->GetZoneID()) || LandingAnimDisabled(Game::server->GetZoneID()) || m_LastRocketConfig.empty()) { m_IsLanding = false; //Don't make us land on VE/minigames lol } @@ -191,6 +194,7 @@ void CharacterComponent::HandleLevelUp() auto* rewardsTable = CDClientManager::Instance()->GetTable("Rewards"); const auto& rewards = rewardsTable->GetByLevelID(m_Level); + bool rewardingItem = rewards.size() > 0; auto* parent = m_Character->GetEntity(); @@ -206,37 +210,34 @@ void CharacterComponent::HandleLevelUp() { return; } + // Tell the client we beginning to send level rewards. + if(rewardingItem) GameMessages::NotifyLevelRewards(parent->GetObjectID(), parent->GetSystemAddress(), m_Level, rewardingItem); for (auto* reward : rewards) { switch (reward->rewardType) { case 0: - inventoryComponent->AddItem(reward->value, reward->count); + inventoryComponent->AddItem(reward->value, reward->count, eLootSourceType::LOOT_SOURCE_LEVEL_REWARD); break; - case 4: { - auto* items = inventoryComponent->GetInventory(ITEMS); + auto* items = inventoryComponent->GetInventory(eInventoryType::ITEMS); items->SetSize(items->GetSize() + reward->value); } break; - case 9: controllablePhysicsComponent->SetSpeedMultiplier(static_cast(reward->value) / 500.0f); break; - case 11: - break; - case 12: break; - default: break; } - } - + } + // Tell the client we have finished sending level rewards. + if(rewardingItem) GameMessages::NotifyLevelRewards(parent->GetObjectID(), parent->GetSystemAddress(), m_Level, !rewardingItem); } void CharacterComponent::SetGMLevel(int gmlevel) { @@ -257,7 +258,10 @@ void CharacterComponent::LoadFromXML() { Game::logger->Log("CharacterComponent", "Failed to find char tag while loading XML!\n"); return; } - + if (character->QueryAttribute("rpt", &m_Reputation) == tinyxml2::XML_NO_ATTRIBUTE) { + SetReputation(0); + } + character->QueryInt64Attribute("ls", &m_Uscore); // Load the statistics @@ -379,6 +383,8 @@ void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) { } character->SetAttribute("ls", m_Uscore); + // Custom attribute to keep track of reputation. + character->SetAttribute("rpt", GetReputation()); character->SetAttribute("stt", StatisticsToString().c_str()); // Set the zone statistics of the form ... @@ -443,6 +449,56 @@ void CharacterComponent::SetLastRocketConfig(std::u16string config) { m_LastRocketConfig = config; } +Item* CharacterComponent::GetRocket(Entity* player) { + Item* rocket = nullptr; + + auto* inventoryComponent = player->GetComponent(); + + if (!inventoryComponent) return rocket; + + // Select the rocket + if (!rocket){ + rocket = inventoryComponent->FindItemById(GetLastRocketItemID()); + } + + if (!rocket) { + rocket = inventoryComponent->FindItemByLot(6416); + } + + if (!rocket) { + Game::logger->Log("CharacterComponent", "Unable to find rocket to equip!\n"); + return rocket; + } + return rocket; +} + +Item* CharacterComponent::RocketEquip(Entity* player) { + Item* rocket = GetRocket(player); + if (!rocket) return rocket; + + // build and define the rocket config + for (LDFBaseData* data : rocket->GetConfig()) { + if (data->GetKey() == u"assemblyPartLOTs") { + std::string newRocketStr = data->GetValueAsString() + ";"; + GeneralUtils::ReplaceInString(newRocketStr, "+", ";"); + SetLastRocketConfig(GeneralUtils::ASCIIToUTF16(newRocketStr)); + } + } + + // Store the last used rocket item's ID + SetLastRocketItemID(rocket->GetId()); + // carry the rocket + rocket->Equip(true); + return rocket; +} + +void CharacterComponent::RocketUnEquip(Entity* player) { + Item* rocket = GetRocket(player); + if (!rocket) return; + // We don't want to carry it anymore + rocket->UnEquip(); +} + void CharacterComponent::TrackMissionCompletion(bool isAchievement) { UpdatePlayerStatistic(MissionsCompleted); diff --git a/dGame/dComponents/CharacterComponent.h b/dGame/dComponents/CharacterComponent.h index 62076fad..99c8a098 100644 --- a/dGame/dComponents/CharacterComponent.h +++ b/dGame/dComponents/CharacterComponent.h @@ -5,6 +5,7 @@ #include "RakNetTypes.h" #include "Character.h" #include "Component.h" +#include "Item.h" #include #include "CDMissionsTable.h" #include "tinyxml2.h" @@ -74,6 +75,26 @@ public: */ void SetLastRocketConfig(std::u16string config); + /** + * Find a player's rocket + * @param player the entity that triggered the event + * @return rocket + */ + Item* GetRocket(Entity* player); + + /** + * Equip a player's rocket + * @param player the entity that triggered the event + * @return rocket + */ + Item* RocketEquip(Entity* player); + + /** + * Find a player's rocket and unequip it + * @param player the entity that triggered the event + */ + void RocketUnEquip(Entity* player); + /** * Gets the current level of the entity * @return the current level of the entity @@ -146,6 +167,18 @@ public: */ bool GetPvpEnabled() const; + /** + * Returns the characters lifetime reputation + * @return The lifetime reputation of this character. + */ + int64_t GetReputation() { return m_Reputation; }; + + /** + * Sets the lifetime reputation of the character to newValue + * @param newValue the value to set reputation to + */ + void SetReputation(int64_t newValue) { m_Reputation = newValue; }; + /** * Sets the current value of PvP combat being enabled * @param value whether to enable PvP combat @@ -291,6 +324,11 @@ private: */ int64_t m_Uscore; + /** + * The lifetime reputation earned by the entity + */ + int64_t m_Reputation; + /** * Whether the character is landing by rocket */ diff --git a/dGame/dComponents/ControllablePhysicsComponent.cpp b/dGame/dComponents/ControllablePhysicsComponent.cpp index 922ae55d..648b1471 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/ControllablePhysicsComponent.cpp @@ -29,6 +29,8 @@ ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity) : Com m_GravityScale = 1; m_DirtyCheats = false; m_IgnoreMultipliers = false; + m_PickupRadius = 0.0f; + m_DirtyPickupRadiusScale = true; if (entity->GetLOT() != 1) // Other physics entities we care about will be added by BaseCombatAI return; @@ -85,7 +87,13 @@ void ControllablePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bo m_DirtyCheats = false; } - outBitStream->Write0(); + outBitStream->Write(m_DirtyPickupRadiusScale); + if (m_DirtyPickupRadiusScale) { + outBitStream->Write(m_PickupRadius); + outBitStream->Write0(); //No clue what this is so im leaving it false. + m_DirtyPickupRadiusScale = false; + } + outBitStream->Write0(); outBitStream->Write(m_DirtyPosition || bIsInitialUpdate); @@ -230,4 +238,32 @@ void ControllablePhysicsComponent::SetDirtyVelocity(bool val) { void ControllablePhysicsComponent::SetDirtyAngularVelocity(bool val) { m_DirtyAngularVelocity = val; -} \ No newline at end of file +} + +void ControllablePhysicsComponent::AddPickupRadiusScale(float value) { + m_ActivePickupRadiusScales.push_back(value); + if (value > m_PickupRadius) { + m_PickupRadius = value; + m_DirtyPickupRadiusScale = true; + } +} + +void ControllablePhysicsComponent::RemovePickupRadiusScale(float value) { + // Attempt to remove pickup radius from active radii + const auto pos = std::find(m_ActivePickupRadiusScales.begin(), m_ActivePickupRadiusScales.end(), value); + if (pos != m_ActivePickupRadiusScales.end()) { + m_ActivePickupRadiusScales.erase(pos); + } else { + Game::logger->Log("ControllablePhysicsComponent", "Warning: Could not find pickup radius %f in list of active radii. List has %i active radii.\n", value, m_ActivePickupRadiusScales.size()); + return; + } + + // Recalculate pickup radius since we removed one by now + m_PickupRadius = 0.0f; + m_DirtyPickupRadiusScale = true; + for (uint32_t i = 0; i < m_ActivePickupRadiusScales.size(); i++) { + auto candidateRadius = m_ActivePickupRadiusScales[i]; + if (m_PickupRadius < candidateRadius) m_PickupRadius = candidateRadius; + } + EntityManager::Instance()->SerializeEntity(m_Parent); +} diff --git a/dGame/dComponents/ControllablePhysicsComponent.h b/dGame/dComponents/ControllablePhysicsComponent.h index 1c1a4f44..c7acec62 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.h +++ b/dGame/dComponents/ControllablePhysicsComponent.h @@ -23,7 +23,7 @@ public: ControllablePhysicsComponent(Entity* entity); ~ControllablePhysicsComponent() override; - void Update(float deltaTime); + void Update(float deltaTime) override; void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); void LoadFromXML(tinyxml2::XMLDocument* doc); void ResetFlags(); @@ -227,6 +227,24 @@ public: dpEntity* GetdpEntity() const { return m_dpEntity; } + /** + * I store this in a vector because if I have 2 separate pickup radii being applied to the player, I dont know which one is correctly active. + * This method adds the pickup radius to the vector of active radii and if its larger than the current one, is applied as the new pickup radius. + */ + void AddPickupRadiusScale(float value) ; + + /** + * Removes the provided pickup radius scale from our list of buffs + * The recalculates what our pickup radius is. + */ + void RemovePickupRadiusScale(float value) ; + + /** + * The pickup radii of this component. + * @return All active radii scales for this component. + */ + std::vector GetActivePickupRadiusScales() { return m_ActivePickupRadiusScales; }; + private: /** * The entity that owns this component @@ -322,6 +340,21 @@ private: * Whether this entity is static, making it unable to move */ bool m_Static; + + /** + * Whether the pickup scale is dirty. + */ + bool m_DirtyPickupRadiusScale; + + /** + * The list of pickup radius scales for this entity + */ + std::vector m_ActivePickupRadiusScales; + + /** + * The active pickup radius for this entity + */ + float m_PickupRadius; }; #endif // CONTROLLABLEPHYSICSCOMPONENT_H diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 4077e2d5..da5db382 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -26,6 +26,7 @@ #include "MissionComponent.h" #include "CharacterComponent.h" +#include "dZoneManager.h" DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) { m_iArmor = 0; @@ -214,6 +215,8 @@ void DestroyableComponent::SetHealth(int32_t value) { void DestroyableComponent::SetMaxHealth(float value, bool playAnim) { m_DirtyHealth = true; + // Used for playAnim if opted in for. + int32_t difference = static_cast(std::abs(m_fMaxHealth - value)); m_fMaxHealth = value; if (m_iHealth > m_fMaxHealth) { @@ -224,27 +227,28 @@ void DestroyableComponent::SetMaxHealth(float value, bool playAnim) { // Now update the player bar if (!m_Parent->GetParentUser()) return; AMFStringValue* amount = new AMFStringValue(); - amount->SetStringValue(std::to_string(value)); + amount->SetStringValue(std::to_string(difference)); AMFStringValue* type = new AMFStringValue(); type->SetStringValue("health"); AMFArrayValue args; args.InsertValue("amount", amount); args.InsertValue("type", type); - GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", &args); delete amount; delete type; } - else { - EntityManager::Instance()->SerializeEntity(m_Parent); - } + + EntityManager::Instance()->SerializeEntity(m_Parent); } void DestroyableComponent::SetArmor(int32_t value) { m_DirtyHealth = true; + // If Destroyable Component already has zero armor do not trigger the passive ability again. + bool hadArmor = m_iArmor > 0; + auto* characterComponent = m_Parent->GetComponent(); if (characterComponent != nullptr) { characterComponent->TrackArmorDelta(value - m_iArmor); @@ -253,7 +257,7 @@ void DestroyableComponent::SetArmor(int32_t value) { m_iArmor = value; auto* inventroyComponent = m_Parent->GetComponent(); - if (m_iArmor == 0 && inventroyComponent != nullptr) { + if (m_iArmor == 0 && inventroyComponent != nullptr && hadArmor) { inventroyComponent->TriggerPassiveAbility(PassiveAbilityTrigger::SentinelArmor); } } @@ -283,9 +287,8 @@ void DestroyableComponent::SetMaxArmor(float value, bool playAnim) { delete amount; delete type; } - else { - EntityManager::Instance()->SerializeEntity(m_Parent); - } + + EntityManager::Instance()->SerializeEntity(m_Parent); } void DestroyableComponent::SetImagination(int32_t value) { @@ -306,6 +309,8 @@ void DestroyableComponent::SetImagination(int32_t value) { void DestroyableComponent::SetMaxImagination(float value, bool playAnim) { m_DirtyHealth = true; + // Used for playAnim if opted in for. + int32_t difference = static_cast(std::abs(m_fMaxImagination - value)); m_fMaxImagination = value; if (m_iImagination > m_fMaxImagination) { @@ -316,22 +321,19 @@ void DestroyableComponent::SetMaxImagination(float value, bool playAnim) { // Now update the player bar if (!m_Parent->GetParentUser()) return; AMFStringValue* amount = new AMFStringValue(); - amount->SetStringValue(std::to_string(value)); + amount->SetStringValue(std::to_string(difference)); AMFStringValue* type = new AMFStringValue(); type->SetStringValue("imagination"); AMFArrayValue args; args.InsertValue("amount", amount); args.InsertValue("type", type); - GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", &args); delete amount; delete type; } - else { - EntityManager::Instance()->SerializeEntity(m_Parent); - } + EntityManager::Instance()->SerializeEntity(m_Parent); } void DestroyableComponent::SetDamageToAbsorb(int32_t value) @@ -591,7 +593,7 @@ void DestroyableComponent::Repair(const uint32_t armor) } -void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, bool echo) +void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32_t skillID, bool echo) { if (GetHealth() <= 0) { @@ -675,11 +677,10 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, bool e return; } - - Smash(source); + Smash(source, eKillType::VIOLENT, u"", skillID); } -void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType, const std::u16string& deathType) +void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType, const std::u16string& deathType, uint32_t skillID) { if (m_iHealth > 0) { @@ -725,31 +726,20 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType if (memberMissions == nullptr) continue; memberMissions->Progress(MissionTaskType::MISSION_TASK_TYPE_SMASH, m_Parent->GetLOT()); + memberMissions->Progress(MissionTaskType::MISSION_TASK_TYPE_SKILL, m_Parent->GetLOT(), skillID); } } else { missions->Progress(MissionTaskType::MISSION_TASK_TYPE_SMASH, m_Parent->GetLOT()); + missions->Progress(MissionTaskType::MISSION_TASK_TYPE_SKILL, m_Parent->GetLOT(), skillID); } } } const auto isPlayer = m_Parent->IsPlayer(); - GameMessages::SendDie( - m_Parent, - source, - source, - true, - killType, - deathType, - 0, - 0, - 0, - isPlayer, - false, - 1 - ); + GameMessages::SendDie(m_Parent, source, source, true, killType, deathType, 0, 0, 0, isPlayer, false, 1); //NANI?! if (!isPlayer) @@ -793,32 +783,34 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType } else { - auto* character = m_Parent->GetCharacter(); - - uint64_t coinsTotal = character->GetCoins(); - - if (coinsTotal > 0) + //Check if this zone allows coin drops + if (dZoneManager::Instance()->GetPlayerLoseCoinOnDeath()) { - uint64_t coinsToLoose = 1; + auto* character = m_Parent->GetCharacter(); + uint64_t coinsTotal = character->GetCoins(); - if (coinsTotal >= 200) + if (coinsTotal > 0) { - float hundreth = (coinsTotal / 100.0f); - coinsToLoose = static_cast(hundreth); + uint64_t coinsToLoose = 1; + + if (coinsTotal >= 200) + { + float hundreth = (coinsTotal / 100.0f); + coinsToLoose = static_cast(hundreth); + } + + if (coinsToLoose > 10000) + { + coinsToLoose = 10000; + } + + coinsTotal -= coinsToLoose; + + LootGenerator::Instance().DropLoot(m_Parent, m_Parent, -1, coinsToLoose, coinsToLoose); + character->SetCoins(coinsTotal, eLootSourceType::LOOT_SOURCE_PICKUP); } - - if (coinsToLoose > 10000) - { - coinsToLoose = 10000; - } - - coinsTotal -= coinsToLoose; - - LootGenerator::Instance().DropLoot(m_Parent, m_Parent, -1, coinsToLoose, coinsToLoose); } - character->SetCoins(coinsTotal, LOOT_SOURCE_PICKUP); - Entity* zoneControl = EntityManager::Instance()->GetZoneControlEntity(); for (CppScripts::Script* script : CppScripts::GetEntityScripts(zoneControl)) { script->OnPlayerDied(zoneControl, m_Parent); diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index d702897c..a1f57be4 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -377,17 +377,19 @@ public: * Attempt to damage this entity, handles everything from health and armor to absorption, immunity and callbacks. * @param damage the damage to attempt to apply * @param source the attacker that caused this damage + * @param skillID the skill that damaged this entity * @param echo whether or not to serialize the damage */ - void Damage(uint32_t damage, LWOOBJID source, bool echo = true); + void Damage(uint32_t damage, LWOOBJID source, uint32_t skillID = 0, bool echo = true); /** * Smashes this entity, notifying all clients * @param source the source that smashed this entity + * @param skillID the skill that killed this entity * @param killType the way this entity was killed, determines if a client animation is played * @param deathType the animation to play when killed */ - void Smash(LWOOBJID source, eKillType killType = eKillType::VIOLENT, const std::u16string& deathType = u""); + void Smash(LWOOBJID source, eKillType killType = eKillType::VIOLENT, const std::u16string& deathType = u"", uint32_t skillID = 0); /** * Pushes a layer of immunity to this entity, making it immune for longer diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index c36ffce9..8c5ec269 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -1,4 +1,4 @@ -#include "InventoryComponent.h" +#include "InventoryComponent.h" #include @@ -23,6 +23,7 @@ #include "CharacterComponent.h" #include "dZoneManager.h" #include "PropertyManagementComponent.h" +#include "DestroyableComponent.h" InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document) : Component(parent) { @@ -83,10 +84,13 @@ Inventory* InventoryComponent::GetInventory(const eInventoryType type) case eInventoryType::ITEMS: size = 20u; break; + case eInventoryType::VAULT_MODELS: case eInventoryType::VAULT_ITEMS: size = 40u; break; - + case eInventoryType::VENDOR_BUYBACK: + size = 27u; + break; default: break; } @@ -137,6 +141,7 @@ const EquipmentMap& InventoryComponent::GetEquippedItems() const void InventoryComponent::AddItem( const LOT lot, const uint32_t count, + eLootSourceType lootSourceType, eInventoryType inventoryType, const std::vector& config, const LWOOBJID parent, @@ -176,7 +181,7 @@ void InventoryComponent::AddItem( if (!config.empty() || bound) { - const auto slot = inventory->FindEmptySlot(); + const auto slot = preferredSlot != -1 && inventory->IsSlotEmpty(preferredSlot) ? preferredSlot : inventory->FindEmptySlot(); if (slot == -1) { @@ -185,7 +190,7 @@ void InventoryComponent::AddItem( return; } - auto* item = new Item(lot, inventory, slot, count, config, parent, showFlyingLoot, isModMoveAndEquip, subKey, bound); + auto* item = new Item(lot, inventory, slot, count, config, parent, showFlyingLoot, isModMoveAndEquip, subKey, bound, lootSourceType); if (missions != nullptr && !IsTransferInventory(inventoryType)) { @@ -203,7 +208,7 @@ void InventoryComponent::AddItem( auto stack = static_cast(info.stackSize); - if (inventoryType == BRICKS) + if (inventoryType == eInventoryType::BRICKS) { stack = 999; } @@ -220,7 +225,7 @@ void InventoryComponent::AddItem( left -= delta; - existing->SetCount(existing->GetCount() + delta, false, true, showFlyingLoot); + existing->SetCount(existing->GetCount() + delta, false, true, showFlyingLoot, lootSourceType); if (isModMoveAndEquip) { @@ -280,8 +285,7 @@ void InventoryComponent::AddItem( continue; } - - auto* item = new Item(lot, inventory, slot, size, {}, parent, showFlyingLoot, isModMoveAndEquip, subKey); + auto* item = new Item(lot, inventory, slot, size, {}, parent, showFlyingLoot, isModMoveAndEquip, subKey, false, lootSourceType); isModMoveAndEquip = false; } @@ -365,7 +369,7 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in left -= delta; - AddItem(lot, delta, inventory, {}, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, LWOOBJID_EMPTY, origin->GetType(), 0, false, preferredSlot); + AddItem(lot, delta, eLootSourceType::LOOT_SOURCE_NONE, inventory, {}, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, LWOOBJID_EMPTY, origin->GetType(), 0, false, preferredSlot); item->SetCount(item->GetCount() - delta, false, false); @@ -383,7 +387,7 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in const auto delta = std::min(item->GetCount(), count); - AddItem(lot, delta, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, LWOOBJID_EMPTY, origin->GetType(), 0, item->GetBound(), preferredSlot); + AddItem(lot, delta, eLootSourceType::LOOT_SOURCE_NONE, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, LWOOBJID_EMPTY, origin->GetType(), 0, item->GetBound(), preferredSlot); item->SetCount(item->GetCount() - delta, false, false); } @@ -794,9 +798,28 @@ void InventoryComponent::Serialize(RakNet::BitStream* outBitStream, const bool b outBitStream->Write0(); - outBitStream->Write0(); //TODO: This is supposed to be true and write the assemblyPartLOTs when they're present. - - outBitStream->Write1(); + bool flag = !item.config.empty(); + outBitStream->Write(flag); + if (flag) { + RakNet::BitStream ldfStream; + ldfStream.Write(item.config.size()); // Key count + for (LDFBaseData* data : item.config) { + if (data->GetKey() == u"assemblyPartLOTs") { + std::string newRocketStr = data->GetValueAsString() + ";"; + GeneralUtils::ReplaceInString(newRocketStr, "+", ";"); + LDFData* ldf_data = new LDFData(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(newRocketStr)); + ldf_data->WriteToPacket(&ldfStream); + delete ldf_data; + } else { + data->WriteToPacket(&ldfStream); + } + } + outBitStream->Write(ldfStream.GetNumberOfBytesUsed() + 1); + outBitStream->Write(0); // Don't compress + outBitStream->Write(ldfStream); + } + + outBitStream->Write1(); } m_Dirty = false; @@ -908,28 +931,46 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) const auto building = character->GetBuildMode(); const auto type = static_cast(item->GetInfo().itemType); + + if (item->GetLot() == 8092 && m_Parent->GetGMLevel() >= GAME_MASTER_LEVEL_OPERATOR && hasCarEquipped == false) + { + auto startPosition = m_Parent->GetPosition(); + + auto startRotation = NiQuaternion::LookAt(startPosition, startPosition + NiPoint3::UNIT_X); + auto angles = startRotation.GetEulerAngles(); + angles.y -= PI; + startRotation = NiQuaternion::FromEulerAngles(angles); + + GameMessages::SendTeleport(m_Parent->GetObjectID(), startPosition, startRotation, m_Parent->GetSystemAddress(), true, true); - if (item->GetLot() == 8092 && m_Parent->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) - { EntityInfo info {}; info.lot = 8092; - info.pos = m_Parent->GetPosition(); - info.rot = m_Parent->GetRotation(); + info.pos = startPosition; + info.rot = startRotation; info.spawnerID = m_Parent->GetObjectID(); - auto* carEntity = EntityManager::Instance()->CreateEntity(info, nullptr, dZoneManager::Instance()->GetZoneControlObject()); - dZoneManager::Instance()->GetZoneControlObject()->AddChild(carEntity); + auto* carEntity = EntityManager::Instance()->CreateEntity(info, nullptr, m_Parent); + m_Parent->AddChild(carEntity); + auto *destroyableComponent = carEntity->GetComponent(); + + // Setup the vehicle stats. + if (destroyableComponent != nullptr) { + destroyableComponent->SetIsSmashable(false); + destroyableComponent->SetIsImmune(true); + } + // #108 auto* possessableComponent = carEntity->GetComponent(); if (possessableComponent != nullptr) { + previousPossessableID = possessableComponent->GetPossessor(); possessableComponent->SetPossessor(m_Parent->GetObjectID()); } auto* moduleAssemblyComponent = carEntity->GetComponent(); - if (moduleAssemblyComponent) + if (moduleAssemblyComponent != nullptr) { moduleAssemblyComponent->SetSubKey(item->GetSubKey()); moduleAssemblyComponent->SetUseOptionalParts(false); @@ -942,11 +983,12 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) } } } - + // #107 auto* possessorComponent = m_Parent->GetComponent(); if (possessorComponent != nullptr) { + previousPossessorID = possessorComponent->GetPossessable(); possessorComponent->SetPossessable(carEntity->GetObjectID()); } @@ -960,13 +1002,26 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) EntityManager::Instance()->ConstructEntity(carEntity); EntityManager::Instance()->SerializeEntity(m_Parent); - //EntityManager::Instance()->SerializeEntity(dZoneManager::Instance()->GetZoneControlObject()); + GameMessages::SendSetJetPackMode(m_Parent, false); - GameMessages::SendNotifyVehicleOfRacingObject(carEntity->GetObjectID(), dZoneManager::Instance()->GetZoneControlObject()->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); - GameMessages::SendRacingPlayerLoaded(m_Parent->GetObjectID(), m_Parent->GetObjectID(), carEntity->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::SendNotifyVehicleOfRacingObject(carEntity->GetObjectID(), m_Parent->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::SendRacingPlayerLoaded(LWOOBJID_EMPTY, m_Parent->GetObjectID(), carEntity->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); GameMessages::SendVehicleUnlockInput(carEntity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); - //GameMessages::SendVehicleSetWheelLockState(carEntity->GetObjectID(), false, false, UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::SendTeleport(m_Parent->GetObjectID(), startPosition, startRotation, m_Parent->GetSystemAddress(), true, true); + GameMessages::SendTeleport(carEntity->GetObjectID(), startPosition, startRotation, m_Parent->GetSystemAddress(), true, true); + EntityManager::Instance()->SerializeEntity(m_Parent); + hasCarEquipped = true; + equippedCarEntity = carEntity; + return; + } else if (item->GetLot() == 8092 && m_Parent->GetGMLevel() >= GAME_MASTER_LEVEL_OPERATOR && hasCarEquipped == true) + { + GameMessages::SendNotifyRacingClient(LWOOBJID_EMPTY, 3, 0, LWOOBJID_EMPTY, u"", m_Parent->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + auto player = dynamic_cast(m_Parent); + player->SendToZone(player->GetCharacter()->GetZoneID()); + equippedCarEntity->Kill(); + hasCarEquipped = false; + equippedCarEntity = nullptr; return; } @@ -1007,11 +1062,11 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) } GenerateProxies(item); + + UpdateSlot(item->GetInfo().equipLocation, { item->GetId(), item->GetLot(), item->GetCount(), item->GetSlot(), item->GetConfig() }); - UpdateSlot(item->GetInfo().equipLocation, { item->GetId(), item->GetLot(), item->GetCount(), item->GetSlot() }); - - ApplyBuff(item->GetLot()); - + ApplyBuff(item); + AddItemSkills(item->GetLot()); EntityManager::Instance()->SerializeEntity(m_Parent); @@ -1038,8 +1093,8 @@ void InventoryComponent::UnEquipItem(Item* item) set->OnUnEquip(lot); } - RemoveBuff(item->GetLot()); - + RemoveBuff(item); + RemoveItemSkills(item->GetLot()); RemoveSlot(item->GetInfo().equipLocation); @@ -1056,9 +1111,9 @@ void InventoryComponent::UnEquipItem(Item* item) } } -void InventoryComponent::ApplyBuff(const LOT lot) const +void InventoryComponent::ApplyBuff(Item* item) const { - const auto buffs = FindBuffs(lot, true); + const auto buffs = FindBuffs(item, true); for (const auto buff : buffs) { @@ -1066,9 +1121,9 @@ void InventoryComponent::ApplyBuff(const LOT lot) const } } -void InventoryComponent::RemoveBuff(const LOT lot) const +void InventoryComponent::RemoveBuff(Item* item) const { - const auto buffs = FindBuffs(lot, false); + const auto buffs = FindBuffs(item, false); for (const auto buff : buffs) { @@ -1195,13 +1250,6 @@ void InventoryComponent::AddItemSkills(const LOT lot) const auto index = m_Skills.find(slot); - if (index != m_Skills.end()) - { - const auto old = index->second; - - GameMessages::SendRemoveSkill(m_Parent, old); - } - const auto skill = FindSkill(lot); if (skill == 0) @@ -1209,6 +1257,13 @@ void InventoryComponent::AddItemSkills(const LOT lot) return; } + if (index != m_Skills.end()) + { + const auto old = index->second; + + GameMessages::SendRemoveSkill(m_Parent, old); + } + GameMessages::SendAddSkill(m_Parent, skill, static_cast(slot)); m_Skills.insert_or_assign(slot, skill); @@ -1384,18 +1439,18 @@ uint32_t InventoryComponent::FindSkill(const LOT lot) return 0; } -std::vector InventoryComponent::FindBuffs(const LOT lot, bool castOnEquip) const +std::vector InventoryComponent::FindBuffs(Item* item, bool castOnEquip) const { + std::vector buffs; + if (item == nullptr) return buffs; auto* table = CDClientManager::Instance()->GetTable("ObjectSkills"); auto* behaviors = CDClientManager::Instance()->GetTable("SkillBehavior"); const auto results = table->Query([=](const CDObjectSkills& entry) { - return entry.objectTemplate == static_cast(lot); + return entry.objectTemplate == static_cast(item->GetLot()); }); - std::vector buffs; - auto* missions = static_cast(m_Parent->GetComponent(COMPONENT_TYPE_MISSION)); for (const auto& result : results) @@ -1415,8 +1470,9 @@ std::vector InventoryComponent::FindBuffs(const LOT lot, bool castOnEq { missions->Progress(MissionTaskType::MISSION_TASK_TYPE_SKILL, result.skillID); } - - buffs.push_back(static_cast(entry.behaviorID)); + + // If item is not a proxy, add its buff to the added buffs. + if (item->GetParent() == LWOOBJID_EMPTY) buffs.push_back(static_cast(entry.behaviorID)); } } diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 7082cfff..bf18d132 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -15,6 +15,7 @@ #include "Component.h" #include "ItemSetPassiveAbility.h" #include "ItemSetPassiveAbilityID.h" +#include "PossessorComponent.h" class Entity; class ItemSet; @@ -86,10 +87,12 @@ public: * @param sourceType the source of the item, used to determine if the item is dropped or mailed if the inventory is full * @param bound whether this item is bound * @param preferredSlot the preferred slot to store this item + * @param lootSourceType The source of the loot. Defaults to none. */ void AddItem( LOT lot, uint32_t count, + eLootSourceType lootSourceType = eLootSourceType::LOOT_SOURCE_NONE, eInventoryType inventoryType = INVALID, const std::vector& config = {}, LWOOBJID parent = LWOOBJID_EMPTY, @@ -192,15 +195,15 @@ public: /** * Adds a buff related to equipping a lot to the entity - * @param lot the lot to find buffs for + * @param item the item to find buffs for */ - void ApplyBuff(LOT lot) const; + void ApplyBuff(Item* item) const; /** * Removes buffs related to equipping a lot from the entity - * @param lot the lot to find buffs for + * @param item the item to find buffs for */ - void RemoveBuff(LOT lot) const; + void RemoveBuff(Item* item) const; /** * Saves the equipped items into a temp state @@ -239,11 +242,11 @@ public: /** * Finds all the buffs related to a lot - * @param lot the lot to get the buffs for + * @param item the item to get the buffs for * @param castOnEquip if true, the skill missions for these buffs will be progressed * @return the buffs related to the specified lot */ - std::vector FindBuffs(LOT lot, bool castOnEquip) const; + std::vector FindBuffs(Item* item, bool castOnEquip) const; /** * Initializes the equipped items with a list of items @@ -384,6 +387,13 @@ private: */ LOT m_Consumable; + /** + * Currently has a car equipped + */ + bool hasCarEquipped = false; + Entity* equippedCarEntity = nullptr; + LWOOBJID previousPossessableID = LWOOBJID_EMPTY; + LWOOBJID previousPossessorID = LWOOBJID_EMPTY; /** * Creates all the proxy items (subitems) for a parent item * @param parent the parent item to generate all the subitems for @@ -399,8 +409,8 @@ private: std::vector FindProxies(LWOOBJID parent); /** - * Returns if the provided ID is a valid proxy item (e.g. we have children for it) - * @param parent the parent item to check for + * Returns true if the provided LWOOBJID is the parent of this Item. + * @param parent the parent item to check for proxies * @return if the provided ID is a valid proxy item */ bool IsValidProxy(LWOOBJID parent); diff --git a/dGame/dComponents/MissionComponent.h b/dGame/dComponents/MissionComponent.h index 6e54a6b0..13a812d2 100644 --- a/dGame/dComponents/MissionComponent.h +++ b/dGame/dComponents/MissionComponent.h @@ -29,8 +29,8 @@ public: explicit MissionComponent(Entity* parent); ~MissionComponent() override; void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); - void LoadFromXml(tinyxml2::XMLDocument* doc); - void UpdateXml(tinyxml2::XMLDocument* doc); + void LoadFromXml(tinyxml2::XMLDocument* doc) override; + void UpdateXml(tinyxml2::XMLDocument* doc) override; /** * Returns all the missions for this entity, mapped by mission ID diff --git a/dGame/dComponents/ModuleAssemblyComponent.h b/dGame/dComponents/ModuleAssemblyComponent.h index 28dcfa70..7153f30b 100644 --- a/dGame/dComponents/ModuleAssemblyComponent.h +++ b/dGame/dComponents/ModuleAssemblyComponent.h @@ -17,7 +17,7 @@ public: ~ModuleAssemblyComponent() override; void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); - void Update(float deltaTime); + void Update(float deltaTime) override; /** * Sets the subkey of this entity diff --git a/dGame/dComponents/MovementAIComponent.h b/dGame/dComponents/MovementAIComponent.h index 477c39f4..032732cc 100644 --- a/dGame/dComponents/MovementAIComponent.h +++ b/dGame/dComponents/MovementAIComponent.h @@ -61,7 +61,7 @@ public: MovementAIComponent(Entity* parentEntity, MovementAIInfo info); ~MovementAIComponent() override; - void Update(float deltaTime); + void Update(float deltaTime) override; /** * Returns the basic settings that this entity uses to move around diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index b31cd35f..a36d59e3 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -75,13 +75,19 @@ PetComponent::PetComponent(Entity* parent, uint32_t componentId) : Component(par m_MovementAI = nullptr; m_TresureTime = 0; m_Preconditions = nullptr; + + std::string checkPreconditions = GeneralUtils::UTF16ToWTF8(parent->GetVar(u"CheckPrecondition")); + + if (!checkPreconditions.empty()) { + SetPreconditions(checkPreconditions); + } } void PetComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { const bool tamed = m_Owner != LWOOBJID_EMPTY; - outBitStream->Write1(); // Dirty? + outBitStream->Write1(); // Always serialize as dirty for now outBitStream->Write(static_cast(m_Status)); outBitStream->Write(static_cast(tamed ? m_Ability : PetAbilityType::Invalid)); // Something with the overhead icon? @@ -262,29 +268,12 @@ void PetComponent::OnUse(Entity* originator) auto position = originatorPosition; - NiPoint3 forward = NiQuaternion::LookAt(m_Parent->GetPosition(), originator->GetPosition()).GetForwardVector(); //m_Parent->GetRotation().GetForwardVector(); + NiPoint3 forward = NiQuaternion::LookAt(m_Parent->GetPosition(), originator->GetPosition()).GetForwardVector(); forward.y = 0; if (dpWorld::Instance().IsLoaded()) { - /* - if (interactionDistance > 2) - { - interactionDistance -= 1; - } - */ - NiPoint3 attempt = petPosition + forward * interactionDistance; - - /* - float deg = std::atan2(petPosition.z - originatorPosition.z, petPosition.x - originatorPosition.x); //* 180 / M_PI; - - auto position = NiPoint3( - petPosition.x + interactionDistance * std::cos(-deg), - petPosition.y, - petPosition.z + interactionDistance * std::sin(-deg) - ); - */ float y = dpWorld::Instance().GetHeightAtPoint(attempt); @@ -308,8 +297,6 @@ void PetComponent::OnUse(Entity* originator) auto rotation = NiQuaternion::LookAt(position, petPosition); - - //GameMessages::SendTeleport(originator->GetObjectID(), position, rotation, originator->GetSystemAddress(), true); GameMessages::SendNotifyPetTamingMinigame( originator->GetObjectID(), @@ -531,14 +518,12 @@ void PetComponent::Update(float deltaTime) m_Timer = 1; } -void PetComponent::TryBuild(std::vector& bricks, bool clientFailed) -{ +void PetComponent::TryBuild(uint32_t numBricks, bool clientFailed) { if (m_Tamer == LWOOBJID_EMPTY) return; auto* tamer = EntityManager::Instance()->GetEntity(m_Tamer); - if (tamer == nullptr) - { + if (tamer == nullptr) { m_Tamer = LWOOBJID_EMPTY; return; @@ -546,19 +531,11 @@ void PetComponent::TryBuild(std::vector& bricks, bool clientFailed) const auto& cached = buildCache.find(m_Parent->GetLOT()); - if (cached == buildCache.end()) - { - GameMessages::SendPetTamingTryBuildResult(m_Tamer, false, 0, tamer->GetSystemAddress()); - - return; - } + if (cached == buildCache.end()) return; auto* destroyableComponent = tamer->GetComponent(); - - if (destroyableComponent == nullptr) - { - return; - } + + if (destroyableComponent == nullptr) return; auto imagination = destroyableComponent->GetImagination(); @@ -568,59 +545,17 @@ void PetComponent::TryBuild(std::vector& bricks, bool clientFailed) EntityManager::Instance()->SerializeEntity(tamer); - const auto& trueBricks = BrickDatabase::Instance()->GetBricks(cached->second.buildFile); - - if (trueBricks.empty() || bricks.empty()) - { - GameMessages::SendPetTamingTryBuildResult(m_Tamer, false, 0, tamer->GetSystemAddress()); - - return; - } - - auto* brickIDTable = CDClientManager::Instance()->GetTable("BrickIDTable"); - - int32_t correct = 0; - - for (const auto& brick : bricks) - { - const auto brickEntries = brickIDTable->Query([brick](const CDBrickIDTable& entry) - { - return entry.NDObjectID == brick.designerID; - }); - - if (brickEntries.empty()) - { - continue; - } - - const auto designerID = brickEntries[0].LEGOBrickID; - - for (const auto& trueBrick : trueBricks) - { - if (designerID == trueBrick.designerID && brick.materialID == trueBrick.materialID) - { - correct++; - - break; - } - } - } - - const auto success = correct >= cached->second.numValidPieces; - - GameMessages::SendPetTamingTryBuildResult(m_Tamer, success, correct, tamer->GetSystemAddress()); - - if (!success) - { - if (imagination < cached->second.imaginationCost) - { + if (clientFailed) { + if (imagination < cached->second.imaginationCost) { ClientFailTamingMinigame(); } - } - else - { + } else { m_Timer = 0; } + + if (numBricks == 0) return; + + GameMessages::SendPetTamingTryBuildResult(m_Tamer, !clientFailed, numBricks, tamer->GetSystemAddress()); } void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) @@ -685,7 +620,7 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) GameMessages::SendRegisterPetDBID(m_Tamer, petSubKey, tamer->GetSystemAddress()); - inventoryComponent->AddItem(m_Parent->GetLOT(), 1, MODELS, {}, LWOOBJID_EMPTY, true, false, petSubKey); + inventoryComponent->AddItem(m_Parent->GetLOT(), 1, eLootSourceType::LOOT_SOURCE_ACTIVITY, eInventoryType::MODELS, {}, LWOOBJID_EMPTY, true, false, petSubKey); auto* item = inventoryComponent->FindItemBySubKey(petSubKey, MODELS); if (item == nullptr) @@ -726,7 +661,6 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) if (missionComponent != nullptr) { - //missionComponent->ForceProgress(506, 768, 1, false); missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_PET_TAMING, m_Parent->GetLOT()); } diff --git a/dGame/dComponents/PetComponent.h b/dGame/dComponents/PetComponent.h index 76afaff6..845cfe31 100644 --- a/dGame/dComponents/PetComponent.h +++ b/dGame/dComponents/PetComponent.h @@ -39,7 +39,7 @@ public: * @param bricks the bricks to try to complete the minigame with * @param clientFailed unused */ - void TryBuild(std::vector& bricks, bool clientFailed); + void TryBuild(uint32_t numBricks, bool clientFailed); /** * Handles a notification from the client regarding the completion of the pet minigame, adding the pet to their diff --git a/dGame/dComponents/PropertyEntranceComponent.cpp b/dGame/dComponents/PropertyEntranceComponent.cpp index 9e286ffa..6779d1c3 100644 --- a/dGame/dComponents/PropertyEntranceComponent.cpp +++ b/dGame/dComponents/PropertyEntranceComponent.cpp @@ -1,12 +1,16 @@ -#include #include "PropertyEntranceComponent.h" + +#include + +#include "Character.h" +#include "Database.h" +#include "GameMessages.h" +#include "PropertyManagementComponent.h" #include "PropertySelectQueryProperty.h" #include "RocketLaunchpadControlComponent.h" -#include "Character.h" -#include "GameMessages.h" +#include "CharacterComponent.h" +#include "UserManager.h" #include "dLogger.h" -#include "Database.h" -#include "PropertyManagementComponent.h" PropertyEntranceComponent::PropertyEntranceComponent(uint32_t componentID, Entity* parent) : Component(parent) { @@ -19,20 +23,25 @@ PropertyEntranceComponent::PropertyEntranceComponent(uint32_t componentID, Entit this->m_PropertyName = entry.propertyName; } -void PropertyEntranceComponent::OnUse(Entity* entity) -{ - GameMessages::SendPropertyEntranceBegin(m_Parent->GetObjectID(), entity->GetSystemAddress()); +void PropertyEntranceComponent::OnUse(Entity* entity) { + auto* characterComponent = entity->GetComponent(); + if (!characterComponent) return; - AMFArrayValue args; + auto* rocket = entity->GetComponent()->RocketEquip(entity); + if (!rocket) return; + + GameMessages::SendPropertyEntranceBegin(m_Parent->GetObjectID(), entity->GetSystemAddress()); + + AMFArrayValue args; auto* state = new AMFStringValue(); state->SetStringValue("property_menu"); args.InsertValue("state", state); - GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "pushGameState", &args); + GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "pushGameState", &args); - delete state; + delete state; } void PropertyEntranceComponent::OnEnterProperty(Entity* entity, uint32_t index, bool returnToZone, const SystemAddress& sysAddr) @@ -49,6 +58,7 @@ void PropertyEntranceComponent::OnEnterProperty(Entity* entity, uint32_t index, } else if (index >= 0) { + // Increment index once here because the first index of other player properties is 2 in the propertyQueries cache. index++; const auto& pair = propertyQueries.find(entity->GetObjectID()); @@ -71,167 +81,269 @@ void PropertyEntranceComponent::OnEnterProperty(Entity* entity, uint32_t index, launcher->SetSelectedCloneId(entity->GetObjectID(), cloneId); - launcher->Launch(entity, LWOOBJID_EMPTY, LWOMAPID_INVALID, cloneId); + launcher->Launch(entity, launcher->GetTargetZone(), cloneId); } -void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, - bool includeNullAddress, - bool includeNullDescription, - bool playerOwn, - bool updateUi, - int32_t numResults, - int32_t reputation, - int32_t sortMethod, - int32_t startIndex, - std::string filterText, - const SystemAddress& sysAddr) -{ - Game::logger->Log("PropertyEntranceComponent", "On Sync %d %d %d %d %i %i %i %i %s\n", - includeNullAddress, - includeNullDescription, - playerOwn, - updateUi, - numResults, - reputation, - sortMethod, - startIndex, - filterText.c_str() - ); +PropertySelectQueryProperty PropertyEntranceComponent::SetPropertyValues(PropertySelectQueryProperty property, LWOCLONEID cloneId, std::string ownerName, std::string propertyName, std::string propertyDescription, float reputation, bool isBFF, bool isFriend, bool isModeratorApproved, bool isAlt, bool isOwned, uint32_t privacyOption, uint32_t timeLastUpdated, float performanceCost) { + property.CloneId = cloneId; + property.OwnerName = ownerName; + property.Name = propertyName; + property.Description = propertyDescription; + property.Reputation = reputation; + property.IsBestFriend = isBFF; + property.IsFriend = isFriend; + property.IsModeratorApproved = isModeratorApproved; + property.IsAlt = isAlt; + property.IsOwned = isOwned; + property.AccessType = privacyOption; + property.DateLastPublished = timeLastUpdated; + property.PerformanceCost = performanceCost; - auto* launchpadComponent = m_Parent->GetComponent(); - if (launchpadComponent == nullptr) - return; + return property; +} + +std::string PropertyEntranceComponent::BuildQuery(Entity* entity, int32_t sortMethod, std::string customQuery, bool wantLimits) { + std::string base; + if (customQuery == "") { + base = baseQueryForProperties; + } else { + base = customQuery; + } + std::string orderBy = ""; + if (sortMethod == SORT_TYPE_FEATURED || sortMethod == SORT_TYPE_FRIENDS) { + std::string friendsList = " AND p.owner_id IN ("; + + auto friendsListQuery = Database::CreatePreppedStmt("SELECT * FROM (SELECT CASE WHEN player_id = ? THEN friend_id WHEN friend_id = ? THEN player_id END AS requested_player FROM friends ) AS fr WHERE requested_player IS NOT NULL ORDER BY requested_player DESC;"); + + friendsListQuery->setInt64(1, entity->GetObjectID()); + friendsListQuery->setInt64(2, entity->GetObjectID()); + + auto friendsListQueryResult = friendsListQuery->executeQuery(); + + while (friendsListQueryResult->next()) { + auto playerIDToConvert = friendsListQueryResult->getInt64(1); + playerIDToConvert = GeneralUtils::ClearBit(playerIDToConvert, OBJECT_BIT_CHARACTER); + playerIDToConvert = GeneralUtils::ClearBit(playerIDToConvert, OBJECT_BIT_PERSISTENT); + friendsList = friendsList + std::to_string(playerIDToConvert) + ","; + } + // Replace trailing comma with the closing parenthesis. + if (friendsList.at(friendsList.size() - 1) == ',') friendsList.erase(friendsList.size() - 1, 1); + friendsList += ") "; + + // If we have no friends then use a -1 for the query. + if (friendsList.find("()") != std::string::npos) friendsList = " AND p.owner_id IN (-1) "; + + orderBy += friendsList + "ORDER BY ci.name ASC "; + + delete friendsListQueryResult; + friendsListQueryResult = nullptr; + + delete friendsListQuery; + friendsListQuery = nullptr; + } + else if (sortMethod == SORT_TYPE_RECENT) { + orderBy = "ORDER BY p.last_updated DESC "; + } + else if (sortMethod == SORT_TYPE_REPUTATION) { + orderBy = "ORDER BY p.reputation DESC, p.last_updated DESC "; + } + else { + orderBy = "ORDER BY p.last_updated DESC "; + } + return base + orderBy + (wantLimits ? "LIMIT ? OFFSET ?;" : ";"); +} + +void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool includeNullAddress, bool includeNullDescription, bool playerOwn, bool updateUi, int32_t numResults, int32_t lReputationTime, int32_t sortMethod, int32_t startIndex, std::string filterText, const SystemAddress& sysAddr){ std::vector entries {}; PropertySelectQueryProperty playerEntry {}; - auto* character = entity->GetCharacter(); - playerEntry.OwnerName = character->GetName(); - playerEntry.Description = "No description."; - playerEntry.Name = "Your property!"; - playerEntry.IsModeratorApproved = true; - playerEntry.AccessType = 2; - playerEntry.CloneId = character->GetPropertyCloneID(); + auto character = entity->GetCharacter(); + if (!character) return; + + // Player property goes in index 1 of the vector. This is how the client expects it. + auto playerPropertyLookup = Database::CreatePreppedStmt("SELECT * FROM properties WHERE owner_id = ? AND zone_id = ?"); + + playerPropertyLookup->setInt(1, character->GetID()); + playerPropertyLookup->setInt(2, this->m_MapID); + + auto playerPropertyLookupResults = playerPropertyLookup->executeQuery(); + + // If the player has a property this query will have a single result. + if (playerPropertyLookupResults->next()) { + const auto cloneId = playerPropertyLookupResults->getUInt64(4); + const auto propertyName = playerPropertyLookupResults->getString(5).asStdString(); + const auto propertyDescription = playerPropertyLookupResults->getString(6).asStdString(); + const auto privacyOption = playerPropertyLookupResults->getInt(9); + const auto modApproved = playerPropertyLookupResults->getBoolean(10); + const auto dateLastUpdated = playerPropertyLookupResults->getInt64(11); + const auto reputation = playerPropertyLookupResults->getUInt(14); + const auto performanceCost = (float)playerPropertyLookupResults->getDouble(16); + + playerEntry = SetPropertyValues(playerEntry, cloneId, character->GetName(), propertyName, propertyDescription, reputation, true, true, modApproved, true, true, privacyOption, dateLastUpdated, performanceCost); + } else { + playerEntry = SetPropertyValues(playerEntry, character->GetPropertyCloneID(), character->GetName(), "", "", 0, true, true); + } + + delete playerPropertyLookupResults; + playerPropertyLookupResults = nullptr; + + delete playerPropertyLookup; + playerPropertyLookup = nullptr; entries.push_back(playerEntry); - sql::ResultSet* propertyEntry; - sql::PreparedStatement* propertyLookup; + const auto query = BuildQuery(entity, sortMethod); - const auto moderating = entity->GetGMLevel() >= GAME_MASTER_LEVEL_LEAD_MODERATOR; - - if (!moderating) - { - propertyLookup = Database::CreatePreppedStmt( - "SELECT * FROM properties WHERE (name LIKE ? OR description LIKE ? OR " - "((SELECT name FROM charinfo WHERE prop_clone_id = clone_id) LIKE ?)) AND " - "(privacy_option = 2 AND mod_approved = true) OR (privacy_option >= 1 " - "AND (owner_id IN (SELECT friend_id FROM friends WHERE player_id = ?) OR owner_id IN (SELECT player_id FROM " - "friends WHERE friend_id = ?))) AND zone_id = ? LIMIT ? OFFSET ?;" - ); + auto propertyLookup = Database::CreatePreppedStmt(query); + + const auto searchString = "%" + filterText + "%"; + propertyLookup->setUInt(1, this->m_MapID); + propertyLookup->setString(2, searchString.c_str()); + propertyLookup->setString(3, searchString.c_str()); + propertyLookup->setString(4, searchString.c_str()); + propertyLookup->setInt(5, sortMethod == SORT_TYPE_FEATURED || sortMethod == SORT_TYPE_FRIENDS ? (uint32_t)PropertyPrivacyOption::Friends : (uint32_t)PropertyPrivacyOption::Public); + propertyLookup->setInt(6, numResults); + propertyLookup->setInt(7, startIndex); - const std::string searchString = "%" + filterText + "%"; - Game::logger->Log("PropertyEntranceComponent", "%s\n", searchString.c_str()); - propertyLookup->setString(1, searchString.c_str()); - propertyLookup->setString(2, searchString.c_str()); - propertyLookup->setString(3, searchString.c_str()); - propertyLookup->setInt64(4, entity->GetObjectID()); - propertyLookup->setInt64(5, entity->GetObjectID()); - propertyLookup->setUInt(6, launchpadComponent->GetTargetZone()); - propertyLookup->setInt(7, numResults); - propertyLookup->setInt(8, startIndex); + auto propertyEntry = propertyLookup->executeQuery(); - propertyEntry = propertyLookup->executeQuery(); - } - else - { - propertyLookup = Database::CreatePreppedStmt( - "SELECT * FROM properties WHERE privacy_option = 2 AND mod_approved = false AND zone_id = ?;" - ); - - propertyLookup->setUInt(1, launchpadComponent->GetTargetZone()); - - propertyEntry = propertyLookup->executeQuery(); - } - - while (propertyEntry->next()) - { - const auto propertyId = propertyEntry->getUInt64(1); - const auto owner = propertyEntry->getUInt64(2); + while (propertyEntry->next()) { + const auto propertyId = propertyEntry->getUInt64(1); + const auto owner = propertyEntry->getInt(2); const auto cloneId = propertyEntry->getUInt64(4); - const auto name = propertyEntry->getString(5).asStdString(); - const auto description = propertyEntry->getString(6).asStdString(); - const auto privacyOption = propertyEntry->getInt(9); - const auto reputation = propertyEntry->getInt(15); + const auto propertyNameFromDb = propertyEntry->getString(5).asStdString(); + const auto propertyDescriptionFromDb = propertyEntry->getString(6).asStdString(); + const auto privacyOption = propertyEntry->getInt(9); + const auto modApproved = propertyEntry->getBoolean(10); + const auto dateLastUpdated = propertyEntry->getInt(11); + const float reputation = propertyEntry->getInt(14); + const auto performanceCost = (float)propertyEntry->getDouble(16); - PropertySelectQueryProperty entry {}; - - auto* nameLookup = Database::CreatePreppedStmt("SELECT name FROM charinfo WHERE prop_clone_id = ?;"); + PropertySelectQueryProperty entry{}; + + std::string ownerName = ""; + bool isOwned = true; + auto nameLookup = Database::CreatePreppedStmt("SELECT name FROM charinfo WHERE prop_clone_id = ?;"); nameLookup->setUInt64(1, cloneId); - auto* nameResult = nameLookup->executeQuery(); + auto nameResult = nameLookup->executeQuery(); - if (!nameResult->next()) - { + if (!nameResult->next()) { delete nameLookup; + nameLookup = nullptr; Game::logger->Log("PropertyEntranceComponent", "Failed to find property owner name for %llu!\n", cloneId); continue; + } else { + isOwned = cloneId == character->GetPropertyCloneID(); + ownerName = nameResult->getString(1).asStdString(); } - else - { - entry.IsOwner = owner == entity->GetObjectID(); - entry.OwnerName = nameResult->getString(1).asStdString(); - } - - if (!moderating) - { - entry.Name = name; - entry.Description = description; - } - else - { - entry.Name = "[Awaiting approval] " + name; - entry.Description = "[Awaiting approval] " + description; - } - - entry.IsFriend = privacyOption == static_cast(PropertyPrivacyOption::Friends); - entry.Reputation = reputation; - entry.CloneId = cloneId; - entry.IsModeratorApproved = true; - entry.AccessType = 3; - entries.push_back(entry); + delete nameResult; + nameResult = nullptr; delete nameLookup; - } + nameLookup = nullptr; + + std::string propertyName = propertyNameFromDb; + std::string propertyDescription = propertyDescriptionFromDb; + + bool isBestFriend = false; + bool isFriend = false; + + // Convert owner char id to LWOOBJID + LWOOBJID ownerObjId = owner; + ownerObjId = GeneralUtils::SetBit(ownerObjId, OBJECT_BIT_CHARACTER); + ownerObjId = GeneralUtils::SetBit(ownerObjId, OBJECT_BIT_PERSISTENT); + + // Query to get friend and best friend fields + auto friendCheck = Database::CreatePreppedStmt("SELECT best_friend FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?)"); + + friendCheck->setInt64(1, entity->GetObjectID()); + friendCheck->setInt64(2, ownerObjId); + friendCheck->setInt64(3, ownerObjId); + friendCheck->setInt64(4, entity->GetObjectID()); + + auto friendResult = friendCheck->executeQuery(); + + // If we got a result than the two players are friends. + if (friendResult->next()) { + isFriend = true; + if (friendResult->getInt(1) == 2) { + isBestFriend = true; + } + } + + delete friendCheck; + friendCheck = nullptr; + + delete friendResult; + friendResult = nullptr; + + bool isModeratorApproved = propertyEntry->getBoolean(10); + + if (!isModeratorApproved && entity->GetGMLevel() >= GAME_MASTER_LEVEL_LEAD_MODERATOR) { + propertyName = "[AWAITING APPROVAL]"; + propertyDescription = "[AWAITING APPROVAL]"; + isModeratorApproved = true; + } + + bool isAlt = false; + // Query to determine whether this property is an alt character of the entity. + auto isAltQuery = Database::CreatePreppedStmt("SELECT id FROM charinfo where account_id in (SELECT account_id from charinfo WHERE id = ?) AND id = ?;"); + + isAltQuery->setInt(1, character->GetID()); + isAltQuery->setInt(2, owner); + + auto isAltQueryResults = isAltQuery->executeQuery(); + + if (isAltQueryResults->next()) { + isAlt = true; + } + + delete isAltQueryResults; + isAltQueryResults = nullptr; + + delete isAltQuery; + isAltQuery = nullptr; + + entry = SetPropertyValues(entry, cloneId, ownerName, propertyName, propertyDescription, reputation, isBestFriend, isFriend, isModeratorApproved, isAlt, isOwned, privacyOption, dateLastUpdated, performanceCost); + + entries.push_back(entry); + } + + delete propertyEntry; + propertyEntry = nullptr; delete propertyLookup; - - /* - const auto entriesSize = entries.size(); - - if (startIndex != 0 && entriesSize > startIndex) - { - for (size_t i = 0; i < startIndex; i++) - { - entries.erase(entries.begin()); - } - } - */ + propertyLookup = nullptr; propertyQueries[entity->GetObjectID()] = entries; + + // Query here is to figure out whether or not to display the button to go to the next page or not. + int32_t numberOfProperties = 0; - GameMessages::SendPropertySelectQuery( - m_Parent->GetObjectID(), - startIndex, - entries.size() >= numResults, - character->GetPropertyCloneID(), - false, - true, - entries, - sysAddr - ); + auto buttonQuery = BuildQuery(entity, sortMethod, "SELECT COUNT(*) FROM properties as p JOIN charinfo as ci ON ci.prop_clone_id = p.clone_id where p.zone_id = ? AND (p.description LIKE ? OR p.name LIKE ? OR ci.name LIKE ?) AND p.privacy_option >= ? ", false); + auto propertiesLeft = Database::CreatePreppedStmt(buttonQuery); + + propertiesLeft->setUInt(1, this->m_MapID); + propertiesLeft->setString(2, searchString.c_str()); + propertiesLeft->setString(3, searchString.c_str()); + propertiesLeft->setString(4, searchString.c_str()); + propertiesLeft->setInt(5, sortMethod == SORT_TYPE_FEATURED || sortMethod == SORT_TYPE_FRIENDS ? 1 : 2); + + auto result = propertiesLeft->executeQuery(); + result->next(); + numberOfProperties = result->getInt(1); + + delete result; + result = nullptr; + + delete propertiesLeft; + propertiesLeft = nullptr; + + GameMessages::SendPropertySelectQuery(m_Parent->GetObjectID(), startIndex, numberOfProperties - (startIndex + numResults) > 0, character->GetPropertyCloneID(), false, true, entries, sysAddr); } \ No newline at end of file diff --git a/dGame/dComponents/PropertyEntranceComponent.h b/dGame/dComponents/PropertyEntranceComponent.h index 8e35fd91..a3be38a6 100644 --- a/dGame/dComponents/PropertyEntranceComponent.h +++ b/dGame/dComponents/PropertyEntranceComponent.h @@ -1,17 +1,17 @@ #pragma once +#include + +#include "Component.h" #include "Entity.h" #include "EntityManager.h" #include "GameMessages.h" -#include "Component.h" -#include /** * Represents the launch pad that's used to select and browse properties */ -class PropertyEntranceComponent : public Component -{ -public: +class PropertyEntranceComponent : public Component { + public: static const uint32_t ComponentType = COMPONENT_TYPE_PROPERTY_ENTRANCE; explicit PropertyEntranceComponent(uint32_t componentID, Entity* parent); @@ -24,11 +24,11 @@ public: /** * Handles the event triggered when the entity selects a property to visit and makes the entity to there * @param entity the entity that triggered the event - * @param index the clone ID of the property to visit + * @param index the index of the property property * @param returnToZone whether or not the entity wishes to go back to the launch zone * @param sysAddr the address to send gamemessage responses to */ - void OnEnterProperty(Entity* entity, uint32_t index, bool returnToZone, const SystemAddress &sysAddr); + void OnEnterProperty(Entity* entity, uint32_t index, bool returnToZone, const SystemAddress& sysAddr); /** * Handles a request for information on available properties when an entity lands on the property @@ -38,23 +38,13 @@ public: * @param playerOwn only query properties owned by the entity * @param updateUi unused * @param numResults unused - * @param reputation unused + * @param lReputationTime unused * @param sortMethod unused * @param startIndex the minimum index to start the query off * @param filterText property names to search for * @param sysAddr the address to send gamemessage responses to */ - void OnPropertyEntranceSync(Entity* entity, - bool includeNullAddress, - bool includeNullDescription, - bool playerOwn, - bool updateUi, - int32_t numResults, - int32_t reputation, - int32_t sortMethod, - int32_t startIndex, - std::string filterText, - const SystemAddress &sysAddr); + void OnPropertyEntranceSync(Entity* entity, bool includeNullAddress, bool includeNullDescription, bool playerOwn, bool updateUi, int32_t numResults, int32_t lReputationTime, int32_t sortMethod, int32_t startIndex, std::string filterText, const SystemAddress& sysAddr); /** * Returns the name of this property @@ -68,8 +58,11 @@ public: */ [[nodiscard]] LWOMAPID GetMapID() const { return m_MapID; }; -private: + PropertySelectQueryProperty SetPropertyValues(PropertySelectQueryProperty property, LWOCLONEID cloneId = LWOCLONEID_INVALID, std::string ownerName = "", std::string propertyName = "", std::string propertyDescription = "", float reputation = 0, bool isBFF = false, bool isFriend = false, bool isModeratorApproved = false, bool isAlt = false, bool isOwned = false, uint32_t privacyOption = 0, uint32_t timeLastUpdated = 0, float performanceCost = 0.0f); + std::string BuildQuery(Entity* entity, int32_t sortMethod, std::string customQuery = "", bool wantLimits = true); + + private: /** * Cache of property information that was queried for property launched, indexed by property ID */ @@ -84,4 +77,13 @@ private: * The base map ID for this property (Avant Grove, etc). */ LWOMAPID m_MapID; + + enum ePropertySortType : int32_t { + SORT_TYPE_FRIENDS = 0, + SORT_TYPE_REPUTATION = 1, + SORT_TYPE_RECENT = 3, + SORT_TYPE_FEATURED = 5 + }; + + std::string baseQueryForProperties = "SELECT p.* FROM properties as p JOIN charinfo as ci ON ci.prop_clone_id = p.clone_id where p.zone_id = ? AND (p.description LIKE ? OR p.name LIKE ? OR ci.name LIKE ?) AND p.privacy_option >= ? "; }; diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index d685e8e9..7f82ae27 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -68,13 +68,18 @@ PropertyManagementComponent::PropertyManagementComponent(Entity* parent) : Compo this->owner = propertyEntry->getUInt64(2); this->owner = GeneralUtils::SetBit(this->owner, OBJECT_BIT_CHARACTER); this->owner = GeneralUtils::SetBit(this->owner, OBJECT_BIT_PERSISTENT); + this->clone_Id = propertyEntry->getInt(2); this->propertyName = propertyEntry->getString(5).c_str(); this->propertyDescription = propertyEntry->getString(6).c_str(); this->privacyOption = static_cast(propertyEntry->getUInt(9)); - this->claimedTime = propertyEntry->getUInt64(13); + this->moderatorRequested = propertyEntry->getInt(10) == 0 && rejectionReason == "" && privacyOption == PropertyPrivacyOption::Public; + this->LastUpdatedTime = propertyEntry->getUInt64(11); + this->claimedTime = propertyEntry->getUInt64(12); + this->rejectionReason = propertyEntry->getString(13).asStdString(); + this->reputation = propertyEntry->getUInt(14); Load(); - } + } delete propertyLookup; } @@ -152,12 +157,18 @@ void PropertyManagementComponent::SetPrivacyOption(PropertyPrivacyOption value) value = PropertyPrivacyOption::Private; } + if (value == PropertyPrivacyOption::Public && privacyOption != PropertyPrivacyOption::Public) { + rejectionReason = ""; + moderatorRequested = true; + } privacyOption = value; - auto* propertyUpdate = Database::CreatePreppedStmt("UPDATE properties SET privacy_option = ? WHERE id = ?;"); + auto* propertyUpdate = Database::CreatePreppedStmt("UPDATE properties SET privacy_option = ?, rejection_reason = ?, mod_approved = ? WHERE id = ?;"); propertyUpdate->setInt(1, static_cast(value)); - propertyUpdate->setInt64(2, propertyId); + propertyUpdate->setString(2, ""); + propertyUpdate->setInt(3, 0); + propertyUpdate->setInt64(4, propertyId); propertyUpdate->executeUpdate(); } @@ -181,38 +192,46 @@ void PropertyManagementComponent::UpdatePropertyDetails(std::string name, std::s OnQueryPropertyData(GetOwner(), UNASSIGNED_SYSTEM_ADDRESS); } -void PropertyManagementComponent::Claim(const LWOOBJID playerId) +bool PropertyManagementComponent::Claim(const LWOOBJID playerId) { if (owner != LWOOBJID_EMPTY) { - return; + return false; } - - SetOwnerId(playerId); - - auto* zone = dZoneManager::Instance()->GetZone(); - - const auto& worldId = zone->GetZoneID(); - const auto zoneId = worldId.GetMapID(); - const auto cloneId = worldId.GetCloneID(); auto* entity = EntityManager::Instance()->GetEntity(playerId); auto* user = entity->GetParentUser(); + auto character = entity->GetCharacter(); + if (!character) return false; + + auto* zone = dZoneManager::Instance()->GetZone(); + + const auto& worldId = zone->GetZoneID(); + const auto propertyZoneId = worldId.GetMapID(); + const auto propertyCloneId = worldId.GetCloneID(); + + const auto playerCloneId = character->GetPropertyCloneID(); + + // If we are not on our clone do not allow us to claim the property + if (propertyCloneId != playerCloneId) return false; + + SetOwnerId(playerId); + propertyId = ObjectIDManager::GenerateRandomObjectID(); - + auto* insertion = Database::CreatePreppedStmt( "INSERT INTO properties" - "(id, owner_id, template_id, clone_id, name, description, rent_amount, rent_due, privacy_option, last_updated, time_claimed, rejection_reason, reputation, zone_id)" - "VALUES (?, ?, ?, ?, ?, '', 0, 0, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), '', 0, ?)" + "(id, owner_id, template_id, clone_id, name, description, rent_amount, rent_due, privacy_option, last_updated, time_claimed, rejection_reason, reputation, zone_id, performance_cost)" + "VALUES (?, ?, ?, ?, ?, '', 0, 0, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), '', 0, ?, 0.0)" ); insertion->setUInt64(1, propertyId); insertion->setUInt64(2, (uint32_t) playerId); insertion->setUInt(3, templateId); - insertion->setUInt64(4, cloneId); + insertion->setUInt64(4, playerCloneId); insertion->setString(5, zone->GetZoneName().c_str()); - insertion->setInt(6, zoneId); + insertion->setInt(6, propertyZoneId); // Try and execute the query, print an error if it fails. try @@ -224,12 +243,14 @@ void PropertyManagementComponent::Claim(const LWOOBJID playerId) Game::logger->Log("PropertyManagementComponent", "Failed to execute query: (%s)!\n", exception.what()); throw exception; + return false; } auto* zoneControlObject = dZoneManager::Instance()->GetZoneControlObject(); for (CppScripts::Script* script : CppScripts::GetEntityScripts(zoneControlObject)) { script->OnZonePropertyRented(zoneControlObject, entity); } + return true; } void PropertyManagementComponent::OnStartBuilding() @@ -275,6 +296,8 @@ void PropertyManagementComponent::OnFinishBuilding() SetPrivacyOption(originalPrivacyOption); UpdateApprovedStatus(false); + + Save(); } void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const NiPoint3 position, NiQuaternion rotation) @@ -388,6 +411,9 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N EntityManager::Instance()->GetZoneControlEntity()->OnZonePropertyModelPlaced(entity); }); + // Progress place model missions + auto missionComponent = entity->GetComponent(); + if (missionComponent != nullptr) missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_PLACE_MODEL, 0); } void PropertyManagementComponent::DeleteModel(const LWOOBJID id, const int deleteReason) @@ -465,7 +491,7 @@ void PropertyManagementComponent::DeleteModel(const LWOOBJID id, const int delet settings.push_back(propertyObjectID); settings.push_back(modelType); - inventoryComponent->AddItem(6662, 1, HIDDEN, settings, LWOOBJID_EMPTY, false, false, spawnerId, INVALID, 13, false, -1); + inventoryComponent->AddItem(6662, 1, eLootSourceType::LOOT_SOURCE_DELETION, eInventoryType::HIDDEN, settings, LWOOBJID_EMPTY, false, false, spawnerId); auto* item = inventoryComponent->FindItemBySubKey(spawnerId); if (item == nullptr) { @@ -500,7 +526,7 @@ void PropertyManagementComponent::DeleteModel(const LWOOBJID id, const int delet return; } - inventoryComponent->AddItem(model->GetLOT(), 1, INVALID, {}, LWOOBJID_EMPTY, false); + inventoryComponent->AddItem(model->GetLOT(), 1, eLootSourceType::LOOT_SOURCE_DELETION, INVALID, {}, LWOOBJID_EMPTY, false); auto* item = inventoryComponent->FindItemByLot(model->GetLOT()); @@ -682,9 +708,12 @@ void PropertyManagementComponent::Save() auto* remove = Database::CreatePreppedStmt("DELETE FROM properties_contents WHERE id = ?;"); lookup->setUInt64(1, propertyId); - - auto* lookupResult = lookup->executeQuery(); - + sql::ResultSet* lookupResult = nullptr; + try { + lookupResult = lookup->executeQuery(); + } catch (sql::SQLException& ex) { + Game::logger->Log("PropertyManagementComponent", "lookup error %s\n", ex.what()); + } std::vector present; while (lookupResult->next()) @@ -727,8 +756,11 @@ void PropertyManagementComponent::Save() insertion->setDouble(9, rotation.y); insertion->setDouble(10, rotation.z); insertion->setDouble(11, rotation.w); - - insertion->execute(); + try { + insertion->execute(); + } catch (sql::SQLException& ex) { + Game::logger->Log("PropertyManagementComponent", "Error inserting into properties_contents. Error %s\n", ex.what()); + } } else { @@ -741,8 +773,11 @@ void PropertyManagementComponent::Save() update->setDouble(7, rotation.w); update->setInt64(8, id); - - update->executeUpdate(); + try { + update->executeUpdate(); + } catch (sql::SQLException& ex) { + Game::logger->Log("PropertyManagementComponent", "Error updating properties_contents. Error: %s\n", ex.what()); + } } } @@ -754,8 +789,11 @@ void PropertyManagementComponent::Save() } remove->setInt64(1, id); - - remove->execute(); + try { + remove->execute(); + } catch (sql::SQLException& ex) { + Game::logger->Log("PropertyManagementComponent", "Error removing from properties_contents. Error %s\n", ex.what()); + } } auto* removeUGC = Database::CreatePreppedStmt("DELETE FROM ugc WHERE id NOT IN (SELECT ugc_id FROM properties_contents);"); @@ -779,7 +817,7 @@ PropertyManagementComponent* PropertyManagementComponent::Instance() return instance; } -void PropertyManagementComponent::OnQueryPropertyData(Entity* originator, const SystemAddress& sysAddr, LWOOBJID author) const +void PropertyManagementComponent::OnQueryPropertyData(Entity* originator, const SystemAddress& sysAddr, LWOOBJID author) { if (author == LWOOBJID_EMPTY) { author = m_Parent->GetObjectID(); @@ -818,18 +856,44 @@ void PropertyManagementComponent::OnQueryPropertyData(Entity* originator, const description = propertyDescription; claimed = claimedTime; privacy = static_cast(this->privacyOption); - } + if (moderatorRequested) { + auto checkStatus = Database::CreatePreppedStmt("SELECT rejection_reason, mod_approved FROM properties WHERE id = ?;"); + checkStatus->setInt64(1, propertyId); + + auto result = checkStatus->executeQuery(); + + result->next(); + + const auto reason = result->getString(1).asStdString();; + const auto modApproved = result->getInt(2); + if (reason != "") { + moderatorRequested = false; + rejectionReason = reason; + } else if (reason == "" && modApproved == 1) { + moderatorRequested = false; + rejectionReason = ""; + } else { + moderatorRequested = true; + rejectionReason = ""; + } + } + } + message.moderatorRequested = moderatorRequested; + message.reputation = reputation; + message.LastUpdatedTime = LastUpdatedTime; message.OwnerId = ownerId; message.OwnerName = ownerName; message.Name = name; message.Description = description; message.ClaimedTime = claimed; message.PrivacyOption = privacy; - + message.cloneId = clone_Id; + message.rejectionReason = rejectionReason; message.Paths = GetPaths(); SendDownloadPropertyData(author, message, UNASSIGNED_SYSTEM_ADDRESS); + // send rejection here? } void PropertyManagementComponent::OnUse(Entity* originator) diff --git a/dGame/dComponents/PropertyManagementComponent.h b/dGame/dComponents/PropertyManagementComponent.h index bf577760..c03c4949 100644 --- a/dGame/dComponents/PropertyManagementComponent.h +++ b/dGame/dComponents/PropertyManagementComponent.h @@ -1,5 +1,6 @@ #pragma once +#include #include "Entity.h" #include "Component.h" @@ -40,7 +41,7 @@ public: * @param sysAddr the address to send game message responses to * @param author optional explicit ID for the property, if not set defaults to the originator */ - void OnQueryPropertyData(Entity* originator, const SystemAddress& sysAddr, LWOOBJID author = LWOOBJID_EMPTY) const; + void OnQueryPropertyData(Entity* originator, const SystemAddress& sysAddr, LWOOBJID author = LWOOBJID_EMPTY); /** * Handles an OnUse event, telling the client who owns this property, etc. @@ -100,8 +101,10 @@ public: /** * Makes this property owned by the passed player ID, storing it in the database * @param playerId the ID of the entity that claimed the property + * + * @return If the claim is successful return true. */ - void Claim(LWOOBJID playerId); + bool Claim(LWOOBJID playerId); /** * Event triggered when the owner of the property starts building, will kick other entities out @@ -158,6 +161,8 @@ public: */ const std::map& GetModels() const; + LWOCLONEID GetCloneId() { return clone_Id; }; + private: /** * This @@ -182,7 +187,7 @@ private: /** * The time since this property was claimed */ - uint64_t claimedTime = 0; + uint64_t claimedTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); /** * The models that are placed on this property @@ -194,11 +199,36 @@ private: */ std::string propertyName = ""; + /** + * The clone ID of this property + */ + LWOCLONEID clone_Id = 0; + + /** + * Whether a moderator was requested + */ + bool moderatorRequested = false; + + /** + * The rejection reason for the property + */ + std::string rejectionReason = ""; + /** * The description of this property */ std::string propertyDescription = ""; + /** + * The reputation of this property + */ + uint32_t reputation = 0; + + /** + * The last time this property was updated + */ + uint32_t LastUpdatedTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + /** * Determines which players may visit this property */ diff --git a/dGame/dComponents/PropertyVendorComponent.cpp b/dGame/dComponents/PropertyVendorComponent.cpp index e6b8fe97..72e8ad64 100644 --- a/dGame/dComponents/PropertyVendorComponent.cpp +++ b/dGame/dComponents/PropertyVendorComponent.cpp @@ -41,13 +41,16 @@ void PropertyVendorComponent::OnBuyFromVendor(Entity* originator, const bool con { if (PropertyManagementComponent::Instance() == nullptr) return; + if (PropertyManagementComponent::Instance()->Claim(originator->GetObjectID()) == false) { + Game::logger->Log("PropertyVendorComponent", "FAILED TO CLAIM PROPERTY. PLAYER ID IS %llu\n", originator->GetObjectID()); + return; + } + GameMessages::SendPropertyRentalResponse(m_Parent->GetObjectID(), 0, 0, 0, 0, originator->GetSystemAddress()); auto* controller = dZoneManager::Instance()->GetZoneControlObject(); controller->OnFireEventServerSide(m_Parent, "propertyRented"); - - PropertyManagementComponent::Instance()->Claim(originator->GetObjectID()); PropertyManagementComponent::Instance()->SetOwner(originator); diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index bbd85672..1d86b274 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -15,6 +15,7 @@ #include "Player.h" #include "PossessableComponent.h" #include "PossessorComponent.h" +#include "RacingTaskParam.h" #include "Spawner.h" #include "VehiclePhysicsComponent.h" #include "dServer.h" @@ -52,6 +53,11 @@ RacingControlComponent::RacingControlComponent(Entity *parent) m_MainWorld = 1200; break; + case 1261: + m_ActivityID = 60; + m_MainWorld = 1260; + break; + case 1303: m_ActivityID = 39; m_MainWorld = 1300; @@ -401,18 +407,21 @@ void RacingControlComponent::HandleMessageBoxResponse(Entity *player, auto *missionComponent = player->GetComponent(); - if (missionComponent != nullptr) { - missionComponent->Progress( - MissionTaskType::MISSION_TASK_TYPE_RACING, 0, 13); // Enter race - missionComponent->Progress( - MissionTaskType::MISSION_TASK_TYPE_RACING, data->finished, - 1); // Finish with rating, one track - missionComponent->Progress( - MissionTaskType::MISSION_TASK_TYPE_RACING, data->finished, - 15); // Finish with rating, multiple tracks - missionComponent->Progress( - MissionTaskType::MISSION_TASK_TYPE_RACING, data->smashedTimes, - 10); // Safe driver type missions + if (missionComponent == nullptr) return; + + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, 0, (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_COMPETED_IN_RACE); // Progress task for competing in a race + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, data->smashedTimes, (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_SAFE_DRIVER); // Finish a race without being smashed. + + // If solo racing is enabled OR if there are 3 players in the race, progress placement tasks. + if(m_SoloRacing || m_LoadedPlayers > 2) { + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, data->finished, (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_FINISH_WITH_PLACEMENT); // Finish in 1st place on a race + if(data->finished == 1) { + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, dZoneManager::Instance()->GetZone()->GetWorldID(), (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_FIRST_PLACE_MULTIPLE_TRACKS); // Finish in 1st place on multiple tracks. + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, dZoneManager::Instance()->GetZone()->GetWorldID(), (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_WIN_RACE_IN_WORLD); // Finished first place in specific world. + } + if (data->finished == m_LoadedPlayers) { + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, dZoneManager::Instance()->GetZone()->GetWorldID(), (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_LAST_PLACE_FINISH); // Finished first place in specific world. + } } } else if (id == "ACT_RACE_EXIT_THE_RACE?" || id == "Exit") { auto *vehicle = EntityManager::Instance()->GetEntity(data->vehicleID); @@ -809,9 +818,7 @@ void RacingControlComponent::Update(float deltaTime) { // Reached the start point, lapped if (respawnIndex == 0) { - time_t lapTime = - std::time(nullptr) - - (player.lap == 1 ? m_StartTime : player.lapTime); + time_t lapTime = std::time(nullptr) - (player.lap == 1 ? m_StartTime : player.lapTime); // Cheating check if (lapTime < 40) { @@ -833,10 +840,9 @@ void RacingControlComponent::Update(float deltaTime) { playerEntity->GetComponent(); if (missionComponent != nullptr) { - // Lap time - missionComponent->Progress( - MissionTaskType::MISSION_TASK_TYPE_RACING, - (lapTime)*1000, 2); + + // Progress lap time tasks + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, (lapTime)*1000, (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_LAP_TIME); if (player.lap == 3) { m_Finished++; @@ -852,15 +858,11 @@ void RacingControlComponent::Update(float deltaTime) { raceTime, raceTime * 1000); // Entire race time - missionComponent->Progress( - MissionTaskType::MISSION_TASK_TYPE_RACING, - (raceTime)*1000, 3); + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, (raceTime)*1000, (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_TOTAL_TRACK_TIME); - auto *characterComponent = - playerEntity->GetComponent(); + auto *characterComponent = playerEntity->GetComponent(); if (characterComponent != nullptr) { - characterComponent->TrackRaceCompleted(m_Finished == - 1); + characterComponent->TrackRaceCompleted(m_Finished == 1); } // TODO: Figure out how to update the GUI leaderboard. diff --git a/dGame/dComponents/RacingControlComponent.h b/dGame/dComponents/RacingControlComponent.h index 0dbb9eaa..63d5b2e4 100644 --- a/dGame/dComponents/RacingControlComponent.h +++ b/dGame/dComponents/RacingControlComponent.h @@ -146,7 +146,7 @@ public: void HandleMessageBoxResponse(Entity* player, const std::string& id); /** - * Get the reacing data from a player's LWOOBJID. + * Get the racing data from a player's LWOOBJID. */ RacingPlayerInfo* GetPlayerData(LWOOBJID playerID); @@ -230,7 +230,7 @@ private: std::vector m_LobbyPlayers; /** - * The number of players that have fi nished the race + * The number of players that have finished the race */ uint32_t m_Finished; diff --git a/dGame/dComponents/RebuildComponent.cpp b/dGame/dComponents/RebuildComponent.cpp index 7d4ae926..abee5e16 100644 --- a/dGame/dComponents/RebuildComponent.cpp +++ b/dGame/dComponents/RebuildComponent.cpp @@ -45,7 +45,14 @@ void RebuildComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitia outBitStream->Write(false); } - + // If build state is completed and we've already serialized once in the completed state, + // don't serializing this component anymore as this will cause the build to jump again. + // If state changes, serialization will begin again. + if (!m_StateDirty && m_State == REBUILD_COMPLETED) { + outBitStream->Write0(); + outBitStream->Write0(); + return; + } // BEGIN Scripted Activity outBitStream->Write1(); @@ -79,6 +86,7 @@ void RebuildComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitia outBitStream->Write(m_ActivatorPosition); outBitStream->Write(m_RepositionPlayer); } + m_StateDirty = false; } void RebuildComponent::Update(float deltaTime) { @@ -139,7 +147,6 @@ void RebuildComponent::Update(float deltaTime) { } if (m_Timer >= m_ResetTime) { - m_Builder = LWOOBJID_EMPTY; GameMessages::SendDieNoImplCode(m_Parent, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eKillType::VIOLENT, u"", 0.0f, 0.0f, 0.0f, false, true); @@ -380,11 +387,11 @@ void RebuildComponent::StartRebuild(Entity* user) { EntityManager::Instance()->SerializeEntity(user); - GameMessages::SendRebuildNotifyState(m_Parent, m_State, eRebuildState::REBUILD_COMPLETED, user->GetObjectID()); + GameMessages::SendRebuildNotifyState(m_Parent, m_State, eRebuildState::REBUILD_BUILDING, user->GetObjectID()); GameMessages::SendEnableRebuild(m_Parent, true, false, false, eFailReason::REASON_NOT_GIVEN, 0.0f, user->GetObjectID()); m_State = eRebuildState::REBUILD_BUILDING; - + m_StateDirty = true; EntityManager::Instance()->SerializeEntity(m_Parent); auto* movingPlatform = m_Parent->GetComponent(); @@ -421,17 +428,18 @@ void RebuildComponent::CompleteRebuild(Entity* user) { EntityManager::Instance()->SerializeEntity(user); GameMessages::SendRebuildNotifyState(m_Parent, m_State, eRebuildState::REBUILD_COMPLETED, user->GetObjectID()); - GameMessages::SendEnableRebuild(m_Parent, false, true, false, eFailReason::REASON_NOT_GIVEN, m_ResetTime, user->GetObjectID()); + GameMessages::SendPlayFXEffect(m_Parent, 507, u"create", "BrickFadeUpVisCompleteEffect", LWOOBJID_EMPTY, 0.4f, 1.0f, true); + GameMessages::SendEnableRebuild(m_Parent, false, false, true, eFailReason::REASON_NOT_GIVEN, m_ResetTime, user->GetObjectID()); + GameMessages::SendTerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()); + m_State = eRebuildState::REBUILD_COMPLETED; + m_StateDirty = true; m_Timer = 0.0f; m_DrainedImagination = 0; EntityManager::Instance()->SerializeEntity(m_Parent); - GameMessages::SendPlayFXEffect(m_Parent, 507, u"create", "BrickFadeUpVisCompleteEffect", LWOOBJID_EMPTY, 0.4f, 1.0f, true); - GameMessages::SendTerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()); - // Removes extra item requirements, isn't live accurate. // In live, all items were removed at the start of the quickbuild, then returned if it was cancelled. // TODO: fix? @@ -455,8 +463,6 @@ void RebuildComponent::CompleteRebuild(Entity* user) { LootGenerator::Instance().DropActivityLoot(builder, m_Parent, m_ActivityId, 1); } - m_Builder = LWOOBJID_EMPTY; - // Notify scripts for (auto* script : CppScripts::GetEntityScripts(m_Parent)) { script->OnRebuildComplete(m_Parent, user); @@ -484,6 +490,7 @@ void RebuildComponent::CompleteRebuild(Entity* user) { character->SetPlayerFlag(flagNumber, true); } } + GameMessages::SendPlayAnimation(user, u"rebuild-celebrate", 1.09f); } void RebuildComponent::ResetRebuild(bool failed) { @@ -500,12 +507,11 @@ void RebuildComponent::ResetRebuild(bool failed) { GameMessages::SendRebuildNotifyState(m_Parent, m_State, eRebuildState::REBUILD_RESETTING, LWOOBJID_EMPTY); m_State = eRebuildState::REBUILD_RESETTING; + m_StateDirty = true; m_Timer = 0.0f; m_TimerIncomplete = 0.0f; m_ShowResetEffect = false; m_DrainedImagination = 0; - - m_Builder = LWOOBJID_EMPTY; EntityManager::Instance()->SerializeEntity(m_Parent); @@ -540,6 +546,7 @@ void RebuildComponent::CancelRebuild(Entity* entity, eFailReason failReason, boo // Now update the component itself m_State = eRebuildState::REBUILD_INCOMPLETE; + m_StateDirty = true; // Notify scripts and possible subscribers for (auto* script : CppScripts::GetEntityScripts(m_Parent)) diff --git a/dGame/dComponents/RebuildComponent.h b/dGame/dComponents/RebuildComponent.h index c177f42c..72c57bb0 100644 --- a/dGame/dComponents/RebuildComponent.h +++ b/dGame/dComponents/RebuildComponent.h @@ -216,7 +216,11 @@ public: */ void CancelRebuild(Entity* builder, eFailReason failReason, bool skipChecks = false); private: - + /** + * Whether or not the quickbuild state has been changed since we last serialized it. + */ + bool m_StateDirty = true; + /** * The state the rebuild is currently in */ @@ -235,7 +239,7 @@ private: /** * The position that the rebuild activator is spawned at */ - NiPoint3 m_ActivatorPosition {}; + NiPoint3 m_ActivatorPosition = NiPoint3::ZERO; /** * The entity that represents the rebuild activator diff --git a/dGame/dComponents/RocketLaunchLupComponent.cpp b/dGame/dComponents/RocketLaunchLupComponent.cpp new file mode 100644 index 00000000..3c326540 --- /dev/null +++ b/dGame/dComponents/RocketLaunchLupComponent.cpp @@ -0,0 +1,32 @@ +#include "RocketLaunchLupComponent.h" +#include "RocketLaunchpadControlComponent.h" +#include "InventoryComponent.h" +#include "CharacterComponent.h" + +RocketLaunchLupComponent::RocketLaunchLupComponent(Entity* parent) : Component(parent) { + m_Parent = parent; + std::string zoneString = GeneralUtils::UTF16ToWTF8(m_Parent->GetVar(u"MultiZoneIDs")); + std::stringstream ss(zoneString); + for (int i; ss >> i;) { + m_LUPWorlds.push_back(i); + if (ss.peek() == ';') + ss.ignore(); + } +} + +RocketLaunchLupComponent::~RocketLaunchLupComponent() {} + +void RocketLaunchLupComponent::OnUse(Entity* originator) { + auto* rocket = originator->GetComponent()->RocketEquip(originator); + if (!rocket) return; + + // the LUP world menu is just the property menu, the client knows how to handle it + GameMessages::SendPropertyEntranceBegin(m_Parent->GetObjectID(), m_Parent->GetSystemAddress()); +} + +void RocketLaunchLupComponent::OnSelectWorld(Entity* originator, uint32_t index) { + auto* rocketLaunchpadControlComponent = m_Parent->GetComponent(); + if (!rocketLaunchpadControlComponent) return; + + rocketLaunchpadControlComponent->Launch(originator, m_LUPWorlds[index], 0); +} diff --git a/dGame/dComponents/RocketLaunchLupComponent.h b/dGame/dComponents/RocketLaunchLupComponent.h new file mode 100644 index 00000000..ce915d70 --- /dev/null +++ b/dGame/dComponents/RocketLaunchLupComponent.h @@ -0,0 +1,39 @@ +#pragma once + +#include "Entity.h" +#include "GameMessages.h" +#include "Component.h" + +/** + * Component that handles the LUP/WBL rocket launchpad that can be interacted with to travel to WBL worlds. + * + */ +class RocketLaunchLupComponent : public Component { +public: + static const uint32_t ComponentType = eReplicaComponentType::COMPONENT_TYPE_ROCKET_LAUNCH_LUP; + + /** + * Constructor for this component, builds the m_LUPWorlds vector + * @param parent parent that contains this component + */ + RocketLaunchLupComponent(Entity* parent); + ~RocketLaunchLupComponent() override; + + /** + * Handles an OnUse event from some entity, preparing it for launch to some other world + * @param originator the entity that triggered the event + */ + void OnUse(Entity* originator) override; + + /** + * Handles an OnUse event from some entity, preparing it for launch to some other world + * @param originator the entity that triggered the event + * @param index index of the world that was selected + */ + void OnSelectWorld(Entity* originator, uint32_t index); +private: + /** + * vector of the LUP World Zone IDs, built from CDServer's LUPZoneIDs table + */ + std::vector m_LUPWorlds {}; +}; diff --git a/dGame/dComponents/RocketLaunchpadControlComponent.cpp b/dGame/dComponents/RocketLaunchpadControlComponent.cpp index 2a4c138e..357fb2d0 100644 --- a/dGame/dComponents/RocketLaunchpadControlComponent.cpp +++ b/dGame/dComponents/RocketLaunchpadControlComponent.cpp @@ -13,6 +13,7 @@ #include "ChatPackets.h" #include "MissionComponent.h" #include "PropertyEntranceComponent.h" +#include "RocketLaunchLupComponent.h" #include "dServer.h" #include "dMessageIdentifiers.h" #include "PacketUtils.h" @@ -40,19 +41,7 @@ RocketLaunchpadControlComponent::~RocketLaunchpadControlComponent() { delete m_AltPrecondition; } -void RocketLaunchpadControlComponent::RocketEquip(Entity* entity, LWOOBJID rocketID) { - if (m_PlayersInRadius.find(entity->GetObjectID()) != m_PlayersInRadius.end()) { - Launch(entity, rocketID); - - //Go ahead and save the player - //This causes a double-save, but it should prevent players from not being saved - //before the next world server starts loading their data. - if (entity->GetCharacter()) - entity->GetCharacter()->SaveXMLToDatabase(); - } -} - -void RocketLaunchpadControlComponent::Launch(Entity* originator, LWOOBJID optionalRocketID, LWOMAPID mapId, LWOCLONEID cloneId) { +void RocketLaunchpadControlComponent::Launch(Entity* originator, LWOMAPID mapId, LWOCLONEID cloneId) { auto zone = mapId == LWOMAPID_INVALID ? m_TargetZone : mapId; if (zone == 0) @@ -60,53 +49,22 @@ void RocketLaunchpadControlComponent::Launch(Entity* originator, LWOOBJID option return; } - TellMasterToPrepZone(zone); - // This also gets triggered by a proximity monitor + item equip, I will set that up when havok is ready - auto* inventoryComponent = originator->GetComponent(); auto* characterComponent = originator->GetComponent(); - auto* character = originator->GetCharacter(); - if (inventoryComponent == nullptr || characterComponent == nullptr || character == nullptr) { + if (!characterComponent || !character) return; + + auto* rocket = characterComponent->GetRocket(originator); + if (!rocket) { + Game::logger->Log("RocketLaunchpadControlComponent", "Unable to find rocket!\n"); return; } - // Select the rocket - - Item* rocket = nullptr; - - if (optionalRocketID != LWOOBJID_EMPTY) - { - rocket = inventoryComponent->FindItemById(optionalRocketID); - } - - if (rocket == nullptr) - { - rocket = inventoryComponent->FindItemById(characterComponent->GetLastRocketItemID()); - } - - if (rocket == nullptr) - { - rocket = inventoryComponent->FindItemByLot(6416); - } - - if (rocket == nullptr) - { - Game::logger->Log("RocketLaunchpadControlComponent", "Unable to find rocket (%llu)!\n", optionalRocketID); - - return; - } - - if (rocket->GetConfig().empty()) // Sanity check - { - rocket->SetCount(0, false, false); - - return; - } + // we have the ability to launch, so now we prep the zone + TellMasterToPrepZone(zone); // Achievement unlocked: "All zones unlocked" - if (!m_AltLandingScene.empty() && m_AltPrecondition->Check(originator)) { character->SetTargetScene(m_AltLandingScene); } @@ -114,27 +72,6 @@ void RocketLaunchpadControlComponent::Launch(Entity* originator, LWOOBJID option character->SetTargetScene(m_TargetScene); } - if (characterComponent) { - for (LDFBaseData* data : rocket->GetConfig()) { - if (data->GetKey() == u"assemblyPartLOTs") { - std::string newRocketStr; - for (char character : data->GetValueAsString()) { - if (character == '+') { - newRocketStr.push_back(';'); - } - else { - newRocketStr.push_back(character); - } - } - newRocketStr.push_back(';'); - characterComponent->SetLastRocketConfig(GeneralUtils::ASCIIToUTF16(newRocketStr)); - } - } - } - - // Store the last used rocket item's ID - characterComponent->SetLastRocketItemID(rocket->GetId()); - characterComponent->UpdatePlayerStatistic(RocketsUsed); character->SaveXMLToDatabase(); @@ -142,24 +79,32 @@ void RocketLaunchpadControlComponent::Launch(Entity* originator, LWOOBJID option SetSelectedMapId(originator->GetObjectID(), zone); GameMessages::SendFireEventClientSide(m_Parent->GetObjectID(), originator->GetSystemAddress(), u"RocketEquipped", rocket->GetId(), cloneId, -1, originator->GetObjectID()); - - rocket->Equip(true); - + GameMessages::SendChangeObjectWorldState(rocket->GetId(), WORLDSTATE_ATTACHED, UNASSIGNED_SYSTEM_ADDRESS); EntityManager::Instance()->SerializeEntity(originator); } void RocketLaunchpadControlComponent::OnUse(Entity* originator) { + // If we are have the property or the LUP component, we don't want to immediately launch + // instead we let their OnUse handlers do their things + // which components of an Object have their OnUse called when using them + // so we don't need to call it here auto* propertyEntrance = m_Parent->GetComponent(); - - if (propertyEntrance != nullptr) - { - propertyEntrance->OnUse(originator); - + if (propertyEntrance) { return; } + auto* rocketLaunchLUP = m_Parent->GetComponent(); + if (rocketLaunchLUP) { + return; + } + + // No rocket no launch + auto* rocket = originator->GetComponent()->RocketEquip(originator); + if (!rocket) { + return; + } Launch(originator); } diff --git a/dGame/dComponents/RocketLaunchpadControlComponent.h b/dGame/dComponents/RocketLaunchpadControlComponent.h index c9ee0691..7fdf4b32 100644 --- a/dGame/dComponents/RocketLaunchpadControlComponent.h +++ b/dGame/dComponents/RocketLaunchpadControlComponent.h @@ -22,21 +22,13 @@ public: RocketLaunchpadControlComponent(Entity* parent, int rocketId); ~RocketLaunchpadControlComponent() override; - /** - * Launches the passed entity using the passed rocket and saves their data - * @param entity the entity to launch - * @param rocketID the ID of the rocket to use - */ - void RocketEquip(Entity* entity, LWOOBJID rocketID); - /** * Launches some entity to another world * @param originator the entity to launch - * @param optionalRocketID the ID of the rocket to launch * @param mapId the world to go to * @param cloneId the clone ID (for properties) */ - void Launch(Entity* originator, LWOOBJID optionalRocketID = LWOOBJID_EMPTY, LWOMAPID mapId = LWOMAPID_INVALID, LWOCLONEID cloneId = LWOCLONEID_INVALID); + void Launch(Entity* originator, LWOMAPID mapId = LWOMAPID_INVALID, LWOCLONEID cloneId = LWOCLONEID_INVALID); /** * Handles an OnUse event from some entity, preparing it for launch to some other world diff --git a/dGame/dComponents/ScriptedActivityComponent.cpp b/dGame/dComponents/ScriptedActivityComponent.cpp index 55add0b6..026fafce 100644 --- a/dGame/dComponents/ScriptedActivityComponent.cpp +++ b/dGame/dComponents/ScriptedActivityComponent.cpp @@ -28,7 +28,7 @@ ScriptedActivityComponent::ScriptedActivityComponent(Entity* parent, int activit const auto mapID = m_ActivityInfo.instanceMapID; - if ((mapID == 1203 || mapID == 1303 || mapID == 1403) && Game::config->GetValue("solo_racing") == "1") { + if ((mapID == 1203 || mapID == 1261 || mapID == 1303 || mapID == 1403) && Game::config->GetValue("solo_racing") == "1") { m_ActivityInfo.minTeamSize = 1; m_ActivityInfo.minTeams = 1; } diff --git a/dGame/dComponents/SimplePhysicsComponent.cpp b/dGame/dComponents/SimplePhysicsComponent.cpp index 8a4cff4e..c9a42971 100644 --- a/dGame/dComponents/SimplePhysicsComponent.cpp +++ b/dGame/dComponents/SimplePhysicsComponent.cpp @@ -17,6 +17,17 @@ SimplePhysicsComponent::SimplePhysicsComponent(uint32_t componentID, Entity* par m_Position = m_Parent->GetDefaultPosition(); m_Rotation = m_Parent->GetDefaultRotation(); m_IsDirty = true; + + const auto& climbable_type = m_Parent->GetVar(u"climbable"); + if (climbable_type == u"wall") { + SetClimbableType(eClimbableType::CLIMBABLE_TYPE_WALL); + } else if (climbable_type == u"ladder") { + SetClimbableType(eClimbableType::CLIMBABLE_TYPE_LADDER); + } else if (climbable_type == u"wallstick") { + SetClimbableType(eClimbableType::CLIMBABLE_TYPE_WALL_STICK); + } else { + SetClimbableType(eClimbableType::CLIMBABLE_TYPE_NOT); + } } SimplePhysicsComponent::~SimplePhysicsComponent() { @@ -24,10 +35,10 @@ SimplePhysicsComponent::~SimplePhysicsComponent() { void SimplePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { if (bIsInitialUpdate) { - outBitStream->Write0(); // climbable - outBitStream->Write(0); // climbableType + outBitStream->Write(m_ClimbableType != eClimbableType::CLIMBABLE_TYPE_NOT); + outBitStream->Write(m_ClimbableType); } - + outBitStream->Write(m_DirtyVelocity || bIsInitialUpdate); if (m_DirtyVelocity || bIsInitialUpdate) { outBitStream->Write(m_Velocity); @@ -46,7 +57,7 @@ void SimplePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bool bIs { outBitStream->Write0(); } - + outBitStream->Write(m_IsDirty || bIsInitialUpdate); if (m_IsDirty || bIsInitialUpdate) { outBitStream->Write(m_Position.x); @@ -66,7 +77,7 @@ uint32_t SimplePhysicsComponent::GetPhysicsMotionState() const return m_PhysicsMotionState; } -void SimplePhysicsComponent::SetPhysicsMotionState(uint32_t value) +void SimplePhysicsComponent::SetPhysicsMotionState(uint32_t value) { m_PhysicsMotionState = value; } diff --git a/dGame/dComponents/SimplePhysicsComponent.h b/dGame/dComponents/SimplePhysicsComponent.h index 081b056b..49e6be5b 100644 --- a/dGame/dComponents/SimplePhysicsComponent.h +++ b/dGame/dComponents/SimplePhysicsComponent.h @@ -14,16 +14,24 @@ class Entity; +enum class eClimbableType : int32_t { + CLIMBABLE_TYPE_NOT = 0, + CLIMBABLE_TYPE_LADDER, + CLIMBABLE_TYPE_WALL, + CLIMBABLE_TYPE_WALL_STICK +}; + + /** * Component that serializes locations of entities to the client */ class SimplePhysicsComponent : public Component { public: static const uint32_t ComponentType = COMPONENT_TYPE_SIMPLE_PHYSICS; - + SimplePhysicsComponent(uint32_t componentID, Entity* parent); ~SimplePhysicsComponent() override; - + void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); /** @@ -86,6 +94,18 @@ public: */ void SetPhysicsMotionState(uint32_t value); + /** + * Returns the ClimbableType of this entity + * @return the ClimbableType of this entity + */ + const eClimbableType& GetClimabbleType() { return m_ClimbableType; } + + /** + * Sets the ClimbableType of this entity + * @param value the ClimbableType to set + */ + void SetClimbableType(const eClimbableType& value) { m_ClimbableType = value; } + private: /** @@ -122,6 +142,11 @@ private: * The current physics motion state */ uint32_t m_PhysicsMotionState = 0; + + /** + * Whether or not the entity is climbable + */ + eClimbableType m_ClimbableType = eClimbableType::CLIMBABLE_TYPE_NOT; }; #endif // SIMPLEPHYSICSCOMPONENT_H diff --git a/dGame/dComponents/SkillComponent.cpp b/dGame/dComponents/SkillComponent.cpp index 7e8b20e3..7aa29523 100644 --- a/dGame/dComponents/SkillComponent.cpp +++ b/dGame/dComponents/SkillComponent.cpp @@ -25,12 +25,14 @@ ProjectileSyncEntry::ProjectileSyncEntry() { } -bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t skillUid, RakNet::BitStream* bitStream, const LWOOBJID target) +bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t skillUid, RakNet::BitStream* bitStream, const LWOOBJID target, uint32_t skillID) { auto* context = new BehaviorContext(this->m_Parent->GetObjectID()); context->caster = m_Parent->GetObjectID(); + context->skillID = skillID; + this->m_managedBehaviors.insert_or_assign(skillUid, context); auto* behavior = Behavior::CreateBehavior(behaviorId); diff --git a/dGame/dComponents/SkillComponent.h b/dGame/dComponents/SkillComponent.h index c0738efc..ad2449c3 100644 --- a/dGame/dComponents/SkillComponent.h +++ b/dGame/dComponents/SkillComponent.h @@ -92,7 +92,7 @@ public: * @param bitStream the bitSteam given by the client to determine the behavior path * @param target the explicit target of the skill */ - bool CastPlayerSkill(uint32_t behaviorId, uint32_t skillUid, RakNet::BitStream* bitStream, LWOOBJID target); + bool CastPlayerSkill(uint32_t behaviorId, uint32_t skillUid, RakNet::BitStream* bitStream, LWOOBJID target, uint32_t skillID = 0); /** * Continues a player skill. Should only be called when the server receives a sync message from the client. diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index b4346bb7..6a8e7356 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -1,116 +1,131 @@ #include "VendorComponent.h" -#include "Game.h" -#include "dServer.h" #include +#include "Game.h" +#include "dServer.h" + VendorComponent::VendorComponent(Entity* parent) : Component(parent) { - auto* compRegistryTable = CDClientManager::Instance()->GetTable("ComponentsRegistry"); - auto* vendorComponentTable = CDClientManager::Instance()->GetTable("VendorComponent"); - auto* lootMatrixTable = CDClientManager::Instance()->GetTable("LootMatrix"); - auto* lootTableTable = CDClientManager::Instance()->GetTable("LootTable"); - - int componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), COMPONENT_TYPE_VENDOR); - std::vector vendorComps = vendorComponentTable->Query([=](CDVendorComponent entry) { return (entry.id == componentID); }); - if (vendorComps.empty()) { - return; - } - m_BuyScalar = vendorComps[0].buyScalar; - m_SellScalar = vendorComps[0].sellScalar; - int lootMatrixID = vendorComps[0].LootMatrixIndex; - std::vector lootMatrices = lootMatrixTable->Query([=](CDLootMatrix entry) { return (entry.LootMatrixIndex == lootMatrixID); }); - if (lootMatrices.empty()) { - return; - } - for (const auto& lootMatrix : lootMatrices) { - int lootTableID = lootMatrix.LootTableIndex; - std::vector vendorItems = lootTableTable->Query([=](CDLootTable entry) { return (entry.LootTableIndex == lootTableID); }); - if (lootMatrix.maxToDrop == 0 || lootMatrix.minToDrop == 0) { - for (CDLootTable item : vendorItems) { - m_Inventory.insert({item.itemid, item.sortPriority}); - } - } else { - auto randomCount = GeneralUtils::GenerateRandomNumber(lootMatrix.minToDrop, lootMatrix.maxToDrop); - - for (size_t i = 0; i < randomCount; i++) { - if (vendorItems.empty()) { - break; - } - - auto randomItemIndex = GeneralUtils::GenerateRandomNumber(0, vendorItems.size() - 1); - - const auto& randomItem = vendorItems[randomItemIndex]; - - vendorItems.erase(vendorItems.begin() + randomItemIndex); - - m_Inventory.insert({randomItem.itemid, randomItem.sortPriority}); - } - } - } - - //Because I want a vendor to sell these cameras - if (parent->GetLOT() == 13569) { - auto randomCamera = GeneralUtils::GenerateRandomNumber(0, 2); - - switch (randomCamera) { - case 0: - m_Inventory.insert({16253, 0}); //Grungagroid - break; - case 1: - m_Inventory.insert({16254, 0}); //Hipstabrick - break; - case 2: - m_Inventory.insert({16204, 0}); //Megabrixel snapshot - break; - default: - break; - } - } - - //Custom code for Max vanity NPC - if (parent->GetLOT() == 9749 && Game::server->GetZoneID() == 1201) { - m_Inventory.clear(); - m_Inventory.insert({11909, 0}); //Top hat w frog - m_Inventory.insert({7785, 0}); //Flash bulb - m_Inventory.insert({12764, 0}); //Big fountain soda - m_Inventory.insert({12241, 0}); //Hot cocoa (from fb) - } + SetupConstants(); + RefreshInventory(true); } VendorComponent::~VendorComponent() = default; void VendorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { - outBitStream->Write1(); - outBitStream->Write1(); // this bit is REQUIRED for vendor + mission multiinteract - outBitStream->Write(HasCraftingStation()); + outBitStream->Write1(); + outBitStream->Write1(); // Has standard items (Required for vendors with missions.) + outBitStream->Write(HasCraftingStation()); // Has multi use items } void VendorComponent::OnUse(Entity* originator) { - GameMessages::SendVendorOpenWindow(m_Parent, originator->GetSystemAddress()); - GameMessages::SendVendorStatusUpdate(m_Parent, originator->GetSystemAddress()); + GameMessages::SendVendorOpenWindow(m_Parent, originator->GetSystemAddress()); + GameMessages::SendVendorStatusUpdate(m_Parent, originator->GetSystemAddress()); } float VendorComponent::GetBuyScalar() const { - return m_BuyScalar; + return m_BuyScalar; } float VendorComponent::GetSellScalar() const { - return m_SellScalar; + return m_SellScalar; } void VendorComponent::SetBuyScalar(float value) { - m_BuyScalar = value; + m_BuyScalar = value; } void VendorComponent::SetSellScalar(float value) { - m_SellScalar = value; + m_SellScalar = value; } std::map& VendorComponent::GetInventory() { - return m_Inventory; + return m_Inventory; } bool VendorComponent::HasCraftingStation() { - // As far as we know, only Umami has a crafting station - return m_Parent->GetLOT() == 13800; + // As far as we know, only Umami has a crafting station + return m_Parent->GetLOT() == 13800; } + +void VendorComponent::RefreshInventory(bool isCreation) { + //Custom code for Max vanity NPC + if (m_Parent->GetLOT() == 9749 && Game::server->GetZoneID() == 1201) { + if (!isCreation) return; + m_Inventory.insert({11909, 0}); //Top hat w frog + m_Inventory.insert({7785, 0}); //Flash bulb + m_Inventory.insert({12764, 0}); //Big fountain soda + m_Inventory.insert({12241, 0}); //Hot cocoa (from fb) + return; + } + m_Inventory.clear(); + auto* lootMatrixTable = CDClientManager::Instance()->GetTable("LootMatrix"); + std::vector lootMatrices = lootMatrixTable->Query([=](CDLootMatrix entry) { return (entry.LootMatrixIndex == m_LootMatrixID); }); + + if (lootMatrices.empty()) return; + // Done with lootMatrix table + + auto* lootTableTable = CDClientManager::Instance()->GetTable("LootTable"); + + for (const auto& lootMatrix : lootMatrices) { + int lootTableID = lootMatrix.LootTableIndex; + std::vector vendorItems = lootTableTable->Query([=](CDLootTable entry) { return (entry.LootTableIndex == lootTableID); }); + if (lootMatrix.maxToDrop == 0 || lootMatrix.minToDrop == 0) { + for (CDLootTable item : vendorItems) { + m_Inventory.insert({item.itemid, item.sortPriority}); + } + } else { + auto randomCount = GeneralUtils::GenerateRandomNumber(lootMatrix.minToDrop, lootMatrix.maxToDrop); + + for (size_t i = 0; i < randomCount; i++) { + if (vendorItems.empty()) break; + + auto randomItemIndex = GeneralUtils::GenerateRandomNumber(0, vendorItems.size() - 1); + + const auto& randomItem = vendorItems[randomItemIndex]; + + vendorItems.erase(vendorItems.begin() + randomItemIndex); + + m_Inventory.insert({randomItem.itemid, randomItem.sortPriority}); + } + } + } + + //Because I want a vendor to sell these cameras + if (m_Parent->GetLOT() == 13569) { + auto randomCamera = GeneralUtils::GenerateRandomNumber(0, 2); + + switch (randomCamera) { + case 0: + m_Inventory.insert({16253, 0}); //Grungagroid + break; + case 1: + m_Inventory.insert({16254, 0}); //Hipstabrick + break; + case 2: + m_Inventory.insert({16204, 0}); //Megabrixel snapshot + break; + default: + break; + } + } + + // Callback timer to refresh this inventory. + m_Parent->AddCallbackTimer(m_RefreshTimeSeconds, [this]() { + RefreshInventory(); + }); + GameMessages::SendVendorStatusUpdate(m_Parent, UNASSIGNED_SYSTEM_ADDRESS); +} + +void VendorComponent::SetupConstants() { + auto* compRegistryTable = CDClientManager::Instance()->GetTable("ComponentsRegistry"); + int componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), COMPONENT_TYPE_VENDOR); + + auto* vendorComponentTable = CDClientManager::Instance()->GetTable("VendorComponent"); + std::vector vendorComps = vendorComponentTable->Query([=](CDVendorComponent entry) { return (entry.id == componentID); }); + if (vendorComps.empty()) return; + m_BuyScalar = vendorComps[0].buyScalar; + m_SellScalar = vendorComps[0].sellScalar; + m_RefreshTimeSeconds = vendorComps[0].refreshTimeSeconds; + m_LootMatrixID = vendorComps[0].LootMatrixIndex; +} \ No newline at end of file diff --git a/dGame/dComponents/VendorComponent.h b/dGame/dComponents/VendorComponent.h index 71297be9..c037d875 100644 --- a/dGame/dComponents/VendorComponent.h +++ b/dGame/dComponents/VendorComponent.h @@ -1,11 +1,12 @@ +#pragma once #ifndef VENDORCOMPONENT_H #define VENDORCOMPONENT_H -#include "RakNetTypes.h" -#include "Entity.h" -#include "GameMessages.h" #include "CDClientManager.h" #include "Component.h" +#include "Entity.h" +#include "GameMessages.h" +#include "RakNetTypes.h" /** * A component for vendor NPCs. A vendor sells items to the player. @@ -19,7 +20,7 @@ public: void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); - void OnUse(Entity* originator); + void OnUse(Entity* originator) override; /** * Gets the buy scaler @@ -56,17 +57,36 @@ public: */ std::map& GetInventory(); + /** + * Refresh the inventory of this vendor. + */ + void RefreshInventory(bool isCreation = false); + + /** + * Called on startup of vendor to setup the variables for the component. + */ + void SetupConstants(); private: /** - * The buy scaler. + * The buy scalar. */ float m_BuyScalar; /** - * The sell scaler. + * The sell scalar. */ float m_SellScalar; + /** + * The refresh time of this vendors' inventory. + */ + float m_RefreshTimeSeconds; + + /** + * Loot matrix id of this vendor. + */ + uint32_t m_LootMatrixID; + /** * The list of items the vendor sells. */ diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index 83187e2f..cdaae38c 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -285,7 +285,7 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System auto* skillComponent = entity->GetComponent(); - success = skillComponent->CastPlayerSkill(behaviorId, startSkill.uiSkillHandle, bs, startSkill.optionalTargetID); + success = skillComponent->CastPlayerSkill(behaviorId, startSkill.uiSkillHandle, bs, startSkill.optionalTargetID, startSkill.skillID); if (success && entity->GetCharacter()) { DestroyableComponent* destComp = entity->GetComponent(); @@ -595,7 +595,9 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System case GAME_MSG_VEHICLE_NOTIFY_HIT_IMAGINATION_SERVER: GameMessages::HandleVehicleNotifyHitImaginationServer(inStream, entity, sysAddr); break; - + case GAME_MSG_UPDATE_PROPERTY_PERFORMANCE_COST: + GameMessages::HandleUpdatePropertyPerformanceCost(inStream, entity, sysAddr); + break; // SG case GAME_MSG_UPDATE_SHOOTING_GALLERY_ROTATION: GameMessages::HandleUpdateShootingGalleryRotation(inStream, entity, sysAddr); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 4c0d184d..58aba14d 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -26,6 +26,7 @@ #include "TeamManager.h" #include "ChatPackets.h" #include "GameConfig.h" +#include "RocketLaunchLupComponent.h" #include #include @@ -411,7 +412,7 @@ void GameMessages::SendGMLevelBroadcast(const LWOOBJID& objectID, uint8_t level) SEND_PACKET_BROADCAST } -void GameMessages::SendAddItemToInventoryClientSync(Entity* entity, const SystemAddress& sysAddr, Item* item, const LWOOBJID& objectID, bool showFlyingLoot, int itemCount, LWOOBJID subKey) { +void GameMessages::SendAddItemToInventoryClientSync(Entity* entity, const SystemAddress& sysAddr, Item* item, const LWOOBJID& objectID, bool showFlyingLoot, int itemCount, LWOOBJID subKey, eLootSourceType lootSourceType) { CBITSTREAM CMSGHEADER @@ -421,9 +422,8 @@ void GameMessages::SendAddItemToInventoryClientSync(Entity* entity, const System bitStream.Write(item->GetInfo().isBOE); bitStream.Write(item->GetInfo().isBOP); - bitStream.Write0(); // Loot source - //if (invType != LOOTTYPE_NONE) bitStream.Write(invType); - + bitStream.Write(lootSourceType != eLootSourceType::LOOT_SOURCE_NONE); // Loot source + if (lootSourceType != eLootSourceType::LOOT_SOURCE_NONE) bitStream.Write(lootSourceType); LWONameValue extraInfo; auto config = item->GetConfig(); @@ -451,8 +451,8 @@ void GameMessages::SendAddItemToInventoryClientSync(Entity* entity, const System auto* inventory = item->GetInventory(); const auto inventoryType = inventory->GetType(); - bitStream.Write(inventoryType != INVENTORY_DEFAULT); - if (inventoryType != INVENTORY_DEFAULT) bitStream.Write(inventoryType); + bitStream.Write(inventoryType != eInventoryType::ITEMS); + if (inventoryType != eInventoryType::ITEMS) bitStream.Write(inventoryType); bitStream.Write(itemCount != 1); if (itemCount != 1) bitStream.Write(itemCount); @@ -558,7 +558,7 @@ void GameMessages::SendNotifyMissionTask(Entity* entity, const SystemAddress& sy SEND_PACKET } -void GameMessages::SendModifyLEGOScore(Entity* entity, const SystemAddress& sysAddr, int64_t score, int sourceType) { +void GameMessages::SendModifyLEGOScore(Entity* entity, const SystemAddress& sysAddr, int64_t score, eLootSourceType sourceType) { CBITSTREAM CMSGHEADER @@ -566,9 +566,8 @@ void GameMessages::SendModifyLEGOScore(Entity* entity, const SystemAddress& sysA bitStream.Write((uint16_t)GAME_MSG_MODIFY_LEGO_SCORE); bitStream.Write(score); - //Stuff stolen from the old codebase, no idea why this works. The proper implementation didn't for some reason. - bitStream.Write((int32_t)129); - bitStream.Write((unsigned char)0); + bitStream.Write(sourceType != eLootSourceType::LOOT_SOURCE_NONE); + if (sourceType != eLootSourceType::LOOT_SOURCE_NONE) bitStream.Write(sourceType); SEND_PACKET } @@ -1254,8 +1253,7 @@ void GameMessages::SendVendorOpenWindow(Entity* entity, const SystemAddress& sys SEND_PACKET } -// ah yes, impl code in a send function, beautiful! -void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr) { +void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr, bool bUpdateOnly) { CBITSTREAM CMSGHEADER @@ -1267,7 +1265,7 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s bitStream.Write(entity->GetObjectID()); bitStream.Write(GAME_MSG::GAME_MSG_VENDOR_STATUS_UPDATE); - bitStream.Write(false); + bitStream.Write(bUpdateOnly); bitStream.Write(static_cast(vendorItems.size())); for (std::pair item : vendorItems) { @@ -1275,6 +1273,7 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s bitStream.Write(static_cast(item.second)); } + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST SEND_PACKET } @@ -1517,6 +1516,18 @@ void GameMessages::SendRequestActivityEnter(LWOOBJID objectId, const SystemAddre SEND_PACKET } +void GameMessages::NotifyLevelRewards(LWOOBJID objectID, const SystemAddress& sysAddr, int level, bool sending_rewards) { + CBITSTREAM + CMSGHEADER + + bitStream.Write(objectID); + bitStream.Write((uint16_t)GAME_MSG::GAME_MSG_NOTIFY_LEVEL_REWARDS); + + bitStream.Write(level); + bitStream.Write(sending_rewards); + + SEND_PACKET +} void GameMessages::SendSetShootingGalleryParams(LWOOBJID objectId, const SystemAddress& sysAddr, float cameraFOV, @@ -2716,10 +2727,15 @@ void GameMessages::HandleEnterProperty(RakNet::BitStream* inStream, Entity* enti auto* player = Player::GetPlayer(sysAddr); auto* entranceComponent = entity->GetComponent(); + if (entranceComponent != nullptr) { + entranceComponent->OnEnterProperty(player, index, returnToZone, sysAddr); + return; + } - if (entranceComponent == nullptr) return; - - entranceComponent->OnEnterProperty(player, index, returnToZone, sysAddr); + auto rocketLaunchLupComponent = entity->GetComponent(); + if (rocketLaunchLupComponent != nullptr) { + rocketLaunchLupComponent->OnSelectWorld(player, index); + } } void GameMessages::HandleSetConsumableItem(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) @@ -3227,12 +3243,12 @@ void GameMessages::HandleClientTradeRequest(RakNet::BitStream* inStream, Entity* void GameMessages::HandleClientTradeCancel(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { - Game::logger->Log("GameMessages", "Trade canceled from (%llu)\n", entity->GetObjectID()); - auto* trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID()); if (trade == nullptr) return; + Game::logger->Log("GameMessages", "Trade canceled from (%llu)\n", entity->GetObjectID()); + TradingManager::Instance()->CancelTrade(trade->GetTradeId()); } @@ -3665,7 +3681,7 @@ void GameMessages::HandlePetTamingTryBuild(RakNet::BitStream* inStream, Entity* return; } - petComponent->TryBuild(bricks, clientFailed); + petComponent->TryBuild(bricks.size(), clientFailed); } void GameMessages::HandleNotifyTamingBuildSuccess(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) @@ -4126,6 +4142,41 @@ void GameMessages::HandleRacingPlayerInfoResetFinished(RakNet::BitStream* inStre } } +void GameMessages::SendUpdateReputation(const LWOOBJID objectId, const int64_t reputation, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(GAME_MSG::GAME_MSG_UPDATE_REPUTATION); + + bitStream.Write(reputation); + + SEND_PACKET; +} + +void GameMessages::HandleUpdatePropertyPerformanceCost(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { + float performanceCost = 0.0f; + + if (inStream->ReadBit()) inStream->Read(performanceCost); + + if (performanceCost == 0.0f) return; + + auto zone = dZoneManager::Instance()->GetZone(); + const auto& worldId = zone->GetZoneID(); + const auto cloneId = worldId.GetCloneID(); + const auto zoneId = worldId.GetMapID(); + + auto updatePerformanceCostQuery = Database::CreatePreppedStmt("UPDATE properties SET performance_cost = ? WHERE clone_id = ? AND zone_id = ?"); + + updatePerformanceCostQuery->setDouble(1, performanceCost); + updatePerformanceCostQuery->setInt(2, cloneId); + updatePerformanceCostQuery->setInt(3, zoneId); + + updatePerformanceCostQuery->executeUpdate(); + + delete updatePerformanceCostQuery; + updatePerformanceCostQuery = nullptr; +} void GameMessages::HandleVehicleNotifyHitImaginationServer(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { @@ -4654,7 +4705,7 @@ void GameMessages::HandleBuyFromVendor(RakNet::BitStream* inStream, Entity* enti inv->RemoveItem(tokenId, altCurrencyCost); - inv->AddItem(item, count); + inv->AddItem(item, count, eLootSourceType::LOOT_SOURCE_VENDOR); } else { @@ -4679,8 +4730,8 @@ void GameMessages::HandleBuyFromVendor(RakNet::BitStream* inStream, Entity* enti inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost); } - character->SetCoins(character->GetCoins() - (coinCost), LOOT_SOURCE_VENDOR); - inv->AddItem(item, count); + character->SetCoins(character->GetCoins() - (coinCost), eLootSourceType::LOOT_SOURCE_VENDOR); + inv->AddItem(item, count, eLootSourceType::LOOT_SOURCE_VENDOR); } GameMessages::SendVendorTransactionResult(entity, sysAddr); @@ -4723,12 +4774,12 @@ void GameMessages::HandleSellToVendor(RakNet::BitStream* inStream, Entity* entit if (Inventory::IsValidItem(itemComp.currencyLOT)) { const auto altCurrency = (itemComp.altCurrencyCost * sellScalar) * count; - inv->AddItem(itemComp.currencyLOT, std::floor(altCurrency)); // Return alt currencies like faction tokens. + inv->AddItem(itemComp.currencyLOT, std::floor(altCurrency), eLootSourceType::LOOT_SOURCE_VENDOR); // Return alt currencies like faction tokens. } //inv->RemoveItem(count, -1, iObjID); - inv->MoveItemToInventory(item, VENDOR_BUYBACK, count, true, false, true); - character->SetCoins(std::floor(character->GetCoins() + ((itemComp.baseValue * sellScalar)*count)), LOOT_SOURCE_VENDOR); + inv->MoveItemToInventory(item, eInventoryType::VENDOR_BUYBACK, count, true, false, true); + character->SetCoins(std::floor(character->GetCoins() + ((itemComp.baseValue * sellScalar)*count)), eLootSourceType::LOOT_SOURCE_VENDOR); //EntityManager::Instance()->SerializeEntity(player); // so inventory updates GameMessages::SendVendorTransactionResult(entity, sysAddr); } @@ -4790,7 +4841,7 @@ void GameMessages::HandleBuybackFromVendor(RakNet::BitStream* inStream, Entity* //inv->RemoveItem(count, -1, iObjID); inv->MoveItemToInventory(item, Inventory::FindInventoryTypeForLot(item->GetLot()), count, true, false); - character->SetCoins(character->GetCoins() - cost, LOOT_SOURCE_VENDOR); + character->SetCoins(character->GetCoins() - cost, eLootSourceType::LOOT_SOURCE_VENDOR); //EntityManager::Instance()->SerializeEntity(player); // so inventory updates GameMessages::SendVendorTransactionResult(entity, sysAddr); } @@ -4981,17 +5032,6 @@ void GameMessages::HandleRequestUse(RakNet::BitStream* inStream, Entity* entity, missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_MISSION_INTERACTION, interactedObject->GetLOT(), interactedObject->GetObjectID()); missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_NON_MISSION_INTERACTION, interactedObject->GetLOT(), interactedObject->GetObjectID()); - - //Do mail stuff: - if (interactedObject->GetLOT() == 3964) { - AMFStringValue* value = new AMFStringValue(); - value->SetStringValue("Mail"); - - AMFArrayValue args; - args.InsertValue("state", value); - GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, "pushGameState", &args); - delete value; - } } void GameMessages::HandlePlayEmote(RakNet::BitStream* inStream, Entity* entity) { @@ -5068,7 +5108,7 @@ void GameMessages::HandleModularBuildConvertModel(RakNet::BitStream* inStream, E item->Disassemble(TEMP_MODELS); - item->SetCount(item->GetCount() - 1, false, false); + item->SetCount(item->GetCount() - 1, false, false, true, eLootSourceType::LOOT_SOURCE_QUICKBUILD); } void GameMessages::HandleSetFlag(RakNet::BitStream* inStream, Entity* entity) { @@ -5234,7 +5274,7 @@ void GameMessages::HandlePickupCurrency(RakNet::BitStream* inStream, Entity* ent auto* ch = entity->GetCharacter(); if (entity->CanPickupCoins(currency)) { - ch->SetCoins(ch->GetCoins() + currency, LOOT_SOURCE_PICKUP); + ch->SetCoins(ch->GetCoins() + currency, eLootSourceType::LOOT_SOURCE_PICKUP); } } @@ -5289,21 +5329,11 @@ void GameMessages::HandleEquipItem(RakNet::BitStream* inStream, Entity* entity) Item* item = inv->FindItemById(objectID); if (!item) return; - /*if (item->GetLot() == 6416) { // if it's a rocket - std::vector rocketPads = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_ROCKET_LAUNCH); - for (Entity* rocketPad : rocketPads) { - RocketLaunchpadControlComponent* rocketComp = static_cast(rocketPad->GetComponent(COMPONENT_TYPE_ROCKET_LAUNCH)); - if (rocketComp) { - rocketComp->RocketEquip(entity, objectID); - } - } - } - else*/ { + item->Equip(); EntityManager::Instance()->SerializeEntity(entity); } -} void GameMessages::HandleUnequipItem(RakNet::BitStream* inStream, Entity* entity) { bool immediate; @@ -5398,6 +5428,8 @@ void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream* inStream, En return; } + iStackCount = std::min(item->GetCount(), iStackCount); + if (bConfirmed) { for (auto i = 0; i < iStackCount; ++i) { if (eInvType == eInventoryType::MODELS) @@ -5447,8 +5479,8 @@ void GameMessages::HandleMoveItemInInventory(RakNet::BitStream* inStream, Entity } void GameMessages::HandleMoveItemBetweenInventoryTypes(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { - int inventoryTypeA; - int inventoryTypeB; + eInventoryType inventoryTypeA; + eInventoryType inventoryTypeB; LWOOBJID objectID; bool showFlyingLoot = true; bool stackCountIsDefault = false; @@ -5465,7 +5497,7 @@ void GameMessages::HandleMoveItemBetweenInventoryTypes(RakNet::BitStream* inStre inStream->Read(templateIDIsDefault); if (templateIDIsDefault) inStream->Read(templateID); - InventoryComponent* inv = static_cast(entity->GetComponent(COMPONENT_TYPE_INVENTORY)); + auto inv = entity->GetComponent(); if (!inv) return; auto* item = inv->FindItemById(objectID); @@ -5486,7 +5518,7 @@ void GameMessages::HandleMoveItemBetweenInventoryTypes(RakNet::BitStream* inStre } } - inv->MoveItemToInventory(item, static_cast(inventoryTypeB), stackCount, showFlyingLoot, false); + inv->MoveItemToInventory(item, inventoryTypeB, stackCount, showFlyingLoot); EntityManager::Instance()->SerializeEntity(entity); } @@ -5553,11 +5585,11 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream* inStream, Entity* if (count == 3) { - inv->AddItem(6416, 1, MODELS, config); + inv->AddItem(6416, 1, eLootSourceType::LOOT_SOURCE_QUICKBUILD, eInventoryType::MODELS, config); } else if (count == 7) { - inv->AddItem(8092, 1, MODELS, config); + inv->AddItem(8092, 1, eLootSourceType::LOOT_SOURCE_QUICKBUILD, eInventoryType::MODELS, config); } auto* missionComponent = character->GetComponent(); @@ -5587,7 +5619,7 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream* inStream, Entity* for (auto* item : items) { - inv->MoveItemToInventory(item, MODELS, item->GetCount(), false); + inv->MoveItemToInventory(item, eInventoryType::MODELS, item->GetCount(), false); } } @@ -5702,7 +5734,7 @@ void GameMessages::HandleDoneArrangingWithItem(RakNet::BitStream* inStream, Enti for (auto* item : items) { - inv->MoveItemToInventory(item, MODELS, item->GetCount(), false, false); + inv->MoveItemToInventory(item, eInventoryType::MODELS, item->GetCount(), false, false); } } @@ -5728,7 +5760,7 @@ void GameMessages::HandleModularBuildMoveAndEquip(RakNet::BitStream* inStream, E return; } - inv->MoveItemToInventory(item, MODELS, 1, false, true); + inv->MoveItemToInventory(item, eInventoryType::MODELS, 1, false, true); } void GameMessages::HandlePickupItem(RakNet::BitStream* inStream, Entity* entity) { @@ -5890,6 +5922,7 @@ void GameMessages::HandleReportBug(RakNet::BitStream* inStream, Entity* entity) std::string nOtherPlayerID; std::string selection; uint32_t messageLength; + int32_t reporterID = 0; //Reading: inStream->Read(messageLength); @@ -5900,6 +5933,9 @@ void GameMessages::HandleReportBug(RakNet::BitStream* inStream, Entity* entity) body.push_back(character); } + auto character = entity->GetCharacter(); + if (character) reporterID = character->GetID(); + uint32_t clientVersionLength; inStream->Read(clientVersionLength); for (unsigned int k = 0; k < clientVersionLength; k++) { @@ -5915,6 +5951,9 @@ void GameMessages::HandleReportBug(RakNet::BitStream* inStream, Entity* entity) inStream->Read(character); nOtherPlayerID.push_back(character); } + // Convert other player id from LWOOBJID to the database id. + uint32_t otherPlayer = LWOOBJID_EMPTY; + if (nOtherPlayerID != "") otherPlayer = std::atoi(nOtherPlayerID.c_str()); uint32_t selectionLength; inStream->Read(selectionLength); @@ -5925,16 +5964,17 @@ void GameMessages::HandleReportBug(RakNet::BitStream* inStream, Entity* entity) } try { - sql::PreparedStatement* insertBug = Database::CreatePreppedStmt("INSERT INTO `bug_reports`(body, client_version, other_player_id, selection) VALUES (?, ?, ?, ?)"); + sql::PreparedStatement* insertBug = Database::CreatePreppedStmt("INSERT INTO `bug_reports`(body, client_version, other_player_id, selection, reporter_id) VALUES (?, ?, ?, ?, ?)"); insertBug->setString(1, GeneralUtils::UTF16ToWTF8(body)); insertBug->setString(2, clientVersion); - insertBug->setString(3, nOtherPlayerID); + insertBug->setString(3, std::to_string(otherPlayer)); insertBug->setString(4, selection); + insertBug->setInt(5, reporterID); insertBug->execute(); delete insertBug; } catch (sql::SQLException& e) { - Game::logger->Log("HandleReportBug", "Couldn't save bug report!\n"); + Game::logger->Log("HandleReportBug", "Couldn't save bug report! (%s)\n", e.what()); } } diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 579d4ec3..602cb4b2 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -61,15 +61,16 @@ namespace GameMessages { void SendGMLevelBroadcast(const LWOOBJID& objectID, uint8_t level); void SendChatModeUpdate(const LWOOBJID& objectID, uint8_t level); - void SendAddItemToInventoryClientSync(Entity* entity, const SystemAddress& sysAddr, Item* item, const LWOOBJID& objectID, bool showFlyingLoot, int itemCount, LWOOBJID subKey = LWOOBJID_EMPTY); + void SendAddItemToInventoryClientSync(Entity* entity, const SystemAddress& sysAddr, Item* item, const LWOOBJID& objectID, bool showFlyingLoot, int itemCount, LWOOBJID subKey = LWOOBJID_EMPTY, eLootSourceType lootSourceType = eLootSourceType::LOOT_SOURCE_NONE); void SendNotifyClientFlagChange(const LWOOBJID& objectID, int iFlagID, bool bFlag, const SystemAddress& sysAddr); void SendChangeObjectWorldState(const LWOOBJID& objectID, int state, const SystemAddress& sysAddr); void SendOfferMission(const LWOOBJID& entity, const SystemAddress& sysAddr, int32_t missionID, const LWOOBJID& offererID); void SendNotifyMission(Entity * entity, const SystemAddress& sysAddr, int missionID, int missionState, bool sendingRewards); void SendNotifyMissionTask(Entity * entity, const SystemAddress& sysAddr, int missionID, int taskMask, std::vector updates); + void NotifyLevelRewards(LWOOBJID objectID, const SystemAddress& sysAddr, int level, bool sending_rewards); - void SendModifyLEGOScore(Entity* entity, const SystemAddress& sysAddr, int64_t score, int sourceType); + void SendModifyLEGOScore(Entity* entity, const SystemAddress& sysAddr, int64_t score, eLootSourceType sourceType); void SendUIMessageServerToSingleClient(Entity* entity, const SystemAddress& sysAddr, const std::string& message, NDGFxValue args); void SendUIMessageServerToAllClients(const std::string& message, NDGFxValue args); @@ -108,7 +109,7 @@ namespace GameMessages { void SendModularBuildEnd(Entity* entity); void SendVendorOpenWindow(Entity* entity, const SystemAddress& sysAddr); - void SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr); + void SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr, bool bUpdateOnly = false); void SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr); void SendRemoveItemFromInventory(Entity* entity, const SystemAddress& sysAddr, LWOOBJID iObjID, LOT templateID, int inventoryType, uint32_t stackCount, uint32_t stackRemaining); @@ -385,6 +386,8 @@ namespace GameMessages { bool bUseLeaderboards ); + void HandleUpdatePropertyPerformanceCost(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr); + void SendNotifyClientShootingGalleryScore(LWOOBJID objectId, const SystemAddress& sysAddr, float addTime, int32_t score, @@ -394,6 +397,8 @@ namespace GameMessages { void HandleUpdateShootingGalleryRotation(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr); + void SendUpdateReputation(const LWOOBJID objectId, const int64_t reputation, const SystemAddress& sysAddr); + // Leaderboards void SendActivitySummaryLeaderboardData(const LWOOBJID& objectID, const Leaderboard* leaderboard, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); diff --git a/dGame/dGameMessages/PropertyDataMessage.cpp b/dGame/dGameMessages/PropertyDataMessage.cpp index 9c3b6d3f..85eb54cb 100644 --- a/dGame/dGameMessages/PropertyDataMessage.cpp +++ b/dGame/dGameMessages/PropertyDataMessage.cpp @@ -13,7 +13,7 @@ void GameMessages::PropertyDataMessage::Serialize(RakNet::BitStream& stream) con stream.Write(TemplateID); // - template id stream.Write(ZoneId); // - map id stream.Write(VendorMapId); // - vendor map id - stream.Write(1); + stream.Write(cloneId); // clone id const auto& name = GeneralUtils::ASCIIToUTF16(Name); stream.Write(uint32_t(name.size())); @@ -40,11 +40,12 @@ void GameMessages::PropertyDataMessage::Serialize(RakNet::BitStream& stream) con stream.Write(0); // - minimum price stream.Write(1); // - rent duration - stream.Write(ClaimedTime); // - timestamp + stream.Write(LastUpdatedTime); // - timestamp stream.Write(1); - stream.Write(0); + stream.Write(reputation); // Reputation + stream.Write(0); const auto& spawn = GeneralUtils::ASCIIToUTF16(SpawnName); stream.Write(uint32_t(spawn.size())); @@ -63,9 +64,18 @@ void GameMessages::PropertyDataMessage::Serialize(RakNet::BitStream& stream) con stream.Write(0); - stream.Write(1); + if (rejectionReason != "") stream.Write(REJECTION_STATUS_REJECTED); + else if (moderatorRequested == true && rejectionReason == "") stream.Write(REJECTION_STATUS_APPROVED); + else stream.Write(REJECTION_STATUS_PENDING); - stream.Write(0); // String length + // Does this go here??? + // const auto& rejectionReasonConverted = GeneralUtils::ASCIIToUTF16(rejectionReason); + // stream.Write(uint32_t(rejectionReasonConverted.size())); + // for (uint32_t i = 0; i < rejectionReasonConverted.size(); ++i) { + // stream.Write(uint16_t(rejectionReasonConverted[i])); + // } + + stream.Write(0); stream.Write(0); diff --git a/dGame/dGameMessages/PropertyDataMessage.h b/dGame/dGameMessages/PropertyDataMessage.h index 219ac08d..5b5d7d0f 100644 --- a/dGame/dGameMessages/PropertyDataMessage.h +++ b/dGame/dGameMessages/PropertyDataMessage.h @@ -28,12 +28,23 @@ namespace GameMessages std::string Name = ""; std::string Description = ""; + std::string rejectionReason = ""; + bool moderatorRequested = 0; + LWOCLONEID cloneId = 0; + uint32_t reputation = 0; uint64_t ClaimedTime = 0; + uint64_t LastUpdatedTime = 0; NiPoint3 ZonePosition = { 548.0f, 406.0f, 178.0f }; char PrivacyOption = 0; float MaxBuildHeight = 128.0f; std::vector Paths = {}; + private: + enum RejectionStatus : uint32_t { + REJECTION_STATUS_APPROVED = 0, + REJECTION_STATUS_PENDING = 1, + REJECTION_STATUS_REJECTED = 2 + }; }; } \ No newline at end of file diff --git a/dGame/dGameMessages/PropertySelectQueryProperty.cpp b/dGame/dGameMessages/PropertySelectQueryProperty.cpp index f32ce537..38e7cea2 100644 --- a/dGame/dGameMessages/PropertySelectQueryProperty.cpp +++ b/dGame/dGameMessages/PropertySelectQueryProperty.cpp @@ -22,15 +22,16 @@ void PropertySelectQueryProperty::Serialize(RakNet::BitStream& stream) const stream.Write(static_cast(description[i])); } - stream.Write(Reputation); - stream.Write(IsBestFriend); - stream.Write(IsFriend); - stream.Write(IsModeratorApproved); - stream.Write(IsAlt); - stream.Write(IsOwner); - stream.Write(AccessType); - stream.Write(DatePublished); - stream.Write(PerformanceCost); + stream.Write(Reputation); + stream.Write(IsBestFriend); + stream.Write(IsFriend); + stream.Write(IsModeratorApproved); + stream.Write(IsAlt); + stream.Write(IsOwned); + stream.Write(AccessType); + stream.Write(DateLastPublished); + stream.Write(PerformanceIndex); + stream.Write(PerformanceCost); } void PropertySelectQueryProperty::Deserialize(RakNet::BitStream& stream) const diff --git a/dGame/dGameMessages/PropertySelectQueryProperty.h b/dGame/dGameMessages/PropertySelectQueryProperty.h index 0aaab912..61fa7b86 100644 --- a/dGame/dGameMessages/PropertySelectQueryProperty.h +++ b/dGame/dGameMessages/PropertySelectQueryProperty.h @@ -9,17 +9,18 @@ public: void Deserialize(RakNet::BitStream& stream) const; - LWOCLONEID CloneId = LWOCLONEID_INVALID; - std::string OwnerName = ""; - std::string Name = ""; - std::string Description = ""; - uint32_t Reputation = 0; - bool IsBestFriend = false; - bool IsFriend = false; - bool IsModeratorApproved = false; - bool IsAlt = false; - bool IsOwner = false; - uint32_t AccessType = 0; - uint32_t DatePublished = 0; - uint64_t PerformanceCost = 0; + LWOCLONEID CloneId = LWOCLONEID_INVALID; // The cloneID of the property + std::string OwnerName = ""; // The property owners name + std::string Name = ""; // The property name + std::string Description = ""; // The property description + float Reputation = 0; // The reputation of the property + bool IsBestFriend = false; // Whether or not the property belongs to a best friend + bool IsFriend = false; // Whether or not the property belongs to a friend + bool IsModeratorApproved = false; // Whether or not a moderator has approved this property + bool IsAlt = false; // Whether or not the property is owned by an alt of the account owner + bool IsOwned = false; // Whether or not the property is owned + uint32_t AccessType = 0; // The privacy option of the property + uint32_t DateLastPublished = 0; // The last day the property was published + float PerformanceCost = 0; // The performance cost of the property + uint32_t PerformanceIndex = 0; // The performance index of the property? Always 0? }; diff --git a/dGame/dInventory/EquippedItem.h b/dGame/dInventory/EquippedItem.h index 0d1d191f..cf5ba253 100644 --- a/dGame/dInventory/EquippedItem.h +++ b/dGame/dInventory/EquippedItem.h @@ -1,6 +1,7 @@ -#pragma once +#pragma once #include "dCommonVars.h" +#include "LDFFormat.h" /** * An item that's equipped, generally as a smaller return type than the regular Item class @@ -26,4 +27,9 @@ struct EquippedItem * The slot this item is stored in */ uint32_t slot = 0; + + /** + * The configuration of the item with any extra data + */ + std::vector config = {}; }; diff --git a/dGame/dInventory/Inventory.cpp b/dGame/dInventory/Inventory.cpp index b77be4b2..6e8be6aa 100644 --- a/dGame/dInventory/Inventory.cpp +++ b/dGame/dInventory/Inventory.cpp @@ -90,17 +90,21 @@ int32_t Inventory::FindEmptySlot() { if (free <= 6) // Up from 1 { - if (type != ITEMS && type != VAULT_ITEMS) + if (type != ITEMS && type != VAULT_ITEMS && type != eInventoryType::VAULT_MODELS) { uint32_t newSize = size; - if (type == MODELS || type == VAULT_MODELS) + if (type == MODELS) { newSize = 240; } + else if (type == eInventoryType::VENDOR_BUYBACK) + { + newSize += 9u; + } else { - newSize += 20; + newSize += 10u; } if (newSize > GetSize()) diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index 77256a20..8f464d36 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -14,7 +14,7 @@ class Inventory; -Item::Item(const LWOOBJID id, const LOT lot, Inventory* inventory, const uint32_t slot, const uint32_t count, const bool bound, const std::vector& config, const LWOOBJID parent, LWOOBJID subKey) +Item::Item(const LWOOBJID id, const LOT lot, Inventory* inventory, const uint32_t slot, const uint32_t count, const bool bound, const std::vector& config, const LWOOBJID parent, LWOOBJID subKey, eLootSourceType lootSourceType) { if (!Inventory::IsValidItem(lot)) { @@ -46,7 +46,8 @@ Item::Item( bool showFlyingLoot, bool isModMoveAndEquip, LWOOBJID subKey, - bool bound) + bool bound, + eLootSourceType lootSourceType) { if (!Inventory::IsValidItem(lot)) { @@ -80,8 +81,7 @@ Item::Item( inventory->AddManagedItem(this); auto* entity = inventory->GetComponent()->GetParent(); - - GameMessages::SendAddItemToInventoryClientSync(entity, entity->GetSystemAddress(), this, id, showFlyingLoot, static_cast(this->count), subKey); + GameMessages::SendAddItemToInventoryClientSync(entity, entity->GetSystemAddress(), this, id, showFlyingLoot, static_cast(this->count), subKey, lootSourceType); if (isModMoveAndEquip) { @@ -148,7 +148,7 @@ PreconditionExpression* Item::GetPreconditionExpression() const return preconditions; } -void Item::SetCount(const uint32_t value, const bool silent, const bool disassemble, const bool showFlyingLoot) +void Item::SetCount(const uint32_t value, const bool silent, const bool disassemble, const bool showFlyingLoot, eLootSourceType lootSourceType) { if (value == count) { @@ -176,7 +176,7 @@ void Item::SetCount(const uint32_t value, const bool silent, const bool disassem if (value > count) { - GameMessages::SendAddItemToInventoryClientSync(entity, entity->GetSystemAddress(), this, id, showFlyingLoot, delta); + GameMessages::SendAddItemToInventoryClientSync(entity, entity->GetSystemAddress(), this, id, showFlyingLoot, delta, LWOOBJID_EMPTY, lootSourceType); } else { @@ -340,7 +340,7 @@ bool Item::UseNonEquip() return false; } - LootGenerator::Instance().GiveLoot(inventory->GetComponent()->GetParent(), result); + LootGenerator::Instance().GiveLoot(inventory->GetComponent()->GetParent(), result, eLootSourceType::LOOT_SOURCE_CONSUMPTION); } inventory->GetComponent()->RemoveItem(lot, 1); @@ -374,7 +374,7 @@ void Item::Disassemble(const eInventoryType inventoryType) for (const auto mod : modArray) { - inventory->GetComponent()->AddItem(mod, 1, inventoryType); + inventory->GetComponent()->AddItem(mod, 1, eLootSourceType::LOOT_SOURCE_DELETION, inventoryType); } } } @@ -472,7 +472,7 @@ void Item::DisassembleModel() continue; } - GetInventory()->GetComponent()->AddItem(brickID[0].NDObjectID, 1); + GetInventory()->GetComponent()->AddItem(brickID[0].NDObjectID, 1, eLootSourceType::LOOT_SOURCE_DELETION); } } diff --git a/dGame/dInventory/Item.h b/dGame/dInventory/Item.h index 83ebb633..0be9449a 100644 --- a/dGame/dInventory/Item.h +++ b/dGame/dInventory/Item.h @@ -36,7 +36,8 @@ public: bool bound, const std::vector& config, LWOOBJID parent, - LWOOBJID subKey + LWOOBJID subKey, + eLootSourceType lootSourceType = eLootSourceType::LOOT_SOURCE_NONE ); /** @@ -62,7 +63,8 @@ public: bool showFlyingLoot = true, bool isModMoveAndEquip = false, LWOOBJID subKey = LWOOBJID_EMPTY, - bool bound = false + bool bound = false, + eLootSourceType lootSourceType = eLootSourceType::LOOT_SOURCE_NONE ); ~Item(); @@ -86,7 +88,7 @@ public: * @param disassemble if items were removed, this returns all the sub parts of the item individually if it had assembly part lots * @param showFlyingLoot shows flying loot to the client, if not silent */ - void SetCount(uint32_t value, bool silent = false, bool disassemble = true, bool showFlyingLoot = true); + void SetCount(uint32_t value, bool silent = false, bool disassemble = true, bool showFlyingLoot = true, eLootSourceType lootSourceType = eLootSourceType::LOOT_SOURCE_NONE); /** * Returns the number of items this item represents (e.g. for stacks) diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index f2f06e58..31987505 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -11,9 +11,12 @@ #include "GameMessages.h" #include "Mail.h" #include "MissionComponent.h" +#include "RacingTaskParam.h" #include "dLocale.h" #include "dLogger.h" #include "dServer.h" +#include "dZoneManager.h" +#include "Database.h" Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) { m_MissionComponent = missionComponent; @@ -313,6 +316,10 @@ void Mission::Complete(const bool yieldRewards) { missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_MISSION_COMPLETE, info->id); + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, info->id, (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_COMPLETE_ANY_RACING_TASK); + + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, info->id, (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_COMPLETE_TRACK_TASKS); + auto* missionEmailTable = CDClientManager::Instance()->GetTable("MissionEmail"); const auto missionId = GetMissionId(); @@ -350,7 +357,7 @@ void Mission::CheckCompletion() { return; } - SetMissionState(MissionState::MISSION_STATE_READY_TO_COMPLETE); + MakeReadyToComplete(); } void Mission::Catchup() { @@ -421,11 +428,15 @@ void Mission::YieldRewards() { } } + int32_t coinsToSend = 0; if (info->LegoScore > 0) { - characterComponent->SetUScore(characterComponent->GetUScore() + info->LegoScore); - - if (info->isMission) { - GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), info->LegoScore, 2); + eLootSourceType lootSource = info->isMission ? eLootSourceType::LOOT_SOURCE_MISSION : eLootSourceType::LOOT_SOURCE_ACHIEVEMENT; + if(characterComponent->GetLevel() >= dZoneManager::Instance()->GetMaxLevel()) { + // Since the character is at the level cap we reward them with coins instead of UScore. + coinsToSend += info->LegoScore * dZoneManager::Instance()->GetLevelCapCurrencyConversion(); + } else { + characterComponent->SetUScore(characterComponent->GetUScore() + info->LegoScore); + GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), info->LegoScore, lootSource); } } @@ -452,11 +463,12 @@ void Mission::YieldRewards() { count = 0; } - inventoryComponent->AddItem(pair.first, count); + inventoryComponent->AddItem(pair.first, count, IsMission() ? eLootSourceType::LOOT_SOURCE_MISSION : eLootSourceType::LOOT_SOURCE_ACHIEVEMENT); } - if (info->reward_currency_repeatable > 0) { - character->SetCoins(character->GetCoins() + info->reward_currency_repeatable, LOOT_SOURCE_MISSION); + if (info->reward_currency_repeatable > 0 || coinsToSend > 0) { + eLootSourceType lootSource = info->isMission ? eLootSourceType::LOOT_SOURCE_MISSION : eLootSourceType::LOOT_SOURCE_ACHIEVEMENT; + character->SetCoins(character->GetCoins() + info->reward_currency_repeatable + coinsToSend, lootSource); } return; @@ -484,12 +496,12 @@ void Mission::YieldRewards() { count = 0; } - inventoryComponent->AddItem(pair.first, count); + inventoryComponent->AddItem(pair.first, count, IsMission() ? eLootSourceType::LOOT_SOURCE_MISSION : eLootSourceType::LOOT_SOURCE_ACHIEVEMENT); } - if (info->reward_currency > 0) { - eLootSourceType lootSource = info->isMission ? LOOT_SOURCE_MISSION : LOOT_SOURCE_ACHIEVEMENT; - character->SetCoins(character->GetCoins() + info->reward_currency, lootSource); + if (info->reward_currency > 0 || coinsToSend > 0) { + eLootSourceType lootSource = info->isMission ? eLootSourceType::LOOT_SOURCE_MISSION : eLootSourceType::LOOT_SOURCE_ACHIEVEMENT; + character->SetCoins(character->GetCoins() + info->reward_currency + coinsToSend, lootSource); } if (info->reward_maxinventory > 0) { @@ -499,21 +511,28 @@ void Mission::YieldRewards() { } if (info->reward_bankinventory > 0) { - auto* inventory = inventoryComponent->GetInventory(VAULT_ITEMS); + auto* inventory = inventoryComponent->GetInventory(eInventoryType::VAULT_ITEMS); + auto modelInventory = inventoryComponent->GetInventory(eInventoryType::VAULT_MODELS); inventory->SetSize(inventory->GetSize() + info->reward_bankinventory); + modelInventory->SetSize(modelInventory->GetSize() + info->reward_bankinventory); } if (info->reward_reputation > 0) { - // TODO: In case of reputation, write code + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_EARN_REPUTATION, 0, 0L, "", info->reward_reputation); + auto character = entity->GetComponent(); + if (character) { + character->SetReputation(character->GetReputation() + info->reward_reputation); + GameMessages::SendUpdateReputation(entity->GetObjectID(), character->GetReputation(), entity->GetSystemAddress()); + } } if (info->reward_maxhealth > 0) { - destroyableComponent->SetMaxHealth(destroyableComponent->GetMaxHealth() + static_cast(info->reward_maxhealth)); + destroyableComponent->SetMaxHealth(destroyableComponent->GetMaxHealth() + static_cast(info->reward_maxhealth), true); } if (info->reward_maximagination > 0) { - destroyableComponent->SetMaxImagination(destroyableComponent->GetMaxImagination() + static_cast(info->reward_maximagination)); + destroyableComponent->SetMaxImagination(destroyableComponent->GetMaxImagination() + static_cast(info->reward_maximagination), true); } EntityManager::Instance()->SerializeEntity(entity); diff --git a/dGame/dMission/MissionTask.cpp b/dGame/dMission/MissionTask.cpp index 423966c4..2dd59887 100644 --- a/dGame/dMission/MissionTask.cpp +++ b/dGame/dMission/MissionTask.cpp @@ -78,14 +78,7 @@ void MissionTask::SetProgress(const uint32_t value, const bool echo) std::vector updates; updates.push_back(static_cast(progress)); - - GameMessages::SendNotifyMissionTask( - entity, - entity->GetSystemAddress(), - static_cast(info->id), - static_cast(1 << (mask + 1)), - updates - ); + GameMessages::SendNotifyMissionTask(entity, entity->GetSystemAddress(), static_cast(info->id), static_cast(1 << (mask + 1)), updates); } @@ -190,15 +183,19 @@ bool MissionTask::InParameters(const uint32_t value) const bool MissionTask::IsComplete() const { - // Minigames are the only ones where the target value is a score they need to get but the actual target is the act ID - return GetType() == MissionTaskType::MISSION_TASK_TYPE_MINIGAME ? progress == info->target : progress >= info->targetValue; + // Mission 668 has task uid 984 which is a bit mask. Its completion value is 3. + if (info->uid == 984) { + return progress >= 3; + } + else { + return progress >= info->targetValue; + } } void MissionTask::Complete() { - // Minigames are the only ones where the target value is a score they need to get but the actual target is the act ID - SetProgress(GetType() == MissionTaskType::MISSION_TASK_TYPE_MINIGAME ? info->target : info->targetValue); + SetProgress(info->targetValue); } @@ -329,18 +326,17 @@ void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string& case MissionTaskType::MISSION_TASK_TYPE_SKILL: { - if (!InParameters(value)) break; - - AddProgress(count); - + // This is a complicated check because for some missions we need to check for the associate being in the parameters instead of the value being in the parameters. + if (associate == LWOOBJID_EMPTY && GetAllTargets().size() == 1 && GetAllTargets()[0] == -1) { + if (InParameters(value)) AddProgress(count); + } else { + if (InParameters(associate) && InAllTargets(value)) AddProgress(count); + } break; } case MissionTaskType::MISSION_TASK_TYPE_MINIGAME: { - if (targets != info->targetGroup || info->targetValue > value) - break; - auto* minigameManager = EntityManager::Instance()->GetEntity(associate); if (minigameManager == nullptr) break; @@ -355,11 +351,16 @@ void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string& if (info->target != gameID) { break; } - - Game::logger->Log("Minigame Task", "Progressing minigame with %s %d > %d (%d)\n", - targets.c_str(), value, info->targetValue, gameID); - SetProgress(info->target); - + // This special case is for shooting gallery missions that want their + // progress value set to 1 instead of being set to the target value. + if(info->targetGroup == targets && value >= info->targetValue && GetMission()->IsMission() && info->target == 1864 && info->targetGroup == "performact_score") { + SetProgress(1); + break; + } + if(info->targetGroup == targets && value >= info->targetValue) { + SetProgress(info->targetValue); + break; + } break; } @@ -423,30 +424,46 @@ void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string& case MissionTaskType::MISSION_TASK_TYPE_RACING: { + // The meaning of associate can be found in RacingTaskParam.h if (parameters.empty()) break; - if (!InAllTargets(dZoneManager::Instance()->GetZone()->GetWorldID())) break; + if (!InAllTargets(dZoneManager::Instance()->GetZone()->GetWorldID()) && !(parameters[0] == 4 || parameters[0] == 5) && !InAllTargets(value)) break; if (parameters[0] != associate) break; - if (associate == 1 || associate == 15) + if (associate == 1 || associate == 2 || associate == 3) { if (value > info->targetValue) break; - AddProgress(1); - } - else if (associate == 2 || associate == 3) - { - if (info->targetValue < value) break; - AddProgress(info->targetValue); } + // task 15 is a bit mask! + else if (associate == 15) { + if (!InAllTargets(value)) break; + + auto tempProgress = GetProgress(); + // If we won at Nimbus Station, set bit 0 + if (value == 1203) SetProgress(tempProgress |= 1 << 0); + // If we won at Gnarled Forest, set bit 1 + else if (value == 1303) SetProgress(tempProgress |= 1 << 1); + // If both bits are set, then the client sees the mission as complete. + } else if (associate == 10) { - if (info->targetValue > value) - { - AddProgress(info->targetValue); - } + // If the player did not crash during the race, progress this task by count. + if (value != 0) break; + + AddProgress(count); + } + else if (associate == 4 || associate == 5 || associate == 14) + { + if (!InAllTargets(value)) break; + AddProgress(count); + } + else if (associate == 17) + { + if (!InAllTargets(value)) break; + AddProgress(count); } else { @@ -464,6 +481,7 @@ void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string& case MissionTaskType::MISSION_TASK_TYPE_SMASH: case MissionTaskType::MISSION_TASK_TYPE_ITEM_COLLECTION: case MissionTaskType::MISSION_TASK_TYPE_PLAYER_FLAG: + case MissionTaskType::MISSION_TASK_TYPE_EARN_REPUTATION: { if (!InAllTargets(value)) break; @@ -471,7 +489,11 @@ void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string& break; } - + case MissionTaskType::MISSION_TASK_TYPE_PLACE_MODEL: + { + AddProgress(count); + break; + } default: Game::logger->Log("MissionTask", "Invalid mission task type (%i)!\n", static_cast(type)); return; diff --git a/dGame/dMission/MissionTask.h b/dGame/dMission/MissionTask.h index 6cdc4039..b77b9c59 100644 --- a/dGame/dMission/MissionTask.h +++ b/dGame/dMission/MissionTask.h @@ -19,7 +19,7 @@ public: * Attempts to progress this task using the provided parameters. Note that the behavior of this method is different * for each mission task type. * @param value the value to progress by - * @param associate optional object ID of an entity that was related to the porgression + * @param associate optional object ID of an entity that was related to the progression * @param targets optional multiple targets that need to be met to progress * @param count a number that indicates the times to progress */ diff --git a/dGame/dMission/MissionTaskType.h b/dGame/dMission/MissionTaskType.h index 8519e0d9..263bf470 100644 --- a/dGame/dMission/MissionTaskType.h +++ b/dGame/dMission/MissionTaskType.h @@ -5,7 +5,7 @@ enum class MissionTaskType : int { MISSION_TASK_TYPE_UNKNOWN = -1, //!< The task type is unknown MISSION_TASK_TYPE_SMASH = 0, //!< A task for smashing something MISSION_TASK_TYPE_SCRIPT = 1, //!< A task handled by a server LUA script - MISSION_TASK_TYPE_ACTIVITY = 2, //!< A task for completing a quickbuild + MISSION_TASK_TYPE_ACTIVITY = 2, //!< A task for completing a quickbuild MISSION_TASK_TYPE_ENVIRONMENT = 3, //!< A task for something in the environment MISSION_TASK_TYPE_MISSION_INTERACTION = 4, //!< A task for interacting with a mission MISSION_TASK_TYPE_EMOTE = 5, //!< A task for playing an emote @@ -16,9 +16,11 @@ enum class MissionTaskType : int { MISSION_TASK_TYPE_MINIGAME = 14, //!< A task for doing something in a minigame MISSION_TASK_TYPE_NON_MISSION_INTERACTION = 15, //!< A task for interacting with a non-mission MISSION_TASK_TYPE_MISSION_COMPLETE = 16, //!< A task for completing a mission + MISSION_TASK_TYPE_EARN_REPUTATION = 17, //!< A task for earning reputation MISSION_TASK_TYPE_POWERUP = 21, //!< A task for collecting a powerup MISSION_TASK_TYPE_PET_TAMING = 22, //!< A task for taming a pet MISSION_TASK_TYPE_RACING = 23, //!< A task for racing MISSION_TASK_TYPE_PLAYER_FLAG = 24, //!< A task for setting a player flag + MISSION_TASK_TYPE_PLACE_MODEL = 25, //!< A task for picking up a model MISSION_TASK_TYPE_VISIT_PROPERTY = 30 //!< A task for visiting a property }; diff --git a/dGame/dMission/RacingTaskParam.h b/dGame/dMission/RacingTaskParam.h new file mode 100644 index 00000000..38f8dd8e --- /dev/null +++ b/dGame/dMission/RacingTaskParam.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +enum class RacingTaskParam : int32_t { + RACING_TASK_PARAM_FINISH_WITH_PLACEMENT = 1, // LootGenerator::RollLootMatrix(uint32_t matrixIn return drops; } -void LootGenerator::GiveLoot(Entity* player, uint32_t matrixIndex) { +void LootGenerator::GiveLoot(Entity* player, uint32_t matrixIndex, eLootSourceType lootSourceType) { player = player->GetOwner(); // If the owner is overwritten, we collect that here std::unordered_map result = RollLootMatrix(player, matrixIndex); - GiveLoot(player, result); + GiveLoot(player, result, lootSourceType); } -void LootGenerator::GiveLoot(Entity* player, std::unordered_map& result) { +void LootGenerator::GiveLoot(Entity* player, std::unordered_map& result, eLootSourceType lootSourceType) { player = player->GetOwner(); // if the owner is overwritten, we collect that here auto* inventoryComponent = player->GetComponent(); @@ -285,7 +285,7 @@ void LootGenerator::GiveLoot(Entity* player, std::unordered_map& r return; for (const auto& pair : result) { - inventoryComponent->AddItem(pair.first, pair.second); + inventoryComponent->AddItem(pair.first, pair.second, lootSourceType); } } @@ -314,13 +314,13 @@ void LootGenerator::GiveActivityLoot(Entity* player, Entity* source, uint32_t ac maxCoins = currencyTable[0].maxvalue; } - GiveLoot(player, selectedReward->LootMatrixIndex); + GiveLoot(player, selectedReward->LootMatrixIndex, eLootSourceType::LOOT_SOURCE_ACTIVITY); uint32_t coins = (int)(minCoins + GeneralUtils::GenerateRandomNumber(0, 1) * (maxCoins - minCoins)); auto* character = player->GetCharacter(); - character->SetCoins(character->GetCoins() + coins, LOOT_SOURCE_ACTIVITY); + character->SetCoins(character->GetCoins() + coins, eLootSourceType::LOOT_SOURCE_ACTIVITY); } void LootGenerator::DropLoot(Entity* player, Entity* killedObject, uint32_t matrixIndex, uint32_t minCoins, uint32_t maxCoins) { diff --git a/dGame/dUtilities/Loot.h b/dGame/dUtilities/Loot.h index 23c6463d..b16c834a 100644 --- a/dGame/dUtilities/Loot.h +++ b/dGame/dUtilities/Loot.h @@ -47,8 +47,8 @@ class LootGenerator : public Singleton { std::unordered_map RollLootMatrix(Entity* player, uint32_t matrixIndex); std::unordered_map RollLootMatrix(uint32_t matrixIndex); - void GiveLoot(Entity* player, uint32_t matrixIndex); - void GiveLoot(Entity* player, std::unordered_map& result); + void GiveLoot(Entity* player, uint32_t matrixIndex, eLootSourceType lootSourceType = eLootSourceType::LOOT_SOURCE_NONE); + void GiveLoot(Entity* player, std::unordered_map& result, eLootSourceType lootSourceType = eLootSourceType::LOOT_SOURCE_NONE); void GiveActivityLoot(Entity* player, Entity* source, uint32_t activityID, int32_t rating = 0); void DropLoot(Entity* player, Entity* killedObject, uint32_t matrixIndex, uint32_t minCoins, uint32_t maxCoins); void DropLoot(Entity* player, Entity* killedObject, std::unordered_map& result, uint32_t minCoins, uint32_t maxCoins); diff --git a/dGame/dUtilities/Mail.cpp b/dGame/dUtilities/Mail.cpp index 302ba1cd..79e99c7d 100644 --- a/dGame/dUtilities/Mail.cpp +++ b/dGame/dUtilities/Mail.cpp @@ -262,7 +262,7 @@ void Mail::HandleSendMail(RakNet::BitStream* packet, const SystemAddress& sysAdd } Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::Success); - entity->GetCharacter()->SetCoins(entity->GetCharacter()->GetCoins() - mailCost, LOOT_SOURCE_MAIL); + entity->GetCharacter()->SetCoins(entity->GetCharacter()->GetCoins() - mailCost, eLootSourceType::LOOT_SOURCE_MAIL); Game::logger->Log("Mail", "Seeing if we need to remove item with ID/count/LOT: %i %i %i\n", itemID, attachmentCount, itemLOT); @@ -363,7 +363,7 @@ void Mail::HandleAttachmentCollect(RakNet::BitStream* packet, const SystemAddres auto inv = static_cast(player->GetComponent(COMPONENT_TYPE_INVENTORY)); if (!inv) return; - inv->AddItem(attachmentLOT, attachmentCount); + inv->AddItem(attachmentLOT, attachmentCount, eLootSourceType::LOOT_SOURCE_MAIL); Mail::SendAttachmentRemoveConfirm(sysAddr, mailID); diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index fee8118a..90f32f69 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -131,6 +131,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit bool success = user->GetMaxGMLevel() >= level; if (success) { + if (entity->GetGMLevel() > GAME_MASTER_LEVEL_CIVILIAN && level == GAME_MASTER_LEVEL_CIVILIAN) { GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); @@ -140,12 +141,12 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); } - WorldPackets::SendGMLevelChange(sysAddr, success, user->GetMaxGMLevel(), entity->GetGMLevel(), level); - GameMessages::SendChatModeUpdate(entity->GetObjectID(), level); - entity->SetGMLevel(level); - Game::logger->Log("SlashCommandHandler", "User %s (%i) has changed their GM level to %i for charID %llu\n", user->GetUsername().c_str(), user->GetAccountID(), level, entity->GetObjectID()); - } - } + WorldPackets::SendGMLevelChange(sysAddr, success, user->GetMaxGMLevel(), entity->GetGMLevel(), level); + GameMessages::SendChatModeUpdate(entity->GetObjectID(), level); + entity->SetGMLevel(level); + Game::logger->Log("SlashCommandHandler", "User %s (%i) has changed their GM level to %i for charID %llu\n", user->GetUsername().c_str(), user->GetAccountID(), level, entity->GetObjectID()); + } + } #ifndef DEVELOPER_SERVER if ((entity->GetGMLevel() > user->GetMaxGMLevel()) || (entity->GetGMLevel() > GAME_MASTER_LEVEL_CIVILIAN && user->GetMaxGMLevel() == GAME_MASTER_LEVEL_JUNIOR_DEVELOPER)) @@ -304,27 +305,15 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit if (chatCommand == "leave-zone") { const auto currentZone = dZoneManager::Instance()->GetZone()->GetZoneID().GetMapID(); - auto newZone = 1100; - - switch (currentZone) - { - case 1101: - newZone = 1100; - break; - case 1204: - newZone = 1200; - break; - default: - newZone = 1100; - break; - } - - if (currentZone == newZone) - { + auto newZone = 0; + if (currentZone % 100 == 0) { ChatPackets::SendSystemMessage(sysAddr, u"You are not in an instanced zone."); - return; + } else { + newZone = (currentZone / 100) * 100; } + // If new zone would be inaccessible, then default to Avant Gardens. + if (!CheckIfAccessibleZone(newZone)) newZone = 1100; ChatPackets::SendSystemMessage(sysAddr, u"Leaving zone..."); @@ -374,12 +363,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit }); } - if (user->GetMaxGMLevel() == 0 || entity->GetGMLevel() >= 0) { - if ((chatCommand == "playanimation" || chatCommand == "playanim") && args.size() == 1) { - std::u16string anim = GeneralUtils::ASCIIToUTF16(args[0], args[0].size()); - GameMessages::SendPlayAnimation(entity, anim); - } - + if (user->GetMaxGMLevel() == 0 || entity->GetGMLevel() >= 0) { if (chatCommand == "die") { entity->Smash(entity->GetObjectID()); } @@ -406,11 +390,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } if (entity->GetGMLevel() == 0) return; - } + } // Log command to database - auto stmt = Database::CreatePreppedStmt("INSERT INTO command_log (character_id, command) VALUES (?, ?);"); - stmt->setInt(1, entity->GetCharacter()->GetID()); + auto stmt = Database::CreatePreppedStmt("INSERT INTO command_log (character_id, command) VALUES (?, ?);"); + stmt->setInt(1, entity->GetCharacter()->GetID()); stmt->setString(2, GeneralUtils::UTF16ToWTF8(command).c_str()); stmt->execute(); delete stmt; @@ -459,6 +443,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); // need to retoggle because it gets reenabled on creation of new character } + if ((chatCommand == "playanimation" || chatCommand == "playanim") && args.size() == 1 && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { + std::u16string anim = GeneralUtils::ASCIIToUTF16(args[0], args[0].size()); + GameMessages::SendPlayAnimation(entity, anim); + } + if (chatCommand == "list-spawns" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { for (const auto& pair : EntityManager::Instance()->GetSpawnPointEntities()) { ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(pair.first)); @@ -672,7 +661,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } - entity->GetCharacter()->SetPlayerFlag(flagId, true); + entity->GetCharacter()->SetPlayerFlag(flagId, true); } if (chatCommand == "setflag" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER && args.size() == 2) @@ -688,7 +677,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag type."); return; } - entity->GetCharacter()->SetPlayerFlag(flagId, onOffFlag == "on"); + entity->GetCharacter()->SetPlayerFlag(flagId, onOffFlag == "on"); } if (chatCommand == "clearflag" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER && args.size() == 1) { @@ -700,7 +689,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } - entity->GetCharacter()->SetPlayerFlag(flagId, false); + entity->GetCharacter()->SetPlayerFlag(flagId, false); } if (chatCommand == "resetmission" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { @@ -798,8 +787,8 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, msg); } - if (chatCommand == "gmadditem" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { - if (args.size() == 1) { + if (chatCommand == "gmadditem" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { + if (args.size() == 1) { uint32_t itemLOT; if (!GeneralUtils::TryParse(args[0], itemLOT)) @@ -810,8 +799,8 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit InventoryComponent * inventory = static_cast(entity->GetComponent(COMPONENT_TYPE_INVENTORY)); - inventory->AddItem(itemLOT, 1); - } else if(args.size() == 2) { + inventory->AddItem(itemLOT, 1, eLootSourceType::LOOT_SOURCE_MODERATION); + } else if(args.size() == 2) { uint32_t itemLOT; if (!GeneralUtils::TryParse(args[0], itemLOT)) @@ -830,12 +819,12 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit InventoryComponent* inventory = static_cast(entity->GetComponent(COMPONENT_TYPE_INVENTORY)); - inventory->AddItem(itemLOT, count); + inventory->AddItem(itemLOT, count, eLootSourceType::LOOT_SOURCE_MODERATION); } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /gmadditem "); - } - } + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /gmadditem "); + } + } if (chatCommand == "mailitem" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_MODERATOR && args.size() >= 2) { const auto& playerName = args[0]; @@ -912,9 +901,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::ASCIIToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); } - if ((chatCommand == "teleport" || chatCommand == "tele") && entity->GetGMLevel() >= GAME_MASTER_LEVEL_JUNIOR_MODERATOR) { - NiPoint3 pos {}; - if (args.size() == 3) { + if ((chatCommand == "teleport" || chatCommand == "tele") && entity->GetGMLevel() >= GAME_MASTER_LEVEL_JUNIOR_MODERATOR) { + NiPoint3 pos {}; + if (args.size() == 3) { float x, y, z; @@ -943,6 +932,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit Game::logger->Log("SlashCommandHandler", "Teleporting objectID: %llu to %f, %f, %f\n", entity->GetObjectID(), pos.x, pos.y, pos.z); GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr); } else if (args.size() == 2) { + float x, z; if (!GeneralUtils::TryParse(args[0], x)) @@ -961,7 +951,6 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit pos.SetY(0.0f); pos.SetZ(z); - Game::logger->Log("SlashCommandHandler", "Teleporting objectID: %llu to X: %f, Z: %f\n", entity->GetObjectID(), pos.x, pos.z); GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr); } else { @@ -988,7 +977,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } } - } + } if (chatCommand == "tpall" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { @@ -1216,7 +1205,6 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit dest->SetImagination(999); dest->SetMaxImagination(999.0f); } - EntityManager::Instance()->SerializeEntity(entity); } @@ -1242,11 +1230,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit dest->SetImagination(9); dest->SetMaxImagination(9.0f); } - EntityManager::Instance()->SerializeEntity(entity); } if (chatCommand == "refillstats" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { + auto dest = static_cast(entity->GetComponent(COMPONENT_TYPE_DESTROYABLE)); if (dest) { dest->SetHealth((int)dest->GetMaxHealth()); @@ -1285,23 +1273,23 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } - EntityInfo info; - info.lot = lot; - info.pos = comp->GetPosition(); - info.rot = comp->GetRotation(); + EntityInfo info; + info.lot = lot; + info.pos = comp->GetPosition(); + info.rot = comp->GetRotation(); info.spawner = nullptr; info.spawnerID = entity->GetObjectID(); info.spawnerNodeID = 0; - Entity* newEntity = EntityManager::Instance()->CreateEntity(info, nullptr); + Entity* newEntity = EntityManager::Instance()->CreateEntity(info, nullptr); - if (newEntity == nullptr) { + if (newEntity == nullptr) { ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity."); return; - } + } - EntityManager::Instance()->ConstructEntity(newEntity); - } + EntityManager::Instance()->ConstructEntity(newEntity); + } if ((chatCommand == "giveuscore") && args.size() == 1 && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { int32_t uscore; @@ -1314,8 +1302,8 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit CharacterComponent* character = entity->GetComponent(); if (character) character->SetUScore(character->GetUScore() + uscore); - - GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), uscore, LOOTTYPE_NONE); + // LOOT_SOURCE_MODERATION should work but it doesn't. Relog to see uscore changes + GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), uscore, eLootSourceType::LOOT_SOURCE_MODERATION); } if (chatCommand == "pos" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { @@ -1345,9 +1333,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendPlayFXEffect(entity, 7074, u"create", "7074", LWOOBJID_EMPTY, 1.0f, 1.0f, true); } - if (chatCommand == "playrebuildfx" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { - GameMessages::SendPlayFXEffect(entity, 230, u"rebuild", "230", LWOOBJID_EMPTY, 1.0f, 1.0f, true); - } + if (chatCommand == "playrebuildfx" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { + GameMessages::SendPlayFXEffect(entity, 230, u"rebuild", "230", LWOOBJID_EMPTY, 1.0f, 1.0f, true); + } if ((chatCommand == "freemoney" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) && args.size() == 1) { int32_t money; @@ -1359,7 +1347,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } auto* ch = entity->GetCharacter(); - ch->SetCoins(ch->GetCoins() + money, LOOT_SOURCE_MODERATION); + ch->SetCoins(ch->GetCoins() + money, eLootSourceType::LOOT_SOURCE_MODERATION); } if ((chatCommand == "setcurrency") && args.size() == 1 && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { @@ -1372,7 +1360,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } auto* ch = entity->GetCharacter(); - ch->SetCoins(money, LOOT_SOURCE_MODERATION); + ch->SetCoins(money, eLootSourceType::LOOT_SOURCE_MODERATION); } // Allow for this on even while not a GM, as it sometimes toggles incorrrectly. @@ -1430,8 +1418,8 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } - if ((chatCommand == "testmap" && args.size() >= 1) && entity->GetGMLevel() >= GAME_MASTER_LEVEL_FORUM_MODERATOR) { - ChatPackets::SendSystemMessage(sysAddr, u"Requesting map change..."); + if ((chatCommand == "testmap" && args.size() >= 1) && entity->GetGMLevel() >= GAME_MASTER_LEVEL_FORUM_MODERATOR) { + ChatPackets::SendSystemMessage(sysAddr, u"Requesting map change..."); uint32_t reqZone; LWOCLONEID cloneId = 0; bool force = false; @@ -1482,12 +1470,12 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ); } - ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, reqZone, cloneId, false, [objid, darwin](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { + ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, reqZone, cloneId, false, [objid, darwin](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { auto* entity = EntityManager::Instance()->GetEntity(objid); - if (entity == nullptr) { - return; - } + if (entity == nullptr) { + return; + } float transferTime = 3.32999992370605f; if (darwin) transferTime = 0.0f; @@ -1509,7 +1497,6 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift); }); - return; }); } else { @@ -1648,7 +1635,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit std::vector data {}; data.push_back(new LDFData(u"reforgedLOT", reforgedItem)); - inventoryComponent->AddItem(baseItem, 1, INVALID, data); + inventoryComponent->AddItem(baseItem, 1, eLootSourceType::LOOT_SOURCE_MODERATION, eInventoryType::INVALID, data); } if (chatCommand == "crash" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_OPERATOR) @@ -1953,54 +1940,17 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } bool SlashCommandHandler::CheckIfAccessibleZone(const unsigned int zoneID) { - switch (zoneID) { - case 98: - case 1000: - case 1001: - - case 1100: - case 1101: - case 1150: - case 1151: - case 1152: - - case 1200: - case 1201: - - case 1250: - case 1251: - case 1260: - - case 1300: - case 1350: - case 1351: - - case 1400: - case 1401: - case 1450: - case 1451: - - case 1600: - case 1601: - case 1602: - case 1603: - case 1604: - - case 1700: - case 1800: - case 1900: - case 2000: - - case 58004: - case 58005: - case 58006: - return true; - - default: - return false; + //We're gonna go ahead and presume we've got the db loaded already: + CDZoneTableTable * zoneTable = CDClientManager::Instance()->GetTable("ZoneTable"); + const CDZoneTable* zone = zoneTable->Query(zoneID); + if (zone != nullptr) { + std::string zonePath = "./res/maps/" + zone->zoneName; + std::transform(zonePath.begin(), zonePath.end(), zonePath.begin(), ::tolower); + std::ifstream f(zonePath.c_str()); + return f.good(); + } else { + return false; } - - return false; } void SlashCommandHandler::SendAnnouncement(const std::string& title, const std::string& message) { diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index b6ccac63..e1e4728f 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -47,6 +47,7 @@ namespace Game { bool shutdownSequenceStarted = false; void ShutdownSequence(); +int FinalizeShutdown(); dLogger* SetupLogger(); void StartAuthServer(); void StartChatServer(); @@ -63,10 +64,11 @@ int main(int argc, char** argv) { //Triggers the shutdown sequence at application exit std::atexit(ShutdownSequence); signal(SIGINT, [](int) { ShutdownSequence(); }); + signal(SIGTERM, [](int) { ShutdownSequence(); }); //Create all the objects we need to run our service: Game::logger = SetupLogger(); - if (!Game::logger) return 0; + if (!Game::logger) return EXIT_FAILURE; Game::logger->Log("MasterServer", "Starting Master server...\n"); Game::logger->Log("MasterServer", "Version: %i.%i\n", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR); @@ -83,7 +85,7 @@ int main(int argc, char** argv) { std::ifstream cdclient_fd(cdclient_path); if (!cdclient_fd.good()) { Game::logger->Log("WorldServer", "%s could not be opened\n", cdclient_path.c_str()); - return -1; + return EXIT_FAILURE; } cdclient_fd.close(); @@ -94,7 +96,7 @@ int main(int argc, char** argv) { Game::logger->Log("WorldServer", "Unable to connect to CDServer SQLite Database\n"); Game::logger->Log("WorldServer", "Error: %s\n", e.errorMessage()); Game::logger->Log("WorldServer", "Error Code: %i\n", e.errorCode()); - return -1; + return EXIT_FAILURE; } //Get CDClient initial information @@ -105,7 +107,7 @@ int main(int argc, char** argv) { Game::logger->Log("WorldServer", "May be caused by corrupted file: %s\n", cdclient_path.c_str()); Game::logger->Log("WorldServer", "Error: %s\n", e.errorMessage()); Game::logger->Log("WorldServer", "Error Code: %i\n", e.errorCode()); - return -1; + return EXIT_FAILURE; } //Connect to the MySQL Database @@ -118,7 +120,7 @@ int main(int argc, char** argv) { Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password); } catch (sql::SQLException& ex) { Game::logger->Log("MasterServer", "Got an error while connecting to the database: %s\n", ex.what()); - return 0; + return EXIT_FAILURE; } //If the first command line argument is -a or --account then make the user @@ -166,7 +168,11 @@ int main(int argc, char** argv) { delete statement; std::cout << "Account created successfully!\n"; - return 0; + + Database::Destroy("MasterServer"); + delete Game::logger; + + return EXIT_SUCCESS; } int maxClients = 999; @@ -266,7 +272,8 @@ int main(int argc, char** argv) { //10m shutdown for universe kill command if (shouldShutdown) { if (framesSinceKillUniverseCommand >= 40000) { - std::exit(0); + //Break main loop and exit + break; } else framesSinceKillUniverseCommand++; @@ -312,14 +319,9 @@ int main(int argc, char** argv) { t += std::chrono::milliseconds(highFrameRate); std::this_thread::sleep_until(t); } - - //Delete our objects here: - Database::Destroy(); - delete Game::im; - delete Game::server; - delete Game::logger; - - return 0; + FinalizeShutdown(); + exit(EXIT_SUCCESS); + return EXIT_SUCCESS; } dLogger* SetupLogger() { @@ -658,7 +660,7 @@ void HandlePacket(Packet* packet) { RakNet::BitStream inStream(packet->data, packet->length, false); uint64_t header = inStream.Read(header); - auto* instance =Game::im->GetInstanceBySysAddr(packet->systemAddress); + auto* instance = Game::im->GetInstanceBySysAddr(packet->systemAddress); if (instance == nullptr) { return; @@ -739,12 +741,20 @@ void ShutdownSequence() { auto ticks = 0; if (!Game::im) { - exit(0); + exit(EXIT_SUCCESS); } Game::logger->Log("MasterServer", "Attempting to shutdown instances, max 60 seconds...\n"); while (true) { + + auto packet = Game::server->Receive(); + if (packet) { + HandlePacket(packet); + Game::server->DeallocatePacket(packet); + packet = nullptr; + } + auto done = true; for (auto* instance : Game::im->GetInstances()) { @@ -758,6 +768,7 @@ void ShutdownSequence() { } if (done) { + Game::logger->Log("MasterServer", "Finished shutting down MasterServer!\n"); break; } @@ -767,9 +778,21 @@ void ShutdownSequence() { ticks++; if (ticks == 600 * 6) { + Game::logger->Log("MasterServer", "Finished shutting down by timeout!\n"); break; } } - exit(0); + FinalizeShutdown(); } + +int FinalizeShutdown() { + //Delete our objects here: + Database::Destroy("MasterServer"); + delete Game::im; + delete Game::server; + delete Game::logger; + + exit(EXIT_SUCCESS); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/dNet/WorldPackets.cpp b/dNet/WorldPackets.cpp index 399ab9e5..ae8f71a4 100644 --- a/dNet/WorldPackets.cpp +++ b/dNet/WorldPackets.cpp @@ -12,6 +12,7 @@ #include "LDFFormat.h" #include "dServer.h" #include "dZoneManager.h" +#include "CharacterComponent.h" #include "ZCompression.h" void WorldPackets::SendLoadStaticZone(const SystemAddress& sysAddr, float x, float y, float z, uint32_t checksum) { @@ -126,26 +127,34 @@ void WorldPackets::SendServerState ( const SystemAddress& sysAddr ) { SEND_PACKET } -void WorldPackets::SendCreateCharacter(const SystemAddress& sysAddr, const LWOOBJID& objectID, const std::string& xmlData, const std::u16string& username, int32_t gm) { +void WorldPackets::SendCreateCharacter(const SystemAddress& sysAddr, Entity* entity, const std::string& xmlData, const std::u16string& username, int32_t gm) { RakNet::BitStream bitStream; PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_CREATE_CHARACTER); RakNet::BitStream data; - data.Write(6); //LDF key count + data.Write(7); //LDF key count - LDFData* objid = new LDFData(u"objid", objectID); + auto character = entity->GetComponent(); + if (!character) { + Game::logger->Log("WorldPackets", "Entity is not a character?? what??"); + return; + } + + LDFData* objid = new LDFData(u"objid", entity->GetObjectID()); LDFData* lot = new LDFData(u"template", 1); LDFData * xmlConfigData = new LDFData(u"xmlData", xmlData); LDFData* name = new LDFData(u"name", username); LDFData* gmlevel = new LDFData(u"gmlevel", gm); LDFData* chatmode = new LDFData(u"chatmode", gm); - + LDFData* reputation = new LDFData(u"reputation", character->GetReputation()); + objid->WriteToPacket(&data); lot->WriteToPacket(&data); name->WriteToPacket(&data); gmlevel->WriteToPacket(&data); chatmode->WriteToPacket(&data); xmlConfigData->WriteToPacket(&data); + reputation->WriteToPacket(&data); delete objid; delete lot; @@ -153,7 +162,8 @@ void WorldPackets::SendCreateCharacter(const SystemAddress& sysAddr, const LWOOB delete gmlevel; delete chatmode; delete name; - + delete reputation; + #ifdef _WIN32 bitStream.Write(data.GetNumberOfBytesUsed() + 1); bitStream.Write(0); @@ -175,7 +185,7 @@ void WorldPackets::SendCreateCharacter(const SystemAddress& sysAddr, const LWOOB PacketUtils::SavePacket("chardata.bin", (const char *)bitStream.GetData(), static_cast(bitStream.GetNumberOfBytesUsed())); SEND_PACKET - Game::logger->Log("WorldPackets", "Sent CreateCharacter for ID: %llu\n", objectID); + Game::logger->Log("WorldPackets", "Sent CreateCharacter for ID: %llu\n", entity->GetObjectID()); } void WorldPackets::SendChatModerationResponse(const SystemAddress& sysAddr, bool requestAccepted, uint32_t requestID, const std::string& receiver, std::unordered_map unacceptedItems) { diff --git a/dNet/WorldPackets.h b/dNet/WorldPackets.h index ff51a7f6..3508d6f0 100644 --- a/dNet/WorldPackets.h +++ b/dNet/WorldPackets.h @@ -4,6 +4,7 @@ #include "dCommonVars.h" #include #include +#include "Entity.h" class User; struct SystemAddress; @@ -16,7 +17,7 @@ namespace WorldPackets { void SendCharacterDeleteResponse(const SystemAddress& sysAddr, bool response); void SendTransferToWorld(const SystemAddress& sysAddr, const std::string& serverIP, uint32_t serverPort, bool mythranShift); void SendServerState(const SystemAddress& sysAddr); - void SendCreateCharacter(const SystemAddress& sysAddr, const LWOOBJID& objectID, const std::string& xmlData, const std::u16string& username, int32_t gm); + void SendCreateCharacter(const SystemAddress& sysAddr, Entity* entity, const std::string& xmlData, const std::u16string& username, int32_t gm); void SendChatModerationResponse(const SystemAddress& sysAddr, bool requestAccepted, uint32_t requestID, const std::string& receiver, std::unordered_map unacceptedItems); void SendGMLevelChange(const SystemAddress& sysAddr, bool success, uint8_t highestLevel, uint8_t prevLevel, uint8_t newLevel); } diff --git a/dNet/dMessageIdentifiers.h b/dNet/dMessageIdentifiers.h index d4f4e62b..8e20ab54 100644 --- a/dNet/dMessageIdentifiers.h +++ b/dNet/dMessageIdentifiers.h @@ -324,6 +324,7 @@ enum GAME_MSG : unsigned short { GAME_MSG_ACTIVITY_STOP = 408, GAME_MSG_SHOOTING_GALLERY_CLIENT_AIM_UPDATE = 409, GAME_MSG_SHOOTING_GALLERY_FIRE = 411, + GAME_MSG_REQUEST_VENDOR_STATUS_UPDATE = 416, GAME_MSG_VENDOR_STATUS_UPDATE = 417, GAME_MSG_NOTIFY_CLIENT_SHOOTING_GALLERY_SCORE = 425, GAME_MSG_CONSUME_CLIENT_ITEM = 427, @@ -382,6 +383,7 @@ enum GAME_MSG : unsigned short { GAME_MSG_PROPERTY_EDITOR_END = 725, GAME_MSG_START_PATHING = 735, GAME_MSG_NOTIFY_CLIENT_ZONE_OBJECT = 737, + GAME_MSG_UPDATE_REPUTATION = 746, GAME_MSG_PROPERTY_RENTAL_RESPONSE = 750, GAME_MSG_REQUEST_PLATFORM_RESYNC = 760, GAME_MSG_PLATFORM_RESYNC = 761, @@ -527,11 +529,13 @@ enum GAME_MSG : unsigned short { GAME_MSG_VEHICLE_NOTIFY_HIT_IMAGINATION_SERVER = 1606, GAME_MSG_ADD_RUN_SPEED_MODIFIER = 1505, GAME_MSG_REMOVE_RUN_SPEED_MODIFIER = 1506, + GAME_MSG_UPDATE_PROPERTY_PERFORMANCE_COST = 1547, GAME_MSG_PROPERTY_ENTRANCE_BEGIN = 1553, GAME_MSG_REQUEST_MOVE_ITEM_BETWEEN_INVENTORY_TYPES = 1666, GAME_MSG_RESPONSE_MOVE_ITEM_BETWEEN_INVENTORY_TYPES = 1667, GAME_MSG_PLAYER_SET_CAMERA_CYCLING_MODE = 1676, GAME_MSG_NOTIFY_SERVER_LEVEL_PROCESSING_COMPLETE = 1734, + GAME_MSG_NOTIFY_LEVEL_REWARDS = 1735, GAME_MSG_MARK_INVENTORY_ITEM_AS_ACTIVE = 1767, END }; \ No newline at end of file diff --git a/dScripts/ActMine.cpp b/dScripts/ActMine.cpp new file mode 100644 index 00000000..9cc116b1 --- /dev/null +++ b/dScripts/ActMine.cpp @@ -0,0 +1,53 @@ +#include "ActMine.h" +#include "SkillComponent.h" +#include "DestroyableComponent.h" +#include "RebuildComponent.h" + +void ActMine::OnStartup(Entity* self) { + self->SetVar(u"RebuildComplete", false); + self->SetProximityRadius(MINE_RADIUS, "mineRadius"); +} + +void ActMine::OnRebuildNotifyState(Entity* self, eRebuildState state) +{ + if (state == eRebuildState::REBUILD_COMPLETED) { + auto* rebuild = self->GetComponent(); + if (rebuild) { + auto* builder = rebuild->GetBuilder(); + self->SetVar(u"Builder", builder->GetObjectID()); + } + + self->SetVar(u"RebuildComplete", true); + self->SetVar(u"NumWarnings", 0); + self->AddToGroup("reset"); + } + +} + +void ActMine::OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) { + auto* detroyable = self->GetComponent(); + if (!detroyable) return; + if (status == "ENTER" && self->GetVar(u"RebuildComplete") == true && detroyable->IsEnemy(entering)) { + GameMessages::SendPlayFXEffect(self->GetObjectID(), 242, u"orange", "sirenlight_B"); + self->AddTimer("Tick", TICK_TIME); + } +} + +void ActMine::OnTimerDone(Entity* self, std::string timerName) { + if (timerName == "Tick") { + if (self->GetVar(u"NumWarnings") >= MAX_WARNINGS){ + auto* skill = self->GetComponent(); + if (!skill) return; + skill->CalculateBehavior(SKILL_ID, BEHAVIOR_ID, LWOOBJID_EMPTY); + self->AddTimer("BlowedUp", BLOWED_UP_TIME); + } else { + GameMessages::SendPlayFXEffect(self->GetObjectID(), 242, u"orange", "sirenlight_B"); + self->AddTimer("Tick", TICK_TIME); + self->SetVar(u"NumWarnings", self->GetVar(u"NumWarnings") + 1); + } + } + + if (timerName == "BlowedUp") { + self->Kill(self); + } +} \ No newline at end of file diff --git a/dScripts/ActMine.h b/dScripts/ActMine.h new file mode 100644 index 00000000..85efadcc --- /dev/null +++ b/dScripts/ActMine.h @@ -0,0 +1,18 @@ +#pragma once +#include "CppScripts.h" + +class ActMine : public CppScripts::Script { + public: + void OnStartup(Entity* self); + void OnRebuildNotifyState(Entity* self, eRebuildState state) override; + void OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status); + void OnTimerDone(Entity* self, std::string timerName); + private: + int MAX_WARNINGS = 3; + float MINE_RADIUS = 10.0; + float TICK_TIME = 0.25; + float BLOWED_UP_TIME = 0.1; + uint32_t SKILL_ID = 317; + uint32_t BEHAVIOR_ID = 3719; +}; + diff --git a/dScripts/AgFans.h b/dScripts/AgFans.h index 0efe9c6d..09cc89d9 100644 --- a/dScripts/AgFans.h +++ b/dScripts/AgFans.h @@ -7,10 +7,16 @@ class AgFans : public CppScripts::Script { public: - void OnStartup(Entity* self); - void OnDie(Entity* self, Entity* killer); - void OnFireEventServerSide(Entity *self, Entity *sender, std::string args, int32_t param1, int32_t param2, - int32_t param3); + void OnStartup(Entity* self) override; + void OnDie(Entity* self, Entity* killer) override; + void OnFireEventServerSide( + Entity *self, + Entity *sender, + std::string args, + int32_t param1, + int32_t param2, + int32_t param3 + ) override; private: void ToggleFX(Entity* self, bool hit); }; diff --git a/dScripts/AgSurvivalBuffStation.cpp b/dScripts/AgSurvivalBuffStation.cpp index 41a754d6..86a2f653 100644 --- a/dScripts/AgSurvivalBuffStation.cpp +++ b/dScripts/AgSurvivalBuffStation.cpp @@ -1,15 +1,66 @@ #include "AgSurvivalBuffStation.h" +#include "DestroyableComponent.h" +#include "EntityManager.h" +#include "GameMessages.h" #include "SkillComponent.h" #include "dLogger.h" +#include "TeamManager.h" void AgSurvivalBuffStation::OnRebuildComplete(Entity* self, Entity* target) { + auto destroyableComponent = self->GetComponent(); + // We set the faction to 1 so that the buff station sees players as friendly targets to buff + if (destroyableComponent != nullptr) destroyableComponent->SetFaction(1); + auto skillComponent = self->GetComponent(); - if (skillComponent == nullptr) return; + if (skillComponent != nullptr) skillComponent->CalculateBehavior(skillIdForBuffStation, behaviorIdForBuffStation, self->GetObjectID()); - skillComponent->CalculateBehavior(201, 1784, self->GetObjectID()); - - self->AddCallbackTimer(10.0f, [self]() { + self->AddCallbackTimer(smashTimer, [self]() { self->Smash(); }); + self->AddTimer("DropArmor", dropArmorTimer); + self->AddTimer("DropLife", dropLifeTimer); + self->AddTimer("Dropimagination", dropImaginationTimer); + // Since all survival players should be on the same team, we get the team. + auto team = TeamManager::Instance()->GetTeam(target->GetObjectID()); + + std::vector builderTeam; + // Not on a team + if (team == nullptr) { + builderTeam.push_back(target->GetObjectID()); + self->SetVar>(u"BuilderTeam", builderTeam); + return; + } + + for (auto memberID : team->members) { + builderTeam.push_back(memberID); + } + self->SetVar>(u"BuilderTeam", builderTeam); +} + +void AgSurvivalBuffStation::OnTimerDone(Entity* self, std::string timerName) { + uint32_t powerupToDrop = lifePowerup; + if (timerName == "DropArmor") { + powerupToDrop = armorPowerup; + self->AddTimer("DropArmor", dropArmorTimer); + } + if (timerName == "DropLife") { + powerupToDrop = lifePowerup; + self->AddTimer("DropLife", dropLifeTimer); + } + if (timerName == "Dropimagination") { + powerupToDrop = imaginationPowerup; + self->AddTimer("Dropimagination", dropImaginationTimer); + } + auto team = self->GetVar>(u"BuilderTeam"); + for (auto memberID : team) { + auto member = EntityManager::Instance()->GetEntity(memberID); + if (member != nullptr && !member->GetIsDead()) { + GameMessages::SendDropClientLoot(member, self->GetObjectID(), powerupToDrop, 0, self->GetPosition()); + } else { + // If player left the team or left early erase them from the team variable. + team.erase(std::find(team.begin(), team.end(), memberID)); + self->SetVar>(u"BuilderTeam", team); + } + } } diff --git a/dScripts/AgSurvivalBuffStation.h b/dScripts/AgSurvivalBuffStation.h index 0b4d1865..b2beaba6 100644 --- a/dScripts/AgSurvivalBuffStation.h +++ b/dScripts/AgSurvivalBuffStation.h @@ -11,6 +11,7 @@ public: * @param target The target of the self that called this script. */ void OnRebuildComplete(Entity* self, Entity* target) override; + void OnTimerDone(Entity* self, std::string timerName) override; private: /** * Skill ID for the buff station. @@ -20,4 +21,32 @@ private: * Behavior ID for the buff station. */ uint32_t behaviorIdForBuffStation = 1784; + /** + * Timer for dropping armor. + */ + float dropArmorTimer = 6.0f; + /** + * Timer for dropping life. + */ + float dropLifeTimer = 3.0f; + /** + * Timer for dropping imagination. + */ + float dropImaginationTimer = 4.0f; + /** + * Timer for smashing. + */ + float smashTimer = 25.0f; + /** + * LOT for armor powerup. + */ + LOT armorPowerup = 6431; + /** + * LOT for life powerup. + */ + LOT lifePowerup = 177; + /** + * LOT for imagination powerup. + */ + LOT imaginationPowerup = 935; }; \ No newline at end of file diff --git a/dScripts/AmDarklingDragon.cpp b/dScripts/AmDarklingDragon.cpp new file mode 100644 index 00000000..be87f466 --- /dev/null +++ b/dScripts/AmDarklingDragon.cpp @@ -0,0 +1,164 @@ +#include "AmDarklingDragon.h" +#include "BaseCombatAIComponent.h" +#include "DestroyableComponent.h" +#include "EntityManager.h" +#include "GameMessages.h" +#include "SkillComponent.h" +#include "BaseCombatAIComponent.h" + + +void AmDarklingDragon::OnStartup(Entity* self) { + self->SetVar(u"weakspot", 0); + + auto* baseCombatAIComponent = self->GetComponent(); + + if (baseCombatAIComponent != nullptr) { + baseCombatAIComponent->SetStunImmune(true); + } +} + +void AmDarklingDragon::OnDie(Entity* self, Entity* killer) { + if (self->GetVar(u"bDied")) { + return; + } + + self->SetVar(u"bDied", true); + + auto golemId = self->GetVar(u"Golem"); + + auto* golem = EntityManager::Instance()->GetEntity(golemId); + + if (golem != nullptr) { + golem->Smash(self->GetObjectID()); + } +} + +void AmDarklingDragon::OnHitOrHealResult(Entity* self, Entity* attacker, int32_t damage) { + GameMessages::SendPlayFXEffect(self, -1, u"gothit", "", LWOOBJID_EMPTY, 1, 1, true); + + if (true) { + auto weakpoint = self->GetVar(u"weakspot"); + + if (weakpoint == 1) + { + self->Smash(attacker->GetObjectID()); + } + } + + auto* destroyableComponent = self->GetComponent(); + + if (destroyableComponent != nullptr) { + Game::logger->Log("AmDarklingDragon", "Armor is %i\n", destroyableComponent->GetArmor()); + + if (destroyableComponent->GetArmor() > 0) return; + + auto weakpoint = self->GetVar(u"weakpoint"); + + if (weakpoint == 0) { + Game::logger->Log("AmDarklingDragon", "Activating weakpoint\n"); + + self->AddTimer("ReviveTimer", 12); + + auto* baseCombatAIComponent = self->GetComponent(); + auto* skillComponent = self->GetComponent(); + + if (baseCombatAIComponent != nullptr) { + baseCombatAIComponent->SetDisabled(true); + baseCombatAIComponent->SetStunned(true); + } + + if (skillComponent != nullptr) { + skillComponent->Interrupt(); + } + + self->SetVar(u"weakpoint", 2); + + GameMessages::SendPlayAnimation(self, u"stunstart", 1.7f); + + self->AddTimer("timeToStunLoop", 1); + + auto position = self->GetPosition(); + auto forward = self->GetRotation().GetForwardVector(); + auto backwards = forward * -1; + + forward.x *= 10; + forward.z *= 10; + + auto rotation = self->GetRotation(); + + auto objectPosition = NiPoint3(); + + objectPosition.y = position.y; + objectPosition.x = position.x - (backwards.x * 8); + objectPosition.z = position.z - (backwards.z * 8); + + auto golem = self->GetVar(u"DragonSmashingGolem"); + + EntityInfo info {}; + info.lot = golem != 0 ? golem : 8340; + info.pos = objectPosition; + info.rot = rotation; + info.spawnerID = self->GetObjectID(); + info.settings = { + new LDFData(u"rebuild_activators", + std::to_string(objectPosition.x + forward.x) + "\x1f" + + std::to_string(objectPosition.y) + "\x1f" + + std::to_string(objectPosition.z + forward.z) + ), + new LDFData(u"respawn", 100000), + new LDFData(u"rebuild_reset_time", 15), + new LDFData(u"no_timed_spawn", true), + new LDFData(u"Dragon", self->GetObjectID()) + }; + + auto* golemObject = EntityManager::Instance()->CreateEntity(info); + + EntityManager::Instance()->ConstructEntity(golemObject); + } + } +} + +void AmDarklingDragon::OnTimerDone(Entity* self, std::string timerName) { + if (timerName == "ReviveHeldTimer") { + self->AddTimer("backToAttack", 2.5); + } + else if (timerName == "ExposeWeakSpotTimer") { + self->SetVar(u"weakspot", 1); + } + else if (timerName == "timeToStunLoop") { + GameMessages::SendPlayAnimation(self, u"stunloop", 1.8f); + } + else if (timerName == "ReviveTimer") { + GameMessages::SendPlayAnimation(self, u"stunend", 2.0f); + self->AddTimer("backToAttack", 1); + } + else if (timerName == "backToAttack") { + auto* baseCombatAIComponent = self->GetComponent(); + auto* skillComponent = self->GetComponent(); + if (baseCombatAIComponent != nullptr) + { + baseCombatAIComponent->SetDisabled(false); + baseCombatAIComponent->SetStunned(false); + } + if (skillComponent != nullptr) + { + skillComponent->Interrupt(); + } + self->SetVar(u"weakspot", -1); + GameMessages::SendNotifyObject(self->GetObjectID(), self->GetObjectID(), u"DragonRevive", UNASSIGNED_SYSTEM_ADDRESS); + } +} + +void AmDarklingDragon::OnFireEventServerSide(Entity *self, Entity *sender, std::string args, int32_t param1, int32_t param2, int32_t param3) { + if (args != "rebuildDone") return; + + self->AddTimer("ExposeWeakSpotTimer", 3.8f); + + self->CancelTimer("ReviveTimer"); + + self->AddTimer("ReviveHeldTimer", 10.5f); + + self->SetVar(u"Golem", sender->GetObjectID()); + + GameMessages::SendPlayAnimation(self, u"quickbuildhold", 1.9f); +} \ No newline at end of file diff --git a/dScripts/AmDarklingDragon.h b/dScripts/AmDarklingDragon.h new file mode 100644 index 00000000..ea4c2a19 --- /dev/null +++ b/dScripts/AmDarklingDragon.h @@ -0,0 +1,48 @@ +#pragma once +#include "CppScripts.h" + +class AmDarklingDragon : public CppScripts::Script +{ +public: + /** + * @brief When called, this function will make self immune to stuns and initialize a weakspot boolean to false. + * + * @param self The Entity that called this function. + */ + void OnStartup(Entity* self) override; + /** + * @brief When called, this function will destroy the golem if it was alive, otherwise returns immediately. + * + * @param self The Entity that called this function. + * @param killer The Entity that killed self. + */ + void OnDie(Entity* self, Entity* killer) override; + /** + * @brief When self is hit or healed, this function will check if self is at zero armor. If self is at zero armor, a golem Entity Quick Build + * is spawned that, when built, will reveal a weakpoint on the dragon that if hit will smash the dragon instantly. If at more than zero armor, + * this function returns early. + * + * @param self The Entity that was hit. + * @param attacker The Entity that attacked self. + * @param damage The amount of damage attacker did to self. + */ + void OnHitOrHealResult(Entity* self, Entity* attacker, int32_t damage) override; + /** + * @brief Called when self has a timer that ended. + * + * @param self The Entity who owns a timer that finished. + * @param timerName The name of a timer attacked to self that has ended. + */ + void OnTimerDone(Entity* self, std::string timerName) override; + /** + * @brief When the Client has finished rebuilding the Golem for the dragon, this function exposes the weak spot for a set amount of time. + * + * @param self The Entity that called this script. + * @param sender The Entity that sent a fired event. + * @param args The argument that tells us what event has been fired off. + * @param param1 Unused in this script. + * @param param2 Unused in this script. + * @param param3 Unused in this script. + */ + void OnFireEventServerSide(Entity *self, Entity *sender, std::string args, int32_t param1, int32_t param2, int32_t param3) override; +}; diff --git a/dScripts/AmDrawBridge.h b/dScripts/AmDrawBridge.h index 1b00e2be..e7b801df 100644 --- a/dScripts/AmDrawBridge.h +++ b/dScripts/AmDrawBridge.h @@ -7,7 +7,7 @@ public: void OnStartup(Entity* self) override; void OnUse(Entity* self, Entity* user) override; void OnTimerDone(Entity* self, std::string timerName) override; - void OnNotifyObject(Entity *self, Entity *sender, const std::string& name, int32_t param1 = 0, int32_t param2 = 0); + void OnNotifyObject(Entity *self, Entity *sender, const std::string& name, int32_t param1 = 0, int32_t param2 = 0) override; void MoveBridgeDown(Entity* self, Entity* bridge, bool down); void NotifyDie(Entity* self, Entity* other); diff --git a/dScripts/AmDropshipComputer.cpp b/dScripts/AmDropshipComputer.cpp index 4101f526..836b09b0 100644 --- a/dScripts/AmDropshipComputer.cpp +++ b/dScripts/AmDropshipComputer.cpp @@ -32,7 +32,7 @@ void AmDropshipComputer::OnUse(Entity* self, Entity* user) return; } - inventoryComponent->AddItem(12323, 1); + inventoryComponent->AddItem(12323, 1, eLootSourceType::LOOT_SOURCE_NONE); } void AmDropshipComputer::OnDie(Entity* self, Entity* killer) diff --git a/dScripts/AmNamedDarklingDragon.cpp b/dScripts/AmNamedDarklingDragon.cpp deleted file mode 100644 index c7f51cbd..00000000 --- a/dScripts/AmNamedDarklingDragon.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "AmNamedDarklingDragon.h" -#include "BaseCombatAIComponent.h" - -void AmNamedDarklingDragon::OnStartup(Entity* self) -{ - auto* baseCombatAIComponent = self->GetComponent(); - - if (baseCombatAIComponent != nullptr) - { - baseCombatAIComponent->SetStunImmune(true); - } -} diff --git a/dScripts/AmNamedDarklingDragon.h b/dScripts/AmNamedDarklingDragon.h deleted file mode 100644 index cad5c2ca..00000000 --- a/dScripts/AmNamedDarklingDragon.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include "CppScripts.h" - -class AmNamedDarklingDragon : public CppScripts::Script -{ -public: - void OnStartup(Entity* self) override; -}; diff --git a/dScripts/AmTeapotServer.cpp b/dScripts/AmTeapotServer.cpp new file mode 100644 index 00000000..17b95eeb --- /dev/null +++ b/dScripts/AmTeapotServer.cpp @@ -0,0 +1,15 @@ +#include "AmTeapotServer.h" +#include "InventoryComponent.h" +#include "GameMessages.h" + + +void AmTeapotServer::OnUse(Entity* self, Entity* user) { + auto* inventoryComponent = user->GetComponent(); + if (!inventoryComponent) return; + + if (inventoryComponent->GetLotCount(BLUE_FLOWER_LEAVES) >= 10){ + inventoryComponent->RemoveItem(BLUE_FLOWER_LEAVES, 10); + inventoryComponent->AddItem(WU_S_IMAGINATION_TEA, 1); + } + GameMessages::SendTerminateInteraction(user->GetObjectID(), FROM_INTERACTION, self->GetObjectID()); +} diff --git a/dScripts/AmTeapotServer.h b/dScripts/AmTeapotServer.h new file mode 100644 index 00000000..19cb5639 --- /dev/null +++ b/dScripts/AmTeapotServer.h @@ -0,0 +1,10 @@ +#pragma once +#include "CppScripts.h" + +class AmTeapotServer : public CppScripts::Script { + public: + void OnUse(Entity* self, Entity* user) override; + private: + LOT BLUE_FLOWER_LEAVES = 12317; + LOT WU_S_IMAGINATION_TEA = 12109; +}; diff --git a/dScripts/BaseConsoleTeleportServer.cpp b/dScripts/BaseConsoleTeleportServer.cpp index b4e88af7..ef2d00e0 100644 --- a/dScripts/BaseConsoleTeleportServer.cpp +++ b/dScripts/BaseConsoleTeleportServer.cpp @@ -19,21 +19,6 @@ void BaseConsoleTeleportServer::BaseOnMessageBoxResponse(Entity* self, Entity* s if (button == 1) { - if (self->GetLOT() == 14333) - { - auto* rocketLaunchComponent = self->GetComponent(); - - if (rocketLaunchComponent == nullptr) - { - rocketLaunchComponent; - } - - const auto& teleportZone = self->GetVar(u"transferZoneID"); - - rocketLaunchComponent->Launch(player, LWOOBJID_EMPTY, std::stoi(GeneralUtils::UTF16ToWTF8(teleportZone))); - - return; - } GameMessages::SendSetStunned(player->GetObjectID(), PUSH, player->GetSystemAddress(), player->GetObjectID(), true, true, true, true, true, true, true diff --git a/dScripts/BaseEnemyApe.cpp b/dScripts/BaseEnemyApe.cpp index 88c846f6..9aa391d1 100644 --- a/dScripts/BaseEnemyApe.cpp +++ b/dScripts/BaseEnemyApe.cpp @@ -8,11 +8,6 @@ void BaseEnemyApe::OnStartup(Entity *self) { self->SetVar(u"timesStunned", 2); self->SetVar(u"knockedOut", false); - - auto* combatAIComponent = self->GetComponent(); - if (combatAIComponent != nullptr) { - combatAIComponent->SetStunImmune(true); - } } void BaseEnemyApe::OnDie(Entity *self, Entity *killer) { @@ -23,10 +18,8 @@ void BaseEnemyApe::OnDie(Entity *self, Entity *killer) { } void BaseEnemyApe::OnSkillCast(Entity *self, uint32_t skillID) { - const auto groundPoundSkill = self->GetVar(u"GroundPoundSkill") != 0 - ? self->GetVar(u"GroundPoundSkill") : 725; - const auto spawnQuickBuildTime = self->GetVar(u"spawnQBTime") != 0.0f - ? self->GetVar(u"spawnQBTime") : 5.0f; + const auto groundPoundSkill = self->GetVar(u"GroundPoundSkill") != 0 ? self->GetVar(u"GroundPoundSkill") : 725; + const auto spawnQuickBuildTime = self->GetVar(u"spawnQBTime") != 0.0f ? self->GetVar(u"spawnQBTime") : 5.0f; if (skillID == groundPoundSkill && self->GetVar(u"QB") == LWOOBJID_EMPTY) { self->AddTimer("spawnQBTime", spawnQuickBuildTime); @@ -56,14 +49,12 @@ void BaseEnemyApe::OnTimerDone(Entity *self, std::string timerName) { if (destroyableComponent != nullptr) { destroyableComponent->SetArmor(destroyableComponent->GetMaxArmor() / timesStunned); } - + EntityManager::Instance()->SerializeEntity(self); self->SetVar(u"timesStunned", timesStunned + 1); StunApe(self, false); } else if (timerName == "spawnQBTime" && self->GetVar(u"QB") == LWOOBJID_EMPTY) { - - // Spawns the QB, which can insta kill the ape - // Quick mafs to spawn the QB in the correct spot + // Spawn QB in front of ape. const auto position = self->GetPosition(); const auto rotation = self->GetRotation(); @@ -107,8 +98,6 @@ void BaseEnemyApe::OnTimerDone(Entity *self, std::string timerName) { auto* skillComponent = self->GetComponent(); if (skillComponent != nullptr) { - // We use a different behavior than the script here, the original one contains a TargetCaster behavior - // but as of writing we can't pass an optional originated to give the loot to the player skillComponent->CalculateBehavior(1273, 29446, self->GetObjectID(), true, false, player->GetObjectID()); } @@ -120,8 +109,7 @@ void BaseEnemyApe::OnFireEventServerSide(Entity *self, Entity *sender, std::stri int32_t param3) { if (args == "rebuildDone" && sender != nullptr) { self->SetVar(u"smasher", sender->GetObjectID()); - const auto anchorDamageDelayTime = self->GetVar(u"AnchorDamageDelayTime") != 0.0f - ? self->GetVar(u"AnchorDamageDelayTime") : 0.5f; + const auto anchorDamageDelayTime = self->GetVar(u"AnchorDamageDelayTime") != 0.0f ? self->GetVar(u"AnchorDamageDelayTime") : 0.5f; self->AddTimer("anchorDamageTimer", anchorDamageDelayTime); } } diff --git a/dScripts/BaseSurvivalServer.cpp b/dScripts/BaseSurvivalServer.cpp index 1174f00b..dd9b9365 100644 --- a/dScripts/BaseSurvivalServer.cpp +++ b/dScripts/BaseSurvivalServer.cpp @@ -200,6 +200,7 @@ void BaseSurvivalServer::OnActivityTimerDone(Entity *self, const std::string &na ActivityTimerStop(self, SpawnTickTimer); ActivityTimerStart(self, CoolDownStopTimer, 1, constants.coolDownTime); + ActivateSpawnerNetwork(spawnerNetworks.rewardNetworks); SpawnerReset(spawnerNetworks.baseNetworks, false); SpawnerReset(spawnerNetworks.randomNetworks, false); } else if (name == CoolDownStopTimer) { @@ -302,7 +303,6 @@ void BaseSurvivalServer::StartWaves(Entity *self) { self->SetVar(FirstTimeDoneVariable, true); self->SetVar(MissionTypeVariable, state.players.size() == 1 ? "survival_time_solo" : "survival_time_team"); - ActivateSpawnerNetwork(spawnerNetworks.rewardNetworks); ActivateSpawnerNetwork(spawnerNetworks.smashNetworks); self->SetNetworkVar(WavesStartedVariable, true); self->SetNetworkVar(StartWaveMessageVariable, "Start!"); diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 1201af6d..9e402a21 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -1,4 +1,4 @@ -//I can feel my soul being torn apart with every script added to this monstrosity. +//I can feel my soul being torn apart with every script added to this monstrosity. // skate fast eat trash // do you think god stays in heaven because he too lives in fear of what he's created? @@ -145,7 +145,9 @@ #include "ImgBrickConsoleQB.h" #include "ActParadoxPipeFix.h" #include "FvNinjaGuard.h" +#include "FvPassThroughWall.h" #include "FvBounceOverWall.h" +#include "FvFong.h" // FB Scripts #include "AgJetEffectServer.h" @@ -161,6 +163,8 @@ #include "GrowingFlower.h" #include "BaseFootRaceManager.h" #include "PropertyPlatform.h" +#include "MailBoxServer.h" +#include "ActMine.h" // Racing Scripts #include "RaceImagineCrateServer.h" @@ -179,6 +183,7 @@ #include "NtVentureCannonServer.h" #include "NtCombatChallengeServer.h" #include "NtCombatChallengeDummy.h" +#include "NtCombatChallengeExplodingDummy.h" #include "BaseInteractDropLootServer.h" #include "NtAssemblyTubeServer.h" #include "NtParadoxPanelServer.h" @@ -199,7 +204,6 @@ #include "NtSleepingGuard.h" // DLU Scripts -#include "SbLupTeleport.h" #include "DLUVanityNPC.h" // AM Scripts @@ -221,8 +225,9 @@ #include "AmSkullkinDrill.h" #include "AmSkullkinDrillStand.h" #include "AmSkullkinTower.h" -#include "AmNamedDarklingDragon.h" +#include "AmDarklingDragon.h" #include "AmBlueX.h" +#include "AmTeapotServer.h" // NJ Scripts #include "NjGarmadonCelebration.h" @@ -271,6 +276,10 @@ #include "AgSurvivalMech.h" #include "AgSurvivalSpiderling.h" +// Frostburgh Scripts +#include "RockHydrantBroken.h" +#include "WhFans.h" + //Big bad global bc this is a namespace and not a class: InvalidScript* invalidToReturn = new InvalidScript(); std::map m_Scripts; @@ -557,8 +566,12 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr script = new ActParadoxPipeFix(); else if (scriptName == "scripts\\ai\\FV\\L_FV_NINJA_GUARDS.lua") script = new FvNinjaGuard(); + else if (scriptName == "scripts\\ai\\FV\\L_ACT_PASS_THROUGH_WALL.lua") + script = new FvPassThroughWall(); else if (scriptName == "scripts\\ai\\FV\\L_ACT_BOUNCE_OVER_WALL.lua") script = new FvBounceOverWall(); + else if (scriptName == "scripts\\02_server\\Map\\FV\\L_NPC_FONG.lua") + script = new FvFong(); //Misc: if (scriptName == "scripts\\02_server\\Map\\General\\L_EXPLODING_ASSET.lua") @@ -577,6 +590,10 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr script = new PropertyPlatform(); else if (scriptName == "scripts\\02_server\\Map\\VE\\L_VE_BRICKSAMPLE_SERVER.lua") return new VeBricksampleServer(); + else if (scriptName == "scripts\\02_server\\Map\\General\\L_MAIL_BOX_SERVER.lua") + script = new MailBoxServer(); + else if (scriptName == "scripts\\ai\\ACT\\L_ACT_MINE.lua") + script = new ActMine(); //Racing: else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\RACE_IMAGINE_CRATE_SERVER.lua") @@ -609,6 +626,8 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr script = new NtCombatChallengeServer(); else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_COMBAT_CHALLENGE_DUMMY.lua") script = new NtCombatChallengeDummy(); + else if (scriptName == "scripts\\02_server\\Map\\NT\\\\L_NT_COMBAT_EXPLODING_TARGET.lua") + script = new NtCombatChallengeExplodingDummy(); else if (scriptName == "scripts\\02_server\\Map\\General\\L_BASE_INTERACT_DROP_LOOT_SERVER.lua") script = new BaseInteractDropLootServer(); else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_ASSEMBLYTUBE_SERVER.lua") @@ -681,15 +700,16 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr script = new AmSkullkinDrillStand(); else if (scriptName == "scripts\\02_server\\Map\\AM\\L_SKULLKIN_TOWER.lua") script = new AmSkullkinTower(); - // This just makes them immune to stuns. TODO: Make seperate scripts else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_NAMED_DARKLING_DRAGON.lua") - script = new AmNamedDarklingDragon(); + script = new AmDarklingDragon(); else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_DRAGON.lua") - script = new AmNamedDarklingDragon(); + script = new AmDarklingDragon(); else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_APE.lua") script = new BaseEnemyApe(); else if (scriptName == "scripts\\02_server\\Map\\AM\\L_BLUE_X.lua") script = new AmBlueX(); + else if (scriptName == "scripts\\02_server\\Map\\AM\\L_TEAPOT_SERVER.lua") + script = new AmTeapotServer(); // Ninjago else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_GARMADON_CELEBRATION_SERVER.lua") @@ -752,8 +772,6 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr script = new NjNyaMissionitems(); //DLU: - else if (scriptName == "scripts\\02_server\\DLU\\L_SB_LUP_TELEPORT.lua") - script = new SbLupTeleport(); else if (scriptName == "scripts\\02_server\\DLU\\DLUVanityNPC.lua") script = new DLUVanityNPC(); @@ -787,6 +805,12 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr else if (scriptName == "scripts\\EquipmentScripts\\BuccaneerValiantShip.lua") script = new BuccaneerValiantShip(); + // FB + else if (scriptName == "scripts\\ai\\NS\\WH\\L_ROCKHYDRANT_BROKEN.lua") + script = new RockHydrantBroken(); + else if (scriptName == "scripts\\ai\\NS\\L_NS_WH_FANS.lua") + script = new WhFans(); + //Ignore these scripts: else if (scriptName == "scripts\\02_server\\Enemy\\General\\L_SUSPEND_LUA_AI.lua") script = invalidToReturn; diff --git a/dScripts/Darkitect.cpp b/dScripts/Darkitect.cpp new file mode 100644 index 00000000..c4e1e45d --- /dev/null +++ b/dScripts/Darkitect.cpp @@ -0,0 +1,35 @@ +#include "Darkitect.h" +#include "MissionComponent.h" +#include "DestroyableComponent.h" +#include "EntityManager.h" +#include "GameMessages.h" +#include "Character.h" + +void Darkitect::Reveal(Entity* self, Entity* player) +{ + const auto playerID = player->GetObjectID(); + + GameMessages::SendNotifyClientObject(self->GetObjectID(), u"reveal", 0, 0, playerID, "", player->GetSystemAddress()); + + self->AddCallbackTimer(20, [this, self, playerID]() { + auto* player = EntityManager::Instance()->GetEntity(playerID); + + if (!player) return; + + auto* destroyableComponent = player->GetComponent(); + auto* missionComponent = player->GetComponent(); + auto* character = player->GetCharacter(); + + if (destroyableComponent != nullptr && missionComponent != nullptr && character != nullptr) { + destroyableComponent->SetArmor(0); + destroyableComponent->SetHealth(1); + destroyableComponent->SetImagination(0); + + if (missionComponent->GetMissionState(1295) == MissionState::MISSION_STATE_ACTIVE) { + character->SetPlayerFlag(1911, true); + } + + EntityManager::Instance()->SerializeEntity(player); + } + }); +} diff --git a/dScripts/Darkitect.h b/dScripts/Darkitect.h new file mode 100644 index 00000000..f0d19648 --- /dev/null +++ b/dScripts/Darkitect.h @@ -0,0 +1,9 @@ +#pragma once + +class Entity; + +class Darkitect +{ +public: + void Reveal(Entity* self, Entity* player); +}; diff --git a/dScripts/FvFong.cpp b/dScripts/FvFong.cpp new file mode 100644 index 00000000..890bf0ff --- /dev/null +++ b/dScripts/FvFong.cpp @@ -0,0 +1,12 @@ +#include "FvFong.h" +#include "Darkitect.h" +#include "MissionComponent.h" + +void FvFong::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, MissionState missionState) +{ + if (missionID == 734 && missionState == MissionState::MISSION_STATE_READY_TO_COMPLETE) + { + Darkitect Baron; + Baron.Reveal(self, target); + } +} diff --git a/dScripts/FvFong.h b/dScripts/FvFong.h new file mode 100644 index 00000000..fc47b484 --- /dev/null +++ b/dScripts/FvFong.h @@ -0,0 +1,8 @@ +#pragma once +#include "CppScripts.h" + +class FvFong : public CppScripts::Script +{ + public: + void OnMissionDialogueOK(Entity* self, Entity* target, int missionID, MissionState missionState) override; +}; diff --git a/dScripts/FvPassThroughWall.cpp b/dScripts/FvPassThroughWall.cpp new file mode 100644 index 00000000..66130a9f --- /dev/null +++ b/dScripts/FvPassThroughWall.cpp @@ -0,0 +1,17 @@ +#include "FvPassThroughWall.h" +#include "InventoryComponent.h" + +void FvPassThroughWall::OnCollisionPhantom(Entity* self, Entity* target) { + auto missionComponent = target->GetComponent(); + if (missionComponent == nullptr) return; + + //Because at the moment we do not have an ItemComponent component, we check to make sure a Maelstrom-Infused hood is equipped. There are only three in the game right now. + auto inventoryComponent = target->GetComponent(); + // If no inventory component is found then abort. + if (inventoryComponent == nullptr) return; + // If no Maelstrom hoods are equipped then abort. + if (!inventoryComponent->IsEquipped(WhiteMaelstromHood) && !inventoryComponent->IsEquipped(BlackMaelstromHood) && !inventoryComponent->IsEquipped(RedMaelstromHood)) return; + + // Progress mission Friend of the Ninja since all prerequisites are met. + missionComponent->ForceProgress(friendOfTheNinjaMissionId, friendOfTheNinjaMissionUid, 1); +} \ No newline at end of file diff --git a/dScripts/FvPassThroughWall.h b/dScripts/FvPassThroughWall.h new file mode 100644 index 00000000..4dacc74e --- /dev/null +++ b/dScripts/FvPassThroughWall.h @@ -0,0 +1,34 @@ +#pragma once +#include "CppScripts.h" + +class FvPassThroughWall : public CppScripts::Script +{ + /** + * @brief This method is called when there is a collision with self from target. + * + * @param self The Entity that called this method. + * @param target The Entity that self is targetting. + */ + void OnCollisionPhantom(Entity* self, Entity* target) override; +private: + /** + * Mission ID for Friend of the Ninjas. + */ + int32_t friendOfTheNinjaMissionId = 848; + /** + * Mission UID for Friend of the Ninjas. + */ + int32_t friendOfTheNinjaMissionUid = 1221; + /** + * Item LOT for Maelstrom-Infused White Ninja Hood + */ + int32_t WhiteMaelstromHood = 2641; + /** + * Item LOT for Maelstrom-Infused Black Ninja Hood + */ + int32_t BlackMaelstromHood = 2642; + /** + * Item LOT for Red Ninja Hood - Maelstrom Infused + */ + int32_t RedMaelstromHood = 1889; +}; \ No newline at end of file diff --git a/dScripts/FvRaceSmashEggImagineServer.cpp b/dScripts/FvRaceSmashEggImagineServer.cpp index 7d7dea08..8e0d0897 100644 --- a/dScripts/FvRaceSmashEggImagineServer.cpp +++ b/dScripts/FvRaceSmashEggImagineServer.cpp @@ -1,8 +1,9 @@ -#include "FvRaceSmashEggImagineServer.h" -#include "DestroyableComponent.h" #include "CharacterComponent.h" +#include "DestroyableComponent.h" #include "EntityManager.h" +#include "FvRaceSmashEggImagineServer.h" #include "PossessableComponent.h" +#include "RacingTaskParam.h" void FvRaceSmashEggImagineServer::OnDie(Entity *self, Entity *killer) { if (killer != nullptr) { @@ -12,18 +13,23 @@ void FvRaceSmashEggImagineServer::OnDie(Entity *self, Entity *killer) { EntityManager::Instance()->SerializeEntity(killer); } - // Crate is killed by the car + // get possessor to progress statistics and tasks. auto* possessableComponent = killer->GetComponent(); if (possessableComponent != nullptr) { auto* possessor = EntityManager::Instance()->GetEntity(possessableComponent->GetPossessor()); if (possessor != nullptr) { + auto* missionComponent = possessor->GetComponent(); auto* characterComponent = possessor->GetComponent(); if (characterComponent != nullptr) { characterComponent->UpdatePlayerStatistic(ImaginationPowerUpsCollected); characterComponent->UpdatePlayerStatistic(RacingSmashablesSmashed); } + if (missionComponent == nullptr) return; + // Dragon eggs have their own smash server so we handle mission progression for them here. + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, 0, (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_SMASHABLES); + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, self->GetLOT(), (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_SMASH_SPECIFIC_SMASHABLE); } } diff --git a/dScripts/GfCampfire.h b/dScripts/GfCampfire.h index 668e7c1d..9e678419 100644 --- a/dScripts/GfCampfire.h +++ b/dScripts/GfCampfire.h @@ -4,11 +4,11 @@ class GfCampfire : public CppScripts::Script { public: - void OnStartup(Entity* self); + void OnStartup(Entity* self) override; void OnFireEventServerSide(Entity *self, Entity *sender, std::string args, int32_t param1, int32_t param2, - int32_t param3); - void OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status); - void OnTimerDone(Entity* self, std::string timerName); + int32_t param3) override; + void OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) override; + void OnTimerDone(Entity* self, std::string timerName) override; void OnSkillEventFired(Entity *self, Entity *caster, const std::string &message) override; private: int32_t m_skillCastId = 43; diff --git a/dScripts/HydrantBroken.cpp b/dScripts/HydrantBroken.cpp index 93424325..2b620a21 100644 --- a/dScripts/HydrantBroken.cpp +++ b/dScripts/HydrantBroken.cpp @@ -2,7 +2,7 @@ #include "EntityManager.h" #include "GameMessages.h" -void HydrantBroken::OnStartup(Entity* self) +void HydrantBroken::OnStartup(Entity* self) { self->AddTimer("playEffect", 1); @@ -10,8 +10,6 @@ void HydrantBroken::OnStartup(Entity* self) const auto bouncers = EntityManager::Instance()->GetEntitiesInGroup(hydrant); - Game::logger->Log("HydrantBroken", "Broken Hydrant spawned (%s)\n", hydrant.c_str()); - for (auto* bouncer : bouncers) { self->SetVar(u"bouncer", bouncer->GetObjectID()); @@ -20,11 +18,11 @@ void HydrantBroken::OnStartup(Entity* self) GameMessages::SendNotifyObject(bouncer->GetObjectID(), self->GetObjectID(), u"enableCollision", UNASSIGNED_SYSTEM_ADDRESS); } - + self->AddTimer("KillBroken", 25); } -void HydrantBroken::OnTimerDone(Entity* self, std::string timerName) +void HydrantBroken::OnTimerDone(Entity* self, std::string timerName) { if (timerName == "KillBroken") { diff --git a/dScripts/HydrantSmashable.cpp b/dScripts/HydrantSmashable.cpp index e39f7a35..5cdf84c9 100644 --- a/dScripts/HydrantSmashable.cpp +++ b/dScripts/HydrantSmashable.cpp @@ -2,20 +2,18 @@ #include "EntityManager.h" #include "GeneralUtils.h" -void HydrantSmashable::OnDie(Entity* self, Entity* killer) +void HydrantSmashable::OnDie(Entity* self, Entity* killer) { const auto hydrantName = self->GetVar(u"hydrant"); LDFBaseData* data = new LDFData(u"hydrant", GeneralUtils::UTF16ToWTF8(hydrantName)); EntityInfo info {}; - info.lot = 7328; + info.lot = HYDRANT_BROKEN; info.pos = self->GetPosition(); info.rot = self->GetRotation(); info.settings = {data}; info.spawnerID = self->GetSpawnerID(); - - Game::logger->Log("HydrantBroken", "Hydrant spawned (%s)\n", data->GetString().c_str()); auto* hydrant = EntityManager::Instance()->CreateEntity(info); diff --git a/dScripts/HydrantSmashable.h b/dScripts/HydrantSmashable.h index c7e23073..90b0f3a6 100644 --- a/dScripts/HydrantSmashable.h +++ b/dScripts/HydrantSmashable.h @@ -1,8 +1,10 @@ #pragma once #include "CppScripts.h" -class HydrantSmashable : public CppScripts::Script +class HydrantSmashable : public CppScripts::Script { public: - void OnDie(Entity* self, Entity* killer) override; + void OnDie(Entity* self, Entity* killer) override; +private: + LOT HYDRANT_BROKEN = 7328; }; \ No newline at end of file diff --git a/dScripts/MailBoxServer.cpp b/dScripts/MailBoxServer.cpp new file mode 100644 index 00000000..3e4e9687 --- /dev/null +++ b/dScripts/MailBoxServer.cpp @@ -0,0 +1,12 @@ +#include "MailBoxServer.h" +#include "AMFFormat.h" +#include "GameMessages.h" + +void MailBoxServer::OnUse(Entity* self, Entity* user) { + AMFStringValue* value = new AMFStringValue(); + value->SetStringValue("Mail"); + AMFArrayValue args; + args.InsertValue("state", value); + GameMessages::SendUIMessageServerToSingleClient(user, user->GetSystemAddress(), "pushGameState", &args); + delete value; +} \ No newline at end of file diff --git a/dScripts/MailBoxServer.h b/dScripts/MailBoxServer.h new file mode 100644 index 00000000..9cd93e24 --- /dev/null +++ b/dScripts/MailBoxServer.h @@ -0,0 +1,15 @@ +#pragma once + +#include "CppScripts.h" + +class MailBoxServer : public CppScripts::Script { +public: + /** + * When a mailbox is interacted with, this method updates the player game state + * to be in a mailbox. + * + * @param self The object that owns this script. + * @param user The user that interacted with this Entity. + */ + void OnUse(Entity* self, Entity* user) override; +}; \ No newline at end of file diff --git a/dScripts/NPCAddRemoveItem.cpp b/dScripts/NPCAddRemoveItem.cpp index 7d36c1e1..58c46ac0 100644 --- a/dScripts/NPCAddRemoveItem.cpp +++ b/dScripts/NPCAddRemoveItem.cpp @@ -11,7 +11,7 @@ void NPCAddRemoveItem::OnMissionDialogueOK(Entity *self, Entity *target, int mis for (const auto& itemSetting : missionSetting.second) { for (const auto& lot : itemSetting.items) { if (itemSetting.add && (missionState == MissionState::MISSION_STATE_AVAILABLE || missionState == MissionState::MISSION_STATE_COMPLETE_AVAILABLE)) { - inventory->AddItem(lot, 1); + inventory->AddItem(lot, 1, eLootSourceType::LOOT_SOURCE_NONE); } else if (itemSetting.remove && (missionState == MissionState::MISSION_STATE_READY_TO_COMPLETE || missionState == MissionState::MISSION_STATE_COMPLETE_READY_TO_COMPLETE)) { inventory->RemoveItem(lot, 1); } diff --git a/dScripts/NjColeNPC.cpp b/dScripts/NjColeNPC.cpp index 54d38bda..1b889445 100644 --- a/dScripts/NjColeNPC.cpp +++ b/dScripts/NjColeNPC.cpp @@ -54,6 +54,6 @@ void NjColeNPC::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, return; } - inventoryComponent->AddItem(16644, 1); + inventoryComponent->AddItem(16644, 1, eLootSourceType::LOOT_SOURCE_NONE); } } diff --git a/dScripts/NjMonastryBossInstance.cpp b/dScripts/NjMonastryBossInstance.cpp index faf92edd..17c4b296 100644 --- a/dScripts/NjMonastryBossInstance.cpp +++ b/dScripts/NjMonastryBossInstance.cpp @@ -47,9 +47,8 @@ void NjMonastryBossInstance::OnStartup(Entity *self) { void NjMonastryBossInstance::OnPlayerLoaded(Entity *self, Entity *player) { ActivityTimerStop(self, WaitingForPlayersTimer); - // Join the player in the activity and charge for joining + // Join the player in the activity UpdatePlayer(self, player->GetObjectID()); - TakeActivityCost(self, player->GetObjectID()); // Buff the player auto* destroyableComponent = player->GetComponent(); @@ -59,23 +58,22 @@ void NjMonastryBossInstance::OnPlayerLoaded(Entity *self, Entity *player) { destroyableComponent->SetImagination((int32_t) destroyableComponent->GetMaxImagination()); } - // Track the player ID + // Add player ID to instance auto totalPlayersLoaded = self->GetVar>(TotalPlayersLoadedVariable); - if (totalPlayersLoaded.empty() || std::find(totalPlayersLoaded.begin(), totalPlayersLoaded.end(), player->GetObjectID()) != totalPlayersLoaded.end()) { - totalPlayersLoaded.push_back(player->GetObjectID()); - } + totalPlayersLoaded.push_back(player->GetObjectID()); // Properly position the player self->SetVar>(TotalPlayersLoadedVariable, totalPlayersLoaded); - TeleportPlayer(player, totalPlayersLoaded.size()); + // This was always spawning all players at position one before and other values cause players to be invisible. + TeleportPlayer(player, 1); // Large teams face a tougher challenge - if (totalPlayersLoaded.size() > 2) + if (totalPlayersLoaded.size() >= 3) self->SetVar(LargeTeamVariable, true); // Start the game if all players in the team have loaded auto* team = TeamManager::Instance()->GetTeam(player->GetObjectID()); - if (team == nullptr || totalPlayersLoaded.size() >= team->members.size()) { + if (team == nullptr || totalPlayersLoaded.size() == team->members.size()) { StartFight(self); return; } @@ -93,6 +91,7 @@ void NjMonastryBossInstance::OnPlayerLoaded(Entity *self, Entity *player) { void NjMonastryBossInstance::OnPlayerExit(Entity *self, Entity *player) { UpdatePlayer(self, player->GetObjectID(), true); + //TODO: Add functionality to dynamically turn off the large team variable when enough players leave. GameMessages::SendNotifyClientObject(self->GetObjectID(), u"PlayerLeft", 0, 0, player->GetObjectID(), "", UNASSIGNED_SYSTEM_ADDRESS); } diff --git a/dScripts/NjScrollChestServer.cpp b/dScripts/NjScrollChestServer.cpp index ba3fda7a..b72f93cf 100644 --- a/dScripts/NjScrollChestServer.cpp +++ b/dScripts/NjScrollChestServer.cpp @@ -12,6 +12,6 @@ void NjScrollChestServer::OnUse(Entity *self, Entity *user) { playerInventory->RemoveItem(keyLOT, 1); // Reward the player with the item set - playerInventory->AddItem(rewardItemLOT, 1); + playerInventory->AddItem(rewardItemLOT, 1, eLootSourceType::LOOT_SOURCE_NONE); } } diff --git a/dScripts/NpcCowboyServer.cpp b/dScripts/NpcCowboyServer.cpp index bfba3540..9223dcd4 100644 --- a/dScripts/NpcCowboyServer.cpp +++ b/dScripts/NpcCowboyServer.cpp @@ -23,10 +23,10 @@ void NpcCowboyServer::OnMissionDialogueOK(Entity* self, Entity* target, int miss { if (inventoryComponent->GetLotCount(14378) == 0) { - inventoryComponent->AddItem(14378, 1); + inventoryComponent->AddItem(14378, 1, eLootSourceType::LOOT_SOURCE_NONE); } } - else if (missionState == MissionState::MISSION_STATE_READY_TO_COMPLETE || missionState == MissionState::MISSION_STATE_READY_TO_COMPLETE) + else if (missionState == MissionState::MISSION_STATE_READY_TO_COMPLETE || missionState == MissionState::MISSION_STATE_COMPLETE_READY_TO_COMPLETE) { inventoryComponent->RemoveItem(14378, 1); } diff --git a/dScripts/NpcPirateServer.cpp b/dScripts/NpcPirateServer.cpp index 4a1db1f6..04c62b47 100644 --- a/dScripts/NpcPirateServer.cpp +++ b/dScripts/NpcPirateServer.cpp @@ -9,7 +9,7 @@ void NpcPirateServer::OnMissionDialogueOK(Entity *self, Entity *target, int miss // Add or remove the lucky shovel based on whether the mission was completed or started if ((missionState == MissionState::MISSION_STATE_AVAILABLE || missionState == MissionState::MISSION_STATE_COMPLETE_AVAILABLE) && luckyShovel == nullptr) { - inventory->AddItem(14591, 1); + inventory->AddItem(14591, 1, eLootSourceType::LOOT_SOURCE_NONE); } else if (missionState == MissionState::MISSION_STATE_READY_TO_COMPLETE || missionState == MissionState::MISSION_STATE_COMPLETE_READY_TO_COMPLETE) { inventory->RemoveItem(14591, 1); } diff --git a/dScripts/NpcWispServer.cpp b/dScripts/NpcWispServer.cpp index 2f94236d..03c8f071 100644 --- a/dScripts/NpcWispServer.cpp +++ b/dScripts/NpcWispServer.cpp @@ -20,7 +20,7 @@ void NpcWispServer::OnMissionDialogueOK(Entity* self, Entity* target, int missio // For the daily we add the maelstrom vacuum if the player doesn't have it yet if (missionID == 1883 && (missionState == MissionState::MISSION_STATE_AVAILABLE || missionState == MissionState::MISSION_STATE_COMPLETE_AVAILABLE) && maelstromVacuum == nullptr) { - inventory->AddItem(maelstromVacuumLot, 1); + inventory->AddItem(maelstromVacuumLot, 1, eLootSourceType::LOOT_SOURCE_NONE); } else if (missionState == MissionState::MISSION_STATE_READY_TO_COMPLETE || missionState == MissionState::MISSION_STATE_COMPLETE_READY_TO_COMPLETE) { inventory->RemoveItem(maelstromVacuumLot, 1); } diff --git a/dScripts/NsConcertInstrument.cpp b/dScripts/NsConcertInstrument.cpp index f0257c93..bfd35083 100644 --- a/dScripts/NsConcertInstrument.cpp +++ b/dScripts/NsConcertInstrument.cpp @@ -195,7 +195,7 @@ void NsConcertInstrument::EquipInstruments(Entity *self, Entity *player) { // Equip the left hand instrument const auto leftInstrumentLot = instrumentLotLeft.find(GetInstrumentLot(self))->second; if (leftInstrumentLot != LOT_NULL) { - inventory->AddItem(leftInstrumentLot, 1, TEMP_ITEMS, {}, LWOOBJID_EMPTY, false); + inventory->AddItem(leftInstrumentLot, 1, eLootSourceType::LOOT_SOURCE_NONE, TEMP_ITEMS, {}, LWOOBJID_EMPTY, false); auto* leftInstrument = inventory->FindItemByLot(leftInstrumentLot, TEMP_ITEMS); leftInstrument->Equip(); } @@ -203,7 +203,7 @@ void NsConcertInstrument::EquipInstruments(Entity *self, Entity *player) { // Equip the right hand instrument const auto rightInstrumentLot = instrumentLotRight.find(GetInstrumentLot(self))->second; if (rightInstrumentLot != LOT_NULL) { - inventory->AddItem(rightInstrumentLot, 1, TEMP_ITEMS, {}, LWOOBJID_EMPTY, false); + inventory->AddItem(rightInstrumentLot, 1, eLootSourceType::LOOT_SOURCE_NONE, TEMP_ITEMS, {}, LWOOBJID_EMPTY, false); auto* rightInstrument = inventory->FindItemByLot(rightInstrumentLot, TEMP_ITEMS); rightInstrument->Equip(); } diff --git a/dScripts/NsTokenConsoleServer.cpp b/dScripts/NsTokenConsoleServer.cpp index f6b022d0..0e9ac09e 100644 --- a/dScripts/NsTokenConsoleServer.cpp +++ b/dScripts/NsTokenConsoleServer.cpp @@ -47,24 +47,29 @@ void NsTokenConsoleServer::OnUse(Entity* self, Entity* user) { GameMessages::SendPlayNDAudioEmitter(self, UNASSIGNED_SYSTEM_ADDRESS, useSound); } + + // Player must be in faction to interact with this entity. + LOT tokenLOT = 0; if (character->GetPlayerFlag(46)) { - inventoryComponent->AddItem(8321, 5); + tokenLOT = 8321; } else if (character->GetPlayerFlag(47)) { - inventoryComponent->AddItem(8318, 5); + tokenLOT = 8318; } else if (character->GetPlayerFlag(48)) { - inventoryComponent->AddItem(8320, 5); + tokenLOT = 8320; } else if (character->GetPlayerFlag(49)) { - inventoryComponent->AddItem(8319, 5); + tokenLOT = 8319; } + inventoryComponent->AddItem(tokenLOT, 5, eLootSourceType::LOOT_SOURCE_NONE); + missionComponent->ForceProgressTaskType(863, 1, 1, false); GameMessages::SendTerminateInteraction(user->GetObjectID(), FROM_INTERACTION, self->GetObjectID()); diff --git a/dScripts/NtCombatChallengeExplodingDummy.cpp b/dScripts/NtCombatChallengeExplodingDummy.cpp new file mode 100644 index 00000000..dd61817d --- /dev/null +++ b/dScripts/NtCombatChallengeExplodingDummy.cpp @@ -0,0 +1,38 @@ +#include "NtCombatChallengeExplodingDummy.h" +#include "NtCombatChallengeDummy.h" +#include "EntityManager.h" +#include "SkillComponent.h" + +void NtCombatChallengeExplodingDummy::OnDie(Entity* self, Entity* killer) +{ + const auto challengeObjectID = self->GetVar(u"challengeObjectID"); + + auto* challengeObject = EntityManager::Instance()->GetEntity(challengeObjectID); + + if (challengeObject != nullptr) + { + for (CppScripts::Script* script : CppScripts::GetEntityScripts(challengeObject)) + { + script->OnDie(challengeObject, killer); + } + } +} + +void NtCombatChallengeExplodingDummy::OnHitOrHealResult(Entity* self, Entity* attacker, int32_t damage) { + const auto challengeObjectID = self->GetVar(u"challengeObjectID"); + + auto* challengeObject = EntityManager::Instance()->GetEntity(challengeObjectID); + + if (challengeObject != nullptr) + { + for (CppScripts::Script* script : CppScripts::GetEntityScripts(challengeObject)) + { + script->OnHitOrHealResult(challengeObject, attacker, damage); + } + } + auto skillComponent = self->GetComponent(); + if (skillComponent != nullptr) { + skillComponent->CalculateBehavior(1338, 30875, attacker->GetObjectID()); + } + self->Kill(attacker); +} \ No newline at end of file diff --git a/dScripts/NtCombatChallengeExplodingDummy.h b/dScripts/NtCombatChallengeExplodingDummy.h new file mode 100644 index 00000000..c1c5ef1c --- /dev/null +++ b/dScripts/NtCombatChallengeExplodingDummy.h @@ -0,0 +1,8 @@ +#pragma once +#include "CppScripts.h" + +class NtCombatChallengeExplodingDummy : public CppScripts::Script +{ + void OnDie(Entity* self, Entity* killer) override; + void OnHitOrHealResult(Entity* self, Entity* attacker, int32_t damage) override; +}; \ No newline at end of file diff --git a/dScripts/NtDarkitectRevealServer.cpp b/dScripts/NtDarkitectRevealServer.cpp index 2bfbb063..b8afa510 100644 --- a/dScripts/NtDarkitectRevealServer.cpp +++ b/dScripts/NtDarkitectRevealServer.cpp @@ -1,53 +1,16 @@ #include "NtDarkitectRevealServer.h" +#include "Darkitect.h" #include "MissionComponent.h" -#include "DestroyableComponent.h" -#include "EntityManager.h" -#include "GameMessages.h" -#include "Character.h" - -void NtDarkitectRevealServer::OnUse(Entity* self, Entity* user) +void NtDarkitectRevealServer::OnUse(Entity* self, Entity* user) { - Darkitect(self, user); + Darkitect Baron; + Baron.Reveal(self, user); - auto* missionComponent = user->GetComponent(); + auto* missionComponent = user->GetComponent(); - if (missionComponent != nullptr) - { - missionComponent->ForceProgressTaskType(1344, 1, 14293); - } -} - -void NtDarkitectRevealServer::Darkitect(Entity* self, Entity* player) -{ - const auto playerID = player->GetObjectID(); - - GameMessages::SendNotifyClientObject(self->GetObjectID(), u"reveal", 0, 0, playerID, "", player->GetSystemAddress()); - - self->AddCallbackTimer(20, [this, self, playerID]() { - auto* player = EntityManager::Instance()->GetEntity(playerID); - - if (player == nullptr) - { - return; - } - - auto* destroyableComponent = player->GetComponent(); - auto* missionComponent = player->GetComponent(); - auto* character = player->GetCharacter(); - - if (destroyableComponent != nullptr && missionComponent != nullptr && character != nullptr) - { - destroyableComponent->SetArmor(0); - destroyableComponent->SetHealth(1); - destroyableComponent->SetImagination(0); - - if (missionComponent->GetMissionState(1295) == MissionState::MISSION_STATE_ACTIVE) - { - character->SetPlayerFlag(1911, true); - } - - EntityManager::Instance()->SerializeEntity(player); - } - }); + if (missionComponent != nullptr) + { + missionComponent->ForceProgressTaskType(1344, 1, 14293); + } } diff --git a/dScripts/NtDarkitectRevealServer.h b/dScripts/NtDarkitectRevealServer.h index 60d89934..2b954671 100644 --- a/dScripts/NtDarkitectRevealServer.h +++ b/dScripts/NtDarkitectRevealServer.h @@ -4,6 +4,5 @@ class NtDarkitectRevealServer : public CppScripts::Script { public: - void OnUse(Entity* self, Entity* user) override; - void Darkitect(Entity* self, Entity* player); + void OnUse(Entity* self, Entity* user) override; }; diff --git a/dScripts/NtDukeServer.cpp b/dScripts/NtDukeServer.cpp index 0f56d6cc..fe196811 100644 --- a/dScripts/NtDukeServer.cpp +++ b/dScripts/NtDukeServer.cpp @@ -29,7 +29,7 @@ void NtDukeServer::OnMissionDialogueOK(Entity *self, Entity *target, int mission auto lotCount = inventoryComponent->GetLotCount(m_SwordLot); if ((state == MissionState::MISSION_STATE_AVAILABLE || state == MissionState::MISSION_STATE_ACTIVE) && lotCount < 1) { - inventoryComponent->AddItem(m_SwordLot, 1); + inventoryComponent->AddItem(m_SwordLot, 1, eLootSourceType::LOOT_SOURCE_NONE); } else if (state == MissionState::MISSION_STATE_READY_TO_COMPLETE) { inventoryComponent->RemoveItem(m_SwordLot, lotCount); } diff --git a/dScripts/NtParadoxPanelServer.cpp b/dScripts/NtParadoxPanelServer.cpp index c1c9dd3f..556002fe 100644 --- a/dScripts/NtParadoxPanelServer.cpp +++ b/dScripts/NtParadoxPanelServer.cpp @@ -38,10 +38,11 @@ void NtParadoxPanelServer::OnUse(Entity* self, Entity* user) GameMessages::SendPlayAnimation(player, u"rebuild-celebrate"); GameMessages::SendNotifyClientObject(self->GetObjectID(), u"SparkStop", 0, 0, player->GetObjectID(), "", player->GetSystemAddress()); - + GameMessages::SendSetStunned(player->GetObjectID(), eStunState::POP, player->GetSystemAddress(), LWOOBJID_EMPTY, false, false, true, false, true, true, false, false, true); self->SetVar(u"bActive", false); }); - + GameMessages::SendPlayAnimation(user, u"nexus-powerpanel", 6.0f); + GameMessages::SendSetStunned(user->GetObjectID(), eStunState::PUSH, user->GetSystemAddress(), LWOOBJID_EMPTY, false, false, true, false, true, true, false, false, true); return; } diff --git a/dScripts/PetDigServer.cpp b/dScripts/PetDigServer.cpp index 41dce8f2..a78c0881 100644 --- a/dScripts/PetDigServer.cpp +++ b/dScripts/PetDigServer.cpp @@ -39,7 +39,7 @@ const std::map PetDigServer::digInfoMap { {12192, DigInfo { 12192, -1, -1, true, false, false, false }}, }; -void PetDigServer::OnStartup(Entity* self) +void PetDigServer::OnStartup(Entity* self) { treasures.push_back(self->GetObjectID()); const auto digInfoIterator = digInfoMap.find(self->GetLOT()); @@ -64,7 +64,7 @@ void PetDigServer::OnStartup(Entity* self) } } -void PetDigServer::OnDie(Entity* self, Entity* killer) +void PetDigServer::OnDie(Entity* self, Entity* killer) { const auto iterator = std::find(treasures.begin(), treasures.end(), self->GetObjectID()); if (iterator != treasures.end()) @@ -91,7 +91,7 @@ void PetDigServer::OnDie(Entity* self, Entity* killer) PetDigServer::HandleBouncerDig(self, owner); } - PetDigServer::ProgressCanYouDigIt(owner); + PetDigServer::ProgressPetDigMissions(owner, self); self->SetNetworkVar(u"treasure_dug", true); // TODO: Reset other pets @@ -157,19 +157,36 @@ void PetDigServer::HandleBouncerDig(const Entity *self, const Entity *owner) { } /** - * Progresses the Can You Dig It mission if the player has never completed it yet + * Progresses the Can You Dig It mission and the Pet Excavator Achievement if the player has never completed it yet * \param owner the owner that just made a pet dig something up */ -void PetDigServer::ProgressCanYouDigIt(const Entity* owner) { +void PetDigServer::ProgressPetDigMissions(const Entity* owner, const Entity* chest) { auto* missionComponent = owner->GetComponent(); if (missionComponent != nullptr) { + // Can You Dig It progress const auto digMissionState = missionComponent->GetMissionState(843); if (digMissionState == MissionState::MISSION_STATE_ACTIVE) { missionComponent->ForceProgress(843, 1216, 1); } + + // Pet Excavator progress + const auto excavatorMissionState = missionComponent->GetMissionState(505); + if (excavatorMissionState == MissionState::MISSION_STATE_ACTIVE) + { + if (chest->HasVar(u"PetDig")) { + int32_t playerFlag = 1260 + chest->GetVarAs(u"PetDig"); + Character* player = owner->GetCharacter(); + + // check if player flag is set + if (!player->GetPlayerFlag(playerFlag)) { + missionComponent->ForceProgress(505, 767, 1); + player->SetPlayerFlag(playerFlag, 1); + } + } + } } } @@ -203,7 +220,7 @@ void PetDigServer::SpawnPet(Entity* self, const Entity* owner, const DigInfo dig EntityManager::Instance()->ConstructEntity(spawnedPet); } -Entity* PetDigServer::GetClosestTresure(NiPoint3 position) +Entity* PetDigServer::GetClosestTresure(NiPoint3 position) { float closestDistance = 0; Entity* closest = nullptr; @@ -215,7 +232,7 @@ Entity* PetDigServer::GetClosestTresure(NiPoint3 position) if (tresure == nullptr) continue; float distance = Vector3::DistanceSquared(tresure->GetPosition(), position); - + if (closest == nullptr || distance < closestDistance) { closestDistance = distance; diff --git a/dScripts/PetDigServer.h b/dScripts/PetDigServer.h index 22a96a46..968ca50d 100644 --- a/dScripts/PetDigServer.h +++ b/dScripts/PetDigServer.h @@ -20,7 +20,7 @@ public: static Entity* GetClosestTresure(NiPoint3 position); private: - static void ProgressCanYouDigIt(const Entity* owner); + static void ProgressPetDigMissions(const Entity* owner, const Entity* chest); static void SpawnPet(Entity* self, const Entity* owner, DigInfo digInfo); static void HandleXBuildDig(const Entity* self, Entity* owner, Entity* pet); static void HandleBouncerDig(const Entity* self, const Entity* owner); diff --git a/dScripts/RaceImagineCrateServer.cpp b/dScripts/RaceImagineCrateServer.cpp index f233d408..166b3363 100644 --- a/dScripts/RaceImagineCrateServer.cpp +++ b/dScripts/RaceImagineCrateServer.cpp @@ -1,10 +1,11 @@ -#include "RaceImagineCrateServer.h" -#include "SkillComponent.h" -#include "GameMessages.h" -#include "EntityManager.h" -#include "DestroyableComponent.h" #include "CharacterComponent.h" +#include "DestroyableComponent.h" +#include "EntityManager.h" +#include "GameMessages.h" #include "PossessableComponent.h" +#include "RaceImagineCrateServer.h" +#include "RacingTaskParam.h" +#include "SkillComponent.h" void RaceImagineCrateServer::OnDie(Entity* self, Entity* killer) { @@ -13,8 +14,6 @@ void RaceImagineCrateServer::OnDie(Entity* self, Entity* killer) return; } - //GameMessages::SendPlayFXEffect(self, -1, u"pickup", "", LWOOBJID_EMPTY, 1, 1, true); - self->SetVar(u"bIsDead", true); if (killer == nullptr) @@ -38,21 +37,24 @@ void RaceImagineCrateServer::OnDie(Entity* self, Entity* killer) EntityManager::Instance()->SerializeEntity(killer); } - // Crate is killed by the car + // Find possessor of race car to progress missions and update stats. auto* possessableComponent = killer->GetComponent(); if (possessableComponent != nullptr) { auto* possessor = EntityManager::Instance()->GetEntity(possessableComponent->GetPossessor()); if (possessor != nullptr) { + auto* missionComponent = possessor->GetComponent(); auto* characterComponent = possessor->GetComponent(); + if (characterComponent != nullptr) { characterComponent->UpdatePlayerStatistic(RacingImaginationCratesSmashed); characterComponent->UpdatePlayerStatistic(RacingSmashablesSmashed); } + + // Progress racing smashable missions + if(missionComponent == nullptr) return; + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, 0, (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_SMASHABLES); } } - - - //skillComponent->CalculateBehavior(586, 9450, killer->GetObjectID(), true); } diff --git a/dScripts/RaceImagineCrateServer.h b/dScripts/RaceImagineCrateServer.h index 9e8ffa5a..42f36b30 100644 --- a/dScripts/RaceImagineCrateServer.h +++ b/dScripts/RaceImagineCrateServer.h @@ -4,5 +4,11 @@ class RaceImagineCrateServer : public CppScripts::Script { public: +/** + * @brief When a boost smashable has been smashed, this function is called + * + * @param self The Entity that called this function. + * @param killer The Entity that killed this Entity. + */ void OnDie(Entity* self, Entity* killer) override; }; diff --git a/dScripts/RaceImaginePowerup.cpp b/dScripts/RaceImaginePowerup.cpp index 91354acf..43e20e9d 100644 --- a/dScripts/RaceImaginePowerup.cpp +++ b/dScripts/RaceImaginePowerup.cpp @@ -1,9 +1,10 @@ -#include "RaceImaginePowerup.h" -#include "DestroyableComponent.h" -#include "PossessorComponent.h" -#include "EntityManager.h" #include "CharacterComponent.h" +#include "DestroyableComponent.h" +#include "EntityManager.h" #include "PossessableComponent.h" +#include "PossessorComponent.h" +#include "RaceImaginePowerup.h" +#include "RacingTaskParam.h" void RaceImaginePowerup::OnFireEventServerSide(Entity *self, Entity *sender, std::string args, int32_t param1, @@ -36,9 +37,7 @@ void RaceImaginePowerup::OnFireEventServerSide(Entity *self, Entity *sender, std auto* missionComponent = sender->GetComponent(); - if (missionComponent != nullptr) - { - missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, self->GetLOT(), 12); - } + if (missionComponent == nullptr) return; + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, self->GetLOT(), (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_COLLECT_IMAGINATION); } } diff --git a/dScripts/RaceSmashServer.cpp b/dScripts/RaceSmashServer.cpp index 4fd09f19..582a8ed3 100644 --- a/dScripts/RaceSmashServer.cpp +++ b/dScripts/RaceSmashServer.cpp @@ -1,7 +1,8 @@ -#include "RaceSmashServer.h" #include "CharacterComponent.h" #include "EntityManager.h" #include "PossessableComponent.h" +#include "RaceSmashServer.h" +#include "RacingTaskParam.h" void RaceSmashServer::OnDie(Entity *self, Entity *killer) { // Crate is smashed by the car @@ -11,10 +12,18 @@ void RaceSmashServer::OnDie(Entity *self, Entity *killer) { auto* possessor = EntityManager::Instance()->GetEntity(possessableComponent->GetPossessor()); if (possessor != nullptr) { + auto* missionComponent = possessor->GetComponent(); auto* characterComponent = possessor->GetComponent(); + if (characterComponent != nullptr) { characterComponent->UpdatePlayerStatistic(RacingSmashablesSmashed); } + + // Progress racing smashable missions + if(missionComponent == nullptr) return; + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, 0, (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_SMASHABLES); + // Progress missions that ask us to smash a specific smashable. + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_RACING, self->GetLOT(), (LWOOBJID)RacingTaskParam::RACING_TASK_PARAM_SMASH_SPECIFIC_SMASHABLE); } } } diff --git a/dScripts/RaceSmashServer.h b/dScripts/RaceSmashServer.h index 750bc00a..7fa1441d 100644 --- a/dScripts/RaceSmashServer.h +++ b/dScripts/RaceSmashServer.h @@ -2,5 +2,11 @@ #include "CppScripts.h" class RaceSmashServer : public CppScripts::Script { + /** + * @brief When a smashable has been destroyed, this function is called. + * + * @param self The Entity that called this function. + * @param killer The Entity that killed this Entity. + */ void OnDie(Entity *self, Entity *killer) override; }; diff --git a/dScripts/RockHydrantBroken.cpp b/dScripts/RockHydrantBroken.cpp new file mode 100644 index 00000000..50e3c88d --- /dev/null +++ b/dScripts/RockHydrantBroken.cpp @@ -0,0 +1,45 @@ +#include "RockHydrantBroken.h" +#include "EntityManager.h" +#include "GameMessages.h" + +void RockHydrantBroken::OnStartup(Entity* self) +{ + self->AddTimer("playEffect", 1); + + const auto hydrant = "hydrant" + self->GetVar(u"hydrant"); + + const auto bouncers = EntityManager::Instance()->GetEntitiesInGroup(hydrant); + + for (auto* bouncer : bouncers) + { + self->SetVar(u"bouncer", bouncer->GetObjectID()); + + + GameMessages::SendBouncerActiveStatus(bouncer->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); + + GameMessages::SendNotifyObject(bouncer->GetObjectID(), self->GetObjectID(), u"enableCollision", UNASSIGNED_SYSTEM_ADDRESS); + } + + self->AddTimer("KillBroken", 10); +} + +void RockHydrantBroken::OnTimerDone(Entity* self, std::string timerName) +{ + if (timerName == "KillBroken") + { + auto* bouncer = EntityManager::Instance()->GetEntity(self->GetVar(u"bouncer")); + + if (bouncer != nullptr) + { + GameMessages::SendBouncerActiveStatus(bouncer->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); + + GameMessages::SendNotifyObject(bouncer->GetObjectID(), self->GetObjectID(), u"disableCollision", UNASSIGNED_SYSTEM_ADDRESS); + } + + self->Kill(); + } + else if (timerName == "playEffect") + { + GameMessages::SendPlayFXEffect(self->GetObjectID(), 4737, u"water", "water", LWOOBJID_EMPTY, 1, 1, true); + } +} diff --git a/dScripts/RockHydrantBroken.h b/dScripts/RockHydrantBroken.h new file mode 100644 index 00000000..3bea8341 --- /dev/null +++ b/dScripts/RockHydrantBroken.h @@ -0,0 +1,10 @@ +#pragma once +#include "CppScripts.h" + +class RockHydrantBroken : public CppScripts::Script +{ +public: + void OnStartup(Entity* self) override; + void OnTimerDone(Entity* self, std::string timerName) override; +}; + diff --git a/dScripts/RockHydrantSmashable.cpp b/dScripts/RockHydrantSmashable.cpp index a54f0b9d..8d5b5861 100644 --- a/dScripts/RockHydrantSmashable.cpp +++ b/dScripts/RockHydrantSmashable.cpp @@ -1,24 +1,21 @@ #include "RockHydrantSmashable.h" #include "EntityManager.h" -#include "SimplePhysicsComponent.h" -#include "Entity.h" -#include "GameMessages.h" -#include "Game.h" -#include "dLogger.h" +#include "GeneralUtils.h" -void RockHydrantSmashable::OnDie(Entity* self, Entity* killer) { - SimplePhysicsComponent* physics = self->GetComponent(); - NiPoint3 pos = physics->GetPosition(); +void RockHydrantSmashable::OnDie(Entity* self, Entity* killer) +{ + const auto hydrantName = self->GetVar(u"hydrant"); - EntityInfo info; - info.lot = 12293; - info.pos = pos; - info.spawner = nullptr; + LDFBaseData* data = new LDFData(u"hydrant", GeneralUtils::UTF16ToWTF8(hydrantName)); + + EntityInfo info {}; + info.lot = ROCK_HYDRANT_BROKEN; + info.pos = self->GetPosition(); + info.rot = self->GetRotation(); + info.settings = {data}; info.spawnerID = self->GetSpawnerID(); - info.spawnerNodeID = 0; - Entity* newEntity = EntityManager::Instance()->CreateEntity(info, nullptr); - if (newEntity) { - EntityManager::Instance()->ConstructEntity(newEntity); - } -} \ No newline at end of file + auto* hydrant = EntityManager::Instance()->CreateEntity(info); + + EntityManager::Instance()->ConstructEntity(hydrant); +} diff --git a/dScripts/RockHydrantSmashable.h b/dScripts/RockHydrantSmashable.h index 12c21fb0..0578cb2e 100644 --- a/dScripts/RockHydrantSmashable.h +++ b/dScripts/RockHydrantSmashable.h @@ -5,5 +5,7 @@ class RockHydrantSmashable : public CppScripts::Script { public: void OnDie(Entity* self, Entity* killer); +private: + LOT ROCK_HYDRANT_BROKEN = 12293; }; diff --git a/dScripts/SGCannon.cpp b/dScripts/SGCannon.cpp index a691443e..a5c2c843 100644 --- a/dScripts/SGCannon.cpp +++ b/dScripts/SGCannon.cpp @@ -61,8 +61,6 @@ void SGCannon::OnStartup(Entity *self) { void SGCannon::OnPlayerLoaded(Entity *self, Entity *player) { Game::logger->Log("SGCannon", "Player loaded\n"); self->SetVar(PlayerIDVariable, player->GetObjectID()); - /*GameMessages::SendSetStunned(player->GetObjectID(), PUSH, player->GetSystemAddress(), LWOOBJID_EMPTY, - true, true, true, true, true, true, true);*/ } void SGCannon::OnFireEventServerSide(Entity *self, Entity *sender, std::string args, int32_t param1, int32_t param2, @@ -561,44 +559,28 @@ void SGCannon::StopGame(Entity *self, bool cancel) { auto* missionComponent = player->GetComponent(); - if (self->GetVar(TotalScoreVariable) >= 25000) - { - // For some reason the client thinks this mission is not complete? - auto* mission = missionComponent->GetMission(229); - - if (mission != nullptr && !mission->IsComplete()) - { - mission->Complete(); - } - } - if (missionComponent != nullptr) { - missionComponent->Progress( - MissionTaskType::MISSION_TASK_TYPE_MINIGAME, - self->GetVar(TotalScoreVariable), - self->GetObjectID(), - "performact_score" - ); + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_MINIGAME, self->GetVar(TotalScoreVariable), self->GetObjectID(), "performact_score"); + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_MINIGAME, self->GetVar(MaxStreakVariable), self->GetObjectID(), "performact_streak"); + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_ACTIVITY, m_CannonLot, 0, "", self->GetVar(TotalScoreVariable)); } LootGenerator::Instance().GiveActivityLoot(player, self, GetGameID(self), self->GetVar(TotalScoreVariable)); - StopActivity(self, player->GetObjectID(), self->GetVar(TotalScoreVariable), - self->GetVar(MaxStreakVariable), percentage); + StopActivity(self, player->GetObjectID(), self->GetVar(TotalScoreVariable), self->GetVar(MaxStreakVariable), percentage); self->SetNetworkVar(AudioFinalWaveDoneVariable, true); // Give the player the model rewards they earned auto* inventory = player->GetComponent(); if (inventory != nullptr) { for (const auto rewardLot : self->GetVar>(RewardsVariable)) { - inventory->AddItem(rewardLot, 1, eInventoryType::MODELS); + inventory->AddItem(rewardLot, 1, eLootSourceType::LOOT_SOURCE_ACTIVITY, eInventoryType::MODELS); } } self->SetNetworkVar(u"UI_Rewards", GeneralUtils::to_u16string(self->GetVar(TotalScoreVariable)) + u"_0_0_0_0_0_0" ); - self->SetVar(TotalScoreVariable, 0); GameMessages::SendRequestActivitySummaryLeaderboardData( player->GetObjectID(), @@ -610,9 +592,6 @@ void SGCannon::StopGame(Entity *self, bool cancel) { 0, false ); - - // The end menu is not in, just send them back to the main world - //static_cast(player)->SendToZone(1300); } GameMessages::SendActivityStop(self->GetObjectID(), false, cancel, player->GetSystemAddress()); @@ -645,10 +624,6 @@ void SGCannon::RegisterHit(Entity* self, Entity* target, const std::string& time if (!self->GetVar(SuperChargeActiveVariable)) { self->SetVar(u"m_curStreak", self->GetVar(u"m_curStreak") + 1); - - if (self->GetVar(u"m_curStreak") > 12) { - self->SetVar(u"m_curStreak", 12); - } } } else { @@ -693,6 +668,14 @@ void SGCannon::RegisterHit(Entity* self, Entity* target, const std::string& time self->SetNetworkVar(u"updateScore", newScore); self->SetNetworkVar(u"beatHighScore", GeneralUtils::to_u16string(newScore)); + + auto* player = EntityManager::Instance()->GetEntity(self->GetVar(PlayerIDVariable)); + if (player == nullptr) return; + + auto missionComponent = player->GetComponent(); + if (missionComponent == nullptr) return; + + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_SMASH, spawnInfo.lot, self->GetObjectID()); } void SGCannon::UpdateStreak(Entity* self) @@ -722,6 +705,8 @@ void SGCannon::UpdateStreak(Entity* self) self->SetNetworkVar(u"UnMarkAll", true); } } + auto maxStreak = self->GetVar(MaxStreakVariable); + if (maxStreak < curStreak) self->SetVar(MaxStreakVariable, curStreak); } float_t SGCannon::GetCurrentBonus(Entity* self) diff --git a/dScripts/SbLupTeleport.cpp b/dScripts/SbLupTeleport.cpp deleted file mode 100644 index 2ea9c1fc..00000000 --- a/dScripts/SbLupTeleport.cpp +++ /dev/null @@ -1,154 +0,0 @@ -#include "SbLupTeleport.h" -#include "dZoneManager.h" -#include "EntityManager.h" -#include "GeneralUtils.h" -#include "GameMessages.h" - -void SbLupTeleport::OnStartup(Entity* self) -{ - self->SetVar(u"currentZone", (int32_t) dZoneManager::Instance()->GetZoneID().GetMapID()); - self->SetVar(u"choiceZone", m_ChoiceZoneID); - self->SetVar(u"teleportAnim", m_TeleportAnim); - self->SetVar(u"teleportString", m_TeleportString); - self->SetVar(u"spawnPoint", m_SpawnPoint); - - args = {}; - - AMFStringValue* callbackClient = new AMFStringValue(); - callbackClient->SetStringValue(std::to_string(self->GetObjectID())); - args.InsertValue("callbackClient", callbackClient); - - AMFStringValue* strIdentifier = new AMFStringValue(); - strIdentifier->SetStringValue("choiceDoor"); - args.InsertValue("strIdentifier", strIdentifier); - - AMFStringValue* title = new AMFStringValue(); - title->SetStringValue("%[LUP_Starbase3001_Launchpad]"); - args.InsertValue("title", title); - - AMFArrayValue* choiceOptions = new AMFArrayValue(); - - { - AMFArrayValue* nsArgs = new AMFArrayValue(); - - AMFStringValue* image = new AMFStringValue(); - image->SetStringValue("textures/ui/zone_thumnails/Deep_Freeze.dds"); - nsArgs->InsertValue("image", image); - - AMFStringValue* caption = new AMFStringValue(); - caption->SetStringValue("%[ZoneTable_1601_DisplayDescription]"); - nsArgs->InsertValue("caption", caption); - - AMFStringValue* identifier = new AMFStringValue(); - identifier->SetStringValue("zoneID_1601"); - nsArgs->InsertValue("identifier", identifier); - - AMFStringValue* tooltipText = new AMFStringValue(); - tooltipText->SetStringValue("%[ZoneTable_1601_summary]"); - nsArgs->InsertValue("tooltipText", tooltipText); - - choiceOptions->PushBackValue(nsArgs); - } - - { - AMFArrayValue* ntArgs = new AMFArrayValue(); - - AMFStringValue* image = new AMFStringValue(); - image->SetStringValue("textures/ui/zone_thumnails/Robot_City.dds"); - ntArgs->InsertValue("image", image); - - AMFStringValue* caption = new AMFStringValue(); - caption->SetStringValue("%[ZoneTable_1602_DisplayDescription]"); - ntArgs->InsertValue("caption", caption); - - AMFStringValue* identifier = new AMFStringValue(); - identifier->SetStringValue("zoneID_1602"); - ntArgs->InsertValue("identifier", identifier); - - AMFStringValue* tooltipText = new AMFStringValue(); - tooltipText->SetStringValue("%[ZoneTable_1602_summary]"); - ntArgs->InsertValue("tooltipText", tooltipText); - - choiceOptions->PushBackValue(ntArgs); - } - - { - AMFArrayValue* ntArgs = new AMFArrayValue(); - - AMFStringValue* image = new AMFStringValue(); - image->SetStringValue("textures/ui/zone_thumnails/Moon_Base.dds"); - ntArgs->InsertValue("image", image); - - AMFStringValue* caption = new AMFStringValue(); - caption->SetStringValue("%[ZoneTable_1603_DisplayDescription]"); - ntArgs->InsertValue("caption", caption); - - AMFStringValue* identifier = new AMFStringValue(); - identifier->SetStringValue("zoneID_1603"); - ntArgs->InsertValue("identifier", identifier); - - AMFStringValue* tooltipText = new AMFStringValue(); - tooltipText->SetStringValue("%[ZoneTable_1603_summary]"); - ntArgs->InsertValue("tooltipText", tooltipText); - - choiceOptions->PushBackValue(ntArgs); - } - - { - AMFArrayValue* ntArgs = new AMFArrayValue(); - - AMFStringValue* image = new AMFStringValue(); - image->SetStringValue("textures/ui/zone_thumnails/Porto_Bello.dds"); - ntArgs->InsertValue("image", image); - - AMFStringValue* caption = new AMFStringValue(); - caption->SetStringValue("%[ZoneTable_1604_DisplayDescription]"); - ntArgs->InsertValue("caption", caption); - - AMFStringValue* identifier = new AMFStringValue(); - identifier->SetStringValue("zoneID_1604"); - ntArgs->InsertValue("identifier", identifier); - - AMFStringValue* tooltipText = new AMFStringValue(); - tooltipText->SetStringValue("%[ZoneTable_1604_summary]"); - ntArgs->InsertValue("tooltipText", tooltipText); - - choiceOptions->PushBackValue(ntArgs); - } - - args.InsertValue("options", choiceOptions); -} - -void SbLupTeleport::OnUse(Entity* self, Entity* user) -{ - auto* player = user; - - //if (CheckChoice(self, player)) - { - GameMessages::SendUIMessageServerToSingleClient(player, player->GetSystemAddress(), "QueueChoiceBox", &args); - } - /*else - { - BaseOnUse(self, player); - }*/ -} - -void SbLupTeleport::OnMessageBoxResponse(Entity* self, Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData) -{ - BaseOnMessageBoxResponse(self, sender, button, identifier, userData); -} - -void SbLupTeleport::OnChoiceBoxResponse(Entity* self, Entity* sender, int32_t button, const std::u16string& buttonIdentifier, const std::u16string& identifier) -{ - BaseChoiceBoxRespond(self, sender, button, buttonIdentifier, identifier); -} - -void SbLupTeleport::OnTimerDone(Entity* self, std::string timerName) -{ - BaseOnTimerDone(self, timerName); -} - -void SbLupTeleport::OnFireEventServerSide(Entity *self, Entity *sender, std::string args, int32_t param1, int32_t param2, int32_t param3) -{ - BaseOnFireEventServerSide(self, sender, args, param1, param2, param3); -} diff --git a/dScripts/SbLupTeleport.h b/dScripts/SbLupTeleport.h deleted file mode 100644 index f6c868d4..00000000 --- a/dScripts/SbLupTeleport.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "CppScripts.h" -#include "ChooseYourDestinationNsToNt.h" -#include "BaseConsoleTeleportServer.h" -#include "AMFFormat.h" - -class SbLupTeleport : public CppScripts::Script, ChooseYourDestinationNsToNt, BaseConsoleTeleportServer -{ -public: - void OnStartup(Entity* self) override; - void OnUse(Entity* self, Entity* user) override; - void OnMessageBoxResponse(Entity* self, Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData) override; - void OnChoiceBoxResponse(Entity* self, Entity* sender, int32_t button, const std::u16string& buttonIdentifier, const std::u16string& identifier) override; - void OnTimerDone(Entity* self, std::string timerName) override; - void OnFireEventServerSide(Entity *self, Entity *sender, std::string args, int32_t param1, int32_t param2, int32_t param3) override; - -private: - int32_t m_ChoiceZoneID = 1600; - std::string m_SpawnPoint = "NS_LW"; - std::u16string m_TeleportAnim = u"lup-teleport"; - std::u16string m_TeleportString = u"UI_TRAVEL_TO_LUP_STATION"; - AMFArrayValue args = {}; -}; diff --git a/dScripts/ScriptedPowerupSpawner.cpp b/dScripts/ScriptedPowerupSpawner.cpp index 5fc013c1..fe35dfed 100644 --- a/dScripts/ScriptedPowerupSpawner.cpp +++ b/dScripts/ScriptedPowerupSpawner.cpp @@ -15,6 +15,11 @@ void ScriptedPowerupSpawner::OnTimerDone(Entity *self, std::string message) { const auto itemLOT = self->GetVar(u"lootLOT"); + // Build drop table + std::unordered_map drops; + + drops.emplace(itemLOT, 1); + // Spawn the required number of powerups auto* owner = EntityManager::Instance()->GetEntity(self->GetSpawnerID()); if (owner != nullptr) { @@ -24,7 +29,7 @@ void ScriptedPowerupSpawner::OnTimerDone(Entity *self, std::string message) { renderComponent->PlayEffect(0, u"cast", "N_cast"); } - LootGenerator::Instance().DropLoot(owner, self, itemLOT, 0, 1); + LootGenerator::Instance().DropLoot(owner, self, drops, 0, 0); } // Increment the current cycle diff --git a/dScripts/TokenConsoleServer.cpp b/dScripts/TokenConsoleServer.cpp index dcefe2f2..58565869 100644 --- a/dScripts/TokenConsoleServer.cpp +++ b/dScripts/TokenConsoleServer.cpp @@ -21,15 +21,17 @@ void TokenConsoleServer::OnUse(Entity* self, Entity* user) { //figure out which faction the player belongs to: auto character = user->GetCharacter(); if (!character) return; - + // At this point the player has to be in a faction. + LOT tokenLOT = 0; if (character->GetPlayerFlag(ePlayerFlags::VENTURE_FACTION)) //venture - inv->AddItem(8321, tokensToGive); + tokenLOT = 8321; else if (character->GetPlayerFlag(ePlayerFlags::ASSEMBLY_FACTION)) //assembly - inv->AddItem(8318, tokensToGive); + tokenLOT = 8318; else if (character->GetPlayerFlag(ePlayerFlags::PARADOX_FACTION)) //paradox - inv->AddItem(8320, tokensToGive); + tokenLOT = 8320; else if (character->GetPlayerFlag(ePlayerFlags::SENTINEL_FACTION)) //sentinel - inv->AddItem(8319, tokensToGive); + tokenLOT = 8319; + inv->AddItem(tokenLOT, tokensToGive, eLootSourceType::LOOT_SOURCE_NONE); } GameMessages::SendTerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, self->GetObjectID()); diff --git a/dScripts/VeBricksampleServer.cpp b/dScripts/VeBricksampleServer.cpp index 42ccd460..f42cd9a4 100644 --- a/dScripts/VeBricksampleServer.cpp +++ b/dScripts/VeBricksampleServer.cpp @@ -10,7 +10,7 @@ void VeBricksampleServer::OnUse(Entity *self, Entity *user) { auto* inventoryComponent = user->GetComponent(); if (loot && inventoryComponent != nullptr && inventoryComponent->GetLotCount(loot) == 0) { - inventoryComponent->AddItem(loot, 1); + inventoryComponent->AddItem(loot, 1, eLootSourceType::LOOT_SOURCE_ACTIVITY); for (auto* brickEntity : EntityManager::Instance()->GetEntitiesInGroup("Bricks")) { GameMessages::SendNotifyClientObject(brickEntity->GetObjectID(), u"Pickedup"); diff --git a/dScripts/VeMissionConsole.cpp b/dScripts/VeMissionConsole.cpp index 4a506dac..f815ebdc 100644 --- a/dScripts/VeMissionConsole.cpp +++ b/dScripts/VeMissionConsole.cpp @@ -8,7 +8,7 @@ void VeMissionConsole::OnUse(Entity *self, Entity *user) { auto* inventoryComponent = user->GetComponent(); if (inventoryComponent != nullptr) { - inventoryComponent->AddItem(12547, 1); // Add the panel required for pickup + inventoryComponent->AddItem(12547, 1, eLootSourceType::LOOT_SOURCE_ACTIVITY); // Add the panel required for pickup } // The flag to set is 101 diff --git a/dScripts/WaveBossHammerling.cpp b/dScripts/WaveBossHammerling.cpp index cb161e94..6dce0ae3 100644 --- a/dScripts/WaveBossHammerling.cpp +++ b/dScripts/WaveBossHammerling.cpp @@ -19,7 +19,6 @@ void WaveBossHammerling::OnFireEventServerSide(Entity *self, Entity *sender, std auto* combatAIComponent = self->GetComponent(); if (combatAIComponent != nullptr) { combatAIComponent->SetDisabled(false); - combatAIComponent->SetStunImmune(false); } } } diff --git a/dScripts/WaveBossHorsemen.cpp b/dScripts/WaveBossHorsemen.cpp index 9f71867f..75bffa5b 100644 --- a/dScripts/WaveBossHorsemen.cpp +++ b/dScripts/WaveBossHorsemen.cpp @@ -20,7 +20,6 @@ WaveBossHorsemen::OnFireEventServerSide(Entity *self, Entity *sender, std::strin auto* combatAIComponent = self->GetComponent(); if (combatAIComponent != nullptr) { combatAIComponent->SetDisabled(false); - combatAIComponent->SetStunImmune(false); } } } diff --git a/dScripts/WhFans.cpp b/dScripts/WhFans.cpp new file mode 100644 index 00000000..3a594a4c --- /dev/null +++ b/dScripts/WhFans.cpp @@ -0,0 +1,69 @@ +#include "WhFans.h" + +#include "RenderComponent.h" + +void WhFans::OnStartup(Entity* self) { + self->SetVar(u"alive", true); + self->SetVar(u"on", false); + + ToggleFX(self, false); +} + +void WhFans::ToggleFX(Entity* self, bool hit) { + std::string fanGroup; + const auto& groups = self->GetGroups(); + if (!groups.empty()) { + fanGroup = groups[0]; + } else { + fanGroup = ""; + } + + std::vector fanVolumes = EntityManager::Instance()->GetEntitiesInGroup(fanGroup); + + auto* renderComponent = self->GetComponent(); + + if (renderComponent == nullptr) return; + + if (fanVolumes.size() == 0 || !self->GetVar(u"alive")) return; + + if (self->GetVar(u"on")) { + GameMessages::SendPlayAnimation(self, u"fan-off"); + + renderComponent->StopEffect("fanOn"); + self->SetVar(u"on", false); + + for (Entity* volume : fanVolumes) { + auto volumePhys = volume->GetComponent(); + if (!volumePhys) continue; + volumePhys->SetPhysicsEffectActive(false); + EntityManager::Instance()->SerializeEntity(volume); + } + } + else if (!self->GetVar(u"on") && self->GetVar(u"alive")) { + GameMessages::SendPlayAnimation(self, u"fan-on"); + + self->SetVar(u"on", true); + + for (Entity* volume : fanVolumes) { + auto volumePhys = volume->GetComponent(); + if (!volumePhys) continue; + volumePhys->SetPhysicsEffectActive(true); + EntityManager::Instance()->SerializeEntity(volume); + } + } +} + +void WhFans::OnFireEventServerSide(Entity *self, Entity *sender, std::string args, int32_t param1, int32_t param2, + int32_t param3) { + if (args.length() == 0 || !self->GetVar(u"alive")) return; + + if ((args == "turnOn" && self->GetVar(u"on")) || (args == "turnOff" && !self->GetVar(u"on"))) return; + ToggleFX(self, false); +} + +void WhFans::OnDie(Entity* self, Entity* killer) { + if (self->GetVar(u"on")) { + ToggleFX(self, true); + } + self->SetVar(u"alive", false); +} diff --git a/dScripts/WhFans.h b/dScripts/WhFans.h new file mode 100644 index 00000000..91aaa9d8 --- /dev/null +++ b/dScripts/WhFans.h @@ -0,0 +1,23 @@ +#pragma once +#include "CppScripts.h" +#include "GameMessages.h" +#include "EntityManager.h" +#include "PhantomPhysicsComponent.h" + +class WhFans : public CppScripts::Script +{ +public: + void OnStartup(Entity* self) override; + void OnDie(Entity* self, Entity* killer) override; + void OnFireEventServerSide( + Entity *self, + Entity *sender, + std::string args, + int32_t param1, + int32_t param2, + int32_t param3 + ) override; +private: + void ToggleFX(Entity* self, bool hit); +}; + diff --git a/dWorldServer/PerformanceManager.cpp b/dWorldServer/PerformanceManager.cpp index 7d459234..6809fec0 100644 --- a/dWorldServer/PerformanceManager.cpp +++ b/dWorldServer/PerformanceManager.cpp @@ -2,123 +2,99 @@ #include "UserManager.h" -#define HIGH 16 -#define MEDIUM 33 -#define LOW 66 +//Times are 1 / fps, in ms +#define HIGH 16 //60 fps +#define MEDIUM 33 //30 fps +#define LOW 66 //15 fps -#define SOCIAL { MEDIUM, LOW } -#define BATTLE { HIGH, MEDIUM } -#define BATTLE_INSTANCE { MEDIUM, LOW } -#define RACE { MEDIUM, LOW } -#define PROPERTY { LOW, LOW } +#define SOCIAL { LOW } +#define SOCIAL_HUB { MEDIUM } //Added to compensate for the large playercounts in NS and NT +#define BATTLE { HIGH } +#define BATTLE_INSTANCE { MEDIUM } +#define RACE { HIGH } +#define PROPERTY { LOW } PerformanceProfile PerformanceManager::m_CurrentProfile = SOCIAL; PerformanceProfile PerformanceManager::m_DefaultProfile = SOCIAL; -PerformanceProfile PerformanceManager::m_InactiveProfile = { LOW, LOW }; +PerformanceProfile PerformanceManager::m_InactiveProfile = { LOW }; -std::map PerformanceManager::m_Profiles = { - // VE - { 1000, SOCIAL }, +std::map PerformanceManager::m_Profiles = { + // VE + { 1000, SOCIAL }, - // AG - { 1100, BATTLE }, - { 1101, BATTLE_INSTANCE }, - { 1102, BATTLE_INSTANCE }, - { 1150, PROPERTY }, - { 1151, PROPERTY }, + // AG + { 1100, BATTLE }, + { 1101, BATTLE_INSTANCE }, + { 1102, BATTLE_INSTANCE }, + { 1150, PROPERTY }, + { 1151, PROPERTY }, - // NS - { 1200, SOCIAL }, - { 1201, SOCIAL }, - { 1203, RACE }, - { 1204, BATTLE_INSTANCE }, - { 1250, PROPERTY }, - { 1251, PROPERTY }, + // NS + { 1200, SOCIAL_HUB }, + { 1201, SOCIAL }, + { 1203, RACE }, + { 1204, BATTLE_INSTANCE }, + { 1250, PROPERTY }, + { 1251, PROPERTY }, - // GF - { 1300, BATTLE }, - { 1302, BATTLE_INSTANCE }, - { 1303, BATTLE_INSTANCE }, - { 1350, PROPERTY }, + // GF + { 1300, BATTLE }, + { 1302, BATTLE_INSTANCE }, + { 1303, BATTLE_INSTANCE }, + { 1350, PROPERTY }, - // FV - { 1400, BATTLE }, - { 1402, BATTLE_INSTANCE }, - { 1403, RACE }, - { 1450, PROPERTY }, + // FV + { 1400, BATTLE }, + { 1402, BATTLE_INSTANCE }, + { 1403, RACE }, + { 1450, PROPERTY }, - // LUP - { 1600, SOCIAL }, - { 1601, SOCIAL }, - { 1602, SOCIAL }, - { 1603, SOCIAL }, - { 1604, SOCIAL }, + // LUP + { 1600, SOCIAL }, + { 1601, SOCIAL }, + { 1602, SOCIAL }, + { 1603, SOCIAL }, + { 1604, SOCIAL }, - // LEGO Club - { 1700, SOCIAL }, + // LEGO Club + { 1700, SOCIAL }, - // AM - { 1800, BATTLE }, + // AM + { 1800, BATTLE }, - // NT - { 1900, SOCIAL }, + // NT + { 1900, SOCIAL_HUB }, - // NJ - { 2000, BATTLE }, - { 2001, BATTLE_INSTANCE }, + // NJ + { 2000, BATTLE }, + { 2001, BATTLE_INSTANCE }, }; -PerformanceManager::PerformanceManager() -{ +PerformanceManager::PerformanceManager() { } -PerformanceManager::~PerformanceManager() -{ +PerformanceManager::~PerformanceManager() { } -void PerformanceManager::SelectProfile(LWOMAPID mapID) -{ - const auto pair = m_Profiles.find(mapID); +void PerformanceManager::SelectProfile(LWOMAPID mapID) { + const auto pair = m_Profiles.find(mapID); - if (pair == m_Profiles.end()) - { - m_CurrentProfile = m_DefaultProfile; + if (pair == m_Profiles.end()) { + m_CurrentProfile = m_DefaultProfile; - return; - } + return; + } - m_CurrentProfile = pair->second; + m_CurrentProfile = pair->second; } -uint32_t PerformanceManager::GetServerFramerate() -{ - if (UserManager::Instance()->GetUserCount() == 0) - { - return m_InactiveProfile.serverFramerate; - } +uint32_t PerformanceManager::GetServerFramerate() { + if (UserManager::Instance()->GetUserCount() == 0) { + return m_InactiveProfile.serverFramerate; + } - return m_CurrentProfile.serverFramerate; -} - -uint32_t PerformanceManager::GetPhysicsFramerate() -{ - if (UserManager::Instance()->GetUserCount() == 0) - { - return m_InactiveProfile.physicsFramerate; - } - - return m_CurrentProfile.physicsFramerate; -} - -uint32_t PerformanceManager::GetPhysicsStepRate() -{ - if (UserManager::Instance()->GetUserCount() == 0) - { - return 10; // Row physics at a really low framerate if the server is empty - } - - return m_CurrentProfile.physicsFramerate / m_CurrentProfile.serverFramerate; -} + return m_CurrentProfile.serverFramerate; +} \ No newline at end of file diff --git a/dWorldServer/PerformanceManager.h b/dWorldServer/PerformanceManager.h index 2cd53718..b8a090e0 100644 --- a/dWorldServer/PerformanceManager.h +++ b/dWorldServer/PerformanceManager.h @@ -4,29 +4,24 @@ #include "dCommonVars.h" -struct PerformanceProfile -{ - uint32_t serverFramerate; - uint32_t physicsFramerate; +struct PerformanceProfile { + uint32_t serverFramerate; }; -class PerformanceManager -{ +class PerformanceManager { public: - ~PerformanceManager(); + ~PerformanceManager(); - static void SelectProfile(LWOMAPID mapID); + static void SelectProfile(LWOMAPID mapID); - static uint32_t GetServerFramerate(); - static uint32_t GetPhysicsFramerate(); - static uint32_t GetPhysicsStepRate(); + static uint32_t GetServerFramerate(); private: - PerformanceManager(); - - static PerformanceProfile m_CurrentProfile; - static PerformanceProfile m_DefaultProfile; - static PerformanceProfile m_InactiveProfile; - static std::map m_Profiles; + PerformanceManager(); + + static PerformanceProfile m_CurrentProfile; + static PerformanceProfile m_DefaultProfile; + static PerformanceProfile m_InactiveProfile; + static std::map m_Profiles; }; diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 89bf4cdf..4f1ec400 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -77,6 +77,9 @@ bool chatConnected = false; bool worldShutdownSequenceStarted = false; bool worldShutdownSequenceComplete = false; void WorldShutdownSequence(); +void WorldShutdownProcess(uint32_t zoneId); +void FinalizeShutdown(); +void SendShutdownMessageToMaster(); dLogger* SetupLogger(int zoneID, int instanceID); void HandlePacketChat(Packet* packet); @@ -99,11 +102,9 @@ int main(int argc, char** argv) { // Triggers the shutdown sequence at application exit std::atexit(WorldShutdownSequence); - - signal(SIGINT, [](int) - { - WorldShutdownSequence(); - }); + + signal(SIGINT, [](int){ WorldShutdownSequence(); }); + signal(SIGTERM, [](int){ WorldShutdownSequence(); }); int zoneID = 1000; int cloneID = 0; @@ -124,7 +125,7 @@ int main(int argc, char** argv) { //Create all the objects we need to run our service: Game::logger = SetupLogger(zoneID, instanceID); if (!Game::logger) return 0; - + Game::logger->SetLogToConsole(true); //We want this info to always be logged. Game::logger->Log("WorldServer", "Starting World server...\n"); Game::logger->Log("WorldServer", "Version: %i.%i\n", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR); @@ -150,7 +151,7 @@ int main(int argc, char** argv) { Game::logger->Log("WorldServer", "Error Code: %i\n", e.errorCode()); return -1; } - + CDClientManager::Instance()->Initialize(); //Connect to the MySQL Database @@ -196,7 +197,7 @@ int main(int argc, char** argv) { //Connect to the chat server: int chatPort = 1501; if (config.GetValue("chat_server_port") != "") chatPort = std::atoi(config.GetValue("chat_server_port").c_str()); - + auto chatSock = SocketDescriptor(uint16_t(ourPort + 2), 0); Game::chatServer = RakNetworkFactory::GetRakPeerInterface(); Game::chatServer->Startup(1, 30, &chatSock, 1); @@ -217,7 +218,7 @@ int main(int argc, char** argv) { int framesSinceLastUsersSave = 0; int framesSinceLastSQLPing = 0; int framesSinceLastUser = 0; - + const float maxPacketProcessingTime = 1.5f; //0.015f; const int maxPacketsToProcess = 1024; @@ -225,9 +226,7 @@ int main(int argc, char** argv) { int framesSinceMasterStatus = 0; int framesSinceShutdownSequence = 0; int currentFramerate = highFrameRate; - int physicsFramerate = highFrameRate; - int physicsStepRate = 0; - int physicsStepCount = 0; + int ghostingStepCount = 0; auto ghostingLastTime = std::chrono::high_resolution_clock::now(); @@ -250,7 +249,7 @@ int main(int argc, char** argv) { "res/CDClient.fdb", "res/cdclient.fdb", }; - + for (const auto& file : aliases) { fileStream.open(file, std::ios::binary | std::ios::in); if (fileStream.is_open()) { @@ -260,7 +259,7 @@ int main(int argc, char** argv) { const int bufferSize = 1024; MD5* md5 = new MD5(); - + char fileStreamBuffer[1024] = {}; while (!fileStream.eof()) { @@ -275,7 +274,7 @@ int main(int argc, char** argv) { md5->update(nullTerminateBuffer, 1); // null terminate the data md5->finalize(); databaseChecksum = md5->hexdigest(); - + delete md5; Game::logger->Log("WorldServer", "FDB Checksum calculated as: %s\n", databaseChecksum.c_str()); @@ -302,9 +301,6 @@ int main(int argc, char** argv) { { currentFramerate = PerformanceManager::GetServerFramerate(); } - - physicsFramerate = PerformanceManager::GetPhysicsFramerate(); - physicsStepRate = PerformanceManager::GetPhysicsStepRate(); //Warning if we ran slow if (deltaTime > currentFramerate) { @@ -315,7 +311,9 @@ int main(int argc, char** argv) { if (!Game::server->GetIsConnectedToMaster()) { framesSinceMasterDisconnect++; - if (framesSinceMasterDisconnect >= 30) { + int framesToWaitForMaster = ready ? 10 : 200; + if (framesSinceMasterDisconnect >= framesToWaitForMaster && !worldShutdownSequenceStarted) { + Game::logger->Log("WorldServer", "Game loop running but no connection to master for %d frames, shutting down\n", framesToWaitForMaster); worldShutdownSequenceStarted = true; } } @@ -338,10 +336,7 @@ int main(int argc, char** argv) { if (zoneID != 0 && deltaTime > 0.0f) { Metrics::StartMeasurement(MetricVariable::Physics); - if (physicsStepCount++ >= physicsStepRate) { - dpWorld::Instance().StepWorld(deltaTime); - physicsStepCount = 0; - } + dpWorld::Instance().StepWorld(deltaTime); Metrics::EndMeasurement(MetricVariable::Physics); Metrics::StartMeasurement(MetricVariable::UpdateEntities); @@ -365,7 +360,7 @@ int main(int argc, char** argv) { //Check for packets here: packet = Game::server->ReceiveFromMaster(); if (packet) { //We can get messages not handle-able by the dServer class, so handle them if we returned anything. - HandlePacket(packet); + HandlePacket(packet); Game::server->DeallocateMasterPacket(packet); } @@ -464,13 +459,13 @@ int main(int argc, char** argv) { t += std::chrono::milliseconds(currentFramerate); std::this_thread::sleep_until(t); - + Metrics::EndMeasurement(MetricVariable::Sleep); if (!ready && Game::server->GetIsConnectedToMaster()) { // Some delay is required here or else we crash the client? - + framesSinceMasterStatus++; if (framesSinceMasterStatus >= 200) @@ -483,80 +478,16 @@ int main(int argc, char** argv) { } } - if (worldShutdownSequenceStarted && !worldShutdownSequenceComplete) - { - if (framesSinceShutdownSequence == 0) { - - ChatPackets::SendSystemMessage(UNASSIGNED_SYSTEM_ADDRESS, u"Server shutting down...", true); - - for (auto i = 0; i < Game::server->GetReplicaManager()->GetParticipantCount(); ++i) - { - const auto& player = Game::server->GetReplicaManager()->GetParticipantAtIndex(i); - - auto* entity = Player::GetPlayer(player); - - if (entity != nullptr && entity->GetCharacter() != nullptr) - { - auto* skillComponent = entity->GetComponent(); - - if (skillComponent != nullptr) - { - skillComponent->Reset(); - } - - entity->GetCharacter()->SaveXMLToDatabase(); - } - } - - if (PropertyManagementComponent::Instance() != nullptr) { - ChatPackets::SendSystemMessage(UNASSIGNED_SYSTEM_ADDRESS, u"Property data saved...", true); - PropertyManagementComponent::Instance()->Save(); - } - - ChatPackets::SendSystemMessage(UNASSIGNED_SYSTEM_ADDRESS, u"Character data saved...", true); - } - - framesSinceShutdownSequence++; - - if (framesSinceShutdownSequence == 100) - { - while (Game::server->GetReplicaManager()->GetParticipantCount() > 0) - { - const auto& player = Game::server->GetReplicaManager()->GetParticipantAtIndex(0); - - Game::server->Disconnect(player, SERVER_DISCON_KICK); - } - - CBITSTREAM; - PacketUtils::WriteHeader(bitStream, MASTER, MSG_MASTER_SHUTDOWN_RESPONSE); - Game::server->SendToMaster(&bitStream); - } - - if (framesSinceShutdownSequence == 300) - { - break; - } + if (worldShutdownSequenceStarted && !worldShutdownSequenceComplete) { + WorldShutdownProcess(zoneID); + break; } Metrics::AddMeasurement(MetricVariable::CPUTime, (1e6 * (1000.0 * (std::clock() - metricCPUTimeStart))) / CLOCKS_PER_SEC); Metrics::EndMeasurement(MetricVariable::Frame); } - - //Delete our objects here: - if (Game::physicsWorld) Game::physicsWorld = nullptr; - if (Game::zoneManager) delete Game::zoneManager; - - Game::logger->Log("WorldServer", "Shutdown complete, zone (%i), instance (%i)\n", Game::server->GetZoneID(), instanceID); - - Metrics::Clear(); - Database::Destroy(); - delete Game::chatFilter; - delete Game::server; - delete Game::logger; - - worldShutdownSequenceComplete = true; - - exit(0); + FinalizeShutdown(); + return EXIT_SUCCESS; } dLogger * SetupLogger(int zoneID, int instanceID) { @@ -574,7 +505,7 @@ dLogger * SetupLogger(int zoneID, int instanceID) { void HandlePacketChat(Packet* packet) { if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { Game::logger->Log("WorldServer", "Lost our connection to chat, zone(%i), instance(%i)\n", Game::server->GetZoneID(), Game::server->GetInstanceID()); - + chatConnected = false; } @@ -648,7 +579,7 @@ void HandlePacketChat(Packet* packet) { inStream.Read(playerId); inStream.Read(playerId); inStream.Read(expire); - + auto* entity = EntityManager::Instance()->GetEntity(playerId); if (entity != nullptr) @@ -898,7 +829,7 @@ void HandlePacket(Packet* packet) { } if (packet->data[1] != WORLD) return; - + switch (packet->data[3]) { case MSG_WORLD_CLIENT_VALIDATION: { std::string username = PacketUtils::ReadString(0x08, packet, true); @@ -913,7 +844,7 @@ void HandlePacket(Packet* packet) { uint32_t gmLevel = 0; auto* stmt = Database::CreatePreppedStmt("SELECT gm_level FROM accounts WHERE name=? LIMIT 1;"); stmt->setString(1, username.c_str()); - + auto* res = stmt->executeQuery(); while (res->next()) { gmLevel = res->getInt(1); @@ -929,7 +860,7 @@ void HandlePacket(Packet* packet) { return; } } - + //Request the session info from Master: CBITSTREAM; PacketUtils::WriteHeader(bitStream, MASTER, MSG_MASTER_REQUEST_SESSION_KEY); @@ -941,7 +872,7 @@ void HandlePacket(Packet* packet) { info.sysAddr = SystemAddress(packet->systemAddress); info.hash = sessionKey; m_PendingUsers.insert(std::make_pair(username, info)); - + break; } @@ -954,7 +885,7 @@ void HandlePacket(Packet* packet) { } //This loops prevents users who aren't authenticated to double-request the char list, which - //would make the login screen freeze sometimes. + //would make the login screen freeze sometimes. if (m_PendingUsers.size() > 0) { for (auto it : m_PendingUsers) { if (it.second.sysAddr == packet->systemAddress) { @@ -969,18 +900,18 @@ void HandlePacket(Packet* packet) { case MSG_WORLD_CLIENT_GAME_MSG: { RakNet::BitStream bitStream(packet->data, packet->length, false); - + uint64_t header; LWOOBJID objectID; uint16_t messageID; - + bitStream.Read(header); bitStream.Read(objectID); bitStream.Read(messageID); - + RakNet::BitStream dataStream; bitStream.Read(dataStream, bitStream.GetNumberOfUnreadBits()); - + GameMessageHandler::HandleMessage(&dataStream, packet->systemAddress, objectID, GAME_MSG(messageID)); break; } @@ -993,7 +924,7 @@ void HandlePacket(Packet* packet) { case MSG_WORLD_CLIENT_LOGIN_REQUEST: { RakNet::BitStream inStream(packet->data, packet->length, false); uint64_t header = inStream.Read(header); - + LWOOBJID playerID = 0; inStream.Read(playerID); playerID = GeneralUtils::ClearBit(playerID, OBJECT_BIT_CHARACTER); @@ -1008,7 +939,7 @@ void HandlePacket(Packet* packet) { UserManager::Instance()->RequestCharacterList(packet->systemAddress); break; } - + case MSG_WORLD_CLIENT_CHARACTER_RENAME_REQUEST: { UserManager::Instance()->RenameCharacter(packet->systemAddress, packet); break; @@ -1019,32 +950,37 @@ void HandlePacket(Packet* packet) { User* user = UserManager::Instance()->GetUser(packet->systemAddress); if (user) { Character* c = user->GetLastUsedChar(); - if (c != nullptr) { + if (c != nullptr) { std::u16string username = GeneralUtils::ASCIIToUTF16(c->GetName()); - WorldPackets::SendCreateCharacter(packet->systemAddress, c->GetObjectID(), c->GetXMLData(), username, c->GetGMLevel()); - WorldPackets::SendServerState(packet->systemAddress); - Game::server->GetReplicaManager()->AddParticipant(packet->systemAddress); - + EntityInfo info {}; info.lot = 1; Entity* player = EntityManager::Instance()->CreateEntity(info, UserManager::Instance()->GetUser(packet->systemAddress)); + WorldPackets::SendCreateCharacter(packet->systemAddress, player, c->GetXMLData(), username, c->GetGMLevel()); + WorldPackets::SendServerState(packet->systemAddress); + const auto respawnPoint = player->GetCharacter()->GetRespawnPoint(dZoneManager::Instance()->GetZone()->GetWorldID()); - + EntityManager::Instance()->ConstructEntity(player, UNASSIGNED_SYSTEM_ADDRESS, true); - + if (respawnPoint != NiPoint3::ZERO) { GameMessages::SendPlayerReachedRespawnCheckpoint(player, respawnPoint, NiQuaternion::IDENTITY); } - - EntityManager::Instance()->ConstructAllEntities(packet->systemAddress); - player->GetComponent()->SetLastRocketConfig(u""); - + EntityManager::Instance()->ConstructAllEntities(packet->systemAddress); + + auto* characterComponent = player->GetComponent(); + if (characterComponent) { + player->GetComponent()->RocketUnEquip(player); + } + c->SetRetroactiveFlags(); + player->RetroactiveVaultSize(); + player->GetCharacter()->SetTargetScene(""); // Fix the destroyable component @@ -1079,7 +1015,7 @@ void HandlePacket(Packet* packet) { int templateId = result.getIntField(0); result.finalize(); - + auto* propertyLookup = Database::CreatePreppedStmt("SELECT * FROM properties WHERE template_id = ? AND clone_id = ?;"); propertyLookup->setInt(1, templateId); @@ -1104,7 +1040,7 @@ void HandlePacket(Packet* packet) { stmtL->setUInt(1, res->getUInt(1)); auto lxres = stmtL->executeQuery(); - + while (lxres->next()) { auto lxfml = lxres->getBlob(1); @@ -1156,7 +1092,7 @@ void HandlePacket(Packet* packet) { noBBB: // Tell the client it's done loading: - GameMessages::SendInvalidZoneTransferList(player, packet->systemAddress, u"https://forms.zohopublic.eu/virtualoffice204/form/DLUInGameSurvey/formperma/kpU-IL5v2-Wt41QcB5UFnYjzlLp-j2LEisF8e11PisU", u"", false, false); + GameMessages::SendInvalidZoneTransferList(player, packet->systemAddress, GeneralUtils::ASCIIToUTF16(Game::config->GetValue("source")), u"", false, false); GameMessages::SendServerDoneLoadingAllObjects(player, packet->systemAddress); //Send the player it's mail count: @@ -1176,9 +1112,9 @@ void HandlePacket(Packet* packet) { { bitStream.Write(playerName[i]); } - + //bitStream.Write(playerName); - + auto zone = dZoneManager::Instance()->GetZone()->GetZoneID(); bitStream.Write(zone.GetMapID()); bitStream.Write(zone.GetInstanceID()); @@ -1287,35 +1223,75 @@ void HandlePacket(Packet* packet) { } } -void WorldShutdownSequence() -{ - if (worldShutdownSequenceStarted || worldShutdownSequenceComplete) - { - return; - } +void WorldShutdownProcess(uint32_t zoneId) { + Game::logger->Log("WorldServer", "Saving map %i instance %i\n", zoneId, instanceID); + for (auto i = 0; i < Game::server->GetReplicaManager()->GetParticipantCount(); ++i) { + const auto& player = Game::server->GetReplicaManager()->GetParticipantAtIndex(i); - worldShutdownSequenceStarted = true; + auto* entity = Player::GetPlayer(player); + Game::logger->Log("WorldServer", "Saving data!\n"); + if (entity != nullptr && entity->GetCharacter() != nullptr) { + auto* skillComponent = entity->GetComponent(); - auto t = std::chrono::high_resolution_clock::now(); - auto ticks = 0; + if (skillComponent != nullptr) { + skillComponent->Reset(); + } + std::string message = "Saving character " + entity->GetCharacter()->GetName() + "...\n"; + Game::logger->Log("WorldServer", message); + entity->GetCharacter()->SaveXMLToDatabase(); + message = "Character data for " + entity->GetCharacter()->GetName() + " was saved!\n"; + Game::logger->Log("WorldServer", message); + } + } - Game::logger->Log("WorldServer", "Attempting to shutdown world, zone (%i), instance (%i), max 10 seconds...\n", Game::server->GetZoneID(), instanceID); + if (PropertyManagementComponent::Instance() != nullptr) { + Game::logger->Log("WorldServer", "Saving ALL property data for zone %i clone %i!\n", zoneId, PropertyManagementComponent::Instance()->GetCloneId()); + PropertyManagementComponent::Instance()->Save(); + Game::logger->Log("WorldServer", "ALL property data saved for zone %i clone %i!\n", zoneId, PropertyManagementComponent::Instance()->GetCloneId()); + } - while (true) - { - if (worldShutdownSequenceStarted) - { - break; - } + Game::logger->Log("WorldServer", "ALL DATA HAS BEEN SAVED FOR ZONE %i INSTANCE %i!\n", zoneId, instanceID); - t += std::chrono::milliseconds(highFrameRate); - std::this_thread::sleep_until(t); + while (Game::server->GetReplicaManager()->GetParticipantCount() > 0) { + const auto& player = Game::server->GetReplicaManager()->GetParticipantAtIndex(0); - ticks++; - - if (ticks == 600) - { - break; - } - } + Game::server->Disconnect(player, SERVER_DISCON_KICK); + } + SendShutdownMessageToMaster(); } + +void WorldShutdownSequence() { + if (worldShutdownSequenceStarted || worldShutdownSequenceComplete) { + return; + } + + worldShutdownSequenceStarted = true; + + Game::logger->Log("WorldServer", "Zone (%i) instance (%i) shutting down outside of main loop!\n", Game::server->GetZoneID(), instanceID); + WorldShutdownProcess(Game::server->GetZoneID()); + FinalizeShutdown(); +} + +void FinalizeShutdown() { + //Delete our objects here: + if (Game::physicsWorld) Game::physicsWorld = nullptr; + if (Game::zoneManager) delete Game::zoneManager; + + Game::logger->Log("WorldServer", "Shutdown complete, zone (%i), instance (%i)\n", Game::server->GetZoneID(), instanceID); + + Metrics::Clear(); + Database::Destroy("WorldServer"); + delete Game::chatFilter; + delete Game::server; + delete Game::logger; + + worldShutdownSequenceComplete = true; + + exit(EXIT_SUCCESS); +} + +void SendShutdownMessageToMaster() { + CBITSTREAM; + PacketUtils::WriteHeader(bitStream, MASTER, MSG_MASTER_SHUTDOWN_RESPONSE); + Game::server->SendToMaster(&bitStream); +} \ No newline at end of file diff --git a/dZoneManager/dZoneManager.cpp b/dZoneManager/dZoneManager.cpp index 70dcec90..12215378 100644 --- a/dZoneManager/dZoneManager.cpp +++ b/dZoneManager/dZoneManager.cpp @@ -26,22 +26,20 @@ void dZoneManager::Initialize(const LWOZONEID& zoneID) { LOT zoneControlTemplate = 2365; - auto query = CDClientDatabase::CreatePreppedStmt( - "SELECT zoneControlTemplate, ghostdistance_min, ghostdistance FROM ZoneTable WHERE zoneID = ?;"); - query.bind(1, (int) zoneID.GetMapID()); + CDZoneTableTable* zoneTable = CDClientManager::Instance()->GetTable("ZoneTable"); + if (zoneTable != nullptr){ + const CDZoneTable* zone = zoneTable->Query(zoneID.GetMapID()); - auto result = query.execQuery(); - - if (!result.eof()) { - zoneControlTemplate = result.getIntField("zoneControlTemplate", 2365); - const auto min = result.getIntField("ghostdistance_min", 100); - const auto max = result.getIntField("ghostdistance", 100); - EntityManager::Instance()->SetGhostDistanceMax(max + min); - EntityManager::Instance()->SetGhostDistanceMin(max); + if (zone != nullptr) { + zoneControlTemplate = zone->zoneControlTemplate != -1 ? zone->zoneControlTemplate : 2365; + const auto min = zone->ghostdistance_min != -1.0f ? zone->ghostdistance_min : 100; + const auto max = zone->ghostdistance != -1.0f ? zone->ghostdistance : 100; + EntityManager::Instance()->SetGhostDistanceMax(max + min); + EntityManager::Instance()->SetGhostDistanceMin(max); + m_PlayerLoseCoinsOnDeath = zone->PlayerLoseCoinsOnDeath; + } } - result.finalize(); - Game::logger->Log("dZoneManager", "Creating zone control object %i\n", zoneControlTemplate); // Create ZoneControl object @@ -121,6 +119,24 @@ LWOZONEID dZoneManager::GetZoneID() const return m_ZoneID; } +uint32_t dZoneManager::GetMaxLevel() { + if (m_MaxLevel == 0) { + auto tableData = CDClientDatabase::ExecuteQuery("SELECT LevelCap FROM WorldConfig WHERE WorldConfigID = 1 LIMIT 1;"); + m_MaxLevel = tableData.getIntField(0, -1); + tableData.finalize(); + } + return m_MaxLevel; +} + +int32_t dZoneManager::GetLevelCapCurrencyConversion() { + if (m_CurrencyConversionRate == 0) { + auto tableData = CDClientDatabase::ExecuteQuery("SELECT LevelCapCurrencyConversion FROM WorldConfig WHERE WorldConfigID = 1 LIMIT 1;"); + m_CurrencyConversionRate = tableData.getIntField(0, -1); + tableData.finalize(); + } + return m_CurrencyConversionRate; +} + void dZoneManager::Update(float deltaTime) { for (auto spawner : m_Spawners) { spawner.second->Update(deltaTime); diff --git a/dZoneManager/dZoneManager.h b/dZoneManager/dZoneManager.h index f6da56cd..3171c81f 100644 --- a/dZoneManager/dZoneManager.h +++ b/dZoneManager/dZoneManager.h @@ -33,6 +33,8 @@ public: void NotifyZone(const dZoneNotifier& notifier, const LWOOBJID& objectID); //Notifies the zone of a certain event or command. void AddSpawner(LWOOBJID id, Spawner* spawner); LWOZONEID GetZoneID() const; + uint32_t GetMaxLevel(); + int32_t GetLevelCapCurrencyConversion(); LWOOBJID MakeSpawner(SpawnerInfo info); Spawner* GetSpawner(LWOOBJID id); void RemoveSpawner(LWOOBJID id); @@ -40,11 +42,23 @@ public: std::vector GetSpawnersInGroup(std::string group); void Update(float deltaTime); Entity* GetZoneControlObject() { return m_ZoneControlObject; } + bool GetPlayerLoseCoinOnDeath() { return m_PlayerLoseCoinsOnDeath; } private: + /** + * The maximum level of the world. + */ + uint32_t m_MaxLevel = 0; + + /** + * The ratio of LEGO Score to currency when the character has hit the max level. + */ + int32_t m_CurrencyConversionRate = 0; + static dZoneManager* m_Address; //Singleton Zone* m_pZone; LWOZONEID m_ZoneID; + bool m_PlayerLoseCoinsOnDeath; //Do players drop coins in this zone when smashed std::map m_Spawners; Entity* m_ZoneControlObject; diff --git a/docker-compose.yml b/docker-compose.yml index db90bd21..4b997e53 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -45,8 +45,8 @@ services: - BUILD_THREADS=${BUILD_THREADS:-1} - BUILD_VERSION=${BUILD_VERSION:-171022} volumes: - - ${CLIENT_PATH:?missing_client_path}:/client - - shared_configs:/shared_configs + - ${CLIENT_PATH:?missing_client_path}:/client:ro + - shared_configs:/shared_configs:ro depends_on: - database ports: diff --git a/docs/Commands.md b/docs/Commands.md new file mode 100644 index 00000000..4d169c76 --- /dev/null +++ b/docs/Commands.md @@ -0,0 +1,140 @@ +# In-game commands + +Here is a summary of the commands available in-game. All commands are prefixed by `/` and typed in the in-game chat window. Some commands requires admin privileges. Operands within `<>` are required, operands within `()` are not. For the full list of in-game commands, please checkout [the source file](../dGame/dUtilities/SlashCommandHandler.cpp). + +## General Commands + +|Command|Usage|Description|Admin Level Requirement| +|--- |--- |--- |--- | +|credits|`/credits`|Displays the names of the people behind Darkflame Universe.|| +|die|`/die`|Smashes the player.|| +|info|`/info`|Displays server info to the user, including where to find the server's source code.|| +|instanceinfo|`/instanceinfo`|Displays in the chat the current zone, clone, and instance id.|| +|ping|`/ping (-l)`|Displays in chat your average ping. If the `-l` flag is used, the latest ping is displayed.|| +|pvp|`/pvp`|Toggle your PVP flag.|| +|resurrect|`/resurrect`|Resurrects the player.|| +|requestmailcount|`/requestmailcount`|Sends notification with number of unread messages in the player's mailbox.|| +|skip-ags|`/skip-ags`|Skips the Avant Gardens Survival minigame mission, "Impress the Sentinel Faction".|| +|skip-sg|`/skip-sg`|Skips the Shooting Gallery minigame mission, "Monarch of the Sea".|| +|who|`/who`|Displays in chat all players on the instance.|| + +## Moderation Commands + +|Command|Usage|Description|Admin Level Requirement| +|--- |--- |--- |--- | +|gmlevel|`/gmlevel `|Within the authorized range of levels for the current account, changes the character's game master level to the specified value. This is required to use certain commands. Aliases: `/setgmlevel`, `/makegm`.|| +|kick|`/kick `|Kicks the player off the server.|2| +|mailitem|`/mailitem `|Mails an item to the given player. The mailed item has predetermined content. The sender name is set to "Darkflame Universe." The title of the message is "Lost item." The body of the message is "This is a replacement item for one you lost."|3| +|ban|`/ban `|Bans a user from the server.|4| +|approveproperty|`/approveproperty`|Approves the property the player is currently visiting.|5| +|mute|`/mute (days) (hours)`|Mute player for the given amount of time. If no time is given, the mute is indefinite.|6| +|gmimmune|`/gmimmunve `|Sets the character's GMImmune state, where value can be one of "1", to make yourself immune to damage, or "0" to undo.|8| +|gminvis|`/gminvis`|Toggles invisibility for the character, though it's currently a bit buggy. Requires nonzero GM Level for the character, but the account must have a GM level of 8.|8| +|setname|`/setname `|Sets a temporary name for your player. The name resets when you log out.|8| +|title|`/title `|Temporarily appends your player's name with " - <title>". This resets when you log out.|8| + +## Server Operation Commands + +|Command|Usage|Description|Admin Level Requirement| +|--- |--- |--- |--- | +|announce|`/announce`|Sends a announcement. `/setanntitle` and `/setannmsg` must be called first to configure the announcement.|8| +|config-set|`/config-set <key> <value>`|Set configuration item.|8| +|config-get|`/config-get <key>`|Get current value of a configuration item.|8| +|kill|`/kill <username>`|Smashes the character whom the given user is playing.|8| +|metrics|`/metrics`|Prints some information about the server's performance.|8| +|setannmsg|`/setannmsg <title>`|Sets the message of an announcement.|8| +|setanntitle|`/setanntitle <title>`|Sets the title of an announcement.|8| +|shutdownuniverse|`/shutdownuniverse`|Sends a shutdown message to the master server. This will send an announcement to all players that the universe will shut down in 10 minutes.|9| + +## Development Commands + +These commands are primarily for development and testing. The usage of many of these commands relies on knowledge of the codebase and client SQLite database. + +|Command|Usage|Description|Admin Level Requirement| +|--- |--- |--- |--- | +|fix-stats|`/fix-stats`|Resets skills, buffs, and destroyables.|| +|join|`/join <password>`|Joins a private zone with given password.|| +|leave-zone|`/leave-zone`|If you are in an instanced zone, transfers you to the closest main world. For example, if you are in an instance of Avant Gardens Survival or the Spider Queen Battle, you are sent to Avant Gardens. If you are in the Battle of Nimbus Station, you are sent to Nimbus Station.|| +|setminifig|`/setminifig <body part> <minifig item id>`|Alters your player's minifig. Body part can be one of "Eyebrows", "Eyes", "HairColor", "HairStyle", "Pants", "LeftHand", "Mouth", "RightHand", "Shirt", or "Hands". Changing minifig parts could break the character so this command is limited to GMs.|1| +|testmap|`/testmap <zone> (force) (clone-id)`|Transfers you to the given zone by id and clone id. Add "force" to skip checking if the zone is accessible (this can softlock your character, though, if you e.g. try to teleport to Frostburgh).|1| +|reportproxphys|`/reportproxphys`|Prints to console the position and radius of proximity sensors.|6| +|spawnphysicsverts|`/spawnphysicsverts`|Spawns a 1x1 brick at all vertices of phantom physics objects.|6| +|teleport|`/teleport <x> (y) <z>`|Teleports you. If no Y is given, you are teleported to the height of the terrain or physics object at (x, z). Alias: `/tele`.|6| +|activatespawner|`/activatespawner <spawner name>`|Activates spawner by name.|8| +|addmission|`/addmission <mission id>`|Accepts the mission, adding it to your journal.|8| +|boost|`/boost`|Adds a passive boost action if you are in a vehicle.|8| +|buff|`/buff <id> <duration>`|Applies the buff with the given id for the given number of seconds.|8| +|buffme|`/buffme`|Sets health, armor, and imagination to 999.|8| +|buffmed|`/buffmed`|Sets health, armor, and imagination to 9.|8| +|clearflag|`/clearflag <flag id>`|Removes the given health or inventory flag from your player. Equivalent of calling `/setflag off <flag id>`.|8| +|completemission|`/completemission <mission id>`|Completes the mission, removing it from your journal.|8| +|createprivate|`/createprivate <zone id> <clone id> <password>`|Creates a private zone with password.|8| +|debugui|`/debugui`|Toggle Debug UI.|8| +|dismount|`/dismount`|Dismounts you from the vehicle.|8| +|force-save|`/force-save`|While saving to database usually happens on regular intervals and when you disconnect from the server, this command saves your player's data to the database.|8| +|freecam|`/freecam`|Toggles freecam mode.|8| +|freemoney|`/freemoney <coins>`|Gives coins.|8| +|getnavmeshheight|`/getnavmeshheight`|Displays the navmesh height at your current position.|8| +|giveuscore|`/giveuscore <uscore>`|Gives uscore.|8| +|gmadditem|`/gmadditem <id> (count)`|Adds the given item to your inventory by id.|8| +|inspect|`/inspect <component> (-m <waypoint> \| -a <animation> \| -s \| -p \| -f (faction) \| -t)`|Finds the closest entity with the given component or LDF variable (ignoring players and racing cars), printing its ID, distance from the player, and whether it is sleeping, as well as the the IDs of all components the entity has. See [Detailed `/inspect` Usage](#detailed-inspect-usage) below.|8| +|list-spawns|`/list-spawns`|Lists all the character spawn points in the zone. Additionally, this command will display the current scene that plays when the character lands in the next zone, if there is one.|8| +|locrow|`/locrow`|Prints the your current position and rotation information to the console.|8| +|lookup|`/lookup <query>`|Searches through the Objects table in the client SQLite database for items whose display name, name, or description contains the query.|8| +|playanimation|`/playanimation <id>`|Plays animation with given ID. Alias: `/playanim`.|8| +|playeffect|`/playeffect <effect id> <effect type> <effect name>`|Plays an effect.|8| +|playlvlfx|`/playlvlfx`|Plays the level up animation on your character.|8| +|playrebuildfx|`/playrebuildfx`|Plays the quickbuild animation on your character.|8| +|pos|`/pos`|Displays your current position in chat and in the console.|8| +|refillstats|`/refillstats`|Refills health, armor, and imagination to their maximum level.|8| +|reforge|`/reforge <base item id> <reforged item id>`|Reforges an item.|8| +|resetmission|`/resetmission <mission id>`|Sets the state of the mission to accepted but not yet started.|8| +|rot|`/rot`|Displays your current rotation in chat and in the console.|8| +|runmacro|`/runmacro <macro>`|Runs any command macro found in `./res/macros/`|8| +|setcontrolscheme|`/setcontrolscheme <scheme number>`|Sets the character control scheme to the specified number.|8| +|setcurrency|`/setcurrency <coins>`|Sets your coins.|8| +|setflag|`/setflag (value) <flag id>`|Sets the given inventory or health flag to the given value, where value can be one of "on" or "off". If no value is given, by default this adds the flag to your character (equivalent of calling `/setflag on <flag id>`).|8| +|setinventorysize|`/setinventorysize <size>`|Sets your inventory size to the given size. Alias: `/setinvsize`|8| +|setuistate|`/setuistate <ui state>`|Changes UI state.|8| +|spawn|`/spawn <id>`|Spawns an object at your location by id.|8| +|speedboost|`/speedboost <amount>`|Sets the speed multiplier to the given amount. `/speedboost 1.5` will set the speed multiplier to 1.5x the normal speed.|8| +|startcelebration|`/startcelebration <id>`|Starts a celebration effect on your character.|8| +|stopeffect|`/stopeffect <effect id>`|Stops the given effect.|8| +|toggle|`/toggle <ui state>`|Toggles UI state.|8| +|tpall|`/tpall`|Teleports all characters to your current position.|8| +|triggerspawner|`/triggerspawner <spawner name>`|Triggers spawner by name.|8| +|unlock-emote|`/unlock-emote <emote id>`|Unlocks for your character the emote of the given id.|8| +|crash|`/crash`|Crashes the server.|9| +|rollloot|`/rollloot <loot matrix index> <item id> <amount>`|Rolls loot matrix.|9| + +## Detailed `/inspect` Usage + +`/inspect <component> (-m <waypoint> | -a <animation> | -s | -p | -f (faction) | -t)` + +Finds the closest entity with the given component or LDF variable (ignoring players and racing cars), printing its ID, distance from the player, and whether it is sleeping, as well as the the IDs of all components the entity has. + +### `/inspect` Options + +* `-m`: If the entity has a moving platform component, sends it to the given waypoint, or stops the platform if `waypoint` is `-1`. +* `-a`: Plays the given animation on the entity. +* `-s`: Prints the entity's settings and spawner ID. +* `-p`: Prints the entity's position +* `-f`: If the entity has a destroyable component, prints whether the entity is smashable and its friendly and enemy faction IDs; if `faction` is specified, adds that faction to the entity. +* `-t`: If the entity has a phantom physics component, prints the effect type, direction, directional multiplier, and whether the effect is active; in any case, if the entity has a trigger, prints the trigger ID. + +## Game Master Levels + +There are 9 Game master levels + +|Level|Variable Name|Description| +|--- |--- |--- | +|0|GAME_MASTER_LEVEL_CIVILIAN|Normal player| +|1|GAME_MASTER_LEVEL_FORUM_MODERATOR|Forum moderator. No permissions on live servers.| +|2|GAME_MASTER_LEVEL_JUNIOR_MODERATOR|Can kick/mute and pull chat logs| +|3|GAME_MASTER_LEVEL_MODERATOR|Can return lost items| +|4|GAME_MASTER_LEVEL_SENIOR_MODERATOR|Can ban| +|5|GAME_MASTER_LEVEL_LEAD_MODERATOR|Can approve properties| +|6|GAME_MASTER_LEVEL_JUNIOR_DEVELOPER|Junior developer & future content team. Civilan on live.| +|7|GAME_MASTER_LEVEL_INACTIVE_DEVELOPER|Inactive developer, limited permissions.| +|8|GAME_MASTER_LEVEL_DEVELOPER|Active developer, full permissions on live.| +|9|GAME_MASTER_LEVEL_OPERATOR|Can shutdown server for restarts & updates.| diff --git a/migrations/cdserver/0_nt_footrace.sql b/migrations/cdserver/0_nt_footrace.sql index 2019f07d..fd37599e 100644 --- a/migrations/cdserver/0_nt_footrace.sql +++ b/migrations/cdserver/0_nt_footrace.sql @@ -1,6 +1,6 @@ BEGIN TRANSACTION; -UPDATE ComponentsRegistry SET component_id = 1901 WHERE id = 12916; +UPDATE ComponentsRegistry SET component_id = 1901 WHERE id = 12916 AND component_type = 39; INSERT INTO ActivityRewards (objectTemplate, ActivityRewardIndex, activityRating, LootMatrixIndex, CurrencyIndex, ChallengeRating, description) VALUES (1901, 166, -1, 598, 1, 4, 'NT Foot Race'); COMMIT; diff --git a/migrations/cdserver/3_plunger_gun_fix.sql b/migrations/cdserver/3_plunger_gun_fix.sql new file mode 100644 index 00000000..35654e8b --- /dev/null +++ b/migrations/cdserver/3_plunger_gun_fix.sql @@ -0,0 +1,2 @@ +-- File added April 9th, 2022 +UPDATE ItemComponent SET itemType = 5 where id = 7082; diff --git a/migrations/cdserver/4_nt_footrace_parrot.sql b/migrations/cdserver/4_nt_footrace_parrot.sql new file mode 100644 index 00000000..3d549815 --- /dev/null +++ b/migrations/cdserver/4_nt_footrace_parrot.sql @@ -0,0 +1,3 @@ +UPDATE ComponentsRegistry SET component_id = 12702 WHERE id = 12916 AND component_type = 2; +UPDATE ComponentsRegistry SET component_id = 6280 WHERE id = 12916 AND component_type = 3; +UPDATE ComponentsRegistry SET component_id = 1791 WHERE id = 12916 AND component_type = 7; diff --git a/migrations/dlu/0_initial.sql b/migrations/dlu/0_initial.sql index 16cb3e7e..522e556f 100644 --- a/migrations/dlu/0_initial.sql +++ b/migrations/dlu/0_initial.sql @@ -1,5 +1,4 @@ -DROP TABLE IF EXISTS accounts; -CREATE TABLE accounts ( +CREATE TABLE IF NOT EXISTS accounts ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(35) NOT NULL UNIQUE, password TEXT NOT NULL, @@ -11,8 +10,7 @@ CREATE TABLE accounts ( mute_expire BIGINT UNSIGNED NOT NULL DEFAULT 0 ); -DROP TABLE IF EXISTS charinfo; -CREATE TABLE charinfo ( +CREATE TABLE IF NOT EXISTS charinfo ( id BIGINT NOT NULL PRIMARY KEY, account_id INT NOT NULL REFERENCES accounts(id), name VARCHAR(35) NOT NULL, @@ -23,21 +21,18 @@ CREATE TABLE charinfo ( permission_map BIGINT UNSIGNED NOT NULL DEFAULT 0 ); -DROP TABLE IF EXISTS charxml; -CREATE TABLE charxml ( +CREATE TABLE IF NOT EXISTS charxml ( id BIGINT NOT NULL PRIMARY KEY REFERENCES charinfo(id), xml_data LONGTEXT NOT NULL ); -DROP TABLE IF EXISTS command_log; -CREATE TABLE command_log ( +CREATE TABLE IF NOT EXISTS command_log ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, character_id BIGINT NOT NULL REFERENCES charinfo(id), command VARCHAR(256) NOT NULL ); -DROP TABLE IF EXISTS friends; -CREATE TABLE friends ( +CREATE TABLE IF NOT EXISTS friends ( player_id BIGINT NOT NULL REFERENCES charinfo(id), friend_id BIGINT NOT NULL REFERENCES charinfo(id), best_friend BOOLEAN NOT NULL DEFAULT FALSE, @@ -45,8 +40,7 @@ CREATE TABLE friends ( PRIMARY KEY (player_id, friend_id) ); -DROP TABLE IF EXISTS leaderboard; -CREATE TABLE leaderboard ( +CREATE TABLE IF NOT EXISTS leaderboard ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, game_id INT UNSIGNED NOT NULL DEFAULT 0, last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(), @@ -55,8 +49,7 @@ CREATE TABLE leaderboard ( score BIGINT UNSIGNED NOT NULL DEFAULT 0 ); -DROP TABLE IF EXISTS mail; -CREATE TABLE mail ( +CREATE TABLE IF NOT EXISTS mail ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, sender_id INT NOT NULL DEFAULT 0, sender_name VARCHAR(35) NOT NULL DEFAULT '', @@ -72,20 +65,17 @@ CREATE TABLE mail ( was_read BOOLEAN NOT NULL DEFAULT FALSE ); -DROP TABLE IF EXISTS object_id_tracker; -CREATE TABLE object_id_tracker ( +CREATE TABLE IF NOT EXISTS object_id_tracker ( last_object_id BIGINT UNSIGNED NOT NULL DEFAULT 0 PRIMARY KEY ); -DROP TABLE IF EXISTS pet_names; -CREATE TABLE pet_names ( +CREATE TABLE IF NOT EXISTS pet_names ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, pet_name TEXT NOT NULL, approved INT UNSIGNED NOT NULL ); -DROP TABLE IF EXISTS play_keys; -CREATE TABLE play_keys ( +CREATE TABLE IF NOT EXISTS play_keys ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, key_string CHAR(19) NOT NULL UNIQUE, key_uses INT NOT NULL DEFAULT 1, @@ -93,8 +83,7 @@ CREATE TABLE play_keys ( active BOOLEAN NOT NULL DEFAULT TRUE ); -DROP TABLE IF EXISTS properties; -CREATE TABLE properties ( +CREATE TABLE IF NOT EXISTS properties ( id BIGINT NOT NULL PRIMARY KEY, owner_id BIGINT NOT NULL REFERENCES charinfo(id), template_id INT UNSIGNED NOT NULL, @@ -112,8 +101,7 @@ CREATE TABLE properties ( zone_id INT NOT NULL ); -DROP TABLE IF EXISTS ugc; -CREATE TABLE ugc ( +CREATE TABLE IF NOT EXISTS ugc ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, account_id INT NOT NULL REFERENCES accounts(id), character_id BIGINT NOT NULL REFERENCES charinfo(id), @@ -123,8 +111,7 @@ CREATE TABLE ugc ( filename TEXT NOT NULL DEFAULT ('') ); -DROP TABLE IF EXISTS properties_contents; -CREATE TABLE properties_contents ( +CREATE TABLE IF NOT EXISTS properties_contents ( id BIGINT NOT NULL PRIMARY KEY, property_id BIGINT NOT NULL REFERENCES properties(id), ugc_id INT NULL REFERENCES ugc(id), @@ -138,8 +125,7 @@ CREATE TABLE properties_contents ( rw FLOAT NOT NULL ); -DROP TABLE IF EXISTS activity_log; -CREATE TABLE activity_log ( +CREATE TABLE IF NOT EXISTS activity_log ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, character_id BIGINT NOT NULL REFERENCES charinfo(id), activity INT NOT NULL, @@ -147,8 +133,7 @@ CREATE TABLE activity_log ( map_id INT NOT NULL ); -DROP TABLE IF EXISTS bug_reports; -CREATE TABLE bug_reports ( +CREATE TABLE IF NOT EXISTS bug_reports ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, body TEXT NOT NULL, client_version TEXT NOT NULL, @@ -157,8 +142,7 @@ CREATE TABLE bug_reports ( submitted TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ); -DROP TABLE IF EXISTS servers; -CREATE TABLE servers ( +CREATE TABLE IF NOT EXISTS servers ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name TEXT NOT NULL, ip TEXT NOT NULL, diff --git a/migrations/dlu/1_unique_charinfo_names.sql b/migrations/dlu/1_unique_charinfo_names.sql new file mode 100644 index 00000000..cdf2537c --- /dev/null +++ b/migrations/dlu/1_unique_charinfo_names.sql @@ -0,0 +1 @@ +ALTER TABLE charinfo ADD UNIQUE (name); \ No newline at end of file diff --git a/migrations/dlu/2_reporter_id.sql b/migrations/dlu/2_reporter_id.sql new file mode 100644 index 00000000..26103342 --- /dev/null +++ b/migrations/dlu/2_reporter_id.sql @@ -0,0 +1 @@ +ALTER TABLE bug_reports ADD reporter_id INT NOT NULL DEFAULT 0; diff --git a/migrations/dlu/3_add_performance_cost.sql b/migrations/dlu/3_add_performance_cost.sql new file mode 100644 index 00000000..15da4470 --- /dev/null +++ b/migrations/dlu/3_add_performance_cost.sql @@ -0,0 +1 @@ +ALTER TABLE properties ADD COLUMN performance_cost DOUBLE(20, 15) DEFAULT 0.0; \ No newline at end of file