Skip to content

Commit

Permalink
Support for skippable cutscenes (SuperTux#1513)
Browse files Browse the repository at this point in the history
* Skippable cutscenes support.

* Fixed the wrapper.cpp file.I rebuilt it the proper way (cmake -DGenerate_wrapper=on ..; make) so it fixed a few errors I made

* Removed unnecessary property (added while testing methods for skippable cutscenes)

* Skippable cutscenes now display a message in the top-left corner of the screen.

FIXME: The text is sometimes not displayed when testing a level from the editor.

* Fixed a problem with the cutscene skip message sometimes not showing

* Fixed coding style

* Improved code quality and removed unnecessary comment

Co-authored-by: Semphris <semphris@protonmail.com>
  • Loading branch information
2 people authored and Mathnerd314 committed Sep 13, 2020
1 parent 90f3d08 commit 67d52ec
Show file tree
Hide file tree
Showing 17 changed files with 345 additions and 12 deletions.
48 changes: 48 additions & 0 deletions src/object/cutscene_info.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SuperTux
// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
//
// 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 "object/cutscene_info.hpp"

#include <stdio.h>

#include "object/camera.hpp"
#include "supertux/resources.hpp"
#include "video/drawing_context.hpp"

CutsceneInfo::CutsceneInfo(/*const Vector& pos*/ const Camera& cam, const std::string& text_, const Level& parent) :
position(cam.get_translation() + Vector(32, 32)),
text(text_),
camera(cam),
level(parent)
{
}

void
CutsceneInfo::update(float dt_sec)
{
position = camera.get_translation() + Vector(32, 32);
}

void
CutsceneInfo::draw(DrawingContext& context)
{
if (level.m_is_in_cutscene && !level.m_skip_cutscene)
{
context.color().draw_text(Resources::normal_font, text, position, ALIGN_LEFT, LAYER_OBJECTS + 1000, CutsceneInfo::text_color);
}
}

/* EOF */
47 changes: 47 additions & 0 deletions src/object/cutscene_info.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SuperTux
// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
//
// 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/>.

#ifndef HEADER_SUPERTUX_OBJECT_CUTSCENE_INFO_HPP
#define HEADER_SUPERTUX_OBJECT_CUTSCENE_INFO_HPP

#include "math/vector.hpp"
#include "object/camera.hpp"
#include "supertux/game_object.hpp"
#include "supertux/level.hpp"
#include "video/color.hpp"

class CutsceneInfo final : public GameObject
{
static Color text_color;
public:
CutsceneInfo(/*const Vector& pos*/ const Camera& cam, const std::string& text_, const Level& parent);
virtual bool is_saveable() const override {
return false;
}

virtual void update(float dt_sec) override;
virtual void draw(DrawingContext& context) override;

private:
Vector position;
std::string text;
const Camera& camera;
const Level& level;
};

#endif

/* EOF */
95 changes: 90 additions & 5 deletions src/scripting/functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,104 @@ bool is_christmas()
return g_config->christmas_mode;
}

void start_cutscene()
{
auto session = GameSession::current();
if (session == nullptr)
{
log_info << "No game session" << std::endl;
return;
}

if (session->get_current_level().m_is_in_cutscene)
{
log_warning << "start_cutscene(): starting a new cutscene above another one, ending preceeding cutscene (use end_cutscene() in scripts!)" << std::endl;
}

session->get_current_level().m_is_in_cutscene = true;
session->get_current_level().m_skip_cutscene = false;
}

void end_cutscene()
{
auto session = GameSession::current();
if (session == nullptr)
{
log_info << "No game session" << std::endl;
return;
}

if (!session->get_current_level().m_is_in_cutscene)
{
log_warning << "end_cutscene(): no cutscene to end, resetting status anyways" << std::endl;
}

session->get_current_level().m_is_in_cutscene = false;
session->get_current_level().m_skip_cutscene = false;
}

bool check_cutscene()
{
auto session = GameSession::current();
if (session == nullptr)
{
log_info << "No game session" << std::endl;
return false;
}

return session->get_current_level().m_is_in_cutscene;
}

void wait(HSQUIRRELVM vm, float seconds)
{
if (auto squirrelenv = static_cast<SquirrelEnvironment*>(sq_getforeignptr(vm)))
if(GameSession::current()->get_current_level().m_skip_cutscene)
{
squirrelenv->wait_for_seconds(vm, seconds);
if (auto squirrelenv = static_cast<SquirrelEnvironment*>(sq_getforeignptr(vm)))
{
// wait anyways, to prevent scripts like `while (true) {wait(0.1); ...}`
squirrelenv->wait_for_seconds(vm, 0);
}
else if (auto squirrelvm = static_cast<SquirrelVirtualMachine*>(sq_getsharedforeignptr(vm)))
{
squirrelvm->wait_for_seconds(vm, 0);
}
else
{
log_warning << "wait(): no VM or environment available\n";
}
}
else if (auto squirrelvm = static_cast<SquirrelVirtualMachine*>(sq_getsharedforeignptr(vm)))
else if(GameSession::current()->get_current_level().m_is_in_cutscene)
{
squirrelvm->wait_for_seconds(vm, seconds);
if (auto squirrelenv = static_cast<SquirrelEnvironment*>(sq_getforeignptr(vm)))
{
// wait anyways, to prevent scripts like `while (true) {wait(0.1); ...}` from freezing the game
squirrelenv->skippable_wait_for_seconds(vm, seconds);
//GameSession::current()->set_scheduler(squirrelenv->get_scheduler());
}
else if (auto squirrelvm = static_cast<SquirrelVirtualMachine*>(sq_getsharedforeignptr(vm)))
{
squirrelvm->skippable_wait_for_seconds(vm, seconds);
//GameSession::current()->set_scheduler(squirrelvm->get_scheduler());
}
else
{
log_warning << "wait(): no VM or environment available\n";
}
}
else
{
log_warning << "wait(): no VM or environment available\n";
if (auto squirrelenv = static_cast<SquirrelEnvironment*>(sq_getforeignptr(vm)))
{
squirrelenv->wait_for_seconds(vm, seconds);
}
else if (auto squirrelvm = static_cast<SquirrelVirtualMachine*>(sq_getsharedforeignptr(vm)))
{
squirrelvm->wait_for_seconds(vm, seconds);
}
else
{
log_warning << "wait(): no VM or environment available\n";
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/scripting/functions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ void set_next_worldmap(const std::string& dirname, const std::string& spawnpoint
/** Load and display a level (on next screenswitch) */
void load_level(const std::string& filename);

/** Manages skippable cutscenes (cancels calls to wait()) */
void start_cutscene();
void end_cutscene();
bool check_cutscene();

/** Suspend the script execution for the specified number of seconds */
void wait(HSQUIRRELVM vm, float seconds) __suspend;

Expand Down
78 changes: 78 additions & 0 deletions src/scripting/wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6075,6 +6075,63 @@ static SQInteger load_level_wrapper(HSQUIRRELVM vm)

}

static SQInteger start_cutscene_wrapper(HSQUIRRELVM vm)
{
(void) vm;

try {
scripting::start_cutscene();

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_cutscene'"));
return SQ_ERROR;
}

}

static SQInteger end_cutscene_wrapper(HSQUIRRELVM vm)
{
(void) vm;

try {
scripting::end_cutscene();

return 0;

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

}

static SQInteger check_cutscene_wrapper(HSQUIRRELVM vm)
{

try {
bool return_value = scripting::check_cutscene();

sq_pushbool(vm, return_value);
return 1;

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

}

static SQInteger wait_wrapper(HSQUIRRELVM vm)
{
HSQUIRRELVM arg0 = vm;
Expand Down Expand Up @@ -7715,6 +7772,27 @@ void register_supertux_wrapper(HSQUIRRELVM v)
throw SquirrelError(v, "Couldn't register function 'load_level'");
}

sq_pushstring(v, "start_cutscene", -1);
sq_newclosure(v, &start_cutscene_wrapper, 0);
sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "x|t");
if(SQ_FAILED(sq_createslot(v, -3))) {
throw SquirrelError(v, "Couldn't register function 'start_cutscene'");
}

sq_pushstring(v, "end_cutscene", -1);
sq_newclosure(v, &end_cutscene_wrapper, 0);
sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "x|t");
if(SQ_FAILED(sq_createslot(v, -3))) {
throw SquirrelError(v, "Couldn't register function 'end_cutscene'");
}

sq_pushstring(v, "check_cutscene", -1);
sq_newclosure(v, &check_cutscene_wrapper, 0);
sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "x|t");
if(SQ_FAILED(sq_createslot(v, -3))) {
throw SquirrelError(v, "Couldn't register function 'check_cutscene'");
}

sq_pushstring(v, "wait", -1);
sq_newclosure(v, &wait_wrapper, 0);
sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "x|tn");
Expand Down
8 changes: 7 additions & 1 deletion src/squirrel/squirrel_environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,13 @@ SquirrelEnvironment::run_script(std::istream& in, const std::string& sourcename)
void
SquirrelEnvironment::wait_for_seconds(HSQUIRRELVM vm, float seconds)
{
m_scheduler->schedule_thread(vm, g_game_time + seconds);
m_scheduler->schedule_thread(vm, g_game_time + seconds, false);
}

void
SquirrelEnvironment::skippable_wait_for_seconds(HSQUIRRELVM vm, float seconds)
{
m_scheduler->schedule_thread(vm, g_game_time + seconds, true);
}

void
Expand Down
1 change: 1 addition & 0 deletions src/squirrel/squirrel_environment.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class SquirrelEnvironment

void update(float dt_sec);
void wait_for_seconds(HSQUIRRELVM vm, float seconds);
void skippable_wait_for_seconds(HSQUIRRELVM vm, float seconds);

private:
void garbage_collect();
Expand Down
10 changes: 8 additions & 2 deletions src/squirrel/squirrel_scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include "squirrel/squirrel_virtual_machine.hpp"
#include "squirrel/squirrel_util.hpp"
#include "supertux/level.hpp"
#include "util/log.hpp"

SquirrelScheduler::SquirrelScheduler(SquirrelVM& vm) :
Expand All @@ -31,7 +32,11 @@ SquirrelScheduler::SquirrelScheduler(SquirrelVM& vm) :
void
SquirrelScheduler::update(float time)
{
while (!schedule.empty() && schedule.front().wakeup_time < time) {
while (!schedule.empty() && (schedule.front().wakeup_time < time ||
(schedule.front().skippable &&
Level::current() != nullptr &&
Level::current()->m_skip_cutscene)
)) {
HSQOBJECT thread_ref = schedule.front().thread_ref;

sq_pushobject(m_vm.get_vm(), thread_ref);
Expand Down Expand Up @@ -65,7 +70,7 @@ SquirrelScheduler::update(float time)
}

void
SquirrelScheduler::schedule_thread(HSQUIRRELVM scheduled_vm, float time)
SquirrelScheduler::schedule_thread(HSQUIRRELVM scheduled_vm, float time, bool skippable)
{
// create a weakref to the VM
sq_pushthread(m_vm.get_vm(), scheduled_vm);
Expand All @@ -77,6 +82,7 @@ SquirrelScheduler::schedule_thread(HSQUIRRELVM scheduled_vm, float time)
throw SquirrelError(m_vm.get_vm(), "Couldn't get thread weakref from vm");
}
entry.wakeup_time = time;
entry.skippable = skippable;

sq_addref(m_vm.get_vm(), & entry.thread_ref);
sq_pop(m_vm.get_vm(), 2);
Expand Down
4 changes: 3 additions & 1 deletion src/squirrel/squirrel_scheduler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ class SquirrelScheduler final

/** time must be absolute time, not relative updates, i.e. g_game_time */
void update(float time);
void schedule_thread(HSQUIRRELVM vm, float time);
void schedule_thread(HSQUIRRELVM vm, float time, bool skippable);

private:
struct ScheduleEntry {
/// weak reference to the squirrel vm object
HSQOBJECT thread_ref;
/// time when the thread should be woken up
float wakeup_time;
// true if calling force_wake_up should wake this entry up
bool skippable;

bool operator<(const ScheduleEntry& other) const
{
Expand Down
Loading

0 comments on commit 67d52ec

Please sign in to comment.