672 lines
20 KiB
C++
672 lines
20 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "alcachofa/camera.h"
|
|
#include "alcachofa/alcachofa.h"
|
|
#include "alcachofa/script.h"
|
|
|
|
#include "common/system.h"
|
|
#include "math/vector4d.h"
|
|
|
|
using namespace Common;
|
|
using namespace Math;
|
|
|
|
namespace Alcachofa {
|
|
|
|
void Camera::resetRotationAndScale() {
|
|
_cur._scale = 1;
|
|
_cur._rotation = 0;
|
|
_cur._usedCenter.z() = 0;
|
|
}
|
|
|
|
void Camera::setRoomBounds(Point bgSize, int16 bgScale) {
|
|
float scaleFactor = 1 - bgScale * kInvBaseScale;
|
|
_roomMin = Vector2d(
|
|
g_system->getWidth() / 2 * scaleFactor,
|
|
g_system->getHeight() / 2 * scaleFactor);
|
|
_roomMax = _roomMin + Vector2d(
|
|
bgSize.x * bgScale * kInvBaseScale,
|
|
bgSize.y * bgScale * kInvBaseScale);
|
|
_roomScale = bgScale;
|
|
}
|
|
|
|
void Camera::setFollow(WalkingCharacter *target, bool catchUp) {
|
|
_cur._isFollowingTarget = target != nullptr;
|
|
_followTarget = target;
|
|
_lastUpdateTime = g_engine->getMillis();
|
|
_catchUp = catchUp;
|
|
if (target == nullptr)
|
|
_isChanging = false;
|
|
}
|
|
|
|
void Camera::setPosition(Vector2d v) {
|
|
setPosition({ v.getX(), v.getY(), _cur._usedCenter.z() });
|
|
}
|
|
|
|
void Camera::setPosition(Vector3d v) {
|
|
_cur._usedCenter = v;
|
|
setFollow(nullptr);
|
|
}
|
|
|
|
void Camera::backup(uint slot) {
|
|
assert(slot < kStateBackupCount);
|
|
_backups[slot] = _cur;
|
|
}
|
|
|
|
void Camera::restore(uint slot) {
|
|
assert(slot < kStateBackupCount);
|
|
auto backupState = _backups[slot];
|
|
_backups[slot] = _cur;
|
|
_cur = backupState;
|
|
}
|
|
|
|
static Matrix4 scale2DMatrix(float scale) {
|
|
Matrix4 m;
|
|
m(0, 0) = scale;
|
|
m(1, 1) = scale;
|
|
return m;
|
|
}
|
|
|
|
void Camera::setupMatricesAround(Vector3d center) {
|
|
Matrix4 matTemp;
|
|
matTemp.buildAroundZ(_cur._rotation);
|
|
_mat3Dto2D.setToIdentity();
|
|
_mat3Dto2D.translate(-center);
|
|
_mat3Dto2D = matTemp * _mat3Dto2D;
|
|
_mat3Dto2D = scale2DMatrix(_cur._scale) * _mat3Dto2D;
|
|
|
|
_mat2Dto3D.setToIdentity();
|
|
_mat2Dto3D.translate(center);
|
|
matTemp.buildAroundZ(-_cur._rotation);
|
|
matTemp = matTemp * scale2DMatrix(1 / _cur._scale);
|
|
_mat2Dto3D = _mat2Dto3D * matTemp;
|
|
}
|
|
|
|
void minmax(Vector3d &min, Vector3d &max, Vector3d val) {
|
|
min.set(
|
|
MIN(min.x(), val.x()),
|
|
MIN(min.y(), val.y()),
|
|
MIN(min.z(), val.z()));
|
|
max.set(
|
|
MAX(max.x(), val.x()),
|
|
MAX(max.y(), val.y()),
|
|
MAX(max.z(), val.z()));
|
|
}
|
|
|
|
Vector3d Camera::setAppliedCenter(Vector3d center) {
|
|
setupMatricesAround(center);
|
|
if (g_engine->game().shouldClipCamera()) {
|
|
const float screenW = g_system->getWidth(), screenH = g_system->getHeight();
|
|
Vector3d min, max;
|
|
min = max = transform2Dto3D(Vector3d(0, 0, _roomScale));
|
|
minmax(min, max, transform2Dto3D(Vector3d(screenW, 0, _roomScale)));
|
|
minmax(min, max, transform2Dto3D(Vector3d(screenW, screenH, _roomScale)));
|
|
minmax(min, max, transform2Dto3D(Vector3d(0, screenH, _roomScale)));
|
|
center.x() += MAX(0.0f, _roomMin.getX() - min.x());
|
|
center.y() += MAX(0.0f, _roomMin.getY() - min.y());
|
|
center.x() -= MAX(0.0f, max.x() - _roomMax.getX());
|
|
center.y() -= MAX(0.0f, max.y() - _roomMax.getY());
|
|
setupMatricesAround(center);
|
|
}
|
|
return _appliedCenter = center;
|
|
}
|
|
|
|
Vector3d Camera::transform2Dto3D(Vector3d v2d) const {
|
|
// if this looks like normal 3D math to *someone* please contact.
|
|
Vector4d vh;
|
|
vh.w() = 1.0f;
|
|
vh.z() = v2d.z() - _cur._usedCenter.z();
|
|
vh.y() = (v2d.y() - g_system->getHeight() * 0.5f) * vh.z() * kInvBaseScale;
|
|
vh.x() = (v2d.x() - g_system->getWidth() * 0.5f) * vh.z() * kInvBaseScale;
|
|
vh = _mat2Dto3D * vh;
|
|
return Vector3d(vh.x(), vh.y(), 0.0f);
|
|
}
|
|
|
|
Vector3d Camera::transform3Dto2D(Vector3d v3d) const {
|
|
// I swear there is a better way than this. This is stupid. But it is original.
|
|
float depthScale = v3d.z() * kInvBaseScale;
|
|
Vector4d vh;
|
|
vh.x() = v3d.x() * depthScale + (1 - depthScale) * g_system->getWidth() * 0.5f;
|
|
vh.y() = v3d.y() * depthScale + (1 - depthScale) * g_system->getHeight() * 0.5f;
|
|
vh.z() = v3d.z();
|
|
vh.w() = 1.0f;
|
|
vh = _mat3Dto2D * vh;
|
|
return Vector3d(
|
|
g_system->getWidth() * 0.5f + vh.x() * kBaseScale / vh.z(),
|
|
g_system->getHeight() * 0.5f + vh.y() * kBaseScale / vh.z(),
|
|
_cur._scale * kBaseScale / vh.z());
|
|
}
|
|
|
|
Point Camera::transform3Dto2D(Point p3d) const {
|
|
auto v2d = transform3Dto2D({ (float)p3d.x, (float)p3d.y, kBaseScale });
|
|
return { (int16)v2d.x(), (int16)v2d.y() };
|
|
}
|
|
|
|
void Camera::update() {
|
|
// original would be some smoothing of delta times, let's not.
|
|
uint32 now = g_engine->getMillis();
|
|
float deltaTime = (now - _lastUpdateTime) / 1000.0f;
|
|
deltaTime = MAX(0.001f, MIN(0.5f, deltaTime));
|
|
_lastUpdateTime = now;
|
|
|
|
if (_catchUp) {
|
|
for (int i = 0; i < 4; i++)
|
|
updateFollowing(50.0f);
|
|
_catchUp = false;
|
|
} else
|
|
updateFollowing(deltaTime);
|
|
setAppliedCenter(_cur._usedCenter + Vector3d(_shake.getX(), _shake.getY(), 0.0f));
|
|
}
|
|
|
|
void Camera::updateFollowing(float deltaTime) {
|
|
if (!_cur._isFollowingTarget || _followTarget == nullptr)
|
|
return;
|
|
const float resolutionFactor = g_system->getWidth() * 0.00125f;
|
|
const float acceleration = 460 * resolutionFactor;
|
|
const float baseDeadZoneSize = 25 * resolutionFactor;
|
|
const float minSpeed = 20 * resolutionFactor;
|
|
const float maxSpeed = this->_cur._maxSpeedFactor * resolutionFactor;
|
|
const float depthScale = _followTarget->graphic()->depthScale();
|
|
const auto characterPolygon = _followTarget->shape()->at(0);
|
|
const float halfHeight = ABS(characterPolygon._points[0].y - characterPolygon._points[2].y) / 2.0f;
|
|
|
|
Vector3d targetCenter = setAppliedCenter({
|
|
_shake.getX() + _followTarget->position().x,
|
|
_shake.getY() + _followTarget->position().y - depthScale * 85,
|
|
_cur._usedCenter.z() });
|
|
targetCenter.y() -= halfHeight;
|
|
float distanceToTarget = as2D(_cur._usedCenter - targetCenter).getMagnitude();
|
|
float moveDistance = _followTarget->stepSizeFactor() * _cur._speed * deltaTime;
|
|
|
|
float deadZoneSize = baseDeadZoneSize / _cur._scale;
|
|
if (_followTarget->isWalking() && depthScale > 0.8f)
|
|
deadZoneSize = (baseDeadZoneSize + (depthScale - 0.8f) * 200) / _cur._scale;
|
|
bool isFarAway = false;
|
|
if (ABS(targetCenter.x() - _cur._usedCenter.x()) > deadZoneSize ||
|
|
ABS(targetCenter.y() - _cur._usedCenter.y()) > deadZoneSize) {
|
|
isFarAway = true;
|
|
_cur._isBraking = false;
|
|
_isChanging = true;
|
|
}
|
|
|
|
if (_cur._isBraking) {
|
|
_cur._speed -= acceleration * 0.9f * deltaTime;
|
|
_cur._speed = MAX(_cur._speed, minSpeed);
|
|
}
|
|
if (_isChanging && !_cur._isBraking) {
|
|
_cur._speed += acceleration * deltaTime;
|
|
_cur._speed = MIN(_cur._speed, maxSpeed);
|
|
if (!isFarAway)
|
|
_cur._isBraking = true;
|
|
}
|
|
if (_isChanging) {
|
|
if (distanceToTarget <= moveDistance) {
|
|
_cur._usedCenter = targetCenter;
|
|
_isChanging = false;
|
|
_cur._isBraking = false;
|
|
} else {
|
|
Vector3d deltaCenter = targetCenter - _cur._usedCenter;
|
|
deltaCenter.z() = 0.0f;
|
|
_cur._usedCenter += deltaCenter * moveDistance / distanceToTarget;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void syncMatrix(Serializer &s, Matrix4 &m) {
|
|
float *data = m.getData();
|
|
for (int i = 0; i < 16; i++)
|
|
s.syncAsFloatLE(data[i]);
|
|
}
|
|
|
|
static void syncVector(Serializer &s, Vector3d &v) {
|
|
s.syncAsFloatLE(v.x());
|
|
s.syncAsFloatLE(v.y());
|
|
s.syncAsFloatLE(v.z());
|
|
}
|
|
|
|
void Camera::State::syncGame(Serializer &s) {
|
|
syncVector(s, _usedCenter);
|
|
s.syncAsFloatLE(_scale);
|
|
s.syncAsFloatLE(_speed);
|
|
s.syncAsFloatLE(_maxSpeedFactor);
|
|
float rotationDegs = _rotation.getDegrees();
|
|
s.syncAsFloatLE(rotationDegs);
|
|
_rotation.setDegrees(rotationDegs);
|
|
s.syncAsByte(_isBraking);
|
|
s.syncAsByte(_isFollowingTarget);
|
|
}
|
|
|
|
void Camera::syncGame(Serializer &s) {
|
|
syncMatrix(s, _mat3Dto2D);
|
|
syncMatrix(s, _mat2Dto3D);
|
|
syncVector(s, _appliedCenter);
|
|
s.syncAsUint32LE(_lastUpdateTime);
|
|
s.syncAsByte(_isChanging);
|
|
_cur.syncGame(s);
|
|
for (uint i = 0; i < kStateBackupCount; i++)
|
|
_backups[i].syncGame(s);
|
|
|
|
// originally the follow object is also searched for before changing the room
|
|
// so that would practically mean only the main characters could be reasonably found
|
|
// instead we fall back to global search
|
|
String name;
|
|
if (_followTarget != nullptr)
|
|
name = _followTarget->name();
|
|
s.syncString(name);
|
|
if (s.isLoading()) {
|
|
if (name.empty())
|
|
_followTarget = nullptr;
|
|
else {
|
|
_followTarget = dynamic_cast<WalkingCharacter *>(g_engine->world().getObjectByName(name.c_str()));
|
|
if (_followTarget == nullptr)
|
|
_followTarget = dynamic_cast<WalkingCharacter *>(g_engine->world().getObjectByNameFromAnyRoom(name.c_str()));
|
|
if (_followTarget == nullptr)
|
|
warning("Camera follow target from savestate was not found: %s", name.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
struct CamLerpTask : public Task {
|
|
CamLerpTask(Process &process, uint32 duration = 0, EasingType easingType = EasingType::Linear)
|
|
: Task(process)
|
|
, _camera(g_engine->camera())
|
|
, _duration(duration)
|
|
, _easingType(easingType) {}
|
|
|
|
TaskReturn run() override {
|
|
TASK_BEGIN;
|
|
_startTime = g_engine->getMillis();
|
|
while (g_engine->getMillis() - _startTime < _duration) {
|
|
update(ease((g_engine->getMillis() - _startTime) / (float)_duration, _easingType));
|
|
_camera._isChanging = true;
|
|
TASK_YIELD(1);
|
|
}
|
|
update(1.0f);
|
|
TASK_END;
|
|
}
|
|
|
|
void debugPrint() override {
|
|
uint32 remaining = g_engine->getMillis() - _startTime <= _duration
|
|
? _duration - (g_engine->getMillis() - _startTime)
|
|
: 0;
|
|
g_engine->console().debugPrintf("%s camera with %ums remaining\n", taskName(), remaining);
|
|
}
|
|
|
|
void syncGame(Serializer &s) override {
|
|
Task::syncGame(s);
|
|
s.syncAsUint32LE(_startTime);
|
|
s.syncAsUint32LE(_duration);
|
|
syncEnum(s, _easingType);
|
|
}
|
|
|
|
protected:
|
|
virtual void update(float t) = 0;
|
|
|
|
Camera &_camera;
|
|
uint32 _startTime = 0, _duration;
|
|
EasingType _easingType;
|
|
};
|
|
|
|
struct CamLerpPosTask final : public CamLerpTask {
|
|
CamLerpPosTask(Process &process, Vector3d targetPos, int32 duration, EasingType easingType)
|
|
: CamLerpTask(process, duration, easingType)
|
|
, _fromPos(_camera._appliedCenter)
|
|
, _deltaPos(targetPos - _camera._appliedCenter) {}
|
|
|
|
CamLerpPosTask(Process &process, Serializer &s)
|
|
: CamLerpTask(process) {
|
|
syncGame(s);
|
|
}
|
|
|
|
void syncGame(Serializer &s) override {
|
|
CamLerpTask::syncGame(s);
|
|
syncVector(s, _fromPos);
|
|
syncVector(s, _deltaPos);
|
|
}
|
|
|
|
const char *taskName() const override;
|
|
|
|
protected:
|
|
void update(float t) override {
|
|
_camera.setPosition(_fromPos + _deltaPos * t);
|
|
}
|
|
|
|
Vector3d _fromPos, _deltaPos;
|
|
};
|
|
DECLARE_TASK(CamLerpPosTask)
|
|
|
|
struct CamLerpScaleTask final : public CamLerpTask {
|
|
CamLerpScaleTask(Process &process, float targetScale, int32 duration, EasingType easingType)
|
|
: CamLerpTask(process, duration, easingType)
|
|
, _fromScale(_camera._cur._scale)
|
|
, _deltaScale(targetScale - _camera._cur._scale) {}
|
|
|
|
CamLerpScaleTask(Process &process, Serializer &s)
|
|
: CamLerpTask(process) {
|
|
syncGame(s);
|
|
}
|
|
|
|
void syncGame(Serializer &s) override {
|
|
CamLerpTask::syncGame(s);
|
|
s.syncAsFloatLE(_fromScale);
|
|
s.syncAsFloatLE(_deltaScale);
|
|
}
|
|
|
|
const char *taskName() const override;
|
|
|
|
protected:
|
|
void update(float t) override {
|
|
_camera._cur._scale = _fromScale + _deltaScale * t;
|
|
}
|
|
|
|
float _fromScale = 0, _deltaScale = 0;
|
|
};
|
|
DECLARE_TASK(CamLerpScaleTask)
|
|
|
|
struct CamLerpPosScaleTask final : public CamLerpTask {
|
|
CamLerpPosScaleTask(Process &process,
|
|
Vector3d targetPos, float targetScale,
|
|
int32 duration,
|
|
EasingType moveEasingType, EasingType scaleEasingType)
|
|
: CamLerpTask(process, duration, EasingType::Linear) // linear as we need different ones per component
|
|
, _fromPos(_camera._appliedCenter)
|
|
, _deltaPos(targetPos - _camera._appliedCenter)
|
|
, _fromScale(_camera._cur._scale)
|
|
, _deltaScale(targetScale - _camera._cur._scale)
|
|
, _moveEasingType(moveEasingType)
|
|
, _scaleEasingType(scaleEasingType) {}
|
|
|
|
CamLerpPosScaleTask(Process &process, Serializer &s)
|
|
: CamLerpTask(process) {
|
|
syncGame(s);
|
|
}
|
|
|
|
void syncGame(Serializer &s) override {
|
|
CamLerpTask::syncGame(s);
|
|
syncVector(s, _fromPos);
|
|
syncVector(s, _deltaPos);
|
|
s.syncAsFloatLE(_fromScale);
|
|
s.syncAsFloatLE(_deltaScale);
|
|
syncEnum(s, _moveEasingType);
|
|
syncEnum(s, _scaleEasingType);
|
|
}
|
|
|
|
const char *taskName() const override;
|
|
|
|
protected:
|
|
void update(float t) override {
|
|
_camera.setPosition(_fromPos + _deltaPos * ease(t, _moveEasingType));
|
|
_camera._cur._scale = _fromScale + _deltaScale * ease(t, _scaleEasingType);
|
|
}
|
|
|
|
Vector3d _fromPos, _deltaPos;
|
|
float _fromScale = 0, _deltaScale = 0;
|
|
EasingType _moveEasingType = {}, _scaleEasingType = {};
|
|
};
|
|
DECLARE_TASK(CamLerpPosScaleTask)
|
|
|
|
struct CamLerpRotationTask final : public CamLerpTask {
|
|
CamLerpRotationTask(Process &process, float targetRotation, int32 duration, EasingType easingType)
|
|
: CamLerpTask(process, duration, easingType)
|
|
, _fromRotation(_camera._cur._rotation.getDegrees())
|
|
, _deltaRotation(targetRotation - _camera._cur._rotation.getDegrees()) {}
|
|
|
|
CamLerpRotationTask(Process &process, Serializer &s)
|
|
: CamLerpTask(process) {
|
|
syncGame(s);
|
|
}
|
|
|
|
void syncGame(Serializer &s) override {
|
|
CamLerpTask::syncGame(s);
|
|
s.syncAsFloatLE(_fromRotation);
|
|
s.syncAsFloatLE(_deltaRotation);
|
|
}
|
|
|
|
const char *taskName() const override;
|
|
|
|
protected:
|
|
void update(float t) override {
|
|
_camera._cur._rotation = Angle(_fromRotation + _deltaRotation * t);
|
|
}
|
|
|
|
float _fromRotation = 0, _deltaRotation = 0;
|
|
};
|
|
DECLARE_TASK(CamLerpRotationTask)
|
|
|
|
static void syncVector(Serializer &s, Vector2d &v) {
|
|
float *data = v.getData();
|
|
s.syncAsFloatLE(data[0]);
|
|
s.syncAsFloatLE(data[1]);
|
|
}
|
|
|
|
struct CamShakeTask final : public CamLerpTask {
|
|
CamShakeTask(Process &process, Vector2d amplitude, Vector2d frequency, int32 duration)
|
|
: CamLerpTask(process, duration, EasingType::Linear)
|
|
, _amplitude(amplitude)
|
|
, _frequency(frequency) {}
|
|
|
|
CamShakeTask(Process &process, Serializer &s)
|
|
: CamLerpTask(process) {
|
|
syncGame(s);
|
|
}
|
|
|
|
void syncGame(Serializer &s) override {
|
|
CamLerpTask::syncGame(s);
|
|
syncVector(s, _amplitude);
|
|
syncVector(s, _frequency);
|
|
}
|
|
|
|
const char *taskName() const override;
|
|
|
|
protected:
|
|
void update(float t) override {
|
|
const Vector2d phase = _frequency * t * (float)M_PI * 2.0f;
|
|
const float amplTimeFactor = 1.0f / expf(t * 5.0f); // a curve starting at 1, depreciating towards 0
|
|
_camera.shake() = {
|
|
sinf(phase.getX()) * _amplitude.getX() * amplTimeFactor,
|
|
sinf(phase.getY()) * _amplitude.getY() * amplTimeFactor
|
|
};
|
|
}
|
|
|
|
Vector2d _amplitude, _frequency;
|
|
};
|
|
DECLARE_TASK(CamShakeTask)
|
|
|
|
struct CamWaitToStopTask final : public Task {
|
|
CamWaitToStopTask(Process &process)
|
|
: Task(process)
|
|
, _camera(g_engine->camera()) {}
|
|
|
|
CamWaitToStopTask(Process &process, Serializer &s)
|
|
: Task(process)
|
|
, _camera(g_engine->camera()) {
|
|
syncGame(s);
|
|
}
|
|
|
|
TaskReturn run() override {
|
|
return _camera._isChanging
|
|
? TaskReturn::yield()
|
|
: TaskReturn::finish(1);
|
|
}
|
|
|
|
void debugPrint() override {
|
|
g_engine->console().debugPrintf("Wait for camera to stop moving\n");
|
|
}
|
|
|
|
const char *taskName() const override;
|
|
|
|
private:
|
|
Camera &_camera;
|
|
};
|
|
DECLARE_TASK(CamWaitToStopTask)
|
|
|
|
struct CamSetInactiveAttributeTask final : public Task {
|
|
enum Attribute {
|
|
kPosZ,
|
|
kScale,
|
|
kRotation
|
|
};
|
|
|
|
CamSetInactiveAttributeTask(Process &process, Attribute attribute, float value, int32 delay)
|
|
: Task(process)
|
|
, _camera(g_engine->camera())
|
|
, _attribute(attribute)
|
|
, _value(value)
|
|
, _delay(delay) {}
|
|
|
|
CamSetInactiveAttributeTask(Process &process, Serializer &s)
|
|
: Task(process)
|
|
, _camera(g_engine->camera()) {
|
|
syncGame(s);
|
|
}
|
|
|
|
TaskReturn run() override {
|
|
if (_delay > 0) {
|
|
uint32 delay = (uint32)_delay;
|
|
_delay = 0;
|
|
return TaskReturn::waitFor(new DelayTask(process(), delay));
|
|
}
|
|
|
|
auto &state = _camera._backups[0];
|
|
switch (_attribute) {
|
|
case kPosZ:
|
|
state._usedCenter.z() = _value;
|
|
break;
|
|
case kScale:
|
|
state._scale = _value;
|
|
break;
|
|
case kRotation:
|
|
state._rotation = _value;
|
|
break;
|
|
default:
|
|
g_engine->game().unknownCamSetInactiveAttribute((int)_attribute);
|
|
break;
|
|
}
|
|
return TaskReturn::finish(0);
|
|
}
|
|
|
|
void debugPrint() override {
|
|
const char *attributeName;
|
|
switch (_attribute) {
|
|
case kPosZ:
|
|
attributeName = "PosZ";
|
|
break;
|
|
case kScale:
|
|
attributeName = "Scale";
|
|
break;
|
|
case kRotation:
|
|
attributeName = "Rotation";
|
|
break;
|
|
default:
|
|
attributeName = "<unknown>";
|
|
break;
|
|
}
|
|
g_engine->console().debugPrintf("Set inactive camera %s to %f after %dms\n", attributeName, _value, _delay);
|
|
}
|
|
|
|
void syncGame(Serializer &s) override {
|
|
Task::syncGame(s);
|
|
syncEnum(s, _attribute);
|
|
s.syncAsFloatLE(_value);
|
|
s.syncAsSint32LE(_delay);
|
|
}
|
|
|
|
const char *taskName() const override;
|
|
|
|
private:
|
|
Camera &_camera;
|
|
Attribute _attribute = {};
|
|
float _value = 0;
|
|
int32 _delay = 0;
|
|
};
|
|
DECLARE_TASK(CamSetInactiveAttributeTask)
|
|
|
|
Task *Camera::lerpPos(Process &process,
|
|
Vector2d targetPos,
|
|
int32 duration, EasingType easingType) {
|
|
if (!process.isActiveForPlayer()) {
|
|
return new DelayTask(process, duration); // lerpPos does not handle inactive players
|
|
}
|
|
Vector3d targetPos3d(targetPos.getX(), targetPos.getY(), _appliedCenter.z());
|
|
return new CamLerpPosTask(process, targetPos3d, duration, easingType);
|
|
}
|
|
|
|
Task *Camera::lerpPos(Process &process,
|
|
Vector3d targetPos,
|
|
int32 duration, EasingType easingType) {
|
|
if (!process.isActiveForPlayer()) {
|
|
return new DelayTask(process, duration); // lerpPos does not handle inactive players
|
|
}
|
|
setFollow(nullptr); // 3D position lerping is the only task that resets following
|
|
return new CamLerpPosTask(process, targetPos, duration, easingType);
|
|
}
|
|
|
|
Task *Camera::lerpPosZ(Process &process,
|
|
float targetPosZ,
|
|
int32 duration, EasingType easingType) {
|
|
if (!process.isActiveForPlayer()) {
|
|
return new CamSetInactiveAttributeTask(process, CamSetInactiveAttributeTask::kPosZ, targetPosZ, duration);
|
|
}
|
|
Vector3d targetPos(_appliedCenter.x(), _appliedCenter.y(), targetPosZ);
|
|
return new CamLerpPosTask(process, targetPos, duration, easingType);
|
|
}
|
|
|
|
Task *Camera::lerpScale(Process &process,
|
|
float targetScale,
|
|
int32 duration, EasingType easingType) {
|
|
if (!process.isActiveForPlayer()) {
|
|
return new CamSetInactiveAttributeTask(process, CamSetInactiveAttributeTask::kScale, targetScale, duration);
|
|
}
|
|
return new CamLerpScaleTask(process, targetScale, duration, easingType);
|
|
}
|
|
|
|
Task *Camera::lerpRotation(Process &process,
|
|
float targetRotation,
|
|
int32 duration, EasingType easingType) {
|
|
if (!process.isActiveForPlayer()) {
|
|
return new CamSetInactiveAttributeTask(process, CamSetInactiveAttributeTask::kRotation, targetRotation, duration);
|
|
}
|
|
return new CamLerpRotationTask(process, targetRotation, duration, easingType);
|
|
}
|
|
|
|
Task *Camera::lerpPosScale(Process &process,
|
|
Vector3d targetPos, float targetScale,
|
|
int32 duration,
|
|
EasingType moveEasingType, EasingType scaleEasingType) {
|
|
if (!process.isActiveForPlayer()) {
|
|
return new CamSetInactiveAttributeTask(process, CamSetInactiveAttributeTask::kScale, targetScale, duration);
|
|
}
|
|
return new CamLerpPosScaleTask(process, targetPos, targetScale, duration, moveEasingType, scaleEasingType);
|
|
}
|
|
|
|
Task *Camera::waitToStop(Process &process) {
|
|
return new CamWaitToStopTask(process);
|
|
}
|
|
|
|
Task *Camera::shake(Process &process, Math::Vector2d amplitude, Math::Vector2d frequency, int32 duration) {
|
|
if (!process.isActiveForPlayer()) {
|
|
return new DelayTask(process, (uint32)duration);
|
|
}
|
|
return new CamShakeTask(process, amplitude, frequency, duration);
|
|
}
|
|
|
|
} // namespace Alcachofa
|