diff --git a/dGame/dBehaviors/NpcCombatSkillBehavior.cpp b/dGame/dBehaviors/NpcCombatSkillBehavior.cpp index 69a6ea9d..433594a2 100644 --- a/dGame/dBehaviors/NpcCombatSkillBehavior.cpp +++ b/dGame/dBehaviors/NpcCombatSkillBehavior.cpp @@ -5,20 +5,40 @@ void NpcCombatSkillBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bit_stream, BehaviorBranchContext branch) { context->skillTime = this->m_npcSkillTime; + const auto* const targetEntity = Game::entityManager->GetEntity(branch.target); + const auto* const sourceEntity = Game::entityManager->GetEntity(context->caster); - for (auto* behavior : this->m_behaviors) { - behavior->Calculate(context, bit_stream, branch); + bool cast = true; + // Check that the target is within the cast range + if (targetEntity && sourceEntity && this->m_maxRange != 0.0f) { + const auto targetPos = targetEntity->GetPosition(); + const auto sourcePos = sourceEntity->GetPosition(); + const auto distance = NiPoint3::DistanceSquared(targetPos, sourcePos); + cast = distance >= this->m_minRange && distance <= this->m_maxRange; + } + + if (cast) { + for (auto* behavior : this->m_behaviors) { + behavior->Calculate(context, bit_stream, branch); + } + } else { + // We failed to find a valid target, do not continue the behavior + context->foundTarget = false; } } void NpcCombatSkillBehavior::Load() { this->m_npcSkillTime = GetFloat("npc skill time"); + this->m_minRange = GetFloat("min range") * 0.9f; // Make the min and max 10% smaller to account for server/client position disagreements + this->m_minRange *= this->m_minRange; + this->m_maxRange = GetFloat("max range") * 0.9f; // Make the min and max 10% smaller to account for server/client position disagreements + this->m_maxRange *= this->m_maxRange; const auto parameters = GetParameterNames(); - for (const auto& parameter : parameters) { - if (parameter.first.rfind("behavior", 0) == 0) { - auto* action = GetAction(parameter.second); + for (const auto& [parameter, value] : parameters) { + if (parameter.rfind("behavior", 0) == 0) { + auto* action = GetAction(value); this->m_behaviors.push_back(action); } diff --git a/dGame/dBehaviors/NpcCombatSkillBehavior.h b/dGame/dBehaviors/NpcCombatSkillBehavior.h index 993aed76..a1c7c0e9 100644 --- a/dGame/dBehaviors/NpcCombatSkillBehavior.h +++ b/dGame/dBehaviors/NpcCombatSkillBehavior.h @@ -9,6 +9,7 @@ public: float m_npcSkillTime; float m_maxRange{}; + float m_minRange{}; /* * Inherited diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index d4d70025..89c39c33 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -350,7 +350,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { continue; } - const auto result = skillComponent->CalculateBehavior(entry.skillId, entry.behavior->m_behaviorId, LWOOBJID_EMPTY); + const auto result = skillComponent->CalculateBehavior(entry.skillId, entry.behavior->m_behaviorId, GetTarget()); if (result.success) { if (m_MovementAI != nullptr) { @@ -759,8 +759,8 @@ void BaseCombatAIComponent::SetTetherSpeed(float value) { m_TetherSpeed = value; } -void BaseCombatAIComponent::Stun(const float time) { - if (m_StunImmune || m_StunTime > time) { +void BaseCombatAIComponent::Stun(const float time, const bool force) { + if (!force && (m_StunImmune || m_StunTime > time)) { return; } diff --git a/dGame/dComponents/BaseCombatAIComponent.h b/dGame/dComponents/BaseCombatAIComponent.h index 18de88cb..728e0d4a 100644 --- a/dGame/dComponents/BaseCombatAIComponent.h +++ b/dGame/dComponents/BaseCombatAIComponent.h @@ -183,8 +183,9 @@ public: /** * Stuns the entity for a certain amount of time, will not work if the entity is stun immune * @param time the time to stun the entity, if stunnable + * @param force whether or not to force the stun and ignore checks */ - void Stun(float time); + void Stun(float time, const bool force = false); /** * Gets the radius that will cause this entity to get aggro'd, causing a target chase diff --git a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp index b3938fd4..869e09ba 100644 --- a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp +++ b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp @@ -16,6 +16,7 @@ #include "eReplicaComponentType.h" #include "RenderComponent.h" #include "PlayerManager.h" +#include "eStateChangeType.h" #include @@ -48,10 +49,30 @@ void BossSpiderQueenEnemyServer::OnStartup(Entity* self) { combat->SetStunImmune(true); m_CurrentBossStage = 1; - + ToggleAttacking(*self, false); + self->SetProximityRadius(65.0f, "AggroRadius"); // Obtain faction and collision group to save for subsequent resets } +void BossSpiderQueenEnemyServer::OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) { + if (name != "AggroRadius" || !entering || !entering->IsPlayer()) return; + + auto playerCount = self->GetVar(u"player_count"); + + if (status == "ENTER") { + if (playerCount == 0) { + ToggleAttacking(*self, true); + } + playerCount++; + } else if (status == "LEAVE") { + playerCount--; + if (playerCount == 0) { + ToggleAttacking(*self, false); + } + } + self->SetVar(u"player_count", playerCount); +} + void BossSpiderQueenEnemyServer::OnDie(Entity* self, Entity* killer) { if (Game::zoneManager->GetZoneID().GetMapID() == instanceZoneID && killer) { for (const auto& player : PlayerManager::GetAllPlayers()) { @@ -71,6 +92,7 @@ void BossSpiderQueenEnemyServer::OnDie(Entity* self, Entity* killer) { self->SetPosition({ 10000, 0, 10000 }); Game::entityManager->SerializeEntity(self); + ToggleAttacking(*self, false); controller->OnFireEventServerSide(self, "ClearProperty"); } @@ -634,3 +656,19 @@ float BossSpiderQueenEnemyServer::PlayAnimAndReturnTime(Entity* self, const std: return animTimer; } + +void BossSpiderQueenEnemyServer::ToggleAttacking(Entity& self, bool on) { + const auto stoppedFlag = self.GetVarAs(u"stoppedFlag"); + + if (!on) { + if (stoppedFlag) return; + + self.SetVar(u"stoppedFlag", true); + combat->Stun(100000.0f, true); // forcibly stun so we stop attacking people trying to put on armor + } else { + if (!stoppedFlag) return; + + self.SetVar(u"stoppedFlag", false); + combat->Stun(0.0f, true); // forcibly turn off the stun we put on above + } +} diff --git a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.h b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.h index b5909000..0f975abe 100644 --- a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.h +++ b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.h @@ -46,7 +46,10 @@ public: void OnTimerDone(Entity* self, std::string timerName) override; + void OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status); + private: + void ToggleAttacking(Entity& self, bool on); //Regular variables: DestroyableComponent* destroyable = nullptr; ControllablePhysicsComponent* controllable = nullptr; diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 8de1d4e5..e4d324f1 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -1019,6 +1019,7 @@ void HandlePacket(Packet* packet) { if (user) { Character* c = user->GetLastUsedChar(); if (c != nullptr) { + if (Game::entityManager->GetEntity(c->GetObjectID())) return; std::u16string username = GeneralUtils::ASCIIToUTF16(c->GetName()); Game::server->GetReplicaManager()->AddParticipant(packet->systemAddress);