mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
The client only simulates knockbacks on the character it controls (LWOControllablePhysComponent::msgKnockback); enemies and NPCs are drawn where the server puts them, so a knockback on them did nothing and scripts stunned them instead. MovementAIComponent::Knockback now flies the object the way the client flies its character: a vector longer than 5 lifts it 0.5, throws it with that velocity under WorldConfig gravity (times its gravity scale) until it lands on the navmesh, no sooner than 250ms later; a shorter one moves it by the vector. Walls taller than a step stop sideways motion, the landing is put back onto the navmesh, and pathing and the combat AI wait until it lands (destinations set mid air are walked to afterwards). Position and velocity go out in the normal serialization every tick. KnockbackBehavior builds the vector like the client's Cast (strength capped at 300, angle as elevation, relative, caster and ignore_self) and knocks back server moved targets that aren't immune, both when the server casts and when a client's skill hits. The blocked bit it writes now also answers for the target's knockback immunity, so a player whose client hasn't caught up with a Personal Fortress isn't knocked out of it. The AM shield generators knock enemies back like live instead of stunning them. Fixes #257 Refs #185 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
2.9 KiB
C++
96 lines
2.9 KiB
C++
#include "dpKnockback.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <numbers>
|
|
|
|
NiPoint3 dpKnockback::ComputeVector(const NiPoint3& away, const float angleDegrees, const float strength) {
|
|
NiPoint3 direction{ 0.0f, 1.0f, 0.0f };
|
|
|
|
// Anything but (nearly) straight up: the flat direction raised by the angle
|
|
if (angleDegrees <= 89.0f || angleDegrees >= 91.0f) {
|
|
direction = NiPoint3(away.x, 0.0f, away.z);
|
|
const auto flatLength = direction.Length();
|
|
direction = flatLength > 0.0f ? direction / flatLength : NiPoint3Constant::ZERO;
|
|
direction.y += std::tan(angleDegrees * std::numbers::pi_v<float> / 180.0f);
|
|
const auto length = direction.Length();
|
|
direction = length > 0.0f ? direction / length : NiPoint3Constant::ZERO;
|
|
}
|
|
|
|
return direction * std::min(strength, MAX_STRENGTH);
|
|
}
|
|
|
|
bool dpKnockback::Arc::Start(const NiPoint3& position, const NiPoint3& vector) {
|
|
m_Elapsed = 0.0f;
|
|
if (vector.SquaredLength() <= MIN_LAUNCH_SPEED * MIN_LAUNCH_SPEED) {
|
|
m_Active = false;
|
|
m_Position = position;
|
|
m_Velocity = NiPoint3Constant::ZERO;
|
|
return false;
|
|
}
|
|
|
|
m_Active = true;
|
|
m_Position = position;
|
|
m_Position.y += LAUNCH_LIFT;
|
|
m_Velocity = vector;
|
|
return true;
|
|
}
|
|
|
|
bool dpKnockback::Arc::Step(float deltaTime, const float gravity, const GroundQuery& ground) {
|
|
while (m_Active && deltaTime > 0.0f) {
|
|
const auto step = std::min(deltaTime, MAX_SUB_STEP);
|
|
deltaTime -= step;
|
|
if (!SubStep(step, gravity, ground)) m_Active = false;
|
|
}
|
|
|
|
return m_Active;
|
|
}
|
|
|
|
bool dpKnockback::Arc::SubStep(const float deltaTime, const float gravity, const GroundQuery& ground) {
|
|
m_Elapsed += deltaTime;
|
|
|
|
// Semi-implicit Euler, like the character controller: velocity first, then position
|
|
m_Velocity.y -= gravity * deltaTime;
|
|
auto next = m_Position + m_Velocity * deltaTime;
|
|
|
|
auto groundHeight = ground(next);
|
|
if (groundHeight - next.y > MAX_STEP_HEIGHT && groundHeight - m_Position.y > MAX_STEP_HEIGHT) {
|
|
// Flew into something taller than a step: stop going sideways and keep falling where we are
|
|
m_Velocity.x = 0.0f;
|
|
m_Velocity.z = 0.0f;
|
|
next.x = m_Position.x;
|
|
next.z = m_Position.z;
|
|
groundHeight = ground(next);
|
|
}
|
|
|
|
m_Position = next;
|
|
|
|
const bool falling = m_Velocity.y <= 0.0f;
|
|
if (falling && m_Position.y <= groundHeight && m_Elapsed >= MIN_AIRBORNE_TIME) {
|
|
m_Position.y = groundHeight;
|
|
m_Velocity = NiPoint3Constant::ZERO;
|
|
return false;
|
|
}
|
|
|
|
// Below the ground before the minimum time is up (or still going up): ride along the ground
|
|
if (m_Position.y < groundHeight) {
|
|
m_Position.y = groundHeight;
|
|
if (m_Velocity.y < 0.0f) m_Velocity.y = 0.0f;
|
|
}
|
|
|
|
if (m_Elapsed >= MAX_AIRBORNE_TIME) {
|
|
m_Velocity = NiPoint3Constant::ZERO;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
NiPoint3 dpKnockback::Nudge(const NiPoint3& position, const NiPoint3& vector, const float maxDrop, const GroundQuery& ground) {
|
|
auto moved = position + vector;
|
|
const auto groundHeight = ground(moved);
|
|
if (std::abs(moved.y - groundHeight) > maxDrop) return position;
|
|
moved.y = groundHeight;
|
|
return moved;
|
|
}
|