1// SuperTux
2// Copyright (C) 2015 Hume2 <teratux.mail@gmail.com>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17#include "editor/editor.hpp"
18
19#include <limits>
20#include <physfs.h>
21
22#include "audio/sound_manager.hpp"
23#include "control/input_manager.hpp"
24#include "editor/button_widget.hpp"
25#include "editor/layer_icon.hpp"
26#include "editor/object_info.hpp"
27#include "editor/resize_marker.hpp"
28#include "editor/tile_selection.hpp"
29#include "editor/tip.hpp"
30#include "editor/tool_icon.hpp"
31#include "editor/undo_manager.hpp"
32#include "gui/dialog.hpp"
33#include "gui/menu_manager.hpp"
34#include "gui/mousecursor.hpp"
35#include "gui/mousecursor.hpp"
36#include "math/util.hpp"
37#include "object/camera.hpp"
38#include "object/player.hpp"
39#include "object/spawnpoint.hpp"
40#include "object/tilemap.hpp"
41#include "physfs/util.hpp"
42#include "sprite/sprite_manager.hpp"
43#include "supertux/game_manager.hpp"
44#include "supertux/level.hpp"
45#include "supertux/level_parser.hpp"
46#include "supertux/menu/menu_storage.hpp"
47#include "supertux/savegame.hpp"
48#include "supertux/screen_fade.hpp"
49#include "supertux/screen_manager.hpp"
50#include "supertux/sector.hpp"
51#include "supertux/tile.hpp"
52#include "supertux/tile_manager.hpp"
53#include "supertux/world.hpp"
54#include "util/file_system.hpp"
55#include "util/reader_mapping.hpp"
56#include "util/string_util.hpp"
57#include "video/compositor.hpp"
58#include "video/drawing_context.hpp"
59#include "video/surface.hpp"
60#include "video/video_system.hpp"
61#include "video/viewport.hpp"
62
63bool Editor::s_resaving_in_progress = false;
64
65bool
66Editor::is_active()
67{
68 if (s_resaving_in_progress) {
69 return true;
70 } else {
71 auto* self = Editor::current();
72 return self && !self->m_leveltested;
73 }
74}
75
76Editor::Editor() :
77 m_level(),
78 m_world(),
79 m_levelfile(),
80 m_test_levelfile(),
81 m_quit_request(false),
82 m_newlevel_request(false),
83 m_reload_request(false),
84 m_reactivate_request(false),
85 m_deactivate_request(false),
86 m_save_request(false),
87 m_test_request(false),
88 m_savegame(),
89 m_sector(),
90 m_levelloaded(false),
91 m_leveltested(false),
92 m_tileset(nullptr),
93 m_widgets(),
94 m_overlay_widget(),
95 m_toolbox_widget(),
96 m_layers_widget(),
97 m_enabled(false),
98 m_bgr_surface(Surface::from_file("images/background/arctis2.png")),
99 m_undo_manager(new UndoManager),
100 m_ignore_sector_change(false)
101{
102 auto toolbox_widget = std::make_unique<EditorToolboxWidget>(*this);
103 auto layers_widget = std::make_unique<EditorLayersWidget>(*this);
104 auto overlay_widget = std::make_unique<EditorOverlayWidget>(*this);
105
106 m_toolbox_widget = toolbox_widget.get();
107 m_layers_widget = layers_widget.get();
108 m_overlay_widget = overlay_widget.get();
109
110 auto undo_button_widget = std::make_unique<ButtonWidget>(
111 SpriteManager::current()->create("images/engine/editor/undo.png"),
112 Vector(10, 10), [this]{ undo(); });
113 auto redo_button_widget = std::make_unique<ButtonWidget>(
114 SpriteManager::current()->create("images/engine/editor/redo.png"),
115 Vector(60, 10), [this]{ redo(); });
116
117 m_widgets.push_back(std::move(undo_button_widget));
118 m_widgets.push_back(std::move(redo_button_widget));
119 m_widgets.push_back(std::move(toolbox_widget));
120 m_widgets.push_back(std::move(layers_widget));
121 m_widgets.push_back(std::move(overlay_widget));
122}
123
124Editor::~Editor()
125{
126}
127
128void
129Editor::draw(Compositor& compositor)
130{
131 auto& context = compositor.make_context();
132
133 if (m_levelloaded) {
134 m_sector->draw(context);
135 context.color().draw_filled_rect(Rectf(Vector(0, 0), Vector(static_cast<float>(context.get_width()),
136 static_cast<float>(context.get_height()))),
137 Color(0.0f, 0.0f, 0.0f),
138 0.0f, std::numeric_limits<int>::min());
139 } else {
140 context.color().draw_surface_scaled(m_bgr_surface,
141 Rectf(Vector(0, 0), Vector(static_cast<float>(context.get_width()),
142 static_cast<float>(context.get_height()))),
143 -100);
144 }
145
146 for(const auto& widget : m_widgets) {
147 widget->draw(context);
148 }
149
150 MouseCursor::current()->draw(context);
151}
152
153void
154Editor::update(float dt_sec, const Controller& controller)
155{
156 // Pass all requests
157 if (m_reload_request) {
158 reload_level();
159 }
160
161 if (m_quit_request) {
162 quit_editor();
163 }
164
165 if (m_newlevel_request) {
166 //Create new level
167 }
168
169 if (m_reactivate_request) {
170 m_enabled = true;
171 m_reactivate_request = false;
172 }
173
174 if (m_save_request) {
175 save_level();
176 m_enabled = true;
177 m_save_request = false;
178 }
179
180 if (m_test_request) {
181 m_test_request = false;
182 MouseCursor::current()->set_icon(nullptr);
183 test_level();
184 return;
185 }
186
187 if (m_deactivate_request) {
188 m_enabled = false;
189 m_deactivate_request = false;
190 return;
191 }
192
193 // update other stuff
194 if (m_levelloaded && !m_leveltested) {
195 BIND_SECTOR(*m_sector);
196
197 for (auto& object : m_sector->get_objects()) {
198 object->editor_update();
199 }
200
201 m_sector->flush_game_objects();
202
203 for (const auto& widget : m_widgets) {
204 widget->update(dt_sec);
205 }
206
207 update_keyboard(controller);
208 }
209}
210
211void
212Editor::save_level()
213{
214 m_undo_manager->reset_index();
215 m_level->save(m_world ? FileSystem::join(m_world->get_basedir(), m_levelfile) :
216 m_levelfile);
217}
218
219std::string
220Editor::get_level_directory() const
221{
222 std::string basedir;
223 if (m_world != nullptr)
224 {
225 basedir = m_world->get_basedir();
226 if (basedir == "./")
227 {
228 basedir = PHYSFS_getRealDir(m_levelfile.c_str());
229 }
230 }
231 else
232 {
233 basedir = FileSystem::dirname(m_levelfile);
234 }
235 return std::string(basedir);
236}
237
238void
239Editor::test_level()
240{
241 Tile::draw_editor_images = false;
242 Compositor::s_render_lighting = true;
243 std::string backup_filename = m_levelfile + "~";
244 std::string directory = get_level_directory();
245
246 // This is jank to get an owned World pointer, GameManager/World
247 // could probably need a refactor to handle this better.
248 std::unique_ptr<World> owned_world;
249 World* current_world = m_world.get();
250 if (!current_world) {
251 owned_world = World::from_directory(directory);
252 current_world = owned_world.get();
253 }
254
255 m_test_levelfile = FileSystem::join(directory, backup_filename);
256 m_level->save(m_test_levelfile);
257 if (!m_level->is_worldmap())
258 {
259 GameManager::current()->start_level(*current_world, backup_filename);
260 }
261 else
262 {
263 GameManager::current()->start_worldmap(*current_world, "", m_test_levelfile);
264 }
265
266 m_leveltested = true;
267}
268
269void
270Editor::open_level_directory()
271{
272 m_level->save(FileSystem::join(get_level_directory(), m_levelfile));
273 auto path = FileSystem::join(PHYSFS_getWriteDir(), get_level_directory());
274 FileSystem::open_path(path);
275}
276
277void
278Editor::set_world(std::unique_ptr<World> w)
279{
280 m_world = std::move(w);
281}
282
283int
284Editor::get_tileselect_select_mode() const
285{
286 return m_toolbox_widget->get_tileselect_select_mode();
287}
288
289int
290Editor::get_tileselect_move_mode() const
291{
292 return m_toolbox_widget->get_tileselect_move_mode();
293}
294
295void
296Editor::scroll(const Vector& velocity)
297{
298 if (!m_levelloaded) return;
299
300 Rectf bounds(0.0f,
301 0.0f,
302 std::max(0.0f, m_sector->get_width() - static_cast<float>(SCREEN_WIDTH - 128)),
303 std::max(0.0f, m_sector->get_height() - static_cast<float>(SCREEN_HEIGHT - 32)));
304 Camera& camera = m_sector->get_camera();
305 Vector pos = camera.get_translation() + velocity;
306 pos = Vector(math::clamp(pos.x, bounds.get_left(), bounds.get_right()),
307 math::clamp(pos.y, bounds.get_top(), bounds.get_bottom()));
308 camera.set_translation(pos);
309
310 m_overlay_widget->update_pos();
311}
312
313void
314Editor::esc_press()
315{
316 m_enabled = false;
317 m_overlay_widget->delete_markers();
318 MenuManager::instance().set_menu(MenuStorage::EDITOR_MENU);
319}
320
321void
322Editor::update_keyboard(const Controller& controller)
323{
324 if (!m_enabled){
325 return;
326 }
327
328 if (controller.pressed(Control::ESCAPE)) {
329 esc_press();
330 return;
331 }
332
333 if (controller.hold(Control::LEFT)) {
334 scroll({-32.0f, 0.0f});
335 }
336
337 if (controller.hold(Control::RIGHT)) {
338 scroll({32.0f, 0.0f});
339 }
340
341 if (controller.hold(Control::UP)) {
342 scroll({0.0f, -32.0f});
343 }
344
345 if (controller.hold(Control::DOWN)) {
346 scroll({0.0f, 32.0f});
347 }
348}
349
350void
351Editor::load_sector(const std::string& name)
352{
353 Sector* sector = m_level->get_sector(name);
354 if (!sector) {
355 sector = m_level->get_sector(0);
356 }
357 set_sector(sector);
358}
359
360void
361Editor::set_sector(Sector* sector)
362{
363 if (!sector) return;
364
365 m_sector = sector;
366 m_sector->activate("main");
367
368 { // initialize badguy sprites and other GameObject stuff
369 BIND_SECTOR(*m_sector);
370 for(auto& object : m_sector->get_objects()) {
371 object->after_editor_set();
372 }
373 }
374
375 m_layers_widget->refresh();
376}
377
378void
379Editor::delete_current_sector()
380{
381 if (m_level->m_sectors.size() <= 1) {
382 log_fatal << "deleting the last sector is not allowed" << std::endl;
383 }
384
385 for (auto i = m_level->m_sectors.begin(); i != m_level->m_sectors.end(); ++i) {
386 if ( i->get() == get_sector() ) {
387 m_level->m_sectors.erase(i);
388 break;
389 }
390 }
391
392 set_sector(m_level->m_sectors.front().get());
393 m_reactivate_request = true;
394}
395
396void
397Editor::set_level(std::unique_ptr<Level> level, bool reset)
398{
399 m_undo_manager->reset_index();
400 std::string sector_name = "main";
401 Vector translation;
402
403 if (!reset && m_sector) {
404 translation = m_sector->get_camera().get_translation();
405 sector_name = m_sector->get_name();
406 }
407
408 m_reload_request = false;
409 m_enabled = true;
410
411 if (reset) {
412 m_toolbox_widget->set_input_type(EditorToolboxWidget::InputType::NONE);
413 }
414
415 // Re/load level
416 m_level = nullptr;
417 m_levelloaded = true;
418
419 m_level = std::move(level);
420
421 if (reset) {
422 m_tileset = TileManager::current()->get_tileset(m_level->get_tileset());
423 }
424
425 load_sector(sector_name);
426
427 if (m_sector != nullptr)
428 {
429 m_sector->activate(sector_name);
430 m_sector->get_camera().set_mode(Camera::Mode::MANUAL);
431
432 if (!reset) {
433 m_sector->get_camera().set_translation(translation);
434 }
435 }
436
437 m_layers_widget->refresh_sector_text();
438 m_toolbox_widget->update_mouse_icon();
439 m_overlay_widget->on_level_change();
440}
441
442void
443Editor::reload_level()
444{
445 ReaderMapping::s_translations_enabled = false;
446 set_level(LevelParser::from_file(m_world ?
447 FileSystem::join(m_world->get_basedir(), m_levelfile) : m_levelfile,
448 StringUtil::has_suffix(m_levelfile, ".stwm"),
449 true));
450 ReaderMapping::s_translations_enabled = true;
451}
452
453void
454Editor::quit_editor()
455{
456 m_quit_request = false;
457
458 auto quit = [this] ()
459 {
460 //Quit level editor
461 m_world = nullptr;
462 m_levelfile = "";
463 m_levelloaded = false;
464 m_enabled = false;
465 Tile::draw_editor_images = false;
466 ScreenManager::current()->pop_screen();
467 };
468
469 check_unsaved_changes([quit] {
470 quit();
471 });
472}
473
474void
475Editor::check_unsaved_changes(const std::function<void ()>& action)
476{
477 if (m_undo_manager->has_unsaved_changes() && m_levelloaded)
478 {
479 m_enabled = false;
480 auto dialog = std::make_unique<Dialog>();
481 dialog->set_text(_("This level contains unsaved changes, do you want to save?"));
482 dialog->add_default_button(_("Yes"), [this, action] {
483 check_save_prerequisites([this, action] {
484 save_level();
485 action();
486 m_enabled = true;
487 });
488 });
489 dialog->add_button(_("No"), [this, action] {
490 action();
491 m_enabled = true;
492 });
493 dialog->add_button(_("Cancel"), [this] {
494 m_enabled = true;
495 });
496 MenuManager::instance().set_dialog(std::move(dialog));
497 }
498 else
499 {
500 action();
501 }
502}
503
504void
505Editor::leave()
506{
507 MouseCursor::current()->set_icon(nullptr);
508 Compositor::s_render_lighting = true;
509}
510
511void
512Editor::setup()
513{
514 Tile::draw_editor_images = true;
515 Sector::s_draw_solids_only = false;
516 if (!m_levelloaded) {
517
518#if 0
519 if (AddonManager::current()->is_old_addon_enabled()) {
520 auto dialog = std::make_unique<Dialog>();
521 dialog->set_text(_("Some obsolete add-ons are still active\nand might cause collisions with default Super Tux structure.\nYou can still enable these add-ons in the menu.\nDisabling these add-ons will not delete your game progress."));
522 dialog->clear_buttons();
523
524 dialog->add_default_button(_("Disable add-ons"), [] {
525 AddonManager::current()->disable_old_addons();
526 MenuManager::instance().push_menu(MenuStorage::EDITOR_LEVELSET_SELECT_MENU);
527 });
528
529 dialog->add_button(_("Ignore (not advised)"), [] {
530 MenuManager::instance().push_menu(MenuStorage::EDITOR_LEVELSET_SELECT_MENU);
531 });
532
533 dialog->add_button(_("Leave editor"), [this] {
534 quit_request = true;
535 });
536
537 MenuManager::instance().set_dialog(std::move(dialog));
538 } else
539#endif
540 {
541 MenuManager::instance().push_menu(MenuStorage::EDITOR_LEVELSET_SELECT_MENU);
542 }
543 }
544 m_toolbox_widget->setup();
545 m_layers_widget->setup();
546 m_savegame = Savegame::from_file("levels/misc");
547
548 // Reactivate the editor after level test
549 if (m_leveltested) {
550 if (!m_test_levelfile.empty())
551 {
552 // Try to remove the test level using the PhysFS file system
553 if (physfsutil::remove(m_test_levelfile) != 0)
554 {
555 // This file is not inside any PhysFS mounts,
556 // try to remove this using normal file system
557 // methods.
558 FileSystem::remove(m_test_levelfile);
559 }
560 }
561 m_leveltested = false;
562 Tile::draw_editor_images = true;
563 m_level->reactivate();
564 m_sector->activate(m_sector->get_player().get_pos());
565 MenuManager::instance().clear_menu_stack();
566 SoundManager::current()->stop_music();
567 m_deactivate_request = false;
568 m_enabled = true;
569 m_toolbox_widget->update_mouse_icon();
570 }
571}
572
573void
574Editor::resize()
575{
576 // Calls on window resize.
577 m_toolbox_widget->resize();
578 m_layers_widget->resize();
579 m_overlay_widget->update_pos();
580}
581
582void
583Editor::event(const SDL_Event& ev)
584{
585 if (!m_enabled) return;
586
587 try
588 {
589 if (ev.type == SDL_KEYDOWN &&
590 ev.key.keysym.sym == SDLK_t &&
591 ev.key.keysym.mod & KMOD_CTRL) {
592 test_level();
593 }
594
595 if (ev.type == SDL_KEYDOWN &&
596 ev.key.keysym.sym == SDLK_s &&
597 ev.key.keysym.mod & KMOD_CTRL) {
598 save_level();
599 }
600
601 if (ev.type == SDL_KEYDOWN &&
602 ev.key.keysym.sym == SDLK_z &&
603 ev.key.keysym.mod & KMOD_CTRL) {
604 undo();
605 }
606
607 if (ev.type == SDL_KEYDOWN &&
608 ev.key.keysym.sym == SDLK_y &&
609 ev.key.keysym.mod & KMOD_CTRL) {
610 redo();
611 }
612
613 if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_F6) {
614 Compositor::s_render_lighting = !Compositor::s_render_lighting;
615 return;
616 }
617
618 m_ignore_sector_change = false;
619
620 BIND_SECTOR(*m_sector);
621
622 for(const auto& widget : m_widgets) {
623 if (widget->event(ev))
624 break;
625 }
626
627 // unreliable heuristic to snapshot the current state for future undo
628 if (((ev.type == SDL_KEYUP && ev.key.repeat == 0 &&
629 ev.key.keysym.sym != SDLK_LSHIFT &&
630 ev.key.keysym.sym != SDLK_RSHIFT &&
631 ev.key.keysym.sym != SDLK_LCTRL &&
632 ev.key.keysym.sym != SDLK_RCTRL) ||
633 ev.type == SDL_MOUSEBUTTONUP))
634 {
635 if (!m_ignore_sector_change) {
636 if (m_level) {
637 m_undo_manager->try_snapshot(*m_level);
638 }
639 }
640 }
641
642 // scroll with mouse wheel
643 if (ev.type == SDL_MOUSEWHEEL) {
644 float scroll_x = static_cast<float>(ev.wheel.x * -32);
645 float scroll_y = static_cast<float>(ev.wheel.y * -32);
646 scroll({scroll_x, scroll_y});
647 }
648 }
649 catch(const std::exception& err)
650 {
651 log_warning << "error while processing Editor::event(): " << err.what() << std::endl;
652 }
653}
654
655void
656Editor::update_node_iterators()
657{
658 m_overlay_widget->update_node_iterators();
659}
660
661void
662Editor::delete_markers()
663{
664 m_overlay_widget->delete_markers();
665}
666
667void
668Editor::sort_layers()
669{
670 m_layers_widget->sort_layers();
671}
672
673void
674Editor::select_tilegroup(int id)
675{
676 m_toolbox_widget->select_tilegroup(id);
677}
678
679const std::vector<Tilegroup>&
680Editor::get_tilegroups() const
681{
682 return m_tileset->get_tilegroups();
683}
684
685void
686Editor::change_tileset()
687{
688 m_tileset = TileManager::current()->get_tileset(m_level->get_tileset());
689 m_toolbox_widget->set_input_type(EditorToolboxWidget::InputType::NONE);
690 for (const auto& sector : m_level->m_sectors) {
691 for (auto& tilemap : sector->get_objects_by_type<TileMap>()) {
692 tilemap.set_tileset(m_tileset);
693 }
694 }
695}
696
697void
698Editor::select_objectgroup(int id)
699{
700 m_toolbox_widget->select_objectgroup(id);
701}
702
703const std::vector<ObjectGroup>&
704Editor::get_objectgroups() const
705{
706 return m_toolbox_widget->get_object_info().m_groups;
707}
708
709void
710Editor::check_save_prerequisites(const std::function<void ()>& callback) const
711{
712 if (m_level->is_worldmap())
713 {
714 callback();
715 return;
716 }
717
718 bool sector_valid = false, spawnpoint_valid = false;
719 for (const auto& sector : m_level->m_sectors)
720 {
721 if (sector->get_name() == "main")
722 {
723 sector_valid = true;
724 for (const auto& spawnpoint : sector->get_objects_by_type<SpawnPointMarker>())
725 {
726 if (spawnpoint.get_name() == "main")
727 {
728 spawnpoint_valid = true;
729 }
730 }
731 }
732 }
733
734 if(sector_valid && spawnpoint_valid)
735 {
736 callback();
737 return;
738 }
739 else
740 {
741 if (!sector_valid)
742 {
743 Dialog::show_message(_("Couldn't find a \"main\" sector.\nPlease change the name of the sector where\nyou'd like the player to start to \"main\""));
744 }
745 else if (!spawnpoint_valid)
746 {
747 Dialog::show_message(_("Couldn't find a \"main\" spawnpoint.\n Please change the name of the spawnpoint where\nyou'd like the player to start to \"main\""));
748 }
749 }
750
751}
752
753void
754Editor::undo()
755{
756 log_info << "attempting undo" << std::endl;
757 auto level = m_undo_manager->undo();
758 if (level) {
759 set_level(std::move(level), false);
760 m_ignore_sector_change = true;
761 } else {
762 log_info << "undo failed" << std::endl;
763 }
764}
765
766void
767Editor::redo()
768{
769 log_info << "attempting redo" << std::endl;
770 auto level = m_undo_manager->redo();
771 if (level) {
772 set_level(std::move(level), false);
773 m_ignore_sector_change = true;
774 } else {
775 log_info << "redo failed" << std::endl;
776 }
777}
778
779/* EOF */
780