diff --git a/README.md b/README.md index 321e1b36..de20e7ad 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,11 @@ Darkflame Universe is a server emulator and does not distribute any LEGO® Unive Development of the latest iteration of Darkflame Universe has been done primarily in a Unix-like environment and is where it has been tested and designed for deployment. It is therefore highly recommended that Darkflame Universe be built and deployed using a Unix-like environment for the most streamlined experience. ### Prerequisites -**Clone the repository** +#### Clone the repository ```bash git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer ``` -**Python** +#### Python Some tools utilized to streamline the setup process require Python 3, make sure you have it installed. @@ -52,7 +52,7 @@ Make sure packages like `gcc`, `cmake`, and `zlib` are installed. Depending on t CMake must be version 3.14 or higher! -**Build the repository** +#### Build the repository You can either run `build.sh` when in the root folder of the repository: @@ -70,8 +70,8 @@ cd build # Run CMake to generate make files cmake .. -# Run make to build the project. To build utilizing multiple cores, append `-j` and the amount of cores to utilize, for example `make -j8` -make +# To build utilizing multiple cores, append `-j` and the amount of cores to utilize, for example `cmake --build . --config Release -j8' +cmake --build . --config Release ``` ### MacOS builds @@ -93,7 +93,7 @@ cmake --build . --config Release ### Windows builds (native) Ensure that you have either the [MSVC](https://visualstudio.microsoft.com/vs/) or the [Clang](https://github.com/llvm/llvm-project/releases/) (recommended) compiler installed. You will also need to install [CMake](https://cmake.org/download/). Currently on native Windows the server will only work in Release mode. -**Build the repository** +#### Build the repository ```batch :: Create the build directory mkdir build @@ -105,7 +105,7 @@ cmake .. :: Run CMake with build flag to build cmake --build . --config Release ``` -**Windows for ARM** has not been tested but should build by doing the following +#### Windows for ARM has not been tested but should build by doing the following ```batch :: Create the build directory mkdir build @@ -121,13 +121,13 @@ cmake --build . --config Release ### Windows builds (WSL) This section will go through how to install [WSL](https://docs.microsoft.com/en-us/windows/wsl/install) and building in a Linux environment under Windows. WSL requires Windows 10 version 2004 and higher (Build 19041 and higher) or Windows 11. -**Open the Command Prompt application with Administrator permissions and run the following:** +#### Open the Command Prompt application with Administrator permissions and run the following: ```bash # Installing Windows Subsystem for Linux wsl --install ``` -**Open the Ubuntu application and run the following:** +#### Open the Ubuntu application and run the following: ```bash # Make sure the install is up to date apt update && apt upgrade @@ -159,7 +159,7 @@ now follow the build section for your system ### Resources -**LEGO® Universe 1.10.64** +#### LEGO® Universe 1.10.64 This repository does not distribute any LEGO® Universe files. A full install of LEGO® Universe version 1.10.64 (latest) is required to finish setting up Darkflame Universe. @@ -182,20 +182,20 @@ shasum -a 256 certutil -hashfile SHA256 ``` -**Unpacking the client** +#### Unpacking the client * Clone lcdr's utilities repository [here](https://github.com/lcdr/utils) * Use `pkextractor.pyw` to unpack the client files if they are not already unpacked -**Setup resource directory** +#### Setup resource directory * In the `build` directory create a `res` directory if it does not already exist. * Copy over or create symlinks from `macros`, `BrickModels`, `chatplus_en_us.txt`, `names`, and `maps` in your client `res` directory to the server `build/res` directory * Unzip the navmeshes [here](./resources/navmeshes.zip) and place them in `build/res/maps/navmeshes` -**Setup locale** +#### Setup locale * In the `build` directory create a `locale` directory if it does not already exist * Copy over or create symlinks from `locale.xml` in your client `locale` directory to the `build/locale` directory -**Client database** +#### Client database * Use `fdb_to_sqlite.py` in lcdr's utilities on `res/cdclient.fdb` in the unpacked client to convert the client database to `cdclient.sqlite` * Move and rename `cdclient.sqlite` into `build/res/CDServer.sqlite` * Run each SQL file in the order at which they appear [here](migrations/cdserver/) on the SQLite database @@ -205,13 +205,16 @@ Darkflame Universe utilizes a MySQL/MariaDB database for account and character i Initial setup can vary drastically based on which operating system or distribution you are running; there are instructions out there for most setups, follow those and come back here when you have a database up and running. * Create a database for Darkflame Universe to use -* Use the command `./MasterServer -m` to automatically run them. -**Configuration** +#### Configuration After the server has been built there should be four `ini` files in the build director: `authconfig.ini`, `chatconfig.ini`, `masterconfig.ini`, and `worldconfig.ini`. Go through them and fill in the database credentials and configure other settings if necessary. -**Verify** +#### Setup and Migrations + +Use the command `./MasterServer -m` to setup the tables in the database. The first time this command is run on a database, the tables will be up to date with the most recent version. To update your database tables, run this command again. Multiple invocations will not affect any functionality. + +#### Verify Your build directory should now look like this: * AuthServer diff --git a/dCommon/eUnequippableActiveType.h b/dCommon/eUnequippableActiveType.h new file mode 100644 index 00000000..8a6d29ba --- /dev/null +++ b/dCommon/eUnequippableActiveType.h @@ -0,0 +1,14 @@ +#pragma once + +#ifndef __EUNEQUIPPABLEACTIVETYPE__H__ +#define __EUNEQUIPPABLEACTIVETYPE__H__ + +#include + +enum class eUnequippableActiveType : int32_t { + INVALID = -1, + PET = 0, + MOUNT +}; + +#endif //!__EUNEQUIPPABLEACTIVETYPE__H__ diff --git a/dDatabase/Tables/CDItemComponentTable.cpp b/dDatabase/Tables/CDItemComponentTable.cpp index 703497ff..6ff192fd 100644 --- a/dDatabase/Tables/CDItemComponentTable.cpp +++ b/dDatabase/Tables/CDItemComponentTable.cpp @@ -45,7 +45,7 @@ CDItemComponentTable::CDItemComponentTable(void) { entry.offsetGroupID = tableData.getIntField(19, -1); entry.buildTypes = tableData.getIntField(20, -1); entry.reqPrecondition = tableData.getStringField(21, ""); - entry.animationFlag = tableData.getIntField(22, -1); + entry.animationFlag = tableData.getIntField(22, 0); entry.equipEffects = tableData.getIntField(23, -1); entry.readyForQA = tableData.getIntField(24, -1) == 1 ? true : false; entry.itemRating = tableData.getIntField(25, -1); @@ -123,7 +123,7 @@ const CDItemComponent& CDItemComponentTable::GetItemComponentByID(unsigned int s entry.offsetGroupID = tableData.getIntField(19, -1); entry.buildTypes = tableData.getIntField(20, -1); entry.reqPrecondition = tableData.getStringField(21, ""); - entry.animationFlag = tableData.getIntField(22, -1); + entry.animationFlag = tableData.getIntField(22, 0); entry.equipEffects = tableData.getIntField(23, -1); entry.readyForQA = tableData.getIntField(24, -1) == 1 ? true : false; entry.itemRating = tableData.getIntField(25, -1); diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 89289004..52eddd06 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -1454,6 +1454,13 @@ void Entity::Smash(const LWOOBJID source, const eKillType killType, const std::u Kill(EntityManager::Instance()->GetEntity(source)); return; } + auto* possessorComponent = GetComponent(); + if (possessorComponent) { + if (possessorComponent->GetPossessable() != LWOOBJID_EMPTY) { + auto* mount = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable()); + if (mount) possessorComponent->Dismount(mount, true); + } + } destroyableComponent->Smash(source, killType, deathType); } diff --git a/dGame/dComponents/CharacterComponent.h b/dGame/dComponents/CharacterComponent.h index 8efb4ef5..196dfa01 100644 --- a/dGame/dComponents/CharacterComponent.h +++ b/dGame/dComponents/CharacterComponent.h @@ -167,18 +167,6 @@ public: */ void SetLastRocketItemID(LWOOBJID lastRocketItemID) { m_LastRocketItemID = lastRocketItemID; } - /** - * Gets the object ID of the mount item that is being used - * @return the object ID of the mount item that is being used - */ - LWOOBJID GetMountItemID() const { return m_MountItemID; } - - /** - * Sets the object ID of the mount item that is being used - * @param m_MountItemID the object ID of the mount item that is being used - */ - void SetMountItemID(LWOOBJID mountItemID) { m_MountItemID = mountItemID; } - /** * Gets the name of this character * @return the name of this character @@ -569,11 +557,6 @@ private: * ID of the last rocket used */ LWOOBJID m_LastRocketItemID = LWOOBJID_EMPTY; - - /** - * Mount Item ID - */ - LWOOBJID m_MountItemID = LWOOBJID_EMPTY; }; #endif // CHARACTERCOMPONENT_H diff --git a/dGame/dComponents/ControllablePhysicsComponent.cpp b/dGame/dComponents/ControllablePhysicsComponent.cpp index 471b9ca1..d7caf6a9 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/ControllablePhysicsComponent.cpp @@ -32,6 +32,7 @@ ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity) : Com m_IgnoreMultipliers = false; m_PickupRadius = 0.0f; m_DirtyPickupRadiusScale = true; + m_IsTeleporting = false; if (entity->GetLOT() != 1) // Other physics entities we care about will be added by BaseCombatAI return; @@ -128,7 +129,10 @@ void ControllablePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bo outBitStream->Write0(); } - if (!bIsInitialUpdate) outBitStream->Write0(); + if (!bIsInitialUpdate) { + outBitStream->Write(m_IsTeleporting); + m_IsTeleporting = false; + } } void ControllablePhysicsComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { diff --git a/dGame/dComponents/ControllablePhysicsComponent.h b/dGame/dComponents/ControllablePhysicsComponent.h index bc05657c..ac481b9f 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.h +++ b/dGame/dComponents/ControllablePhysicsComponent.h @@ -220,6 +220,18 @@ public: */ bool GetStatic() const { return m_Static; } + /** + * Sets if the entity is Teleporting, + * @param value whether or not the entity is Is Teleporting + */ + void SetIsTeleporting(const bool value) { m_IsTeleporting = value; } + + /** + * Returns whether or not this entity is currently is teleporting + * @return whether or not this entity is currently is teleporting + */ + bool GetIsTeleporting() const { return m_IsTeleporting; } + /** * Returns the Physics entity for the component * @return Physics entity for the component @@ -355,6 +367,11 @@ private: * The active pickup radius for this entity */ float m_PickupRadius; + + /** + * If the entity is teleporting + */ + bool m_IsTeleporting; }; #endif // CONTROLLABLEPHYSICSCOMPONENT_H diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 1cbf7aad..8b9ae42d 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -26,6 +26,8 @@ #include "MissionComponent.h" #include "CharacterComponent.h" +#include "PossessableComponent.h" +#include "PossessorComponent.h" #include "dZoneManager.h" DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) { @@ -608,6 +610,24 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32 SetHealth(health); SetIsShielded(absorb > 0); + // Dismount on the possessable hit + auto possessable = m_Parent->GetComponent(); + if (possessable && possessable->GetDepossessOnHit()) { + possessable->Dismount(); + } + + // Dismount on the possessor hit + auto possessor = m_Parent->GetComponent(); + if (possessor) { + auto possessableId = possessor->GetPossessable(); + if (possessableId != LWOOBJID_EMPTY) { + auto possessable = EntityManager::Instance()->GetEntity(possessableId); + if (possessable) { + possessor->Dismount(possessable); + } + } + } + if (m_Parent->GetLOT() != 1) { echo = true; } diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 6dbb3bd8..930ace8d 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -26,6 +26,7 @@ #include "DestroyableComponent.h" #include "dConfig.h" #include "eItemType.h" +#include "eUnequippableActiveType.h" InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document) : Component(parent) { this->m_Dirty = true; @@ -62,23 +63,23 @@ InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* do const auto& info = Inventory::FindItemComponent(item.itemid); UpdateSlot(info.equipLocation, { id, static_cast(item.itemid), item.count, slot++ }); - + // Equip this items proxies. auto subItems = info.subItems; - + subItems.erase(std::remove_if(subItems.begin(), subItems.end(), ::isspace), subItems.end()); - + if (!subItems.empty()) { const auto subItemsSplit = GeneralUtils::SplitString(subItems, ','); - + for (auto proxyLotAsString : subItemsSplit) { const auto proxyLOT = static_cast(std::stoi(proxyLotAsString)); - + const auto& proxyInfo = Inventory::FindItemComponent(proxyLOT); const LWOOBJID proxyId = ObjectIDManager::Instance()->GenerateObjectID(); - + // Use item.count since we equip item.count number of the item this is a requested proxy of - UpdateSlot(proxyInfo.equipLocation, { proxyId, proxyLOT, item.count, slot++ } ); + UpdateSlot(proxyInfo.equipLocation, { proxyId, proxyLOT, item.count, slot++ }); } } } @@ -795,9 +796,7 @@ void InventoryComponent::RemoveSlot(const std::string& location) { } void InventoryComponent::EquipItem(Item* item, const bool skipChecks) { - if (!Inventory::IsValidItem(item->GetLot())) { - return; - } + if (!Inventory::IsValidItem(item->GetLot())) return; // Temp items should be equippable but other transfer items shouldn't be (for example the instruments in RB) if (item->IsEquipped() @@ -820,9 +819,7 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) { auto* characterComponent = m_Parent->GetComponent(); - if (characterComponent != nullptr) { - characterComponent->SetLastRocketItemID(item->GetId()); - } + if (characterComponent != nullptr) characterComponent->SetLastRocketItemID(item->GetId()); lauchPad->OnUse(m_Parent); @@ -836,94 +833,8 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) { 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); - - EntityInfo info{}; - info.lot = 8092; - info.pos = startPosition; - info.rot = startRotation; - info.spawnerID = m_Parent->GetObjectID(); - - 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 != nullptr) { - moduleAssemblyComponent->SetSubKey(item->GetSubKey()); - moduleAssemblyComponent->SetUseOptionalParts(false); - - for (auto* config : item->GetConfig()) { - if (config->GetKey() == u"assemblyPartLOTs") { - moduleAssemblyComponent->SetAssemblyPartsLOTs(GeneralUtils::ASCIIToUTF16(config->GetValueAsString())); - } - } - } - // #107 - auto* possessorComponent = m_Parent->GetComponent(); - - if (possessorComponent) possessorComponent->SetPossessable(carEntity->GetObjectID()); - - auto* characterComponent = m_Parent->GetComponent(); - - if (characterComponent) characterComponent->SetIsRacing(true); - - EntityManager::Instance()->ConstructEntity(carEntity); - EntityManager::Instance()->SerializeEntity(m_Parent); - GameMessages::SendSetJetPackMode(m_Parent, false); - - 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::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; - } - - if (!building) { - if (item->GetLot() == 6086) { - return; - } - - if (type == eItemType::ITEM_TYPE_LOOT_MODEL || type == eItemType::ITEM_TYPE_VEHICLE) { - return; - } - } + if (!building && (item->GetLot() == 6086 || type == eItemType::ITEM_TYPE_LOOT_MODEL || type == eItemType::ITEM_TYPE_VEHICLE)) return; if (type != eItemType::ITEM_TYPE_LOOT_MODEL && type != eItemType::ITEM_TYPE_MODEL) { if (!item->GetBound() && !item->GetPreconditionExpression()->Check(m_Parent)) { @@ -940,9 +851,7 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) { set->OnEquip(lot); } - if (item->GetInfo().isBOE) { - item->SetBound(true); - } + if (item->GetInfo().isBOE) item->SetBound(true); GenerateProxies(item); @@ -989,6 +898,85 @@ void InventoryComponent::UnEquipItem(Item* item) { } } +void InventoryComponent::HandlePossession(Item* item) { + auto* characterComponent = m_Parent->GetComponent(); + if (!characterComponent) return; + + auto* possessorComponent = m_Parent->GetComponent(); + if (!possessorComponent) return; + + // Don't do anything if we are busy dismounting + if (possessorComponent->GetIsDismounting()) return; + + // Check to see if we are already mounting something + auto* currentlyPossessedEntity = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable()); + auto currentlyPossessedItem = possessorComponent->GetMountItemID(); + + if (currentlyPossessedItem) { + if (currentlyPossessedEntity) possessorComponent->Dismount(currentlyPossessedEntity); + return; + } + + GameMessages::SendSetStunned(m_Parent->GetObjectID(), eStunState::PUSH, m_Parent->GetSystemAddress(), LWOOBJID_EMPTY, true, false, true, false, false, false, false, true, true, true, true, true, true, true, true, true); + + // Set the mount Item ID so that we know what were handling + possessorComponent->SetMountItemID(item->GetId()); + GameMessages::SendSetMountInventoryID(m_Parent, item->GetId(), UNASSIGNED_SYSTEM_ADDRESS); + + // Create entity to mount + auto startRotation = m_Parent->GetRotation(); + + EntityInfo info{}; + info.lot = item->GetLot(); + info.pos = m_Parent->GetPosition(); + info.rot = startRotation; + info.spawnerID = m_Parent->GetObjectID(); + + auto* mount = EntityManager::Instance()->CreateEntity(info, nullptr, m_Parent); + + // Check to see if the mount is a vehicle, if so, flip it + auto* vehicleComponent = mount->GetComponent(); + if (vehicleComponent) { + auto angles = startRotation.GetEulerAngles(); + // Make it right side up + angles.x -= PI; + // Make it going in the direction of the player + angles.y -= PI; + startRotation = NiQuaternion::FromEulerAngles(angles); + mount->SetRotation(startRotation); + // We're pod racing now + characterComponent->SetIsRacing(true); + } + + // Setup the destroyable stats + auto* destroyableComponent = mount->GetComponent(); + if (destroyableComponent) { + destroyableComponent->SetIsSmashable(false); + destroyableComponent->SetIsImmune(true); + } + + // Mount it + auto* possessableComponent = mount->GetComponent(); + if (possessableComponent) { + possessableComponent->SetIsItemSpawned(true); + possessableComponent->SetPossessor(m_Parent->GetObjectID()); + // Possess it + possessorComponent->SetPossessable(mount->GetObjectID()); + possessorComponent->SetPossessableType(possessableComponent->GetPossessionType()); + } + + GameMessages::SendSetJetPackMode(m_Parent, false); + + // Make it go to the client + EntityManager::Instance()->ConstructEntity(mount); + // Update the possessor + EntityManager::Instance()->SerializeEntity(m_Parent); + + // have to unlock the input so it vehicle can be driven + if (vehicleComponent) GameMessages::SendVehicleUnlockInput(mount->GetObjectID(), false, m_Parent->GetSystemAddress()); + GameMessages::SendMarkInventoryItemAsActive(m_Parent->GetObjectID(), true, eUnequippableActiveType::MOUNT, item->GetId(), m_Parent->GetSystemAddress()); +} + void InventoryComponent::ApplyBuff(Item* item) const { const auto buffs = FindBuffs(item, true); diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 01b4ca86..394cb801 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -198,6 +198,13 @@ public: */ void UnEquipItem(Item* item); + /** + * Unequips an Item from the inventory + * @param item the Item to unequip + * @return if we were successful + */ + void HandlePossession(Item* item); + /** * Adds a buff related to equipping a lot to the entity * @param item the item to find buffs for diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index ef882555..8863f9be 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -14,6 +14,7 @@ #include "dpWorld.h" #include "PetDigServer.h" #include "../dWorldServer/ObjectIDManager.h" +#include "eUnequippableActiveType.h" #include "Game.h" #include "dConfig.h" @@ -883,7 +884,7 @@ void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) { GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress()); } - GameMessages::SendMarkInventoryItemAsActive(m_Owner, true, 0, m_ItemId, GetOwner()->GetSystemAddress()); + GameMessages::SendMarkInventoryItemAsActive(m_Owner, true, eUnequippableActiveType::PET, m_ItemId, GetOwner()->GetSystemAddress()); activePets[m_Owner] = m_Parent->GetObjectID(); @@ -945,7 +946,7 @@ void PetComponent::AddDrainImaginationTimer(Item* item, bool fromTaming) { void PetComponent::Deactivate() { GameMessages::SendPlayFXEffect(m_Parent->GetObjectID(), -1, u"despawn", "", LWOOBJID_EMPTY, 1, 1, true); - GameMessages::SendMarkInventoryItemAsActive(m_Owner, false, 0, m_ItemId, GetOwner()->GetSystemAddress()); + GameMessages::SendMarkInventoryItemAsActive(m_Owner, false, eUnequippableActiveType::PET, m_ItemId, GetOwner()->GetSystemAddress()); activePets.erase(m_Owner); diff --git a/dGame/dComponents/PossessableComponent.cpp b/dGame/dComponents/PossessableComponent.cpp index be31914b..37591532 100644 --- a/dGame/dComponents/PossessableComponent.cpp +++ b/dGame/dComponents/PossessableComponent.cpp @@ -18,7 +18,7 @@ PossessableComponent::PossessableComponent(Entity* parent, uint32_t componentId) // Should a result not exist for this default to attached visible if (!result.eof()) { - m_PossessionType = static_cast(result.getIntField(0, 0)); + m_PossessionType = static_cast(result.getIntField(0, 1)); // Default to Attached Visible m_DepossessOnHit = static_cast(result.getIntField(1, 0)); } else { m_PossessionType = ePossessionType::ATTACHED_VISIBLE; @@ -30,7 +30,7 @@ PossessableComponent::PossessableComponent(Entity* parent, uint32_t componentId) void PossessableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { outBitStream->Write(m_DirtyPossessable || bIsInitialUpdate); if (m_DirtyPossessable || bIsInitialUpdate) { - m_DirtyPossessable = false; + m_DirtyPossessable = false; // reset flag outBitStream->Write(m_Possessor != LWOOBJID_EMPTY); if (m_Possessor != LWOOBJID_EMPTY) outBitStream->Write(m_Possessor); @@ -38,9 +38,18 @@ void PossessableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsIn if (m_AnimationFlag != eAnimationFlags::IDLE_INVALID) outBitStream->Write(m_AnimationFlag); outBitStream->Write(m_ImmediatelyDepossess); + m_ImmediatelyDepossess = false; // reset flag } } -void PossessableComponent::OnUse(Entity* originator) { - // TODO: Implement this +void PossessableComponent::Dismount() { + SetPossessor(LWOOBJID_EMPTY); + if (m_ItemSpawned) m_Parent->ScheduleKillAfterUpdate(); +} + +void PossessableComponent::OnUse(Entity* originator) { + auto* possessor = originator->GetComponent(); + if (possessor) { + possessor->Mount(m_Parent); + } } diff --git a/dGame/dComponents/PossessableComponent.h b/dGame/dComponents/PossessableComponent.h index 77905c9c..43ce8610 100644 --- a/dGame/dComponents/PossessableComponent.h +++ b/dGame/dComponents/PossessableComponent.h @@ -20,14 +20,24 @@ public: void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); /** - * Sets the possessor of this entity + * @brief mounts the Entity + */ + void Mount(); + + /** + * @brief dismounts the Entity + */ + void Dismount(); + + /** + * Sets the possessor of this Entity * @param value the ID of the possessor to set */ void SetPossessor(LWOOBJID value) { m_Possessor = value; m_DirtyPossessable = true; }; /** - * Returns the possessor of this entity - * @return the possessor of this entity + * Returns the possessor of this Entity + * @return the possessor of this Entity */ LWOOBJID GetPossessor() const { return m_Possessor; }; @@ -38,19 +48,19 @@ public: void SetAnimationFlag(eAnimationFlags value) { m_AnimationFlag = value; m_DirtyPossessable = true; }; /** - * Returns the possession type of this entity - * @return the possession type of this entity + * Returns the possession type of this Entity + * @return the possession type of this Entity */ ePossessionType GetPossessionType() const { return m_PossessionType; }; /** - * Returns if the entity should deposses on hit - * @return if the entity should deposses on hit + * Returns if the Entity should deposses on hit + * @return if the Entity should deposses on hit */ bool GetDepossessOnHit() const { return m_DepossessOnHit; }; /** - * Forcibly depossess the entity + * Forcibly depossess the Entity */ void ForceDepossess() { m_ImmediatelyDepossess = true; m_DirtyPossessable = true; }; @@ -58,13 +68,13 @@ public: * Set if the parent entity was spawned from an item * @param value if the parent entity was spawned from an item */ - void SetItemSpawned(bool value) { m_ItemSpawned = value; }; + void SetIsItemSpawned(bool value) { m_ItemSpawned = value; }; /** * Returns if the parent entity was spawned from an item * @return if the parent entity was spawned from an item */ - LWOOBJID GetItemSpawned() const { return m_ItemSpawned; }; + LWOOBJID GetIsItemSpawned() const { return m_ItemSpawned; }; /** * Handles an OnUsed event by some other entity, if said entity has a Possessor it becomes the possessor diff --git a/dGame/dComponents/PossessorComponent.cpp b/dGame/dComponents/PossessorComponent.cpp index 4ace324b..f0cad5ca 100644 --- a/dGame/dComponents/PossessorComponent.cpp +++ b/dGame/dComponents/PossessorComponent.cpp @@ -1,12 +1,28 @@ #include "PossessorComponent.h" +#include "PossessableComponent.h" +#include "CharacterComponent.h" +#include "EntityManager.h" +#include "GameMessages.h" +#include "eUnequippableActiveType.h" PossessorComponent::PossessorComponent(Entity* parent) : Component(parent) { m_Possessable = LWOOBJID_EMPTY; } -PossessorComponent::~PossessorComponent() {} - - +PossessorComponent::~PossessorComponent() { + if (m_Possessable != LWOOBJID_EMPTY) { + auto* mount = EntityManager::Instance()->GetEntity(m_Possessable); + if (mount) { + auto* possessable = mount->GetComponent(); + if (possessable) { + if (possessable->GetIsItemSpawned()) { + GameMessages::SendMarkInventoryItemAsActive(m_Parent->GetObjectID(), false, eUnequippableActiveType::MOUNT, GetMountItemID(), m_Parent->GetSystemAddress()); + } + possessable->Dismount(); + } + } + } +} void PossessorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { outBitStream->Write(m_DirtyPossesor || bIsInitialUpdate); @@ -19,3 +35,48 @@ void PossessorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInit outBitStream->Write(m_PossessableType); } } + +void PossessorComponent::Mount(Entity* mount) { + // Don't do anything if we are busy dismounting + if (GetIsDismounting() || !mount) return; + + GameMessages::SendSetMountInventoryID(m_Parent, mount->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + auto* possessableComponent = mount->GetComponent(); + if (possessableComponent) { + possessableComponent->SetPossessor(m_Parent->GetObjectID()); + SetPossessable(mount->GetObjectID()); + SetPossessableType(possessableComponent->GetPossessionType()); + } + + auto characterComponent = m_Parent->GetComponent(); + if (characterComponent) characterComponent->SetIsRacing(true); + + // GM's to send + GameMessages::SendSetJetPackMode(m_Parent, false); + GameMessages::SendVehicleUnlockInput(mount->GetObjectID(), false, m_Parent->GetSystemAddress()); + GameMessages::SendSetStunned(m_Parent->GetObjectID(), eStunState::PUSH, m_Parent->GetSystemAddress(), LWOOBJID_EMPTY, true, false, true, false, false, false, false, true, true, true, true, true, true, true, true, true); + + EntityManager::Instance()->SerializeEntity(m_Parent); + EntityManager::Instance()->SerializeEntity(mount); +} + +void PossessorComponent::Dismount(Entity* mount, bool forceDismount) { + // Don't do anything if we are busy dismounting + if (GetIsDismounting() || !mount) return; + SetIsDismounting(true); + + if (mount) { + auto* possessableComponent = mount->GetComponent(); + if (possessableComponent) { + possessableComponent->SetPossessor(LWOOBJID_EMPTY); + if (forceDismount) possessableComponent->ForceDepossess(); + } + EntityManager::Instance()->SerializeEntity(m_Parent); + EntityManager::Instance()->SerializeEntity(mount); + + auto characterComponent = m_Parent->GetComponent(); + if (characterComponent) characterComponent->SetIsRacing(false); + } + // Make sure we don't have wacky controls + GameMessages::SendSetPlayerControlScheme(m_Parent, eControlSceme::SCHEME_A); +} diff --git a/dGame/dComponents/PossessorComponent.h b/dGame/dComponents/PossessorComponent.h index 2735adac..00b24445 100644 --- a/dGame/dComponents/PossessorComponent.h +++ b/dGame/dComponents/PossessorComponent.h @@ -25,35 +25,63 @@ public: void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); /** - * Sets the entity that this entity is possessing - * @param value the ID of the entity this ID should posess + * @brief Mounts the entity + * + * @param mount Entity to be mounted + */ + void Mount(Entity* mount); + + /** + * @brief Dismounts the entity + * + * @param mount Entity to be dismounted + * @param forceDismount Should we forcibly dismount the entity + */ + void Dismount(Entity* mount, bool forceDismount = false); + + /** + * Sets the ID that this entity is possessing + * @param value The ID that this entity is possessing */ void SetPossessable(LWOOBJID value) { m_Possessable = value; m_DirtyPossesor = true; } /** * Returns the entity that this entity is currently posessing - * @return the entity that this entity is currently posessing + * @return The entity that this entity is currently posessing */ LWOOBJID GetPossessable() const { return m_Possessable; } /** - * Sets if we are busy mounting or dismounting - * @param value if we are busy mounting or dismounting + * Sets if we are busy dismounting + * @param value If we are busy dismounting */ - void SetIsBusy(bool value) { m_IsBusy = value; } + void SetIsDismounting(bool value) { m_IsDismounting = value; } /** - * Returns if we are busy mounting or dismounting - * @return if we are busy mounting or dismounting + * Returns if we are busy dismounting + * @return If we are busy dismounting */ - bool GetIsBusy() const { return m_IsBusy; } + bool GetIsDismounting() const { return m_IsDismounting; } /** * Sets the possesible type that's currently used, merely used by the shooting gallery if it's 0 - * @param value the possesible type to set + * @param value The possesible type to set */ void SetPossessableType(ePossessionType value) { m_PossessableType = value; m_DirtyPossesor = true; } + + /** + * Gets the object ID of the mount item that is being used + * @return The object ID of the mount item that is being used + */ + LWOOBJID GetMountItemID() const { return m_MountItemID; } + + /** + * Sets the object ID of the mount item that is being used + * @param m_MountItemID The object ID of the mount item that is being used + */ + void SetMountItemID(LWOOBJID mountItemID) { m_MountItemID = mountItemID; } + private: /** @@ -68,14 +96,19 @@ private: ePossessionType m_PossessableType = ePossessionType::NO_POSSESSION; /** - * @brief if the possessor is dirty + * @brief If the possessor is dirty * */ bool m_DirtyPossesor = false; /** - * @brief if the possessor is busy mounting or dismounting + * @brief If the possessor is busy dismounting * */ - bool m_IsBusy = false; + bool m_IsDismounting = false; + + /** + * Mount Item ID + */ + LWOOBJID m_MountItemID = LWOOBJID_EMPTY; }; diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index b3b60fe7..9cf09bbc 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -653,6 +653,10 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System GameMessages::HandleUpdatePlayerStatistic(inStream, entity); break; + case GAME_MSG_DISMOUNT_COMPLETE: + GameMessages::HandleDismountComplete(inStream, entity, sysAddr); + break; + default: //Game::logger->Log("GameMessageHandler", "Unknown game message ID: %X", messageID); break; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 2586b411..e34b7fe7 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -27,6 +27,7 @@ #include "ChatPackets.h" #include "GameConfig.h" #include "RocketLaunchLupComponent.h" +#include "eUnequippableActiveType.h" #include #include @@ -3367,7 +3368,7 @@ void GameMessages::SendRegisterPetDBID(LWOOBJID objectId, LWOOBJID petDBID, cons SEND_PACKET; } -void GameMessages::SendMarkInventoryItemAsActive(LWOOBJID objectId, bool bActive, int32_t iType, LWOOBJID itemID, const SystemAddress& sysAddr) { +void GameMessages::SendMarkInventoryItemAsActive(LWOOBJID objectId, bool bActive, eUnequippableActiveType iType, LWOOBJID itemID, const SystemAddress& sysAddr) { CBITSTREAM; CMSGHEADER; @@ -3376,8 +3377,8 @@ void GameMessages::SendMarkInventoryItemAsActive(LWOOBJID objectId, bool bActive bitStream.Write(bActive); - bitStream.Write(iType != 0); - if (iType != 0) bitStream.Write(iType); + bitStream.Write(iType != eUnequippableActiveType::INVALID); + if (iType != eUnequippableActiveType::INVALID) bitStream.Write(iType); bitStream.Write(itemID != LWOOBJID_EMPTY); if (itemID != LWOOBJID_EMPTY) bitStream.Write(itemID); @@ -3814,7 +3815,6 @@ void GameMessages::SendDisplayChatBubble(LWOOBJID objectId, const std::u16string void GameMessages::SendSetMountInventoryID(Entity* entity, const LWOOBJID& objectID, const SystemAddress& sysAddr) { CBITSTREAM; CMSGHEADER; - bitStream.Write(entity->GetObjectID()); bitStream.Write(GAME_MSG::GAME_MSG_SET_MOUNT_INVENTORY_ID); bitStream.Write(objectID); @@ -3824,30 +3824,53 @@ void GameMessages::SendSetMountInventoryID(Entity* entity, const LWOOBJID& objec void GameMessages::HandleDismountComplete(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { + // Get the objectID from the bitstream LWOOBJID objectId{}; inStream->Read(objectId); - auto* mount = EntityManager::Instance()->GetEntity(objectId); + // If we aren't possessing somethings, the don't do anything if (objectId != LWOOBJID_EMPTY) { - PossessorComponent* possessor; - if (entity->TryGetComponent(COMPONENT_TYPE_POSSESSOR, possessor)) { - if (mount) { - possessor->SetIsBusy(false); - possessor->SetPossessable(LWOOBJID_EMPTY); - possessor->SetPossessableType(ePossessionType::NO_POSSESSION); + auto* possessorComponent = entity->GetComponent(); + auto* mount = EntityManager::Instance()->GetEntity(objectId); + // make sure we have the things we need and they aren't null + if (possessorComponent && mount) { + if (!possessorComponent->GetIsDismounting()) return; + possessorComponent->SetIsDismounting(false); + possessorComponent->SetPossessable(LWOOBJID_EMPTY); + possessorComponent->SetPossessableType(ePossessionType::NO_POSSESSION); - GameMessages::SendSetStunned(entity->GetObjectID(), eStunState::POP, UNASSIGNED_SYSTEM_ADDRESS, LWOOBJID_EMPTY, true, false, true, false, false, false, false, true, true, true, true, true, true, true, true, true); - - EntityManager::Instance()->SerializeEntity(entity); + // character related things + auto* character = entity->GetComponent(); + if (character) { + // If we had an active item turn it off + if (possessorComponent->GetMountItemID() != LWOOBJID_EMPTY) GameMessages::SendMarkInventoryItemAsActive(entity->GetObjectID(), false, eUnequippableActiveType::MOUNT, possessorComponent->GetMountItemID(), entity->GetSystemAddress()); + possessorComponent->SetMountItemID(LWOOBJID_EMPTY); } + + // Set that the controllabel phsyics comp is teleporting + auto* controllablePhysicsComponent = entity->GetComponent(); + if (controllablePhysicsComponent) controllablePhysicsComponent->SetIsTeleporting(true); + + // Call dismoint on the possessable comp to let it handle killing the possessable + auto* possessableComponent = mount->GetComponent(); + if (possessableComponent) possessableComponent->Dismount(); + + // Update the entity that was possessing + EntityManager::Instance()->SerializeEntity(entity); + + // We aren't mounted so remove the stun + GameMessages::SendSetStunned(entity->GetObjectID(), eStunState::POP, UNASSIGNED_SYSTEM_ADDRESS, LWOOBJID_EMPTY, true, false, true, false, false, false, false, true, true, true, true, true, true, true, true, true); } } } void GameMessages::HandleAcknowledgePossession(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { - Game::logger->Log("HandleAcknowledgePossession", "Got AcknowledgePossession from %i", entity->GetLOT()); EntityManager::Instance()->SerializeEntity(entity); + LWOOBJID objectId{}; + inStream->Read(objectId); + auto* mount = EntityManager::Instance()->GetEntity(objectId); + if (mount) EntityManager::Instance()->SerializeEntity(mount); } //Racing @@ -4698,12 +4721,6 @@ void GameMessages::HandleFireEventServerSide(RakNet::BitStream* inStream, Entity return; } - if (args == u"toggleMail") { - AMFArrayValue args; - args.InsertValue("visible", new AMFFalseValue()); - GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, "ToggleMail", &args); - } - // This should probably get it's own "ServerEvents" system or something at some point if (args == u"ZonePlayer") { // Should probably check to make sure they're using a launcher at some point before someone makes a hack that lets you testmap diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 47e5f41c..31bdebb3 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -19,6 +19,7 @@ class NiQuaternion; class User; class Entity; class NiPoint3; +enum class eUnequippableActiveType; namespace GameMessages { class PropertyDataMessage; @@ -318,7 +319,7 @@ namespace GameMessages { void SendRegisterPetDBID(LWOOBJID objectId, LWOOBJID petDBID, const SystemAddress& sysAddr); - void SendMarkInventoryItemAsActive(LWOOBJID objectId, bool bActive, int32_t iType, LWOOBJID itemID, const SystemAddress& sysAddr); + void SendMarkInventoryItemAsActive(LWOOBJID objectId, bool bActive, eUnequippableActiveType iType, LWOOBJID itemID, const SystemAddress& sysAddr); void SendClientExitTamingMinigame(LWOOBJID objectId, bool bVoluntaryExit, const SystemAddress& sysAddr); diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index 62e198a3..695ab47b 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -10,6 +10,9 @@ #include "dLogger.h" #include "EntityManager.h" #include "RenderComponent.h" +#include "PossessableComponent.h" +#include "CharacterComponent.h" +#include "eItemType.h" class Inventory; @@ -71,6 +74,12 @@ Item::Item( id = GeneralUtils::SetBit(id, OBJECT_BIT_CHARACTER); id = GeneralUtils::SetBit(id, OBJECT_BIT_PERSISTENT); + const auto type = static_cast(info->itemType); + + if (type == eItemType::ITEM_TYPE_MOUNT) { + id = GeneralUtils::SetBit(id, OBJECT_BIT_CLIENT); + } + this->id = id; inventory->AddManagedItem(this); @@ -254,49 +263,34 @@ bool Item::Consume() { return success; } -bool Item::UseNonEquip() { - auto* compRegistryTable = CDClientManager::Instance()->GetTable("ComponentsRegistry"); - - const auto packageComponentId = compRegistryTable->GetByIDAndType(lot, COMPONENT_TYPE_PACKAGE); - - auto* packCompTable = CDClientManager::Instance()->GetTable("PackageComponent"); - - auto packages = packCompTable->Query([=](const CDPackageComponent entry) {return entry.id == static_cast(packageComponentId); }); - - const auto success = !packages.empty(); - - auto inventoryComponent = inventory->GetComponent(); - - auto playerEntity = inventoryComponent->GetParent(); - - if (subKey != LWOOBJID_EMPTY) { +void Item::UseNonEquip() { + const auto type = static_cast(info->itemType); + if (type == eItemType::ITEM_TYPE_MOUNT) { + GetInventory()->GetComponent()->HandlePossession(this); + } else if (type == eItemType::ITEM_TYPE_PET_INVENTORY_ITEM && subKey != LWOOBJID_EMPTY) { const auto& databasePet = GetInventory()->GetComponent()->GetDatabasePet(subKey); - if (databasePet.lot != LOT_NULL) { GetInventory()->GetComponent()->SpawnPet(this); - - return true; } - } - if (success && (playerEntity->GetGMLevel() >= eGameMasterLevel::GAME_MASTER_LEVEL_JUNIOR_DEVELOPER || this->GetPreconditionExpression()->Check(playerEntity))) { - auto* entityParent = inventory->GetComponent()->GetParent(); + } else if (type == eItemType::ITEM_TYPE_PACKAGE) { + auto* compRegistryTable = CDClientManager::Instance()->GetTable("ComponentsRegistry"); + const auto packageComponentId = compRegistryTable->GetByIDAndType(lot, COMPONENT_TYPE_PACKAGE); + auto* packCompTable = CDClientManager::Instance()->GetTable("PackageComponent"); + auto packages = packCompTable->Query([=](const CDPackageComponent entry) {return entry.id == static_cast(packageComponentId); }); - for (auto& pack : packages) { - std::unordered_map result{}; - - result = LootGenerator::Instance().RollLootMatrix(entityParent, pack.LootMatrixIndex); - - if (!inventory->GetComponent()->HasSpaceForLoot(result)) { - return false; + const auto success = !packages.empty(); + if (success) { + auto* entityParent = inventory->GetComponent()->GetParent(); + for (auto& pack : packages) { + std::unordered_map result{}; + result = LootGenerator::Instance().RollLootMatrix(entityParent, pack.LootMatrixIndex); + if (!inventory->GetComponent()->HasSpaceForLoot(result)) { + } + LootGenerator::Instance().GiveLoot(inventory->GetComponent()->GetParent(), result, eLootSourceType::LOOT_SOURCE_CONSUMPTION); } - - LootGenerator::Instance().GiveLoot(inventory->GetComponent()->GetParent(), result, eLootSourceType::LOOT_SOURCE_CONSUMPTION); + inventory->GetComponent()->RemoveItem(lot, 1); } - Game::logger->Log("Item", "Used (%i)", lot); - inventory->GetComponent()->RemoveItem(lot, 1); } - - return success; } void Item::Disassemble(const eInventoryType inventoryType) { diff --git a/dGame/dInventory/Item.h b/dGame/dInventory/Item.h index 0613ca85..db7e246a 100644 --- a/dGame/dInventory/Item.h +++ b/dGame/dInventory/Item.h @@ -193,9 +193,8 @@ public: /** * Uses this item if its non equip, essentially an interface for the linked GM - * @return whether the use was successful, e.g. the skill was cast */ - bool UseNonEquip(); + void UseNonEquip(); /** * Disassembles the part LOTs of this item back into the inventory, if it has any diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index b91021de..c6f1da9f 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -419,6 +419,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit 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); + auto* possessorComponent = entity->GetComponent(); + if (possessorComponent) { + auto* possessedComponent = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable()); + if (possessedComponent) GameMessages::SendPlayAnimation(possessedComponent, anim); + } } if (chatCommand == "list-spawns" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { @@ -471,12 +476,24 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit auto* controllablePhysicsComponent = entity->GetComponent(); - if (controllablePhysicsComponent == nullptr) { - return; - } - + if (!controllablePhysicsComponent) return; controllablePhysicsComponent->SetSpeedMultiplier(boost); + // speedboost possesables + auto possessor = entity->GetComponent(); + if (possessor) { + auto possessedID = possessor->GetPossessable(); + if (possessedID != LWOOBJID_EMPTY) { + auto possessable = EntityManager::Instance()->GetEntity(possessedID); + if (possessable) { + auto* possessControllablePhysicsComponent = possessable->GetComponent(); + if (possessControllablePhysicsComponent) { + possessControllablePhysicsComponent->SetSpeedMultiplier(boost); + } + } + } + } + EntityManager::Instance()->SerializeEntity(entity); } @@ -894,21 +911,17 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /teleport () - if no Y given, will teleport to the height of the terrain (or any physics object)."); } - auto* possessorComponent = entity->GetComponent(); - if (possessorComponent != nullptr) { + auto* possessorComponent = entity->GetComponent(); + if (possessorComponent) { auto* possassableEntity = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable()); if (possassableEntity != nullptr) { auto* vehiclePhysicsComponent = possassableEntity->GetComponent(); - - if (vehiclePhysicsComponent != nullptr) { + if (vehiclePhysicsComponent) { vehiclePhysicsComponent->SetPosition(pos); - EntityManager::Instance()->SerializeEntity(possassableEntity); - - Game::logger->Log("ClientPackets", "Forced updated vehicle position"); - } + } else GameMessages::SendTeleport(possassableEntity->GetObjectID(), pos, NiQuaternion(), sysAddr); } } } @@ -926,18 +939,12 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } if (chatCommand == "dismount" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { - PossessorComponent* possessorComponent; - if (entity->TryGetComponent(COMPONENT_TYPE_POSSESSOR, possessorComponent)) { - Entity* vehicle = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable()); - if (!vehicle) return; - - PossessableComponent* possessableComponent; - if (vehicle->TryGetComponent(COMPONENT_TYPE_POSSESSABLE, possessableComponent)) { - possessableComponent->SetPossessor(LWOOBJID_EMPTY); - possessorComponent->SetPossessable(LWOOBJID_EMPTY); - - EntityManager::Instance()->SerializeEntity(vehicle); - EntityManager::Instance()->SerializeEntity(entity); + auto* possessorComponent = entity->GetComponent(); + if (possessorComponent) { + auto possessableId = possessorComponent->GetPossessable(); + if (possessableId != LWOOBJID_EMPTY) { + auto* possessableEntity = EntityManager::Instance()->GetEntity(possessableId); + if (possessableEntity) possessorComponent->Dismount(possessableEntity, true); } } } @@ -1231,6 +1238,18 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } + auto vehiclePhysicsComponent = newEntity->GetComponent(); + if (vehiclePhysicsComponent) { + auto newRot = newEntity->GetRotation(); + auto angles = newRot.GetEulerAngles(); + // make it right side up + angles.x -= PI; + // make it going in the direction of the player + angles.y -= PI; + newRot = NiQuaternion::FromEulerAngles(angles); + newEntity->SetRotation(newRot); + } + EntityManager::Instance()->ConstructEntity(newEntity); } @@ -1520,7 +1539,33 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } - GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + if (args.size() >= 1) { + float time; + + if (!GeneralUtils::TryParse(args[0], time)) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost time."); + return; + } else { + GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + entity->AddCallbackTimer(time, [vehicle]() { + if (!vehicle) return; + GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + }); + } + } else { + GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + } + + } + + if ((chatCommand == "unboost") && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { + auto* possessorComponent = entity->GetComponent(); + + if (possessorComponent == nullptr) return; + auto* vehicle = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable()); + + if (vehicle == nullptr) return; + GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); } if (chatCommand == "activatespawner" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER && args.size() >= 1) { diff --git a/dNet/ClientPackets.cpp b/dNet/ClientPackets.cpp index b49801cc..39e835d2 100644 --- a/dNet/ClientPackets.cpp +++ b/dNet/ClientPackets.cpp @@ -140,14 +140,19 @@ void ClientPackets::HandleClientPositionUpdate(const SystemAddress& sysAddr, Pac inStream.Read(angVelocity.z); } - bool hasVehicle = false; + bool updateChar = true; if (possessorComponent != nullptr) { auto* possassableEntity = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable()); if (possassableEntity != nullptr) { - auto* vehiclePhysicsComponent = possassableEntity->GetComponent(); + auto* possessableComponent = possassableEntity->GetComponent(); + if (possessableComponent) { + // While possessing something, only update char if we are attached to the thing we are possessing + if (possessableComponent->GetPossessionType() != ePossessionType::ATTACHED_VISIBLE) updateChar = false; + } + auto* vehiclePhysicsComponent = possassableEntity->GetComponent(); if (vehiclePhysicsComponent != nullptr) { // This is flipped for whatever reason rotation = NiQuaternion(rotation.z, rotation.y, rotation.x, rotation.w); @@ -160,19 +165,30 @@ void ClientPackets::HandleClientPositionUpdate(const SystemAddress& sysAddr, Pac vehiclePhysicsComponent->SetDirtyVelocity(velocityFlag); vehiclePhysicsComponent->SetAngularVelocity(angVelocity); vehiclePhysicsComponent->SetDirtyAngularVelocity(angVelocityFlag); - - EntityManager::Instance()->SerializeEntity(possassableEntity); - - hasVehicle = true; + } else { + // Need to get the mount's controllable physics + auto* controllablePhysicsComponent = possassableEntity->GetComponent(); + if (!controllablePhysicsComponent) return; + controllablePhysicsComponent->SetPosition(position); + controllablePhysicsComponent->SetRotation(rotation); + controllablePhysicsComponent->SetIsOnGround(onGround); + controllablePhysicsComponent->SetIsOnRail(onRail); + controllablePhysicsComponent->SetVelocity(velocity); + controllablePhysicsComponent->SetDirtyVelocity(velocityFlag); + controllablePhysicsComponent->SetAngularVelocity(angVelocity); + controllablePhysicsComponent->SetDirtyAngularVelocity(angVelocityFlag); } + EntityManager::Instance()->SerializeEntity(possassableEntity); } } - if (hasVehicle) { + if (!updateChar) { velocity = NiPoint3::ZERO; angVelocity = NiPoint3::ZERO; } + + // Handle statistics auto* characterComponent = entity->GetComponent(); if (characterComponent != nullptr) { @@ -192,9 +208,7 @@ void ClientPackets::HandleClientPositionUpdate(const SystemAddress& sysAddr, Pac player->SetGhostReferencePoint(position); EntityManager::Instance()->QueueGhostUpdate(player->GetObjectID()); - if (!hasVehicle) { - EntityManager::Instance()->SerializeEntity(entity); - } + if (updateChar) EntityManager::Instance()->SerializeEntity(entity); //TODO: add moving platform stuffs /*bool movingPlatformFlag; diff --git a/dScripts/AgQbWall.cpp b/dScripts/AgQbWall.cpp new file mode 100644 index 00000000..d6222419 --- /dev/null +++ b/dScripts/AgQbWall.cpp @@ -0,0 +1,15 @@ +#include "AgQbWall.h" + +void AgQbWall::OnRebuildComplete(Entity* self, Entity* player) { + self->SetVar(u"player", player->GetObjectID()); + auto targetWallSpawners = GeneralUtils::UTF16ToWTF8(self->GetVar(u"spawner")); + if (targetWallSpawners != "") { + auto groupObjs = EntityManager::Instance()->GetEntitiesInGroup(targetWallSpawners); + for (auto* obj : groupObjs) { + if (obj) { + obj->SetVar(u"player", player->GetObjectID()); + obj->OnFireEventServerSide(self, "spawnMobs"); + } + } + } +} diff --git a/dScripts/AgQbWall.h b/dScripts/AgQbWall.h new file mode 100644 index 00000000..93187e0e --- /dev/null +++ b/dScripts/AgQbWall.h @@ -0,0 +1,7 @@ +#pragma once +#include "CppScripts.h" + +class AgQbWall : public CppScripts::Script { +public: + void OnRebuildComplete(Entity* self, Entity* player) override; +}; diff --git a/dScripts/CMakeLists.txt b/dScripts/CMakeLists.txt index 4476ad7c..0a907a05 100644 --- a/dScripts/CMakeLists.txt +++ b/dScripts/CMakeLists.txt @@ -21,6 +21,7 @@ set(DSCRIPT_SOURCES "ActivityManager.cpp" "AgPropGuard.cpp" "AgPropguards.cpp" "AgQbElevator.cpp" + "AgQbWall.cpp" "AgSalutingNpcs.cpp" "AgShipPlayerDeathTrigger.cpp" "AgShipPlayerShockServer.cpp" @@ -94,11 +95,13 @@ set(DSCRIPT_SOURCES "ActivityManager.cpp" "FvConsoleRightQuickbuild.cpp" "FvDragonSmashingGolemQb.cpp" "FvFacilityBrick.cpp" + "FvFacilityPipes.cpp" "FvFlyingCreviceDragon.cpp" "FvFong.cpp" "FvFreeGfNinjas.cpp" "FvHorsemenTrigger.cpp" "FvMaelstromCavalry.cpp" + "FvMaelstromGeyser.cpp" "FvMaelstromDragon.cpp" "FvNinjaGuard.cpp" "FvPandaServer.cpp" @@ -113,7 +116,9 @@ set(DSCRIPT_SOURCES "ActivityManager.cpp" "GfCaptainsCannon.cpp" "GfJailkeepMission.cpp" "GfJailWalls.cpp" + "GfMaelstromGeyser.cpp" "GfOrgan.cpp" + "GfParrotCrash.cpp" "GfTikiTorch.cpp" "GrowingFlower.cpp" "HydrantBroken.cpp" @@ -193,6 +198,7 @@ set(DSCRIPT_SOURCES "ActivityManager.cpp" "PetDigServer.cpp" "PetFromDigServer.cpp" "PetFromObjectServer.cpp" + "PirateRep.cpp" "PropertyBankInteract.cpp" "PropertyDeathPlane.cpp" "PropertyDevice.cpp" @@ -201,6 +207,7 @@ set(DSCRIPT_SOURCES "ActivityManager.cpp" "PrSeagullFly.cpp" "PrWhistle.cpp" "QbEnemyStunner.cpp" + "QbSpawner.cpp" "RaceImagineCrateServer.cpp" "RaceImaginePowerup.cpp" "RaceMaelstromGeiser.cpp" @@ -240,6 +247,7 @@ set(DSCRIPT_SOURCES "ActivityManager.cpp" "WaveBossHammerling.cpp" "WaveBossHorsemen.cpp" "WaveBossSpiderling.cpp" + "WblGenericZone.cpp" "WhFans.cpp" "WildAmbients.cpp" "WishingWellServer.cpp" diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 82cd2ebc..d313f2f9 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -61,6 +61,8 @@ #include "VeMissionConsole.h" #include "VeEpsilonServer.h" #include "AgSurvivalBuffStation.h" +#include "QbSpawner.h" +#include "AgQbWall.h" // NS Scripts #include "NsModularBuild.h" @@ -117,6 +119,9 @@ #include "GfApeSmashingQB.h" #include "ZoneGfProperty.h" #include "GfArchway.h" +#include "GfMaelstromGeyser.h" +#include "PirateRep.h" +#include "GfParrotCrash.h" // SG Scripts #include "SGCannon.h" @@ -141,12 +146,14 @@ #include "FvConsoleLeftQuickbuild.h" #include "FvConsoleRightQuickbuild.h" #include "FvFacilityBrick.h" +#include "FvFacilityPipes.h" #include "ImgBrickConsoleQB.h" #include "ActParadoxPipeFix.h" #include "FvNinjaGuard.h" #include "FvPassThroughWall.h" #include "FvBounceOverWall.h" #include "FvFong.h" +#include "FvMaelstromGeyser.h" // FB Scripts #include "AgJetEffectServer.h" @@ -281,6 +288,9 @@ #include "RockHydrantBroken.h" #include "WhFans.h" +// WBL scripts +#include "WblGenericZone.h" + //Big bad global bc this is a namespace and not a class: InvalidScript* invalidToReturn = new InvalidScript(); std::map m_Scripts; @@ -461,6 +471,10 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr script = new WildAmbients(); else if (scriptName == "scripts\\ai\\NS\\NS_PP_01\\L_NS_PP_01_TELEPORT.lua") script = new PropertyDeathPlane(); + else if (scriptName == "scripts\\02_server\\Map\\General\\L_QB_SPAWNER.lua") + script = new QbSpawner(); + else if (scriptName == "scripts\\ai\\AG\\L_AG_QB_Wall.lua") + script = new AgQbWall(); //GF: else if (scriptName == "scripts\\02_server\\Map\\GF\\L_GF_TORCH.lua") @@ -495,9 +509,14 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr script = new GfApeSmashingQB(); else if (scriptName == "scripts\\zone\\PROPERTY\\GF\\L_ZONE_GF_PROPERTY.lua") script = new ZoneGfProperty(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_ARCHWAY.lua") { + else if (scriptName == "scripts\\ai\\GF\\L_GF_ARCHWAY.lua") script = new GfArchway(); - } + else if (scriptName == "scripts\\ai\\GF\\L_GF_MAELSTROM_GEYSER.lua") + script = new GfMaelstromGeyser(); + else if (scriptName == "scripts\\ai\\GF\\L_PIRATE_REP.lua") + script = new PirateRep(); + else if (scriptName == "scripts\\ai\\GF\\L_GF_PARROT_CRASH.lua") + script = new GfParrotCrash(); // SG else if (scriptName == "scripts\\ai\\MINIGAME\\SG_GF\\SERVER\\SG_CANNON.lua") @@ -564,6 +583,8 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr script = new FvConsoleRightQuickbuild(); else if (scriptName == "scripts\\ai\\FV\\L_FV_FACILITY_BRICK.lua") script = new FvFacilityBrick(); + else if (scriptName == "scripts\\ai\\FV\\L_FV_FACILITY_PIPES.lua") + script = new FvFacilityPipes(); else if (scriptName == "scripts\\02_server\\Map\\FV\\L_IMG_BRICK_CONSOLE_QB.lua") script = new ImgBrickConsoleQB(); else if (scriptName == "scripts\\ai\\FV\\L_ACT_PARADOX_PIPE_FIX.lua") @@ -576,6 +597,9 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr script = new FvBounceOverWall(); else if (scriptName == "scripts\\02_server\\Map\\FV\\L_NPC_FONG.lua") script = new FvFong(); + else if (scriptName == "scripts\\ai\\FV\\L_FV_MAELSTROM_GEYSER.lua") { + script = new FvMaelstromGeyser(); + } //Misc: if (scriptName == "scripts\\02_server\\Map\\General\\L_EXPLODING_ASSET.lua") @@ -813,12 +837,17 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr script = new BuccaneerValiantShip(); else if (scriptName == "scripts\\EquipmentScripts\\FireFirstSkillonStartup.lua") script = new FireFirstSkillonStartup(); + // 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(); + // WBL + else if (scriptName == "scripts\\zone\\LUPs\\WBL_generic_zone.lua") + script = new WblGenericZone(); + //Ignore these scripts: else if (scriptName == "scripts\\02_server\\Enemy\\General\\L_SUSPEND_LUA_AI.lua") script = invalidToReturn; diff --git a/dScripts/FvFacilityPipes.cpp b/dScripts/FvFacilityPipes.cpp new file mode 100644 index 00000000..dd35ffe1 --- /dev/null +++ b/dScripts/FvFacilityPipes.cpp @@ -0,0 +1,10 @@ +#include "FvFacilityPipes.h" +#include "GameMessages.h" + +void FvFacilityPipes::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) { + if (args == "startFX") { + GameMessages::SendPlayFXEffect(self->GetObjectID(), m_LeftPipeEffectID, m_EffectType, m_LeftPipeEffectName); + GameMessages::SendPlayFXEffect(self->GetObjectID(), m_RightPipeEffectID, m_EffectType, m_RightPipeEffectName); + GameMessages::SendPlayFXEffect(self->GetObjectID(), m_ImaginationCanisterEffectID, m_EffectType, m_ImaginationCanisterEffectName); + } +} diff --git a/dScripts/FvFacilityPipes.h b/dScripts/FvFacilityPipes.h new file mode 100644 index 00000000..8a33976c --- /dev/null +++ b/dScripts/FvFacilityPipes.h @@ -0,0 +1,15 @@ +#pragma once +#include "CppScripts.h" + +class FvFacilityPipes : public CppScripts::Script { +public: + void OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) override; +private: + const std::u16string m_EffectType = u"create"; + const std::string m_LeftPipeEffectName = "LeftPipeOff"; + const int32_t m_LeftPipeEffectID = 2774; + const std::string m_RightPipeEffectName = "RightPipeOff"; + const int32_t m_RightPipeEffectID = 2777; + const std::string m_ImaginationCanisterEffectName = "imagination_canister"; + const int32_t m_ImaginationCanisterEffectID = 2750; +}; diff --git a/dScripts/FvMaelstromGeyser.cpp b/dScripts/FvMaelstromGeyser.cpp new file mode 100644 index 00000000..66aa43dc --- /dev/null +++ b/dScripts/FvMaelstromGeyser.cpp @@ -0,0 +1,18 @@ +#include "FvMaelstromGeyser.h" +#include "SkillComponent.h" + +void FvMaelstromGeyser::OnStartup(Entity* self) { + self->AddTimer(m_StartSkillTimerName, m_StartSkillTimerTime); + self->AddTimer(m_KillSelfTimerName, m_KillSelfTimerTime); +} + +void FvMaelstromGeyser::OnTimerDone(Entity* self, std::string timerName) { + if (timerName == m_StartSkillTimerName) { + auto* skillComponent = self->GetComponent(); + skillComponent->CalculateBehavior(m_SkillID, m_BehaviorID, LWOOBJID_EMPTY, true); + } + if (timerName == m_KillSelfTimerName) { + self->Smash(LWOOBJID_EMPTY, eKillType::SILENT); + } +} + diff --git a/dScripts/FvMaelstromGeyser.h b/dScripts/FvMaelstromGeyser.h new file mode 100644 index 00000000..245eb56e --- /dev/null +++ b/dScripts/FvMaelstromGeyser.h @@ -0,0 +1,18 @@ +#pragma once +#include "CppScripts.h" + +class FvMaelstromGeyser final : public CppScripts::Script +{ +public: + void OnStartup(Entity* self) override; + void OnTimerDone(Entity* self, std::string timerName) override; + + +private: + const std::string m_StartSkillTimerName = "startSkill"; + const float m_StartSkillTimerTime = 2.0; + const std::string m_KillSelfTimerName = "killSelf"; + const float m_KillSelfTimerTime = 5.5; + const uint32_t m_SkillID = 831; + const uint32_t m_BehaviorID = 15500; +}; diff --git a/dScripts/GfMaelstromGeyser.cpp b/dScripts/GfMaelstromGeyser.cpp new file mode 100644 index 00000000..825307a7 --- /dev/null +++ b/dScripts/GfMaelstromGeyser.cpp @@ -0,0 +1,18 @@ +#include "GfMaelstromGeyser.h" +#include "SkillComponent.h" + +void GfMaelstromGeyser::OnStartup(Entity* self) { + self->AddTimer(m_StartSkillTimerName, m_StartSkillTimerTime); + self->AddTimer(m_KillSelfTimerName, m_KillSelfTimerTime); +} + +void GfMaelstromGeyser::OnTimerDone(Entity* self, std::string timerName) { + if (timerName == m_StartSkillTimerName) { + auto* skillComponent = self->GetComponent(); + skillComponent->CalculateBehavior(m_SkillID, m_BehaviorID, LWOOBJID_EMPTY, true); + } + if (timerName == m_KillSelfTimerName) { + self->Smash(LWOOBJID_EMPTY, eKillType::SILENT); + } +} + diff --git a/dScripts/GfMaelstromGeyser.h b/dScripts/GfMaelstromGeyser.h new file mode 100644 index 00000000..60e42798 --- /dev/null +++ b/dScripts/GfMaelstromGeyser.h @@ -0,0 +1,17 @@ +#pragma once +#include "CppScripts.h" + +class GfMaelstromGeyser final : public CppScripts::Script +{ +public: + void OnStartup(Entity* self) override; + void OnTimerDone(Entity* self, std::string timerName) override; + +private: + const std::string m_StartSkillTimerName = "startSkill"; + const float m_StartSkillTimerTime = 2.0; + const std::string m_KillSelfTimerName = "killSelf"; + const float m_KillSelfTimerTime = 7.5; + const uint32_t m_SkillID = 607; + const uint32_t m_BehaviorID = 10500; +}; diff --git a/dScripts/GfParrotCrash.cpp b/dScripts/GfParrotCrash.cpp new file mode 100644 index 00000000..6b76a51d --- /dev/null +++ b/dScripts/GfParrotCrash.cpp @@ -0,0 +1,13 @@ +#include "GfParrotCrash.h" +#include "SkillComponent.h" +#include "Entity.h" +#include "dLogger.h" + +void GfParrotCrash::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) { + auto* skillComponent = self->GetComponent(); + if (args == "Slow") { + skillComponent->CalculateBehavior(m_SlowSkillID, m_SlowBehaviorID, sender->GetObjectID()); + } else if (args == "Unslow") { + skillComponent->CalculateBehavior(m_UnslowSkillID, m_UnslowBehaviorID, sender->GetObjectID()); + } +} diff --git a/dScripts/GfParrotCrash.h b/dScripts/GfParrotCrash.h new file mode 100644 index 00000000..fe70a16c --- /dev/null +++ b/dScripts/GfParrotCrash.h @@ -0,0 +1,13 @@ +#pragma once +#include "CppScripts.h" + +class GfParrotCrash : public CppScripts::Script { +public: + void OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) override; +private: + const uint32_t m_SlowSkillID = 795; + const uint32_t m_SlowBehaviorID = 14214; + const uint32_t m_UnslowSkillID = 796; + const uint32_t m_UnslowBehaviorID = 14215; +}; + diff --git a/dScripts/MailBoxServer.cpp b/dScripts/MailBoxServer.cpp index 81156835..c2534f3e 100644 --- a/dScripts/MailBoxServer.cpp +++ b/dScripts/MailBoxServer.cpp @@ -9,3 +9,11 @@ void MailBoxServer::OnUse(Entity* self, Entity* user) { args.InsertValue("state", value); GameMessages::SendUIMessageServerToSingleClient(user, user->GetSystemAddress(), "pushGameState", &args); } + +void MailBoxServer::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) { + if (args == "toggleMail") { + AMFArrayValue args; + args.InsertValue("visible", new AMFFalseValue()); + GameMessages::SendUIMessageServerToSingleClient(sender, sender->GetSystemAddress(), "ToggleMail", &args); + } +} diff --git a/dScripts/MailBoxServer.h b/dScripts/MailBoxServer.h index f459e8b3..9ada6a0e 100644 --- a/dScripts/MailBoxServer.h +++ b/dScripts/MailBoxServer.h @@ -12,4 +12,5 @@ public: * @param user The user that interacted with this Entity. */ void OnUse(Entity* self, Entity* user) override; + void OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) override; }; diff --git a/dScripts/PirateRep.cpp b/dScripts/PirateRep.cpp new file mode 100644 index 00000000..eb4cf510 --- /dev/null +++ b/dScripts/PirateRep.cpp @@ -0,0 +1,11 @@ +#include "PirateRep.h" +#include "Character.h" + +void PirateRep::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, MissionState missionState) { + if (missionID == m_PirateRepMissionID && missionState >= MissionState::MISSION_STATE_READY_TO_COMPLETE) { + auto* character = target->GetCharacter(); + if (character) { + character->SetPlayerFlag(ePlayerFlags::GF_PIRATE_REP, true); + } + } +} diff --git a/dScripts/PirateRep.h b/dScripts/PirateRep.h new file mode 100644 index 00000000..8fc82c5e --- /dev/null +++ b/dScripts/PirateRep.h @@ -0,0 +1,9 @@ +#pragma once +#include "CppScripts.h" + +class PirateRep : public CppScripts::Script { +public: + void OnMissionDialogueOK(Entity* self, Entity* target, int missionID, MissionState missionState) override; +private: + const int m_PirateRepMissionID = 301; +}; diff --git a/dScripts/QbSpawner.cpp b/dScripts/QbSpawner.cpp new file mode 100644 index 00000000..edee6148 --- /dev/null +++ b/dScripts/QbSpawner.cpp @@ -0,0 +1,136 @@ +#include "QbSpawner.h" +#include "BaseCombatAIComponent.h" +#include "MovementAIComponent.h" + +void QbSpawner::OnStartup(Entity* self) { + auto mobNum = self->GetVar(u"mobNum"); + auto spawnDist = self->GetVar(u"spawnDist"); + auto mobTemplate = self->GetVar(u"mobTemplate"); + auto spawnTime = self->GetVar(u"spawnTime"); + + if (!mobNum) self->SetVar(u"mobNum", m_DefaultMobNum); + if (!spawnDist) self->SetVar(u"spawnDist", m_DefaultSpawnDist); + if (!mobTemplate) self->SetVar(u"mobTemplate", m_DefaultMobTemplate); + if (!spawnTime) self->SetVar(u"spawnTime", m_DefaultSpawnTime); + + // go ahead and setup the mob table here + std::vector mobTable; + mobTable.assign(self->GetVar(u"mobNum"), LWOOBJID_EMPTY); + + self->SetVar>(u"mobTable", mobTable); +} + +void QbSpawner::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) { + auto gateObjID = sender->GetObjectID(); + if (!gateObjID) return; + if (args == "spawnMobs") { + self->SetVar(u"gateObj", gateObjID); + auto spawnTime = self->GetVar(u"spawnTime"); + self->AddTimer("SpawnMobEnemies", spawnTime); + } +} + +void QbSpawner::OnTimerDone(Entity* self, std::string timerName) { + if (timerName == "SpawnMobEnemies") { + auto mobTable = self->GetVar>(u"mobTable"); + + auto spawnDist = self->GetVar(u"spawnDist"); + auto mobTemplate = self->GetVar(u"mobTemplate"); + + auto gateObjID = self->GetVar(u"gateObj"); + if (!gateObjID) return; + + auto* gate = EntityManager::Instance()->GetEntity(gateObjID); + if (!gate) return; + + auto oPos = gate->GetPosition(); + auto oDir = gate->GetRotation().GetForwardVector(); + NiPoint3 newPos( + oPos.x + (oDir.x * spawnDist), + oPos.y, + oPos.z + (oDir.z * spawnDist) + ); + auto newRot = NiQuaternion::LookAt(newPos, oPos); + + for (int i = 0; i < mobTable.size(); i++) { + int posOffset = -10; + if (mobTable[i] == LWOOBJID_EMPTY) { + posOffset = posOffset + 5 * i; + auto newOffset = newPos; + newOffset.z = newOffset.z + posOffset; + + EntityInfo info{}; + info.lot = mobTemplate; + info.pos = newOffset; + info.rot = newRot; + info.spawnerID = self->GetObjectID(); + info.spawnerNodeID = 0; + info.settings = { + new LDFData(u"no_timed_spawn", true), + new LDFData(u"aggroRadius", 70), + new LDFData(u"softtetherRadius", 80), + new LDFData(u"tetherRadius", 90), + new LDFData(u"wanderRadius", 5), + new LDFData(u"mobTableLoc", i) + }; + + auto* child = EntityManager::Instance()->CreateEntity(info, nullptr, self); + EntityManager::Instance()->ConstructEntity(child); + + OnChildLoaded(self, child); + } else { + auto* mob = EntityManager::Instance()->GetEntity(mobTable[i]); + AggroTargetObject(self, mob); + } + } + + } +} + +void QbSpawner::OnChildLoaded(Entity* self, Entity* child) { + auto mobTable = self->GetVar>(u"mobTable"); + auto tableLoc = child->GetVar(u"mobTableLoc"); + + mobTable[tableLoc] = child->GetObjectID(); + self->SetVar>(u"mobTable", mobTable); + + AggroTargetObject(self, child); + + const auto selfID = self->GetObjectID(); + + child->AddDieCallback([this, selfID, child]() { + auto* self = EntityManager::Instance()->GetEntity(selfID); + OnChildRemoved(self, child); + } + ); +} + +void QbSpawner::OnChildRemoved(Entity* self, Entity* child) { + auto mobTable = self->GetVar>(u"mobTable"); + auto tableLoc = child->GetVar(u"mobTableLoc"); + + mobTable[tableLoc] = LWOOBJID_EMPTY; + self->SetVar>(u"mobTable", mobTable); +} + +void QbSpawner::AggroTargetObject(Entity* self, Entity* enemy) { + auto* baseCombatAIComponent = enemy->GetComponent(); + if (!baseCombatAIComponent) return; + + auto gateObjID = self->GetVar(u"gateObj"); + if (gateObjID) { + auto* gate = EntityManager::Instance()->GetEntity(gateObjID); + if (gate) { + auto* movementAIComponent = enemy->GetComponent(); + if (movementAIComponent) movementAIComponent->SetDestination(gate->GetPosition()); + baseCombatAIComponent->Taunt(gateObjID, 1000); + } + } + + auto playerObjID = self->GetVar(u"player"); + if (playerObjID) { + baseCombatAIComponent->Taunt(playerObjID, 100); + } + +} + diff --git a/dScripts/QbSpawner.h b/dScripts/QbSpawner.h new file mode 100644 index 00000000..105d0835 --- /dev/null +++ b/dScripts/QbSpawner.h @@ -0,0 +1,17 @@ +#pragma once +#include "CppScripts.h" + +class QbSpawner : public CppScripts::Script { +public: + void OnStartup(Entity* self) override; + void OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) override; + void OnTimerDone(Entity* self, std::string timerName) override; + void OnChildLoaded(Entity* self, Entity* child); + void OnChildRemoved(Entity* self, Entity* child); + void AggroTargetObject(Entity* self, Entity* enemy); +private: + const int m_DefaultMobNum = 3; + const float m_DefaultSpawnDist = 25.0; + const LWOOBJID m_DefaultMobTemplate = 4712; + const float m_DefaultSpawnTime = 2.0; +}; diff --git a/dScripts/WblGenericZone.cpp b/dScripts/WblGenericZone.cpp new file mode 100644 index 00000000..5a670d8e --- /dev/null +++ b/dScripts/WblGenericZone.cpp @@ -0,0 +1,10 @@ +#include "WblGenericZone.h" +#include "Player.h" + +void WblGenericZone::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) { + if (args == m_WblAbortMsg) { + if (!sender) return; + auto player = dynamic_cast(sender); + if (player) player->SendToZone(m_WblMainZone); + } +} diff --git a/dScripts/WblGenericZone.h b/dScripts/WblGenericZone.h new file mode 100644 index 00000000..55b66e81 --- /dev/null +++ b/dScripts/WblGenericZone.h @@ -0,0 +1,10 @@ +#pragma once +#include "CppScripts.h" +class WblGenericZone : public CppScripts::Script +{ +public: + void OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) override; +private: + const LWOMAPID m_WblMainZone = 1600; + const std::string m_WblAbortMsg = "AbortWBLZone"; +}; diff --git a/docs/Commands.md b/docs/Commands.md index 8cefaa14..95ec28f9 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -63,7 +63,8 @@ These commands are primarily for development and testing. The usage of many of t |teleport|`/teleport (y) `|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 `|Activates spawner by name.|8| |addmission|`/addmission `|Accepts the mission, adding it to your journal.|8| -|boost|`/boost`|Adds a passive boost action if you are in a vehicle.|8| +|boost|`/boost (time)`|Adds a passive boost action if you are in a vehicle. If time is given it will end after that amount of time|8| +|unboost|`/unboost`|Removes a passive vehicle boost|8| |buff|`/buff `|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| @@ -71,7 +72,7 @@ These commands are primarily for development and testing. The usage of many of t |completemission|`/completemission `|Completes the mission, removing it from your journal.|8| |createprivate|`/createprivate `|Creates a private zone with password.|8| |debugui|`/debugui`|Toggle Debug UI.|8| -|dismount|`/dismount`|Dismounts you from the vehicle.|8| +|dismount|`/dismount`|Dismounts you from the vehicle or mount.|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 `|Gives coins.|8| diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index c7a27510..978c5532 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -6,13 +6,15 @@ set(tinyxml2_BUILD_TESTING OFF) # Source Code for tinyxml2 add_subdirectory(tinyxml2) -# Source Code for libbcrypt -# Disable warning about no project version. -set(CMAKE_POLICY_DEFAULT_CMP0048 NEW) -# Disable warning about the minimum version of cmake used for bcrypt being deprecated in the future -set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "" FORCE) - -add_subdirectory(libbcrypt) +# Source Code for libbcrypt. Uses a file glob instead to get around Windows build issues. +file( + GLOB SOURCES_LIBBCRYPT + LIST_DIRECTORIES false + RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" + ${CMAKE_CURRENT_SOURCE_DIR}/libbcrypt/*.c + ${CMAKE_CURRENT_SOURCE_DIR}/libbcrypt/src/*.c +) +add_library(bcrypt ${SOURCES_LIBBCRYPT}) # Source code for sqlite add_subdirectory(SQLite)