Skip to content

Commit

Permalink
Add scriptable earthquake effect to Camera (#2612)
Browse files Browse the repository at this point in the history
Implements an earthquake effect to Camera, which vertically shakes the camera at a certain minimal delay, with a given average strength.

This feature can be managed through the start_earthquake and stop_earthquake scripting Camera functions.

Fixes #2611.
  • Loading branch information
Vankata453 authored Aug 28, 2023
1 parent 02711d9 commit b59db06
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 8 deletions.
81 changes: 79 additions & 2 deletions src/object/camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <math.h>
#include <physfs.h>

#include "math/random.hpp"
#include "math/util.hpp"
#include "object/player.hpp"
#include "supertux/level.hpp"
Expand Down Expand Up @@ -146,6 +147,11 @@ Camera::Camera(const std::string& name) :
m_shakespeed(),
m_shakedepth_x(),
m_shakedepth_y(),
m_earthquake(false),
m_earthquake_strength(),
m_earthquake_delay(),
m_earthquake_last_offset(0.f),
m_earthquake_delay_timer(),
m_scroll_from(0.0f, 0.0f),
m_scroll_goal(0.0f, 0.0f),
m_scroll_to_pos(),
Expand Down Expand Up @@ -179,6 +185,11 @@ Camera::Camera(const ReaderMapping& reader) :
m_shakespeed(),
m_shakedepth_x(),
m_shakedepth_y(),
m_earthquake(false),
m_earthquake_strength(),
m_earthquake_delay(),
m_earthquake_last_offset(0.f),
m_earthquake_delay_timer(),
m_scroll_from(0.0f, 0.0f),
m_scroll_goal(0.0f, 0.0f),
m_scroll_to_pos(),
Expand Down Expand Up @@ -303,6 +314,36 @@ Camera::shake(float duration, float x, float y)
m_shakespeed = math::PI_2 / duration;
}

void
Camera::start_earthquake(float strength, float delay)
{
if (strength <= 0.f)
{
log_warning << "Invalid earthquake strength value provided. Setting to 3." << std::endl;
strength = 3.f;
}
if (delay <= 0.f)
{
log_warning << "Invalid earthquake delay value provided. Setting to 0.05." << std::endl;
delay = 0.05f;
}

m_earthquake = true;
m_earthquake_strength = strength;
m_earthquake_delay = delay;
}

void
Camera::stop_earthquake()
{
m_translation.y -= m_earthquake_last_offset;
m_cached_translation.y -= m_earthquake_last_offset;

m_earthquake = false;
m_earthquake_last_offset = 0.f;
m_earthquake_delay_timer.stop();
}

void
Camera::scroll_to(const Vector& goal, float scrolltime)
{
Expand Down Expand Up @@ -360,7 +401,8 @@ Camera::update(float dt_sec)
}

update_scale(dt_sec);
shake();
update_shake();
update_earthquake();
}

void
Expand All @@ -383,18 +425,24 @@ Camera::keep_in_bounds(Vector& translation_)
float width = d_sector->get_width();
float height = d_sector->get_height();

// Remove any earthquake offset from the translation.
translation_.y -= m_earthquake_last_offset;

// Don't scroll before the start or after the level's end.
translation_.x = math::clamp(translation_.x, 0.0f, width - static_cast<float>(m_screen_size.width));
translation_.y = math::clamp(translation_.y, 0.0f, height - static_cast<float>(m_screen_size.height));

// Add any earthquake offset we may have removed earlier.
translation_.y += m_earthquake_last_offset;

if (height < static_cast<float>(m_screen_size.height))
translation_.y = height / 2.0f - static_cast<float>(m_screen_size.height) / 2.0f;
if (width < static_cast<float>(m_screen_size.width))
translation_.x = width / 2.0f - static_cast<float>(m_screen_size.width) / 2.0f;
}

void
Camera::shake()
Camera::update_shake()
{
if (m_shaketimer.started()) {

Expand All @@ -412,6 +460,35 @@ Camera::shake()
}
}

void
Camera::update_earthquake()
{
if (!m_earthquake)
return;

if (m_earthquake_delay_timer.check())
{
if (m_earthquake_last_offset == 0.f)
{
m_earthquake_last_offset = m_earthquake_strength * graphicsRandom.randf(-2, 2);
m_translation.y += m_earthquake_last_offset;
m_cached_translation.y += m_earthquake_last_offset;
}
else
{
m_translation.y -= m_earthquake_last_offset;
m_cached_translation.y -= m_earthquake_last_offset;
m_earthquake_last_offset = 0.f;
}

m_earthquake_delay_timer.start(m_earthquake_delay + static_cast<float>(graphicsRandom.rand(0, 1)));
}
else if (!m_earthquake_delay_timer.started())
{
m_earthquake_delay_timer.start(m_earthquake_delay + static_cast<float>(graphicsRandom.rand(0, 1)));
}
}

void
Camera::update_scroll_normal(float dt_sec)
{
Expand Down
16 changes: 15 additions & 1 deletion src/object/camera.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ class Camera final : public GameObject,
/** shake camera in a direction 1 time */
void shake(float duration, float x, float y);

/** Shake the camera vertically with a specified average strength, at a certain minimal delay, until stopped. */
void start_earthquake(float strength, float delay);
void stop_earthquake();

/** scroll the upper left edge of the camera in scrolltime seconds
to the position goal */
void scroll_to(const Vector& goal, float scrolltime);
Expand Down Expand Up @@ -124,9 +128,12 @@ class Camera final : public GameObject,
void update_scroll_normal_multiplayer(float dt_sec);
void update_scroll_autoscroll(float dt_sec);
void update_scroll_to(float dt_sec);

void update_scale(float dt_sec);
void update_shake();
void update_earthquake();

void keep_in_bounds(Vector& vector);
void shake();

private:
Mode m_mode;
Expand All @@ -149,6 +156,13 @@ class Camera final : public GameObject,
float m_shakedepth_x;
float m_shakedepth_y;

// Earthquake
bool m_earthquake;
float m_earthquake_strength,
m_earthquake_delay,
m_earthquake_last_offset;
Timer m_earthquake_delay_timer;

// scrollto mode
Vector m_scroll_from;
Vector m_scroll_goal;
Expand Down
20 changes: 18 additions & 2 deletions src/scripting/camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,27 @@ Camera::reload_config()
}

void
Camera::shake(float speed, float x, float y)
Camera::shake(float duration, float x, float y)
{
SCRIPT_GUARD_VOID;
BIND_SECTOR(::Sector::get());
object.shake(speed, x, y);
object.shake(duration, x, y);
}

void
Camera::start_earthquake(float strength, float delay)
{
SCRIPT_GUARD_VOID;
BIND_SECTOR(::Sector::get());
object.start_earthquake(strength, delay);
}

void
Camera::stop_earthquake()
{
SCRIPT_GUARD_VOID;
BIND_SECTOR(::Sector::get());
object.stop_earthquake();
}

void
Expand Down
16 changes: 13 additions & 3 deletions src/scripting/camera.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,22 @@ class Camera final
void reload_config();

/**
* Moves camera to the given coordinates in ""time"" seconds, returning quickly to the original position afterwards.
* @param float $speed
* Shakes the camera in a certain direction only 1 time.
* @param float $duration
* @param float $x
* @param float $y
*/
void shake(float speed, float x, float y);
void shake(float duration, float x, float y);
/**
* Starts "earthquake" mode, which shakes the camera vertically with a specified average ""strength"", at a certain minimal ""delay"", until stopped.
* @param float $strength
* @param float $delay
*/
void start_earthquake(float strength, float delay);
/**
* Stops "earthquake" mode.
*/
void stop_earthquake();
/**
* Moves the camera to the specified absolute position. The origin is at the top left.
* @param float $x
Expand Down
74 changes: 74 additions & 0 deletions src/scripting/wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,66 @@ static SQInteger Camera_shake_wrapper(HSQUIRRELVM vm)

}

static SQInteger Camera_start_earthquake_wrapper(HSQUIRRELVM vm)
{
SQUserPointer data;
if(SQ_FAILED(sq_getinstanceup(vm, 1, &data, nullptr, SQTrue)) || !data) {
sq_throwerror(vm, _SC("'start_earthquake' called without instance"));
return SQ_ERROR;
}
scripting::Camera* _this = reinterpret_cast<scripting::Camera*> (data);

SQFloat arg0;
if(SQ_FAILED(sq_getfloat(vm, 2, &arg0))) {
sq_throwerror(vm, _SC("Argument 1 not a float"));
return SQ_ERROR;
}
SQFloat arg1;
if(SQ_FAILED(sq_getfloat(vm, 3, &arg1))) {
sq_throwerror(vm, _SC("Argument 2 not a float"));
return SQ_ERROR;
}

try {
_this->start_earthquake(arg0, arg1);

return 0;

} catch(std::exception& e) {
sq_throwerror(vm, e.what());
return SQ_ERROR;
} catch(...) {
sq_throwerror(vm, _SC("Unexpected exception while executing function 'start_earthquake'"));
return SQ_ERROR;
}

}

static SQInteger Camera_stop_earthquake_wrapper(HSQUIRRELVM vm)
{
SQUserPointer data;
if(SQ_FAILED(sq_getinstanceup(vm, 1, &data, nullptr, SQTrue)) || !data) {
sq_throwerror(vm, _SC("'stop_earthquake' called without instance"));
return SQ_ERROR;
}
scripting::Camera* _this = reinterpret_cast<scripting::Camera*> (data);


try {
_this->stop_earthquake();

return 0;

} catch(std::exception& e) {
sq_throwerror(vm, e.what());
return SQ_ERROR;
} catch(...) {
sq_throwerror(vm, _SC("Unexpected exception while executing function 'stop_earthquake'"));
return SQ_ERROR;
}

}

static SQInteger Camera_set_pos_wrapper(HSQUIRRELVM vm)
{
SQUserPointer data;
Expand Down Expand Up @@ -14123,6 +14183,20 @@ void register_supertux_wrapper(HSQUIRRELVM v)
throw SquirrelError(v, "Couldn't register function 'shake'");
}

sq_pushstring(v, "start_earthquake", -1);
sq_newclosure(v, &Camera_start_earthquake_wrapper, 0);
sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, ".b|nb|n");
if(SQ_FAILED(sq_createslot(v, -3))) {
throw SquirrelError(v, "Couldn't register function 'start_earthquake'");
}

sq_pushstring(v, "stop_earthquake", -1);
sq_newclosure(v, &Camera_stop_earthquake_wrapper, 0);
sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, ".");
if(SQ_FAILED(sq_createslot(v, -3))) {
throw SquirrelError(v, "Couldn't register function 'stop_earthquake'");
}

sq_pushstring(v, "set_pos", -1);
sq_newclosure(v, &Camera_set_pos_wrapper, 0);
sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, ".b|nb|n");
Expand Down

0 comments on commit b59db06

Please sign in to comment.