#include "ControllablePhysicsComponent.h" #include "Entity.h" #include "BitStream.h" #include "Logger.h" #include "Game.h" #include "dpWorld.h" #include "dpEntity.h" #include "CDPhysicsComponentTable.h" #include "CDComponentsRegistryTable.h" #include "CDClientManager.h" #include "EntityManager.h" #include "Character.h" #include "dZoneManager.h" #include "LevelProgressionComponent.h" #include "eStateChangeType.h" ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity) : PhysicsComponent(entity) { m_Velocity = {}; m_AngularVelocity = {}; m_InJetpackMode = false; m_IsOnGround = true; m_IsOnRail = false; m_DirtyVelocity = true; m_dpEntity = nullptr; m_Static = false; m_SpeedMultiplier = 1; m_GravityScale = 1; m_DirtyCheats = false; m_IgnoreMultipliers = false; m_DirtyEquippedItemInfo = true; m_PickupRadius = 0.0f; m_DirtyBubble = false; m_IsInBubble = false; m_SpecialAnims = false; m_BubbleType = eBubbleType::DEFAULT; m_IsTeleporting = false; m_ImmuneToStunAttackCount = 0; m_ImmuneToStunEquipCount = 0; m_ImmuneToStunInteractCount = 0; m_ImmuneToStunJumpCount = 0; m_ImmuneToStunMoveCount = 0; m_ImmuneToStunTurnCount = 0; m_ImmuneToStunUseItemCount = 0; if (entity->GetLOT() != 1) // Other physics entities we care about will be added by BaseCombatAI return; if (entity->GetLOT() == 1) { LOG("Using patch to load minifig physics"); float radius = 1.5f; m_dpEntity = new dpEntity(m_Parent->GetObjectID(), radius, false); m_dpEntity->SetCollisionGroup(COLLISION_GROUP_DYNAMIC | COLLISION_GROUP_FRIENDLY); dpWorld::AddEntity(m_dpEntity); } } ControllablePhysicsComponent::~ControllablePhysicsComponent() { if (m_dpEntity) { dpWorld::RemoveEntity(m_dpEntity); } } void ControllablePhysicsComponent::Update(float deltaTime) { if (m_Velocity == NiPoint3Constant::ZERO) return; SetPosition(m_Position + (m_Velocity * deltaTime)); Game::entityManager->SerializeEntity(m_Parent); } void ControllablePhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { //If this is a creation, then we assume the position is dirty, even when it isn't. //This is because new clients will still need to receive the position. //if (bIsInitialUpdate) m_DirtyPosition = true; if (bIsInitialUpdate) { outBitStream.Write(m_InJetpackMode); if (m_InJetpackMode) { outBitStream.Write(m_JetpackEffectID); outBitStream.Write(m_JetpackFlying); outBitStream.Write(m_JetpackBypassChecks); } outBitStream.Write1(); // always write these on construction outBitStream.Write(m_ImmuneToStunMoveCount); outBitStream.Write(m_ImmuneToStunJumpCount); outBitStream.Write(m_ImmuneToStunTurnCount); outBitStream.Write(m_ImmuneToStunAttackCount); outBitStream.Write(m_ImmuneToStunUseItemCount); outBitStream.Write(m_ImmuneToStunEquipCount); outBitStream.Write(m_ImmuneToStunInteractCount); } outBitStream.Write(m_DirtyCheats || bIsInitialUpdate); if (m_DirtyCheats || bIsInitialUpdate) { outBitStream.Write(m_GravityScale); outBitStream.Write(m_SpeedMultiplier); if (!bIsInitialUpdate) m_DirtyCheats = false; } outBitStream.Write(m_DirtyEquippedItemInfo || bIsInitialUpdate); if (m_DirtyEquippedItemInfo || bIsInitialUpdate) { outBitStream.Write(m_PickupRadius); outBitStream.Write(m_InJetpackMode); if (!bIsInitialUpdate) m_DirtyEquippedItemInfo = false; } outBitStream.Write(m_DirtyBubble || bIsInitialUpdate); if (m_DirtyBubble || bIsInitialUpdate) { outBitStream.Write(m_IsInBubble); if (m_IsInBubble) { outBitStream.Write(m_BubbleType); outBitStream.Write(m_SpecialAnims); } if (!bIsInitialUpdate) m_DirtyBubble = false; } bool isVelocityZero = m_Velocity != NiPoint3Constant::ZERO; bool isAngularVelocityZero = m_AngularVelocity != NiPoint3Constant::ZERO; bool shouldWriteFrameStats = m_DirtyPosition || bIsInitialUpdate || isVelocityZero || isAngularVelocityZero; outBitStream.Write(m_DirtyPosition || bIsInitialUpdate); if (m_DirtyPosition || bIsInitialUpdate) { outBitStream.Write(m_Position.x); outBitStream.Write(m_Position.y); outBitStream.Write(m_Position.z); outBitStream.Write(m_Rotation.x); outBitStream.Write(m_Rotation.y); outBitStream.Write(m_Rotation.z); outBitStream.Write(m_Rotation.w); outBitStream.Write(m_IsOnGround); outBitStream.Write(m_IsOnRail); outBitStream.Write(isVelocityZero); if (isVelocityZero) { outBitStream.Write(m_Velocity.x); outBitStream.Write(m_Velocity.y); outBitStream.Write(m_Velocity.z); } outBitStream.Write(isAngularVelocityZero); if (isAngularVelocityZero) { outBitStream.Write(m_AngularVelocity.x); outBitStream.Write(m_AngularVelocity.y); outBitStream.Write(m_AngularVelocity.z); } outBitStream.Write0(); // LocalSpaceInfo if (!bIsInitialUpdate) { m_DirtyPosition = false; outBitStream.Write(m_IsTeleporting); m_IsTeleporting = false; } } } void ControllablePhysicsComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); if (!character) { LOG("Failed to find char tag!"); return; } m_Parent->GetCharacter()->LoadXmlRespawnCheckpoints(); character->QueryAttribute("lzx", &m_Position.x); character->QueryAttribute("lzy", &m_Position.y); character->QueryAttribute("lzz", &m_Position.z); character->QueryAttribute("lzrx", &m_Rotation.x); character->QueryAttribute("lzry", &m_Rotation.y); character->QueryAttribute("lzrz", &m_Rotation.z); character->QueryAttribute("lzrw", &m_Rotation.w); m_DirtyPosition = true; } void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument* doc) { tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); if (!character) { LOG("Failed to find char tag while updating XML!"); return; } auto zoneInfo = Game::zoneManager->GetZone()->GetZoneID(); if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { character->SetAttribute("lzx", m_Position.x); character->SetAttribute("lzy", m_Position.y); character->SetAttribute("lzz", m_Position.z); character->SetAttribute("lzrx", m_Rotation.x); character->SetAttribute("lzry", m_Rotation.y); character->SetAttribute("lzrz", m_Rotation.z); character->SetAttribute("lzrw", m_Rotation.w); } } void ControllablePhysicsComponent::SetPosition(const NiPoint3& pos) { if (m_Static) return; PhysicsComponent::SetPosition(pos); if (m_dpEntity) m_dpEntity->SetPosition(pos); } void ControllablePhysicsComponent::SetRotation(const NiQuaternion& rot) { if (m_Static) return; PhysicsComponent::SetRotation(rot); if (m_dpEntity) m_dpEntity->SetRotation(rot); } void ControllablePhysicsComponent::SetVelocity(const NiPoint3& vel) { if (m_Static || m_Velocity == vel) { return; } m_Velocity = vel; m_DirtyPosition = true; if (m_dpEntity) m_dpEntity->SetVelocity(vel); } void ControllablePhysicsComponent::SetAngularVelocity(const NiPoint3& vel) { if (m_Static || m_AngularVelocity == vel) { return; } m_AngularVelocity = vel; m_DirtyPosition = true; } void ControllablePhysicsComponent::SetIsOnGround(bool val) { if (val == m_IsOnGround) return; m_DirtyPosition = true; m_IsOnGround = val; } void ControllablePhysicsComponent::SetIsOnRail(bool val) { if (val == m_IsOnRail) return; m_DirtyPosition = true; m_IsOnRail = val; } void ControllablePhysicsComponent::SetDirtyPosition(bool val) { m_DirtyPosition = val; } void ControllablePhysicsComponent::AddPickupRadiusScale(float value) { m_ActivePickupRadiusScales.push_back(value); if (value > m_PickupRadius) { m_PickupRadius = value; m_DirtyEquippedItemInfo = true; } } void ControllablePhysicsComponent::RemovePickupRadiusScale(float value) { // Attempt to remove pickup radius from active radii const auto pos = std::find(m_ActivePickupRadiusScales.begin(), m_ActivePickupRadiusScales.end(), value); if (pos != m_ActivePickupRadiusScales.end()) { m_ActivePickupRadiusScales.erase(pos); } else { LOG_DEBUG("Warning: Could not find pickup radius %f in list of active radii. List has %i active radii.", value, m_ActivePickupRadiusScales.size()); return; } // Recalculate pickup radius since we removed one by now m_PickupRadius = 0.0f; m_DirtyEquippedItemInfo = true; for (uint32_t i = 0; i < m_ActivePickupRadiusScales.size(); i++) { auto candidateRadius = m_ActivePickupRadiusScales[i]; if (m_PickupRadius < candidateRadius) m_PickupRadius = candidateRadius; } Game::entityManager->SerializeEntity(m_Parent); } void ControllablePhysicsComponent::AddSpeedboost(float value) { m_ActiveSpeedBoosts.push_back(value); m_SpeedBoost = value; SetSpeedMultiplier(value / 500.0f); // 500 being the base speed } void ControllablePhysicsComponent::RemoveSpeedboost(float value) { const auto pos = std::find(m_ActiveSpeedBoosts.begin(), m_ActiveSpeedBoosts.end(), value); if (pos != m_ActiveSpeedBoosts.end()) { m_ActiveSpeedBoosts.erase(pos); } else { LOG_DEBUG("Warning: Could not find speedboost %f in list of active speedboosts. List has %i active speedboosts.", value, m_ActiveSpeedBoosts.size()); return; } // Recalculate speedboost since we removed one m_SpeedBoost = 0.0f; if (m_ActiveSpeedBoosts.empty()) { // no active speed boosts left, so return to base speed auto* levelProgressionComponent = m_Parent->GetComponent(); if (levelProgressionComponent) m_SpeedBoost = levelProgressionComponent->GetSpeedBase(); } else { // Used the last applied speedboost m_SpeedBoost = m_ActiveSpeedBoosts.back(); } SetSpeedMultiplier(m_SpeedBoost / 500.0f); // 500 being the base speed Game::entityManager->SerializeEntity(m_Parent); } void ControllablePhysicsComponent::ActivateBubbleBuff(eBubbleType bubbleType, bool specialAnims) { if (m_IsInBubble) { LOG("Already in bubble"); return; } m_BubbleType = bubbleType; m_IsInBubble = true; m_DirtyBubble = true; m_SpecialAnims = specialAnims; Game::entityManager->SerializeEntity(m_Parent); } void ControllablePhysicsComponent::DeactivateBubbleBuff() { m_DirtyBubble = true; m_IsInBubble = false; Game::entityManager->SerializeEntity(m_Parent); }; void ControllablePhysicsComponent::SetStunImmunity( const eStateChangeType state, const LWOOBJID originator, const bool bImmuneToStunAttack, const bool bImmuneToStunEquip, const bool bImmuneToStunInteract, const bool bImmuneToStunJump, const bool bImmuneToStunMove, const bool bImmuneToStunTurn, const bool bImmuneToStunUseItem) { if (state == eStateChangeType::POP) { if (bImmuneToStunAttack && m_ImmuneToStunAttackCount > 0) m_ImmuneToStunAttackCount -= 1; if (bImmuneToStunEquip && m_ImmuneToStunEquipCount > 0) m_ImmuneToStunEquipCount -= 1; if (bImmuneToStunInteract && m_ImmuneToStunInteractCount > 0) m_ImmuneToStunInteractCount -= 1; if (bImmuneToStunJump && m_ImmuneToStunJumpCount > 0) m_ImmuneToStunJumpCount -= 1; if (bImmuneToStunMove && m_ImmuneToStunMoveCount > 0) m_ImmuneToStunMoveCount -= 1; if (bImmuneToStunTurn && m_ImmuneToStunTurnCount > 0) m_ImmuneToStunTurnCount -= 1; if (bImmuneToStunUseItem && m_ImmuneToStunUseItemCount > 0) m_ImmuneToStunUseItemCount -= 1; } else if (state == eStateChangeType::PUSH) { if (bImmuneToStunAttack) m_ImmuneToStunAttackCount += 1; if (bImmuneToStunEquip) m_ImmuneToStunEquipCount += 1; if (bImmuneToStunInteract) m_ImmuneToStunInteractCount += 1; if (bImmuneToStunJump) m_ImmuneToStunJumpCount += 1; if (bImmuneToStunMove) m_ImmuneToStunMoveCount += 1; if (bImmuneToStunTurn) m_ImmuneToStunTurnCount += 1; if (bImmuneToStunUseItem) m_ImmuneToStunUseItemCount += 1; } GameMessages::SendSetStunImmunity( m_Parent->GetObjectID(), state, m_Parent->GetSystemAddress(), originator, bImmuneToStunAttack, bImmuneToStunEquip, bImmuneToStunInteract, bImmuneToStunJump, bImmuneToStunMove, bImmuneToStunTurn, bImmuneToStunUseItem ); }