diff --git a/Dockerfile b/Dockerfile index efb82b42..9086cf17 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,7 @@ COPY --from=build /app/build/*Server /app/ # Necessary suplimentary files COPY --from=build /app/build/*.ini /app/configs/ -COPY --from=build /app/build/vanity/*.* /app/vanity/* +COPY --from=build /app/build/vanity/*.* /app/vanity/ COPY --from=build /app/build/navmeshes /app/navmeshes COPY --from=build /app/build/migrations /app/migrations COPY --from=build /app/build/*.dcf /app/ @@ -39,7 +39,7 @@ COPY --from=build /app/build/*.dcf /app/ # backup of config and vanity files to copy to the host incase # of a mount clobbering the copy from above COPY --from=build /app/build/*.ini /app/default-configs/ -COPY --from=build /app/build/vanity/*.* /app/default-vanity/* +COPY --from=build /app/build/vanity/*.* /app/default-vanity/ # needed as the container runs with the root user # and therefore sudo doesn't exist diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index 81b6ddef..d6e99230 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -179,6 +179,7 @@ int main(int argc, char** argv) { } void HandlePacket(Packet* packet) { + if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { LOG("A server has disconnected, erasing their connected players from the list."); } else if (packet->data[0] == ID_NEW_INCOMING_CONNECTION) { diff --git a/dCommon/Diagnostics.cpp b/dCommon/Diagnostics.cpp index 2ed27fef..9f129194 100644 --- a/dCommon/Diagnostics.cpp +++ b/dCommon/Diagnostics.cpp @@ -201,7 +201,7 @@ void OnTerminate() { } void MakeBacktrace() { - struct sigaction sigact; + struct sigaction sigact{}; sigact.sa_sigaction = CritErrHdlr; sigact.sa_flags = SA_RESTART | SA_SIGINFO; diff --git a/dCommon/LDFFormat.h b/dCommon/LDFFormat.h index 2cd9156c..054ddb42 100644 --- a/dCommon/LDFFormat.h +++ b/dCommon/LDFFormat.h @@ -31,22 +31,22 @@ public: virtual ~LDFBaseData() {} - virtual void WriteToPacket(RakNet::BitStream& packet) = 0; + virtual void WriteToPacket(RakNet::BitStream& packet) const = 0; - virtual const std::u16string& GetKey() = 0; + virtual const std::u16string& GetKey() const = 0; - virtual eLDFType GetValueType() = 0; + virtual eLDFType GetValueType() const = 0; /** Gets a string from the key/value pair * @param includeKey Whether or not to include the key in the data * @param includeTypeId Whether or not to include the type id in the data * @return The string representation of the data */ - virtual std::string GetString(bool includeKey = true, bool includeTypeId = true) = 0; + virtual std::string GetString(bool includeKey = true, bool includeTypeId = true) const = 0; - virtual std::string GetValueAsString() = 0; + virtual std::string GetValueAsString() const = 0; - virtual LDFBaseData* Copy() = 0; + virtual LDFBaseData* Copy() const = 0; /** * Given an input string, return the data as a LDF key. @@ -62,7 +62,7 @@ private: T value; //! Writes the key to the packet - void WriteKey(RakNet::BitStream& packet) { + void WriteKey(RakNet::BitStream& packet) const { packet.Write(this->key.length() * sizeof(uint16_t)); for (uint32_t i = 0; i < this->key.length(); ++i) { packet.Write(this->key[i]); @@ -70,7 +70,7 @@ private: } //! Writes the value to the packet - void WriteValue(RakNet::BitStream& packet) { + void WriteValue(RakNet::BitStream& packet) const { packet.Write(this->GetValueType()); packet.Write(this->value); } @@ -90,7 +90,7 @@ public: /*! \return The value */ - const T& GetValue(void) { return this->value; } + const T& GetValue(void) const { return this->value; } //! Sets the value /*! @@ -102,13 +102,13 @@ public: /*! \return The value string */ - std::string GetValueString(void) { return ""; } + std::string GetValueString(void) const { return ""; } //! Writes the data to a packet /*! \param packet The packet */ - void WriteToPacket(RakNet::BitStream& packet) override { + void WriteToPacket(RakNet::BitStream& packet) const override { this->WriteKey(packet); this->WriteValue(packet); } @@ -117,13 +117,13 @@ public: /*! \return The key */ - const std::u16string& GetKey(void) override { return this->key; } + const std::u16string& GetKey(void) const override { return this->key; } //! Gets the LDF Type /*! \return The LDF value type */ - eLDFType GetValueType(void) override { return LDF_TYPE_UNKNOWN; } + eLDFType GetValueType(void) const override { return LDF_TYPE_UNKNOWN; } //! Gets the string data /*! @@ -131,7 +131,7 @@ public: \param includeTypeId Whether or not to include the type id in the data \return The string representation of the data */ - std::string GetString(const bool includeKey = true, const bool includeTypeId = true) override { + std::string GetString(const bool includeKey = true, const bool includeTypeId = true) const override { if (GetValueType() == -1) { return GeneralUtils::UTF16ToWTF8(this->key) + "=-1:"; } @@ -154,11 +154,11 @@ public: return stream.str(); } - std::string GetValueAsString() override { + std::string GetValueAsString() const override { return this->GetValueString(); } - LDFBaseData* Copy() override { + LDFBaseData* Copy() const override { return new LDFData(key, value); } @@ -166,19 +166,19 @@ public: }; // LDF Types -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_UTF_16; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_S32; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_FLOAT; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_DOUBLE; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_U32; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_BOOLEAN; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_U64; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_OBJID; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_UTF_8; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_UTF_16; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_S32; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_FLOAT; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_DOUBLE; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_U32; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_BOOLEAN; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_U64; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_OBJID; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_UTF_8; }; // The specialized version for std::u16string (UTF-16) template<> -inline void LDFData::WriteValue(RakNet::BitStream& packet) { +inline void LDFData::WriteValue(RakNet::BitStream& packet) const { packet.Write(this->GetValueType()); packet.Write(this->value.length()); @@ -189,7 +189,7 @@ inline void LDFData::WriteValue(RakNet::BitStream& packet) { // The specialized version for bool template<> -inline void LDFData::WriteValue(RakNet::BitStream& packet) { +inline void LDFData::WriteValue(RakNet::BitStream& packet) const { packet.Write(this->GetValueType()); packet.Write(this->value); @@ -197,7 +197,7 @@ inline void LDFData::WriteValue(RakNet::BitStream& packet) { // The specialized version for std::string (UTF-8) template<> -inline void LDFData::WriteValue(RakNet::BitStream& packet) { +inline void LDFData::WriteValue(RakNet::BitStream& packet) const { packet.Write(this->GetValueType()); packet.Write(this->value.length()); @@ -206,18 +206,18 @@ inline void LDFData::WriteValue(RakNet::BitStream& packet) { } } -template<> inline std::string LDFData::GetValueString() { +template<> inline std::string LDFData::GetValueString() const { return GeneralUtils::UTF16ToWTF8(this->value, this->value.size()); } -template<> inline std::string LDFData::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData::GetValueString() { return std::to_string(this->value); } +template<> inline std::string LDFData::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData::GetValueString() const { return std::to_string(this->value); } -template<> inline std::string LDFData::GetValueString() { return this->value; } +template<> inline std::string LDFData::GetValueString() const { return this->value; } #endif //!__LDFFORMAT__H__ diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index 56a43848..bed274b0 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -1,6 +1,7 @@ #include "dConfig.h" #include +#include #include "BinaryPathFinder.h" #include "GeneralUtils.h" diff --git a/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h b/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h new file mode 100644 index 00000000..b99687d0 --- /dev/null +++ b/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h @@ -0,0 +1,21 @@ +#ifndef __EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__ +#define __EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__ + +#include + +enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t { + SUCCESS, + FAIL_GENERIC, + FAIL_INV_FULL, + FAIL_ITEM_NOT_FOUND, + FAIL_CANT_MOVE_TO_THAT_INV_TYPE, + FAIL_NOT_NEAR_BANK, + FAIL_CANT_SWAP_ITEMS, + FAIL_SOURCE_TYPE, + FAIL_WRONG_DEST_TYPE, + FAIL_SWAP_DEST_TYPE, + FAIL_CANT_MOVE_THINKING_HAT, + FAIL_DISMOUNT_BEFORE_MOVING +}; + +#endif //!__EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__ diff --git a/dGame/Character.cpp b/dGame/Character.cpp index 59a67462..57a951d9 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -27,6 +27,8 @@ Character::Character(uint32_t id, User* parentUser) { m_ID = id; m_ParentUser = parentUser; m_OurEntity = nullptr; + m_GMLevel = eGameMasterLevel::CIVILIAN; + m_PermissionMap = static_cast(0); } Character::~Character() { diff --git a/dGame/Character.h b/dGame/Character.h index 77f286f0..7a83325b 100644 --- a/dGame/Character.h +++ b/dGame/Character.h @@ -464,22 +464,22 @@ private: /** * The ID of this character. First 32 bits of the ObjectID. */ - uint32_t m_ID; + uint32_t m_ID{}; /** * The 64-bit unique ID used in the game. */ - LWOOBJID m_ObjectID; + LWOOBJID m_ObjectID{ LWOOBJID_EMPTY }; /** * The user that owns this character. */ - User* m_ParentUser; + User* m_ParentUser{}; /** * If the character is in game, this is the entity that it represents, else nullptr. */ - Entity* m_OurEntity; + Entity* m_OurEntity{}; /** * 0-9, the Game Master level of this character. @@ -506,17 +506,17 @@ private: /** * Whether the custom name of this character is rejected */ - bool m_NameRejected; + bool m_NameRejected{}; /** * The current amount of coins of this character */ - int64_t m_Coins; + int64_t m_Coins{}; /** * Whether the character is building */ - bool m_BuildMode; + bool m_BuildMode{}; /** * The items equipped by the character on world load @@ -583,7 +583,7 @@ private: /** * The ID of the properties of this character */ - uint32_t m_PropertyCloneID; + uint32_t m_PropertyCloneID{}; /** * The XML data for this character, stored as string @@ -613,7 +613,7 @@ private: /** * The last time this character logged in */ - uint64_t m_LastLogin; + uint64_t m_LastLogin{}; /** * The gameplay flags this character has (not just true values) diff --git a/dGame/User.cpp b/dGame/User.cpp index 0b2c3c3f..806d4611 100644 --- a/dGame/User.cpp +++ b/dGame/User.cpp @@ -12,6 +12,7 @@ User::User(const SystemAddress& sysAddr, const std::string& username, const std: m_AccountID = 0; m_Username = ""; m_SessionKey = ""; + m_MuteExpire = 0; m_MaxGMLevel = eGameMasterLevel::CIVILIAN; //The max GM level this account can assign to it's characters m_LastCharID = 0; diff --git a/dGame/dBehaviors/BehaviorContext.cpp b/dGame/dBehaviors/BehaviorContext.cpp index a3721d8f..abba74be 100644 --- a/dGame/dBehaviors/BehaviorContext.cpp +++ b/dGame/dBehaviors/BehaviorContext.cpp @@ -105,7 +105,7 @@ void BehaviorContext::ExecuteUpdates() { this->scheduledUpdates.clear(); } -void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bitStream) { +bool BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bitStream) { BehaviorSyncEntry entry; auto found = false; @@ -128,7 +128,7 @@ void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bit if (!found) { LOG("Failed to find behavior sync entry with sync id (%i)!", syncId); - return; + return false; } auto* behavior = entry.behavior; @@ -137,10 +137,11 @@ void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bit if (behavior == nullptr) { LOG("Invalid behavior for sync id (%i)!", syncId); - return; + return false; } behavior->Sync(this, bitStream, branch); + return true; } diff --git a/dGame/dBehaviors/BehaviorContext.h b/dGame/dBehaviors/BehaviorContext.h index 3e6c0b1d..4922f736 100644 --- a/dGame/dBehaviors/BehaviorContext.h +++ b/dGame/dBehaviors/BehaviorContext.h @@ -93,7 +93,7 @@ struct BehaviorContext void ExecuteUpdates(); - void SyncBehavior(uint32_t syncId, RakNet::BitStream& bitStream); + bool SyncBehavior(uint32_t syncId, RakNet::BitStream& bitStream); void Update(float deltaTime); diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 60dceeef..bfb0bbfa 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -29,7 +29,8 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): Component(parent) { m_Target = LWOOBJID_EMPTY; - SetAiState(AiState::spawn); + m_DirtyStateOrTarget = true; + m_State = AiState::spawn; m_Timer = 1.0f; m_StartPosition = parent->GetPosition(); m_MovementAI = nullptr; diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 60dd071c..172877b0 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -558,19 +558,9 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) { itemElement->QueryAttribute("parent", &parent); // End custom xml - std::vector config; + auto* item = new Item(id, lot, inventory, slot, count, bound, {}, parent, subKey); - auto* extraInfo = itemElement->FirstChildElement("x"); - - if (extraInfo) { - std::string modInfo = extraInfo->Attribute("ma"); - - LDFBaseData* moduleAssembly = new LDFData(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(modInfo.substr(2, modInfo.size() - 1))); - - config.push_back(moduleAssembly); - } - - const auto* item = new Item(id, lot, inventory, slot, count, bound, config, parent, subKey); + item->LoadConfigXml(*itemElement); if (equipped) { const auto info = Inventory::FindItemComponent(lot); @@ -676,17 +666,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) { itemElement->SetAttribute("parent", item->GetParent()); // End custom xml - for (auto* data : item->GetConfig()) { - if (data->GetKey() != u"assemblyPartLOTs") { - continue; - } - - auto* extraInfo = document.NewElement("x"); - - extraInfo->SetAttribute("ma", data->GetString(false).c_str()); - - itemElement->LinkEndChild(extraInfo); - } + item->SaveConfigXml(*itemElement); bagElement->LinkEndChild(itemElement); } @@ -895,8 +875,6 @@ void InventoryComponent::UnEquipItem(Item* item) { RemoveSlot(item->GetInfo().equipLocation); - PurgeProxies(item); - UnequipScripts(item); Game::entityManager->SerializeEntity(m_Parent); @@ -906,6 +884,8 @@ void InventoryComponent::UnEquipItem(Item* item) { PropertyManagementComponent::Instance()->GetParent()->OnZonePropertyModelRemovedWhileEquipped(m_Parent); Game::zoneManager->GetZoneControlObject()->OnZonePropertyModelRemovedWhileEquipped(m_Parent); } + + PurgeProxies(item); } @@ -1525,10 +1505,10 @@ void InventoryComponent::PurgeProxies(Item* item) { const auto root = item->GetParent(); if (root != LWOOBJID_EMPTY) { - item = FindItemById(root); + Item* itemRoot = FindItemById(root); - if (item != nullptr) { - UnEquipItem(item); + if (itemRoot != nullptr) { + UnEquipItem(itemRoot); } return; @@ -1600,18 +1580,18 @@ void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument& document) { } -bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId){ +bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId) { BehaviorSlot behaviorSlot = BehaviorSlot::Invalid; - if (slot == 1 ) behaviorSlot = BehaviorSlot::Primary; - else if (slot == 2 ) behaviorSlot = BehaviorSlot::Offhand; - else if (slot == 3 ) behaviorSlot = BehaviorSlot::Neck; - else if (slot == 4 ) behaviorSlot = BehaviorSlot::Head; - else if (slot == 5 ) behaviorSlot = BehaviorSlot::Consumable; + if (slot == 1) behaviorSlot = BehaviorSlot::Primary; + else if (slot == 2) behaviorSlot = BehaviorSlot::Offhand; + else if (slot == 3) behaviorSlot = BehaviorSlot::Neck; + else if (slot == 4) behaviorSlot = BehaviorSlot::Head; + else if (slot == 5) behaviorSlot = BehaviorSlot::Consumable; else return false; return SetSkill(behaviorSlot, skillId); } -bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId){ +bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) { if (skillId == 0) return false; const auto index = m_Skills.find(slot); if (index != m_Skills.end()) { @@ -1623,4 +1603,3 @@ bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId){ m_Skills.insert_or_assign(slot, skillId); return true; } - diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index debe0bd8..073c09e1 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -32,6 +32,8 @@ #include "eGameMasterLevel.h" #include "eMissionState.h" #include "dNavMesh.h" +#include "eGameActivity.h" +#include "eStateChangeType.h" std::unordered_map PetComponent::currentActivities{}; std::unordered_map PetComponent::activePets{}; @@ -210,24 +212,23 @@ void PetComponent::OnUse(Entity* originator) { if (dpWorld::IsLoaded()) { NiPoint3 attempt = petPosition + forward * interactionDistance; - float y = dpWorld::GetNavMesh()->GetHeightAtPoint(attempt); + NiPoint3 nearestPoint = dpWorld::GetNavMesh()->NearestPoint(attempt); - while (std::abs(y - petPosition.y) > 4 && interactionDistance > 10) { + while (std::abs(nearestPoint.y - petPosition.y) > 4 && interactionDistance > 10) { const NiPoint3 forward = m_Parent->GetRotation().GetForwardVector(); attempt = originatorPosition + forward * interactionDistance; - y = dpWorld::GetNavMesh()->GetHeightAtPoint(attempt); + nearestPoint = dpWorld::GetNavMesh()->NearestPoint(attempt); interactionDistance -= 0.5f; } - position = attempt; + position = nearestPoint; } else { position = petPosition + forward * interactionDistance; } - auto rotation = NiQuaternion::LookAt(position, petPosition); GameMessages::SendNotifyPetTamingMinigame( @@ -246,11 +247,11 @@ void PetComponent::OnUse(Entity* originator) { m_Parent->GetObjectID(), LWOOBJID_EMPTY, originator->GetObjectID(), - true, + false, ePetTamingNotifyType::BEGIN, - petPosition, - position, - rotation, + NiPoint3Constant::ZERO, + NiPoint3Constant::ZERO, + NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f), UNASSIGNED_SYSTEM_ADDRESS ); @@ -258,11 +259,18 @@ void PetComponent::OnUse(Entity* originator) { m_Tamer = originator->GetObjectID(); SetStatus(5); + Game::entityManager->SerializeEntity(m_Parent); currentActivities.insert_or_assign(m_Tamer, m_Parent->GetObjectID()); // Notify the start of a pet taming minigame m_Parent->GetScript()->OnNotifyPetTamingMinigame(m_Parent, originator, ePetTamingNotifyType::BEGIN); + + auto* characterComponent = originator->GetComponent(); + if (characterComponent != nullptr) { + characterComponent->SetCurrentActivity(eGameActivity::PET_TAMING); + Game::entityManager->SerializeEntity(originator); + } } void PetComponent::Update(float deltaTime) { @@ -627,6 +635,11 @@ void PetComponent::RequestSetPetName(std::u16string name) { UNASSIGNED_SYSTEM_ADDRESS ); + auto* characterComponent = tamer->GetComponent(); + if (characterComponent != nullptr) { + characterComponent->SetCurrentActivity(eGameActivity::NONE); + Game::entityManager->SerializeEntity(tamer); + } GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()); auto* modelEntity = Game::entityManager->GetEntity(m_ModelId); @@ -666,6 +679,11 @@ void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) { UNASSIGNED_SYSTEM_ADDRESS ); + auto* characterComponent = tamer->GetComponent(); + if (characterComponent != nullptr) { + characterComponent->SetCurrentActivity(eGameActivity::NONE); + Game::entityManager->SerializeEntity(tamer); + } GameMessages::SendNotifyTamingModelLoadedOnServer(m_Tamer, tamer->GetSystemAddress()); GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()); @@ -712,6 +730,11 @@ void PetComponent::ClientFailTamingMinigame() { UNASSIGNED_SYSTEM_ADDRESS ); + auto* characterComponent = tamer->GetComponent(); + if (characterComponent != nullptr) { + characterComponent->SetCurrentActivity(eGameActivity::NONE); + Game::entityManager->SerializeEntity(tamer); + } GameMessages::SendNotifyTamingModelLoadedOnServer(m_Tamer, tamer->GetSystemAddress()); GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()); diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 21d39249..b896d3f2 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -25,6 +25,7 @@ #include "LeaderboardManager.h" #include "dZoneManager.h" #include "CDActivitiesTable.h" +#include "eStateChangeType.h" #include #ifndef M_PI @@ -77,6 +78,9 @@ void RacingControlComponent::OnPlayerLoaded(Entity* player) { m_LoadedPlayers++; + // not live accurate to stun the player but prevents them from using skills during the race that are not meant to be used. + GameMessages::SendSetStunned(player->GetObjectID(), eStateChangeType::PUSH, player->GetSystemAddress(), LWOOBJID_EMPTY, true, true, true, true, true, true, true, true, true); + LOG("Loading player %i", m_LoadedPlayers); m_LobbyPlayers.push_back(player->GetObjectID()); diff --git a/dGame/dComponents/SkillComponent.cpp b/dGame/dComponents/SkillComponent.cpp index d926ff2c..58b13dc5 100644 --- a/dGame/dComponents/SkillComponent.cpp +++ b/dGame/dComponents/SkillComponent.cpp @@ -38,7 +38,7 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s context->skillID = skillID; - this->m_managedBehaviors.insert_or_assign(skillUid, context); + this->m_managedBehaviors.insert({ skillUid, context }); auto* behavior = Behavior::CreateBehavior(behaviorId); @@ -52,17 +52,24 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s } void SkillComponent::SyncPlayerSkill(const uint32_t skillUid, const uint32_t syncId, RakNet::BitStream& bitStream) { - const auto index = this->m_managedBehaviors.find(skillUid); + const auto index = this->m_managedBehaviors.equal_range(skillUid); - if (index == this->m_managedBehaviors.end()) { + if (index.first == this->m_managedBehaviors.end()) { LOG("Failed to find skill with uid (%i)!", skillUid, syncId); return; } - auto* context = index->second; + bool foundSyncId = false; + for (auto it = index.first; it != index.second && !foundSyncId; ++it) { + const auto& context = it->second; - context->SyncBehavior(syncId, bitStream); + foundSyncId = context->SyncBehavior(syncId, bitStream); + } + + if (!foundSyncId) { + LOG("Failed to find sync id (%i) for skill with uid (%i)!", syncId, skillUid); + } } @@ -138,7 +145,7 @@ void SkillComponent::Update(const float deltaTime) { for (const auto& pair : this->m_managedBehaviors) pair.second->UpdatePlayerSyncs(deltaTime); } - std::map keep{}; + std::multimap keep{}; for (const auto& pair : this->m_managedBehaviors) { auto* context = pair.second; @@ -176,7 +183,7 @@ void SkillComponent::Update(const float deltaTime) { } } - keep.insert_or_assign(pair.first, context); + keep.insert({ pair.first, context }); } this->m_managedBehaviors = keep; @@ -285,7 +292,7 @@ SkillExecutionResult SkillComponent::CalculateBehavior( return { false, 0 }; } - this->m_managedBehaviors.insert_or_assign(context->skillUId, context); + this->m_managedBehaviors.insert({ context->skillUId, context }); if (!clientInitalized) { // Echo start skill diff --git a/dGame/dComponents/SkillComponent.h b/dGame/dComponents/SkillComponent.h index 24d92148..f74b4411 100644 --- a/dGame/dComponents/SkillComponent.h +++ b/dGame/dComponents/SkillComponent.h @@ -188,7 +188,7 @@ private: /** * All of the active skills mapped by their unique ID. */ - std::map m_managedBehaviors; + std::multimap m_managedBehaviors; /** * All active projectiles. diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index abe11ea5..b9286d25 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -76,8 +76,8 @@ void VendorComponent::RefreshInventory(bool isCreation) { if (vendorItems.empty()) break; auto randomItemIndex = GeneralUtils::GenerateRandomNumber(0, vendorItems.size() - 1); const auto& randomItem = vendorItems.at(randomItemIndex); - vendorItems.erase(vendorItems.begin() + randomItemIndex); if (SetupItem(randomItem.itemid)) m_Inventory.push_back(SoldItem(randomItem.itemid, randomItem.sortPriority)); + vendorItems.erase(vendorItems.begin() + randomItemIndex); } } } diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 93b10cec..4bd667b4 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -99,9 +99,11 @@ #include "ActivityManager.h" #include "PlayerManager.h" #include "eVendorTransactionResult.h" +#include "eReponseMoveItemBetweenInventoryTypeCode.h" #include "CDComponentsRegistryTable.h" #include "CDObjectsTable.h" +#include "eItemType.h" void GameMessages::SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender) { CBITSTREAM; @@ -412,7 +414,8 @@ void GameMessages::SendPlatformResync(Entity* entity, const SystemAddress& sysAd bitStream.Write(qUnexpectedRotation.w); } - SEND_PACKET_BROADCAST; + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; } void GameMessages::SendRestoreToPostLoadStats(Entity* entity, const SystemAddress& sysAddr) { @@ -4563,16 +4566,31 @@ void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& if (inStream.ReadBit()) inStream.Read(itemLOT); if (invTypeDst == invTypeSrc) { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC); return; } auto* inventoryComponent = entity->GetComponent(); - if (inventoryComponent != nullptr) { + if (inventoryComponent) { if (itemID != LWOOBJID_EMPTY) { auto* item = inventoryComponent->FindItemById(itemID); - if (!item) return; + if (!item) { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_ITEM_NOT_FOUND); + return; + } + + if (item->GetLot() == 6086) { // Thinking hat + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_CANT_MOVE_THINKING_HAT); + return; + } + + auto* destInv = inventoryComponent->GetInventory(invTypeDst); + if (destInv && destInv->GetEmptySlots() == 0) { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_INV_FULL); + return; + } // Despawn the pet if we are moving that pet to the vault. auto* petComponent = PetComponent::GetActivePet(entity->GetObjectID()); @@ -4581,10 +4599,32 @@ void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& } inventoryComponent->MoveItemToInventory(item, invTypeDst, iStackCount, showFlyingLoot, false, false, destSlot); + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::SUCCESS); } + } else { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC); } } +void GameMessages::SendResponseMoveItemBetweenInventoryTypes(LWOOBJID objectId, const SystemAddress& sysAddr, eInventoryType inventoryTypeDestination, eInventoryType inventoryTypeSource, eReponseMoveItemBetweenInventoryTypeCode response) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(eGameMessageType::RESPONSE_MOVE_ITEM_BETWEEN_INVENTORY_TYPES); + + bitStream.Write(inventoryTypeDestination != eInventoryType::ITEMS); + if (inventoryTypeDestination != eInventoryType::ITEMS) bitStream.Write(inventoryTypeDestination); + + bitStream.Write(inventoryTypeSource != eInventoryType::ITEMS); + if (inventoryTypeSource != eInventoryType::ITEMS) bitStream.Write(inventoryTypeSource); + + bitStream.Write(response != eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC); + if (response != eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC) bitStream.Write(response); + + SEND_PACKET; +} + void GameMessages::SendShowActivityCountdown(LWOOBJID objectId, bool bPlayAdditionalSound, bool bPlayCountdownSound, std::u16string sndName, int32_t stateToPlaySoundOn, const SystemAddress& sysAddr) { CBITSTREAM; @@ -5351,7 +5391,8 @@ void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream& inStream, En iStackCount = std::min(item->GetCount(), iStackCount); if (bConfirmed) { - if (eInvType == eInventoryType::MODELS) { + const auto itemType = static_cast(item->GetInfo().itemType); + if (itemType == eItemType::MODEL || itemType == eItemType::LOOT_MODEL) { item->DisassembleModel(iStackCount); } auto lot = item->GetLot(); @@ -6209,3 +6250,18 @@ void GameMessages::SendSlashCommandFeedbackText(Entity* entity, std::u16string t auto sysAddr = entity->GetSystemAddress(); SEND_PACKET; } + +void GameMessages::SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(eGameMessageType::FORCE_CAMERA_TARGET_CYCLE); + bitStream.Write(bForceCycling); + bitStream.Write(cyclingMode != eCameraTargetCyclingMode::ALLOW_CYCLE_TEAMMATES); + if (cyclingMode != eCameraTargetCyclingMode::ALLOW_CYCLE_TEAMMATES) bitStream.Write(cyclingMode); + bitStream.Write(optionalTargetID); + + auto sysAddr = entity->GetSystemAddress(); + SEND_PACKET; +} diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index b842710e..e3c16471 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -39,6 +39,12 @@ enum class eQuickBuildFailReason : uint32_t; enum class eQuickBuildState : uint32_t; enum class BehaviorSlot : int32_t; enum class eVendorTransactionResult : uint32_t; +enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t; + +enum class eCameraTargetCyclingMode : int32_t { + ALLOW_CYCLE_TEAMMATES, + DISALLOW_CYCLING +}; namespace GameMessages { class PropertyDataMessage; @@ -589,6 +595,7 @@ namespace GameMessages { //NT: void HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); + void SendResponseMoveItemBetweenInventoryTypes(LWOOBJID objectId, const SystemAddress& sysAddr, eInventoryType inventoryTypeDestination, eInventoryType inventoryTypeSource, eReponseMoveItemBetweenInventoryTypeCode response); void SendShowActivityCountdown(LWOOBJID objectId, bool bPlayAdditionalSound, bool bPlayCountdownSound, std::u16string sndName, int32_t stateToPlaySoundOn, const SystemAddress& sysAddr); @@ -666,6 +673,7 @@ namespace GameMessages { void HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity); void SendSlashCommandFeedbackText(Entity* entity, std::u16string text); + void SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID); }; #endif // GAMEMESSAGES_H diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index b6193692..32603761 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -27,6 +27,23 @@ #include "CDComponentsRegistryTable.h" #include "CDPackageComponentTable.h" +namespace { + const std::map ExtraSettingAbbreviations = { + { "assemblyPartLOTs", "ma" }, + { "blueprintID", "b" }, + { "userModelID", "ui" }, + { "userModelName", "un" }, + { "userModelDesc", "ud" }, + { "userModelHasBhvr", "ub" }, + { "userModelBehaviors", "ubh" }, + { "userModelBehaviorSourceID", "ubs" }, + { "userModelPhysicsType", "up" }, + { "userModelMod", "um" }, + { "userModelOpt", "uo" }, + { "reforgedLOT", "rl" }, + }; +} + 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)) { return; @@ -122,6 +139,10 @@ uint32_t Item::GetSlot() const { return slot; } +std::vector Item::GetConfig() const { + return config; +} + std::vector& Item::GetConfig() { return config; } @@ -251,7 +272,7 @@ bool Item::Consume() { auto skills = skillsTable->Query([this](const CDObjectSkills entry) { return entry.objectTemplate == static_cast(lot); - }); + }); auto success = false; @@ -515,3 +536,35 @@ Item::~Item() { config.clear(); } + +void Item::SaveConfigXml(tinyxml2::XMLElement& i) const { + tinyxml2::XMLElement* x = nullptr; + + for (const auto* config : this->config) { + const auto& key = GeneralUtils::UTF16ToWTF8(config->GetKey()); + const auto saveKey = ExtraSettingAbbreviations.find(key); + if (saveKey == ExtraSettingAbbreviations.end()) { + continue; + } + + if (!x) { + x = i.InsertNewChildElement("x"); + } + + const auto dataToSave = config->GetString(false); + x->SetAttribute(saveKey->second.c_str(), dataToSave.c_str()); + } +} + +void Item::LoadConfigXml(const tinyxml2::XMLElement& i) { + const auto* x = i.FirstChildElement("x"); + if (!x) return; + + for (const auto& pair : ExtraSettingAbbreviations) { + const auto* data = x->Attribute(pair.second.c_str()); + if (!data) continue; + + const auto value = pair.first + "=" + data; + config.push_back(LDFBaseData::DataFromString(value)); + } +} diff --git a/dGame/dInventory/Item.h b/dGame/dInventory/Item.h index 04d05d7c..72ff264c 100644 --- a/dGame/dInventory/Item.h +++ b/dGame/dInventory/Item.h @@ -9,6 +9,10 @@ #include "eInventoryType.h" #include "eLootSourceType.h" +namespace tinyxml2 { + class XMLElement; +}; + /** * An item that can be stored in an inventory and optionally consumed or equipped * TODO: ideally this should be a component @@ -116,6 +120,12 @@ public: */ std::vector& GetConfig(); + /** + * Returns current config info for this item, e.g. for rockets + * @return current config info for this item + */ + std::vector GetConfig() const; + /** * Returns the database info for this item * @return the database info for this item @@ -214,6 +224,10 @@ public: */ void RemoveFromInventory(); + void SaveConfigXml(tinyxml2::XMLElement& i) const; + + void LoadConfigXml(const tinyxml2::XMLElement& i); + private: /** * The object ID of this item diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index c2ed2a42..2a841e39 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -454,6 +454,16 @@ void Mission::YieldRewards() { } } + // Even with no repeatable column, reputation is repeatable + if (info.reward_reputation > 0) { + missionComponent->Progress(eMissionTaskType::EARN_REPUTATION, 0, LWOOBJID_EMPTY, "", info.reward_reputation); + auto* const character = entity->GetComponent(); + if (character) { + character->SetReputation(character->GetReputation() + info.reward_reputation); + GameMessages::SendUpdateReputation(entity->GetObjectID(), character->GetReputation(), entity->GetSystemAddress()); + } + } + if (m_Completions > 0) { std::vector> items; @@ -532,15 +542,6 @@ void Mission::YieldRewards() { modelInventory->SetSize(modelInventory->GetSize() + info.reward_bankinventory); } - if (info.reward_reputation > 0) { - missionComponent->Progress(eMissionTaskType::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), true); } diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 3ba1ab38..ca4e03d4 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -8,6 +8,7 @@ #include "SlashCommandHandler.h" #include +#include #include "DEVGMCommands.h" #include "GMGreaterThanZeroCommands.h" @@ -30,7 +31,6 @@ void SlashCommandHandler::RegisterCommand(Command command) { } for (const auto& alias : command.aliases) { - LOG_DEBUG("Registering command %s", alias.c_str()); auto [_, success] = RegisteredCommands.try_emplace(alias, command); // Don't allow duplicate commands if (!success) { @@ -60,11 +60,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity* if (commandHandle.requiredLevel > eGameMasterLevel::CIVILIAN) Database::Get()->InsertSlashCommandUsage(entity->GetObjectID(), input); commandHandle.handle(entity, sysAddr, args); } else if (entity->GetGMLevel() != eGameMasterLevel::CIVILIAN) { - // We don't need to tell civilians they aren't high enough level error = "You are not high enough GM level to use \"" + command + "\""; } } else if (entity->GetGMLevel() == eGameMasterLevel::CIVILIAN) { - // We don't need to tell civilians commands don't exist error = "Command " + command + " does not exist!"; } @@ -75,32 +73,74 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity* void GMZeroCommands::Help(Entity* entity, const SystemAddress& sysAddr, const std::string args) { std::ostringstream feedback; - if (args.empty()) { - feedback << "----- Commands -----"; - for (const auto& [alias, command] : CommandInfos) { - // TODO: Limit displaying commands based on GM level they require - if (command.requiredLevel > entity->GetGMLevel()) continue; - LOG("Help command: %s", alias.c_str()); - feedback << "\n/" << alias << ": " << command.help; + constexpr size_t pageSize = 10; + + std::string trimmedArgs = args; + trimmedArgs.erase(trimmedArgs.begin(), std::find_if_not(trimmedArgs.begin(), trimmedArgs.end(), [](unsigned char ch) { + return std::isspace(ch); + })); + trimmedArgs.erase(std::find_if_not(trimmedArgs.rbegin(), trimmedArgs.rend(), [](unsigned char ch) { + return std::isspace(ch); + }).base(), trimmedArgs.end()); + + std::optional parsedPage = GeneralUtils::TryParse(trimmedArgs); + if (trimmedArgs.empty() || parsedPage.has_value()) { + size_t page = parsedPage.value_or(1); + + std::map accessibleCommands; + for (const auto& [commandName, command] : CommandInfos) { + if (command.requiredLevel <= entity->GetGMLevel()) { + accessibleCommands.emplace(commandName, command); + } + } + + size_t totalPages = (accessibleCommands.size() + pageSize - 1) / pageSize; + + if (page < 1 || page > totalPages) { + feedback << "Invalid page number. Total pages: " << totalPages; + GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedback.str())); + return; + } + + auto it = accessibleCommands.begin(); + std::advance(it, (page - 1) * pageSize); + size_t endIdx = std::min(page * pageSize, accessibleCommands.size()); + + feedback << "----- Commands (Page " << page << " of " << totalPages << ") -----"; + for (size_t i = (page - 1) * pageSize; i < endIdx; ++i, ++it) { + feedback << "\n/" << it->first << ": " << it->second.help; + } + + const auto feedbackStr = feedback.str(); + if (!feedbackStr.empty()) { + GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); + } + return; + } + + auto it = std::ranges::find_if(CommandInfos, [&trimmedArgs](const auto& pair) { + return std::ranges::find(pair.second.aliases, trimmedArgs) != pair.second.aliases.end(); + }); + + if (it != CommandInfos.end() && entity->GetGMLevel() >= it->second.requiredLevel) { + const auto& command = it->second; + feedback << "----- " << it->first << " Info -----\n"; + feedback << command.info << "\n"; + if (command.aliases.size() > 1) { + feedback << "Aliases: "; + for (size_t i = 0; i < command.aliases.size(); ++i) { + if (i > 0) feedback << ", "; + feedback << command.aliases[i]; + } } } else { - auto it = CommandInfos.find(args); - if (it != CommandInfos.end() && entity->GetGMLevel() >= it->second.requiredLevel) { - feedback << "----- " << args << " -----\n"; - feedback << it->second.info; - if (it->second.aliases.size() > 1) { - feedback << "\nAliases: "; - for (size_t i = 0; i < it->second.aliases.size(); i++) { - if (i > 0) feedback << ", "; - feedback << it->second.aliases[i]; - } - } - } else if (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN) { - feedback << "Command " << std::quoted(args) << " does not exist!"; - } + feedback << "Command not found."; } + const auto feedbackStr = feedback.str(); - if (!feedbackStr.empty()) GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); + if (!feedbackStr.empty()) { + GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); + } } void SlashCommandHandler::SendAnnouncement(const std::string& title, const std::string& message) { @@ -888,6 +928,15 @@ void SlashCommandHandler::Startup() { }; RegisterCommand(FindPlayerCommand); + Command SpectateCommand{ + .help = "Spectate a player", + .info = "Specify a player name to spectate. They must be in the same world as you. Leave blank to stop spectating", + .aliases = { "spectate", "follow" }, + .handle = GMGreaterThanZeroCommands::Spectate, + .requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR + }; + RegisterCommand(SpectateCommand); + // Register GM Zero Commands Command HelpCommand{ @@ -1007,4 +1056,383 @@ void SlashCommandHandler::Startup() { }; RegisterCommand(InstanceInfoCommand); + //Commands that are handled by the client + + Command faqCommand{ + .help = "Show the LU FAQ Page", + .info = "Show the LU FAQ Page", + .aliases = {"faq","faqs"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(faqCommand); + + Command teamChatCommand{ + .help = "Send a message to your teammates.", + .info = "Send a message to your teammates.", + .aliases = {"team","t"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(teamChatCommand); + + Command showStoreCommand{ + .help = "Show the LEGO shop page.", + .info = "Show the LEGO shop page.", + .aliases = {"shop","store"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(showStoreCommand); + + Command minigamesCommand{ + .help = "Show the LEGO minigames page!", + .info = "Show the LEGO minigames page!", + .aliases = {"minigames"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(minigamesCommand); + + Command forumsCommand{ + .help = "Show the LU Forums!", + .info = "Show the LU Forums!", + .aliases = {"forums"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(forumsCommand); + + Command exitGameCommand{ + .help = "Exit to desktop", + .info = "Exit to desktop", + .aliases = {"exit","quit"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(exitGameCommand); + + Command thumbsUpCommand{ + .help = "Oh, yeah!", + .info = "Oh, yeah!", + .aliases = {"thumb","thumbs","thumbsup"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(thumbsUpCommand); + + Command victoryCommand{ + .help = "Victory!", + .info = "Victory!", + .aliases = {"victory!"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(victoryCommand); + + Command backflipCommand{ + .help = "Do a flip!", + .info = "Do a flip!", + .aliases = {"backflip"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(backflipCommand); + + Command clapCommand{ + .help = "A round of applause!", + .info = "A round of applause!", + .aliases = {"clap"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(clapCommand); + + Command logoutCharacterCommand{ + .help = "Returns you to the character select screen.", + .info = "Returns you to the character select screen.", + .aliases = {"camp","logoutcharacter"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(logoutCharacterCommand); + + Command sayCommand{ + .help = "Say something outloud so that everyone can hear you", + .info = "Say something outloud so that everyone can hear you", + .aliases = {"s","say"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(sayCommand); + + Command whisperCommand{ + .help = "Send a private message to another player.", + .info = "Send a private message to another player.", + .aliases = {"tell","w","whisper"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(whisperCommand); + + Command locationCommand{ + .help = "Output your current location on the map to the chat box.", + .info = "Output your current location on the map to the chat box.", + .aliases = {"loc","locate","location"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(locationCommand); + + Command logoutCommand{ + .help = "Returns you to the login screen.", + .info = "Returns you to the login screen.", + .aliases = {"logout","logoutaccount"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(logoutCommand); + + Command shrugCommand{ + .help = "I dunno...", + .info = "I dunno...", + .aliases = {"shrug"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(shrugCommand); + + Command leaveTeamCommand{ + .help = "Leave your current team.", + .info = "Leave your current team.", + .aliases = {"leave","leaveteam","teamleave","tleave"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(leaveTeamCommand); + + Command teamLootTypeCommand{ + .help = "[rr|ffa] Set the loot for your current team (round-robin/free for all).", + .info = "[rr|ffa] Set the loot for your current team (round-robin/free for all).", + .aliases = {"setloot","teamsetloot","tloot","tsetloot"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(teamLootTypeCommand); + + Command removeFriendCommand{ + .help = "[name] Removes a player from your friends list.", + .info = "[name] Removes a player from your friends list.", + .aliases = {"removefriend"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(removeFriendCommand); + + Command yesCommand{ + .help = "Aye aye, captain!", + .info = "Aye aye, captain!", + .aliases = {"yes"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(yesCommand); + + Command teamInviteCommand{ + .help = "[name] Invite a player to your team.", + .info = "[name] Invite a player to your team.", + .aliases = {"invite","inviteteam","teaminvite","tinvite"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(teamInviteCommand); + + Command danceCommand{ + .help = "Dance 'til you can't dance no more.", + .info = "Dance 'til you can't dance no more.", + .aliases = {"dance"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(danceCommand); + + Command sighCommand{ + .help = "Another day, another brick.", + .info = "Another day, another brick.", + .aliases = {"sigh"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(sighCommand); + + Command recommendedOptionsCommand{ + .help = "Sets the recommended performance options in the cfg file", + .info = "Sets the recommended performance options in the cfg file", + .aliases = {"recommendedperfoptions"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(recommendedOptionsCommand); + + Command setTeamLeaderCommand{ + .help = "[name] Set the leader for your current team.", + .info = "[name] Set the leader for your current team.", + .aliases = {"leader","setleader","teamsetleader","tleader","tsetleader"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(setTeamLeaderCommand); + + Command cringeCommand{ + .help = "I don't even want to talk about it...", + .info = "I don't even want to talk about it...", + .aliases = {"cringe"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(cringeCommand); + + Command talkCommand{ + .help = "Jibber Jabber", + .info = "Jibber Jabber", + .aliases = {"talk"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(talkCommand); + + Command cancelQueueCommand{ + .help = "Cancel Your position in the queue if you are in one.", + .info = "Cancel Your position in the queue if you are in one.", + .aliases = {"cancelqueue"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(cancelQueueCommand); + + Command lowPerformanceCommand{ + .help = "Sets the default low-spec performance options in the cfg file", + .info = "Sets the default low-spec performance options in the cfg file", + .aliases = {"perfoptionslow"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(lowPerformanceCommand); + + Command kickFromTeamCommand{ + .help = "[name] Kick a player from your current team.", + .info = "[name] Kick a player from your current team.", + .aliases = {"kick","kickplayer","teamkickplayer","tkick","tkickplayer"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(kickFromTeamCommand); + + Command thanksCommand{ + .help = "Express your gratitude for another.", + .info = "Express your gratitude for another.", + .aliases = {"thanks"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(thanksCommand); + + Command waveCommand{ + .help = "Wave to other players.", + .info = "Wave to other players.", + .aliases = {"wave"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(waveCommand); + + Command whyCommand{ + .help = "Why|!?!!", + .info = "Why|!?!!", + .aliases = {"why"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(whyCommand); + + Command midPerformanceCommand{ + .help = "Sets the default medium-spec performance options in the cfg file", + .info = "Sets the default medium-spec performance options in the cfg file", + .aliases = {"perfoptionsmid"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(midPerformanceCommand); + + Command highPerformanceCommand{ + .help = "Sets the default high-spec performance options in the cfg file", + .info = "Sets the default high-spec performance options in the cfg file", + .aliases = {"perfoptionshigh"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(highPerformanceCommand); + + Command gaspCommand{ + .help = "Oh my goodness!", + .info = "Oh my goodness!", + .aliases = {"gasp"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(gaspCommand); + + Command ignoreCommand{ + .help = "[name] Add a player to your ignore list.", + .info = "[name] Add a player to your ignore list.", + .aliases = {"addignore"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(ignoreCommand); + + Command addFriendCommand{ + .help = "[name] Add a player to your friends list.", + .info = "[name] Add a player to your friends list.", + .aliases = {"addfriend"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(addFriendCommand); + + Command cryCommand{ + .help = "Show everyone your 'Aw' face.", + .info = "Show everyone your 'Aw' face.", + .aliases = {"cry"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(cryCommand); + + Command giggleCommand{ + .help = "A good little chuckle", + .info = "A good little chuckle", + .aliases = {"giggle"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(giggleCommand); + + Command saluteCommand{ + .help = "For those about to build...", + .info = "For those about to build...", + .aliases = {"salute"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(saluteCommand); + + Command removeIgnoreCommand{ + .help = "[name] Removes a player from your ignore list.", + .info = "[name] Removes a player from your ignore list.", + .aliases = {"removeIgnore"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(removeIgnoreCommand); } diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp index ea33aa03..b9eaf7bf 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp @@ -322,4 +322,19 @@ namespace GMGreaterThanZeroCommands { request.Serialize(bitStream); Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); } + + void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) { + GameMessages::SendForceCameraTargetCycle(entity, false, eCameraTargetCyclingMode::DISALLOW_CYCLING, entity->GetObjectID()); + return; + } + + auto player = PlayerManager::GetPlayer(args); + if (!player) { + GameMessages::SendSlashCommandFeedbackText(entity, u"Player not found"); + return; + } + GameMessages::SendSlashCommandFeedbackText(entity, u"Spectating Player"); + GameMessages::SendForceCameraTargetCycle(entity, false, eCameraTargetCyclingMode::DISALLOW_CYCLING, player->GetObjectID()); + } } diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h index 7cb3d8d7..c278fc0a 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h @@ -15,6 +15,7 @@ namespace GMGreaterThanZeroCommands { void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args); void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args); void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args); } #endif //!GMGREATERTHANZEROCOMMANDS_H diff --git a/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp index f183d5ea..6c9811c2 100644 --- a/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp @@ -224,5 +224,9 @@ namespace GMZeroCommands { ChatPackets::SendSystemMessage(sysAddr, u"Map: " + (GeneralUtils::to_u16string(zoneId.GetMapID())) + u"\nClone: " + (GeneralUtils::to_u16string(zoneId.GetCloneID())) + u"\nInstance: " + (GeneralUtils::to_u16string(zoneId.GetInstanceID()))); } + + //For client side commands + void ClientHandled(Entity* entity, const SystemAddress& sysAddr, const std::string args) {} + }; diff --git a/dGame/dUtilities/SlashCommands/GMZeroCommands.h b/dGame/dUtilities/SlashCommands/GMZeroCommands.h index 3b2389b5..d3f6753d 100644 --- a/dGame/dUtilities/SlashCommands/GMZeroCommands.h +++ b/dGame/dUtilities/SlashCommands/GMZeroCommands.h @@ -15,6 +15,7 @@ namespace GMZeroCommands { void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args); void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args); void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ClientHandled(Entity* entity, const SystemAddress& sysAddr, const std::string args); } #endif //!GMZEROCOMMANDS_H diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index cb893da3..6043fe63 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -285,7 +285,6 @@ void ParseXml(const std::string& file) { } if (zoneID.value() != currentZoneID) { - LOG_DEBUG("Skipping (%s) %i location because it is in %i and not the current zone (%i)", name, lot, zoneID.value(), currentZoneID); continue; } diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 3d5f4aff..0f36ee37 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -40,6 +40,7 @@ #include "BitStreamUtils.h" #include "Start.h" #include "Server.h" +#include "CDZoneTableTable.h" namespace Game { Logger* logger = nullptr; @@ -277,6 +278,17 @@ int main(int argc, char** argv) { PersistentIDManager::Initialize(); Game::im = new InstanceManager(Game::logger, Game::server->GetIP()); + //Get CDClient initial information + try { + CDClientManager::LoadValuesFromDatabase(); + } catch (CppSQLite3Exception& e) { + LOG("Failed to initialize CDServer SQLite Database"); + LOG("May be caused by corrupted file: %s", (Game::assetManager->GetResPath() / "CDServer.sqlite").string().c_str()); + LOG("Error: %s", e.errorMessage()); + LOG("Error Code: %i", e.errorCode()); + return EXIT_FAILURE; + } + //Depending on the config, start up servers: if (Game::config->GetValue("prestart_servers") != "0") { StartChatServer(); @@ -382,6 +394,7 @@ int main(int argc, char** argv) { } void HandlePacket(Packet* packet) { + if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION) { LOG("A server has disconnected"); diff --git a/dNavigation/dNavMesh.cpp b/dNavigation/dNavMesh.cpp index f49dd31e..d9584b00 100644 --- a/dNavigation/dNavMesh.cpp +++ b/dNavigation/dNavMesh.cpp @@ -112,6 +112,31 @@ void dNavMesh::LoadNavmesh() { m_NavMesh = mesh; } +NiPoint3 dNavMesh::NearestPoint(const NiPoint3& location, const float halfExtent) const { + NiPoint3 toReturn = location; + if (m_NavMesh != nullptr) { + float pos[3]; + pos[0] = location.x; + pos[1] = location.y; + pos[2] = location.z; + + dtPolyRef nearestRef = 0; + float polyPickExt[3] = { halfExtent, halfExtent, halfExtent }; + float nearestPoint[3] = { 0.0f, 0.0f, 0.0f }; + dtQueryFilter filter{}; + + auto hasPoly = m_NavQuery->findNearestPoly(pos, polyPickExt, &filter, &nearestRef, nearestPoint); + if (hasPoly != DT_SUCCESS) { + toReturn = location; + } else { + toReturn.x = nearestPoint[0]; + toReturn.y = nearestPoint[1]; + toReturn.z = nearestPoint[2]; + } + } + return toReturn; +} + float dNavMesh::GetHeightAtPoint(const NiPoint3& location, const float halfExtentsHeight) const { if (m_NavMesh == nullptr) { return location.y; diff --git a/dNavigation/dNavMesh.h b/dNavigation/dNavMesh.h index 8a55c649..60e07e7c 100644 --- a/dNavigation/dNavMesh.h +++ b/dNavigation/dNavMesh.h @@ -21,7 +21,7 @@ public: /** * Get the height at a point - * + * * @param location The location to check for height at. This is the center of the search area. * @param halfExtentsHeight The half extents height of the search area. This is the distance from the center to the top and bottom of the search area. * The larger the value of halfExtentsHeight is, the larger the performance cost of the query. @@ -29,7 +29,7 @@ public: */ float GetHeightAtPoint(const NiPoint3& location, const float halfExtentsHeight = 32.0f) const; std::vector GetPath(const NiPoint3& startPos, const NiPoint3& endPos, float speed = 10.0f); - + NiPoint3 NearestPoint(const NiPoint3& location, const float halfExtent = 32.0f) const; bool IsNavmeshLoaded() { return m_NavMesh != nullptr; } private: diff --git a/dNet/BitStreamUtils.h b/dNet/BitStreamUtils.h index 1322ec95..33fde564 100644 --- a/dNet/BitStreamUtils.h +++ b/dNet/BitStreamUtils.h @@ -5,6 +5,7 @@ #include "MessageIdentifiers.h" #include "BitStream.h" #include +#include enum class eConnectionType : uint16_t; diff --git a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp index 40b248f5..b64bb7a8 100644 --- a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp +++ b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp @@ -15,6 +15,7 @@ #include "SkillComponent.h" #include "eReplicaComponentType.h" #include "RenderComponent.h" +#include "PlayerManager.h" #include @@ -53,11 +54,13 @@ void BossSpiderQueenEnemyServer::OnStartup(Entity* self) { void BossSpiderQueenEnemyServer::OnDie(Entity* self, Entity* killer) { if (Game::zoneManager->GetZoneID().GetMapID() == instanceZoneID && killer) { - auto* missionComponent = killer->GetComponent(); - if (missionComponent == nullptr) - return; + for (const auto& player : PlayerManager::GetAllPlayers()) { + auto* missionComponent = player->GetComponent(); + if (missionComponent == nullptr) + return; - missionComponent->CompleteMission(instanceMissionID); + missionComponent->CompleteMission(instanceMissionID); + } } // There is suppose to be a 0.1 second delay here but that may be admitted? diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 784edbdb..ed0de2ba 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -14,6 +14,7 @@ #include "AgShipPlayerDeathTrigger.h" #include "AgShipPlayerShockServer.h" #include "AgSpaceStuff.h" +#include "AgShipShake.h" #include "AgImagSmashable.h" #include "NpcNpSpacemanBob.h" #include "StoryBoxInteractServer.h" @@ -341,6 +342,7 @@ namespace { { "scripts\\ai\\AG\\L_AG_SHIP_PLAYER_DEATH_TRIGGER.lua", []() { return new AgShipPlayerDeathTrigger(); } }, {"scripts\\ai\\NP\\L_NPC_NP_SPACEMAN_BOB.lua", []() { return new NpcNpSpacemanBob(); } }, {"scripts\\ai\\AG\\L_AG_SPACE_STUFF.lua", []() { return new AgSpaceStuff();} }, + {"scripts\\ai\\AG\\L_AG_SHIP_SHAKE.lua", []() { return new AgShipShake();}}, {"scripts\\ai\\AG\\L_AG_SHIP_PLAYER_SHOCK_SERVER.lua", []() { return new AgShipPlayerShockServer();} }, {"scripts\\ai\\AG\\L_AG_IMAG_SMASHABLE.lua", []() { return new AgImagSmashable();} }, {"scripts\\02_server\\Map\\General\\L_STORY_BOX_INTERACT_SERVER.lua", []() { return new StoryBoxInteractServer();} }, @@ -580,6 +582,7 @@ namespace { {"scripts\\02_server\\Map\\AM\\L_SKULLKIN_DRILL_STAND.lua", []() {return new AmSkullkinDrillStand();}}, {"scripts\\02_server\\Map\\AM\\L_SKULLKIN_TOWER.lua", []() {return new AmSkullkinTower();}}, {"scripts\\02_server\\Enemy\\AM\\L_AM_NAMED_DARKLING_DRAGON.lua", []() {return new AmDarklingDragon();}}, + {"scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_DRAGON.lua", []() {return new AmDarklingDragon();}}, {"scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_APE.lua", []() {return new BaseEnemyApe();}}, {"scripts\\02_server\\Map\\AM\\L_BLUE_X.lua", []() {return new AmBlueX();}}, {"scripts\\02_server\\Map\\AM\\L_TEAPOT_SERVER.lua", []() {return new AmTeapotServer();}}, @@ -654,6 +657,7 @@ namespace { //Pickups {"scripts\\ai\\SPEC\\L_SPECIAL_1_BRONZE-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(1);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_1_GOLD-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(10000);}}, {"scripts\\ai\\SPEC\\L_SPECIAL_1_SILVER-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(100);}}, {"scripts\\ai\\SPEC\\L_SPECIAL_10_BRONZE-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(10);}}, {"scripts\\ai\\SPEC\\L_SPECIAL_10_GOLD-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(100000);}}, @@ -700,7 +704,8 @@ CppScripts::Script* const CppScripts::GetScript(Entity* parent, const std::strin (scriptName == "scripts\\02_server\\Enemy\\General\\L_BASE_ENEMY_SPIDERLING.lua") || (scriptName == "scripts\\ai\\FV\\L_ACT_NINJA_STUDENT.lua") || (scriptName == "scripts\\ai\\WILD\\L_WILD_GF_FROG.lua") || - (scriptName == "scripts\\empty.lua") + (scriptName == "scripts\\empty.lua") || + (scriptName == "scripts\\ai\\AG\\L_AG_SENTINEL_GUARD.lua") )) LOG_DEBUG("LOT %i attempted to load CppScript for '%s', but returned InvalidScript.", parent->GetLOT(), scriptName.c_str()); } diff --git a/dScripts/ai/AG/AgShipShake.cpp b/dScripts/ai/AG/AgShipShake.cpp new file mode 100644 index 00000000..7dd2323a --- /dev/null +++ b/dScripts/ai/AG/AgShipShake.cpp @@ -0,0 +1,81 @@ +#include "AgShipShake.h" +#include "EntityInfo.h" +#include "GeneralUtils.h" +#include "GameMessages.h" +#include "EntityManager.h" +#include "RenderComponent.h" +#include "Entity.h" + +void AgShipShake::OnStartup(Entity* self) { + EntityInfo info{}; + + info.pos = { -418, 585, -30 }; + info.lot = 33; + info.spawnerID = self->GetObjectID(); + + auto* ref = Game::entityManager->CreateEntity(info); + + Game::entityManager->ConstructEntity(ref); + + self->SetVar(u"ShakeObject", ref->GetObjectID()); + + self->AddTimer("ShipShakeIdle", 2.0f); + self->SetVar(u"RandomTime", 10); +} + +void AgShipShake::OnTimerDone(Entity* self, std::string timerName) { + auto* shipFxObject = GetEntityInGroup(ShipFX); + auto* shipFxObject2 = GetEntityInGroup(ShipFX2); + auto* debrisObject = GetEntityInGroup(DebrisFX); + if (timerName == "ShipShakeIdle") { + auto* ref = Game::entityManager->GetEntity(self->GetVar(u"ShakeObject")); + + const auto randomTime = self->GetVar(u"RandomTime"); + auto time = GeneralUtils::GenerateRandomNumber(0, randomTime + 1); + + if (time < randomTime / 2) { + time += randomTime / 2; + } + + self->AddTimer("ShipShakeIdle", static_cast(time)); + + if (ref) + GameMessages::SendPlayEmbeddedEffectOnAllClientsNearObject(ref, FXName, ref->GetObjectID(), 500.0f); + + + if (debrisObject) + GameMessages::SendPlayFXEffect(debrisObject, -1, u"DebrisFall", "Debris", LWOOBJID_EMPTY, 1.0f, 1.0f, true); + + const auto randomFx = GeneralUtils::GenerateRandomNumber(0, 3); + + if (shipFxObject) { + std::string effectType = "shipboom" + std::to_string(randomFx); + GameMessages::SendPlayFXEffect(shipFxObject, 559, GeneralUtils::ASCIIToUTF16(effectType), "FX", LWOOBJID_EMPTY, 1.0f, 1.0f, true); + } + + self->AddTimer("ShipShakeExplode", 5.0f); + + if (shipFxObject2) + RenderComponent::PlayAnimation(shipFxObject2, u"explosion"); + } else if (timerName == "ShipShakeExplode") { + if (shipFxObject) + RenderComponent::PlayAnimation(shipFxObject, u"idle"); + if (shipFxObject2) + RenderComponent::PlayAnimation(shipFxObject2, u"idle"); + } +} + +Entity* AgShipShake::GetEntityInGroup(const std::string& group) { + auto entities = Game::entityManager->GetEntitiesInGroup(group); + Entity* en = nullptr; + + for (auto entity : entities) { + if (entity) { + en = entity; + break; + } + } + + return en; +} + diff --git a/dScripts/ai/AG/AgShipShake.h b/dScripts/ai/AG/AgShipShake.h new file mode 100644 index 00000000..4cc26f96 --- /dev/null +++ b/dScripts/ai/AG/AgShipShake.h @@ -0,0 +1,16 @@ +#pragma once +#include "CppScripts.h" + +class AgShipShake : public CppScripts::Script { +public: + void OnStartup(Entity* self) override; + void OnTimerDone(Entity* self, std::string timerName) override; + + std::string DebrisFX = "DebrisFX"; + std::string ShipFX = "ShipFX"; + std::string ShipFX2 = "ShipFX2"; + std::u16string FXName = u"camshake-bridge"; + +private: + Entity* GetEntityInGroup(const std::string& group); +}; diff --git a/dScripts/ai/AG/AgSpaceStuff.cpp b/dScripts/ai/AG/AgSpaceStuff.cpp index 130d4354..dedbd08c 100644 --- a/dScripts/ai/AG/AgSpaceStuff.cpp +++ b/dScripts/ai/AG/AgSpaceStuff.cpp @@ -8,21 +8,6 @@ void AgSpaceStuff::OnStartup(Entity* self) { self->AddTimer("FloaterScale", 5.0f); - - EntityInfo info{}; - - info.pos = { -418, 585, -30 }; - info.lot = 33; - info.spawnerID = self->GetObjectID(); - - auto* ref = Game::entityManager->CreateEntity(info); - - Game::entityManager->ConstructEntity(ref); - - self->SetVar(u"ShakeObject", ref->GetObjectID()); - - self->AddTimer("ShipShakeIdle", 2.0f); - self->SetVar(u"RandomTime", 10); } void AgSpaceStuff::OnTimerDone(Entity* self, std::string timerName) { @@ -37,70 +22,5 @@ void AgSpaceStuff::OnTimerDone(Entity* self, std::string timerName) { RenderComponent::PlayAnimation(self, u"path_0" + (GeneralUtils::to_u16string(pathType))); self->AddTimer("FloaterScale", randTime); - } else if (timerName == "ShipShakeExplode") { - DoShake(self, true); - } else if (timerName == "ShipShakeIdle") { - DoShake(self, false); } } - -void AgSpaceStuff::DoShake(Entity* self, bool explodeIdle) { - - if (!explodeIdle) { - auto* ref = Game::entityManager->GetEntity(self->GetVar(u"ShakeObject")); - - const auto randomTime = self->GetVar(u"RandomTime"); - auto time = GeneralUtils::GenerateRandomNumber(0, randomTime + 1); - - if (time < randomTime / 2) { - time += randomTime / 2; - } - - self->AddTimer("ShipShakeIdle", static_cast(time)); - - if (ref) - GameMessages::SendPlayEmbeddedEffectOnAllClientsNearObject(ref, FXName, ref->GetObjectID(), 500.0f); - - auto* debrisObject = GetEntityInGroup(DebrisFX); - - if (debrisObject) - GameMessages::SendPlayFXEffect(debrisObject, -1, u"DebrisFall", "Debris", LWOOBJID_EMPTY, 1.0f, 1.0f, true); - - const auto randomFx = GeneralUtils::GenerateRandomNumber(0, 3); - - auto* shipFxObject = GetEntityInGroup(ShipFX); - if (shipFxObject) { - std::string effectType = "shipboom" + std::to_string(randomFx); - GameMessages::SendPlayFXEffect(shipFxObject, 559, GeneralUtils::ASCIIToUTF16(effectType), "FX", LWOOBJID_EMPTY, 1.0f, 1.0f, true); - } - - self->AddTimer("ShipShakeExplode", 5.0f); - - auto* shipFxObject2 = GetEntityInGroup(ShipFX2); - if (shipFxObject2) - RenderComponent::PlayAnimation(shipFxObject2, u"explosion"); - } else { - auto* shipFxObject = GetEntityInGroup(ShipFX); - auto* shipFxObject2 = GetEntityInGroup(ShipFX2); - - if (shipFxObject) - RenderComponent::PlayAnimation(shipFxObject, u"idle"); - - if (shipFxObject2) - RenderComponent::PlayAnimation(shipFxObject2, u"idle"); - } -} - -Entity* AgSpaceStuff::GetEntityInGroup(const std::string& group) { - auto entities = Game::entityManager->GetEntitiesInGroup(group); - Entity* en = nullptr; - - for (auto entity : entities) { - if (entity) { - en = entity; - break; - } - } - - return en; -} diff --git a/dScripts/ai/AG/AgSpaceStuff.h b/dScripts/ai/AG/AgSpaceStuff.h index 8d816691..568f2baf 100644 --- a/dScripts/ai/AG/AgSpaceStuff.h +++ b/dScripts/ai/AG/AgSpaceStuff.h @@ -5,14 +5,5 @@ class AgSpaceStuff : public CppScripts::Script { public: void OnStartup(Entity* self); void OnTimerDone(Entity* self, std::string timerName); - void DoShake(Entity* self, bool explodeIdle); - - std::string DebrisFX = "DebrisFX"; - std::string ShipFX = "ShipFX"; - std::string ShipFX2 = "ShipFX2"; - std::u16string FXName = u"camshake-bridge"; - -private: - Entity* GetEntityInGroup(const std::string& group); }; diff --git a/dScripts/ai/AG/CMakeLists.txt b/dScripts/ai/AG/CMakeLists.txt index e74aac78..101f86fd 100644 --- a/dScripts/ai/AG/CMakeLists.txt +++ b/dScripts/ai/AG/CMakeLists.txt @@ -1,6 +1,7 @@ set(DSCRIPTS_SOURCES_AI_AG "AgShipPlayerDeathTrigger.cpp" "AgSpaceStuff.cpp" + "AgShipShake.cpp" "AgShipPlayerShockServer.cpp" "AgImagSmashable.cpp" "ActSharkPlayerDeathTrigger.cpp" diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 9e55d8b7..d3dc78cc 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -529,6 +529,7 @@ int main(int argc, char** argv) { } void HandlePacketChat(Packet* packet) { + if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { LOG("Lost our connection to chat, zone(%i), instance(%i)", Game::server->GetZoneID(), Game::server->GetInstanceID()); @@ -542,7 +543,7 @@ void HandlePacketChat(Packet* packet) { chatConnected = true; } - if (packet->data[0] == ID_USER_PACKET_ENUM) { + if (packet->data[0] == ID_USER_PACKET_ENUM && packet->length >= 4) { if (static_cast(packet->data[1]) == eConnectionType::CHAT) { switch (static_cast(packet->data[3])) { case eChatMessageType::WORLD_ROUTE_PACKET: { @@ -557,8 +558,9 @@ void HandlePacketChat(Packet* packet) { //Write our stream outwards: CBITSTREAM; - for (BitSize_t i = 0; i < inStream.GetNumberOfBytesUsed(); i++) { - bitStream.Write(packet->data[i + 16]); //16 bytes == header + playerID to skip + unsigned char data; + while (inStream.Read(data)) { + bitStream.Write(data); } SEND_PACKET; //send routed packet to player @@ -659,7 +661,7 @@ void HandlePacketChat(Packet* packet) { } void HandleMasterPacket(Packet* packet) { - + if (packet->length < 2) return; if (static_cast(packet->data[1]) != eConnectionType::MASTER || packet->length < 4) return; switch (static_cast(packet->data[3])) { case eMasterMessageType::REQUEST_PERSISTENT_ID_RESPONSE: { @@ -785,6 +787,7 @@ void HandleMasterPacket(Packet* packet) { } void HandlePacket(Packet* packet) { + if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { auto user = UserManager::Instance()->GetUser(packet->systemAddress); if (!user) return; @@ -1207,8 +1210,8 @@ void HandlePacket(Packet* packet) { //Now write the rest of the data: auto data = inStream.GetData(); - for (uint32_t i = 0; i < size; ++i) { - bitStream.Write(data[i + 23]); + for (uint32_t i = 23; i - 23 < size && i < packet->length; ++i) { + bitStream.Write(data[i]); } Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE_ORDERED, 0, Game::chatSysAddr, false); diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 41135a80..f6056476 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -23,6 +23,11 @@ if(NOT WIN32) target_include_directories(bcrypt PRIVATE "libbcrypt/include/bcrypt") endif() +# Need to define this on Clang and GNU for 'strdup' support +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + target_compile_definitions(bcrypt PRIVATE "_POSIX_C_SOURCE=200809L") +endif() + target_include_directories(bcrypt INTERFACE "libbcrypt/include") target_include_directories(bcrypt PRIVATE "libbcrypt/src")