mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-07-20 14:21:57 +00:00
fix: enemies on path not resetting aggro radius correctly (#2015)
tested that aggro is reset correctly and enemies no longer infinitely path to players. Tether now properly calculates the time to tether instead of guessing
This commit is contained in:
@@ -65,7 +65,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t compo
|
||||
|
||||
if (!componentResult.fieldIsNull("hardTetherRadius"))
|
||||
m_HardTetherRadius = componentResult.getFloatField("hardTetherRadius");
|
||||
|
||||
|
||||
m_MinRoundLength = componentResult.getFloatField("minRoundLength");
|
||||
m_MaxRoundLength = componentResult.getFloatField("maxRoundLength");
|
||||
m_CombatRoundLength = componentResult.getFloatField("combatRoundLength");
|
||||
@@ -161,15 +161,10 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
|
||||
// Check if we should stop the tether effect
|
||||
if (m_TetherEffectActive) {
|
||||
m_TetherTime -= deltaTime;
|
||||
if (m_Target != LWOOBJID_EMPTY || (NiPoint3::DistanceSquared(
|
||||
m_StartPosition,
|
||||
m_Parent->GetPosition()) < 20 * 20 && m_TetherTime <= 0)
|
||||
) {
|
||||
GameMessages::SendStopFXEffect(m_Parent, true, "tether");
|
||||
m_TetherEffectActive = false;
|
||||
}
|
||||
m_ForcedTetherTime -= deltaTime;
|
||||
if (m_ForcedTetherTime >= 0) return;
|
||||
GameMessages::SendStopFXEffect(m_Parent, true, "tether");
|
||||
m_TetherEffectActive = false;
|
||||
}
|
||||
|
||||
for (auto entry = m_RemovedThreatList.begin(); entry != m_RemovedThreatList.end();) {
|
||||
@@ -316,14 +311,17 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
||||
SetAiState(AiState::aggro);
|
||||
} else {
|
||||
SetAiState(AiState::idle);
|
||||
if (m_MovementAI) m_MovementAI->SetMaxSpeed(1.0f);
|
||||
// Don't clobber the pursuit speed set by TetherLogic while the tether is still active,
|
||||
// otherwise the entity slows to wander speed and takes far longer than m_TetherTime to
|
||||
// return to its spawn point.
|
||||
if (m_MovementAI && !m_TetherEffectActive) m_MovementAI->SetMaxSpeed(1.0f);
|
||||
}
|
||||
|
||||
if (!hasSkillToCast) return;
|
||||
|
||||
if (m_Target == LWOOBJID_EMPTY) {
|
||||
SetAiState(AiState::idle);
|
||||
if (m_MovementAI) m_MovementAI->SetMaxSpeed(1.0f);
|
||||
if (m_MovementAI && !m_TetherEffectActive) m_MovementAI->SetMaxSpeed(1.0f);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -810,7 +808,9 @@ void BaseCombatAIComponent::Wake() {
|
||||
void BaseCombatAIComponent::TetherLogic() {
|
||||
auto* destroyableComponent = m_Parent->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (destroyableComponent != nullptr && destroyableComponent->HasFaction(4)) {
|
||||
const bool applyTetherEffect = destroyableComponent != nullptr && destroyableComponent->HasFaction(4);
|
||||
|
||||
if (applyTetherEffect) {
|
||||
auto serilizationRequired = false;
|
||||
|
||||
if (destroyableComponent->GetHealth() != destroyableComponent->GetMaxHealth()) {
|
||||
@@ -832,16 +832,27 @@ void BaseCombatAIComponent::TetherLogic() {
|
||||
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->SetCurrentSpeed(m_PursuitSpeed);
|
||||
m_MovementAI->SetDestination(m_StartPosition);
|
||||
}
|
||||
|
||||
if (applyTetherEffect) {
|
||||
// Use the actual navmesh path length (populated by SetDestination above) instead of the
|
||||
// straight-line distance, otherwise the tether timer expires before the entity can walk
|
||||
// around obstacles back to its spawn point.
|
||||
const auto distance = m_MovementAI != nullptr
|
||||
? m_MovementAI->GetRemainingPathDistance()
|
||||
: NiPoint3::Distance(m_Parent->GetPosition(), m_StartPosition);
|
||||
const auto speed = m_PursuitSpeed * MovementAIComponent::GetBaseSpeed(m_Parent->GetLOT());
|
||||
m_TetherTime = speed > 0.0f ? distance / speed : 0.0f;
|
||||
m_ForcedTetherTime = m_TetherTime;
|
||||
}
|
||||
}
|
||||
|
||||
void BaseCombatAIComponent::ForceTether() {
|
||||
|
||||
@@ -241,6 +241,10 @@ public:
|
||||
|
||||
void SetStartingPosition(const NiPoint3& pos) { m_StartPosition = pos; }
|
||||
|
||||
bool GetOutOfCombat() const { return m_OutOfCombat; }
|
||||
|
||||
bool GetIsTethering() const { return m_TetherEffectActive; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* Returns the current target or the target that currently is the largest threat to this entity
|
||||
|
||||
@@ -131,9 +131,13 @@ void MovementAIComponent::Update(const float deltaTime) {
|
||||
|
||||
const auto approxPos = ApproximateLocation();
|
||||
SetPosition(approxPos);
|
||||
// Set the AIs new home based on where our current waypoint is IF we're idle, that way we can return to this
|
||||
// Set the AIs new home based on where our current waypoint is IF wehave a path, we're idle, not out of combat (grace period if you left the radius),
|
||||
// and we're not tethering to home, that way we can return to this
|
||||
// when resuming the pathing after losing aggro while moving the aggro hitbox with us
|
||||
if (m_BaseCombatAI && m_BaseCombatAI->GetState() == AiState::idle) m_BaseCombatAI->SetStartingPosition(approxPos);
|
||||
const bool idleNotResetting = m_Path && m_BaseCombatAI && m_BaseCombatAI->GetState() == AiState::idle && !m_BaseCombatAI->GetOutOfCombat() && !m_BaseCombatAI->GetIsTethering();
|
||||
if (idleNotResetting) {
|
||||
m_BaseCombatAI->SetStartingPosition(approxPos);
|
||||
}
|
||||
|
||||
if (m_TimeTravelled < m_TimeToTravel) return;
|
||||
m_TimeTravelled = 0.0f;
|
||||
@@ -169,7 +173,7 @@ void MovementAIComponent::Update(const float deltaTime) {
|
||||
}
|
||||
} else {
|
||||
// Only try to renew or continue the path if we're in the idle or spawn state and we actually have a combatAI component
|
||||
if (!m_BaseCombatAI || (m_BaseCombatAI && m_BaseCombatAI->GetState() == AiState::idle)) {
|
||||
if (!m_BaseCombatAI || (idleNotResetting)) {
|
||||
// Check if there are more waypoints in the queue, if so set our next destination to the next waypoint
|
||||
const auto waypointNum = m_IsBounced ? m_CurrentPath.size() : m_CurrentPathWaypointCount - m_CurrentPath.size() - 1;
|
||||
RunWaypointCommands(waypointNum);
|
||||
@@ -205,6 +209,8 @@ void MovementAIComponent::Update(const float deltaTime) {
|
||||
|
||||
m_CurrentPath.pop();
|
||||
}
|
||||
} else {
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,6 +347,18 @@ float MovementAIComponent::GetBaseSpeed(LOT lot) {
|
||||
return speed;
|
||||
}
|
||||
|
||||
float MovementAIComponent::GetRemainingPathDistance() const {
|
||||
if (m_InterpolatedWaypoints.empty() || m_PathIndex >= m_InterpolatedWaypoints.size()) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float total = NiPoint3::Distance(m_Parent->GetPosition(), m_InterpolatedWaypoints[m_PathIndex]);
|
||||
for (size_t i = m_PathIndex; i + 1 < m_InterpolatedWaypoints.size(); ++i) {
|
||||
total += NiPoint3::Distance(m_InterpolatedWaypoints[i], m_InterpolatedWaypoints[i + 1]);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
void MovementAIComponent::SetPosition(const NiPoint3& value) {
|
||||
m_Parent->SetPosition(value);
|
||||
}
|
||||
|
||||
@@ -209,6 +209,13 @@ public:
|
||||
*/
|
||||
static float GetBaseSpeed(LOT lot);
|
||||
|
||||
/**
|
||||
* Returns the total distance remaining along the current path, following the
|
||||
* interpolated waypoints from the entity's current position to the final destination.
|
||||
* @return the total remaining path distance in world units
|
||||
*/
|
||||
float GetRemainingPathDistance() const;
|
||||
|
||||
bool IsPaused() const { return m_Paused; }
|
||||
|
||||
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||
|
||||
Reference in New Issue
Block a user