diff --git a/src/object/cutscene_info.cpp b/src/object/cutscene_info.cpp new file mode 100644 index 00000000000..cea79b84159 --- /dev/null +++ b/src/object/cutscene_info.cpp @@ -0,0 +1,48 @@ +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// 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 . + +#include "object/cutscene_info.hpp" + +#include + +#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 */ diff --git a/src/object/cutscene_info.hpp b/src/object/cutscene_info.hpp new file mode 100644 index 00000000000..41f986ed615 --- /dev/null +++ b/src/object/cutscene_info.hpp @@ -0,0 +1,47 @@ +// SuperTux +// Copyright (C) 2006 Matthias Braun +// +// 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 . + +#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 */ diff --git a/src/scripting/functions.cpp b/src/scripting/functions.cpp index e850f059423..6563e51af66 100644 --- a/src/scripting/functions.cpp +++ b/src/scripting/functions.cpp @@ -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(sq_getforeignptr(vm))) + if(GameSession::current()->get_current_level().m_skip_cutscene) { - squirrelenv->wait_for_seconds(vm, seconds); + if (auto squirrelenv = static_cast(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(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(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(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(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(sq_getforeignptr(vm))) + { + squirrelenv->wait_for_seconds(vm, seconds); + } + else if (auto squirrelvm = static_cast(sq_getsharedforeignptr(vm))) + { + squirrelvm->wait_for_seconds(vm, seconds); + } + else + { + log_warning << "wait(): no VM or environment available\n"; + } } } diff --git a/src/scripting/functions.hpp b/src/scripting/functions.hpp index 41a72652375..1ec4a0a3a88 100644 --- a/src/scripting/functions.hpp +++ b/src/scripting/functions.hpp @@ -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; diff --git a/src/scripting/wrapper.cpp b/src/scripting/wrapper.cpp index 976fcee8647..d19766c8ea7 100644 --- a/src/scripting/wrapper.cpp +++ b/src/scripting/wrapper.cpp @@ -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; @@ -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"); diff --git a/src/squirrel/squirrel_environment.cpp b/src/squirrel/squirrel_environment.cpp index 4c7a64ebca3..7f2a1f23f02 100644 --- a/src/squirrel/squirrel_environment.cpp +++ b/src/squirrel/squirrel_environment.cpp @@ -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 diff --git a/src/squirrel/squirrel_environment.hpp b/src/squirrel/squirrel_environment.hpp index 925d6b55875..73d90a3ce7b 100644 --- a/src/squirrel/squirrel_environment.hpp +++ b/src/squirrel/squirrel_environment.hpp @@ -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(); diff --git a/src/squirrel/squirrel_scheduler.cpp b/src/squirrel/squirrel_scheduler.cpp index 49ac9256457..5396b2c97de 100644 --- a/src/squirrel/squirrel_scheduler.cpp +++ b/src/squirrel/squirrel_scheduler.cpp @@ -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) : @@ -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); @@ -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); @@ -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); diff --git a/src/squirrel/squirrel_scheduler.hpp b/src/squirrel/squirrel_scheduler.hpp index 38f3da80e4e..548584ac56e 100644 --- a/src/squirrel/squirrel_scheduler.hpp +++ b/src/squirrel/squirrel_scheduler.hpp @@ -32,7 +32,7 @@ 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 { @@ -40,6 +40,8 @@ class SquirrelScheduler final 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 { diff --git a/src/squirrel/squirrel_virtual_machine.cpp b/src/squirrel/squirrel_virtual_machine.cpp index c77e7e7d80e..ba26a5cf2db 100644 --- a/src/squirrel/squirrel_virtual_machine.cpp +++ b/src/squirrel/squirrel_virtual_machine.cpp @@ -150,7 +150,13 @@ SquirrelVirtualMachine::update_debugger() void SquirrelVirtualMachine::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 +SquirrelVirtualMachine::skippable_wait_for_seconds(HSQUIRRELVM vm, float seconds) +{ + m_scheduler->schedule_thread(vm, g_game_time + seconds, true); } void diff --git a/src/squirrel/squirrel_virtual_machine.hpp b/src/squirrel/squirrel_virtual_machine.hpp index 99543483a7b..1ff7a24ccad 100644 --- a/src/squirrel/squirrel_virtual_machine.hpp +++ b/src/squirrel/squirrel_virtual_machine.hpp @@ -36,6 +36,7 @@ class SquirrelVirtualMachine final : public Currenton SquirrelVM& get_vm() { return m_vm; } void wait_for_seconds(HSQUIRRELVM vm, float seconds); + void skippable_wait_for_seconds(HSQUIRRELVM vm, float seconds); void update(float dt_sec); /** adds thread waiting for a screen switch event */ diff --git a/src/supertux/colorscheme.cpp b/src/supertux/colorscheme.cpp index ed46150048b..8c662e5205d 100644 --- a/src/supertux/colorscheme.cpp +++ b/src/supertux/colorscheme.cpp @@ -16,6 +16,7 @@ #include "supertux/colorscheme.hpp" +#include "object/cutscene_info.hpp" #include "editor/overlay_widget.hpp" #include "object/floating_text.hpp" #include "object/level_time.hpp" @@ -48,6 +49,7 @@ Color PlayerStatusHUD::text_color(1.f,1.f,0.6f); Color TextObject::default_color(1.f,1.f,1.f); Color FloatingText::text_color(1.f,1.f,0.6f); +Color CutsceneInfo::text_color(1.f,1.f,0.6f); Color LevelTime::text_color(1.f,1.f,0.6f); diff --git a/src/supertux/game_session.cpp b/src/supertux/game_session.cpp index 8de1b128d43..ff814bb75bc 100644 --- a/src/supertux/game_session.cpp +++ b/src/supertux/game_session.cpp @@ -19,6 +19,7 @@ #include "audio/sound_manager.hpp" #include "control/input_manager.hpp" #include "gui/menu_manager.hpp" +#include "math/vector.hpp" #include "object/camera.hpp" #include "object/endsequence_fireworks.hpp" #include "object/endsequence_walkleft.hpp" @@ -75,7 +76,8 @@ GameSession::GameSession(const std::string& levelfile_, Savegame& savegame, Stat m_max_fire_bullets_at_start(), m_max_ice_bullets_at_start(), m_active(false), - m_end_seq_started(false) + m_end_seq_started(false), + m_current_cutscene_text() { if (restart_level() != 0) throw std::runtime_error ("Initializing the level failed."); @@ -173,6 +175,12 @@ GameSession::on_escape_press() m_currentsector->get_player().m_dying_timer.start(FLT_EPSILON); return; // don't let the player open the menu, when Tux is dying } + + if (m_level->m_is_in_cutscene && !m_level->m_skip_cutscene) + { + m_level->m_skip_cutscene = true; + return; + } if (!m_level->m_suppress_pause_menu) { toggle_pause(); @@ -337,6 +345,30 @@ GameSession::update(float dt_sec, const Controller& controller) MenuManager::instance().set_menu(MenuStorage::DEBUG_MENU); } } + + if (m_level->m_is_in_cutscene && !m_level->m_skip_cutscene && m_current_cutscene_text == nullptr) + { + /*std::string cutscene_text = _("Press escape to skip"); + FloatingText* text = new FloatingText( + // *(new Vector(32, 32)), + m_currentsector->get_camera().get_translation() + *(new Vector(cutscene_text.size() * 8 + 32, 32)), + cutscene_text + ); + + m_currentsector->add_object(std::unique_ptr (text));*/ + + //m_current_cutscene_text = std::unique_ptr (cutscene_text); + + + } + else if ((!m_level->m_is_in_cutscene || m_level->m_skip_cutscene) && m_current_cutscene_text != nullptr) + { + printf("Before\n"); + /*try { + m_current_cutscene_text->remove_me(); + } catch(...) {}*/ + printf("After\n"); + } process_events(); diff --git a/src/supertux/game_session.hpp b/src/supertux/game_session.hpp index 085a1b4cfc3..d89e8236570 100644 --- a/src/supertux/game_session.hpp +++ b/src/supertux/game_session.hpp @@ -22,6 +22,8 @@ #include #include "math/vector.hpp" +#include "squirrel/squirrel_scheduler.hpp" +#include "supertux/game_object.hpp" #include "supertux/game_session_recorder.hpp" #include "supertux/player_status.hpp" #include "supertux/screen.hpp" @@ -88,6 +90,8 @@ class GameSession final : public Screen, void force_ghost_mode(); Savegame& get_savegame() const { return m_savegame; } + + void set_scheduler(SquirrelScheduler& new_scheduler); private: void check_end_conditions(); @@ -149,6 +153,8 @@ class GameSession final : public Screen, bool m_active; /** Game active? **/ bool m_end_seq_started; + + std::unique_ptr m_current_cutscene_text; private: GameSession(const GameSession&) = delete; diff --git a/src/supertux/level.cpp b/src/supertux/level.cpp index 1de07551963..4b72bf8fbb8 100644 --- a/src/supertux/level.cpp +++ b/src/supertux/level.cpp @@ -41,7 +41,9 @@ Level::Level(bool worldmap) : m_stats(), m_target_time(), m_tileset("images/tiles.strf"), - m_suppress_pause_menu() + m_suppress_pause_menu(), + m_is_in_cutscene(false), + m_skip_cutscene(false) { s_current = this; } diff --git a/src/supertux/level.hpp b/src/supertux/level.hpp index bed56a4966d..8cf772bf5ca 100644 --- a/src/supertux/level.hpp +++ b/src/supertux/level.hpp @@ -79,6 +79,8 @@ class Level final float m_target_time; std::string m_tileset; bool m_suppress_pause_menu; + bool m_is_in_cutscene; + bool m_skip_cutscene; private: Level(const Level&) = delete; diff --git a/src/supertux/sector.cpp b/src/supertux/sector.cpp index c6d60dceeba..d022b4962ba 100644 --- a/src/supertux/sector.cpp +++ b/src/supertux/sector.cpp @@ -30,6 +30,7 @@ #include "object/background.hpp" #include "object/bullet.hpp" #include "object/camera.hpp" +#include "object/cutscene_info.hpp" #include "object/display_effect.hpp" #include "object/gradient.hpp" #include "object/music_object.hpp" @@ -158,6 +159,9 @@ Sector::finish_construction(bool editable) flush_game_objects(); + auto cutscene_text = new CutsceneInfo(get_camera(), _("Press escape to skip"), m_level); + add_object(std::unique_ptr (cutscene_text)); + m_fully_constructed = true; }