diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 59f6e0e0..f5887996 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -97,6 +97,8 @@ #include "CDSkillBehaviorTable.h" #include "CDZoneTableTable.h" +#include + Observable Entity::OnPlayerPositionUpdate; Entity::Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser, Entity* parentEntity) { @@ -286,8 +288,9 @@ void Entity::Initialize() { AddComponent(propertyEntranceComponentID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CONTROLLABLE_PHYSICS) > 0) { - auto* controllablePhysics = AddComponent(); + const int32_t controllablePhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CONTROLLABLE_PHYSICS); + if (controllablePhysicsComponentID > 0) { + auto* controllablePhysics = AddComponent(controllablePhysicsComponentID); if (m_Character) { controllablePhysics->LoadFromXml(m_Character->GetXMLDoc()); @@ -330,16 +333,19 @@ void Entity::Initialize() { AddComponent(simplePhysicsComponentID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS) > 0) { - AddComponent(); + const int32_t rigidBodyPhantomPhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS); + if (rigidBodyPhantomPhysicsComponentID > 0) { + AddComponent(rigidBodyPhantomPhysicsComponentID); } - if (markedAsPhantom || compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PHANTOM_PHYSICS) > 0) { - AddComponent()->SetPhysicsEffectActive(false); + const int32_t phantomPhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PHANTOM_PHYSICS); + if (markedAsPhantom || phantomPhysicsComponentID > 0) { + AddComponent(phantomPhysicsComponentID)->SetPhysicsEffectActive(false); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::HAVOK_VEHICLE_PHYSICS) > 0) { - auto* havokVehiclePhysicsComponent = AddComponent(); + const int32_t havokVehiclePhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::HAVOK_VEHICLE_PHYSICS); + if (havokVehiclePhysicsComponentID > 0) { + auto* havokVehiclePhysicsComponent = AddComponent(havokVehiclePhysicsComponentID); havokVehiclePhysicsComponent->SetPosition(m_DefaultPosition); havokVehiclePhysicsComponent->SetRotation(m_DefaultRotation); } @@ -2161,7 +2167,19 @@ void Entity::SetRespawnPos(const NiPoint3& position) { auto* characterComponent = GetComponent(); if (characterComponent) characterComponent->SetRespawnPos(position); } + void Entity::SetRespawnRot(const NiQuaternion& rotation) { auto* characterComponent = GetComponent(); if (characterComponent) characterComponent->SetRespawnRot(rotation); } + +int32_t Entity::GetCollisionGroup() const { + for (const auto* component : m_Components | std::views::values) { + auto* compToCheck = dynamic_cast(component); + if (compToCheck) { + return compToCheck->GetCollisionGroup(); + } + } + + return 0; +} diff --git a/dGame/Entity.h b/dGame/Entity.h index 5d2b9527..2ed7aa53 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -107,6 +107,11 @@ public: const SystemAddress& GetSystemAddress() const; + // Returns the collision group for this entity. + // Because the collision group is stored on a base component, this will look for a physics component + // then return the collision group from that. + int32_t GetCollisionGroup() const; + /** * Setters */ diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index bfb0bbfa..fbe5a382 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -16,6 +16,7 @@ #include "DestroyableComponent.h" #include +#include #include #include @@ -27,7 +28,7 @@ #include "CDPhysicsComponentTable.h" #include "dNavMesh.h" -BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): Component(parent) { +BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id) : Component(parent) { m_Target = LWOOBJID_EMPTY; m_DirtyStateOrTarget = true; m_State = AiState::spawn; @@ -37,6 +38,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): m_Disabled = false; m_SkillEntries = {}; m_SoftTimer = 5.0f; + m_ForcedTetherTime = 0.0f; //Grab the aggro information from BaseCombatAI: auto componentQuery = CDClientDatabase::CreatePreppedStmt( @@ -170,6 +172,17 @@ void BaseCombatAIComponent::Update(const float deltaTime) { GameMessages::SendStopFXEffect(m_Parent, true, "tether"); m_TetherEffectActive = false; } + m_ForcedTetherTime -= deltaTime; + if (m_ForcedTetherTime >= 0) return; + } + + for (auto entry = m_RemovedThreatList.begin(); entry != m_RemovedThreatList.end();) { + entry->second -= deltaTime; + if (entry->second <= 0.0f) { + entry = m_RemovedThreatList.erase(entry); + } else { + ++entry; + } } if (m_SoftTimer <= 0.0f) { @@ -287,40 +300,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { } if (!m_TetherEffectActive && m_OutOfCombat && (m_OutOfCombatTime -= deltaTime) <= 0) { - auto* destroyableComponent = m_Parent->GetComponent(); - - if (destroyableComponent != nullptr && destroyableComponent->HasFaction(4)) { - auto serilizationRequired = false; - - if (destroyableComponent->GetHealth() != destroyableComponent->GetMaxHealth()) { - destroyableComponent->SetHealth(destroyableComponent->GetMaxHealth()); - - serilizationRequired = true; - } - - if (destroyableComponent->GetArmor() != destroyableComponent->GetMaxArmor()) { - destroyableComponent->SetArmor(destroyableComponent->GetMaxArmor()); - - serilizationRequired = true; - } - - if (serilizationRequired) { - Game::entityManager->SerializeEntity(m_Parent); - } - - GameMessages::SendPlayFXEffect(m_Parent->GetObjectID(), 6270, u"tether", "tether"); - - m_TetherEffectActive = true; - - m_TetherTime = 3.0f; - } - - // Speed towards start position - if (m_MovementAI != nullptr) { - m_MovementAI->SetHaltDistance(0); - m_MovementAI->SetMaxSpeed(m_PursuitSpeed); - m_MovementAI->SetDestination(m_StartPosition); - } + TetherLogic(); m_OutOfCombat = false; m_OutOfCombatTime = 0.0f; @@ -499,7 +479,7 @@ std::vector BaseCombatAIComponent::GetTargetWithinAggroRange() const { const auto distance = Vector3::DistanceSquared(m_Parent->GetPosition(), other->GetPosition()); - if (distance > m_AggroRadius * m_AggroRadius) continue; + if (distance > m_AggroRadius * m_AggroRadius || m_RemovedThreatList.contains(id)) continue; targets.push_back(id); } @@ -626,6 +606,7 @@ const NiPoint3& BaseCombatAIComponent::GetStartPosition() const { void BaseCombatAIComponent::ClearThreat() { m_ThreatEntries.clear(); + m_Target = LWOOBJID_EMPTY; m_DirtyThreat = true; } @@ -806,3 +787,55 @@ void BaseCombatAIComponent::Wake() { m_dpEntity->SetSleeping(false); m_dpEntityEnemy->SetSleeping(false); } + +void BaseCombatAIComponent::TetherLogic() { + auto* destroyableComponent = m_Parent->GetComponent(); + + if (destroyableComponent != nullptr && destroyableComponent->HasFaction(4)) { + auto serilizationRequired = false; + + if (destroyableComponent->GetHealth() != destroyableComponent->GetMaxHealth()) { + destroyableComponent->SetHealth(destroyableComponent->GetMaxHealth()); + + serilizationRequired = true; + } + + if (destroyableComponent->GetArmor() != destroyableComponent->GetMaxArmor()) { + destroyableComponent->SetArmor(destroyableComponent->GetMaxArmor()); + + serilizationRequired = true; + } + + if (serilizationRequired) { + Game::entityManager->SerializeEntity(m_Parent); + } + + GameMessages::SendPlayFXEffect(m_Parent->GetObjectID(), 6270, u"tether", "tether"); + + m_TetherEffectActive = true; + + m_TetherTime = 3.0f; + } + + // Speed towards start position + if (m_MovementAI != nullptr) { + m_MovementAI->SetHaltDistance(0); + m_MovementAI->SetMaxSpeed(m_PursuitSpeed); + m_MovementAI->SetDestination(m_StartPosition); + } +} + +void BaseCombatAIComponent::ForceTether() { + SetTarget(LWOOBJID_EMPTY); + m_ThreatEntries.clear(); + TetherLogic(); + m_ForcedTetherTime = m_TetherTime; + + SetAiState(AiState::aggro); +} + +void BaseCombatAIComponent::IgnoreThreat(const LWOOBJID threat, const float value) { + m_RemovedThreatList[threat] = value; + SetThreat(threat, 0.0f); + m_Target = LWOOBJID_EMPTY; +} diff --git a/dGame/dComponents/BaseCombatAIComponent.h b/dGame/dComponents/BaseCombatAIComponent.h index 89985d64..52adb429 100644 --- a/dGame/dComponents/BaseCombatAIComponent.h +++ b/dGame/dComponents/BaseCombatAIComponent.h @@ -224,6 +224,16 @@ public: */ void Wake(); + // Force this entity to tether and ignore all other actions + void ForceTether(); + + // heals the entity to full health and armor + // and tethers them to their spawn point + void TetherLogic(); + + // Ignore a threat for a certain amount of time + void IgnoreThreat(const LWOOBJID target, const float time); + private: /** * Returns the current target or the target that currently is the largest threat to this entity @@ -382,6 +392,12 @@ private: */ bool m_DirtyStateOrTarget = false; + // The amount of time the entity will be forced to tether for + float m_ForcedTetherTime = 0.0f; + + // The amount of time a removed threat will be ignored for. + std::map m_RemovedThreatList; + /** * Whether the current entity is a mech enemy, needed as mechs tether radius works differently * @return whether this entity is a mech diff --git a/dGame/dComponents/ControllablePhysicsComponent.cpp b/dGame/dComponents/ControllablePhysicsComponent.cpp index 18e4b19d..1a05020b 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/ControllablePhysicsComponent.cpp @@ -15,7 +15,7 @@ #include "LevelProgressionComponent.h" #include "eStateChangeType.h" -ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity) : PhysicsComponent(entity) { +ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity, int32_t componentId) : PhysicsComponent(entity, componentId) { m_Velocity = {}; m_AngularVelocity = {}; m_InJetpackMode = false; diff --git a/dGame/dComponents/ControllablePhysicsComponent.h b/dGame/dComponents/ControllablePhysicsComponent.h index 6309b8fc..31a6bb30 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.h +++ b/dGame/dComponents/ControllablePhysicsComponent.h @@ -23,7 +23,7 @@ class ControllablePhysicsComponent : public PhysicsComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::CONTROLLABLE_PHYSICS; - ControllablePhysicsComponent(Entity* entity); + ControllablePhysicsComponent(Entity* entity, int32_t componentId); ~ControllablePhysicsComponent() override; void Update(float deltaTime) override; diff --git a/dGame/dComponents/HavokVehiclePhysicsComponent.cpp b/dGame/dComponents/HavokVehiclePhysicsComponent.cpp index 635830cc..e977f952 100644 --- a/dGame/dComponents/HavokVehiclePhysicsComponent.cpp +++ b/dGame/dComponents/HavokVehiclePhysicsComponent.cpp @@ -1,7 +1,7 @@ #include "HavokVehiclePhysicsComponent.h" #include "EntityManager.h" -HavokVehiclePhysicsComponent::HavokVehiclePhysicsComponent(Entity* parent) : PhysicsComponent(parent) { +HavokVehiclePhysicsComponent::HavokVehiclePhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) { m_Velocity = NiPoint3Constant::ZERO; m_AngularVelocity = NiPoint3Constant::ZERO; m_IsOnGround = true; diff --git a/dGame/dComponents/HavokVehiclePhysicsComponent.h b/dGame/dComponents/HavokVehiclePhysicsComponent.h index 83eb82fe..ad6087a7 100644 --- a/dGame/dComponents/HavokVehiclePhysicsComponent.h +++ b/dGame/dComponents/HavokVehiclePhysicsComponent.h @@ -13,7 +13,7 @@ class HavokVehiclePhysicsComponent : public PhysicsComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::HAVOK_VEHICLE_PHYSICS; - HavokVehiclePhysicsComponent(Entity* parentEntity); + HavokVehiclePhysicsComponent(Entity* parentEntity, int32_t componentId); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/PhantomPhysicsComponent.cpp b/dGame/dComponents/PhantomPhysicsComponent.cpp index 95fed36e..be7fe774 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.cpp +++ b/dGame/dComponents/PhantomPhysicsComponent.cpp @@ -27,7 +27,7 @@ #include "dpShapeBox.h" #include "dpShapeSphere.h" -PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent) : PhysicsComponent(parent) { +PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) { m_Position = m_Parent->GetDefaultPosition(); m_Rotation = m_Parent->GetDefaultRotation(); m_Scale = m_Parent->GetDefaultScale(); diff --git a/dGame/dComponents/PhantomPhysicsComponent.h b/dGame/dComponents/PhantomPhysicsComponent.h index 89cfb857..cd54587b 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.h +++ b/dGame/dComponents/PhantomPhysicsComponent.h @@ -30,7 +30,7 @@ class PhantomPhysicsComponent final : public PhysicsComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PHANTOM_PHYSICS; - PhantomPhysicsComponent(Entity* parent); + PhantomPhysicsComponent(Entity* parent, int32_t componentId); ~PhantomPhysicsComponent() override; void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/PhysicsComponent.cpp b/dGame/dComponents/PhysicsComponent.cpp index 4a250a6a..67fca8d9 100644 --- a/dGame/dComponents/PhysicsComponent.cpp +++ b/dGame/dComponents/PhysicsComponent.cpp @@ -14,10 +14,21 @@ #include "EntityInfo.h" -PhysicsComponent::PhysicsComponent(Entity* parent) : Component(parent) { +PhysicsComponent::PhysicsComponent(Entity* parent, int32_t componentId) : Component(parent) { m_Position = NiPoint3Constant::ZERO; m_Rotation = NiQuaternionConstant::IDENTITY; m_DirtyPosition = false; + + CDPhysicsComponentTable* physicsComponentTable = CDClientManager::GetTable(); + + if (physicsComponentTable) { + auto* info = physicsComponentTable->GetByID(componentId); + if (info) { + m_CollisionGroup = info->collisionGroup; + } + } + + if (m_Parent->HasVar(u"CollisionGroupID")) m_CollisionGroup = m_Parent->GetVar(u"CollisionGroupID"); } void PhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { diff --git a/dGame/dComponents/PhysicsComponent.h b/dGame/dComponents/PhysicsComponent.h index 4bf0828a..41a4b9d1 100644 --- a/dGame/dComponents/PhysicsComponent.h +++ b/dGame/dComponents/PhysicsComponent.h @@ -15,7 +15,7 @@ class dpEntity; class PhysicsComponent : public Component { public: - PhysicsComponent(Entity* parent); + PhysicsComponent(Entity* parent, int32_t componentId); virtual ~PhysicsComponent() = default; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; @@ -25,6 +25,9 @@ public: const NiQuaternion& GetRotation() const { return m_Rotation; } virtual void SetRotation(const NiQuaternion& rot) { if (m_Rotation == rot) return; m_Rotation = rot; m_DirtyPosition = true; } + + int32_t GetCollisionGroup() const noexcept { return m_CollisionGroup; } + void SetCollisionGroup(int32_t group) noexcept { m_CollisionGroup = group; } protected: dpEntity* CreatePhysicsEntity(eReplicaComponentType type); @@ -37,6 +40,8 @@ protected: NiQuaternion m_Rotation; bool m_DirtyPosition; + + int32_t m_CollisionGroup{}; }; #endif //!__PHYSICSCOMPONENT__H__ diff --git a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp index df81aab3..d0f8caeb 100644 --- a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp +++ b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp @@ -12,7 +12,7 @@ #include "dpShapeSphere.h" #include"EntityInfo.h" -RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent) : PhysicsComponent(parent) { +RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) { m_Position = m_Parent->GetDefaultPosition(); m_Rotation = m_Parent->GetDefaultRotation(); m_Scale = m_Parent->GetDefaultScale(); diff --git a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h index 11595ec0..e7ce93d6 100644 --- a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h +++ b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h @@ -21,7 +21,7 @@ class RigidbodyPhantomPhysicsComponent : public PhysicsComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS; - RigidbodyPhantomPhysicsComponent(Entity* parent); + RigidbodyPhantomPhysicsComponent(Entity* parent, int32_t componentId); void Update(const float deltaTime) override; diff --git a/dGame/dComponents/SimplePhysicsComponent.cpp b/dGame/dComponents/SimplePhysicsComponent.cpp index 6bc2e2bc..825570f2 100644 --- a/dGame/dComponents/SimplePhysicsComponent.cpp +++ b/dGame/dComponents/SimplePhysicsComponent.cpp @@ -13,7 +13,7 @@ #include "Entity.h" -SimplePhysicsComponent::SimplePhysicsComponent(Entity* parent, uint32_t componentID) : PhysicsComponent(parent) { +SimplePhysicsComponent::SimplePhysicsComponent(Entity* parent, int32_t componentID) : PhysicsComponent(parent, componentID) { m_Position = m_Parent->GetDefaultPosition(); m_Rotation = m_Parent->GetDefaultRotation(); diff --git a/dGame/dComponents/SimplePhysicsComponent.h b/dGame/dComponents/SimplePhysicsComponent.h index b4491e12..61362712 100644 --- a/dGame/dComponents/SimplePhysicsComponent.h +++ b/dGame/dComponents/SimplePhysicsComponent.h @@ -30,7 +30,7 @@ class SimplePhysicsComponent : public PhysicsComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SIMPLE_PHYSICS; - SimplePhysicsComponent(Entity* parent, uint32_t componentID); + SimplePhysicsComponent(Entity* parent, int32_t componentID); ~SimplePhysicsComponent() override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index b42e7d01..eea76fba 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -6338,6 +6338,40 @@ void GameMessages::SendUpdateInventoryUi(LWOOBJID objectId, const SystemAddress& bitStream.Write(objectId); bitStream.Write(MessageType::Game::UPDATE_INVENTORY_UI); - + + SEND_PACKET; +} + +void GameMessages::DisplayTooltip::Send() const { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(target); + bitStream.Write(msgId); + + bitStream.Write(doOrDie); + bitStream.Write(noRepeat); + bitStream.Write(noRevive); + bitStream.Write(isPropertyTooltip); + bitStream.Write(show); + bitStream.Write(translate); + bitStream.Write(time); + bitStream.Write(id.size()); + bitStream.Write(id); + + std::string toWrite; + for (const auto* item : localizeParams) { + toWrite += item->GetString() + "\n"; + } + if (!toWrite.empty()) toWrite.pop_back(); + bitStream.Write(toWrite.size()); + bitStream.Write(GeneralUtils::ASCIIToUTF16(toWrite)); + if (!toWrite.empty()) bitStream.Write(0x00); // Null Terminator + + bitStream.Write(imageName.size()); + bitStream.Write(imageName); + bitStream.Write(text.size()); + bitStream.Write(text); + SEND_PACKET; } diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 1dbe5d81..62d59a1d 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -11,6 +11,7 @@ #include "eCyclingMode.h" #include "eLootSourceType.h" #include "Brick.h" +#include "MessageType/Game.h" class AMFBaseValue; class Entity; @@ -20,6 +21,7 @@ class User; class Leaderboard; class PropertySelectQueryProperty; class TradeItem; +class LDFBaseData; enum class eAnimationFlags : uint32_t; @@ -47,6 +49,15 @@ enum class eCameraTargetCyclingMode : int32_t { }; namespace GameMessages { + struct GameMsg { + GameMsg(MessageType::Game gmId) : msgId{ gmId } {} + virtual ~GameMsg() = default; + virtual void Send() const {} + MessageType::Game msgId; + LWOOBJID target{ LWOOBJID_EMPTY }; + SystemAddress sysAddr{ UNASSIGNED_SYSTEM_ADDRESS }; + }; + class PropertyDataMessage; void SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender); void SendTeleport(const LWOOBJID& objectID, const NiPoint3& pos, const NiQuaternion& rot, const SystemAddress& sysAddr, bool bSetRotation = false); @@ -680,6 +691,22 @@ namespace GameMessages { // This is a client gm however its default values are exactly what we need to get around the invisible inventory item issues. void SendUpdateInventoryUi(LWOOBJID objectId, const SystemAddress& sysAddr); + + struct DisplayTooltip : public GameMsg { + DisplayTooltip() : GameMsg(MessageType::Game::DISPLAY_TOOLTIP) {} + bool doOrDie{}; + bool noRepeat{}; + bool noRevive{}; + bool isPropertyTooltip{}; + bool show{}; + bool translate{}; + int32_t time{}; + std::u16string id{}; + std::vector localizeParams{}; + std::u16string imageName{}; + std::u16string text{}; + void Send() const override; + }; }; #endif // GAMEMESSAGES_H diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.h index e1f88713..afd7a14c 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.h @@ -8,7 +8,6 @@ class AMFArrayValue; /** * @brief Sent when a player moves a Behavior A at position B to their inventory. */ -#pragma warning("This Control Behavior Message does not have a test yet. Non-developers can ignore this warning.") class MoveToInventoryMessage : public BehaviorMessageBase { public: MoveToInventoryMessage(const AMFArrayValue& arguments); diff --git a/dGame/dUtilities/Preconditions.cpp b/dGame/dUtilities/Preconditions.cpp index a0aac27e..118d9037 100644 --- a/dGame/dUtilities/Preconditions.cpp +++ b/dGame/dUtilities/Preconditions.cpp @@ -137,7 +137,7 @@ bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluat return inventoryComponent->GetLotCount(value) >= count; case PreconditionType::DoesNotHaveItem: - return inventoryComponent->IsEquipped(value) < count; + return inventoryComponent->IsEquipped(value) && count > 0; case PreconditionType::HasAchievement: if (missionComponent == nullptr) return false; return missionComponent->GetMissionState(value) >= eMissionState::COMPLETE; diff --git a/dScripts/02_server/Map/General/CMakeLists.txt b/dScripts/02_server/Map/General/CMakeLists.txt index 3379e5b0..dc32d53c 100644 --- a/dScripts/02_server/Map/General/CMakeLists.txt +++ b/dScripts/02_server/Map/General/CMakeLists.txt @@ -2,6 +2,7 @@ set(DSCRIPTS_SOURCES_02_SERVER_MAP_GENERAL "BankInteractServer.cpp" "BaseInteractDropLootServer.cpp" "Binoculars.cpp" + "EnemyClearThreat.cpp" "ExplodingAsset.cpp" "FrictionVolumeServer.cpp" "ForceVolumeServer.cpp" diff --git a/dScripts/02_server/Map/General/EnemyClearThreat.cpp b/dScripts/02_server/Map/General/EnemyClearThreat.cpp new file mode 100644 index 00000000..b026899c --- /dev/null +++ b/dScripts/02_server/Map/General/EnemyClearThreat.cpp @@ -0,0 +1,25 @@ +#include "EnemyClearThreat.h" + +#include "BaseCombatAIComponent.h" +#include "PhysicsComponent.h" + +void EnemyClearThreat::OnCollisionPhantom(Entity* self, Entity* target) { + if (!target) return; + + const auto colGroup = target->GetCollisionGroup(); + if (colGroup == 12) { // enemy + auto* const baseCombatAiComponent = target->GetComponent(); + if (!baseCombatAiComponent) return; + + baseCombatAiComponent->ClearThreat(); + baseCombatAiComponent->ForceTether(); + } else if (colGroup == 10) { // player + const auto enemies = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::BASE_COMBAT_AI); + for (const auto& enemy : enemies) { + auto* const baseCombatAiComponent = enemy->GetComponent(); + if (!baseCombatAiComponent) continue; + + baseCombatAiComponent->IgnoreThreat(target->GetObjectID(), 3.0f); + } + } +} diff --git a/dScripts/02_server/Map/General/EnemyClearThreat.h b/dScripts/02_server/Map/General/EnemyClearThreat.h new file mode 100644 index 00000000..97cce426 --- /dev/null +++ b/dScripts/02_server/Map/General/EnemyClearThreat.h @@ -0,0 +1,11 @@ +#ifndef ENEMYCLEARTHREAT_H +#define ENEMYCLEARTHREAT_H + +#include "CppScripts.h" + +class EnemyClearThreat : public CppScripts::Script { +public: + void OnCollisionPhantom(Entity* self, Entity* target) override; +}; + +#endif //!ENEMYCLEARTHREAT_H diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index ed0de2ba..54328451 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -327,6 +327,8 @@ #include "VisToggleNotifierServer.h" #include "LupGenericInteract.h" #include "WblRobotCitizen.h" +#include "EnemyClearThreat.h" +#include "AgSpiderBossMessage.h" #include #include @@ -686,8 +688,22 @@ namespace { {"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizenOrange.lua", []() {return new WblRobotCitizen();}}, {"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizenRed.lua", []() {return new WblRobotCitizen();}}, {"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizenYellow.lua", []() {return new WblRobotCitizen();}}, + {"scripts\\02_server\\Map\\General\\L_ENEMY_CLEAR_THREAT.lua", []() {return new EnemyClearThreat();}}, + {"scripts\\ai\\AG\\L_AG_SPIDER_BOSS_MESSAGE.lua", []() {return new AgSpiderBossMessage();}}, }; + + std::set g_ExcludedScripts = { + "scripts\\02_server\\Enemy\\General\\L_SUSPEND_LUA_AI.lua", + "scripts\\02_server\\Enemy\\General\\L_BASE_ENEMY_SPIDERLING.lua", + "scripts\\ai\\AG\\L_AG_SENTINEL_GUARD.lua", + "scripts\\ai\\FV\\L_ACT_NINJA_STUDENT.lua", + "scripts\\ai\\WILD\\L_WILD_GF_FROG.lua", + "scripts\\empty.lua", + "scripts\\zone\\AG\\L_ZONE_AG.lua", + "scripts\\zone\\NS\\L_ZONE_NS.lua", + "scripts\\zone\\GF\\L_ZONE_GF.lua", + }; }; CppScripts::Script* const CppScripts::GetScript(Entity* parent, const std::string& scriptName) { @@ -699,14 +715,8 @@ CppScripts::Script* const CppScripts::GetScript(Entity* parent, const std::strin const auto itrTernary = scriptLoader.find(scriptName); Script* script = itrTernary != scriptLoader.cend() ? itrTernary->second() : &InvalidToReturn; - if (script == &InvalidToReturn) { - if ((scriptName.length() > 0) && !((scriptName == "scripts\\02_server\\Enemy\\General\\L_SUSPEND_LUA_AI.lua") || - (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\\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()); + if (script == &InvalidToReturn && !scriptName.empty() && !g_ExcludedScripts.contains(scriptName)) { + LOG_DEBUG("LOT %i attempted to load CppScript for '%s', but returned InvalidScript.", parent->GetLOT(), scriptName.c_str()); } g_Scripts[scriptName] = script; diff --git a/dScripts/ai/AG/AgSpiderBossMessage.cpp b/dScripts/ai/AG/AgSpiderBossMessage.cpp new file mode 100644 index 00000000..6e6cc1b9 --- /dev/null +++ b/dScripts/ai/AG/AgSpiderBossMessage.cpp @@ -0,0 +1,81 @@ +#include "AgSpiderBossMessage.h" + +#include "Entity.h" +#include "GameMessages.h" + +#include "RenderComponent.h" + +Box AgSpiderBossMessage::GetBox(Entity* self) const { + return self->GetVar(u"box"); +} + +void AgSpiderBossMessage::SetBox(Entity* self, const Box& box) const { + self->SetVar(u"box", box); +} + +void AgSpiderBossMessage::MakeBox(Entity* self) const { + auto box = GetBox(self); + if (box.boxTarget == LWOOBJID_EMPTY || box.isDisplayed || box.boxSelf == LWOOBJID_EMPTY) return; + + box.isDisplayed = true; + SetBox(self, box); + self->AddTimer("BoxTimer", box.boxTime); + + const auto* const tgt = Game::entityManager->GetEntity(box.boxTarget); + if (!tgt) return; + GameMessages::DisplayTooltip tooltip; + tooltip.target = tgt->GetObjectID(); + tooltip.sysAddr = tgt->GetSystemAddress(); + tooltip.show = true; + tooltip.text = box.boxText; + tooltip.time = box.boxTime * 1000; // to ms + tooltip.Send(); +} + +void AgSpiderBossMessage::OnCollisionPhantom(Entity* self, Entity* target) { + if (!target || !target->IsPlayer()) return; + + auto box = GetBox(self); + // knockback the target + auto forward = target->GetRotation().GetForwardVector(); + box.boxTarget = target->GetObjectID(); + GameMessages::SendPlayFXEffect(target->GetObjectID(), 1378, u"create", "pushBack"); + RenderComponent::PlayAnimation(target, "knockback-recovery"); + forward.y += 15; + forward.x *= 100; + forward.z *= 100; + GameMessages::SendKnockback(target->GetObjectID(), self->GetObjectID(), self->GetObjectID(), 0, forward); + + if (box.isTouch || box.isDisplayed) return; + box.boxSelf = self->GetObjectID(); + box.isTouch = true; + box.boxText = u"%[SPIDER_CAVE_MESSAGE]"; + SetBox(self, box); + self->AddTimer("EventTimer", 0.1f); +} + +void AgSpiderBossMessage::OnOffCollisionPhantom(Entity* self, Entity* target) { + if (!target) return; + auto box = GetBox(self); + box.isTouch = false; + box.Reset(); + SetBox(self, box); +} + +void AgSpiderBossMessage::OnTimerDone(Entity* self, std::string timerName) { + if (timerName == "BoxTimer") { + auto box = GetBox(self); + box.isDisplayed = false; + SetBox(self, box); + ResetBox(self); + } else if (timerName == "EventTimer") { + auto box = GetBox(self); + MakeBox(self); + } +} + +void AgSpiderBossMessage::ResetBox(Entity* self) const { + auto box = GetBox(self); + box.Reset(); + SetBox(self, box); +} diff --git a/dScripts/ai/AG/AgSpiderBossMessage.h b/dScripts/ai/AG/AgSpiderBossMessage.h new file mode 100644 index 00000000..70a2fbee --- /dev/null +++ b/dScripts/ai/AG/AgSpiderBossMessage.h @@ -0,0 +1,37 @@ +#ifndef AGSPIDERBOSSMESSAGE_H +#define AGSPIDERBOSSMESSAGE_H + +#include "CppScripts.h" + +struct Box { + LWOOBJID boxTarget{}; + bool isDisplayed{}; + bool isTouch{}; + bool isFirst{}; + LWOOBJID boxSelf{}; + std::u16string boxText{}; + int32_t boxTime{ 1 }; + + void Reset() { + boxTarget = LWOOBJID_EMPTY; + isDisplayed = false; + isTouch = false; + isFirst = false; + boxSelf = LWOOBJID_EMPTY; + boxText.clear(); + boxTime = 1; + } +}; + +class AgSpiderBossMessage : public CppScripts::Script { +public: + Box GetBox(Entity* self) const; + void SetBox(Entity* self, const Box& box) const; + void MakeBox(Entity* self) const; + void OnCollisionPhantom(Entity* self, Entity* target) override; + void OnOffCollisionPhantom(Entity* self, Entity* target) override; + void OnTimerDone(Entity* self, std::string timerName) override; + void ResetBox(Entity* self) const; +}; + +#endif //!AGSPIDERBOSSMESSAGE_H diff --git a/dScripts/ai/AG/CMakeLists.txt b/dScripts/ai/AG/CMakeLists.txt index 101f86fd..237e266a 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" + "AgSpiderBossMessage.cpp" "AgShipShake.cpp" "AgShipPlayerShockServer.cpp" "AgImagSmashable.cpp"