feat: implement SMSG_CAMERA_SHAKE with sinusoidal camera shake effect

- Add triggerShake(magnitude, frequency, duration) to CameraController
- Apply envelope-decaying sinusoidal XYZ offset to camera in update()
- Handle SMSG_CAMERA_SHAKE opcode in GameHandler dispatch
- Translate shakeId to magnitude (minor <50: 0.04, larger: 0.08 world units)
- Wire CameraShakeCallback from GameHandler through to CameraController
- Shake uses 18Hz oscillation with 30% fade-out envelope at end of duration
This commit is contained in:
Kelsi 2026-03-12 19:37:53 -07:00
parent 214c1a9ff8
commit 9aa4b223dc
5 changed files with 70 additions and 0 deletions

View file

@ -646,6 +646,11 @@ void Application::setState(AppState newState) {
renderer->getCameraController()->applyKnockBack(vcos, vsin, hspeed, vspeed);
}
});
gameHandler->setCameraShakeCallback([this](float magnitude, float frequency, float duration) {
if (renderer && renderer->getCameraController()) {
renderer->getCameraController()->triggerShake(magnitude, frequency, duration);
}
});
}
// Load quest marker models
loadQuestMarkerModels();

View file

@ -2760,6 +2760,25 @@ void GameHandler::handlePacket(network::Packet& packet) {
handleMoveKnockBack(packet);
break;
case Opcode::SMSG_CAMERA_SHAKE: {
// uint32 shakeID (CameraShakes.dbc), uint32 shakeType
// We don't parse CameraShakes.dbc; apply a hardcoded moderate shake.
if (packet.getSize() - packet.getReadPos() >= 8) {
uint32_t shakeId = packet.readUInt32();
uint32_t shakeType = packet.readUInt32();
(void)shakeType;
// Map shakeId ranges to approximate magnitudes:
// IDs < 50: minor environmental (0.04), others: larger boss effects (0.08)
float magnitude = (shakeId < 50) ? 0.04f : 0.08f;
if (cameraShakeCallback_) {
cameraShakeCallback_(magnitude, 18.0f, 0.5f);
}
LOG_DEBUG("SMSG_CAMERA_SHAKE: id=", shakeId, " type=", shakeType,
" magnitude=", magnitude);
}
break;
}
case Opcode::SMSG_CLIENT_CONTROL_UPDATE: {
// Minimal parse: PackedGuid + uint8 allowMovement.
if (packet.getSize() - packet.getReadPos() < 2) {

View file

@ -140,6 +140,17 @@ std::optional<float> CameraController::getCachedFloorHeight(float x, float y, fl
return result;
}
void CameraController::triggerShake(float magnitude, float frequency, float duration) {
// Allow stronger shake to override weaker; don't allow zero magnitude.
if (magnitude <= 0.0f || duration <= 0.0f) return;
if (magnitude > shakeMagnitude_ || shakeElapsed_ >= shakeDuration_) {
shakeMagnitude_ = magnitude;
shakeFrequency_ = frequency;
shakeDuration_ = duration;
shakeElapsed_ = 0.0f;
}
}
void CameraController::update(float deltaTime) {
if (!enabled || !camera) {
return;
@ -1859,6 +1870,23 @@ void CameraController::update(float deltaTime) {
wasFalling = !grounded && verticalVelocity <= 0.0f;
// R key is now handled above with chat safeguard (WantTextInput check)
// Camera shake (SMSG_CAMERA_SHAKE): apply sinusoidal offset to final camera position.
if (shakeElapsed_ < shakeDuration_) {
shakeElapsed_ += deltaTime;
float t = shakeElapsed_ / shakeDuration_;
// Envelope: fade out over the last 30% of shake duration
float envelope = (t < 0.7f) ? 1.0f : (1.0f - (t - 0.7f) / 0.3f);
float theta = shakeElapsed_ * shakeFrequency_ * 2.0f * 3.14159265f;
glm::vec3 offset(
shakeMagnitude_ * envelope * std::sin(theta),
shakeMagnitude_ * envelope * std::cos(theta * 1.3f),
shakeMagnitude_ * envelope * std::sin(theta * 0.7f) * 0.5f
);
if (camera) {
camera->setPosition(camera->getPosition() + offset);
}
}
}
void CameraController::processMouseMotion(const SDL_MouseMotionEvent& event) {