Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
513 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/data/gfx/gui.png b/data/gfx/gui.png
index ebfaa49..d181a24 100644
Binary files a/data/gfx/gui.png and b/data/gfx/gui.png differ
diff --git a/data/levelpacks/default/locale/ru.po b/data/levelpacks/default/locale/ru.po
index a3004c2..08c8e00 100644
--- a/data/levelpacks/default/locale/ru.po
+++ b/data/levelpacks/default/locale/ru.po
@@ -1,105 +1,106 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
-#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-04-01 17:56+0300\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Jz Pan <acme.pjz@gmail.com>, 2018\n"
-"Language-Team: Russian (https://www.transifex.com/acmepjz/teams/88148/ru/)\n"
+"PO-Revision-Date: 2018-09-24 04:13+0000\n"
+"Last-Translator: mesnevi <shams@airpost.net>\n"
+"Language-Team: Russian <https://hosted.weblate.org/projects/me-and-my-shadow/"
+"levels-default/ru/>\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
-"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
-"%100>=11 && n%100<=14)? 2 : 3);\n"
+"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
+"(n%100>=11 && n%100<=14)? 2 : 3);\n"
+"X-Generator: Weblate 3.2-dev\n"
#: default\Regroup.map:1
msgid "Regroup"
msgstr "Перегруппировка"
#: default\3.map:1
msgid "Running in the Sky"
msgstr "Пробегая по небу"
#: default\4.map:1
msgid "Both Up and Down"
msgstr "Как вверх, так и вниз"
#: default\map02.map:1
msgid "Snail"
msgstr "Улитка"
#: default\Towers.map:1
msgid "Towers"
msgstr "Башни"
#: default\map01.map:1
msgid "Simple"
msgstr "Простой"
#: default\map04.map:1
msgid "Double trouble"
msgstr "Двойные проблемы"
#: default\levels.lst:1
msgid "default"
msgstr ""
#: default\Skyscrapers.map:1
msgid "Skyscrapers"
msgstr "Небоскрёбы"
#: default\QuickSwap.map:1
msgid "Quick swap"
-msgstr "Быстрая замена"
+msgstr "Мигом"
#: default\5.map:1
msgid "stopping the spikes"
msgstr "Останавливая шипы"
#: default\levels.lst:2
msgid "Default"
msgstr "По умолчанию"
#: default\Remote.map:1
msgid "Remote control"
msgstr "Удалённый контроль"
#: default\map03.map:1
msgid "Spiky travel"
msgstr "Шипастое путешествие"
#: default\2.map:1
msgid "Tricky Jumping"
msgstr "Хитрое прыгание"
#: default\1.map:1
msgid "Building Teamwork"
msgstr "Налаживая командную работу"
#: default\Timing.map:1
msgid "Timing"
msgstr "На время"
#: default\Volcano.map:1
msgid "Volcano"
msgstr "Вулкан"
#: default\Sweeper.map:1
msgid "Sweeper"
msgstr "Чистильщик"
#: default\Switches.map:1
msgid "Switches"
msgstr "Переключатели"
#: default\map05.map:1
msgid "Wall breaking"
msgstr "Ломая стены"
diff --git a/data/levelpacks/tutorial/locale/ru.po b/data/levelpacks/tutorial/locale/ru.po
index 2175940..8efd4b0 100644
--- a/data/levelpacks/tutorial/locale/ru.po
+++ b/data/levelpacks/tutorial/locale/ru.po
@@ -1,477 +1,478 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
-#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-04-01 17:56+0300\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Jz Pan <acme.pjz@gmail.com>, 2018\n"
-"Language-Team: Russian (https://www.transifex.com/acmepjz/teams/88148/ru/)\n"
+"PO-Revision-Date: 2018-09-24 04:13+0000\n"
+"Last-Translator: mesnevi <shams@airpost.net>\n"
+"Language-Team: Russian <https://hosted.weblate.org/projects/me-and-my-shadow/"
+"levels-tut/ru/>\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
-"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
-"%100>=11 && n%100<=14)? 2 : 3);\n"
+"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
+"(n%100>=11 && n%100<=14)? 2 : 3);\n"
+"X-Generator: Weblate 3.2-dev\n"
#: tutorial\tut12.map:17
msgid "Now you'll need to help your shadow cross.\n"
-msgstr "Теперь тебе надо помочь твоей тени перебраться на ту сторону."
+msgstr "Теперь надо помочь твоей тени перебраться на ту сторону.\n"
#: tutorial\tut10.map:19
msgid ""
"You can save your progress in a level with\n"
"checkpoints. You can restore them at any\n"
"time using the F3 button."
msgstr ""
"Ты можешь сохранять свой прогресс на уровне с помощью\n"
"контрольных точек. Чтобы начинать с того же места,\n"
"в любое время можно нажать клавишу F3."
#: tutorial\tut18.map:14
msgid ""
"This trigger will deactivate the moving block.\n"
"Try to stop it at the right moment.\n"
"You can only do this once, so if it fails you'll\n"
"have to reset the level with the 'R' key."
msgstr ""
"Этот триггер деактивирует движущийся блок.\n"
"Попытайся остановить его в подходящий момент.\n"
"Ты можешь сделать это только один раз, поэтому если у тебя\n"
"не получится, придется перезапустить уровень с помощью клавиши 'R'."
#: tutorial\tut21.map:49
msgid ""
"If your shadow falls down here you will have to restart.\n"
"Press 'R' to restart the level."
msgstr ""
"Если тень упадёт, придётся перезапустить игру.\n"
"Чтобы начать снова, нажми клавишу 'R'."
#: tutorial\levels.lst:1
msgid "tutorial"
-msgstr ""
+msgstr "обучение"
#: tutorial\tut23.map:1
msgid "Collecting Keys"
msgstr "Ключи"
#: tutorial\tut10.map:46
msgid "You've chosen the right way."
msgstr "Ты выбрал правильный путь."
#: tutorial\tut22.map:1
msgid "Shadow swap"
msgstr "Перестановки тени"
#: tutorial\tut20.map:187
msgid ""
"A dead end, you'd better go back and choose\n"
"the other portal."
msgstr ""
"Тупик, тебе лучше вернуться назад и выбрать\n"
"другой портал."
#: tutorial\tut03.map:13
msgid ""
"See those gaps over there?\n"
"Reach the finish without falling."
msgstr ""
"Видишь эти пропасти впереди?\n"
"Доберись до финиша, не упав вниз."
#: tutorial\tut07.map:33
msgid ""
"If your shadow dies you'll have to restart.\n"
"Restart the game by pressing the 'R' key."
msgstr ""
"Если тень умрет, тебе придется перезапустить уровень.\n"
"Чтобы начать снова, нажми клавишу 'R'."
#: tutorial\tut02.map:18
msgid ""
"You can jump using the up key.\n"
"Try jumping over these blocks."
msgstr ""
"Ты можешь прыгать, используя клавишу 'вверх'.\n"
"Попробуй перепрыгнуть через эти блоки."
#: tutorial\tut07.map:1
msgid "Shadow challenge"
msgstr "Испытание для тени"
#: tutorial\tut20.map:1
msgid "Portal mayhem"
msgstr "Хаос порталов"
#: tutorial\tut02.map:1
msgid "First jumps"
msgstr "Первые прыжки"
#: tutorial\tut20.map:16
msgid ""
"Portals point to another portal or to nothing.\n"
"Try to reach the exit in this portal mayhem."
msgstr ""
"Порталы указывают на другой портал или же никуда.\n"
"Постарайся добраться до выхода через этот хаос порталов."
#: tutorial\tut05.map:32
msgid ""
"TIP:\n"
"Think what moves your shadow has to make.\n"
"Then let your character record those moves.\n"
"You can break it down into smaller recordings."
msgstr ""
"Подсказка:\n"
"Подумай, какие движения необходимо сделать тени.\n"
"Затем запиши эти движения.\n"
"Ты можешь разбить последовательность движений на несколько записей."
#: tutorial\tut11.map:17
msgid ""
"Until now the levels were static.\n"
"There are however moving blocks."
msgstr ""
"До сих пор все уровни были статическими.\n"
"Как бы то ни было, существуют движущиеся блоки."
#: tutorial\tut10.map:32
msgid "Save your progress here."
msgstr "Сохрани свой прогресс здесь."
#: tutorial\tut13.map:1
msgid "Moving spikes"
msgstr "Движущиеся шипы"
#: tutorial\tut13.map:18
msgid "Watch out for the moving spikes."
msgstr "Остерегайся движущихся шипов."
#: tutorial\tut16.map:1
msgid "The switch"
msgstr "Переключатель"
#: tutorial\tut25.map:13
msgid "The very best of luck!"
msgstr "Удачи!"
#: tutorial\tut20.map:156
msgid "Now choose one of the two."
msgstr "Выбери один из двух."
#: tutorial\tut19.map:42
msgid ""
"NOTE:\n"
"You can go back by entering this portal.\n"
"It is however a bit different, you don't have to\n"
"press the down key, it will activate when you walk in it."
msgstr ""
"Заметка:\n"
"Ты можешь вернуться обратно, зайдя в портал.\n"
"Однако он немного другой, тебе не надо нажимать клавишу,\n"
"он сам активируется, когда ты встаешь на него."
#: tutorial\tut15.map:20
msgid ""
"This gap is impossible to jump over.\n"
"Step on the button next of you."
msgstr ""
"Через эту пропасть невозможно перепрыгнуть.\n"
"Встань на кнопку перед тобой."
#: tutorial\tut06.map:1
msgid "Shadow walk"
msgstr "Прогулка тени"
#: tutorial\tut10.map:54
msgid ""
"This is the wrong way.\n"
"Go back to the previous checkpoint by pressing F3."
msgstr ""
"Это неправильный путь.\n"
"Вернись к предыдущей контрольной точке, нажав клавишу F3."
#: tutorial\tut25.map:318
msgid "You have made it!"
msgstr "Ты сделал это!"
#: tutorial\tut17.map:1
msgid "Toggle trigger"
msgstr "Переключающий триггер"
#: tutorial\tut24.map:1
msgid "Warning"
msgstr "Предупреждение"
#: tutorial\tut05.map:1
msgid "Shadow"
msgstr "Тень"
#: tutorial\tut06.map:23
msgid ""
"NOTE:\n"
"Although you can't jump on those blocks\n"
"you can record the jumps for your shadow."
msgstr ""
"Заметка:\n"
"Хоть ты и не можешь запрыгнуть на эти блоки,\n"
"ты можешь записать прыжки для своей тени."
#: tutorial\tut19.map:39
msgid ""
"Now it's time to check out the portals.\n"
"To get to the exit you'll have to take the portal.\n"
"Walk to it and press the down arrow to\n"
"activate."
msgstr ""
"Теперь время посмотреть на порталы.\n"
"Чтобы добраться до выхода, тебе надо воспользоваться порталом.\n"
"Подойди к нему и нажми клавишу 'вниз',\n"
"чтобы активировать его."
#: tutorial\tut18.map:1
msgid "Stop trigger"
msgstr "Останавливающий триггер"
#: tutorial\tut01.map:1
msgid "Walk in the park"
msgstr "Прогулка по парку"
#: tutorial\tut09.map:14
msgid ""
"Those blocks are fragile.\n"
"If you step on them too often they'll break."
msgstr ""
"Эти блоки хрупкие.\n"
"Если ты будешь часто на них вставать, они разрушатся."
#: tutorial\tut19.map:1
msgid "First portals"
msgstr "Первые порталы"
#: tutorial\tut25.map:1
msgid "Final"
msgstr "Финал"
#: tutorial\tut04.map:1
msgid "First spikes"
msgstr "Первые шипы"
#: tutorial\tut12.map:1
msgid "Moving shadow blocks"
msgstr "Движущиеся блоки для тени"
#: tutorial\tut22.map:22
msgid ""
"You need your shadow to reach the exit.\n"
"Make use of the swapper to get him down (or \n"
"to get yourself down)."
msgstr ""
"Нужно, чтобы тень добралась до выхода.\n"
"Используй перестановки, чтобы спустить его вниз\n"
"(или спустить вниз себя самого)."
#: tutorial\tut21.map:8
msgid ""
"Now it's time for something completely\n"
"different: swappoints. When you or your\n"
"shadow activate them you'll swap places."
msgstr ""
"А теперь время для чего-то совершенно\n"
"нового: точки перестановки. Когда ты или твоя\n"
"тень активируют их, вы меняетесь местами."
#: tutorial\tut06.map:27
msgid ""
"Only your shadow can stand on those shadow\n"
"blocks. Try to get him up there."
msgstr ""
"Только твоя тень может стоять на этих блоках.\n"
"Попытайся взобраться тенью на них."
#: tutorial\tut17.map:14
msgid ""
"You've only seen triggers that activate other\n"
"blocks, but they can also deactivate or toggle\n"
"them."
msgstr ""
"Ты видел триггеры, которые только активируют\n"
"другие блоки, но они также могут деактивировать\n"
"или переключать их."
#: tutorial\tut14.map:68
msgid ""
"When standing on conveyor belts you'll\n"
"move without walking."
msgstr ""
"Стоя на конвейере, ты будешь\n"
"непроизвольно двигаться."
#: tutorial\tut25.map:46
msgid ""
"TIP:\n"
"Try to get your shadow in front of the shadow\n"
"wall. If he falls down you'd better restart."
msgstr ""
"Подсказка:\n"
"Постарайся встать тенью перед стеной для тени. Если он упадёт вниз,\n"
"тебе будет лучше начать сначала."
#: tutorial\tut22.map:33
msgid ""
"TIP:\n"
"When your shadow is trapped stand on the\n"
"right side of the shadow blocks. Now record \n"
"the down key and let your shadow mimic."
msgstr ""
"Подсказка:\n"
"Когда твоя тень будет в ловушке, встань справа\n"
"от блоков для тени. Теперь начни запись, нажми\n"
"клавишу 'вниз' и дай тени воспроизвести твои действия."
#: tutorial\tut08.map:1
msgid "Teamwork"
msgstr "Командная работа"
#: tutorial\tut14.map:71
msgid ""
"Let the shadow finish the level by walking to\n"
"the finish. But don't stand still because your\n"
"shadow will move all the way back."
msgstr ""
"Дай тени закончить уровень, добравшись до финиша.\n"
"Но не стой просто так, потому что твоя тень\n"
"будет смещаться назад на конвейере."
#: tutorial\tut21.map:1
msgid "Swappoints"
msgstr "Точки перестановки"
#: tutorial\tut23.map:18
msgid ""
"One thing you need to know before you're ready are keys.\n"
"Sometimes there are keys spread around the level.\n"
"The exit is locked until you get all the keys."
msgstr ""
"Ещё одна вещь, которую тебе нужно знать, это ключи.\n"
"Иногда ключи распределены по всему уровню.\n"
"Выход закрыт до тех пор, пока ты не соберёшь все ключи."
#: tutorial\tut03.map:34
msgid "Good job!"
-msgstr "Хорошая работа!"
+msgstr "Молодец!"
#: tutorial\tut16.map:14
msgid ""
"There's another type of trigger: the switch.\n"
"Use the switch to activate the elevator so that you\n"
"can reach the exit."
msgstr ""
"Есть ещё один вид триггера: переключатель.\n"
"Используй переключатель, чтобы активировать подъёмник,\n"
"который поможет добраться до выхода."
#: tutorial\tut09.map:1
msgid "Fragile"
msgstr "Хрупкие блоки"
#: tutorial\tut24.map:18
msgid ""
"That's all there is.\n"
"Now it's time to put your skills to the test.\n"
"Enter the exit to go to the last level.\n"
"Good luck!"
msgstr ""
"Вот и всё.\n"
"Настало время проверить твои навыки.\n"
"Пройди к выходу, чтобы перейти на последний уровень.\n"
"Удачи!"
#: tutorial\tut25.map:459
msgid "Where could your shadow be?"
msgstr "Где же может быть твоя тень?"
#: tutorial\tut01.map:18
msgid ""
"Welcome to Me and My Shadow.\n"
"You can use the arrow keys to walk to the exit.\n"
"\n"
"Good luck!"
msgstr ""
"Добро пожаловать в игру \"Me and My Shadow\".\n"
"Ты можешь использовать стрелки для того, чтобы добраться до выхода.\n"
"\n"
"Удачи!"
#: tutorial\tut15.map:1
msgid "Triggering"
msgstr "Триггер"
#: tutorial\tut03.map:1
msgid "Jumping around"
msgstr "Прыгая тут и там"
#: tutorial\tut10.map:1
msgid "Checkpoints"
msgstr "Контрольные точки"
#: tutorial\tut07.map:19
msgid ""
"Spikes are not only deadly for you,\n"
"but also for your shadow."
msgstr ""
"Шипы смертельны не только для тебя,\n"
"но и для твоей тени."
#: tutorial\tut08.map:21
msgid ""
"You and your shadow need to work together.\n"
"Move your shadow towards the wall and jump\n"
"on him so that you can jump over the wall."
msgstr ""
"Тебе и твоей тени надо работать сообща.\n"
"Поставь тень около стены и запрыгни\n"
"на неё, чтобы перепрыгнуть через стену."
#: tutorial\tut04.map:19
msgid ""
"Spikes are deadly.\n"
"Don't touch them!"
msgstr ""
"Шипы смертельны для тебя.\n"
"Не касайся их!"
#: tutorial\levels.lst:3
msgid "Step by step introduction"
msgstr "Введение шаг за шагом"
#: tutorial\tut11.map:1
msgid "Moving blocks"
msgstr "Движущиеся блоки"
#: tutorial\tut14.map:1
msgid "Conveyor madness"
msgstr "Конвейерное сумашествие"
#: tutorial\tut05.map:29
msgid ""
"You can't reach the exit, but your shadow can.\n"
"Press space to record your moves.\n"
"Press space once again to let your shadow\n"
"mimic your recording."
msgstr ""
"Ты не можешь добраться до выхода, но это может сделать твоя тень.\n"
"Нажми пробел, чтобы записать свои движения.\n"
"Нажми пробел ещё раз, чтобы дать своей тени\n"
"воспроизвести записанное."
#: tutorial\levels.lst:2
msgid "You have finished the tutorial!"
msgstr "Ты завершил обучение!"
diff --git a/data/locale/de.po b/data/locale/de.po
index 0eba39b..c3684a6 100644
--- a/data/locale/de.po
+++ b/data/locale/de.po
@@ -1,2123 +1,2123 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the meandmyshadow package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: meandmyshadow 0.5svn\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-09 16:18+0800\n"
-"PO-Revision-Date: 2018-09-22 13:26+0000\n"
+"PO-Revision-Date: 2018-09-24 04:13+0000\n"
"Last-Translator: Wuzzy <almikes@aol.com>\n"
"Language-Team: German <https://hosted.weblate.org/projects/me-and-my-shadow/"
"translations/de/>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.2-dev\n"
#: ../src/AchievementList.cpp:43
msgid "Newbie"
msgstr "Neuling"
#: ../src/AchievementList.cpp:43
msgid "Complete a level."
msgstr "Schließe einen Level ab."
#: ../src/AchievementList.cpp:44
msgid "Experienced player"
msgstr "Erfahrener Spieler"
#: ../src/AchievementList.cpp:44
msgid "Complete 50 levels."
msgstr "Schließe 50 Levels ab."
#: ../src/AchievementList.cpp:45
msgid "Good job!"
msgstr "Gut gemacht!"
#: ../src/AchievementList.cpp:45
msgid "Receive a gold medal."
msgstr "Gewinne eine Goldmedaillie."
#: ../src/AchievementList.cpp:46
msgid "Expert"
msgstr "Experte"
#: ../src/AchievementList.cpp:46
msgid "Earn 50 gold medal."
msgstr "Gewinne 50 Goldmedaillien."
#: ../src/AchievementList.cpp:48
msgid "Graduate"
msgstr "Absolvent"
#: ../src/AchievementList.cpp:48
msgid "Complete the tutorial level pack."
msgstr "Schließe die Einführungs-Levelsammlung ab."
#: ../src/AchievementList.cpp:49
msgid "Outstanding graduate"
msgstr "Herausragender Absolvent"
#: ../src/AchievementList.cpp:49
msgid "Complete the tutorial level pack with gold for all levels."
msgstr "Beende die Einführungs-Levelsammung mit Gold für alle Levels."
#: ../src/AchievementList.cpp:51
msgid "Hooked"
msgstr "Angebissen"
#: ../src/AchievementList.cpp:51
msgid "Play Me and My Shadow for more than 2 hours."
msgstr "Spiele Me and My Shadow für mehr als 2 Stunden."
#: ../src/AchievementList.cpp:52
msgid "Loyal fan of Me and My Shadow"
msgstr "Loyaler Fan von Me and My Shadow"
#: ../src/AchievementList.cpp:52
msgid "Play Me and My Shadow for more than 24 hours."
msgstr "Spiele Me and My Shadow für mehr als 24 Stunden."
#: ../src/AchievementList.cpp:54
msgid "Constructor"
msgstr "Baumeister"
#: ../src/AchievementList.cpp:54
msgid "Use the level editor for more than 2 hours."
msgstr "Benutze den Level-Editor für mehr als 2 Stunden."
#: ../src/AchievementList.cpp:55
msgid "The creator"
msgstr "Der Erschaffer"
#: ../src/AchievementList.cpp:55
msgid "Use the level editor for more than 24 hours."
msgstr "Benutze den Level-Editor für mehr als 24 Stunden."
#: ../src/AchievementList.cpp:57
msgid "Look, cute level!"
msgstr "Sieh mal, ein schöner Level!"
#: ../src/AchievementList.cpp:57
msgid "Create a level for the first time."
msgstr "Baue deinen ersten Level."
#: ../src/AchievementList.cpp:58
msgid "The level museum"
msgstr "Das Level-Museum"
#: ../src/AchievementList.cpp:58
msgid "Create 50 levels."
msgstr "Erstelle 50 Levels."
#: ../src/AchievementList.cpp:60
msgid "Hello, World!"
msgstr "Hallo, Welt!"
#: ../src/AchievementList.cpp:60
msgid "Write a script for the first time."
msgstr "Schreib dein erstes Skript."
#: ../src/AchievementList.cpp:62
msgid "Frog"
msgstr "Frosch"
#: ../src/AchievementList.cpp:62
msgid "Jump 1000 times."
msgstr "Spring 1000 mal."
#: ../src/AchievementList.cpp:64
msgid "Wanderer"
msgstr "Wanderer"
#: ../src/AchievementList.cpp:64
msgid "Travel 100 meters."
msgstr "Reise für 100 Meter."
#: ../src/AchievementList.cpp:65
msgid "Runner"
msgstr "Läufer"
#: ../src/AchievementList.cpp:65
msgid "Travel 1 kilometer."
msgstr "Reise 1 Kilometer."
#: ../src/AchievementList.cpp:66
msgid "Long distance runner"
msgstr "Langstreckenläufer"
#: ../src/AchievementList.cpp:66
msgid "Travel 10 kilometers."
msgstr "Reise für 10 Kilometer."
#: ../src/AchievementList.cpp:67
msgid "Marathon runner"
msgstr "Marathonläufer"
#: ../src/AchievementList.cpp:67
msgid "Travel 42,195 meters."
msgstr "Reise 42.195 Meter."
#: ../src/AchievementList.cpp:69
msgid "Be careful!"
msgstr "Sei vorsichtig!"
#: ../src/AchievementList.cpp:69
msgid "Die for the first time."
msgstr "Stirb zum ersten Mal."
#: ../src/AchievementList.cpp:70
msgid "It doesn't matter..."
msgstr "Es ist egal …"
#: ../src/AchievementList.cpp:70
msgid "Die 50 times."
msgstr "Stirb 50 mal."
#: ../src/AchievementList.cpp:71
msgid "Expert of trial and error"
msgstr "Experte im Rumprobieren"
#: ../src/AchievementList.cpp:71
msgid "Die 1000 times."
msgstr "Stirb 1000 mal."
#: ../src/AchievementList.cpp:73
msgid "Keep an eye for moving blocks!"
msgstr "Pass auf die sich bewegenden Blöcke auf!"
#: ../src/AchievementList.cpp:73
msgid "Get squashed for the first time."
msgstr "Werde zum ersten Mal zerquetscht."
#: ../src/AchievementList.cpp:74
msgid "Potato masher"
msgstr "Kartoffelstampfer"
#: ../src/AchievementList.cpp:74
msgid "Get squashed 50 times."
msgstr "Werde 50 mal zerquetscht."
#: ../src/AchievementList.cpp:76
msgid "Double kill"
msgstr "Doppelmord"
#: ../src/AchievementList.cpp:76
msgid "Get both the player and the shadow dead."
msgstr "Lass sowohl den Spieler als auch den Schatten sterben."
#: ../src/AchievementList.cpp:78
msgid "Bad luck"
msgstr "Pech"
#: ../src/AchievementList.cpp:78
msgid "Die 5 times in under 5 seconds."
msgstr "Stirb 5 mal in weniger als 5 Sekunden."
#: ../src/AchievementList.cpp:79
msgid "This level is too dangerous"
msgstr "Dieser Level ist zu gefährlich"
#: ../src/AchievementList.cpp:79
msgid "Die 10 times in under 5 seconds."
msgstr "Stirb 10 mal in weniger als 5 Sekunden."
#: ../src/AchievementList.cpp:81
msgid "You forgot your friend"
msgstr "Du hast deinen Freund vergessen"
#: ../src/AchievementList.cpp:81
msgid "Finish the level with the player or the shadow dead."
msgstr "Beende den Level, wenn der Spieler oder der Schatten tot ist."
#: ../src/AchievementList.cpp:82
msgid "Just in time"
msgstr "Rechtzeitig"
#: ../src/AchievementList.cpp:82
msgid "Reach the exit with the player and the shadow simultaneously."
msgstr "Erreiche den Ausgang mit dem Spieler und Schatten gleichzeitig."
#: ../src/AchievementList.cpp:84
msgid "Recorder"
msgstr "Aufzeichner"
#: ../src/AchievementList.cpp:84
msgid "Record 100 times."
msgstr "Mach 100 Aufzeichnungen."
#: ../src/AchievementList.cpp:85
msgid "Shadowmaster"
msgstr "Schattenmeister"
#: ../src/AchievementList.cpp:85
msgid "Record 1000 times."
msgstr "Mach 1000 Aufzeichnungen."
#: ../src/AchievementList.cpp:87
msgid "Switch puller"
msgstr "Schalterumleger"
#: ../src/AchievementList.cpp:87
msgid "Pull the switch 100 times."
msgstr "Leg den Schalter 100 mal um."
#: ../src/AchievementList.cpp:88
msgid "The switch is broken!"
msgstr "Der Schalter ist kaputt!"
#: ../src/AchievementList.cpp:88
msgid "Pull the switch 1000 times."
msgstr "Leg den Schalter 1000 mal um."
#: ../src/AchievementList.cpp:90
msgid "Swapper"
msgstr "Vertauscher"
#: ../src/AchievementList.cpp:90
msgid "Swap 100 times."
msgstr "Vertausche 100 mal."
#: ../src/AchievementList.cpp:91
msgid "Player to shadow to player to shadow..."
msgstr "Spieler zu Schatten zu Spieler zu Schatten …"
#: ../src/AchievementList.cpp:91
msgid "Swap 1000 times."
msgstr "Vertausche 1000 mal."
#: ../src/AchievementList.cpp:93
msgid "Play it save"
msgstr "Sicher ist sicher"
#: ../src/AchievementList.cpp:93
msgid "Save 1000 times."
msgstr "Speichere 1000 mal."
#: ../src/AchievementList.cpp:94
msgid "This game is too hard"
msgstr "Das Spiel ist zu schwer"
#: ../src/AchievementList.cpp:94
msgid "Load the game 1000 times."
msgstr "Lade das Spiel 1000 mal."
#: ../src/AchievementList.cpp:96
msgid "No, thanks"
msgstr "Nein danke"
#: ../src/AchievementList.cpp:96
msgid "Complete a level with checkpoint, but without saving."
msgstr "Beende ein Level mit Kontrollpunkt, aber, ohne zu speichern."
#: ../src/AchievementList.cpp:98
msgid "Panic save"
msgstr "Panikspeichern"
#: ../src/AchievementList.cpp:98
msgid "Save twice in 1 second."
msgstr "Speichere zwei mal in 1 Sekunde."
#: ../src/AchievementList.cpp:99
msgid "Panic load"
msgstr "Panikladen"
#: ../src/AchievementList.cpp:99
msgid "Load twice in 1 second."
msgstr "Lade zwei mal in 1 Sekunde."
#: ../src/AchievementList.cpp:101
msgid "Bad saving position"
msgstr "Schlechte Speicherposition"
#: ../src/AchievementList.cpp:101
msgid "Load the game and die within 1 second."
msgstr "Lade das Spiel und stirb innerhalb 1 Sekunde."
#: ../src/AchievementList.cpp:102
msgid "This level is too hard"
msgstr "Dieser Level ist zu schwer"
#: ../src/AchievementList.cpp:102
msgid "Load the same save and die 100 times."
msgstr "Lade den selben Spielstand und stirb 100 mal."
#: ../src/AchievementList.cpp:104
msgid "Quick swap"
msgstr "Schneller Tausch"
#: ../src/AchievementList.cpp:104
msgid "Swap twice in under a second."
msgstr "Vertausche zwei mal in weniger als einer Sekunde."
#: ../src/AchievementList.cpp:107
msgid "Horizontal confusion"
msgstr "Horizontale Verwirrung"
#: ../src/AchievementList.cpp:107
msgid "Press left and right simultaneously."
msgstr "Drücke gleichzeitig links und rechts."
#: ../src/AchievementList.cpp:109
msgid "Cheater"
msgstr "Cheater"
#: ../src/AchievementList.cpp:109
msgid "Cheat in game."
msgstr "Cheate im Spiel."
#: ../src/AchievementList.cpp:111
msgid "Programmer"
msgstr "Programmierer"
#: ../src/AchievementList.cpp:111
msgid "Play the development version of Me and My Shadow."
msgstr "Spiele die Entwicklerversion von Me and My Shadow."
#: ../src/Addons.cpp:44 ../src/LevelPackManager.cpp:108
msgid "Levels"
msgstr "Levels"
#: ../src/Addons.cpp:44
msgid "Single level which usually contain demanding puzzles"
msgstr "Einzelne Level, die üblicherweise knifflige Rätsel enthalten"
#: ../src/Addons.cpp:45
msgid "Levelpacks"
msgstr "Levelsammlungen"
#: ../src/Addons.cpp:45
msgid "Collection of levels with the same author or style"
msgstr "Sammlungen von Levels vom selben Autoren oder im selben Stil"
#: ../src/Addons.cpp:46
msgid "Themes"
msgstr "Themen"
#: ../src/Addons.cpp:46
msgid "Give every block and background a new look and feel"
msgstr "Geben jeden Block und dem Hintergrund ein neues Erscheinungsbild"
#: ../src/Addons.cpp:55 ../src/TitleMenu.cpp:46
msgid "Addons"
msgstr "Add-ons"
#: ../src/Addons.cpp:87
msgid "Unable to initialize addon menu:"
msgstr "Unfähig, das Add-ons-Menü zu initialisieren:"
#: ../src/Addons.cpp:95 ../src/Addons.cpp:158 ../src/Addons.cpp:662
#: ../src/Addons.cpp:690 ../src/CreditsMenu.cpp:89 ../src/LevelSelect.cpp:168
#: ../src/StatisticsScreen.cpp:159
msgid "Back"
msgstr "Zurück"
#: ../src/Addons.cpp:169
msgid "ERROR: unable to download addons file!"
msgstr "FEHLER: Konnte Add-ons-Datei nicht herunterladen!"
# TRANSLATORS: addon_list is the name of a file and should not be translated.
#: ../src/Addons.cpp:182
msgid "ERROR: unable to load addon_list file!"
msgstr "FEHLER: Konnte addon_list-Datei nicht herunterladen!"
#: ../src/Addons.cpp:193
msgid "ERROR: Invalid file format of addons file!"
msgstr "FEHLER: Ungültiges Dateiformat der Add-ons-Datei!"
#: ../src/Addons.cpp:205
msgid "ERROR: Addon list version is unsupported!"
msgstr "FEHLER: Add-on-Listen-Version ist nicht unterstützt!"
# TRANSLATORS: installed_addons is the name of a file and should not be
# translated.
#: ../src/Addons.cpp:226
msgid "ERROR: Unable to create the installed_addons file."
msgstr "FEHLER: Konnte die installed_addons-Datei nicht erzeugen."
#: ../src/Addons.cpp:238
msgid "ERROR: Invalid file format of the installed_addons!"
msgstr "FEHLER: Ungültiges Dateiformat der installed_addons!"
# TRANSLATORS: indicates the author of an addon.
#: ../src/Addons.cpp:389 ../src/Addons.cpp:621
#, c-format
msgid "by %s"
msgstr "von %s"
#: ../src/Addons.cpp:397
msgid "Installed"
msgstr "Installiert"
#: ../src/Addons.cpp:402
msgid "Updatable"
msgstr "Aktualisierbar"
#: ../src/Addons.cpp:412
msgid "Not installed"
msgstr "Nicht installiert"
#: ../src/Addons.cpp:625
#, c-format
msgid "Version: %d\n"
msgstr "Version: %d\n"
#: ../src/Addons.cpp:627
#, c-format
msgid "Installed version: %d\n"
msgstr "Installierte Version: %d\n"
#: ../src/Addons.cpp:630
#, c-format
msgid "License: %s\n"
msgstr "Lizenz: %s\n"
#: ../src/Addons.cpp:633
#, c-format
msgid "Website: %s\n"
msgstr "Webpräsenz: %s\n"
#: ../src/Addons.cpp:637
msgid "(No descriptions provided)"
msgstr "(Keine Beschreibungen vorhanden)"
#: ../src/Addons.cpp:657 ../src/Addons.cpp:684
msgid "Remove"
msgstr "Entfernen"
#: ../src/Addons.cpp:673
msgid "Update"
msgstr "Aktualisieren"
#: ../src/Addons.cpp:679
msgid "Install"
msgstr "Installieren"
#: ../src/Addons.cpp:774
#, c-format
msgid "This addon can't be removed because it's needed by %s."
msgstr "Dieses Add-on kann nicht entfernt werden, da es von %s benötigt wird."
#: ../src/Addons.cpp:774 ../src/Addons.cpp:1051
msgid "Dependency"
msgstr "Abhängigkeit"
#: ../src/Addons.cpp:803
#, c-format
msgid "WARNING: File '%s' appears to have been removed already."
msgstr "ACHTUNG: Datei „%s“ scheint bereits entfernt worden zu sein."
#: ../src/Addons.cpp:803 ../src/Addons.cpp:810 ../src/Addons.cpp:818
#: ../src/Addons.cpp:825 ../src/Addons.cpp:834 ../src/Addons.cpp:840
#: ../src/Addons.cpp:859 ../src/Addons.cpp:866 ../src/Addons.cpp:893
#: ../src/Addons.cpp:900 ../src/Addons.cpp:907 ../src/Addons.cpp:918
#: ../src/Addons.cpp:947 ../src/Addons.cpp:952 ../src/Addons.cpp:962
#: ../src/Addons.cpp:968 ../src/Addons.cpp:981 ../src/Addons.cpp:986
#: ../src/Addons.cpp:1008 ../src/Addons.cpp:1014 ../src/Addons.cpp:1044
msgid "Addon error"
msgstr "Add-on-Fehler"
#: ../src/Addons.cpp:810
#, c-format
msgid "ERROR: Unable to remove file '%s'!"
msgstr "FEHER: Konnte Datei „%s“ nicht entfernen!"
#: ../src/Addons.cpp:818
#, c-format
msgid "WARNING: Directory '%s' appears to have been removed already."
msgstr "ACHTUNG: Verzeichnis: „%s“ scheint bereits entfernt worden zu sein."
#: ../src/Addons.cpp:825
#, c-format
msgid "ERROR: Unable to remove directory '%s'!"
msgstr "FEHLER: Konnte Verzeichnis „%s“ nicht entfernen!"
#: ../src/Addons.cpp:834
#, c-format
msgid "WARNING: Level '%s' appears to have been removed already."
msgstr "ACHTUNG: Level „%s“ scheint bereits entfernt worden zu sein."
#: ../src/Addons.cpp:840
#, c-format
msgid "ERROR: Unable to remove level '%s'!"
msgstr "FEHER: Konnte Level „%s“ nicht entfernen!"
#: ../src/Addons.cpp:859
#, c-format
msgid "WARNING: Levelpack directory '%s' appears to have been removed already."
msgstr ""
"ACHTUNG: Levelsammlungsverzeichnis: „%s“ scheint bereits entfernt worden zu "
"sein."
#: ../src/Addons.cpp:866
#, c-format
msgid "ERROR: Unable to remove levelpack directory '%s'!"
msgstr "FEHLER: Konnte Levelsammlungsverzeichnis „%s“ nicht entfernen!"
#: ../src/Addons.cpp:893
#, c-format
msgid "ERROR: Unable to download addon file %s."
msgstr "FEHLER: Konnte Add-ons-Datei %s nicht herunterladen."
#: ../src/Addons.cpp:900
#, c-format
msgid "ERROR: Unable to extract addon file %s."
msgstr "FEHLER: Konnte Add-ons-Datei %s nicht extrahieren."
#: ../src/Addons.cpp:907
msgid "ERROR: Addon is missing metadata!"
msgstr "FEHLER: Dem Add-on fehlen die Metadaten!"
#: ../src/Addons.cpp:918
msgid "ERROR: Invalid file format for metadata file!"
msgstr "FEHLER: Ungültiges Dateiformat für die Metadaten-Datei!"
#: ../src/Addons.cpp:947
#, c-format
msgid "WARNING: File '%s' already exists, addon may be broken or not working!"
msgstr ""
"ACHTUNG: Datei „%s“ existiert bereits, das Add-on könnte kaputt sein oder "
"nicht funktionieren!"
#: ../src/Addons.cpp:952
#, c-format
msgid ""
"WARNING: Unable to copy file '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
"ACHTUNG: Konnte Datei „%s“ nicht nach „%s“ kopieren, das Add-on könnte "
"kaputt sein oder nicht funktionieren!"
#: ../src/Addons.cpp:962
#, c-format
msgid ""
"WARNING: Destination directory '%s' already exists, addon may be broken or "
"not working!"
msgstr ""
"ACHTUNG: Zielverzeichnis „%s“ existiert bereits, das Add-on könnte kaputt "
"sein oder nicht funktionieren!"
#: ../src/Addons.cpp:968 ../src/Addons.cpp:1014
#, c-format
msgid ""
"WARNING: Unable to move directory '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
"ACHTUNG: Konnte Datei „%s“ nicht nach „%s“ verschieben, das Add-on könnte "
"kaputt sein oder nicht funktionieren!"
#: ../src/Addons.cpp:981
#, c-format
msgid "WARNING: Level '%s' already exists, addon may be broken or not working!"
msgstr ""
"ACHTUNG: Level „%s“ existiert bereits, das Add-on könnte kaputt sein oder "
"nicht funktionieren!"
#: ../src/Addons.cpp:986
#, c-format
msgid ""
"WARNING: Unable to copy level '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
"ACHTUNG: Konnte Level „%s“ nicht nach „%s“ kopieren, das Add-on könnte "
"kaputt sein oder nicht funktionieren!"
#: ../src/Addons.cpp:1008
#, c-format
msgid ""
"WARNING: Levelpack directory '%s' already exists, addon may be broken or not "
"working!"
msgstr ""
"ACHTUNG: Levelsammlungsverzeichnis „%s“ existiert bereits, das Add-on könnte "
"kaputt sein oder nicht funktionieren!"
#: ../src/Addons.cpp:1044
#, c-format
msgid "ERROR: Addon requires another addon (%s) which can't be found!"
msgstr ""
"FEHLER: Das Add-on benötigt ein anderes Add-on (%s), welches nicht gefunden "
"werden kann!"
#: ../src/Addons.cpp:1051
#, c-format
msgid "The addon %s is needed and will be installed now."
msgstr "Das Add-on %s wird benötigt und wird jetzt installiert."
#: ../src/Block.cpp:822 ../src/LevelEditor.cpp:265
msgid "On"
msgstr "Ein"
#: ../src/Block.cpp:823 ../src/LevelEditor.cpp:266
msgid "Off"
msgstr "Aus"
#: ../src/CommandManager.cpp:41
#, c-format
msgid "Undo %s"
msgstr "Rückgängig: %s"
#: ../src/CommandManager.cpp:43
msgid "Can't undo"
msgstr "Kann nicht rückgängig machen"
#: ../src/CommandManager.cpp:49
#, c-format
msgid "Redo %s"
msgstr "Wiederherstellen: %s"
#: ../src/CommandManager.cpp:51
msgid "Can't redo"
msgstr "Kann nicht wiederherstellen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:190
msgid "Resize level"
msgstr "Levelgröße ändern"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:807
msgid "Modify level property"
msgstr "Leveleigenschaft ändern"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:919
#, c-format
msgid "Add scenery layer %s"
msgstr "Szenerieebene %s hinzufügen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:921
#, c-format
msgid "Delete scenery layer %s"
msgstr "Szenerieebene %s löschen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:951
#, c-format
msgid "Modify the property of scenery layer %s"
msgstr "Eigenschaft von Szenerieebene %s ändern"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:1040
#, c-format
msgid "Move %d object from layer %s to layer %s"
msgid_plural "Move %d objects from layer %s to layer %s"
msgstr[0] "%d Objekt von Ebene %s nach Ebene %s verschieben"
msgstr[1] "%d Objekte von Ebene %s nach Ebene %s verschieben"
#: ../src/CreditsMenu.cpp:35 ../src/TitleMenu.cpp:53
msgid "Credits"
msgstr "Abspann"
# TRANSLATORS: Font used in GUI:
# - Use "knewave" for languages using Latin and Latin-derived alphabets
# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:569 ../src/Functions.cpp:570 ../src/Functions.cpp:571
#: ../src/Functions.cpp:588
msgid "knewave"
msgstr "knewave"
# TRANSLATORS: Font used for normal text:
# - Use "Blokletters-Viltstift" for languages using Latin and Latin-derived
# alphabets
# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:575
msgid "Blokletters-Viltstift"
msgstr "Blokletters-Viltstift"
#: ../src/Functions.cpp:674
msgid "Loading..."
msgstr "Laden ..."
#: ../src/Functions.cpp:1243 ../src/Functions.cpp:1270
#: ../src/LevelEditor.cpp:559 ../src/LevelEditor.cpp:693
#: ../src/LevelEditor.cpp:758 ../src/LevelEditor.cpp:821
#: ../src/LevelEditor.cpp:908 ../src/LevelEditor.cpp:1033
#: ../src/LevelEditor.cpp:1083 ../src/LevelEditor.cpp:1180
#: ../src/LevelEditor.cpp:1244 ../src/LevelEditor.cpp:2923
#: ../src/LevelEditSelect.cpp:244 ../src/LevelEditSelect.cpp:277
#: ../src/LevelEditSelect.cpp:317
msgid "OK"
msgstr "OK"
#: ../src/Functions.cpp:1244 ../src/Functions.cpp:1256
#: ../src/Functions.cpp:1266 ../src/LevelEditor.cpp:565
#: ../src/LevelEditor.cpp:699 ../src/LevelEditor.cpp:764
#: ../src/LevelEditor.cpp:827 ../src/LevelEditor.cpp:914
#: ../src/LevelEditor.cpp:1039 ../src/LevelEditor.cpp:1089
#: ../src/LevelEditor.cpp:1186 ../src/LevelEditor.cpp:1250
#: ../src/LevelEditor.cpp:2929 ../src/LevelEditSelect.cpp:248
#: ../src/LevelEditSelect.cpp:281 ../src/LevelEditSelect.cpp:321
#: ../src/OptionsMenu.cpp:289
msgid "Cancel"
msgstr "Abbrechen"
#: ../src/Functions.cpp:1248
msgid "Abort"
msgstr "Abbrechen"
#: ../src/Functions.cpp:1249 ../src/Functions.cpp:1265
msgid "Retry"
msgstr "Neu versuchen"
#: ../src/Functions.cpp:1250
msgid "Ignore"
msgstr "Ignorieren"
#: ../src/Functions.cpp:1254 ../src/Functions.cpp:1260
msgid "Yes"
msgstr "Ja"
#: ../src/Functions.cpp:1255 ../src/Functions.cpp:1261
msgid "No"
msgstr "Nein"
# TRANSLATORS: Please do not remove %s or %d from your translation:
# - %d means the level number in a levelpack
# - %s means the name of current level
#: ../src/Game.cpp:280 ../src/Game.cpp:1236
#, c-format
msgid "Level %d %s"
msgstr "Level %d %s"
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:915
#, c-format
msgid "Press %s key to save the game."
msgstr "Drücke die %s-Taste, um das Spiel zu speichern."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:920
#, c-format
msgid "Press %s key to swap the position of player and shadow."
msgstr ""
"Drücke die %s-Taste, um die Position von dir und deinem Schatten zu "
"vertauschen."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:925
#, c-format
msgid "Press %s key to activate the switch."
msgstr "Drücke die %s-Taste, um den Schalter zu aktivieren."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:930
#, c-format
msgid "Press %s key to teleport."
msgstr "Drücke die %s-Taste zum Teleportieren."
# TRANSLATORS: Please do not remove %s from your translation:
# - first %s means currently configured key to restart game
# - Second %s means configured key to load from last save
#: ../src/Game.cpp:972
#, c-format
msgid "Press %s to restart current level or press %s to load the game."
msgstr ""
"Drücke %s, um den aktellen Level neuzustarten oder drücke %s, um das Spiel "
"zu laden."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with currently configured key to restart game
#: ../src/Game.cpp:983
#, c-format
msgid "Press %s to restart current level."
msgstr "Drücke %s, um den aktuellen Level neuzustarten."
#: ../src/Game.cpp:996
msgid "Your shadow has died."
msgstr "Dein Schatten ist gestorben."
#: ../src/Game.cpp:1052
#, c-format
msgid "%d recording"
msgid_plural "%d recordings"
msgstr[0] "%d Aufzeichnung"
msgstr[1] "%d Aufzeichnungen"
#: ../src/Game.cpp:1224
msgid "You've finished:"
msgstr "Abgeschlossen:"
# TRANSLATORS: Please do not remove %-.2f from your translation:
# - %-.2f means time in seconds
# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1291
#, c-format
msgid "Time: %-.2fs"
msgstr "Zeit: %-.2fs"
# TRANSLATORS: Please do not remove %-.2f from your translation:
# - %-.2f means time in seconds
# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1300
#, c-format
msgid "Best time: %-.2fs"
msgstr "Bestzeit: %-.2fs"
#: ../src/Game.cpp:1311
#, c-format
msgid "Target time: %-.2fs"
msgstr "Zielzeit: %-.2fs"
# TRANSLATORS: Please do not remove %d from your translation:
# - %d means the number of recordings user has made
#: ../src/Game.cpp:1332
#, c-format
msgid "Recordings: %d"
msgstr "Aufzeichnungen: %d"
# TRANSLATORS: Please do not remove %d from your translation:
# - %d means the number of recordings user has made
#: ../src/Game.cpp:1340
#, c-format
msgid "Best recordings: %d"
msgstr "Beste Aufzeichnungen: %d"
#: ../src/Game.cpp:1350
#, c-format
msgid "Target recordings: %d"
msgstr "Ziel: %d Aufzeichnungen"
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with name of a prize medal (gold, silver or bronze)
#: ../src/Game.cpp:1363
#, c-format
msgid "You earned the %s medal"
msgstr "Du hast die %smedaillie gewonnen"
#: ../src/Game.cpp:1363
msgid "GOLD"
msgstr "Gold"
#: ../src/Game.cpp:1363
msgid "SILVER"
msgstr "Silber"
#: ../src/Game.cpp:1363
msgid "BRONZE"
msgstr "Bronze"
# TRANSLATORS: used as return to the level selector menu
#: ../src/Game.cpp:1390
msgid "Menu"
msgstr "Menü"
# TRANSLATORS: used as restart level
#: ../src/Game.cpp:1397 ../src/InputManager.cpp:47
msgid "Restart"
msgstr "Neu versuchen"
# TRANSLATORS: used as next level
#: ../src/Game.cpp:1404
msgid "Next"
msgstr "Nächster"
#: ../src/Game.cpp:1430
msgid "Game replay is done."
msgstr "Wiederholung ist fertig."
#: ../src/Game.cpp:1430
msgid "Game Replay"
msgstr "Wiederholung"
#: ../src/Game.cpp:1767 ../src/Game.cpp:1769
msgid "Congratulations"
msgstr "Glückwunsch"
#: ../src/Game.cpp:1769
msgid "You have finished the levelpack!"
msgstr "Du hast diese Levelsammlung abgeschlossen!"
#: ../src/InputManager.cpp:46
msgid "Up (in menu)"
msgstr "Rauf (im Menü)"
#: ../src/InputManager.cpp:46
msgid "Down (in menu)"
msgstr "Runter (im Menü)"
#: ../src/InputManager.cpp:46
msgid "Left"
msgstr "Links"
#: ../src/InputManager.cpp:46
msgid "Right"
msgstr "Rechts"
#: ../src/InputManager.cpp:46
msgid "Jump"
msgstr "Sprung"
#: ../src/InputManager.cpp:46
msgid "Action"
msgstr "Aktion"
#: ../src/InputManager.cpp:46
msgid "Space (Record)"
msgstr "Leer (Aufzeichnen)"
#: ../src/InputManager.cpp:46
msgid "Cancel recording"
msgstr "Aufzeichnung abbrechen"
#: ../src/InputManager.cpp:47
msgid "Escape"
msgstr "Escape"
#: ../src/InputManager.cpp:47
msgid "Tab (View shadow/Level prop.)"
msgstr "Tab (Schatten ansehen/Leveleinst.)"
#: ../src/InputManager.cpp:47
msgid "Save game (in editor)"
msgstr "Spiel speichern (im Editor)"
#: ../src/InputManager.cpp:47
msgid "Load game"
msgstr "Spiel laden"
#: ../src/InputManager.cpp:47
msgid "Swap (in editor)"
msgstr "Vertauscher (im Editor)"
#: ../src/InputManager.cpp:48
msgid "Teleport (in editor)"
msgstr "Teleportieren (im Editor)"
#: ../src/InputManager.cpp:48
msgid "Suicide (in editor)"
msgstr "Selbstmord (im Editor)"
#: ../src/InputManager.cpp:48
msgid "Shift (in editor)"
msgstr "Laufband (im Editor)"
#: ../src/InputManager.cpp:48
msgid "Next block type (in Editor)"
msgstr "Nächster Blocktyp (im Editor)"
#: ../src/InputManager.cpp:49
msgid "Previous block type (in editor)"
msgstr "Vorheriger Blocktyp (im Editor)"
#: ../src/InputManager.cpp:49
msgid "Select (in menu)"
msgstr "Auswählen (im Menü)"
# TRANSLAOTRS: This is used when the name of the key code is not found.
#: ../src/InputManager.cpp:156
#, c-format
msgid "(Key %d)"
msgstr "(Taste %d)"
#: ../src/InputManager.cpp:163
#, c-format
msgid "Joystick axis %d %s"
msgstr "Joystick-Achse %d %s"
#: ../src/InputManager.cpp:166
#, c-format
msgid "Joystick button %d"
msgstr "Joystick-Button %d"
#: ../src/InputManager.cpp:171
#, c-format
msgid "Joystick hat %d left"
msgstr "Joystick-D-Pad %d links"
#: ../src/InputManager.cpp:174
#, c-format
msgid "Joystick hat %d right"
msgstr "Joystick-D-Pad %d rechts"
#: ../src/InputManager.cpp:177
#, c-format
msgid "Joystick hat %d up"
msgstr "Joystick-D-Pad %d oben"
#: ../src/InputManager.cpp:180
#, c-format
msgid "Joystick hat %d down"
msgstr "Joystick-D-Pad %d unten"
# TRANSLAOTRS: This is used when the JOYSTICK_HAT value is invalid.
#: ../src/InputManager.cpp:185
#, c-format
msgid "Joystick hat %d %d"
msgstr "Joystick-D-Pad %d %d"
#: ../src/InputManager.cpp:202
msgid "OR"
msgstr "ODER"
#: ../src/InputManager.cpp:416
msgid "Select an item and press a key to change it."
msgstr "Wähle einen Eintrag und drücke eine Taste, um ihn zu ändern."
#: ../src/InputManager.cpp:419
msgid "Press backspace to clear the selected item."
msgstr "Drücke die Rücktaste, um den gewählten Eintrag zu leeren."
#: ../src/LevelEditor.cpp:56
msgid "Block"
msgstr "Block"
#: ../src/LevelEditor.cpp:56
msgid "Player Start"
msgstr "Spieler-Startpunkt"
#: ../src/LevelEditor.cpp:56
msgid "Shadow Start"
msgstr "Schatten-Startpunkt"
#: ../src/LevelEditor.cpp:57
msgid "Exit"
msgstr "Ausgang"
#: ../src/LevelEditor.cpp:57
msgid "Shadow Block"
msgstr "Schattenblock"
#: ../src/LevelEditor.cpp:57
msgid "Spikes"
msgstr "Stacheln"
#: ../src/LevelEditor.cpp:58
msgid "Checkpoint"
msgstr "Kontrollpunkt"
#: ../src/LevelEditor.cpp:58 ../src/LevelEditSelect.cpp:312
msgid "Swap"
msgstr "Vertauscher"
#: ../src/LevelEditor.cpp:58
msgid "Fragile"
msgstr "Brüchig"
#: ../src/LevelEditor.cpp:59
msgid "Moving Block"
msgstr "Beweglicher Block"
#: ../src/LevelEditor.cpp:59
msgid "Moving Shadow Block"
msgstr "Beweglicher Schattenblock"
#: ../src/LevelEditor.cpp:59
msgid "Moving Spikes"
msgstr "Bewegliche Stacheln"
#: ../src/LevelEditor.cpp:60
msgid "Teleporter"
msgstr "Teleporter"
#: ../src/LevelEditor.cpp:60
msgid "Button"
msgstr "Taster"
#: ../src/LevelEditor.cpp:60
msgid "Switch"
msgstr "Schalter"
#: ../src/LevelEditor.cpp:61
msgid "Conveyor Belt"
msgstr "Fließband"
#: ../src/LevelEditor.cpp:61
msgid "Shadow Conveyor Belt"
msgstr "Schattenfließband"
#: ../src/LevelEditor.cpp:61
msgid "Notification Block"
msgstr "Hinweisblock"
#: ../src/LevelEditor.cpp:61
msgid "Collectable"
msgstr "Sammelobjekt"
#: ../src/LevelEditor.cpp:61
msgid "Pushable"
msgstr "Schiebeobjekt"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:310
msgid "Select"
msgstr "Auswählen"
#: ../src/LevelEditor.cpp:65
msgid "Add"
msgstr "Hinzufügen"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:311
msgid "Delete"
msgstr "Löschen"
#: ../src/LevelEditor.cpp:65 ../src/LevelPlaySelect.cpp:66
#: ../src/TitleMenu.cpp:43
msgid "Play"
msgstr "Spielen"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:2852
msgid "Level settings"
msgstr "Leveleinstellungen"
#: ../src/LevelEditor.cpp:65
msgid "Save level"
msgstr "Level speichern"
#: ../src/LevelEditor.cpp:65
msgid "Back to menu"
msgstr "Zurück zum Menü"
#: ../src/LevelEditor.cpp:65
msgid "Configure"
msgstr "Konfigurieren"
#: ../src/LevelEditor.cpp:84
#, c-format
msgid "%s (Scenery)"
msgstr "%s (Szenerie)"
#: ../src/LevelEditor.cpp:267
msgid "Toggle"
msgstr "Umschalten"
#: ../src/LevelEditor.cpp:270
msgid "Complete"
msgstr "ganz"
#: ../src/LevelEditor.cpp:271
msgid "One step"
msgstr "1. Stufe"
#: ../src/LevelEditor.cpp:272
msgid "Two steps"
msgstr "2. Stufe"
#: ../src/LevelEditor.cpp:273
msgid "Gone"
msgstr "zerbrochen"
#: ../src/LevelEditor.cpp:291
msgid "Negative infinity"
msgstr "Minus unendlich"
#: ../src/LevelEditor.cpp:293
msgid "Zero"
msgstr "Null"
#: ../src/LevelEditor.cpp:295
msgid "Level size"
msgstr "Levelgröße"
#: ../src/LevelEditor.cpp:297
msgid "Positive infinity"
msgstr "Plus unendlich"
#: ../src/LevelEditor.cpp:299
msgid "Default"
msgstr "Standard"
#: ../src/LevelEditor.cpp:308
msgid "Deselect"
msgstr "Abwählen"
#: ../src/LevelEditor.cpp:318 ../src/LevelEditor.cpp:1136
#, c-format
msgid "Horizontal repeat start: %s"
msgstr "Horizontale Wiederholung, Start: %s"
#: ../src/LevelEditor.cpp:320 ../src/LevelEditor.cpp:1137
#, c-format
msgid "Horizontal repeat end: %s"
msgstr "Horizontale Wiederholung, Ende: %s"
#: ../src/LevelEditor.cpp:322 ../src/LevelEditor.cpp:1138
#, c-format
msgid "Vertical repeat start: %s"
msgstr "Vertikale Wiederholung, Start: %s"
#: ../src/LevelEditor.cpp:324 ../src/LevelEditor.cpp:1139
#, c-format
msgid "Vertical repeat end: %s"
msgstr "Vertikale Wiederholung, Ende: %s"
#: ../src/LevelEditor.cpp:329 ../src/LevelEditor.cpp:1150
msgid "Custom scenery"
msgstr "Benutzerdefinierte Szenerie"
#: ../src/LevelEditor.cpp:335 ../src/LevelEditor.cpp:600
#: ../src/LevelEditor.cpp:602
msgid "Visible"
msgstr "Sichtbar"
#: ../src/LevelEditor.cpp:344
msgid "Link"
msgstr "Verbindung"
#: ../src/LevelEditor.cpp:345
msgid "Remove Links"
msgstr "Verbindungen entfernen"
#: ../src/LevelEditor.cpp:349 ../src/LevelEditor.cpp:624
#: ../src/LevelEditor.cpp:626
msgid "Automatic"
msgstr "Automatisch"
#: ../src/LevelEditor.cpp:359 ../src/LevelEditor.cpp:649
#, c-format
msgid "Behavior: %s"
msgstr "Verhalten: %s"
#: ../src/LevelEditor.cpp:362
msgid "Path"
msgstr "Pfad"
#: ../src/LevelEditor.cpp:363
msgid "Remove Path"
msgstr "Pfad entfernen"
#: ../src/LevelEditor.cpp:365 ../src/LevelEditor.cpp:371
#: ../src/LevelEditor.cpp:587 ../src/LevelEditor.cpp:589
msgid "Activated"
msgstr "Aktiviert"
#: ../src/LevelEditor.cpp:366 ../src/LevelEditor.cpp:612
#: ../src/LevelEditor.cpp:614
msgid "Looping"
msgstr "Schleife"
#: ../src/LevelEditor.cpp:372 ../src/LevelEditor.cpp:3526
msgid "Speed"
msgstr "Geschwindigkeit"
#: ../src/LevelEditor.cpp:378 ../src/LevelEditor.cpp:668
#, c-format
msgid "State: %s"
msgstr "Zustand: %s"
#: ../src/LevelEditor.cpp:382 ../src/LevelEditor.cpp:3511
msgid "Message"
msgstr "Nachricht"
#: ../src/LevelEditor.cpp:384 ../src/LevelEditor.cpp:1202
#: ../src/LevelEditor.cpp:3825
msgid "Appearance"
msgstr "Erscheinungsbild"
#: ../src/LevelEditor.cpp:389 ../src/LevelEditor.cpp:431
#: ../src/LevelEditor.cpp:715
msgid "Scripting"
msgstr "Skript"
#: ../src/LevelEditor.cpp:402 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Background layer: %s"
msgstr "Hintergrundebene: %s"
#: ../src/LevelEditor.cpp:409 ../src/LevelEditor.cpp:866
#: ../src/LevelEditor.cpp:884
msgid "Blocks layer"
msgstr "Blockebene"
#: ../src/LevelEditor.cpp:417 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Foreground layer: %s"
msgstr "Vordergrundebene: %s"
#: ../src/LevelEditor.cpp:423
msgid "Add new layer"
msgstr "Neue Ebene hinzufügen"
#: ../src/LevelEditor.cpp:424
msgid "Delete selected layer"
msgstr "Gewählte Ebene löschen"
#: ../src/LevelEditor.cpp:425
msgid "Configure selected layer"
msgstr "Gewählte Ebene konfigurieren"
#: ../src/LevelEditor.cpp:426
msgid "Move selected object to layer"
msgstr "Gewähltes Objekt zur Ebene verschieben"
#: ../src/LevelEditor.cpp:430 ../src/OptionsMenu.cpp:55
msgid "Settings"
msgstr "Einstellungen"
#: ../src/LevelEditor.cpp:463
msgid ""
"NOTE: the layers are sorted by name alphabetically.\n"
"The layer is background layer if its name is < 'f'\n"
"by dictionary order, otherwise it's foreground layer."
msgstr ""
"ANMERKUNG: Die Ebenen werden alphabetisch\n"
"nach Namen sortiert. Die Ebene ist eine Hinter-\n"
"grundebene falls ihr Name < „f“ nach Wörterbuch-\n"
"sortierung ist, ansonsten ist sie eine Vordegrund-\n"
"ebene."
#: ../src/LevelEditor.cpp:539
msgid "Notification block"
msgstr "Hinweisblock"
#: ../src/LevelEditor.cpp:545
msgid "Enter message here:"
msgstr "Gib die Nachricht hier ein:"
#: ../src/LevelEditor.cpp:646
msgid "Behavior"
msgstr "Verhalten"
#: ../src/LevelEditor.cpp:665
msgid "State"
msgstr "Zustand"
#: ../src/LevelEditor.cpp:673
msgid "Conveyor belt speed"
msgstr "Fließbandgeschwindigkeit"
#: ../src/LevelEditor.cpp:679
msgid "Enter speed here:"
msgstr "Geschwindigkeit:"
#: ../src/LevelEditor.cpp:690
msgid "NOTE: 1 Speed = 0.08 block/s"
msgstr "ANMERKUNG: 1 Einheit = 0,08 Blöcke/s"
#: ../src/LevelEditor.cpp:721
msgid "Id:"
msgstr "ID:"
#: ../src/LevelEditor.cpp:787
msgid "Level Scripting"
msgstr "Level-Skript"
#: ../src/LevelEditor.cpp:892
msgid "Add layer"
msgstr "Ebene hinzufügen"
#: ../src/LevelEditor.cpp:898
msgid "Enter the layer name:"
msgstr "Ebenennamen eingeben:"
#: ../src/LevelEditor.cpp:943
#, c-format
msgid "Are you sure you want to delete layer '%s'?"
msgstr "Bist du sicher, dass du die Ebene „%s“ löschen willst?"
#: ../src/LevelEditor.cpp:944
msgid "Delete layer"
msgstr "Ebene löschen"
#: ../src/LevelEditor.cpp:968
msgid "Layer settings"
msgstr "Ebeneneinstellungen"
#: ../src/LevelEditor.cpp:974
msgid "Layer name:"
msgstr "Ebenenname:"
#: ../src/LevelEditor.cpp:989
msgid "Layer moving speed (1 speed = 0.8 block/s):"
msgstr "Ebenenbewegungsgeschwindigkeit (1 Einheit = 0,8 Blöcke/s):"
#: ../src/LevelEditor.cpp:1010
msgid "Speed of following camera:"
msgstr "Geschwindigkeit der Verfolgungskamera:"
#: ../src/LevelEditor.cpp:1062
msgid "Move to layer"
msgstr "Zur Ebene verschieben"
#: ../src/LevelEditor.cpp:1068
msgid "Enter the layer name (create new layer if necessary):"
msgstr "Ebenennamen eingeben (erstellt neue Ebene, falls nötig):"
#: ../src/LevelEditor.cpp:1132
msgid "Repeat mode"
msgstr "Wiederholungsmodus"
#: ../src/LevelEditor.cpp:1156
msgid "Custom scenery:"
msgstr "Benutzerdefinierte Szenerie:"
#: ../src/LevelEditor.cpp:1219
msgid "(Use the default appearance for this block)"
msgstr "(Standard für diesen Block benutzen)"
# TRANSLATORS: Block name
# TRANSLATORS: Context: Resize/Move ...
# TRANSLATORS: Context: Add/Remove ...
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1465 ../src/LevelEditor.cpp:1707
#: ../src/LevelEditor.cpp:1723 ../src/LevelEditor.cpp:1772
#: ../src/LevelEditor.cpp:4400
msgid "Custom scenery block"
msgstr "Benutzerdefinierter Szenierieblock"
#: ../src/LevelEditor.cpp:1673
msgid "Toolbox"
msgstr "Werkzeugkasten"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Resize %s"
msgstr "%s skalieren"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Move %s"
msgstr "%s verschieben"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1713
#, c-format
msgid "Move %d object"
msgid_plural "Move %d objects"
msgstr[0] "%d Objekt verschieben"
msgstr[1] "%d Objekte verschieben"
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Add %s"
msgstr "%s hinzufügen"
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Remove %s"
msgstr "%s entfernen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1729
#, c-format
msgid "Add %d object"
msgid_plural "Add %d objects"
msgstr[0] "%d Objekt hinzufügen"
msgstr[1] "%d Objekte hinzufügen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1731
#, c-format
msgid "Remove %d object"
msgid_plural "Remove %d objects"
msgstr[0] "%d Objekt entfernen"
msgstr[1] "%d Objekte entfernen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1739
#, c-format
msgid "Add path to %s"
msgstr "Pfad zu %s hinzufügen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1741
#, c-format
msgid "Remove a path point from %s"
msgstr "Einen Pfadpunkt von %s entfernen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1747
#, c-format
msgid "Remove all paths from %s"
msgstr "Alle Pfade von %s entfernen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1753
#, c-format
msgid "Add link from %s to %s"
msgstr "Verbindung von %s nach %s hinzufügen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1759
#, c-format
msgid "Remove all links from %s"
msgstr "Alle Verbindungen von %s entfernen"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1766
msgid "Modify the %2 property of %1"
msgstr "Die Eigenschaft %2 von %1 ändern"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1818
#, c-format
msgid "Edit the script of %s"
msgstr "Skript von %s bearbeiten"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1821
msgid "Edit the script of level"
msgstr "Skript des Levels ändern"
#: ../src/LevelEditor.cpp:2146 ../src/LevelEditor.cpp:2226
msgid "The level has unsaved changes."
msgstr "Der Level hat nicht gespeicherte Änderungen."
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Are you sure you want to quit?"
msgstr "Bist du sicher, dass du gehen willst?"
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Quit prompt"
msgstr "Beenden-Frage"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
#, c-format
msgid "Level \"%s\" saved"
msgstr "Level „%s“ gespeichert"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
msgid "Saved"
msgstr "Gespeichert"
#: ../src/LevelEditor.cpp:2859 ../src/LevelEditSelect.cpp:208
msgid "Name:"
msgstr "Name:"
#: ../src/LevelEditor.cpp:2866
msgid "Theme:"
msgstr "Thema:"
#: ../src/LevelEditor.cpp:2873
msgid "Examples: %DATA%/themes/classic"
msgstr "Beispiele: %DATA%/themes/classic"
#: ../src/LevelEditor.cpp:2875
msgid "or %USER%/themes/Orange"
msgstr "oder %USER%/themes/Orange"
#: ../src/LevelEditor.cpp:2878
msgid "Music:"
msgstr "Musik:"
#: ../src/LevelEditor.cpp:2887
msgid "Target time (s):"
msgstr "Zielzeit (s):"
#: ../src/LevelEditor.cpp:2903
msgid "Target recordings:"
msgstr "Zielaufzeichnungen:"
#: ../src/LevelEditor.cpp:2919
msgid "Restart level editor is required"
msgstr "Neustart des Leveleditors erforderlich"
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3718
#: ../src/LevelEditor.cpp:3739
msgid "Please enter a layer name."
msgstr "Bitte Ebenennamen eingeben."
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3683
#: ../src/LevelEditor.cpp:3718 ../src/LevelEditor.cpp:3722
#: ../src/LevelEditor.cpp:3739 ../src/LevelEditor.cpp:3743
#: ../src/LevelEditSelect.cpp:644 ../src/LevelEditSelect.cpp:683
#: ../src/LevelEditSelect.cpp:688 ../src/LevelEditSelect.cpp:693
#: ../src/LevelEditSelect.cpp:698 ../src/LevelEditSelect.cpp:796
msgid "Error"
msgstr "Fehler"
#: ../src/LevelEditor.cpp:3683 ../src/LevelEditor.cpp:3722
#, c-format
msgid "The layer '%s' already exists."
msgstr "Die Ebene „%s“ existiert bereits."
#: ../src/LevelEditor.cpp:3743
msgid "Source and destination layers are the same."
msgstr "Quell- und Zielebenen sind identisch."
#: ../src/LevelEditor.cpp:3760
msgid "Scenery"
msgstr "Szenerie"
#: ../src/LevelEditor.cpp:4190 ../src/LevelEditor.cpp:4218
#, c-format
msgid "Speed: %d = %0.2f block/s"
msgstr "Geschwindigkeit: %d = %0.2f Blöcke/s"
#: ../src/LevelEditor.cpp:4203
msgid "Stop at this point"
msgstr "An diesem Punkt anhalten"
#: ../src/LevelEditor.cpp:4208
#, c-format
msgid "Pause: %d = %0.3fs"
msgstr "Pause: %d = %0.3f s"
#: ../src/LevelEditSelect.cpp:41 ../src/TitleMenu.cpp:45
msgid "Map Editor"
msgstr "Karteneditor"
#: ../src/LevelEditSelect.cpp:66
msgid "New Levelpack"
msgstr "Neue Levelsammlung"
#: ../src/LevelEditSelect.cpp:71
msgid "Pack Properties"
msgstr "Sammlungseinstellungen"
#: ../src/LevelEditSelect.cpp:76
msgid "Remove Pack"
msgstr "Sammlung löschen"
#: ../src/LevelEditSelect.cpp:81
msgid "Move Map"
msgstr "Karte verschieben"
#: ../src/LevelEditSelect.cpp:89
msgid "Remove Map"
msgstr "Karte löschen"
#: ../src/LevelEditSelect.cpp:94
msgid "Edit Map"
msgstr "Karte bearbeiten"
#: ../src/LevelEditSelect.cpp:205
msgid "Properties"
msgstr "Einstellungen"
#: ../src/LevelEditSelect.cpp:217
msgid "Description:"
msgstr "Beschreibung:"
#: ../src/LevelEditSelect.cpp:226
msgid "Congratulation text:"
msgstr "Glückwunschtext:"
#: ../src/LevelEditSelect.cpp:235
msgid "Music list:"
msgstr "Musikliste:"
#: ../src/LevelEditSelect.cpp:265 ../src/LevelEditSelect.cpp:485
msgid "Add level"
msgstr "Level hinzufügen"
#: ../src/LevelEditSelect.cpp:268
msgid "File name:"
msgstr "Dateiname:"
#: ../src/LevelEditSelect.cpp:293
msgid "Move level"
msgstr "Level verschieben"
#: ../src/LevelEditSelect.cpp:296
msgid "Level: "
msgstr "Level: "
#: ../src/LevelEditSelect.cpp:310
msgid "Before"
msgstr "Davor"
#: ../src/LevelEditSelect.cpp:311
msgid "After"
msgstr "Dahinter"
#: ../src/LevelEditSelect.cpp:368 ../src/LevelPlaySelect.cpp:124
msgid "Individual levels which are not contained in any level packs"
msgstr "Einzelne Levels, die nicht Teil eines Levelsammlung sind"
#: ../src/LevelEditSelect.cpp:577
#, c-format
msgid "Are you sure remove the level pack '%s'?"
msgstr "Willst du die Levelsammlung „%s“ wirklich löschen?"
#: ../src/LevelEditSelect.cpp:577 ../src/LevelEditSelect.cpp:607
msgid "Remove prompt"
msgstr "Löschabfrage"
#: ../src/LevelEditSelect.cpp:607
#, c-format
msgid "Are you sure remove the map '%s'?"
msgstr "Willst du die Karte „%s“ wirklich löschen?"
#: ../src/LevelEditSelect.cpp:644
msgid "Levelpack name cannot be empty."
msgstr "Levelsammlungsname kann nicht leer sein."
#: ../src/LevelEditSelect.cpp:683
#, c-format
msgid "The levelpack directory '%s' already exists!"
-msgstr "Die Ebene „%s“ existiert bereits!"
+msgstr "Das Levelsammlungsverzeichnis „%s“ existiert bereits!"
#: ../src/LevelEditSelect.cpp:688
#, c-format
msgid "Unable to create levelpack directory '%s'!"
msgstr "Konnte Levelsammlungsverzeichnis „%s“ nicht entfernen!"
#: ../src/LevelEditSelect.cpp:693
#, c-format
msgid "The levelpack file '%s' already exists!"
msgstr "Die Levelsammlungsdatei %s existiert bereits!"
#: ../src/LevelEditSelect.cpp:698
#, c-format
msgid "Unable to create levelpack file '%s'!"
msgstr "Konnte Levelsammlungsdatei „%s“ nicht entfernen!"
#: ../src/LevelEditSelect.cpp:758
msgid "No file name given for the new level."
msgstr "Es wurde kein Dateiname für den Level angegeben."
#: ../src/LevelEditSelect.cpp:758
msgid "Missing file name"
msgstr "Dateiname fehlt"
#: ../src/LevelEditSelect.cpp:796
#, c-format
msgid "The file %s already exists."
msgstr "Die Datei %s existiert bereits."
#: ../src/LevelEditSelect.cpp:849
msgid "The entered level number isn't valid!"
msgstr "Die eingegebene Levelnummer ist ungültig!"
#: ../src/LevelEditSelect.cpp:849
msgid "Illegal number"
msgstr "Ungültige Nummer"
#: ../src/LevelInfoRender.cpp:19
msgid "Choose a level"
msgstr "Wähle einen Level"
#: ../src/LevelInfoRender.cpp:20
msgid "Time:"
msgstr "Zeit:"
#: ../src/LevelInfoRender.cpp:21 ../src/StatisticsScreen.cpp:259
msgid "Recordings:"
msgstr "Aufzeichnungen:"
#: ../src/LevelPackManager.cpp:124
msgid "Custom Levels"
msgstr "Benutzerdefinierte Levels"
#: ../src/LevelPlaySelect.cpp:41
msgid "Select Level"
msgstr "Levelauswahl"
# TRANSLATORS: Used for button which clear any level progress like unlocked
# levels and highscores.
#: ../src/OptionsMenu.cpp:66
msgid "Clear Progress"
msgstr "Fortschritt löschen"
#: ../src/OptionsMenu.cpp:109
msgid "General"
msgstr "Allgemein"
#: ../src/OptionsMenu.cpp:110
msgid "Controls"
msgstr "Steuerung"
#: ../src/OptionsMenu.cpp:121
msgid "Music"
msgstr "Musik"
#: ../src/OptionsMenu.cpp:129
msgid "Sound"
msgstr "Toneffekte"
#: ../src/OptionsMenu.cpp:137
msgid "Resolution"
msgstr "Auflösung"
#: ../src/OptionsMenu.cpp:177
msgid "Language"
msgstr "Sprache"
# TRANSLATORS: as detect user's language automatically
#: ../src/OptionsMenu.cpp:185
msgid "Auto-Detect"
msgstr "automatisch erkennen"
#: ../src/OptionsMenu.cpp:209
msgid "Theme"
msgstr "Thema"
#: ../src/OptionsMenu.cpp:247
msgid "Internet proxy"
msgstr "Internet-Proxy"
#: ../src/OptionsMenu.cpp:256
msgid "Fullscreen"
msgstr "Vollbild"
#: ../src/OptionsMenu.cpp:261
msgid "Quick record"
msgstr "Schnellaufzeichnung"
#: ../src/OptionsMenu.cpp:266
msgid "Internet"
msgstr "Internet"
#: ../src/OptionsMenu.cpp:271
msgid "Fade transition"
-msgstr "Übergang"
+msgstr "Überblenden"
#: ../src/OptionsMenu.cpp:294
msgid "Save Changes"
msgstr "Änderungen speichern"
#: ../src/OptionsMenu.cpp:513
msgid "Do you really want to reset level progress?"
msgstr "Willst du wirklich den Levelfortschritt zurücksetzen?"
#: ../src/OptionsMenu.cpp:513
msgid "Warning"
msgstr "Warnung"
#: ../src/StatisticsManager.cpp:386
msgid "New achievement:"
msgstr "Neue Errungenschaft:"
#: ../src/StatisticsManager.cpp:394
#, c-format
msgid "Achieved on %s"
msgstr "Errungen am %s"
#: ../src/StatisticsManager.cpp:400
msgid "Unknown achievement"
msgstr "Unbekannte Errungenschaft"
#: ../src/StatisticsManager.cpp:406
#, c-format
msgid "Achieved %1.0f%%"
msgstr "%1.0f%% errungen"
#: ../src/StatisticsManager.cpp:410
msgid "Not achieved"
msgstr "Nicht errungen"
#: ../src/StatisticsScreen.cpp:57 ../src/TitleMenu.cpp:55
msgid "Achievements and Statistics"
msgstr "Errungenschaften und Statistik"
#: ../src/StatisticsScreen.cpp:166
msgid "Achievements"
msgstr "Errungenschaften"
#: ../src/StatisticsScreen.cpp:167
msgid "Statistics"
msgstr "Statistik"
#: ../src/StatisticsScreen.cpp:234
msgid "Total"
msgstr "Gesamt"
#: ../src/StatisticsScreen.cpp:246
msgid "Traveling distance (m)"
msgstr "Reisestrecke (m)"
#: ../src/StatisticsScreen.cpp:247
msgid "Jump times"
msgstr "Anzahl Sprünge"
#: ../src/StatisticsScreen.cpp:248
msgid "Die times"
msgstr "Anzahl Tode"
#: ../src/StatisticsScreen.cpp:249
msgid "Squashed times"
msgstr "Anzahl Zerquetschte"
#: ../src/StatisticsScreen.cpp:260
msgid "Switch pulled times:"
msgstr "Anzahl umgelegte Schalter:"
#: ../src/StatisticsScreen.cpp:261
msgid "Swap times:"
msgstr "Anzahl Vertauscher:"
#: ../src/StatisticsScreen.cpp:262
msgid "Save times:"
msgstr "Anzahl Speicherungen:"
#: ../src/StatisticsScreen.cpp:263
msgid "Load times:"
msgstr "Anzahl Ladevorgänge:"
#: ../src/StatisticsScreen.cpp:268
msgid "Completed levels:"
msgstr "Abgeschlossene Levels:"
#: ../src/StatisticsScreen.cpp:306
msgid "In-game time:"
msgstr "Spielzeit:"
#: ../src/StatisticsScreen.cpp:308
msgid "Level editing time:"
msgstr "Leveleditorzeit:"
#: ../src/StatisticsScreen.cpp:310
msgid "Created levels:"
msgstr "Erstellte Levels:"
#: ../src/TitleMenu.cpp:44
msgid "Options"
msgstr "Optionen"
#: ../src/TitleMenu.cpp:47
msgid "Quit"
msgstr "Beenden"
#: ../src/TitleMenu.cpp:131
msgid "Enable internet in order to install addons."
msgstr "Internetzugang erlauben, um Add-ons zu installieren."
#: ../src/TitleMenu.cpp:131
msgid "Internet disabled"
msgstr "Internetzugang nicht erlaubt"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Return"
msgstr "Eingabe"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Escape"
msgstr "Escape"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Backspace"
msgstr "Rücktaste"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Tab"
msgstr "Tab"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Space"
msgstr "Leertaste"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "CapsLock"
msgstr "Feststelltaste"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PrintScreen"
msgstr "Druck"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "ScrollLock"
msgstr "Rollen"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Pause"
msgstr "Pause"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Insert"
msgstr "Einfügen"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Home"
msgstr "Pos1"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PageUp"
msgstr "Bild auf"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Delete"
msgstr "Entfernen"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "End"
msgstr "Ende"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PageDown"
msgstr "Bild ab"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right"
msgstr "Rechts"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left"
msgstr "Links"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Down"
msgstr "Unten"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Up"
msgstr "Oben"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Numlock"
msgstr "Numlock"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "SysReq"
msgstr "S-Abf"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Ctrl"
msgstr "Strg links"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Shift"
msgstr "Umschalt links"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Alt"
msgstr "Alt links"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left GUI"
msgstr "GUI links"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Ctrl"
msgstr "Strg rechts"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Shift"
msgstr "Umschalt rechts"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Alt"
msgstr "Alt rechts"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right GUI"
msgstr "GUI rechts"
diff --git a/data/locale/fr.po b/data/locale/fr.po
index ade34f9..89040c8 100644
--- a/data/locale/fr.po
+++ b/data/locale/fr.po
@@ -1,2100 +1,2101 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the meandmyshadow package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
-#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: meandmyshadow 0.5svn\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-09 16:18+0800\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Jz Pan <acme.pjz@gmail.com>, 2018\n"
-"Language-Team: French (https://www.transifex.com/acmepjz/teams/88148/fr/)\n"
+"PO-Revision-Date: 2018-09-24 17:06+0000\n"
+"Last-Translator: Poussinou <francoisvincentmail-game@yahoo.fr>\n"
+"Language-Team: French <https://hosted.weblate.org/projects/me-and-my-shadow/"
+"translations/fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: Weblate 3.2-dev\n"
#: ../src/AchievementList.cpp:43
msgid "Newbie"
msgstr "Bizuth"
#: ../src/AchievementList.cpp:43
msgid "Complete a level."
msgstr "Compléter un niveau."
#: ../src/AchievementList.cpp:44
msgid "Experienced player"
msgstr "Joueur expérimenté"
#: ../src/AchievementList.cpp:44
msgid "Complete 50 levels."
msgstr "Compléter 50 niveaux."
#: ../src/AchievementList.cpp:45
msgid "Good job!"
msgstr "Bien joué !"
#: ../src/AchievementList.cpp:45
msgid "Receive a gold medal."
msgstr "Obtenir une médaille d'or."
#: ../src/AchievementList.cpp:46
msgid "Expert"
msgstr "Expert"
#: ../src/AchievementList.cpp:46
msgid "Earn 50 gold medal."
msgstr "Obtenir 50 médailles d'or."
#: ../src/AchievementList.cpp:48
msgid "Graduate"
msgstr "Diplômé"
#: ../src/AchievementList.cpp:48
msgid "Complete the tutorial level pack."
msgstr "Compléter tous les niveaux du tutoriel."
#: ../src/AchievementList.cpp:49
msgid "Outstanding graduate"
msgstr "Diplômé avec mention"
#: ../src/AchievementList.cpp:49
msgid "Complete the tutorial level pack with gold for all levels."
msgstr ""
"Compléter les niveaux du tutoriel avec la médaille d'or à chaque niveau."
#: ../src/AchievementList.cpp:51
msgid "Hooked"
msgstr "Accro"
#: ../src/AchievementList.cpp:51
msgid "Play Me and My Shadow for more than 2 hours."
msgstr "Jouer à Me and My Shadow pendant plus de 2 heures."
#: ../src/AchievementList.cpp:52
msgid "Loyal fan of Me and My Shadow"
msgstr "Fan fidèle de Me and My Shadow"
#: ../src/AchievementList.cpp:52
msgid "Play Me and My Shadow for more than 24 hours."
msgstr "Jouer à Me and My Shadow pendant plus de 24 heures."
#: ../src/AchievementList.cpp:54
msgid "Constructor"
msgstr "Bâtisseur"
#: ../src/AchievementList.cpp:54
msgid "Use the level editor for more than 2 hours."
msgstr "Utiliser l'éditeur de niveau pendant plus de 2 heures."
#: ../src/AchievementList.cpp:55
msgid "The creator"
msgstr "Le créateur"
#: ../src/AchievementList.cpp:55
msgid "Use the level editor for more than 24 hours."
msgstr "Utiliser l'éditeur de niveau pendant plus de 24 heures."
#: ../src/AchievementList.cpp:57
msgid "Look, cute level!"
msgstr "Regardez, un joli niveau !"
#: ../src/AchievementList.cpp:57
msgid "Create a level for the first time."
msgstr "Créer un niveau pour la première fois."
#: ../src/AchievementList.cpp:58
msgid "The level museum"
msgstr "Le musée des niveaux"
#: ../src/AchievementList.cpp:58
msgid "Create 50 levels."
msgstr "Créer 50 niveaux."
#: ../src/AchievementList.cpp:60
msgid "Hello, World!"
-msgstr ""
+msgstr "Bonjour tout le monde !"
#: ../src/AchievementList.cpp:60
msgid "Write a script for the first time."
-msgstr ""
+msgstr "Écrivez un script pour la première fois."
#: ../src/AchievementList.cpp:62
msgid "Frog"
msgstr "Grenouille"
#: ../src/AchievementList.cpp:62
msgid "Jump 1000 times."
msgstr "Sauter 1000 fois."
#: ../src/AchievementList.cpp:64
msgid "Wanderer"
msgstr "Vagabond"
#: ../src/AchievementList.cpp:64
msgid "Travel 100 meters."
msgstr "Parcourir 100 mètres."
#: ../src/AchievementList.cpp:65
msgid "Runner"
msgstr "Coureur"
#: ../src/AchievementList.cpp:65
msgid "Travel 1 kilometer."
msgstr "Parcourir 1 kilomètre."
#: ../src/AchievementList.cpp:66
msgid "Long distance runner"
msgstr "Coureur de fond"
#: ../src/AchievementList.cpp:66
msgid "Travel 10 kilometers."
msgstr "Parcourir 10 kilomètres."
#: ../src/AchievementList.cpp:67
msgid "Marathon runner"
msgstr "Coureur de marathon"
#: ../src/AchievementList.cpp:67
msgid "Travel 42,195 meters."
msgstr "Parcourir 42 195 mètres."
#: ../src/AchievementList.cpp:69
msgid "Be careful!"
msgstr "Sois prudent !"
#: ../src/AchievementList.cpp:69
msgid "Die for the first time."
msgstr "Mourir pour la première fois."
#: ../src/AchievementList.cpp:70
msgid "It doesn't matter..."
msgstr "C'est pas grave..."
#: ../src/AchievementList.cpp:70
msgid "Die 50 times."
msgstr "Mourir 50 fois."
#: ../src/AchievementList.cpp:71
msgid "Expert of trial and error"
msgstr "Expert de l'approche par tâtonnement"
#: ../src/AchievementList.cpp:71
msgid "Die 1000 times."
msgstr "Mourir 1000 fois."
#: ../src/AchievementList.cpp:73
msgid "Keep an eye for moving blocks!"
msgstr "Fais attention aux blocs mobiles !"
#: ../src/AchievementList.cpp:73
msgid "Get squashed for the first time."
msgstr "Se faire écraser pour la première fois."
#: ../src/AchievementList.cpp:74
msgid "Potato masher"
msgstr "Presse-purée"
#: ../src/AchievementList.cpp:74
msgid "Get squashed 50 times."
msgstr "Se faire écraser 50 fois."
#: ../src/AchievementList.cpp:76
msgid "Double kill"
msgstr "D'une pierre deux coups"
#: ../src/AchievementList.cpp:76
msgid "Get both the player and the shadow dead."
msgstr "Faire mourir à la fois le joueur et l'ombre."
#: ../src/AchievementList.cpp:78
msgid "Bad luck"
msgstr "Pas de bol"
#: ../src/AchievementList.cpp:78
msgid "Die 5 times in under 5 seconds."
msgstr "Mourir 5 fois en moins de 5 secondes."
#: ../src/AchievementList.cpp:79
msgid "This level is too dangerous"
msgstr "Ce niveau est trop dangereux"
#: ../src/AchievementList.cpp:79
msgid "Die 10 times in under 5 seconds."
msgstr "Mourir 10 fois en moins de 5 secondes."
#: ../src/AchievementList.cpp:81
msgid "You forgot your friend"
msgstr "Tu as oublié ton copain"
#: ../src/AchievementList.cpp:81
msgid "Finish the level with the player or the shadow dead."
msgstr "Finir le niveau alors que le joueur ou l'ombre sont morts."
#: ../src/AchievementList.cpp:82
msgid "Just in time"
msgstr "Juste à temps"
#: ../src/AchievementList.cpp:82
msgid "Reach the exit with the player and the shadow simultaneously."
msgstr "Atteindre la sortie avec le joueur et l'ombre au même moment."
#: ../src/AchievementList.cpp:84
msgid "Recorder"
msgstr "Enregistreur"
#: ../src/AchievementList.cpp:84
msgid "Record 100 times."
msgstr "Enregistrer 100 fois."
#: ../src/AchievementList.cpp:85
msgid "Shadowmaster"
msgstr "Maître de l'ombre"
#: ../src/AchievementList.cpp:85
msgid "Record 1000 times."
msgstr "Enregistrer 1000 fois."
#: ../src/AchievementList.cpp:87
msgid "Switch puller"
msgstr "Machiniste"
#: ../src/AchievementList.cpp:87
msgid "Pull the switch 100 times."
msgstr "Commuter l'interrupteur 100 fois."
#: ../src/AchievementList.cpp:88
msgid "The switch is broken!"
msgstr "L'interrupteur est cassé !"
#: ../src/AchievementList.cpp:88
msgid "Pull the switch 1000 times."
msgstr "Commuter l'interrupteur 1000 fois."
#: ../src/AchievementList.cpp:90
msgid "Swapper"
msgstr "Échangeur"
#: ../src/AchievementList.cpp:90
msgid "Swap 100 times."
msgstr "Échanger 100 fois de place."
#: ../src/AchievementList.cpp:91
msgid "Player to shadow to player to shadow..."
msgstr "Du joueur à l'ombre au joueur à l'ombre..."
#: ../src/AchievementList.cpp:91
msgid "Swap 1000 times."
msgstr "Échanger 1000 fois de place."
#: ../src/AchievementList.cpp:93
msgid "Play it save"
msgstr "Pas de prise de risque"
#: ../src/AchievementList.cpp:93
msgid "Save 1000 times."
msgstr "Sauvegarder 1000 fois."
#: ../src/AchievementList.cpp:94
msgid "This game is too hard"
msgstr "Ce jeu est trop dur"
#: ../src/AchievementList.cpp:94
msgid "Load the game 1000 times."
msgstr "Charger une sauvegarde 1000 fois."
#: ../src/AchievementList.cpp:96
msgid "No, thanks"
-msgstr ""
+msgstr "Non merci"
#: ../src/AchievementList.cpp:96
msgid "Complete a level with checkpoint, but without saving."
-msgstr ""
+msgstr "Terminez un niveau avec un point de contrôle, mais sans l'enregistrer."
#: ../src/AchievementList.cpp:98
msgid "Panic save"
msgstr "Sauvegarde panique"
#: ../src/AchievementList.cpp:98
msgid "Save twice in 1 second."
msgstr "Sauvegarder deux fois en une seconde."
#: ../src/AchievementList.cpp:99
msgid "Panic load"
msgstr "Chargement panique"
#: ../src/AchievementList.cpp:99
msgid "Load twice in 1 second."
msgstr "Chargement une sauvegarde deux fois en une seconde."
#: ../src/AchievementList.cpp:101
msgid "Bad saving position"
msgstr "Sauvegarde malchanceuse"
#: ../src/AchievementList.cpp:101
msgid "Load the game and die within 1 second."
msgstr "Charger une sauvegarde et mourir en moins d'une seconde."
#: ../src/AchievementList.cpp:102
msgid "This level is too hard"
msgstr "Ce niveau est trop dur"
#: ../src/AchievementList.cpp:102
msgid "Load the same save and die 100 times."
msgstr "Charger la même sauvegarde et mourir 100 fois."
#: ../src/AchievementList.cpp:104
msgid "Quick swap"
msgstr "Échange éclair"
#: ../src/AchievementList.cpp:104
msgid "Swap twice in under a second."
msgstr "Échange deux fois de position en moins d'une seconde."
#: ../src/AchievementList.cpp:107
msgid "Horizontal confusion"
msgstr "Confusion horizontale"
#: ../src/AchievementList.cpp:107
msgid "Press left and right simultaneously."
msgstr "Appuyez sur gauche et droite simultanément."
#: ../src/AchievementList.cpp:109
msgid "Cheater"
-msgstr ""
+msgstr "Tricheur"
#: ../src/AchievementList.cpp:109
msgid "Cheat in game."
-msgstr ""
+msgstr "Triche dans le jeu."
#: ../src/AchievementList.cpp:111
msgid "Programmer"
msgstr "Programmeur"
#: ../src/AchievementList.cpp:111
msgid "Play the development version of Me and My Shadow."
msgstr "Jouer à la version de développement de Me and My Shadow."
#: ../src/Addons.cpp:44 ../src/LevelPackManager.cpp:108
msgid "Levels"
-msgstr ""
+msgstr "Niveaux"
#: ../src/Addons.cpp:44
msgid "Single level which usually contain demanding puzzles"
msgstr ""
#: ../src/Addons.cpp:45
msgid "Levelpacks"
msgstr ""
#: ../src/Addons.cpp:45
msgid "Collection of levels with the same author or style"
msgstr ""
#: ../src/Addons.cpp:46
msgid "Themes"
msgstr ""
#: ../src/Addons.cpp:46
msgid "Give every block and background a new look and feel"
msgstr ""
#: ../src/Addons.cpp:55 ../src/TitleMenu.cpp:46
msgid "Addons"
msgstr "Extensions"
#: ../src/Addons.cpp:87
msgid "Unable to initialize addon menu:"
msgstr "Impossible d'initialiser le menu des extensions :"
#: ../src/Addons.cpp:95 ../src/Addons.cpp:158 ../src/Addons.cpp:662
#: ../src/Addons.cpp:690 ../src/CreditsMenu.cpp:89 ../src/LevelSelect.cpp:168
#: ../src/StatisticsScreen.cpp:159
msgid "Back"
msgstr "Retour"
#: ../src/Addons.cpp:169
msgid "ERROR: unable to download addons file!"
msgstr "ERREUR : impossible de télécharger le fichier des extensions !"
# TRANSLATORS: addon_list is the name of a file and should not be translated.
#: ../src/Addons.cpp:182
msgid "ERROR: unable to load addon_list file!"
msgstr "ERREUR : impossible de charger le fichier addon_list !"
#: ../src/Addons.cpp:193
msgid "ERROR: Invalid file format of addons file!"
msgstr "ERREUR : Format invalide du ficher des extensions !"
#: ../src/Addons.cpp:205
msgid "ERROR: Addon list version is unsupported!"
msgstr ""
# TRANSLATORS: installed_addons is the name of a file and should not be
# translated.
#: ../src/Addons.cpp:226
msgid "ERROR: Unable to create the installed_addons file."
msgstr "ERREUR : Impossible de créer le fichier installed_addons."
#: ../src/Addons.cpp:238
msgid "ERROR: Invalid file format of the installed_addons!"
msgstr "ERREUR : Format invalide du fichier installed_addons !"
# TRANSLATORS: indicates the author of an addon.
#: ../src/Addons.cpp:389 ../src/Addons.cpp:621
#, c-format
msgid "by %s"
msgstr "par %s"
#: ../src/Addons.cpp:397
msgid "Installed"
msgstr "Installé"
#: ../src/Addons.cpp:402
msgid "Updatable"
msgstr "Mise à jour disponible"
#: ../src/Addons.cpp:412
msgid "Not installed"
msgstr "Non installé"
#: ../src/Addons.cpp:625
#, c-format
msgid "Version: %d\n"
msgstr ""
#: ../src/Addons.cpp:627
#, c-format
msgid "Installed version: %d\n"
msgstr ""
#: ../src/Addons.cpp:630
#, c-format
msgid "License: %s\n"
msgstr ""
#: ../src/Addons.cpp:633
#, c-format
msgid "Website: %s\n"
msgstr ""
#: ../src/Addons.cpp:637
msgid "(No descriptions provided)"
msgstr ""
#: ../src/Addons.cpp:657 ../src/Addons.cpp:684
msgid "Remove"
msgstr "Désinstaller"
#: ../src/Addons.cpp:673
msgid "Update"
msgstr "Mettre à jour"
#: ../src/Addons.cpp:679
msgid "Install"
msgstr "Installer"
#: ../src/Addons.cpp:774
#, c-format
msgid "This addon can't be removed because it's needed by %s."
msgstr ""
#: ../src/Addons.cpp:774 ../src/Addons.cpp:1051
msgid "Dependency"
msgstr ""
#: ../src/Addons.cpp:803
#, c-format
msgid "WARNING: File '%s' appears to have been removed already."
msgstr ""
#: ../src/Addons.cpp:803 ../src/Addons.cpp:810 ../src/Addons.cpp:818
#: ../src/Addons.cpp:825 ../src/Addons.cpp:834 ../src/Addons.cpp:840
#: ../src/Addons.cpp:859 ../src/Addons.cpp:866 ../src/Addons.cpp:893
#: ../src/Addons.cpp:900 ../src/Addons.cpp:907 ../src/Addons.cpp:918
#: ../src/Addons.cpp:947 ../src/Addons.cpp:952 ../src/Addons.cpp:962
#: ../src/Addons.cpp:968 ../src/Addons.cpp:981 ../src/Addons.cpp:986
#: ../src/Addons.cpp:1008 ../src/Addons.cpp:1014 ../src/Addons.cpp:1044
msgid "Addon error"
msgstr ""
#: ../src/Addons.cpp:810
#, c-format
msgid "ERROR: Unable to remove file '%s'!"
msgstr ""
#: ../src/Addons.cpp:818
#, c-format
msgid "WARNING: Directory '%s' appears to have been removed already."
msgstr ""
#: ../src/Addons.cpp:825
#, c-format
msgid "ERROR: Unable to remove directory '%s'!"
msgstr ""
#: ../src/Addons.cpp:834
#, c-format
msgid "WARNING: Level '%s' appears to have been removed already."
msgstr ""
#: ../src/Addons.cpp:840
#, c-format
msgid "ERROR: Unable to remove level '%s'!"
msgstr ""
#: ../src/Addons.cpp:859
#, c-format
msgid "WARNING: Levelpack directory '%s' appears to have been removed already."
msgstr ""
#: ../src/Addons.cpp:866
#, c-format
msgid "ERROR: Unable to remove levelpack directory '%s'!"
msgstr ""
#: ../src/Addons.cpp:893
#, c-format
msgid "ERROR: Unable to download addon file %s."
msgstr ""
#: ../src/Addons.cpp:900
#, c-format
msgid "ERROR: Unable to extract addon file %s."
msgstr ""
#: ../src/Addons.cpp:907
msgid "ERROR: Addon is missing metadata!"
msgstr ""
#: ../src/Addons.cpp:918
msgid "ERROR: Invalid file format for metadata file!"
msgstr ""
#: ../src/Addons.cpp:947
#, c-format
msgid "WARNING: File '%s' already exists, addon may be broken or not working!"
msgstr ""
#: ../src/Addons.cpp:952
#, c-format
msgid ""
"WARNING: Unable to copy file '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
#: ../src/Addons.cpp:962
#, c-format
msgid ""
"WARNING: Destination directory '%s' already exists, addon may be broken or "
"not working!"
msgstr ""
#: ../src/Addons.cpp:968 ../src/Addons.cpp:1014
#, c-format
msgid ""
"WARNING: Unable to move directory '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
#: ../src/Addons.cpp:981
#, c-format
msgid "WARNING: Level '%s' already exists, addon may be broken or not working!"
msgstr ""
#: ../src/Addons.cpp:986
#, c-format
msgid ""
"WARNING: Unable to copy level '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
#: ../src/Addons.cpp:1008
#, c-format
msgid ""
"WARNING: Levelpack directory '%s' already exists, addon may be broken or not "
"working!"
msgstr ""
#: ../src/Addons.cpp:1044
#, c-format
msgid "ERROR: Addon requires another addon (%s) which can't be found!"
msgstr ""
#: ../src/Addons.cpp:1051
#, c-format
msgid "The addon %s is needed and will be installed now."
msgstr ""
#: ../src/Block.cpp:822 ../src/LevelEditor.cpp:265
msgid "On"
msgstr "Activé"
#: ../src/Block.cpp:823 ../src/LevelEditor.cpp:266
msgid "Off"
msgstr "Désactivé"
#: ../src/CommandManager.cpp:41
#, c-format
msgid "Undo %s"
msgstr ""
#: ../src/CommandManager.cpp:43
msgid "Can't undo"
msgstr ""
#: ../src/CommandManager.cpp:49
#, c-format
msgid "Redo %s"
msgstr ""
#: ../src/CommandManager.cpp:51
msgid "Can't redo"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:190
msgid "Resize level"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:807
msgid "Modify level property"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:919
#, c-format
msgid "Add scenery layer %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:921
#, c-format
msgid "Delete scenery layer %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:951
#, c-format
msgid "Modify the property of scenery layer %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:1040
#, c-format
msgid "Move %d object from layer %s to layer %s"
msgid_plural "Move %d objects from layer %s to layer %s"
msgstr[0] ""
msgstr[1] ""
#: ../src/CreditsMenu.cpp:35 ../src/TitleMenu.cpp:53
msgid "Credits"
msgstr "Crédits"
# TRANSLATORS: Font used in GUI:
# - Use "knewave" for languages using Latin and Latin-derived alphabets
# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:569 ../src/Functions.cpp:570 ../src/Functions.cpp:571
#: ../src/Functions.cpp:588
msgid "knewave"
msgstr "knewave"
# TRANSLATORS: Font used for normal text:
# - Use "Blokletters-Viltstift" for languages using Latin and Latin-derived
# alphabets
# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:575
msgid "Blokletters-Viltstift"
msgstr "Blokletters-Viltstift"
#: ../src/Functions.cpp:674
msgid "Loading..."
msgstr "Chargement..."
#: ../src/Functions.cpp:1243 ../src/Functions.cpp:1270
#: ../src/LevelEditor.cpp:559 ../src/LevelEditor.cpp:693
#: ../src/LevelEditor.cpp:758 ../src/LevelEditor.cpp:821
#: ../src/LevelEditor.cpp:908 ../src/LevelEditor.cpp:1033
#: ../src/LevelEditor.cpp:1083 ../src/LevelEditor.cpp:1180
#: ../src/LevelEditor.cpp:1244 ../src/LevelEditor.cpp:2923
#: ../src/LevelEditSelect.cpp:244 ../src/LevelEditSelect.cpp:277
#: ../src/LevelEditSelect.cpp:317
msgid "OK"
msgstr "OK"
#: ../src/Functions.cpp:1244 ../src/Functions.cpp:1256
#: ../src/Functions.cpp:1266 ../src/LevelEditor.cpp:565
#: ../src/LevelEditor.cpp:699 ../src/LevelEditor.cpp:764
#: ../src/LevelEditor.cpp:827 ../src/LevelEditor.cpp:914
#: ../src/LevelEditor.cpp:1039 ../src/LevelEditor.cpp:1089
#: ../src/LevelEditor.cpp:1186 ../src/LevelEditor.cpp:1250
#: ../src/LevelEditor.cpp:2929 ../src/LevelEditSelect.cpp:248
#: ../src/LevelEditSelect.cpp:281 ../src/LevelEditSelect.cpp:321
#: ../src/OptionsMenu.cpp:289
msgid "Cancel"
msgstr "Annuler"
#: ../src/Functions.cpp:1248
msgid "Abort"
msgstr "Interrompre"
#: ../src/Functions.cpp:1249 ../src/Functions.cpp:1265
msgid "Retry"
msgstr "Réessayer"
#: ../src/Functions.cpp:1250
msgid "Ignore"
msgstr "Ignorer"
#: ../src/Functions.cpp:1254 ../src/Functions.cpp:1260
msgid "Yes"
msgstr "Oui"
#: ../src/Functions.cpp:1255 ../src/Functions.cpp:1261
msgid "No"
msgstr "Non"
# TRANSLATORS: Please do not remove %s or %d from your translation:
# - %d means the level number in a levelpack
# - %s means the name of current level
#: ../src/Game.cpp:280 ../src/Game.cpp:1236
#, c-format
msgid "Level %d %s"
msgstr "Niveau %d %s"
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:915
#, c-format
msgid "Press %s key to save the game."
msgstr "Appuyez sur la touche %s pour sauvegarder la partie."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:920
#, c-format
msgid "Press %s key to swap the position of player and shadow."
msgstr ""
"Appuyez sur la touche %s pour échanger la position du joueur et de l'ombre."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:925
#, c-format
msgid "Press %s key to activate the switch."
msgstr "Appuyez sur la touche %s pour activer le bouton."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:930
#, c-format
msgid "Press %s key to teleport."
msgstr "Appuyez sur la touche %s pour être téléporté."
# TRANSLATORS: Please do not remove %s from your translation:
# - first %s means currently configured key to restart game
# - Second %s means configured key to load from last save
#: ../src/Game.cpp:972
#, c-format
msgid "Press %s to restart current level or press %s to load the game."
msgstr ""
"Appuyez sur %s pour recommencer le niveau actuel ou sur %s pour charger la "
"sauvegarde."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with currently configured key to restart game
#: ../src/Game.cpp:983
#, c-format
msgid "Press %s to restart current level."
msgstr "Appuyez sur %s pour recommencer le niveau actuel."
#: ../src/Game.cpp:996
msgid "Your shadow has died."
msgstr "Votre ombre est morte."
#: ../src/Game.cpp:1052
#, c-format
msgid "%d recording"
msgid_plural "%d recordings"
msgstr[0] ""
msgstr[1] ""
#: ../src/Game.cpp:1224
msgid "You've finished:"
msgstr "Vous avez terminé :"
# TRANSLATORS: Please do not remove %-.2f from your translation:
# - %-.2f means time in seconds
# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1291
#, c-format
msgid "Time: %-.2fs"
msgstr "Temps : %-.2f s"
# TRANSLATORS: Please do not remove %-.2f from your translation:
# - %-.2f means time in seconds
# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1300
#, c-format
msgid "Best time: %-.2fs"
msgstr "Meilleur temps : %-.2f s"
#: ../src/Game.cpp:1311
#, c-format
msgid "Target time: %-.2fs"
msgstr "Objectif de temps : %-.2f s"
# TRANSLATORS: Please do not remove %d from your translation:
# - %d means the number of recordings user has made
#: ../src/Game.cpp:1332
#, c-format
msgid "Recordings: %d"
msgstr "Enregistrements : %d"
# TRANSLATORS: Please do not remove %d from your translation:
# - %d means the number of recordings user has made
#: ../src/Game.cpp:1340
#, c-format
msgid "Best recordings: %d"
msgstr "Meilleurs enregistrements : %d"
#: ../src/Game.cpp:1350
#, c-format
msgid "Target recordings: %d"
msgstr "Objectif d'enregistrements : %d"
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with name of a prize medal (gold, silver or bronze)
#: ../src/Game.cpp:1363
#, c-format
msgid "You earned the %s medal"
msgstr "Vous avez gagné la médaille %s"
#: ../src/Game.cpp:1363
msgid "GOLD"
msgstr "d'OR"
#: ../src/Game.cpp:1363
msgid "SILVER"
msgstr "d'ARGENT"
#: ../src/Game.cpp:1363
msgid "BRONZE"
msgstr "de BRONZE"
# TRANSLATORS: used as return to the level selector menu
#: ../src/Game.cpp:1390
msgid "Menu"
msgstr "Menu"
# TRANSLATORS: used as restart level
#: ../src/Game.cpp:1397 ../src/InputManager.cpp:47
msgid "Restart"
msgstr "Recommencer"
# TRANSLATORS: used as next level
#: ../src/Game.cpp:1404
msgid "Next"
msgstr "Suivant"
#: ../src/Game.cpp:1430
msgid "Game replay is done."
msgstr "Le replay de la partie est terminé."
#: ../src/Game.cpp:1430
msgid "Game Replay"
msgstr "Replay de la partie"
#: ../src/Game.cpp:1767 ../src/Game.cpp:1769
msgid "Congratulations"
msgstr "Félicitations"
#: ../src/Game.cpp:1769
msgid "You have finished the levelpack!"
msgstr "Vous avez complété le pack de niveaux !"
#: ../src/InputManager.cpp:46
msgid "Up (in menu)"
msgstr "Haut (dans le menu)"
#: ../src/InputManager.cpp:46
msgid "Down (in menu)"
msgstr "Bas (dans le menu)"
#: ../src/InputManager.cpp:46
msgid "Left"
msgstr "Gauche"
#: ../src/InputManager.cpp:46
msgid "Right"
msgstr "Droite"
#: ../src/InputManager.cpp:46
msgid "Jump"
msgstr "Sauter"
#: ../src/InputManager.cpp:46
msgid "Action"
msgstr "Action"
#: ../src/InputManager.cpp:46
msgid "Space (Record)"
msgstr "Enregistrement des mouvements"
#: ../src/InputManager.cpp:46
msgid "Cancel recording"
msgstr "Annuler l'enregistrement"
#: ../src/InputManager.cpp:47
msgid "Escape"
msgstr "Quitter"
#: ../src/InputManager.cpp:47
msgid "Tab (View shadow/Level prop.)"
msgstr "Voir l'ombre/les propriétés du niveau"
#: ../src/InputManager.cpp:47
msgid "Save game (in editor)"
msgstr "Sauvegarder la partie (éditeur)"
#: ../src/InputManager.cpp:47
msgid "Load game"
msgstr "Charger la sauvegarde"
#: ../src/InputManager.cpp:47
msgid "Swap (in editor)"
msgstr "Échanger les places (éditeur)"
#: ../src/InputManager.cpp:48
msgid "Teleport (in editor)"
msgstr "Téléporter (éditeur)"
#: ../src/InputManager.cpp:48
msgid "Suicide (in editor)"
msgstr "Suicide (éditeur)"
#: ../src/InputManager.cpp:48
msgid "Shift (in editor)"
msgstr "Shift (éditeur)"
#: ../src/InputManager.cpp:48
msgid "Next block type (in Editor)"
msgstr "Type de bloc suivant (éditeur)"
#: ../src/InputManager.cpp:49
msgid "Previous block type (in editor)"
msgstr "Type de bloc précédent (éditeur)"
#: ../src/InputManager.cpp:49
msgid "Select (in menu)"
msgstr "Sélectionner (dans le menu)"
# TRANSLAOTRS: This is used when the name of the key code is not found.
#: ../src/InputManager.cpp:156
#, c-format
msgid "(Key %d)"
msgstr ""
#: ../src/InputManager.cpp:163
#, c-format
msgid "Joystick axis %d %s"
msgstr ""
#: ../src/InputManager.cpp:166
#, c-format
msgid "Joystick button %d"
msgstr ""
#: ../src/InputManager.cpp:171
#, c-format
msgid "Joystick hat %d left"
msgstr ""
#: ../src/InputManager.cpp:174
#, c-format
msgid "Joystick hat %d right"
msgstr ""
#: ../src/InputManager.cpp:177
#, c-format
msgid "Joystick hat %d up"
msgstr ""
#: ../src/InputManager.cpp:180
#, c-format
msgid "Joystick hat %d down"
msgstr ""
# TRANSLAOTRS: This is used when the JOYSTICK_HAT value is invalid.
#: ../src/InputManager.cpp:185
#, c-format
msgid "Joystick hat %d %d"
msgstr ""
#: ../src/InputManager.cpp:202
msgid "OR"
msgstr "OU"
#: ../src/InputManager.cpp:416
msgid "Select an item and press a key to change it."
msgstr "Sélectionner un élément et appuyez sur une touche pour le changer."
#: ../src/InputManager.cpp:419
msgid "Press backspace to clear the selected item."
msgstr ""
"Appuyez sur la touche \"Retour arrière\" pour effacer l'élément sélectionné."
#: ../src/LevelEditor.cpp:56
msgid "Block"
msgstr "Bloc"
#: ../src/LevelEditor.cpp:56
msgid "Player Start"
msgstr "Départ du joueur"
#: ../src/LevelEditor.cpp:56
msgid "Shadow Start"
msgstr "Départ de l'ombre"
#: ../src/LevelEditor.cpp:57
msgid "Exit"
msgstr "Sortie"
#: ../src/LevelEditor.cpp:57
msgid "Shadow Block"
msgstr "Bloc d'ombre"
#: ../src/LevelEditor.cpp:57
msgid "Spikes"
msgstr "Piques"
#: ../src/LevelEditor.cpp:58
msgid "Checkpoint"
msgstr "Point de contrôle"
#: ../src/LevelEditor.cpp:58 ../src/LevelEditSelect.cpp:312
msgid "Swap"
msgstr "Échanger"
#: ../src/LevelEditor.cpp:58
msgid "Fragile"
msgstr "Fragile"
#: ../src/LevelEditor.cpp:59
msgid "Moving Block"
msgstr "Bloc mobile"
#: ../src/LevelEditor.cpp:59
msgid "Moving Shadow Block"
msgstr "Bloc d'ombre mobile"
#: ../src/LevelEditor.cpp:59
msgid "Moving Spikes"
msgstr "Piques mobiles"
#: ../src/LevelEditor.cpp:60
msgid "Teleporter"
msgstr "Téléporteur"
#: ../src/LevelEditor.cpp:60
msgid "Button"
msgstr "Bouton"
#: ../src/LevelEditor.cpp:60
msgid "Switch"
msgstr "Interrupteur"
#: ../src/LevelEditor.cpp:61
msgid "Conveyor Belt"
msgstr "Tapis roulant"
#: ../src/LevelEditor.cpp:61
msgid "Shadow Conveyor Belt"
msgstr "Tapis roulant d'ombre"
#: ../src/LevelEditor.cpp:61
msgid "Notification Block"
msgstr "Panneau d'information"
#: ../src/LevelEditor.cpp:61
msgid "Collectable"
msgstr "Collectable"
#: ../src/LevelEditor.cpp:61
msgid "Pushable"
msgstr "Déplaçable"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:310
msgid "Select"
msgstr "Sélectionner"
#: ../src/LevelEditor.cpp:65
msgid "Add"
msgstr "Ajouter"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:311
msgid "Delete"
msgstr "Supprimer"
#: ../src/LevelEditor.cpp:65 ../src/LevelPlaySelect.cpp:66
#: ../src/TitleMenu.cpp:43
msgid "Play"
msgstr "Jouer"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:2852
msgid "Level settings"
msgstr "Paramètres du niveau"
#: ../src/LevelEditor.cpp:65
msgid "Save level"
msgstr "Enregistrer le niveau"
#: ../src/LevelEditor.cpp:65
msgid "Back to menu"
msgstr "Retour au menu"
#: ../src/LevelEditor.cpp:65
msgid "Configure"
msgstr "Configurer"
#: ../src/LevelEditor.cpp:84
#, c-format
msgid "%s (Scenery)"
msgstr ""
#: ../src/LevelEditor.cpp:267
msgid "Toggle"
msgstr "Bascule"
#: ../src/LevelEditor.cpp:270
msgid "Complete"
msgstr "Complet"
#: ../src/LevelEditor.cpp:271
msgid "One step"
msgstr "Une étape"
#: ../src/LevelEditor.cpp:272
msgid "Two steps"
msgstr "Deux étapes"
#: ../src/LevelEditor.cpp:273
msgid "Gone"
msgstr "Disparu"
#: ../src/LevelEditor.cpp:291
msgid "Negative infinity"
msgstr ""
#: ../src/LevelEditor.cpp:293
msgid "Zero"
msgstr ""
#: ../src/LevelEditor.cpp:295
msgid "Level size"
msgstr ""
#: ../src/LevelEditor.cpp:297
msgid "Positive infinity"
msgstr ""
#: ../src/LevelEditor.cpp:299
msgid "Default"
msgstr ""
#: ../src/LevelEditor.cpp:308
msgid "Deselect"
msgstr "Déselectionner"
#: ../src/LevelEditor.cpp:318 ../src/LevelEditor.cpp:1136
#, c-format
msgid "Horizontal repeat start: %s"
msgstr ""
#: ../src/LevelEditor.cpp:320 ../src/LevelEditor.cpp:1137
#, c-format
msgid "Horizontal repeat end: %s"
msgstr ""
#: ../src/LevelEditor.cpp:322 ../src/LevelEditor.cpp:1138
#, c-format
msgid "Vertical repeat start: %s"
msgstr ""
#: ../src/LevelEditor.cpp:324 ../src/LevelEditor.cpp:1139
#, c-format
msgid "Vertical repeat end: %s"
msgstr ""
#: ../src/LevelEditor.cpp:329 ../src/LevelEditor.cpp:1150
msgid "Custom scenery"
msgstr ""
#: ../src/LevelEditor.cpp:335 ../src/LevelEditor.cpp:600
#: ../src/LevelEditor.cpp:602
msgid "Visible"
msgstr ""
#: ../src/LevelEditor.cpp:344
msgid "Link"
msgstr "Lier"
#: ../src/LevelEditor.cpp:345
msgid "Remove Links"
msgstr "Délier"
#: ../src/LevelEditor.cpp:349 ../src/LevelEditor.cpp:624
#: ../src/LevelEditor.cpp:626
msgid "Automatic"
msgstr "Automatique"
#: ../src/LevelEditor.cpp:359 ../src/LevelEditor.cpp:649
#, c-format
msgid "Behavior: %s"
msgstr ""
#: ../src/LevelEditor.cpp:362
msgid "Path"
msgstr "Parcours"
#: ../src/LevelEditor.cpp:363
msgid "Remove Path"
msgstr "Supprimer le parcours"
#: ../src/LevelEditor.cpp:365 ../src/LevelEditor.cpp:371
#: ../src/LevelEditor.cpp:587 ../src/LevelEditor.cpp:589
msgid "Activated"
msgstr ""
#: ../src/LevelEditor.cpp:366 ../src/LevelEditor.cpp:612
#: ../src/LevelEditor.cpp:614
msgid "Looping"
msgstr "Boucle"
#: ../src/LevelEditor.cpp:372 ../src/LevelEditor.cpp:3526
msgid "Speed"
msgstr "Vitesse"
#: ../src/LevelEditor.cpp:378 ../src/LevelEditor.cpp:668
#, c-format
msgid "State: %s"
msgstr ""
#: ../src/LevelEditor.cpp:382 ../src/LevelEditor.cpp:3511
msgid "Message"
msgstr "Message"
#: ../src/LevelEditor.cpp:384 ../src/LevelEditor.cpp:1202
#: ../src/LevelEditor.cpp:3825
msgid "Appearance"
msgstr ""
#: ../src/LevelEditor.cpp:389 ../src/LevelEditor.cpp:431
#: ../src/LevelEditor.cpp:715
msgid "Scripting"
msgstr "Script"
#: ../src/LevelEditor.cpp:402 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Background layer: %s"
msgstr ""
#: ../src/LevelEditor.cpp:409 ../src/LevelEditor.cpp:866
#: ../src/LevelEditor.cpp:884
msgid "Blocks layer"
msgstr ""
#: ../src/LevelEditor.cpp:417 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Foreground layer: %s"
msgstr ""
#: ../src/LevelEditor.cpp:423
msgid "Add new layer"
msgstr ""
#: ../src/LevelEditor.cpp:424
msgid "Delete selected layer"
msgstr ""
#: ../src/LevelEditor.cpp:425
msgid "Configure selected layer"
msgstr ""
#: ../src/LevelEditor.cpp:426
msgid "Move selected object to layer"
msgstr ""
#: ../src/LevelEditor.cpp:430 ../src/OptionsMenu.cpp:55
msgid "Settings"
msgstr "Paramètres"
#: ../src/LevelEditor.cpp:463
msgid ""
"NOTE: the layers are sorted by name alphabetically.\n"
"The layer is background layer if its name is < 'f'\n"
"by dictionary order, otherwise it's foreground layer."
msgstr ""
#: ../src/LevelEditor.cpp:539
msgid "Notification block"
msgstr "Panneau d'information"
#: ../src/LevelEditor.cpp:545
msgid "Enter message here:"
msgstr "Écrivez le message :"
#: ../src/LevelEditor.cpp:646
msgid "Behavior"
msgstr ""
#: ../src/LevelEditor.cpp:665
msgid "State"
msgstr ""
#: ../src/LevelEditor.cpp:673
msgid "Conveyor belt speed"
msgstr "Vitesse du tapis roulant"
#: ../src/LevelEditor.cpp:679
msgid "Enter speed here:"
msgstr "Définissez la vitesse :"
#: ../src/LevelEditor.cpp:690
msgid "NOTE: 1 Speed = 0.08 block/s"
msgstr ""
#: ../src/LevelEditor.cpp:721
msgid "Id:"
msgstr "Identifiant :"
#: ../src/LevelEditor.cpp:787
msgid "Level Scripting"
msgstr "Script du niveau"
#: ../src/LevelEditor.cpp:892
msgid "Add layer"
msgstr ""
#: ../src/LevelEditor.cpp:898
msgid "Enter the layer name:"
msgstr ""
#: ../src/LevelEditor.cpp:943
#, c-format
msgid "Are you sure you want to delete layer '%s'?"
msgstr ""
#: ../src/LevelEditor.cpp:944
msgid "Delete layer"
msgstr ""
#: ../src/LevelEditor.cpp:968
msgid "Layer settings"
msgstr ""
#: ../src/LevelEditor.cpp:974
msgid "Layer name:"
msgstr ""
#: ../src/LevelEditor.cpp:989
msgid "Layer moving speed (1 speed = 0.8 block/s):"
msgstr ""
#: ../src/LevelEditor.cpp:1010
msgid "Speed of following camera:"
msgstr ""
#: ../src/LevelEditor.cpp:1062
msgid "Move to layer"
msgstr ""
#: ../src/LevelEditor.cpp:1068
msgid "Enter the layer name (create new layer if necessary):"
msgstr ""
#: ../src/LevelEditor.cpp:1132
msgid "Repeat mode"
msgstr ""
#: ../src/LevelEditor.cpp:1156
msgid "Custom scenery:"
msgstr ""
#: ../src/LevelEditor.cpp:1219
msgid "(Use the default appearance for this block)"
msgstr ""
# TRANSLATORS: Block name
# TRANSLATORS: Context: Resize/Move ...
# TRANSLATORS: Context: Add/Remove ...
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1465 ../src/LevelEditor.cpp:1707
#: ../src/LevelEditor.cpp:1723 ../src/LevelEditor.cpp:1772
#: ../src/LevelEditor.cpp:4400
msgid "Custom scenery block"
msgstr ""
#: ../src/LevelEditor.cpp:1673
msgid "Toolbox"
msgstr ""
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Resize %s"
msgstr ""
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Move %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1713
#, c-format
msgid "Move %d object"
msgid_plural "Move %d objects"
msgstr[0] ""
msgstr[1] ""
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Add %s"
msgstr ""
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Remove %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1729
#, c-format
msgid "Add %d object"
msgid_plural "Add %d objects"
msgstr[0] ""
msgstr[1] ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1731
#, c-format
msgid "Remove %d object"
msgid_plural "Remove %d objects"
msgstr[0] ""
msgstr[1] ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1739
#, c-format
msgid "Add path to %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1741
#, c-format
msgid "Remove a path point from %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1747
#, c-format
msgid "Remove all paths from %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1753
#, c-format
msgid "Add link from %s to %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1759
#, c-format
msgid "Remove all links from %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1766
msgid "Modify the %2 property of %1"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1818
#, c-format
msgid "Edit the script of %s"
msgstr ""
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1821
msgid "Edit the script of level"
msgstr ""
#: ../src/LevelEditor.cpp:2146 ../src/LevelEditor.cpp:2226
msgid "The level has unsaved changes."
msgstr ""
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Are you sure you want to quit?"
msgstr "Êtes-vous sûr(e) de vouloir quitter ?"
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Quit prompt"
msgstr "Quitter"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
#, c-format
msgid "Level \"%s\" saved"
msgstr "Niveau \"%s\" enregistré"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
msgid "Saved"
msgstr "Enregistré"
#: ../src/LevelEditor.cpp:2859 ../src/LevelEditSelect.cpp:208
msgid "Name:"
msgstr "Nom :"
#: ../src/LevelEditor.cpp:2866
msgid "Theme:"
msgstr "Thème :"
#: ../src/LevelEditor.cpp:2873
msgid "Examples: %DATA%/themes/classic"
msgstr ""
#: ../src/LevelEditor.cpp:2875
msgid "or %USER%/themes/Orange"
msgstr ""
#: ../src/LevelEditor.cpp:2878
msgid "Music:"
msgstr ""
#: ../src/LevelEditor.cpp:2887
msgid "Target time (s):"
msgstr "Objectif de temps (s) :"
#: ../src/LevelEditor.cpp:2903
msgid "Target recordings:"
msgstr "Objectif d'enregistrements :"
#: ../src/LevelEditor.cpp:2919
msgid "Restart level editor is required"
msgstr ""
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3718
#: ../src/LevelEditor.cpp:3739
msgid "Please enter a layer name."
msgstr ""
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3683
#: ../src/LevelEditor.cpp:3718 ../src/LevelEditor.cpp:3722
#: ../src/LevelEditor.cpp:3739 ../src/LevelEditor.cpp:3743
#: ../src/LevelEditSelect.cpp:644 ../src/LevelEditSelect.cpp:683
#: ../src/LevelEditSelect.cpp:688 ../src/LevelEditSelect.cpp:693
#: ../src/LevelEditSelect.cpp:698 ../src/LevelEditSelect.cpp:796
msgid "Error"
msgstr "Erreur"
#: ../src/LevelEditor.cpp:3683 ../src/LevelEditor.cpp:3722
#, c-format
msgid "The layer '%s' already exists."
msgstr ""
#: ../src/LevelEditor.cpp:3743
msgid "Source and destination layers are the same."
msgstr ""
#: ../src/LevelEditor.cpp:3760
msgid "Scenery"
msgstr ""
#: ../src/LevelEditor.cpp:4190 ../src/LevelEditor.cpp:4218
#, c-format
msgid "Speed: %d = %0.2f block/s"
msgstr ""
#: ../src/LevelEditor.cpp:4203
msgid "Stop at this point"
msgstr ""
#: ../src/LevelEditor.cpp:4208
#, c-format
msgid "Pause: %d = %0.3fs"
msgstr ""
#: ../src/LevelEditSelect.cpp:41 ../src/TitleMenu.cpp:45
msgid "Map Editor"
msgstr "Éditeur de niveau"
#: ../src/LevelEditSelect.cpp:66
msgid "New Levelpack"
msgstr "Nouveau pack de niveaux"
#: ../src/LevelEditSelect.cpp:71
msgid "Pack Properties"
msgstr "Propriétés du pack"
#: ../src/LevelEditSelect.cpp:76
msgid "Remove Pack"
msgstr "Supprimer le pack"
#: ../src/LevelEditSelect.cpp:81
msgid "Move Map"
msgstr "Déplacer le niveau"
#: ../src/LevelEditSelect.cpp:89
msgid "Remove Map"
msgstr "Supprimer le niveau"
#: ../src/LevelEditSelect.cpp:94
msgid "Edit Map"
msgstr "Éditer le niveau"
#: ../src/LevelEditSelect.cpp:205
msgid "Properties"
msgstr "Propriétés"
#: ../src/LevelEditSelect.cpp:217
msgid "Description:"
msgstr "Description :"
#: ../src/LevelEditSelect.cpp:226
msgid "Congratulation text:"
msgstr "Texte de félicitation :"
#: ../src/LevelEditSelect.cpp:235
msgid "Music list:"
msgstr ""
#: ../src/LevelEditSelect.cpp:265 ../src/LevelEditSelect.cpp:485
msgid "Add level"
msgstr "Ajouter un niveau"
#: ../src/LevelEditSelect.cpp:268
msgid "File name:"
msgstr "Nom de fichier :"
#: ../src/LevelEditSelect.cpp:293
msgid "Move level"
msgstr "Déplacer le niveau"
#: ../src/LevelEditSelect.cpp:296
msgid "Level: "
msgstr "Niveau cible :"
#: ../src/LevelEditSelect.cpp:310
msgid "Before"
msgstr "Avant"
#: ../src/LevelEditSelect.cpp:311
msgid "After"
msgstr "Après"
#: ../src/LevelEditSelect.cpp:368 ../src/LevelPlaySelect.cpp:124
msgid "Individual levels which are not contained in any level packs"
msgstr ""
#: ../src/LevelEditSelect.cpp:577
#, c-format
msgid "Are you sure remove the level pack '%s'?"
msgstr ""
#: ../src/LevelEditSelect.cpp:577 ../src/LevelEditSelect.cpp:607
msgid "Remove prompt"
msgstr "Suppression"
#: ../src/LevelEditSelect.cpp:607
#, c-format
msgid "Are you sure remove the map '%s'?"
msgstr ""
#: ../src/LevelEditSelect.cpp:644
msgid "Levelpack name cannot be empty."
msgstr ""
#: ../src/LevelEditSelect.cpp:683
#, c-format
msgid "The levelpack directory '%s' already exists!"
msgstr ""
#: ../src/LevelEditSelect.cpp:688
#, c-format
msgid "Unable to create levelpack directory '%s'!"
msgstr ""
#: ../src/LevelEditSelect.cpp:693
#, c-format
msgid "The levelpack file '%s' already exists!"
msgstr ""
#: ../src/LevelEditSelect.cpp:698
#, c-format
msgid "Unable to create levelpack file '%s'!"
msgstr ""
#: ../src/LevelEditSelect.cpp:758
msgid "No file name given for the new level."
msgstr "Aucun nom de fichier n'a été spécifié pour le nouveau niveau."
#: ../src/LevelEditSelect.cpp:758
msgid "Missing file name"
msgstr "Nom de fichier manquant"
#: ../src/LevelEditSelect.cpp:796
#, c-format
msgid "The file %s already exists."
msgstr ""
#: ../src/LevelEditSelect.cpp:849
msgid "The entered level number isn't valid!"
msgstr "Le numéro de niveau entré n'est pas valide !"
#: ../src/LevelEditSelect.cpp:849
msgid "Illegal number"
msgstr "Nombre invalide"
#: ../src/LevelInfoRender.cpp:19
msgid "Choose a level"
msgstr "Choisissez un niveau"
#: ../src/LevelInfoRender.cpp:20
msgid "Time:"
msgstr "Temps :"
#: ../src/LevelInfoRender.cpp:21 ../src/StatisticsScreen.cpp:259
msgid "Recordings:"
msgstr "Enregistrements :"
#: ../src/LevelPackManager.cpp:124
msgid "Custom Levels"
msgstr ""
#: ../src/LevelPlaySelect.cpp:41
msgid "Select Level"
msgstr "Choix du niveau"
# TRANSLATORS: Used for button which clear any level progress like unlocked
# levels and highscores.
#: ../src/OptionsMenu.cpp:66
msgid "Clear Progress"
msgstr "Réinitialiser la progression"
#: ../src/OptionsMenu.cpp:109
msgid "General"
msgstr "Général"
#: ../src/OptionsMenu.cpp:110
msgid "Controls"
msgstr "Contrôles"
#: ../src/OptionsMenu.cpp:121
msgid "Music"
msgstr "Musique"
#: ../src/OptionsMenu.cpp:129
msgid "Sound"
msgstr "Son"
#: ../src/OptionsMenu.cpp:137
msgid "Resolution"
msgstr "Résolution"
#: ../src/OptionsMenu.cpp:177
msgid "Language"
msgstr "Langue"
# TRANSLATORS: as detect user's language automatically
#: ../src/OptionsMenu.cpp:185
msgid "Auto-Detect"
msgstr "Détection automatique"
#: ../src/OptionsMenu.cpp:209
msgid "Theme"
msgstr "Thème"
#: ../src/OptionsMenu.cpp:247
msgid "Internet proxy"
msgstr "Proxy Internet"
#: ../src/OptionsMenu.cpp:256
msgid "Fullscreen"
msgstr "Plein écran"
#: ../src/OptionsMenu.cpp:261
msgid "Quick record"
msgstr "Enregistrements rapides"
#: ../src/OptionsMenu.cpp:266
msgid "Internet"
msgstr "Accès à Internet"
#: ../src/OptionsMenu.cpp:271
msgid "Fade transition"
msgstr "Transition en fondu"
#: ../src/OptionsMenu.cpp:294
msgid "Save Changes"
msgstr "Enregistrer les changements"
#: ../src/OptionsMenu.cpp:513
msgid "Do you really want to reset level progress?"
msgstr "Souhaitez-vous vraiment réinitialiser votre progression ?"
#: ../src/OptionsMenu.cpp:513
msgid "Warning"
msgstr "Attention"
#: ../src/StatisticsManager.cpp:386
msgid "New achievement:"
msgstr "Nouveau succès :"
#: ../src/StatisticsManager.cpp:394
#, c-format
msgid "Achieved on %s"
msgstr "Obtenu le %s"
#: ../src/StatisticsManager.cpp:400
msgid "Unknown achievement"
msgstr "Succès inconnu"
#: ../src/StatisticsManager.cpp:406
#, c-format
msgid "Achieved %1.0f%%"
msgstr "Complété à %1.0f %%"
#: ../src/StatisticsManager.cpp:410
msgid "Not achieved"
msgstr "Non obtenu"
#: ../src/StatisticsScreen.cpp:57 ../src/TitleMenu.cpp:55
msgid "Achievements and Statistics"
msgstr "Succès et statistiques"
#: ../src/StatisticsScreen.cpp:166
msgid "Achievements"
msgstr "Succès"
#: ../src/StatisticsScreen.cpp:167
msgid "Statistics"
msgstr "Statistiques"
#: ../src/StatisticsScreen.cpp:234
msgid "Total"
msgstr "Total"
#: ../src/StatisticsScreen.cpp:246
msgid "Traveling distance (m)"
msgstr "Distance parcourue (m)"
#: ../src/StatisticsScreen.cpp:247
msgid "Jump times"
msgstr "Nombre de sauts"
#: ../src/StatisticsScreen.cpp:248
msgid "Die times"
msgstr "Nombre de morts"
#: ../src/StatisticsScreen.cpp:249
msgid "Squashed times"
msgstr "Nombre d'écrasements"
#: ../src/StatisticsScreen.cpp:260
msgid "Switch pulled times:"
msgstr "Nombre d'interrupteur utilisés :"
#: ../src/StatisticsScreen.cpp:261
msgid "Swap times:"
msgstr "Nombre d'échanges :"
#: ../src/StatisticsScreen.cpp:262
msgid "Save times:"
msgstr "Nombre de sauvegardes :"
#: ../src/StatisticsScreen.cpp:263
msgid "Load times:"
msgstr "Nombre de chargements :"
#: ../src/StatisticsScreen.cpp:268
msgid "Completed levels:"
msgstr "Niveaux complétés :"
#: ../src/StatisticsScreen.cpp:306
msgid "In-game time:"
msgstr "Durée en jeu :"
#: ../src/StatisticsScreen.cpp:308
msgid "Level editing time:"
msgstr "Durée dans l'éditeur de niveau :"
#: ../src/StatisticsScreen.cpp:310
msgid "Created levels:"
msgstr "Niveaux créés :"
#: ../src/TitleMenu.cpp:44
msgid "Options"
msgstr "Options"
#: ../src/TitleMenu.cpp:47
msgid "Quit"
msgstr "Quitter"
#: ../src/TitleMenu.cpp:131
msgid "Enable internet in order to install addons."
msgstr "Activer l'accès à Internet pour installer des extensions."
#: ../src/TitleMenu.cpp:131
msgid "Internet disabled"
msgstr "Accès à Internet désactivé"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Return"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Escape"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Backspace"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Tab"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Space"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "CapsLock"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PrintScreen"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "ScrollLock"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Pause"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Insert"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Home"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PageUp"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Delete"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "End"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PageDown"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Down"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Up"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Numlock"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "SysReq"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Ctrl"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Shift"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Alt"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left GUI"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Ctrl"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Shift"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Alt"
msgstr ""
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right GUI"
msgstr ""
diff --git a/data/locale/ru.po b/data/locale/ru.po
index ecf7a0d..f82af2e 100644
--- a/data/locale/ru.po
+++ b/data/locale/ru.po
@@ -1,2110 +1,2032 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# Russian translation for Me and My Shadow strings
+# Copyright (C)
# This file is distributed under the same license as the meandmyshadow package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+# Jz Pan <acme_pjz@hotmail.com>, 2012.
#
msgid ""
msgstr ""
-"Project-Id-Version: meandmyshadow 0.5svn\n"
+"Project-Id-Version: Russian (Me And My Shadow)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-09 16:18+0800\n"
-"PO-Revision-Date: 2018-09-13 12:13+0000\n"
-"Last-Translator: Jz Pan <acme_pjz@hotmail.com>\n"
+"PO-Revision-Date: 2018-09-24 04:13+0000\n"
+"Last-Translator: mesnevi <shams@airpost.net>\n"
"Language-Team: Russian <https://hosted.weblate.org/projects/me-and-my-shadow/"
"translations/ru/>\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
-"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
-"(n%100>=11 && n%100<=14)? 2 : 3);\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 3.2-dev\n"
#: ../src/AchievementList.cpp:43
msgid "Newbie"
-msgstr ""
+msgstr "Новичок"
#: ../src/AchievementList.cpp:43
msgid "Complete a level."
-msgstr ""
+msgstr "Уровень пройден."
#: ../src/AchievementList.cpp:44
msgid "Experienced player"
-msgstr ""
+msgstr "Матерый игрок"
#: ../src/AchievementList.cpp:44
msgid "Complete 50 levels."
-msgstr ""
+msgstr "Пройдено 50 уровней."
#: ../src/AchievementList.cpp:45
msgid "Good job!"
-msgstr ""
+msgstr "Молодец!"
#: ../src/AchievementList.cpp:45
msgid "Receive a gold medal."
-msgstr ""
+msgstr "Получена золотая медаль."
#: ../src/AchievementList.cpp:46
msgid "Expert"
-msgstr ""
+msgstr "Спец"
#: ../src/AchievementList.cpp:46
msgid "Earn 50 gold medal."
-msgstr ""
+msgstr "Получено 50 золотых медалей."
#: ../src/AchievementList.cpp:48
msgid "Graduate"
-msgstr ""
+msgstr "Выпускник"
#: ../src/AchievementList.cpp:48
msgid "Complete the tutorial level pack."
-msgstr ""
+msgstr "Пройдены все обучающие уровни."
#: ../src/AchievementList.cpp:49
msgid "Outstanding graduate"
-msgstr ""
+msgstr "Выпускник-отличник"
#: ../src/AchievementList.cpp:49
msgid "Complete the tutorial level pack with gold for all levels."
-msgstr ""
+msgstr "Все обучающие уровни пройдены с золотыми медалями."
#: ../src/AchievementList.cpp:51
msgid "Hooked"
-msgstr ""
+msgstr "Зависимый"
#: ../src/AchievementList.cpp:51
msgid "Play Me and My Shadow for more than 2 hours."
-msgstr ""
+msgstr "За игрой прошло больше 2 часов."
#: ../src/AchievementList.cpp:52
msgid "Loyal fan of Me and My Shadow"
-msgstr ""
+msgstr "Преданный поклонник"
#: ../src/AchievementList.cpp:52
msgid "Play Me and My Shadow for more than 24 hours."
-msgstr ""
+msgstr "За игрой прошло более суток."
#: ../src/AchievementList.cpp:54
msgid "Constructor"
-msgstr ""
+msgstr "Творец"
#: ../src/AchievementList.cpp:54
msgid "Use the level editor for more than 2 hours."
-msgstr ""
+msgstr "Редактор уровней использовался более 2 часов."
#: ../src/AchievementList.cpp:55
msgid "The creator"
-msgstr ""
+msgstr "Могучий творец"
#: ../src/AchievementList.cpp:55
msgid "Use the level editor for more than 24 hours."
-msgstr ""
+msgstr "Редактор уровней использовался больше суток."
#: ../src/AchievementList.cpp:57
msgid "Look, cute level!"
-msgstr ""
+msgstr "Ах, какая прелесть!"
#: ../src/AchievementList.cpp:57
msgid "Create a level for the first time."
-msgstr ""
+msgstr "Вы создали свой первый уровень."
#: ../src/AchievementList.cpp:58
msgid "The level museum"
-msgstr ""
+msgstr "Выставка достижений"
#: ../src/AchievementList.cpp:58
msgid "Create 50 levels."
-msgstr ""
+msgstr "Вы создали 50 уровней."
#: ../src/AchievementList.cpp:60
msgid "Hello, World!"
-msgstr ""
+msgstr "Кто здесь?!"
#: ../src/AchievementList.cpp:60
msgid "Write a script for the first time."
-msgstr ""
+msgstr "Вы написали свой первый скрипт."
#: ../src/AchievementList.cpp:62
msgid "Frog"
-msgstr ""
+msgstr "Лягушка-попрыгушка"
#: ../src/AchievementList.cpp:62
msgid "Jump 1000 times."
-msgstr ""
+msgstr "Вы подпрыгнули 1000 раз."
#: ../src/AchievementList.cpp:64
msgid "Wanderer"
-msgstr ""
+msgstr "Пешеход"
#: ../src/AchievementList.cpp:64
msgid "Travel 100 meters."
-msgstr ""
+msgstr "Вы прошли 100 метров."
#: ../src/AchievementList.cpp:65
msgid "Runner"
-msgstr ""
+msgstr "Стариковская трусца"
#: ../src/AchievementList.cpp:65
msgid "Travel 1 kilometer."
-msgstr ""
+msgstr "Вы прошли 1 километр."
#: ../src/AchievementList.cpp:66
msgid "Long distance runner"
-msgstr ""
+msgstr "Забег на длинную дистанцию"
#: ../src/AchievementList.cpp:66
msgid "Travel 10 kilometers."
-msgstr ""
+msgstr "Вы прошли 10 километров."
#: ../src/AchievementList.cpp:67
msgid "Marathon runner"
-msgstr ""
+msgstr "Марафон"
#: ../src/AchievementList.cpp:67
msgid "Travel 42,195 meters."
-msgstr ""
+msgstr "Вы прошли 42 километра 195 метров."
#: ../src/AchievementList.cpp:69
msgid "Be careful!"
-msgstr ""
+msgstr "Осторожно!"
#: ../src/AchievementList.cpp:69
msgid "Die for the first time."
-msgstr ""
+msgstr "Первая смерть."
#: ../src/AchievementList.cpp:70
msgid "It doesn't matter..."
-msgstr ""
+msgstr "Ну и ладно..."
#: ../src/AchievementList.cpp:70
msgid "Die 50 times."
-msgstr ""
+msgstr "Вы умерли 50 раз."
#: ../src/AchievementList.cpp:71
msgid "Expert of trial and error"
-msgstr ""
+msgstr "Гуру метода проб и ошибок"
#: ../src/AchievementList.cpp:71
msgid "Die 1000 times."
-msgstr ""
+msgstr "Вы умерли 1000 раз."
#: ../src/AchievementList.cpp:73
msgid "Keep an eye for moving blocks!"
-msgstr ""
+msgstr "Надо было следить за двигающимся блоком!"
#: ../src/AchievementList.cpp:73
msgid "Get squashed for the first time."
-msgstr ""
+msgstr "Вас в первый раз раздавило."
#: ../src/AchievementList.cpp:74
msgid "Potato masher"
-msgstr ""
+msgstr "Кому пюре?"
#: ../src/AchievementList.cpp:74
msgid "Get squashed 50 times."
-msgstr ""
+msgstr "Вас раздавило 50 раз."
#: ../src/AchievementList.cpp:76
msgid "Double kill"
-msgstr ""
+msgstr "Одним выстрелом двух зайцев"
#: ../src/AchievementList.cpp:76
msgid "Get both the player and the shadow dead."
-msgstr ""
+msgstr "И игрок, и тень мертвы."
#: ../src/AchievementList.cpp:78
msgid "Bad luck"
-msgstr ""
+msgstr "Тотальное невезение"
#: ../src/AchievementList.cpp:78
msgid "Die 5 times in under 5 seconds."
-msgstr ""
+msgstr "Вы умерли 5 раз за 5 секунд."
#: ../src/AchievementList.cpp:79
msgid "This level is too dangerous"
-msgstr ""
+msgstr "Не уровень, а дикие джунгли"
#: ../src/AchievementList.cpp:79
msgid "Die 10 times in under 5 seconds."
-msgstr ""
+msgstr "Вы умерли 10 раз за 5 секунд."
#: ../src/AchievementList.cpp:81
msgid "You forgot your friend"
-msgstr ""
+msgstr "Друзья? О таких не слышал"
#: ../src/AchievementList.cpp:81
msgid "Finish the level with the player or the shadow dead."
-msgstr ""
+msgstr "В конце уровня или игрок, или тень мертвы."
#: ../src/AchievementList.cpp:82
msgid "Just in time"
-msgstr ""
+msgstr "Одновременно"
#: ../src/AchievementList.cpp:82
msgid "Reach the exit with the player and the shadow simultaneously."
-msgstr ""
+msgstr "Игрок и тень одновременно закончили уровень."
#: ../src/AchievementList.cpp:84
msgid "Recorder"
-msgstr ""
+msgstr "Оператор"
#: ../src/AchievementList.cpp:84
msgid "Record 100 times."
-msgstr ""
+msgstr "Сделано 100 записей."
#: ../src/AchievementList.cpp:85
msgid "Shadowmaster"
-msgstr ""
+msgstr "Повелитель теней"
#: ../src/AchievementList.cpp:85
msgid "Record 1000 times."
-msgstr ""
+msgstr "Сделано 1000 записей."
#: ../src/AchievementList.cpp:87
msgid "Switch puller"
-msgstr ""
+msgstr "Нажми на кнопку"
#: ../src/AchievementList.cpp:87
msgid "Pull the switch 100 times."
-msgstr ""
+msgstr "Вы поменяли положение переключателя 100 раз."
#: ../src/AchievementList.cpp:88
msgid "The switch is broken!"
-msgstr ""
+msgstr "Сейчас сломается!"
#: ../src/AchievementList.cpp:88
msgid "Pull the switch 1000 times."
-msgstr ""
+msgstr "Вы поменяли положение перключателя 1000 раз."
#: ../src/AchievementList.cpp:90
msgid "Swapper"
-msgstr ""
+msgstr "Поменяться местами"
#: ../src/AchievementList.cpp:90
msgid "Swap 100 times."
-msgstr ""
+msgstr "Вы переключились между игроком и тенью 100 раз."
#: ../src/AchievementList.cpp:91
msgid "Player to shadow to player to shadow..."
-msgstr ""
+msgstr "Туда-сюда..."
#: ../src/AchievementList.cpp:91
msgid "Swap 1000 times."
-msgstr ""
+msgstr "Вы переключились между игроком и тенью 1000 раз."
#: ../src/AchievementList.cpp:93
msgid "Play it save"
-msgstr ""
+msgstr "Осторожность лишней не бывает"
#: ../src/AchievementList.cpp:93
msgid "Save 1000 times."
-msgstr ""
+msgstr "Игра сохранялась 1000 раз."
#: ../src/AchievementList.cpp:94
msgid "This game is too hard"
-msgstr ""
+msgstr "Непосильный труд"
#: ../src/AchievementList.cpp:94
msgid "Load the game 1000 times."
-msgstr ""
+msgstr "Игра загружалась 1000 раз."
#: ../src/AchievementList.cpp:96
msgid "No, thanks"
-msgstr ""
+msgstr "Нет уж, спасибо"
#: ../src/AchievementList.cpp:96
msgid "Complete a level with checkpoint, but without saving."
-msgstr ""
+msgstr "Завершить уровень с чекпойнтами без сохранения."
#: ../src/AchievementList.cpp:98
msgid "Panic save"
-msgstr ""
+msgstr "Паническая атака"
#: ../src/AchievementList.cpp:98
msgid "Save twice in 1 second."
-msgstr ""
+msgstr "Дважды сохранить игру за 1 секунду."
#: ../src/AchievementList.cpp:99
msgid "Panic load"
-msgstr ""
+msgstr "Не туда"
#: ../src/AchievementList.cpp:99
msgid "Load twice in 1 second."
-msgstr ""
+msgstr "Дважды загрузить игру за 1 секунду."
#: ../src/AchievementList.cpp:101
msgid "Bad saving position"
-msgstr ""
+msgstr "Местечко так себе"
#: ../src/AchievementList.cpp:101
msgid "Load the game and die within 1 second."
-msgstr ""
+msgstr "Загрузить игру и умереть через секунду."
#: ../src/AchievementList.cpp:102
msgid "This level is too hard"
-msgstr ""
+msgstr "Да что ж такое"
#: ../src/AchievementList.cpp:102
msgid "Load the same save and die 100 times."
-msgstr ""
+msgstr "Загрузить один и тот же сейв и умерть 100 раз."
#: ../src/AchievementList.cpp:104
msgid "Quick swap"
-msgstr ""
+msgstr "Мигом"
#: ../src/AchievementList.cpp:104
msgid "Swap twice in under a second."
-msgstr ""
+msgstr "Дважды переключиться между игроком и тенью за одну секунду."
#: ../src/AchievementList.cpp:107
msgid "Horizontal confusion"
-msgstr ""
+msgstr "Сено-солома"
#: ../src/AchievementList.cpp:107
msgid "Press left and right simultaneously."
-msgstr ""
+msgstr "Одновременно нажимать направо и налево."
#: ../src/AchievementList.cpp:109
msgid "Cheater"
-msgstr ""
+msgstr "Жулик"
#: ../src/AchievementList.cpp:109
msgid "Cheat in game."
-msgstr ""
+msgstr "Не надо было жульничать."
#: ../src/AchievementList.cpp:111
msgid "Programmer"
-msgstr ""
+msgstr "Разработчик"
#: ../src/AchievementList.cpp:111
msgid "Play the development version of Me and My Shadow."
-msgstr ""
+msgstr "Играть в версию, находящуюся в разработке."
#: ../src/Addons.cpp:44 ../src/LevelPackManager.cpp:108
msgid "Levels"
-msgstr ""
+msgstr "Уровни"
#: ../src/Addons.cpp:44
msgid "Single level which usually contain demanding puzzles"
-msgstr ""
+msgstr "Отдельные уровни, требующие сильного умственного напряжения"
#: ../src/Addons.cpp:45
msgid "Levelpacks"
-msgstr ""
+msgstr "Наборы уровней"
#: ../src/Addons.cpp:45
msgid "Collection of levels with the same author or style"
msgstr ""
+"Несколько уровней, сделанных одним и тем же автором или в похожем стиле"
#: ../src/Addons.cpp:46
msgid "Themes"
-msgstr ""
+msgstr "Темы"
#: ../src/Addons.cpp:46
msgid "Give every block and background a new look and feel"
-msgstr ""
+msgstr "Новый вид для блоков и фона"
#: ../src/Addons.cpp:55 ../src/TitleMenu.cpp:46
msgid "Addons"
msgstr "Дополнения"
#: ../src/Addons.cpp:87
msgid "Unable to initialize addon menu:"
-msgstr "Не удалось инициализировать меню дополнений"
+msgstr "Не удалось запустить меню дополнений:"
#: ../src/Addons.cpp:95 ../src/Addons.cpp:158 ../src/Addons.cpp:662
#: ../src/Addons.cpp:690 ../src/CreditsMenu.cpp:89 ../src/LevelSelect.cpp:168
#: ../src/StatisticsScreen.cpp:159
msgid "Back"
msgstr "Назад"
#: ../src/Addons.cpp:169
msgid "ERROR: unable to download addons file!"
-msgstr ""
+msgstr "ОШИБКА: не удалось загрузить файл дополнения!"
-# TRANSLATORS: addon_list is the name of a file and should not be translated.
#: ../src/Addons.cpp:182
msgid "ERROR: unable to load addon_list file!"
-msgstr ""
+msgstr "ОШИБКА: не удалось загрузить файл addon_list!"
#: ../src/Addons.cpp:193
msgid "ERROR: Invalid file format of addons file!"
-msgstr ""
+msgstr "ОШИБКА: Неверный формат файла дополнения!"
#: ../src/Addons.cpp:205
msgid "ERROR: Addon list version is unsupported!"
-msgstr ""
+msgstr "ОШИБКА: версия списка дополнений не поддерживается!"
-# TRANSLATORS: installed_addons is the name of a file and should not be
-# translated.
#: ../src/Addons.cpp:226
msgid "ERROR: Unable to create the installed_addons file."
-msgstr ""
+msgstr "ОШИБКА: невозможно создать список дополнений installed_addons."
#: ../src/Addons.cpp:238
msgid "ERROR: Invalid file format of the installed_addons!"
-msgstr ""
+msgstr "ОШИБКА: у списка дополнений installed_addons неправильный формат!"
-# TRANSLATORS: indicates the author of an addon.
#: ../src/Addons.cpp:389 ../src/Addons.cpp:621
#, c-format
msgid "by %s"
-msgstr ""
+msgstr "автор %"
#: ../src/Addons.cpp:397
msgid "Installed"
-msgstr ""
+msgstr "Установлено"
#: ../src/Addons.cpp:402
msgid "Updatable"
-msgstr ""
+msgstr "Есть обновления"
#: ../src/Addons.cpp:412
msgid "Not installed"
-msgstr ""
+msgstr "Не установлено"
#: ../src/Addons.cpp:625
#, c-format
msgid "Version: %d\n"
-msgstr ""
+msgstr "Версия: %d\n"
#: ../src/Addons.cpp:627
#, c-format
msgid "Installed version: %d\n"
-msgstr ""
+msgstr "Установленная версия: %d\n"
#: ../src/Addons.cpp:630
#, c-format
msgid "License: %s\n"
-msgstr ""
+msgstr "Лицензия: %s\n"
#: ../src/Addons.cpp:633
#, c-format
msgid "Website: %s\n"
-msgstr ""
+msgstr "Веб-сайт: %s\n"
#: ../src/Addons.cpp:637
msgid "(No descriptions provided)"
-msgstr ""
+msgstr "(Описания нет)"
#: ../src/Addons.cpp:657 ../src/Addons.cpp:684
msgid "Remove"
-msgstr ""
+msgstr "Удалить"
#: ../src/Addons.cpp:673
msgid "Update"
msgstr "Обновить"
#: ../src/Addons.cpp:679
msgid "Install"
msgstr "Установить"
#: ../src/Addons.cpp:774
#, c-format
msgid "This addon can't be removed because it's needed by %s."
-msgstr ""
+msgstr "Это дополнение удалить нельзя, оно нужно для правильной работы %s."
#: ../src/Addons.cpp:774 ../src/Addons.cpp:1051
msgid "Dependency"
-msgstr ""
+msgstr "Зависимости"
#: ../src/Addons.cpp:803
#, c-format
msgid "WARNING: File '%s' appears to have been removed already."
-msgstr ""
+msgstr "ВНИМАНИЕ: файл '%s' уже удален."
#: ../src/Addons.cpp:803 ../src/Addons.cpp:810 ../src/Addons.cpp:818
#: ../src/Addons.cpp:825 ../src/Addons.cpp:834 ../src/Addons.cpp:840
#: ../src/Addons.cpp:859 ../src/Addons.cpp:866 ../src/Addons.cpp:893
#: ../src/Addons.cpp:900 ../src/Addons.cpp:907 ../src/Addons.cpp:918
#: ../src/Addons.cpp:947 ../src/Addons.cpp:952 ../src/Addons.cpp:962
#: ../src/Addons.cpp:968 ../src/Addons.cpp:981 ../src/Addons.cpp:986
#: ../src/Addons.cpp:1008 ../src/Addons.cpp:1014 ../src/Addons.cpp:1044
msgid "Addon error"
-msgstr ""
+msgstr "Ошибка дополнения"
#: ../src/Addons.cpp:810
#, c-format
msgid "ERROR: Unable to remove file '%s'!"
-msgstr ""
+msgstr "ОШИБКА: не получается удалить файл '%s'!"
#: ../src/Addons.cpp:818
#, c-format
msgid "WARNING: Directory '%s' appears to have been removed already."
-msgstr ""
+msgstr "ВНИМАНИЕ: Директорию '%s' уже удалили."
#: ../src/Addons.cpp:825
#, c-format
msgid "ERROR: Unable to remove directory '%s'!"
-msgstr ""
+msgstr "ОШИБКА: не получается удалить директорию '%s'!"
#: ../src/Addons.cpp:834
#, c-format
msgid "WARNING: Level '%s' appears to have been removed already."
-msgstr ""
+msgstr "ВНИМАНИЕ: Уровень '%s' уже удалили."
#: ../src/Addons.cpp:840
#, c-format
msgid "ERROR: Unable to remove level '%s'!"
-msgstr ""
+msgstr "ОШИБКА: не получается удалить уровень '%s'!"
#: ../src/Addons.cpp:859
#, c-format
msgid "WARNING: Levelpack directory '%s' appears to have been removed already."
-msgstr ""
+msgstr "ВНИМАНИЕ: Набор уровней '%s' уже удалили."
#: ../src/Addons.cpp:866
#, c-format
msgid "ERROR: Unable to remove levelpack directory '%s'!"
-msgstr ""
+msgstr "ОШИБКА: не получается удалить набор уровней '%s'!"
#: ../src/Addons.cpp:893
#, c-format
msgid "ERROR: Unable to download addon file %s."
-msgstr ""
+msgstr "ОШИБКА: не получается загрузить файл дополнения %s."
#: ../src/Addons.cpp:900
#, c-format
msgid "ERROR: Unable to extract addon file %s."
-msgstr ""
+msgstr "ОШИБКА: не получается распаковать файл дополнения %s."
#: ../src/Addons.cpp:907
msgid "ERROR: Addon is missing metadata!"
-msgstr ""
+msgstr "ОШИБКА: дополнение не содержит метаданных!"
#: ../src/Addons.cpp:918
msgid "ERROR: Invalid file format for metadata file!"
-msgstr ""
+msgstr "ОШИБКА: неверный формат файла метаданных!"
#: ../src/Addons.cpp:947
#, c-format
msgid "WARNING: File '%s' already exists, addon may be broken or not working!"
msgstr ""
+"ВНИМАНИЕ: Файл '%s' уже существует. Возможно, дополнение глючит или не "
+"работает!"
#: ../src/Addons.cpp:952
#, c-format
msgid ""
"WARNING: Unable to copy file '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
+"ВНИМАНИЕ: не получается скопировать файл '%s' в '%s'. Возможно, дополнение "
+"глючит или не работает!"
#: ../src/Addons.cpp:962
#, c-format
msgid ""
"WARNING: Destination directory '%s' already exists, addon may be broken or "
"not working!"
msgstr ""
+"ВНИМАНИЕ: директория '%s' уже существует. Возможно, дополнение глючит или не "
+"работает!"
#: ../src/Addons.cpp:968 ../src/Addons.cpp:1014
#, c-format
msgid ""
"WARNING: Unable to move directory '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
+"ВНИМАНИЕ: не получается скопировать директорию '%s' в '%s'. Возможно, "
+"дополнение глючит или не работает!"
#: ../src/Addons.cpp:981
#, c-format
msgid "WARNING: Level '%s' already exists, addon may be broken or not working!"
msgstr ""
+"ВНИМАНИЕ: Уровень '%s' уже существует. Возможно, дополнение глючит или не "
+"работает!"
#: ../src/Addons.cpp:986
#, c-format
msgid ""
"WARNING: Unable to copy level '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
+"ВНИМАНИЕ: не получается скопировать уровень '%s' в '%s'. Возможно, "
+"дополнение глючит или не работает!"
#: ../src/Addons.cpp:1008
#, c-format
msgid ""
"WARNING: Levelpack directory '%s' already exists, addon may be broken or not "
"working!"
msgstr ""
+"ВНИМАНИЕ: Директория с набором уровней '%s' уже существует. Возможно, "
+"дополнение глючит или не работает!"
#: ../src/Addons.cpp:1044
#, c-format
msgid "ERROR: Addon requires another addon (%s) which can't be found!"
msgstr ""
+"ОШИБКА: для правильной работы дополнения необходимо установить другое "
+"дополнение (%s)!"
#: ../src/Addons.cpp:1051
#, c-format
msgid "The addon %s is needed and will be installed now."
-msgstr ""
+msgstr "Необходимо установить дополнение %s. Установка сейчас начнется."
#: ../src/Block.cpp:822 ../src/LevelEditor.cpp:265
msgid "On"
msgstr "Вкл"
#: ../src/Block.cpp:823 ../src/LevelEditor.cpp:266
msgid "Off"
msgstr "Выкл"
#: ../src/CommandManager.cpp:41
#, c-format
msgid "Undo %s"
-msgstr ""
+msgstr "Отменить %s"
#: ../src/CommandManager.cpp:43
msgid "Can't undo"
-msgstr ""
+msgstr "Отменить не получается"
#: ../src/CommandManager.cpp:49
#, c-format
msgid "Redo %s"
-msgstr ""
+msgstr "Повторить %s"
#: ../src/CommandManager.cpp:51
msgid "Can't redo"
-msgstr ""
+msgstr "Повторить не получается"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:190
msgid "Resize level"
-msgstr ""
+msgstr "Изменение размеров уровня"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:807
msgid "Modify level property"
-msgstr ""
+msgstr "Изменение свойств уровня"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:919
#, c-format
msgid "Add scenery layer %s"
-msgstr ""
+msgstr "Добавление слоя ландшафта %s"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:921
#, c-format
msgid "Delete scenery layer %s"
-msgstr ""
+msgstr "Удаление слоя ландшафта %s"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:951
#, c-format
msgid "Modify the property of scenery layer %s"
-msgstr ""
+msgstr "Изменение свойств слоя ландшафта %s"
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:1040
#, c-format
msgid "Move %d object from layer %s to layer %s"
msgid_plural "Move %d objects from layer %s to layer %s"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "Переместить %d объект из слоя %s в слой %s"
+msgstr[1] "Переместить %d объекта из слоя %s в слой %s"
+msgstr[2] "Переместить %d объектов из слоя %s в слой %s"
+msgstr[3] "объекта"
#: ../src/CreditsMenu.cpp:35 ../src/TitleMenu.cpp:53
msgid "Credits"
-msgstr "Разработчики"
+msgstr "Благодарности"
# TRANSLATORS: Font used in GUI:
# - Use "knewave" for languages using Latin and Latin-derived alphabets
# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:569 ../src/Functions.cpp:570 ../src/Functions.cpp:571
#: ../src/Functions.cpp:588
msgid "knewave"
msgstr "DejaVuSansCondensed-Oblique"
-# TRANSLATORS: Font used for normal text:
-# - Use "Blokletters-Viltstift" for languages using Latin and Latin-derived
-# alphabets
-# - "DroidSansFallback" can be used for non-Latin writing systems
#: ../src/Functions.cpp:575
msgid "Blokletters-Viltstift"
msgstr "DejaVuSansCondensed-Oblique"
#: ../src/Functions.cpp:674
msgid "Loading..."
-msgstr ""
+msgstr "Загрузка..."
#: ../src/Functions.cpp:1243 ../src/Functions.cpp:1270
#: ../src/LevelEditor.cpp:559 ../src/LevelEditor.cpp:693
#: ../src/LevelEditor.cpp:758 ../src/LevelEditor.cpp:821
#: ../src/LevelEditor.cpp:908 ../src/LevelEditor.cpp:1033
#: ../src/LevelEditor.cpp:1083 ../src/LevelEditor.cpp:1180
#: ../src/LevelEditor.cpp:1244 ../src/LevelEditor.cpp:2923
#: ../src/LevelEditSelect.cpp:244 ../src/LevelEditSelect.cpp:277
#: ../src/LevelEditSelect.cpp:317
msgid "OK"
msgstr "ОК"
#: ../src/Functions.cpp:1244 ../src/Functions.cpp:1256
#: ../src/Functions.cpp:1266 ../src/LevelEditor.cpp:565
#: ../src/LevelEditor.cpp:699 ../src/LevelEditor.cpp:764
#: ../src/LevelEditor.cpp:827 ../src/LevelEditor.cpp:914
#: ../src/LevelEditor.cpp:1039 ../src/LevelEditor.cpp:1089
#: ../src/LevelEditor.cpp:1186 ../src/LevelEditor.cpp:1250
#: ../src/LevelEditor.cpp:2929 ../src/LevelEditSelect.cpp:248
#: ../src/LevelEditSelect.cpp:281 ../src/LevelEditSelect.cpp:321
#: ../src/OptionsMenu.cpp:289
msgid "Cancel"
msgstr "Отмена"
#: ../src/Functions.cpp:1248
msgid "Abort"
msgstr "Прервать"
#: ../src/Functions.cpp:1249 ../src/Functions.cpp:1265
msgid "Retry"
msgstr "Попробовать снова"
#: ../src/Functions.cpp:1250
msgid "Ignore"
-msgstr "Игнорировать"
+msgstr "Пропустить"
#: ../src/Functions.cpp:1254 ../src/Functions.cpp:1260
msgid "Yes"
msgstr "Да"
#: ../src/Functions.cpp:1255 ../src/Functions.cpp:1261
msgid "No"
msgstr "Нет"
-# TRANSLATORS: Please do not remove %s or %d from your translation:
-# - %d means the level number in a levelpack
-# - %s means the name of current level
#: ../src/Game.cpp:280 ../src/Game.cpp:1236
#, c-format
msgid "Level %d %s"
msgstr "Уровень %d %s"
-# TRANSLATORS: Please do not remove %s from your translation:
-# - %s will be replaced with current action key
#: ../src/Game.cpp:915
#, c-format
msgid "Press %s key to save the game."
msgstr "Чтобы сохранить игру, нажмите клавишу %s."
-# TRANSLATORS: Please do not remove %s from your translation:
-# - %s will be replaced with current action key
#: ../src/Game.cpp:920
#, c-format
msgid "Press %s key to swap the position of player and shadow."
msgstr "Чтобы поменять позицию игрока и тени, нажмите клавишу %s."
-# TRANSLATORS: Please do not remove %s from your translation:
-# - %s will be replaced with current action key
#: ../src/Game.cpp:925
#, c-format
msgid "Press %s key to activate the switch."
msgstr "Чтобы активировать переключатель, нажмите клавишу %s."
-# TRANSLATORS: Please do not remove %s from your translation:
-# - %s will be replaced with current action key
#: ../src/Game.cpp:930
#, c-format
msgid "Press %s key to teleport."
msgstr "Чтобы телепортироваться, нажмите клавишу %s."
-# TRANSLATORS: Please do not remove %s from your translation:
-# - first %s means currently configured key to restart game
-# - Second %s means configured key to load from last save
#: ../src/Game.cpp:972
#, c-format
msgid "Press %s to restart current level or press %s to load the game."
msgstr ""
"Чтобы перезапустить этот уровень, нажмите %s, или нажмите %s, чтобы "
"загрузить игру."
-# TRANSLATORS: Please do not remove %s from your translation:
-# - %s will be replaced with currently configured key to restart game
#: ../src/Game.cpp:983
#, c-format
msgid "Press %s to restart current level."
msgstr "Чтобы перезапустить этот уровень, нажмите %s."
#: ../src/Game.cpp:996
msgid "Your shadow has died."
msgstr "Ваша тень умерла."
#: ../src/Game.cpp:1052
#, c-format
msgid "%d recording"
msgid_plural "%d recordings"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%d запись"
+msgstr[1] "%d записи"
+msgstr[2] "%d записей"
+msgstr[3] "%d записи"
#: ../src/Game.cpp:1224
msgid "You've finished:"
-msgstr "Вы завершили:"
+msgstr "Завершенных уровней:"
-# TRANSLATORS: Please do not remove %-.2f from your translation:
-# - %-.2f means time in seconds
-# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1291
#, c-format
msgid "Time: %-.2fs"
msgstr "Время: %-.2fs"
-# TRANSLATORS: Please do not remove %-.2f from your translation:
-# - %-.2f means time in seconds
-# - s is shortened form of a second. Try to keep it so.
#: ../src/Game.cpp:1300
#, c-format
msgid "Best time: %-.2fs"
msgstr "Лучшее время: %-.2fs"
#: ../src/Game.cpp:1311
#, c-format
msgid "Target time: %-.2fs"
msgstr "Необходимое время: %-.2fs"
-# TRANSLATORS: Please do not remove %d from your translation:
-# - %d means the number of recordings user has made
#: ../src/Game.cpp:1332
#, c-format
msgid "Recordings: %d"
msgstr "Записей: %d"
-# TRANSLATORS: Please do not remove %d from your translation:
-# - %d means the number of recordings user has made
#: ../src/Game.cpp:1340
#, c-format
msgid "Best recordings: %d"
msgstr "Лучшая запись: %d"
#: ../src/Game.cpp:1350
#, c-format
msgid "Target recordings: %d"
msgstr "Необходимо записей: %d"
-# TRANSLATORS: Please do not remove %s from your translation:
-# - %s will be replaced with name of a prize medal (gold, silver or bronze)
#: ../src/Game.cpp:1363
#, c-format
msgid "You earned the %s medal"
msgstr "Вы получили %s медаль"
#: ../src/Game.cpp:1363
msgid "GOLD"
msgstr "золотую"
#: ../src/Game.cpp:1363
msgid "SILVER"
msgstr "серебряную"
#: ../src/Game.cpp:1363
msgid "BRONZE"
msgstr "бронзовую"
-# TRANSLATORS: used as return to the level selector menu
#: ../src/Game.cpp:1390
msgid "Menu"
msgstr "Меню"
-# TRANSLATORS: used as restart level
#: ../src/Game.cpp:1397 ../src/InputManager.cpp:47
msgid "Restart"
msgstr "Снова"
-# TRANSLATORS: used as next level
#: ../src/Game.cpp:1404
msgid "Next"
msgstr "Далее"
#: ../src/Game.cpp:1430
msgid "Game replay is done."
msgstr "Воспроизведение завершено."
#: ../src/Game.cpp:1430
msgid "Game Replay"
msgstr "Воспроизведение игры"
#: ../src/Game.cpp:1767 ../src/Game.cpp:1769
msgid "Congratulations"
msgstr "Поздравляем"
#: ../src/Game.cpp:1769
msgid "You have finished the levelpack!"
msgstr "Вы закончили этот набор уровней!"
#: ../src/InputManager.cpp:46
msgid "Up (in menu)"
msgstr "Вверх (в меню)"
#: ../src/InputManager.cpp:46
msgid "Down (in menu)"
msgstr "Вниз (в меню)"
#: ../src/InputManager.cpp:46
msgid "Left"
msgstr "Влево"
#: ../src/InputManager.cpp:46
msgid "Right"
msgstr "Вправо"
#: ../src/InputManager.cpp:46
msgid "Jump"
msgstr "Прыжок"
#: ../src/InputManager.cpp:46
msgid "Action"
msgstr "Действие"
#: ../src/InputManager.cpp:46
msgid "Space (Record)"
msgstr "Пробел (запись)"
#: ../src/InputManager.cpp:46
msgid "Cancel recording"
-msgstr ""
+msgstr "Отменить запись"
#: ../src/InputManager.cpp:47
msgid "Escape"
msgstr "Выход"
#: ../src/InputManager.cpp:47
msgid "Tab (View shadow/Level prop.)"
msgstr "Tab (смотреть на тень/настройки уровня)"
#: ../src/InputManager.cpp:47
msgid "Save game (in editor)"
msgstr "Сохранить игру (в редакторе)"
#: ../src/InputManager.cpp:47
msgid "Load game"
msgstr "Загрузить игру"
#: ../src/InputManager.cpp:47
msgid "Swap (in editor)"
msgstr "Перестановка (в редакторе)"
#: ../src/InputManager.cpp:48
msgid "Teleport (in editor)"
msgstr "Телепорт (в редакторе)"
#: ../src/InputManager.cpp:48
msgid "Suicide (in editor)"
msgstr "Смерть (в редакторе)"
#: ../src/InputManager.cpp:48
msgid "Shift (in editor)"
msgstr "Сдвиг (в редакторе)"
#: ../src/InputManager.cpp:48
msgid "Next block type (in Editor)"
msgstr "Следующий вид блока (в редакторе)"
#: ../src/InputManager.cpp:49
msgid "Previous block type (in editor)"
msgstr "Предыдущий вид блока (в редакторе)"
#: ../src/InputManager.cpp:49
msgid "Select (in menu)"
msgstr "Выбрать (в меню)"
-# TRANSLAOTRS: This is used when the name of the key code is not found.
#: ../src/InputManager.cpp:156
#, c-format
msgid "(Key %d)"
msgstr ""
#: ../src/InputManager.cpp:163
#, c-format
msgid "Joystick axis %d %s"
msgstr ""
#: ../src/InputManager.cpp:166
#, c-format
msgid "Joystick button %d"
msgstr ""
#: ../src/InputManager.cpp:171
#, c-format
msgid "Joystick hat %d left"
msgstr ""
#: ../src/InputManager.cpp:174
#, c-format
msgid "Joystick hat %d right"
msgstr ""
#: ../src/InputManager.cpp:177
#, c-format
msgid "Joystick hat %d up"
msgstr ""
#: ../src/InputManager.cpp:180
#, c-format
msgid "Joystick hat %d down"
msgstr ""
-# TRANSLAOTRS: This is used when the JOYSTICK_HAT value is invalid.
#: ../src/InputManager.cpp:185
#, c-format
msgid "Joystick hat %d %d"
msgstr ""
#: ../src/InputManager.cpp:202
msgid "OR"
msgstr ""
#: ../src/InputManager.cpp:416
msgid "Select an item and press a key to change it."
msgstr ""
#: ../src/InputManager.cpp:419
msgid "Press backspace to clear the selected item."
msgstr ""
#: ../src/LevelEditor.cpp:56
msgid "Block"
msgstr "Блок"
#: ../src/LevelEditor.cpp:56
msgid "Player Start"
msgstr "Точка начала"
#: ../src/LevelEditor.cpp:56
msgid "Shadow Start"
msgstr "Точка начала тени"
#: ../src/LevelEditor.cpp:57
msgid "Exit"
msgstr "Выход"
#: ../src/LevelEditor.cpp:57
msgid "Shadow Block"
msgstr "Блок для тени"
#: ../src/LevelEditor.cpp:57
msgid "Spikes"
msgstr "Шипы"
#: ../src/LevelEditor.cpp:58
msgid "Checkpoint"
msgstr "Контрольная точка"
#: ../src/LevelEditor.cpp:58 ../src/LevelEditSelect.cpp:312
msgid "Swap"
msgstr "Перестановка"
#: ../src/LevelEditor.cpp:58
msgid "Fragile"
-msgstr "Хрупкий блок"
+msgstr "Хрупкие блоки"
#: ../src/LevelEditor.cpp:59
msgid "Moving Block"
msgstr "Движущийся блок"
#: ../src/LevelEditor.cpp:59
msgid "Moving Shadow Block"
msgstr "Движущийся блок для тени"
#: ../src/LevelEditor.cpp:59
msgid "Moving Spikes"
msgstr "Движущиеся шипы"
#: ../src/LevelEditor.cpp:60
msgid "Teleporter"
msgstr "Телепорт"
#: ../src/LevelEditor.cpp:60
msgid "Button"
msgstr "Кнопка"
#: ../src/LevelEditor.cpp:60
msgid "Switch"
msgstr "Переключатель"
#: ../src/LevelEditor.cpp:61
msgid "Conveyor Belt"
msgstr "Конвейер"
#: ../src/LevelEditor.cpp:61
msgid "Shadow Conveyor Belt"
msgstr "Конвейер для тени"
#: ../src/LevelEditor.cpp:61
msgid "Notification Block"
msgstr "Блок оповещения"
#: ../src/LevelEditor.cpp:61
msgid "Collectable"
msgstr "Ключ"
#: ../src/LevelEditor.cpp:61
msgid "Pushable"
msgstr ""
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:310
msgid "Select"
msgstr "Выбрать"
#: ../src/LevelEditor.cpp:65
msgid "Add"
msgstr "Добавить"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:311
msgid "Delete"
msgstr "Удалить"
#: ../src/LevelEditor.cpp:65 ../src/LevelPlaySelect.cpp:66
#: ../src/TitleMenu.cpp:43
msgid "Play"
msgstr "Играть"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:2852
msgid "Level settings"
msgstr "Настройки уровня"
#: ../src/LevelEditor.cpp:65
msgid "Save level"
msgstr "Сохранить уровень"
#: ../src/LevelEditor.cpp:65
msgid "Back to menu"
msgstr "Назад в меню"
#: ../src/LevelEditor.cpp:65
msgid "Configure"
msgstr "Настройки"
#: ../src/LevelEditor.cpp:84
#, c-format
msgid "%s (Scenery)"
msgstr ""
#: ../src/LevelEditor.cpp:267
msgid "Toggle"
msgstr "Переключать"
#: ../src/LevelEditor.cpp:270
msgid "Complete"
msgstr "Целый"
#: ../src/LevelEditor.cpp:271
msgid "One step"
msgstr "На два шага"
#: ../src/LevelEditor.cpp:272
msgid "Two steps"
msgstr "На один шаг"
#: ../src/LevelEditor.cpp:273
msgid "Gone"
msgstr "Разрушен"
#: ../src/LevelEditor.cpp:291
msgid "Negative infinity"
msgstr ""
#: ../src/LevelEditor.cpp:293
msgid "Zero"
msgstr ""
#: ../src/LevelEditor.cpp:295
msgid "Level size"
msgstr ""
#: ../src/LevelEditor.cpp:297
msgid "Positive infinity"
msgstr ""
#: ../src/LevelEditor.cpp:299
msgid "Default"
msgstr ""
#: ../src/LevelEditor.cpp:308
msgid "Deselect"
msgstr ""
#: ../src/LevelEditor.cpp:318 ../src/LevelEditor.cpp:1136
#, c-format
msgid "Horizontal repeat start: %s"
msgstr ""
#: ../src/LevelEditor.cpp:320 ../src/LevelEditor.cpp:1137
#, c-format
msgid "Horizontal repeat end: %s"
msgstr ""
#: ../src/LevelEditor.cpp:322 ../src/LevelEditor.cpp:1138
#, c-format
msgid "Vertical repeat start: %s"
msgstr ""
#: ../src/LevelEditor.cpp:324 ../src/LevelEditor.cpp:1139
#, c-format
msgid "Vertical repeat end: %s"
msgstr ""
#: ../src/LevelEditor.cpp:329 ../src/LevelEditor.cpp:1150
msgid "Custom scenery"
msgstr ""
#: ../src/LevelEditor.cpp:335 ../src/LevelEditor.cpp:600
#: ../src/LevelEditor.cpp:602
msgid "Visible"
msgstr ""
#: ../src/LevelEditor.cpp:344
msgid "Link"
msgstr ""
#: ../src/LevelEditor.cpp:345
msgid "Remove Links"
msgstr ""
#: ../src/LevelEditor.cpp:349 ../src/LevelEditor.cpp:624
#: ../src/LevelEditor.cpp:626
msgid "Automatic"
msgstr ""
#: ../src/LevelEditor.cpp:359 ../src/LevelEditor.cpp:649
#, c-format
msgid "Behavior: %s"
msgstr ""
#: ../src/LevelEditor.cpp:362
msgid "Path"
msgstr "Путь"
#: ../src/LevelEditor.cpp:363
msgid "Remove Path"
msgstr ""
#: ../src/LevelEditor.cpp:365 ../src/LevelEditor.cpp:371
#: ../src/LevelEditor.cpp:587 ../src/LevelEditor.cpp:589
msgid "Activated"
msgstr ""
#: ../src/LevelEditor.cpp:366 ../src/LevelEditor.cpp:612
#: ../src/LevelEditor.cpp:614
msgid "Looping"
msgstr ""
#: ../src/LevelEditor.cpp:372 ../src/LevelEditor.cpp:3526
msgid "Speed"
msgstr ""
#: ../src/LevelEditor.cpp:378 ../src/LevelEditor.cpp:668
#, c-format
msgid "State: %s"
msgstr ""
#: ../src/LevelEditor.cpp:382 ../src/LevelEditor.cpp:3511
msgid "Message"
msgstr ""
#: ../src/LevelEditor.cpp:384 ../src/LevelEditor.cpp:1202
#: ../src/LevelEditor.cpp:3825
msgid "Appearance"
msgstr ""
#: ../src/LevelEditor.cpp:389 ../src/LevelEditor.cpp:431
#: ../src/LevelEditor.cpp:715
msgid "Scripting"
msgstr ""
#: ../src/LevelEditor.cpp:402 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Background layer: %s"
msgstr ""
#: ../src/LevelEditor.cpp:409 ../src/LevelEditor.cpp:866
#: ../src/LevelEditor.cpp:884
msgid "Blocks layer"
msgstr ""
#: ../src/LevelEditor.cpp:417 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Foreground layer: %s"
msgstr ""
#: ../src/LevelEditor.cpp:423
msgid "Add new layer"
msgstr ""
#: ../src/LevelEditor.cpp:424
msgid "Delete selected layer"
msgstr ""
#: ../src/LevelEditor.cpp:425
msgid "Configure selected layer"
msgstr ""
#: ../src/LevelEditor.cpp:426
msgid "Move selected object to layer"
msgstr ""
#: ../src/LevelEditor.cpp:430 ../src/OptionsMenu.cpp:55
msgid "Settings"
msgstr "Настройки"
#: ../src/LevelEditor.cpp:463
msgid ""
"NOTE: the layers are sorted by name alphabetically.\n"
"The layer is background layer if its name is < 'f'\n"
"by dictionary order, otherwise it's foreground layer."
msgstr ""
#: ../src/LevelEditor.cpp:539
msgid "Notification block"
msgstr "Блок оповещения"
#: ../src/LevelEditor.cpp:545
msgid "Enter message here:"
msgstr "Введите сообщение:"
#: ../src/LevelEditor.cpp:646
msgid "Behavior"
msgstr ""
#: ../src/LevelEditor.cpp:665
msgid "State"
msgstr ""
#: ../src/LevelEditor.cpp:673
msgid "Conveyor belt speed"
msgstr ""
#: ../src/LevelEditor.cpp:679
msgid "Enter speed here:"
msgstr "Введите скорость:"
#: ../src/LevelEditor.cpp:690
msgid "NOTE: 1 Speed = 0.08 block/s"
msgstr ""
#: ../src/LevelEditor.cpp:721
msgid "Id:"
msgstr ""
#: ../src/LevelEditor.cpp:787
msgid "Level Scripting"
msgstr ""
#: ../src/LevelEditor.cpp:892
msgid "Add layer"
msgstr ""
#: ../src/LevelEditor.cpp:898
msgid "Enter the layer name:"
msgstr ""
#: ../src/LevelEditor.cpp:943
#, c-format
msgid "Are you sure you want to delete layer '%s'?"
msgstr ""
#: ../src/LevelEditor.cpp:944
msgid "Delete layer"
msgstr ""
#: ../src/LevelEditor.cpp:968
msgid "Layer settings"
msgstr ""
#: ../src/LevelEditor.cpp:974
msgid "Layer name:"
msgstr ""
#: ../src/LevelEditor.cpp:989
msgid "Layer moving speed (1 speed = 0.8 block/s):"
msgstr ""
#: ../src/LevelEditor.cpp:1010
msgid "Speed of following camera:"
msgstr ""
#: ../src/LevelEditor.cpp:1062
msgid "Move to layer"
msgstr ""
#: ../src/LevelEditor.cpp:1068
msgid "Enter the layer name (create new layer if necessary):"
msgstr ""
#: ../src/LevelEditor.cpp:1132
msgid "Repeat mode"
msgstr ""
#: ../src/LevelEditor.cpp:1156
msgid "Custom scenery:"
msgstr ""
#: ../src/LevelEditor.cpp:1219
msgid "(Use the default appearance for this block)"
msgstr ""
-# TRANSLATORS: Block name
-# TRANSLATORS: Context: Resize/Move ...
-# TRANSLATORS: Context: Add/Remove ...
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1465 ../src/LevelEditor.cpp:1707
#: ../src/LevelEditor.cpp:1723 ../src/LevelEditor.cpp:1772
#: ../src/LevelEditor.cpp:4400
msgid "Custom scenery block"
msgstr ""
#: ../src/LevelEditor.cpp:1673
msgid "Toolbox"
msgstr "Инструментарий"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Resize %s"
msgstr ""
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Move %s"
msgstr ""
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1713
#, c-format
msgid "Move %d object"
msgid_plural "Move %d objects"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Add %s"
msgstr ""
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Remove %s"
msgstr ""
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1729
#, c-format
msgid "Add %d object"
msgid_plural "Add %d objects"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1731
#, c-format
msgid "Remove %d object"
msgid_plural "Remove %d objects"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1739
#, c-format
msgid "Add path to %s"
msgstr ""
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1741
#, c-format
msgid "Remove a path point from %s"
msgstr ""
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1747
#, c-format
msgid "Remove all paths from %s"
msgstr ""
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1753
#, c-format
msgid "Add link from %s to %s"
msgstr ""
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1759
#, c-format
msgid "Remove all links from %s"
msgstr ""
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1766
msgid "Modify the %2 property of %1"
msgstr ""
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1818
#, c-format
msgid "Edit the script of %s"
msgstr ""
-# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1821
msgid "Edit the script of level"
msgstr ""
#: ../src/LevelEditor.cpp:2146 ../src/LevelEditor.cpp:2226
msgid "The level has unsaved changes."
msgstr ""
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Are you sure you want to quit?"
msgstr "Вы действительно хотите выйти?"
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Quit prompt"
msgstr "Выход"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
#, c-format
msgid "Level \"%s\" saved"
msgstr "Уровень \"%s\" сохранен"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
msgid "Saved"
msgstr "Сохранено"
#: ../src/LevelEditor.cpp:2859 ../src/LevelEditSelect.cpp:208
msgid "Name:"
msgstr "Название:"
#: ../src/LevelEditor.cpp:2866
msgid "Theme:"
msgstr "Тема:"
#: ../src/LevelEditor.cpp:2873
msgid "Examples: %DATA%/themes/classic"
msgstr ""
#: ../src/LevelEditor.cpp:2875
msgid "or %USER%/themes/Orange"
msgstr ""
#: ../src/LevelEditor.cpp:2878
msgid "Music:"
msgstr ""
#: ../src/LevelEditor.cpp:2887
msgid "Target time (s):"
msgstr "Необходимое время (с):"
#: ../src/LevelEditor.cpp:2903
msgid "Target recordings:"
msgstr "Необходимо записей:"
#: ../src/LevelEditor.cpp:2919
msgid "Restart level editor is required"
msgstr ""
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3718
#: ../src/LevelEditor.cpp:3739
msgid "Please enter a layer name."
msgstr ""
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3683
#: ../src/LevelEditor.cpp:3718 ../src/LevelEditor.cpp:3722
#: ../src/LevelEditor.cpp:3739 ../src/LevelEditor.cpp:3743
#: ../src/LevelEditSelect.cpp:644 ../src/LevelEditSelect.cpp:683
#: ../src/LevelEditSelect.cpp:688 ../src/LevelEditSelect.cpp:693
#: ../src/LevelEditSelect.cpp:698 ../src/LevelEditSelect.cpp:796
msgid "Error"
msgstr "Ошибка"
#: ../src/LevelEditor.cpp:3683 ../src/LevelEditor.cpp:3722
#, c-format
msgid "The layer '%s' already exists."
msgstr ""
#: ../src/LevelEditor.cpp:3743
msgid "Source and destination layers are the same."
msgstr ""
#: ../src/LevelEditor.cpp:3760
msgid "Scenery"
msgstr ""
#: ../src/LevelEditor.cpp:4190 ../src/LevelEditor.cpp:4218
#, c-format
msgid "Speed: %d = %0.2f block/s"
msgstr ""
#: ../src/LevelEditor.cpp:4203
msgid "Stop at this point"
msgstr ""
#: ../src/LevelEditor.cpp:4208
#, c-format
msgid "Pause: %d = %0.3fs"
msgstr ""
#: ../src/LevelEditSelect.cpp:41 ../src/TitleMenu.cpp:45
msgid "Map Editor"
msgstr "Редактор карт"
#: ../src/LevelEditSelect.cpp:66
msgid "New Levelpack"
msgstr "Новый набор уровней"
#: ../src/LevelEditSelect.cpp:71
msgid "Pack Properties"
msgstr "Свойства набора"
#: ../src/LevelEditSelect.cpp:76
msgid "Remove Pack"
msgstr "Удалить набор"
#: ../src/LevelEditSelect.cpp:81
msgid "Move Map"
msgstr "Переместить карту"
#: ../src/LevelEditSelect.cpp:89
msgid "Remove Map"
msgstr "Удалить карту"
#: ../src/LevelEditSelect.cpp:94
msgid "Edit Map"
msgstr "Редактировать карту"
#: ../src/LevelEditSelect.cpp:205
msgid "Properties"
msgstr "Свойства"
#: ../src/LevelEditSelect.cpp:217
msgid "Description:"
msgstr "Описание:"
#: ../src/LevelEditSelect.cpp:226
msgid "Congratulation text:"
msgstr "Текст поздравления:"
#: ../src/LevelEditSelect.cpp:235
msgid "Music list:"
msgstr ""
#: ../src/LevelEditSelect.cpp:265 ../src/LevelEditSelect.cpp:485
msgid "Add level"
msgstr "Добавить уровень"
#: ../src/LevelEditSelect.cpp:268
msgid "File name:"
msgstr "Название файла:"
#: ../src/LevelEditSelect.cpp:293
msgid "Move level"
msgstr "Переместить уровень"
#: ../src/LevelEditSelect.cpp:296
msgid "Level: "
msgstr "Уровень:"
#: ../src/LevelEditSelect.cpp:310
msgid "Before"
msgstr "До:"
#: ../src/LevelEditSelect.cpp:311
msgid "After"
msgstr "После:"
#: ../src/LevelEditSelect.cpp:368 ../src/LevelPlaySelect.cpp:124
msgid "Individual levels which are not contained in any level packs"
msgstr ""
#: ../src/LevelEditSelect.cpp:577
#, c-format
msgid "Are you sure remove the level pack '%s'?"
msgstr ""
#: ../src/LevelEditSelect.cpp:577 ../src/LevelEditSelect.cpp:607
msgid "Remove prompt"
msgstr "Удаление"
#: ../src/LevelEditSelect.cpp:607
#, c-format
msgid "Are you sure remove the map '%s'?"
msgstr ""
#: ../src/LevelEditSelect.cpp:644
msgid "Levelpack name cannot be empty."
msgstr ""
#: ../src/LevelEditSelect.cpp:683
#, c-format
msgid "The levelpack directory '%s' already exists!"
msgstr ""
#: ../src/LevelEditSelect.cpp:688
#, c-format
msgid "Unable to create levelpack directory '%s'!"
msgstr ""
#: ../src/LevelEditSelect.cpp:693
#, c-format
msgid "The levelpack file '%s' already exists!"
msgstr ""
#: ../src/LevelEditSelect.cpp:698
#, c-format
msgid "Unable to create levelpack file '%s'!"
msgstr ""
#: ../src/LevelEditSelect.cpp:758
msgid "No file name given for the new level."
msgstr "Новому уровню не было присвоено имени файла."
#: ../src/LevelEditSelect.cpp:758
msgid "Missing file name"
msgstr "Отсутствует имя файла"
#: ../src/LevelEditSelect.cpp:796
#, c-format
msgid "The file %s already exists."
msgstr ""
#: ../src/LevelEditSelect.cpp:849
msgid "The entered level number isn't valid!"
msgstr "Введенный номер уровня некорректен!"
#: ../src/LevelEditSelect.cpp:849
msgid "Illegal number"
msgstr "Некорректный номер"
#: ../src/LevelInfoRender.cpp:19
msgid "Choose a level"
msgstr "Выберите уровень"
#: ../src/LevelInfoRender.cpp:20
msgid "Time:"
msgstr "Время:"
#: ../src/LevelInfoRender.cpp:21 ../src/StatisticsScreen.cpp:259
msgid "Recordings:"
msgstr "Записей:"
#: ../src/LevelPackManager.cpp:124
msgid "Custom Levels"
msgstr ""
#: ../src/LevelPlaySelect.cpp:41
msgid "Select Level"
msgstr "Выберите уровень"
-# TRANSLATORS: Used for button which clear any level progress like unlocked
-# levels and highscores.
#: ../src/OptionsMenu.cpp:66
msgid "Clear Progress"
msgstr "Очистить прогресс"
#: ../src/OptionsMenu.cpp:109
msgid "General"
msgstr ""
#: ../src/OptionsMenu.cpp:110
msgid "Controls"
msgstr ""
#: ../src/OptionsMenu.cpp:121
msgid "Music"
msgstr "Музыка"
#: ../src/OptionsMenu.cpp:129
msgid "Sound"
msgstr "Звуки"
#: ../src/OptionsMenu.cpp:137
msgid "Resolution"
msgstr "Разрешение"
#: ../src/OptionsMenu.cpp:177
msgid "Language"
msgstr "Язык"
-# TRANSLATORS: as detect user's language automatically
#: ../src/OptionsMenu.cpp:185
msgid "Auto-Detect"
msgstr "Автоопределение"
#: ../src/OptionsMenu.cpp:209
msgid "Theme"
msgstr "Тема"
#: ../src/OptionsMenu.cpp:247
msgid "Internet proxy"
msgstr "Интернет-прокси"
#: ../src/OptionsMenu.cpp:256
msgid "Fullscreen"
msgstr "На весь экран"
#: ../src/OptionsMenu.cpp:261
msgid "Quick record"
msgstr ""
#: ../src/OptionsMenu.cpp:266
msgid "Internet"
msgstr "Интернет"
#: ../src/OptionsMenu.cpp:271
msgid "Fade transition"
msgstr ""
#: ../src/OptionsMenu.cpp:294
msgid "Save Changes"
msgstr "Сохранить изменения"
#: ../src/OptionsMenu.cpp:513
msgid "Do you really want to reset level progress?"
msgstr "Вы действительно хотите очистить прогресс?"
#: ../src/OptionsMenu.cpp:513
msgid "Warning"
msgstr "Предупреждение"
#: ../src/StatisticsManager.cpp:386
msgid "New achievement:"
msgstr ""
#: ../src/StatisticsManager.cpp:394
#, c-format
msgid "Achieved on %s"
msgstr ""
#: ../src/StatisticsManager.cpp:400
msgid "Unknown achievement"
msgstr ""
#: ../src/StatisticsManager.cpp:406
#, c-format
msgid "Achieved %1.0f%%"
msgstr ""
#: ../src/StatisticsManager.cpp:410
msgid "Not achieved"
msgstr ""
#: ../src/StatisticsScreen.cpp:57 ../src/TitleMenu.cpp:55
msgid "Achievements and Statistics"
msgstr ""
#: ../src/StatisticsScreen.cpp:166
msgid "Achievements"
msgstr ""
#: ../src/StatisticsScreen.cpp:167
msgid "Statistics"
msgstr ""
#: ../src/StatisticsScreen.cpp:234
msgid "Total"
msgstr ""
#: ../src/StatisticsScreen.cpp:246
msgid "Traveling distance (m)"
msgstr ""
#: ../src/StatisticsScreen.cpp:247
msgid "Jump times"
msgstr ""
#: ../src/StatisticsScreen.cpp:248
msgid "Die times"
msgstr ""
#: ../src/StatisticsScreen.cpp:249
msgid "Squashed times"
msgstr ""
#: ../src/StatisticsScreen.cpp:260
msgid "Switch pulled times:"
msgstr ""
#: ../src/StatisticsScreen.cpp:261
msgid "Swap times:"
msgstr ""
#: ../src/StatisticsScreen.cpp:262
msgid "Save times:"
msgstr ""
#: ../src/StatisticsScreen.cpp:263
msgid "Load times:"
msgstr ""
#: ../src/StatisticsScreen.cpp:268
msgid "Completed levels:"
msgstr ""
#: ../src/StatisticsScreen.cpp:306
msgid "In-game time:"
msgstr ""
#: ../src/StatisticsScreen.cpp:308
msgid "Level editing time:"
msgstr ""
#: ../src/StatisticsScreen.cpp:310
msgid "Created levels:"
msgstr ""
#: ../src/TitleMenu.cpp:44
msgid "Options"
msgstr "Опции"
#: ../src/TitleMenu.cpp:47
msgid "Quit"
msgstr "Выход"
#: ../src/TitleMenu.cpp:131
msgid "Enable internet in order to install addons."
msgstr "Чтобы устанавливать дополнения, разрешите подключение к интернету."
#: ../src/TitleMenu.cpp:131
msgid "Internet disabled"
msgstr "Подключение к интернету запрещено"
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Return"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Escape"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Backspace"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Tab"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Space"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "CapsLock"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PrintScreen"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "ScrollLock"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Pause"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Insert"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Home"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PageUp"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Delete"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "End"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "PageDown"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Down"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Up"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Numlock"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "SysReq"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Ctrl"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Shift"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Alt"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left GUI"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Ctrl"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Shift"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Alt"
msgstr ""
-# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right GUI"
msgstr ""
diff --git a/data/themes/Cloudscape/tiles/tiles.png b/data/themes/Cloudscape/tiles/tiles.png
index 898ae3a..a7b06e2 100644
Binary files a/data/themes/Cloudscape/tiles/tiles.png and b/data/themes/Cloudscape/tiles/tiles.png differ
diff --git a/src/Addons.cpp b/src/Addons.cpp
index f17d0c1..8d98c9a 100644
--- a/src/Addons.cpp
+++ b/src/Addons.cpp
@@ -1,1080 +1,1082 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Addons.h"
#include "GameState.h"
#include "Functions.h"
#include "FileManager.h"
#include "GUIObject.h"
#include "GUIOverlay.h"
#include "GUIScrollBar.h"
#include "GUITextArea.h"
#include "GUIListBox.h"
#include "POASerializer.h"
#include "LevelPackManager.h"
#include "InputManager.h"
#include "ThemeManager.h"
#include <string>
#include <sstream>
#include <iostream>
#include "libs/tinyformat/tinyformat.h"
#include <SDL.h>
#include <SDL_ttf.h>
using namespace std;
static const char* predefinedCategories[] = {
"levels", __("Levels"), __("Single level which usually contain demanding puzzles"),
"levelpacks", __("Levelpacks"), __("Collection of levels with the same author or style"),
"themes", __("Themes"), __("Give every block and background a new look and feel"),
NULL,
};
static std::map<std::string, std::string> categoryNameMap;
static std::map<std::string, std::string> categoryDescriptionMap;
Addons::Addons(SDL_Renderer &renderer, ImageManager &imageManager):selected(NULL){
//Render the title.
title = titleTextureFromText(renderer, _("Addons"), objThemes.getTextColor(false), SCREEN_WIDTH);
//Load placeholder addon icons and screenshot.
addonIcon["levels"] = imageManager.loadImage(getDataPath() + "/gfx/addon1.png");
addonIcon["levelpacks"] = imageManager.loadImage(getDataPath() + "/gfx/addon2.png");
addonIcon["themes"] = imageManager.loadImage(getDataPath() + "/gfx/addon3.png");
addonIcon[std::string()] = imageManager.loadImage(getDataPath() + "/gfx/addon0.png");
//Load predefined categories.
if (categoryNameMap.empty()) {
for (int i = 0; predefinedCategories[i]; i += 3) {
categoryNameMap[predefinedCategories[i]] = predefinedCategories[i + 1];
categoryDescriptionMap[predefinedCategories[i]] = predefinedCategories[i + 2];
}
}
screenshot=imageManager.loadTexture(getDataPath()+"/gfx/screenshot.png", renderer);
//Open the addons file in the user cache path for writing (downloading) to.
FILE* addon=fopen((getUserPath(USER_CACHE)+"addons").c_str(),"wb");
//Clear the GUI if any.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Show a loading screen
{
int w = 0, h = 0;
SDL_GetRendererOutputSize(&renderer, &w, &h);
SDL_Color fg = { 255, 255, 255, 0 };
TexturePtr loadingTexture = titleTextureFromText(renderer, _("Loading..."), fg, w);
SDL_Rect loadingRect = rectFromTexture(*loadingTexture);
loadingRect.x = (w - loadingRect.w) / 2;
loadingRect.y = (h - loadingRect.h) / 2;
+ SDL_SetRenderDrawColor(&renderer, 0, 0, 0, 255);
+ SDL_RenderClear(&renderer);
SDL_RenderCopy(&renderer, loadingTexture.get(), NULL, &loadingRect);
SDL_RenderPresent(&renderer);
SDL_RenderClear(&renderer);
}
//Try to get(download) the addonsList.
if(getAddonsList(addon, renderer, imageManager)==false){
//It failed so we show the error message.
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
GUIObject* obj=new GUILabel(imageManager,renderer,90,96,200,32,_("Unable to initialize addon menu:"));
obj->name="lbl";
GUIObjectRoot->addChild(obj);
obj=new GUILabel(imageManager,renderer,120,130,200,32,error.c_str());
obj->name="lbl";
GUIObjectRoot->addChild(obj);
obj=new GUIButton(imageManager,renderer,90,550,200,32,_("Back"));
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
return;
}
//Now create the GUI.
createGUI(renderer, imageManager);
}
Addons::~Addons(){
//If the GUIObjectRoot exist delete it.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
void Addons::createGUI(SDL_Renderer& renderer, ImageManager& imageManager){
//Downloaded the addons file now we can create the GUI.
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Create list of categories
categoryList = new GUISingleLineListBox(imageManager, renderer, (SCREEN_WIDTH - 500) / 2, 100, 500, 32);
categoryList->name="lstTabs";
//Loop through the categories and add them to the list.
//FIXME: Hack for easy detecting which categories there are.
{
set<string> categories;
for (const auto& a : addons) {
categories.insert(a.type);
}
for (const auto& c : categories) {
auto it = categoryNameMap.find(c);
categoryList->addItem(c, it == categoryNameMap.end() ? c.c_str() : _(it->second));
}
}
categoryList->value=0;
categoryList->eventCallback=this;
GUIObjectRoot->addChild(categoryList);
//category description
categoryDescription = new GUILabel(imageManager, renderer, 0, 136, SCREEN_WIDTH, 32, "", 0, true, true, GUIGravityCenter);
if (categoryList->value >= 0 && categoryList->value < (int)categoryList->item.size()) {
auto it = categoryDescriptionMap.find(categoryList->item[categoryList->value].first);
if (it != categoryDescriptionMap.end()) categoryDescription->caption = _(it->second);
}
GUIObjectRoot->addChild(categoryDescription);
//Create the list for the addons.
//By default levels will be selected.
list=new GUIListBox(imageManager,renderer,SCREEN_WIDTH*0.1,176,SCREEN_WIDTH*0.8,SCREEN_HEIGHT-228);
addonsToList(categoryList->getName(), renderer, imageManager);
list->name="lstAddons";
list->clickEvents=true;
list->eventCallback=this;
list->value=-1;
GUIObjectRoot->addChild(list);
type="levels";
//The back button.
GUIObject* obj=new GUIButton(imageManager,renderer,20,20,-1,32,_("Back"));
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
}
bool Addons::getAddonsList(FILE* file, SDL_Renderer& renderer, ImageManager& imageManager){
//First we download the file.
if(downloadFile(getSettings()->getValue("addon_url"),file)==false){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: unable to download addons file!"<<endl;
error=_("ERROR: unable to download addons file!");
return false;
}
fclose(file);
//Load the downloaded file.
ifstream addonFile;
addonFile.open((getUserPath(USER_CACHE)+"addons").c_str());
if(!addonFile.good()) {
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: unable to load addon_list file!"<<endl;
/// TRANSLATORS: addon_list is the name of a file and should not be translated.
error=_("ERROR: unable to load addon_list file!");
return false;
}
//Parse the addonsfile.
TreeStorageNode obj;
{
POASerializer objSerializer;
if(!objSerializer.readNode(addonFile,&obj,true)){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Invalid file format of addons file!"<<endl;
error=_("ERROR: Invalid file format of addons file!");
return false;
}
}
//Check the addon version in the addons list.
int version=0;
if(!obj.attributes["version"].empty())
version=atoi(obj.attributes["version"][0].c_str());
if(version<MIN_VERSION || version>MAX_VERSION){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Addon list version is unsupported! (received: "<<version<<" supported:"<<MIN_VERSION<<"-"<<MAX_VERSION<<")"<<endl;
error=_("ERROR: Addon list version is unsupported!");
return false;
}
//Also load the installed_addons file.
ifstream iaddonFile;
iaddonFile.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
if(!iaddonFile) {
//The installed_addons file doesn't exist, so we create it.
ofstream iaddons;
iaddons.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
iaddons<<" "<<endl;
iaddons.close();
//Also load the installed_addons file.
iaddonFile.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
if(!iaddonFile) {
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Unable to create the installed_addons file."<<endl;
/// TRANSLATORS: installed_addons is the name of a file and should not be translated.
error=_("ERROR: Unable to create the installed_addons file.");
return false;
}
}
//And parse the installed_addons file.
TreeStorageNode obj1;
{
POASerializer objSerializer;
if(!objSerializer.readNode(iaddonFile,&obj1,true)){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Invalid file format of the installed_addons!"<<endl;
error=_("ERROR: Invalid file format of the installed_addons!");
return false;
}
}
//Fill the vector.
fillAddonList(obj,obj1, renderer, imageManager);
//Close the files.
iaddonFile.close();
addonFile.close();
return true;
}
void Addons::fillAddonList(TreeStorageNode &objAddons, TreeStorageNode &objInstalledAddons,
SDL_Renderer& renderer, ImageManager& imageManager){
//Loop through the blocks of the addons file.
//These should contain the types levels, levelpacks, themes.
for(unsigned int i=0;i<objAddons.subNodes.size();i++){
TreeStorageNode* block=objAddons.subNodes[i];
if(block==NULL) continue;
//Check what kind of block it is, only category at the moment.
if(block->name=="category" && block->value.size()>0){
string type=block->value[0];
//Now loop the entries(subNodes) of the block.
for(unsigned int i=0;i<block->subNodes.size();i++){
TreeStorageNode* entry=block->subNodes[i];
if(entry==NULL) continue;
if(entry->name=="entry" && entry->value.size()==1){
//The entry is valid so create a new Addon.
Addon addon;
addon.icon=nullptr;
addon.screenshot=nullptr;
addon.type=type;
addon.name=entry->value[0];
addon.version = 0;
addon.installedVersion = 0;
if(!entry->attributes["file"].empty())
addon.file=entry->attributes["file"][0];
if(!entry->attributes["author"].empty())
addon.author=entry->attributes["author"][0];
if(!entry->attributes["description"].empty())
addon.description=entry->attributes["description"][0];
if(!entry->attributes["license"].empty())
addon.license=entry->attributes["license"][0];
if(!entry->attributes["website"].empty())
addon.website=entry->attributes["website"][0];
if(entry->attributes["icon"].size()>1){
//There are (at least) two values, the url to the icon and its md5sum used for caching.
addon.icon=loadCachedImage(
entry->attributes["icon"][0].c_str(),
entry->attributes["icon"][1].c_str(),
imageManager
);
}
if(entry->attributes["screenshot"].size()>1){
//There are (at least) two values, the url to the screenshot and its md5sum used for caching.
addon.screenshot=loadCachedTexture(
entry->attributes["screenshot"][0].c_str(),
entry->attributes["screenshot"][1].c_str(),
renderer,
imageManager
);
}
if(!entry->attributes["version"].empty())
addon.version=atoi(entry->attributes["version"][0].c_str());
addon.upToDate=false;
addon.installed=false;
//Check if the addon is already installed.
for(unsigned int i=0;i<objInstalledAddons.subNodes.size();i++){
TreeStorageNode* installed=objInstalledAddons.subNodes[i];
if(installed==NULL) continue;
if(installed->name=="entry" && installed->value.size()==3){
if(addon.type.compare(installed->value[0])==0 && addon.name.compare(installed->value[1])==0) {
addon.installed=true;
addon.installedVersion=atoi(installed->value[2].c_str());
if(addon.installedVersion>=addon.version) {
addon.upToDate=true;
}
//Read the dependencies and content from the file.
for(unsigned int j=0;j<installed->subNodes.size();j++){
if(installed->subNodes[j]->name=="content"){
TreeStorageNode* obj=installed->subNodes[j];
for(unsigned int k=0;k<obj->subNodes.size();k++){
if(obj->subNodes[k]->value.size()==1)
addon.content.push_back(pair<string,string>(obj->subNodes[k]->name,obj->subNodes[k]->value[0]));
}
}else if(installed->subNodes[j]->name=="dependencies"){
TreeStorageNode* obj=installed->subNodes[j];
for(unsigned int k=0;k<obj->subNodes.size();k++){
if(obj->subNodes[k]->value.size()==1)
addon.dependencies.push_back(pair<string,string>(obj->subNodes[k]->name,obj->subNodes[k]->value[0]));
}
}
}
}
}
}
//Finally put him in the list.
addons.push_back(addon);
}
}
}
}
}
void Addons::addonsToList(const std::string &type, SDL_Renderer& renderer, ImageManager&){
//Clear the list.
list->clearItems();
//Loop through the addons.
for(unsigned int i=0;i<addons.size();i++) {
//Make sure the addon is of the requested type.
if(addons[i].type!=type)
continue;
const Addon& addon=addons[i];
string entry=addon.name+" by "+addon.author;
if(addon.installed){
if(addon.upToDate){
entry+=" *";
}else{
entry+=" +";
}
}
SurfacePtr surf = createSurface(list->width,74);
//Check if there's an icon for the addon.
if(addon.icon){
applySurface(5, 5, addon.icon, surf.get(), NULL);
}else{
auto it = addonIcon.find(type);
if (it == addonIcon.end()) it = addonIcon.find(std::string());
assert(it != addonIcon.end());
applySurface(5, 5, it->second, surf.get(), NULL);
}
SDL_Surface* nameSurf=TTF_RenderUTF8_Blended(fontGUI,addon.name.c_str(),objThemes.getTextColor(true));
SDL_SetSurfaceAlphaMod(nameSurf,0xFF);
applySurface(74,-1,nameSurf,surf.get(),NULL);
SDL_FreeSurface(nameSurf);
/// TRANSLATORS: indicates the author of an addon.
string authorLine = tfm::format(_("by %s"),addon.author);
SDL_Surface* authorSurf=TTF_RenderUTF8_Blended(fontText,authorLine.c_str(),objThemes.getTextColor(true));
SDL_SetSurfaceAlphaMod(authorSurf,0xFF);
applySurface(74,43,authorSurf,surf.get(),NULL);
SDL_FreeSurface(authorSurf);
if(addon.installed){
if(addon.upToDate){
SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Installed"),objThemes.getTextColor(true));
SDL_SetSurfaceAlphaMod(infoSurf,0xFF);
applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf.get(),NULL);
SDL_FreeSurface(infoSurf);
}else{
SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Updatable"),objThemes.getTextColor(true));
SDL_SetSurfaceAlphaMod(infoSurf,0xFF);
applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf.get(),NULL);
SDL_FreeSurface(infoSurf);
}
}else{
SDL_Color c = objThemes.getTextColor(true);
c.r = c.r / 2 + 128;
c.g = c.g / 2 + 128;
c.b = c.b / 2 + 128;
SDL_Surface* infoSurf = TTF_RenderUTF8_Blended(fontText, _("Not installed"), c);
SDL_SetSurfaceAlphaMod(infoSurf,0xFF);
applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf.get(),NULL);
SDL_FreeSurface(infoSurf);
}
list->addItem(renderer,entry,textureFromSurface(renderer,std::move(surf)));
}
}
bool Addons::saveInstalledAddons(){
//Open the file.
ofstream iaddons;
iaddons.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
if(!iaddons) return false;
TreeStorageNode installed;
//Loop through all the addons.
vector<Addon>::iterator it;
for(it=addons.begin();it!=addons.end();++it){
//Check if the level is installed or not.
if(it->installed) {
TreeStorageNode *entry=new TreeStorageNode;
entry->name="entry";
entry->value.push_back(it->type);
entry->value.push_back(it->name);
char version[64];
sprintf(version,"%d",it->installedVersion);
entry->value.push_back(version);
//Now add a subNode for each content.
TreeStorageNode* content=new TreeStorageNode;
content->name="content";
for(unsigned int i=0;i<it->content.size();i++){
TreeStorageNode* contentEntry=new TreeStorageNode;
contentEntry->name=it->content[i].first;
contentEntry->value.push_back(it->content[i].second);
//Add the content node to the entry node.
content->subNodes.push_back(contentEntry);
}
entry->subNodes.push_back(content);
//Now add a sub node for the dependencies.
TreeStorageNode* deps=new TreeStorageNode;
deps->name="dependencies";
for(unsigned int i=0;i<it->dependencies.size();i++){
TreeStorageNode* depsEntry=new TreeStorageNode;
depsEntry->name=it->dependencies[i].first;
depsEntry->value.push_back(it->dependencies[i].second);
//Add the content node to the entry node.
deps->subNodes.push_back(depsEntry);
}
entry->subNodes.push_back(deps);
//And add the entry to the top node.
installed.subNodes.push_back(entry);
}
}
//And write away the file.
POASerializer objSerializer;
objSerializer.writeNode(&installed,iaddons,true,true);
return true;
}
SharedTexture Addons::loadCachedTexture(const char* url,const char* md5sum,
SDL_Renderer& renderer, ImageManager& imageManager){
//Check if the image is cached.
string imageFile=getUserPath(USER_CACHE)+"images/"+md5sum;
if(fileExists(imageFile.c_str())){
//It is, so load the image.
return imageManager.loadTexture(imageFile, renderer);
}else{
//Download the image.
FILE* file=fopen(imageFile.c_str(),"wb");
//Downloading failed.
if(!downloadFile(url,file)){
cerr<<"ERROR: Unable to download image from "<<url<<endl;
fclose(file);
return NULL;
}
fclose(file);
//Load the image.
return imageManager.loadTexture(imageFile, renderer);
}
}
SDL_Surface* Addons::loadCachedImage(const char* url, const char* md5sum,
ImageManager& imageManager){
//Check if the image is cached.
string imageFile = getUserPath(USER_CACHE) + "images/" + md5sum;
if (fileExists(imageFile.c_str())){
//It is, so load the image.
return imageManager.loadImage(imageFile);
} else{
//Download the image.
FILE* file = fopen(imageFile.c_str(), "wb");
//Downloading failed.
if (!downloadFile(url, file)){
cerr << "ERROR: Unable to download image from " << url << endl;
fclose(file);
return NULL;
}
fclose(file);
//Load the image.
return imageManager.loadImage(imageFile);
}
}
void Addons::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//Check if we should quit.
if(event.type==SDL_QUIT){
//Save the installed addons before exiting.
saveInstalledAddons();
setNextState(STATE_EXIT);
}
//Check if escape is pressed, if so return to the main menu.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
//Check horizontal movement
int value = categoryList->value;
if (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT)){
isKeyboardOnly = true;
value++;
if (value >= (int)categoryList->item.size()) value = 0;
} else if (inputMgr.isKeyDownEvent(INPUTMGR_LEFT)){
isKeyboardOnly = true;
value--;
if (value < 0) value = categoryList->item.size() - 1;
}
if (value >= 0 && value < (int)categoryList->item.size()) {
if (categoryList->value != value) {
categoryList->value = value;
GUIEventCallback_OnEvent(imageManager, renderer, categoryList->name, categoryList, GUIEventChange);
return;
}
//Check vertical movement
if (inputMgr.isKeyDownEvent(INPUTMGR_UP)){
isKeyboardOnly = true;
list->value--;
if (list->value < 0) list->value = 0;
//FIXME: ad-hoc stupid code
list->scrollScrollbar(0xC0000000);
list->scrollScrollbar(list->value);
} else if (inputMgr.isKeyDownEvent(INPUTMGR_DOWN)){
isKeyboardOnly = true;
list->value++;
if (list->value >= (int)list->item.size()) list->value = list->item.size() - 1;
//FIXME: ad-hoc stupid code
list->scrollScrollbar(0xC0000000);
list->scrollScrollbar(list->value);
}
if (isKeyboardOnly && inputMgr.isKeyDownEvent(INPUTMGR_SELECT) && list->value >= 0 && list->value<(int)list->item.size()) {
GUIEventCallback_OnEvent(imageManager, renderer, list->name, list, GUIEventChange); // ???
GUIEventCallback_OnEvent(imageManager, renderer, list->name, list, GUIEventClick);
return;
}
}
}
void Addons::logic(ImageManager&, SDL_Renderer&){}
void Addons::render(ImageManager&, SDL_Renderer& renderer){
//Draw background.
objThemes.getBackground(true)->draw(renderer);
//Draw the title.
drawTitleTexture(SCREEN_WIDTH, *title, renderer);
}
void Addons::resize(ImageManager& imageManager, SDL_Renderer& renderer){
//Delete the gui (if any).
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Now create a new one.
createGUI(renderer, imageManager);
}
void Addons::showAddon(ImageManager& imageManager, SDL_Renderer& renderer){
//Make sure an addon is selected.
if(!selected)
return;
//Skip next mouse up event since we're clicking a list box and showing a new window.
GUISkipNextMouseUpEvent = true;
//Create a root object.
GUIObject* root=new GUIFrame(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-400)/2,600,400,selected->name.c_str());
//Create the 'by creator' label.
GUIObject* obj=new GUILabel(imageManager,renderer,0,50,600,50,tfm::format(_("by %s"),selected->author).c_str(),0,true,true,GUIGravityCenter);
root->addChild(obj);
//Create the description text.
std::string s = tfm::format(_("Version: %d\n"), selected->version);
if (selected->installed) {
s += tfm::format(_("Installed version: %d\n"), selected->installedVersion);
}
if (!selected->license.empty()) {
s += tfm::format(_("License: %s\n"), appendURLToLicense(selected->license));
}
if (!selected->website.empty()) {
s += tfm::format(_("Website: %s\n"), selected->website);
}
s += '\n';
if (selected->description.empty()) {
s += _("(No descriptions provided)");
} else {
s += selected->description;
}
GUITextArea* description=new GUITextArea(imageManager,renderer,10,100,370,200);
description->setString(renderer, s, true);
description->editable=false;
description->onResize();
description->extractHyperlinks();
root->addChild(description);
//Create the screenshot image. (If a screenshot is missing, we use the default screenshot.)
GUIImage* img=new GUIImage(imageManager,renderer,390,100,200,150,selected->screenshot?selected->screenshot:screenshot);
root->addChild(img);
GUIButton *cancelButton;
//Add buttons depending on the installed/update status.
if(selected->installed && !selected->upToDate){
GUIObject* bRemove=new GUIButton(imageManager,renderer,root->width*0.97,350,-1,32,_("Remove"),0,true,true,GUIGravityRight);
bRemove->name="cmdRemove";
bRemove->eventCallback=this;
root->addChild(bRemove);
//Create a back button.
cancelButton = new GUIButton(imageManager, renderer, root->width*0.03, 350, -1, 32, _("Back"), 0, true, true, GUIGravityLeft);
cancelButton->name = "cmdCloseOverlay";
cancelButton->eventCallback = this;
root->addChild(cancelButton);
//Update widget sizes.
root->render(renderer, 0,0,false);
//Create a nicely centered button.
obj = new GUIButton(imageManager, renderer,
(int)floor((cancelButton->left + cancelButton->width + bRemove->left - bRemove->width)*0.5), 350,
-1, 32, _("Update"), 0, true, true, GUIGravityCenter);
obj->name="cmdUpdate";
obj->eventCallback=this;
root->addChild(obj);
}else{
if(!selected->installed){
obj=new GUIButton(imageManager,renderer,root->width*0.9,350,-1,32,_("Install"),0,true,true,GUIGravityRight);
obj->name="cmdInstall";
obj->eventCallback=this;
root->addChild(obj);
}else if(selected->upToDate){
obj=new GUIButton(imageManager,renderer,root->width*0.9,350,-1,32,_("Remove"),0,true,true,GUIGravityRight);
obj->name="cmdRemove";
obj->eventCallback=this;
root->addChild(obj);
}
//Create a back button.
cancelButton = new GUIButton(imageManager, renderer, root->width*0.1, 350, -1, 32, _("Back"), 0, true, true, GUIGravityLeft);
cancelButton->name = "cmdCloseOverlay";
cancelButton->eventCallback = this;
root->addChild(cancelButton);
}
new AddonOverlay(renderer, root, cancelButton, description, TabFocus | ReturnControls);
}
void Addons::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
if(name=="lstTabs"){
//Get the category type.
type=categoryList->getName();
//Get the description of current category.
auto it = categoryDescriptionMap.find(type);
if (it != categoryDescriptionMap.end()) categoryDescription->caption = _(it->second);
else categoryDescription->caption.clear();
//Get the list corresponding with the category and select the first entry.
addonsToList(type, renderer, imageManager);
list->value=-1;
//Call an event as if an entry in the addons listbox was clicked.
GUIEventCallback_OnEvent(imageManager, renderer, "lstAddons",list,GUIEventChange);
}else if(name=="lstAddons"){
//Check which type of event.
if(eventType==GUIEventChange){
//Get the addon struct that belongs to it.
Addon* addon=NULL;
//Make sure the addon list on screen isn't empty.
if (list->value >= 0 && list->value < (int)list->item.size()){
//Get the name of the (newly) selected entry.
string entry=list->getItem(list->value);
//Loop through the addons of the selected category.
for(unsigned int i=0;i<addons.size();i++){
//Make sure the addons are of the same type.
if(addons[i].type!=categoryList->getName())
continue;
string prefix=addons[i].name;
if(!entry.compare(0,prefix.size(),prefix)){
addon=&addons[i];
}
}
}
//Set the new addon as selected and unselect the list.
selected=addon;
if (!isKeyboardOnly) list->value = -1;
}else if(eventType==GUIEventClick){
//Make sure an addon is selected.
if(selected){
showAddon(imageManager,renderer);
}
}
}else if(name=="cmdBack"){
saveInstalledAddons();
setNextState(STATE_MENU);
}else if(name=="cmdCloseOverlay"){
//We can safely delete the GUIObjectRoot, since it's handled by the GUIOverlay.
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}else if(name=="cmdUpdate"){
//NOTE: This simply removes the addon and reinstalls it.
//The complete addon is downloaded either way so no need for checking what has been changed/added/removed/etc...
if(selected){
removeAddon(imageManager,renderer,selected);
installAddon(imageManager,renderer,selected);
}
addonsToList(categoryList->getName(), renderer, imageManager);
}else if(name=="cmdInstall"){
if(selected)
installAddon(imageManager,renderer,selected);
addonsToList(categoryList->getName(), renderer, imageManager);
}else if(name=="cmdRemove"){
//TODO: Check for dependencies.
//Loop through the addons to check if this addon is a dependency of another addon.
vector<Addon>::iterator it;
for(it=addons.begin();it!=addons.end();++it){
//Check if the addon has dependencies.
if(!it->dependencies.empty()){
vector<pair<string,string> >::iterator depIt;
for(depIt=it->dependencies.begin();depIt!=it->dependencies.end();++depIt){
if(depIt->first=="addon" && depIt->second==selected->name){
msgBox(imageManager,renderer,tfm::format(_("This addon can't be removed because it's needed by %s."),it->name),MsgBoxOKOnly,_("Dependency"));
return;
}
}
}
}
if(selected)
removeAddon(imageManager,renderer,selected);
addonsToList(categoryList->getName(), renderer, imageManager);
}
//NOTE: In case of install/remove/update we can delete the GUIObjectRoot, since it's managed by the GUIOverlay.
if(name=="cmdUpdate" || name=="cmdInstall" || name=="cmdRemove"){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
void Addons::removeAddon(ImageManager& imageManager,SDL_Renderer& renderer, Addon* addon){
//To remove an addon we loop over the content vector in the structure.
//NOTE: This should contain all INSTALLED content, if something failed during installation it isn't added.
for(unsigned int i=0;i<addon->content.size();i++){
//Check the type of content.
if(addon->content[i].first=="file"){
string file=getUserPath(USER_DATA)+addon->content[i].second;
//Check if the file exists.
if(!fileExists(file.c_str())){
cerr<<"WARNING: File '"<<file<<"' appears to have been removed already."<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: File '%s' appears to have been removed already."),file),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Remove the file.
if(!removeFile(file.c_str())){
cerr<<"ERROR: Unable to remove file '"<<file<<"'!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to remove file '%s'!"),file),MsgBoxOKOnly,_("Addon error"));
continue;
}
}else if(addon->content[i].first=="folder"){
string dir=getUserPath(USER_DATA)+addon->content[i].second;
//Check if the directory exists.
if(!dirExists(dir.c_str())){
cerr<<"WARNING: Directory '"<<dir<<"' appears to have been removed already."<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Directory '%s' appears to have been removed already."),dir),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Remove the directory.
if(!removeDirectory(dir.c_str())){
cerr<<"ERROR: Unable to remove directory '"<<dir<<"'!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to remove directory '%s'!"),dir),MsgBoxOKOnly,_("Addon error"));
continue;
}
}else if(addon->content[i].first=="level"){
string file=getUserPath(USER_DATA)+"levels/"+addon->content[i].second;
//Check if the level file exists.
if(!fileExists(file.c_str())){
cerr<<"WARNING: Level '"<<file<<"' appears to have been removed already."<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Level '%s' appears to have been removed already."),file),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Remove the level file.
if(!removeFile(file.c_str())){
cerr<<"ERROR: Unable to remove level '"<<file<<"'!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to remove level '%s'!"),file),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Also remove the level from the Levels levelpack.
LevelPack* levelsPack=getLevelPackManager()->getLevelPack(LEVELS_PATH);
for(int i=0;i<levelsPack->getLevelCount();i++){
if(levelsPack->getLevelFile(i)==file){
//Remove the level and break out of the loop.
levelsPack->removeLevel(i);
break;
}
}
}else if(addon->content[i].first=="levelpack"){
//FIXME: We assume no trailing slash since there mustn't be one for installing, bad :(
string dir=getUserPath(USER_DATA)+"levelpacks/"+addon->content[i].second+"/";
//Check if the directory exists.
if(!dirExists(dir.c_str())){
cerr<<"WARNING: Levelpack directory '"<<dir<<"' appears to have been removed already."<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Levelpack directory '%s' appears to have been removed already."),dir),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Remove the directory.
if(!removeDirectory(dir.c_str())){
cerr<<"ERROR: Unable to remove levelpack directory '"<<dir<<"'!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to remove levelpack directory '%s'!"),dir),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Also remove the levelpack from the levelpackManager.
getLevelPackManager()->removeLevelPack(dir, true);
}
}
//Now that the content has been removed clear the content list itself.
addon->content.clear();
//And finally set the addon to not installed.
addon->installed=false;
addon->installedVersion=0;
//Also clear the 'offline' information.
addon->content.clear();
addon->dependencies.clear();
}
void Addons::installAddon(ImageManager& imageManager,SDL_Renderer& renderer, Addon* addon){
string tmpDir=getUserPath(USER_CACHE)+"tmp/";
string fileName=fileNameFromPath(addon->file,true);
//Download the selected addon to the tmp folder.
if(!downloadFile(addon->file,tmpDir)){
cerr<<"ERROR: Unable to download addon file "<<addon->file<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to download addon file %s."),addon->file),MsgBoxOKOnly,_("Addon error"));
return;
}
//Now extract the addon.
if(!extractFile(tmpDir+fileName,tmpDir+"/addon/")){
cerr<<"ERROR: Unable to extract addon file "<<addon->file<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to extract addon file %s."),addon->file),MsgBoxOKOnly,_("Addon error"));
return;
}
ifstream metadata((tmpDir+"/addon/metadata").c_str());
if(!metadata){
cerr<<"ERROR: Addon is missing metadata!"<<endl;
msgBox(imageManager,renderer,_("ERROR: Addon is missing metadata!"),MsgBoxOKOnly,_("Addon error"));
return;
}
//Read the metadata from the addon.
TreeStorageNode obj;
{
POASerializer objSerializer;
if(!objSerializer.readNode(metadata,&obj,true)){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Invalid file format for metadata file!"<<endl;
msgBox(imageManager,renderer,_("ERROR: Invalid file format for metadata file!"),MsgBoxOKOnly,_("Addon error"));
return;
}
}
//Loop through the subNodes.
for(unsigned int i=0;i<obj.subNodes.size();i++){
//Check for the content subNode (there should only be one).
if(obj.subNodes[i]->name=="content"){
TreeStorageNode* obj1=obj.subNodes[i];
//Loop through the subNodes of that.
for(unsigned int j=0;j<obj1->subNodes.size();j++){
TreeStorageNode* obj2=obj1->subNodes[j];
//This code happens for all types of content.
string source=tmpDir+"addon/content/";
if(obj2->value.size()>0)
source+=obj2->value[0];
//The destination MUST be in the user data path.
string dest=getUserPath(USER_DATA);
if(obj2->value.size()>1)
dest+=obj2->value[1];
//Check what the content type is.
if(obj2->name=="file" && obj2->value.size()==2){
//Now copy the file.
if(fileExists(dest.c_str())){
cerr<<"WARNING: File '"<<dest<<"' already exists, addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: File '%s' already exists, addon may be broken or not working!"),dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
if(!copyFile(source.c_str(),dest.c_str())){
cerr<<"WARNING: Unable to copy file '"<<source<<"' to '"<<dest<<"', addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Unable to copy file '%s' to '%s', addon may be broken or not working!"),source,dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Add it to the content vector.
addon->content.push_back(pair<string,string>("file",obj2->value[1]));
}else if(obj2->name=="folder" && obj2->value.size()==2){
//The dest must NOT exist, otherwise it will fail.
if(dirExists(dest.c_str())){
cerr<<"WARNING: Destination directory '"<<dest<<"' already exists, addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Destination directory '%s' already exists, addon may be broken or not working!"),dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//FIXME: Copy the directory instead of renaming it, in case the same folder/parts of the folder are needed in different places.
if(!renameDirectory(source.c_str(),dest.c_str())){
cerr<<"WARNING: Unable to move directory '"<<source<<"' to '"<<dest<<"', addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Unable to move directory '%s' to '%s', addon may be broken or not working!"),source,dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Add it to the content vector.
addon->content.push_back(pair<string,string>("folder",obj2->value[1]));
}else if(obj2->name=="level" && obj2->value.size()==1){
//The destination MUST be in the levels folder in the user data path.
dest+="levels/"+fileNameFromPath(source);
//Now copy the file.
if(fileExists(dest.c_str())){
cerr<<"WARNING: Level '"<<dest<<"' already exists, addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Level '%s' already exists, addon may be broken or not working!"),dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
if(!copyFile(source.c_str(),dest.c_str())){
cerr<<"WARNING: Unable to copy level '"<<source<<"' to '"<<dest<<"', addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Unable to copy level '%s' to '%s', addon may be broken or not working!"),source,dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//It's a level so add it to the Levels levelpack.
LevelPack* levelsPack=getLevelPackManager()->getLevelPack(LEVELS_PATH);
if(levelsPack){
levelsPack->addLevel(dest);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
}else{
cerr<<"ERROR: Unable to add level to Levels levelpack"<<endl;
}
addon->content.push_back(pair<string,string>("level",fileNameFromPath(source)));
}else if(obj2->name=="levelpack" && obj2->value.size()==1){
//TODO: Check if the source contains a trailing slash.
//The destination MUST be in the user data path.
dest+="levelpacks/"+fileNameFromPath(source);
//The dest must NOT exist, otherwise it will fail.
if(dirExists(dest.c_str())){
cerr<<"WARNING: Levelpack directory '"<<dest<<"' already exists, addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Levelpack directory '%s' already exists, addon may be broken or not working!"),dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//FIXME: Copy the directory instead of renaming it, in case the same folder/parts of the folder are needed in different places.
if(!renameDirectory(source.c_str(),dest.c_str())){
cerr<<"WARNING: Unable to move directory '"<<source<<"' to '"<<dest<<"', addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Unable to move directory '%s' to '%s', addon may be broken or not working!"),source,dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//It's a levelpack so add it to the levelpack manager.
getLevelPackManager()->loadLevelPack(dest);
addon->content.push_back(pair<string,string>("levelpack",fileNameFromPath(source)));
}
}
}else if(obj.subNodes[i]->name=="dependencies"){
TreeStorageNode* obj1=obj.subNodes[i];
//Loop through the subNodes of that.
for(unsigned int j=0;j<obj1->subNodes.size();j++){
TreeStorageNode* obj2=obj1->subNodes[j];
if(obj2->name=="addon" && obj2->value.size()>0){
Addon* dep=NULL;
//Check if the requested addon can be found.
vector<Addon>::iterator it;
for(it=addons.begin();it!=addons.end();++it){
if(it->name==obj2->value[0]){
dep=&(*it);
break;
}
}
if(!dep){
cerr<<"ERROR: Addon requires another addon ("<<obj2->value[0]<<") which can't be found!"<<endl;
msgBox(imageManager, renderer, tfm::format(_("ERROR: Addon requires another addon (%s) which can't be found!"), obj2->value[0]), MsgBoxOKOnly, _("Addon error"));
continue;
}
//The addon has been found, try to install it.
//FIXME: Somehow prevent recursion, maybe max depth (??)
if(!dep->installed){
msgBox(imageManager, renderer, tfm::format(_("The addon %s is needed and will be installed now."), dep->name), MsgBoxOKOnly, _("Dependency"));
installAddon(imageManager,renderer, dep);
}
//Add the dependency to the addon.
addon->dependencies.push_back(pair<string,string>("addon",dep->name));
}
}
}
}
//The addon is installed and up to date, but not necessarily flawless.
addon->installed=true;
addon->upToDate=true;
addon->installedVersion=addon->version;
}
diff --git a/src/Block.cpp b/src/Block.cpp
index d33444d..9025c2c 100644
--- a/src/Block.cpp
+++ b/src/Block.cpp
@@ -1,1173 +1,1173 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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.
*
* Me And My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Block.h"
#include "Functions.h"
#include "LevelEditor.h"
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
using namespace std;
Block::Block(Game* parent,int x,int y,int w,int h,int type):
GameObject(parent),
animation(0),
flags(0),
temp(0),
dx(0),
dy(0),
movingPosTime(-1),
speed(0),
objCurrentStand(NULL),
xVel(0), yVel(0),
inAir(false), xVelBase(0), yVelBase(0),
isDelete(false)
{
//Make sure the type is set, if not init should be called somewhere else with this information.
if(type>=0 && type<TYPE_MAX)
init(x,y,w,h,type);
}
Block::Block(const Block& other)
: GameObject(other)
, ScriptProxyUserClass(other)
, animation(other.animation)
, flags(other.flags)
, temp(other.temp)
, dx(other.dx), dy(other.dy)
, movingPos(other.movingPos)
, movingPosTime(-1)
, speed(other.speed)
, objCurrentStand(other.objCurrentStand)
, customAppearanceName(other.customAppearanceName)
, appearance(other.appearance)
, xVel(other.xVel), yVel(other.yVel)
, inAir(other.inAir), xVelBase(other.xVelBase), yVelBase(other.yVelBase)
, id(other.id), destination(other.destination), message(other.message)
, isDelete(other.isDelete)
{
auto se = parent->getScriptExecutor();
if (se == NULL) return;
lua_State *state = se->getLuaState();
if (state == NULL) return;
//Copy the compiledScripts.
for (auto it = other.compiledScripts.begin(); it != other.compiledScripts.end(); ++it) {
lua_rawgeti(state, LUA_REGISTRYINDEX, it->second);
compiledScripts[it->first] = luaL_ref(state, LUA_REGISTRYINDEX);
}
}
Block::~Block() {
auto se = parent->getScriptExecutor();
if (se == NULL) return;
lua_State *state = se->getLuaState();
if (state == NULL) return;
//Delete the compiledScripts.
for (auto it = compiledScripts.begin(); it != compiledScripts.end(); ++it) {
luaL_unref(state, LUA_REGISTRYINDEX, it->second);
}
}
int Block::getPathMaxTime() {
if (movingPosTime < 0) {
movingPosTime = 0;
for (const SDL_Rect& p : movingPos) {
movingPosTime += p.w;
}
}
return movingPosTime;
}
void Block::init(int x,int y,int w,int h,int type){
//First set the location and size of the box.
//The default size is 50x50.
box.x=boxBase.x=x;
box.y=boxBase.y=y;
box.w=boxBase.w=w;
box.h=boxBase.h=h;
//Set the type.
this->type=type;
//Some types need type specific code.
if(type==TYPE_START_PLAYER){
//This is the player start so set the player here.
//We center the player, the player is 23px wide.
parent->player.setLocation(box.x+(box.w-23)/2,box.y);
parent->player.fx=box.x+(box.w-23)/2;
parent->player.fy=box.y;
}else if(type==TYPE_START_SHADOW){
//This is the shadow start so set the shadow here.
//We center the shadow, the shadow is 23px wide.
parent->shadow.setLocation(box.x+(box.w-23)/2,box.y);
parent->shadow.fx=box.x+(box.w-23)/2;
parent->shadow.fy=box.y;
}
objCurrentStand=NULL;
inAir=true;
xVel=yVel=xVelBase=yVelBase=0;
//And load the (default) appearance.
objThemes.getBlock(type)->createInstance(&appearance);
}
void Block::show(SDL_Renderer& renderer){
//Make sure we are visible.
if ((flags & 0x80000000) != 0 && (stateID != STATE_LEVEL_EDITOR || dynamic_cast<LevelEditor*>(parent)->isPlayMode()))
return;
//Check if the block is visible.
if(checkCollision(camera,box)==true || (stateID==STATE_LEVEL_EDITOR && checkCollision(camera,boxBase)==true)){
//Some type of block needs additional state check.
switch(type){
case TYPE_CHECKPOINT:
//Check if the checkpoint is last used.
if(parent!=NULL && parent->objLastCheckPoint.get()==this){
if(!temp) appearance.changeState("activated");
temp=1;
}else{
if(temp) appearance.changeState("default");
temp=0;
}
break;
}
//Always draw the base. (This is only supposed to show in editor.)
appearance.drawState("base", renderer, boxBase.x - camera.x, boxBase.y - camera.y, boxBase.w, boxBase.h);
//What we need to draw depends on the type of block.
switch (type) {
default:
//Draw normal.
appearance.draw(renderer, box.x - camera.x, box.y - camera.y, box.w, box.h);
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
//Draw conveyor belt.
if (animation) {
// FIXME: ad-hoc code. Should add a new animation type in theme system.
const int a = animation / 10;
const SDL_Rect r = { box.x - camera.x, box.y - camera.y, box.w, box.h };
appearance.draw(renderer, box.x - camera.x - 50 + a, box.y - camera.y, box.w + 50, box.h, &r);
} else {
appearance.draw(renderer, box.x - camera.x, box.y - camera.y, box.w, box.h);
}
break;
}
//Some types need to draw something on top of the base/default.
switch(type){
case TYPE_BUTTON:
if(flags&4){
if(animation<5) animation++;
}else{
if(animation>0) animation--;
}
appearance.drawState("button", renderer, box.x - camera.x, box.y - camera.y - 5 + animation, box.w, box.h);
break;
}
//Draw some stupid icons during edit mode.
if (stateID == STATE_LEVEL_EDITOR) {
auto bmGUI = static_cast<LevelEditor*>(parent)->getGuiTexture();
if (!bmGUI) {
return;
}
int x = box.x - camera.x + 2;
//Scripted blocks
if (!scripts.empty()){
const SDL_Rect r = { 0, 32, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
//Invisible blocks
if (flags & 0x80000000) {
const SDL_Rect r = { 16, 48, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
//Block with custom appearance
if (!customAppearanceName.empty()) {
const SDL_Rect r = { 48, 16, 16, 16 };
const SDL_Rect dstRect = { x, box.y - camera.y + 2, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
x += 16;
}
}
}
}
SDL_Rect Block::getBox(int boxType){
SDL_Rect r={0,0,0,0};
switch(boxType){
case BoxType_Base:
return boxBase;
case BoxType_Previous:
r.x=box.x-dx;
r.y=box.y-dy;
r.w=box.w;
r.h=box.h;
return r;
case BoxType_Delta:
r.x=dx;
r.y=dy;
return r;
case BoxType_Velocity:
r.x=xVel;
r.y=yVel;
//NOTE: In case of the pushable block we sometimes need to substract one from the vertical velocity.
//The yVel is set to one when it's resting, but should be handled as zero in collision.
if(type==TYPE_PUSHABLE && !inAir)
r.y=0;
return r;
case BoxType_Current:
return box;
}
return r;
}
void Block::moveTo(int x,int y){
//The block has moved so calculate the delta.
//NOTE: Every delta is summed since they all happened within one frame and for collision/movement we need the resulting delta.
int delta=(x-box.x);
dx+=delta;
xVel+=delta;
delta=(y-box.y);
dy+=delta;
yVel+=delta;
//And set the new location.
box.x=x;
box.y=y;
}
void Block::growTo(int w,int h){
//The block has changed size
//NOTE: Every delta is summed since they all happened within one frame and for collision/movement we need the resulting delta.
int delta=(w-box.w);
dx+=delta;
xVel+=delta;
delta=(h-box.h);
dy+=delta;
yVel+=delta;
//And set the new location.
box.w=w;
box.h=h;
}
void Block::playAnimation(){
switch(type){
case TYPE_SWAP:
appearance.changeState("activated");
break;
case TYPE_SWITCH:
temp^=1;
appearance.changeState(temp?"activated":"default");
break;
}
}
void Block::onEvent(int eventType){
//Make sure we are visible, otherwise no events should be handled except for 'OnCreate'.
if ((flags & 0x80000000) != 0 && eventType != GameObjectEvent_OnCreate)
return;
//Iterator used to check if the map contains certain entries.
map<int,int>::iterator it;
//Check if there's a script for the event.
it=compiledScripts.find(eventType);
if(it!=compiledScripts.end()){
//There is a script so execute it and check return value.
int ret=parent->getScriptExecutor()->executeScript(it->second,this);
//Return value 1 means do default event process.
//Other values are coming soon...
if(ret!=1) return;
}
//Event handling.
switch(eventType){
case GameObjectEvent_PlayerWalkOn:
switch(type){
case TYPE_FRAGILE:
if ((flags & 0x3) < 3) {
flags++;
const int f = flags & 0x3;
const char* s = (f <= 0) ? "default" : ((f == 1) ? "fragile1" : ((f == 2) ? "fragile2" : "fragile3"));
appearance.changeState(s);
}
break;
}
break;
case GameObjectEvent_PlayerIsOn:
switch(type){
case TYPE_BUTTON:
temp=1;
break;
}
break;
case GameObjectEvent_OnPlayerInteraction:
switch (type) {
case TYPE_SWITCH:
//Make sure that the id isn't emtpy.
if (!id.empty()) {
parent->broadcastObjectEvent(0x10000 | (flags & 3),
-1, id.c_str());
} else {
cerr << "WARNING: invalid switch id!" << endl;
}
break;
}
break;
case GameObjectEvent_OnToggle:
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
flags^=1;
break;
case TYPE_PORTAL:
appearance.changeState("activated");
break;
case TYPE_COLLECTABLE:
appearance.changeState("inactive");
flags|=1;
break;
}
break;
case GameObjectEvent_OnSwitchOn:
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
flags&=~1;
break;
case TYPE_EXIT:
appearance.changeState("default");
break;
}
break;
case GameObjectEvent_OnSwitchOff:
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
flags|=1;
break;
case TYPE_EXIT:
appearance.changeState("closed");
break;
}
break;
}
}
int Block::queryProperties(int propertyType,Player* obj){
switch(propertyType){
case GameObjectProperty_PlayerCanWalkOn:
switch(type){
case TYPE_BLOCK:
case TYPE_MOVING_BLOCK:
case TYPE_CONVEYOR_BELT:
case TYPE_BUTTON:
case TYPE_PUSHABLE:
return 1;
case TYPE_SHADOW_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_SHADOW_CONVEYOR_BELT:
if(obj!=NULL && obj->isShadow()) return 1;
break;
case TYPE_FRAGILE:
if ((flags & 0x3) < 3) return 1;
break;
}
break;
case GameObjectProperty_IsSpikes:
switch(type){
case TYPE_SPIKES:
case TYPE_MOVING_SPIKES:
return 1;
}
break;
case GameObjectProperty_Flags:
return flags;
break;
default:
break;
}
return 0;
}
void Block::getEditorData(std::vector<std::pair<std::string,std::string> >& obj){
//Every block has an id.
obj.push_back(pair<string,string>("id",id));
//And visibility.
obj.push_back(pair<string, string>("visible", (flags & 0x80000000) == 0 ? "1" : "0"));
//And custom appearance.
obj.push_back(pair<string, string>("appearance", customAppearanceName));
//Block specific properties.
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
char s[64],s0[64];
sprintf(s,"%d",(int)movingPos.size());
obj.push_back(pair<string,string>("MovingPosCount",s));
obj.push_back(pair<string,string>("activated",(flags&0x1)?"0":"1"));
obj.push_back(pair<string,string>("loop",(flags&0x2)?"0":"1"));
for(unsigned int i=0;i<movingPos.size();i++){
sprintf(s0+1,"%u",i);
sprintf(s,"%d",movingPos[i].x);
s0[0]='x';
obj.push_back(pair<string,string>(s0,s));
sprintf(s,"%d",movingPos[i].y);
s0[0]='y';
obj.push_back(pair<string,string>(s0,s));
sprintf(s,"%d",movingPos[i].w);
s0[0]='t';
obj.push_back(pair<string,string>(s0,s));
}
}
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
{
char s[64];
obj.push_back(pair<string,string>("activated",(flags&0x1)?"0":"1"));
sprintf(s,"%d",speed);
obj.push_back(pair<string,string>("speed10",s));
}
break;
case TYPE_PORTAL:
obj.push_back(pair<string,string>("automatic",(flags&0x1)?"1":"0"));
obj.push_back(pair<string,string>("destination",destination));
break;
case TYPE_BUTTON:
case TYPE_SWITCH:
{
string s;
switch(flags&0x3){
case 1:
s="on";
break;
case 2:
s="off";
break;
default:
s="toggle";
break;
}
obj.push_back(pair<string,string>("behaviour",s));
}
break;
case TYPE_NOTIFICATION_BLOCK:
{
string value=message;
//Change \n with the characters '\n'.
while(value.find('\n',0)!=string::npos){
size_t pos=value.find('\n',0);
value=value.replace(pos,1,"\\n");
}
obj.push_back(pair<string,string>("message",value));
}
break;
case TYPE_FRAGILE:
{
char s[64];
sprintf(s,"%d",flags&0x3);
obj.push_back(pair<string,string>("state",s));
}
break;
}
}
void Block::setEditorData(std::map<std::string,std::string>& obj){
//Iterator used to check if the map contains certain entries.
map<string,string>::iterator it;
//Check if the data contains the appearance.
it = obj.find("appearance");
if (it != obj.end()) {
std::string newAppearanceName;
if (it->second.empty() || it->second == std::string(Game::blockName[type]) + "_Scenery") {
//Use the default appearance. (Do nothing since newAppearanceName is already empty)
} else {
//Use the custom appearance.
newAppearanceName = it->second;
}
if (newAppearanceName != customAppearanceName) {
//Try to find the custom appearance.
ThemeBlock *themeBlock = NULL;
if (!newAppearanceName.empty()) {
themeBlock = objThemes.getScenery(newAppearanceName);
if (themeBlock == NULL) {
std::cerr << "ERROR: failed to load custom appearance '" << newAppearanceName << "' for block " << Game::blockName[type] << std::endl;
}
} else {
themeBlock = objThemes.getBlock(type);
if (themeBlock == NULL) {
std::cerr << "ERROR: failed to load default appearance for block " << Game::blockName[type] << std::endl;
}
}
if (themeBlock) {
//Update the custom appearance name.
customAppearanceName = newAppearanceName;
//Recreate the theme block instance.
themeBlock->createInstance(&appearance);
//Do some block specific stuff,
//e.g. reset the state according to block type,
//or load some missing part of block states from default appearance.
switch (type) {
case TYPE_FRAGILE:
{
const int f = flags & 0x3;
const char* s = (f == 0) ? "default" : ((f == 1) ? "fragile1" : ((f == 2) ? "fragile2" : "fragile3"));
appearance.changeState(s);
}
break;
case TYPE_BUTTON:
if (appearance.blockStates.find("button") == appearance.blockStates.end()) {
//Try to load the "button" state from default appearance
ThemeBlockInstance defaultAppearance;
objThemes.getBlock(type)->createInstance(&defaultAppearance);
auto it = defaultAppearance.blockStates.find("button");
if (it != defaultAppearance.blockStates.end() && it->second >= 0 && it->second < (int)defaultAppearance.states.size()) {
appearance.states.push_back(defaultAppearance.states[it->second]);
appearance.blockStates[it->first] = appearance.states.size() - 1;
}
}
break;
}
}
}
}
//Check if the data contains the id block.
it=obj.find("id");
if(it!=obj.end()){
//Set the id of the block.
id=obj["id"];
}
//Check if the data contains the visibility
it = obj.find("visible");
if (it != obj.end()) {
//Set the visibility.
const string& s = it->second;
flags = (flags & ~0x80000000) | ((s == "true" || atoi(s.c_str())) ? 0 : 0x80000000);
}
//Block specific properties.
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//Make sure that the editor data contains MovingPosCount.
it=obj.find("MovingPosCount");
if(it!=obj.end()){
char s0[64];
int m=atoi(obj["MovingPosCount"].c_str());
movingPos.clear();
for(int i=0;i<m;i++){
SDL_Rect r={0,0,0,0};
sprintf(s0+1,"%d",i);
s0[0]='x';
r.x=atoi(obj[s0].c_str());
s0[0]='y';
r.y=atoi(obj[s0].c_str());
s0[0]='t';
r.w=atoi(obj[s0].c_str());
movingPos.push_back(r);
}
}
//Check if the activated or disabled key is in the data.
//NOTE: 'disabled' is obsolete in V0.5.
it=obj.find("activated");
if(it!=obj.end()){
const string& s=it->second;
flags&=~0x1;
if(!(s=="true" || atoi(s.c_str()))) flags|=0x1;
}else{
it=obj.find("disabled");
if(it!=obj.end()){
const string& s=it->second;
flags&=~0x1;
if(s=="true" || atoi(s.c_str())) flags|=0x1;
}
}
//Check if the loop key is in the data.
it=obj.find("loop");
if(it!=obj.end()){
const string& s=it->second;
flags |= 0x2;
if (s == "true" || atoi(s.c_str())) flags &= ~0x2;
}
}
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
{
//Check if there's a speed key in the editor data.
//NOTE: 'speed' is obsolete in V0.5.
it=obj.find("speed10");
if(it!=obj.end()){
speed=atoi(it->second.c_str());
}else{
it = obj.find("speed");
if (it != obj.end()){
speed = atoi(it->second.c_str()) * 10;
}
}
//Check if the activated or disabled key is in the data.
//NOTE: 'disabled' is obsolete in V0.5.
it=obj.find("activated");
if(it!=obj.end()){
const string& s=it->second;
flags&=~0x1;
if(!(s=="true" || atoi(s.c_str()))) flags|=0x1;
}else{
it=obj.find("disabled");
if(it!=obj.end()){
const string& s=it->second;
flags&=~0x1;
if(s=="true" || atoi(s.c_str())) flags|=0x1;
}
}
}
break;
case TYPE_PORTAL:
{
//Check if the automatic key is in the data.
it=obj.find("automatic");
if(it!=obj.end()){
const string& s=it->second;
flags&=~0x1;
if(s=="true" || atoi(s.c_str())) flags|=0x1;
}
//Check if the destination key is in the data.
it=obj.find("destination");
if(it!=obj.end()){
destination=it->second;
}
}
break;
case TYPE_BUTTON:
case TYPE_SWITCH:
{
//Check if the behaviour key is in the data.
it=obj.find("behaviour");
if(it!=obj.end()){
const string& s=it->second;
flags&=~0x3;
if(s=="on" || s==_("On")) flags|=1;
else if(s=="off" || s==_("Off")) flags|=2;
}
}
break;
case TYPE_NOTIFICATION_BLOCK:
{
//Check if the message key is in the data.
it=obj.find("message");
if(it!=obj.end()){
message=it->second;
//Change the characters '\n' to a real \n
while(message.find("\\n")!=string::npos){
message=message.replace(message.find("\\n"),2,"\n");
}
}
}
break;
case TYPE_FRAGILE:
{
//Check if the status is in the data.
it=obj.find("state");
if(it!=obj.end()){
flags=(flags&~0x3)|(atoi(it->second.c_str())&0x3);
const int f = flags & 0x3;
const char* s=(f==0)?"default":((f==1)?"fragile1":((f==2)?"fragile2":"fragile3"));
appearance.changeState(s);
}
}
break;
}
}
std::string Block::getEditorProperty(std::string property){
//First get the complete editor data.
vector<pair<string,string> > objMap;
vector<pair<string,string> >::iterator it;
getEditorData(objMap);
//Loop through the entries.
for(it=objMap.begin();it!=objMap.end();++it){
if(it->first==property)
return it->second;
}
//Nothing found.
return "";
}
void Block::setEditorProperty(std::string property,std::string value){
//Create a map to hold the property.
std::map<std::string,std::string> editorData;
editorData[property]=value;
//And call the setEditorData method.
setEditorData(editorData);
}
bool Block::loadFromNode(ImageManager&, SDL_Renderer&, TreeStorageNode* objNode){
//Make sure there are enough parameters.
if(objNode->value.size()<3)
return false;
//Load the type and location.
int type = 0;
{
auto it = Game::blockNameMap.find(objNode->value[0]);
if (it != Game::blockNameMap.end()) {
type = it->second;
} else {
cerr << "WARNING: Unknown block type '" << objNode->value[0] << "'!" << endl;
}
}
int x=atoi(objNode->value[1].c_str());
int y=atoi(objNode->value[2].c_str());
int w=50;
int h=50;
if(objNode->value.size()>3)
w=atoi(objNode->value[3].c_str());
if(objNode->value.size()>4)
h=atoi(objNode->value[4].c_str());
//Call the init method.
init(x,y,w,h,type);
//Loop through the attributes as editorProperties.
map<string,string> obj;
for(map<string,vector<string> >::iterator i=objNode->attributes.begin();i!=objNode->attributes.end();++i){
if(i->second.size()>0) obj[i->first]=i->second[0];
}
setEditorData(obj);
//Loop through the subNodes.
for(unsigned int i=0;i<objNode->subNodes.size();i++){
//FIXME: Ugly variable naming.
TreeStorageNode* obj=objNode->subNodes[i];
if(obj==NULL) continue;
//Check for a script block.
if(obj->name=="script" && !obj->value.empty()){
map<string,int>::iterator it=Game::gameObjectEventNameMap.find(obj->value[0]);
if(it!=Game::gameObjectEventNameMap.end()){
int eventType=it->second;
const std::string& script=obj->attributes["script"][0];
if(!script.empty()) scripts[eventType]=script;
}
}
}
return true;
}
void Block::move(){
bool isPlayMode = stateID != STATE_LEVEL_EDITOR || dynamic_cast<LevelEditor*>(parent)->isPlayMode();
//Make sure we are visible, if not return.
if ((flags & 0x80000000) != 0 && isPlayMode)
return;
//First update the animation of the appearance.
appearance.updateAnimation();
//Block specific move code.
switch(type){
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
//Only move block when we are in play mode.
if (isPlayMode) {
//Make sure the block is enabled, if so increase the time.
if(!(flags&0x1)) temp++;
int t=temp;
SDL_Rect r0={0,0,0,0},r1;
dx=0;
dy=0;
//Loop through the moving positions.
for(unsigned int i=0;i<movingPos.size();i++){
r1.x=movingPos[i].x;
r1.y=movingPos[i].y;
r1.w=movingPos[i].w;
if(t==0&&r1.w==0){ // time == 0 means the block deactivates at this point automatically
r1.w=1;
flags|=0x1;
}
if(t>=0 && t<(int)r1.w){
int newX=boxBase.x+(int)(float(r0.x)+(float(r1.x)-float(r0.x))*float(t)/float(r1.w));
int newY=boxBase.y+(int)(float(r0.y)+(float(r1.y)-float(r0.y))*float(t)/float(r1.w));
//Calculate the delta and velocity.
xVel=dx=newX-box.x;
yVel=dy=newY-box.y;
//Set the new location of the moving block.
box.x=newX;
box.y=newY;
return;
} else if (t == (int)r1.w && i == movingPos.size() - 1) {
//If the time is the time of the movingPosition then set it equal to the location.
//We do this to prevent a slight edge between normal blocks and moving blocks.
int newX=boxBase.x+r1.x;
int newY=boxBase.y+r1.y;
xVel=dx=newX-box.x;
yVel=dy=newY-box.y;
box.x=newX;
box.y=newY;
return;
}
t-=r1.w;
r0.x=r1.x;
r0.y=r1.y;
}
//Only reset the stuff when we're looping.
if((flags & 0x2) == 0){
//Set the time back to zero.
temp=0;
//Calculate the delta movement.
if(!movingPos.empty() && movingPos.back().x==0 && movingPos.back().y==0){
dx=boxBase.x-box.x;
dy=boxBase.y-box.y;
}
//Set the movingblock back to it's initial location.
box.x=boxBase.x;
box.y=boxBase.y;
} else {
//Reached the end, but not looping
xVel=yVel=dx=dy=0;
}
}
break;
case TYPE_BUTTON:
//Only move block when we are in play mode.
if (isPlayMode) {
//Check the third bit of flags to see if temp changed.
int new_flags=temp?4:0;
if((flags^new_flags)&4){
//The button has been pressed or unpressed so change the third bit on flags.
flags=(flags&~4)|new_flags;
if(parent && (new_flags || (flags&3)==0)){
//Make sure that id isn't empty.
if(!id.empty()){
parent->broadcastObjectEvent(0x10000|(flags&3),-1,id.c_str());
}else{
cerr<<"WARNING: invalid button id!"<<endl;
}
}
}
temp=0;
}
break;
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
//NOTE: we update conveyor belt animation even in edit mode.
//Increase the conveyor belt animation.
if((flags&1)==0){
//Since now 1 speed = 0.1 pixel/s we need some more sophisticated calculation.
int a = animation + speed, d = 0;
if (a < 0) {
//Add a delta value to make it positive
d = (((-a) / 500) + 1) * 500;
}
//Set the velocity NOTE This isn't the actual velocity of the block, but the speed of the player/shadow standing on it.
xVel = (a + d) / 10 - (animation + d) / 10;
//Update animation value
animation = (a + d) % 500;
assert(animation >= 0);
} else {
//Clear the velocity NOTE This isn't the actual velocity of the block, but the speed of the player/shadow standing on it.
xVel = 0;
}
break;
case TYPE_PUSHABLE:
//Only move block when we are in play mode.
if (isPlayMode) {
//Update the vertical velocity, horizontal is set by the player.
if(inAir==true){
yVel+=1;
//Cap fall speed to 13.
if(yVel>13)
yVel=13;
}
if (auto ocs = objCurrentStand.get()) {
//Now get the velocity and delta of the object the player is standing on.
SDL_Rect v=ocs->getBox(BoxType_Velocity);
SDL_Rect delta=ocs->getBox(BoxType_Delta);
switch(ocs->type){
//For conveyor belts the velocity is transfered.
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
{
xVelBase+=v.x;
}
break;
//In other cases, such as, player on shadow, player on crate... the change in x position must be considered.
default:
{
if(delta.x != 0)
xVelBase+=delta.x;
}
break;
}
//NOTE: Only copy the velocity of the block when moving down.
//Upwards is automatically resolved before the player is moved.
if(delta.y>0)
yVelBase=delta.y;
else
yVelBase=0;
}
//Set the object the player is currently standing to NULL.
objCurrentStand=NULL;
//Store the location of the player.
int lastX=box.x;
int lastY=box.y;
//An array that will hold all the GameObjects that are involved in the collision/movement.
vector<Block*> objects;
//All the blocks have moved so if there's collision with the player, the block moved into him.
- for(unsigned int o=0;o<parent->levelObjects.size();o++){
+ for(auto o : parent->levelObjects){
//Make sure to only check visible blocks.
- if(parent->levelObjects[o]->flags & 0x80000000)
+ if(o->flags & 0x80000000)
continue;
//Make sure we aren't the block.
- if(parent->levelObjects[o]==this)
+ if(o==this)
continue;
- //Make sure the object is solid for the player.
- if(!parent->levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,&parent->player))
+ //Make sure the object is spike or solid for the player.
+ if(o->type!=TYPE_SPIKES && o->type!=TYPE_MOVING_SPIKES && !o->queryProperties(GameObjectProperty_PlayerCanWalkOn,&parent->player))
continue;
//Check for collision.
- if(checkCollision(box,parent->levelObjects[o]->getBox()))
- objects.push_back(parent->levelObjects[o]);
+ if(checkCollision(box,o->getBox()))
+ objects.push_back(o);
}
//There was collision so try to resolve it.
if(!objects.empty()){
//FIXME: When multiple moving blocks are overlapping the pushable can be "bounced" off depending on the block order.
- for(unsigned int o=0;o<objects.size();o++){
- SDL_Rect r=objects[o]->getBox();
- SDL_Rect delta=objects[o]->getBox(BoxType_Delta);
+ for(auto o : objects){
+ SDL_Rect r=o->getBox();
+ SDL_Rect delta=o->getBox(BoxType_Delta);
//Check on which side of the box the pushable is.
if(delta.x!=0){
if(delta.x>0){
//Move the pushable right if necessary.
if((r.x+r.w)-box.x<=delta.x && box.x<r.x+r.w)
box.x=r.x+r.w;
}else{
//Move the pushable left if necessary.
if((box.x+box.w)-r.x<=-delta.x && box.x>r.x-box.w)
box.x=r.x-box.w;
}
}
if(delta.y!=0){
if(delta.y>0){
//Move the pushable down if necessary.
if((r.y+r.h)-box.y<=delta.y && box.y<r.y+r.h)
box.y=r.y+r.h;
}else{
//Move the pushable up if necessary.
if((box.y+box.h)-r.y<=-delta.y && box.y>r.y-box.h)
box.y=r.y-box.h;
}
}
}
}
//Reuse the objects array, this time for blocks the block moves into.
objects.clear();
//Determine the collision frame.
SDL_Rect frame={box.x,box.y,box.w,box.h};
//Keep the horizontal movement of the block in mind.
if(xVel+xVelBase>=0){
frame.w+=(xVel+xVelBase);
}else{
frame.x+=(xVel+xVelBase);
frame.w-=(xVel+xVelBase);
}
//And the vertical movement.
if(yVel+yVelBase>=0){
frame.h+=(yVel+yVelBase);
}else{
frame.y+=(yVel+yVelBase);
frame.h-=(yVel+yVelBase);
}
//Loop through the game objects.
- for(unsigned int o=0; o<parent->levelObjects.size(); o++){
+ for(auto o : parent->levelObjects){
//Make sure the object is visible.
- if(parent->levelObjects[o]->flags & 0x80000000)
+ if(o->flags & 0x80000000)
continue;
//Make sure we aren't the block.
- if(parent->levelObjects[o]==this)
+ if(o==this)
continue;
- //Check if the player can collide with this game object.
- if(!parent->levelObjects[o]->queryProperties(GameObjectProperty_PlayerCanWalkOn,&parent->player))
+ //Check if the player can collide with this game object, or this object is spike.
+ if(o->type!=TYPE_SPIKES && o->type!=TYPE_MOVING_SPIKES && !o->queryProperties(GameObjectProperty_PlayerCanWalkOn,&parent->player))
continue;
//Check if the block is inside the frame.
- if(checkCollision(frame,parent->levelObjects[o]->getBox()))
- objects.push_back(parent->levelObjects[o]);
+ if(checkCollision(frame,o->getBox()))
+ objects.push_back(o);
}
//Horizontal pass.
if(xVel+xVelBase!=0){
box.x+=xVel+xVelBase;
- for(unsigned int o=0;o<objects.size();o++){
- SDL_Rect r=objects[o]->getBox();
+ for(auto o : objects){
+ SDL_Rect r=o->getBox();
if(!checkCollision(box,r))
continue;
if(xVel+xVelBase>0){
//We came from the left so the right edge of the player must be less or equal than xVel+xVelBase.
if((box.x+box.w)-r.x<=xVel+xVelBase)
box.x=r.x-box.w;
}else{
//We came from the right so the left edge of the player must be greater or equal than xVel+xVelBase.
if(box.x-(r.x+r.w)>=xVel+xVelBase)
box.x=r.x+r.w;
}
}
}
//Some variables that are used in vertical movement.
Block* lastStand=NULL;
inAir=true;
//Vertical pass.
if(yVel+yVelBase!=0){
box.y+=yVel+yVelBase;
- for(unsigned int o=0;o<objects.size();o++){
- SDL_Rect r=objects[o]->getBox();
+ for(auto o : objects){
+ SDL_Rect r=o->getBox();
if(!checkCollision(box,r))
continue;
//Now check how we entered the block (vertically or horizontally).
if(yVel+yVelBase>0){
//We came from the top so the bottom edge of the player must be less or equal than yVel+yVelBase.
if((box.y+box.h)-r.y<=yVel+yVelBase){
//NOTE: lastStand is handled later since the player can stand on only one block at the time.
//Check if there's already a lastStand.
if(lastStand){
//There is one, so check 'how much' the player is on the blocks.
- SDL_Rect r=objects[o]->getBox();
+ SDL_Rect r=o->getBox();
int w=0;
if(box.x+box.w>r.x+r.w)
w=(r.x+r.w)-box.x;
else
w=(box.x+box.w)-r.x;
//Do the same for the other box.
r=lastStand->getBox();
int w2=0;
if(box.x+box.w>r.x+r.w)
w2=(r.x+r.w)-box.x;
else
w2=(box.x+box.w)-r.x;
//NOTE: It doesn't matter which block the player is on if they are both stationary.
- SDL_Rect v=objects[o]->getBox(BoxType_Velocity);
+ SDL_Rect v=o->getBox(BoxType_Velocity);
SDL_Rect v2=lastStand->getBox(BoxType_Velocity);
if(v.y==v2.y){
if(w>w2)
- lastStand=objects[o];
+ lastStand=o;
}else if(v.y<v2.y){
- lastStand=objects[o];
+ lastStand=o;
}
}else{
- lastStand=objects[o];
+ lastStand=o;
}
}
}else{
//We came from the bottom so the upper edge of the player must be greater or equal than yVel+yVelBase.
if(box.y-(r.y+r.h)>=yVel+yVelBase){
box.y=r.y+r.h;
yVel=0;
}
}
}
}
if(lastStand){
inAir=false;
yVel=1;
SDL_Rect r=lastStand->getBox();
box.y=r.y-box.h;
}
//Block will currently be standing on whatever it was last standing on.
objCurrentStand=lastStand;
dx=box.x-lastX;
dy=box.y-lastY;
xVel=0;
xVelBase=0;
}
break;
}
}
diff --git a/src/CommandManager.cpp b/src/CommandManager.cpp
index c7bbc65..631841d 100644
--- a/src/CommandManager.cpp
+++ b/src/CommandManager.cpp
@@ -1,154 +1,154 @@
/*
* Copyright (C) 2018 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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.
*
* Me And My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
// An undo/redo system based on <http://www.codeproject.com/Articles/2500/A-Basic-Undo-Redo-Framework-For-C>.
// Originally written by squarecross <https://forum.freegamedev.net/viewtopic.php?f=48&t=5432>.
#include "CommandManager.h"
#include "Commands.h"
#include "Functions.h"
#include "libs/tinyformat/tinyformat.h"
#include <iostream>
bool CommandManager::canUndo() const {
return currentCommand > 0;
}
bool CommandManager::canRedo() const {
return currentCommand < (int)undoList.size();
}
std::string CommandManager::describeUndo() {
if (canUndo()) {
- return tfm::format(_("Undo %s"), undoList[currentCommand - 1]->describe());
+ return tfm::format(_("Undo %s"), undoList[currentCommand - 1]->describe()) + " (Ctrl+Z)";
} else {
return _("Can't undo");
}
}
std::string CommandManager::describeRedo() {
if (canRedo()) {
- return tfm::format(_("Redo %s"), undoList[currentCommand]->describe());
+ return tfm::format(_("Redo %s"), undoList[currentCommand]->describe()) + " (Ctrl+Y)";
} else {
return _("Can't redo");
}
}
int CommandManager::getUndoLevel() const {
return undoLevel;
}
void CommandManager::setUndoLevel(int newValue) {
undoLevel = newValue;
// check if the current undo list is too big
const int n = undoList.size() - undoLevel;
if (n > 0) {
currentCommand -= n;
pivot = (pivot < n) ? -1 : (pivot - n);
for (int i = 0; i < n; i++) {
delete undoList[i];
}
undoList.erase(undoList.begin(), undoList.begin() + n);
}
}
bool CommandManager::isChanged() const {
return currentCommand != pivot;
}
void CommandManager::doCommand(Command* command) {
// clear the redo list
if (currentCommand < (int)undoList.size()) {
if (pivot > currentCommand) pivot = -1;
for (int i = currentCommand; i < (int)undoList.size(); i++) {
delete undoList[i];
}
undoList.erase(undoList.begin() + currentCommand, undoList.end());
}
// debug
#ifdef _DEBUG
std::cout << "Do command: " << command->describe() << std::endl;
#endif
// Execute the command and add it to undo list
command->execute();
undoList.push_back(command);
currentCommand++;
// check if the current undo list is too big
setUndoLevel(undoLevel);
}
void CommandManager::undo() {
if (canUndo()) {
//Move the pointer
currentCommand--;
//Gets the command in undoList.
Command* command = undoList[currentCommand];
// debug
#ifdef _DEBUG
std::cout << "Undo command: " << command->describe() << std::endl;
#endif
//undoing the command
command->unexecute();
}
}
void CommandManager::redo() {
if (canRedo()) {
//Gets the command in undoList.
Command* command = undoList[currentCommand];
// debug
#ifdef _DEBUG
std::cout << "Redo command: " << command->describe() << std::endl;
#endif
//redoing the command
command->execute();
//Move the pointer
currentCommand++;
}
}
void CommandManager::destroy() {
for (int i = 0; i < (int)undoList.size(); i++) {
delete undoList[i];
}
undoList.clear();
currentCommand = 0;
pivot = -1;
}
void CommandManager::resetChange() {
pivot = currentCommand;
}
diff --git a/src/GUISpinBox.cpp b/src/GUISpinBox.cpp
index 6409d6a..664125b 100644
--- a/src/GUISpinBox.cpp
+++ b/src/GUISpinBox.cpp
@@ -1,203 +1,203 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Functions.h"
#include "GUISpinBox.h"
#include "ThemeManager.h"
#include <algorithm>
bool GUISpinBox::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Backup the old state.
int oldState = state;
//First we call the GUITextBox::handleEvents().
bool b0 = GUITextBox::handleEvents(renderer, x, y, enabled, visible, processed);
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when he and his parent are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when he and his parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//Reset "key" to stop contant update of "number" in render().
//If the mouse is still on the button, the "key" will be reassigned later.
key=-1;
//Only check for events when the object is both enabled and visible.
if (enabled&&visible){
//Check if there's a key press and the event hasn't been already processed.
if (state == 2 && event.type == SDL_KEYDOWN && !b) {
if (event.key.keysym.sym == SDLK_UP) {
updateValue(true);
b = true;
} else if (event.key.keysym.sym == SDLK_DOWN) {
updateValue(false);
b = true;
}
}
//Only process mouse event when not in keyboard only mode
if (!isKeyboardOnly) {
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Check if the mouse is inside the text box.
if (i >= x && i < x + width && j >= y && j < y + height){
//Check if the mouse is inside the up/down button.
if (i >= x + width - 16) {
//Reset the cursor back to normal.
currentCursor = CURSOR_POINTER;
//Check for a mouse button press.
if (k&SDL_BUTTON(1)){
if (j < y + 17){
//Set the key values correct.
this->key = SDLK_UP;
keyHoldTime = 0;
keyTime = getKeyboardRepeatDelay();
//Update once to prevent a lag.
updateValue(true);
} else{
//Set the key values correct.
this->key = SDLK_DOWN;
keyHoldTime = 0;
keyTime = getKeyboardRepeatDelay();
//Update once to prevent a lag.
updateValue(false);
}
}
}
//Allow mouse wheel to change value.
if (event.type == SDL_MOUSEWHEEL){
if (event.wheel.y > 0){
updateValue(true);
b = true;
} else if (event.wheel.y < 0){
updateValue(false);
b = true;
}
}
}
}
//Validate the input when we lost focus.
if (oldState == 2 && state == 0){
update();
}
}
return b || b0;
}
void GUISpinBox::render(SDL_Renderer &renderer, int x, int y, bool draw){
//Call the GUITextBox::render().
GUITextBox::render(renderer, x, y, draw);
//FIXME: Logic in the render method since that is update constant.
if(key!=-1){
//Increase the key time.
keyHoldTime++;
//Make sure the deletionTime isn't to short.
if(keyHoldTime>=keyTime){
keyHoldTime=0;
keyTime = getKeyboardRepeatInterval();
//Now check the which key it was.
switch(key){
case SDLK_UP:
{
updateValue(true);
break;
}
case SDLK_DOWN:
{
updateValue(false);
break;
}
}
}
}
//There's no need drawing when it's invisible.
if(!visible || !draw)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Draw arrow buttons.
SDL_Rect srcRect = { 80, 0, 16, 16 };
SDL_Rect dstRect = { x + width - 18, y + 1, srcRect.w, srcRect.h };
SDL_RenderCopy(&renderer, bmGuiTex.get(), &srcRect, &dstRect);
srcRect.x = 96;
dstRect.y += 16;
SDL_RenderCopy(&renderer, bmGuiTex.get(), &srcRect, &dstRect);
}
void GUISpinBox::update(){
//Read number from the caption string.
- float number=(float)atof(caption.c_str());
+ double number=atof(caption.c_str());
//Stay in the limits.
if(number>limitMax){
number=limitMax;
}else if(number<limitMin){
number=limitMin;
}
//Write the number to the caption string.
char str[128];
sprintf(str,format.c_str(),number);
updateText(str);
}
void GUISpinBox::updateValue(bool positive){
//Read number from the caption string.
- float number=(float)atof(caption.c_str());
+ double number=atof(caption.c_str());
//Apply change.
if(positive)
number+=change;
else
number-=change;
//Stay in the limits.
if(number>limitMax){
number=limitMax;
}else if(number<limitMin){
number=limitMin;
}
//Write the number to the caption string.
char str[128];
sprintf(str,format.c_str(),number);
updateText(str);
}
diff --git a/src/GUISpinBox.h b/src/GUISpinBox.h
index 2bbf0e8..8a1f3be 100644
--- a/src/GUISpinBox.h
+++ b/src/GUISpinBox.h
@@ -1,70 +1,70 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GUISPINBOX_H
#define GUISPINBOX_H
#include "GUIObject.h"
class GUISpinBox:public GUITextBox{
public:
GUISpinBox(ImageManager& imageManager,SDL_Renderer& renderer,int left=0,int top=0,int width=0,int height=0,
bool enabled=true,bool visible=true):
GUITextBox(imageManager,renderer,left,top,width,height,NULL,0,enabled,visible),
change(1.0f),limitMax(100),limitMin(-100),format("%g"),
key(-1),keyHoldTime(0),keyTime(0){}
//Amount of single change.
- float change;
+ double change;
//Widget's value stays between these values.
- float limitMax,limitMin;
+ double limitMax,limitMin;
//Standard C printf format used for displaying the number.
std::string format;
//Method to update widget's value.
void update();
//Method to change widget's value.
//positive: Boolean if add or remove change.
void updateValue(bool positive);
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(SDL_Renderer&,int x=0,int y=0,bool enabled=true,bool visible=true,bool processed=false) override;
//Method that will render the GUIScrollBar.
//x: The x location to draw the GUIObject. (x+left)
//y: The y location to draw the GUIObject. (y+top)
virtual void render(SDL_Renderer& renderer, int x=0,int y=0,bool draw=true) override;
private:
//Integer containing the key that is holded.
int key;
//Integer containing the time the key is pressed.
int keyHoldTime;
//The time it takes to invoke the key action again.
int keyTime;
};
#endif
diff --git a/src/Game.cpp b/src/Game.cpp
index 6bc72c4..be1105c 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -1,1832 +1,1861 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Block.h"
#include "GameState.h"
#include "Functions.h"
#include "GameObjects.h"
#include "ThemeManager.h"
#include "Game.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "InputManager.h"
#include "MusicManager.h"
#include "Render.h"
#include "StatisticsManager.h"
#include "ScriptExecutor.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>
#include <map>
#include <algorithm>
#include <assert.h>
#include <stdio.h>
#include <SDL_ttf.h>
#include "libs/tinyformat/tinyformat.h"
using namespace std;
const char* Game::blockName[TYPE_MAX]={"Block","PlayerStart","ShadowStart",
"Exit","ShadowBlock","Spikes",
"Checkpoint","Swap","Fragile",
"MovingBlock","MovingShadowBlock","MovingSpikes",
"Teleporter","Button","Switch",
"ConveyorBelt","ShadowConveyorBelt","NotificationBlock", "Collectable", "Pushable"
};
map<string,int> Game::blockNameMap;
map<int,string> Game::gameObjectEventTypeMap;
map<string,int> Game::gameObjectEventNameMap;
map<int,string> Game::levelEventTypeMap;
map<string,int> Game::levelEventNameMap;
string Game::recordFile;
//An internal function.
static void copyCompiledScripts(lua_State *state, const std::map<int, int>& src, std::map<int, int>& dest) {
//Clear the existing scripts.
for (auto it = dest.begin(); it != dest.end(); ++it) {
luaL_unref(state, LUA_REGISTRYINDEX, it->second);
}
dest.clear();
//Copy the source to the destination.
for (auto it = src.begin(); it != src.end(); ++it) {
lua_rawgeti(state, LUA_REGISTRYINDEX, it->second);
dest[it->first] = luaL_ref(state, LUA_REGISTRYINDEX);
}
}
//An internal function.
static void copyLevelObjects(const std::vector<Block*>& src, std::vector<Block*>& dest, bool setActive) {
//Clear the existing objects.
for (auto o : dest) delete o;
dest.clear();
//Copy the source to the destination.
for (auto o : src) {
if (o == NULL || o->isDelete) continue;
Block* o2 = new Block(*o);
dest.push_back(o2);
if (setActive) o2->setActive();
}
}
Game::Game(SDL_Renderer &renderer, ImageManager &imageManager):isReset(false)
, scriptExecutor(new ScriptExecutor())
,currentLevelNode(NULL)
,customTheme(NULL)
,background(NULL)
,won(false)
,interlevel(false)
,gameTipIndex(0)
,time(0),timeSaved(0)
,recordings(0),recordingsSaved(0)
,cameraMode(CAMERA_PLAYER),cameraModeSaved(CAMERA_PLAYER)
,player(this),shadow(this),objLastCheckPoint(NULL)
,currentCollectables(0),totalCollectables(0),currentCollectablesSaved(0){
saveStateNextTime=false;
loadStateNextTime=false;
recentSwap=recentSwapSaved=-10000;
recentLoad=recentSave=0;
action=imageManager.loadTexture(getDataPath()+"gfx/actions.png", renderer);
medals=imageManager.loadTexture(getDataPath()+"gfx/medals.png", renderer);
//Get the collectable image from the theme.
//NOTE: Isn't there a better way to retrieve the image?
objThemes.getBlock(TYPE_COLLECTABLE)->createInstance(&collectable);
//Hide the cursor if not in the leveleditor.
if(stateID!=STATE_LEVEL_EDITOR)
SDL_ShowCursor(SDL_DISABLE);
}
Game::~Game(){
//Simply call our destroy method.
destroy();
//Before we leave make sure the cursor is visible.
SDL_ShowCursor(SDL_ENABLE);
}
void Game::destroy(){
delete scriptExecutor;
scriptExecutor = NULL;
//Loop through the levelObjects, etc. and delete them.
for (auto o : levelObjects) delete o;
levelObjects.clear();
for (auto o : levelObjectsSave) delete o;
levelObjectsSave.clear();
for (auto o : levelObjectsInitial) delete o;
levelObjectsInitial.clear();
//Loop through the sceneryLayers and delete them.
for(auto it=sceneryLayers.begin();it!=sceneryLayers.end();++it){
delete it->second;
}
sceneryLayers.clear();
//Clear the name and the editor data.
levelName.clear();
levelFile.clear();
editorData.clear();
//Remove everything from the themeManager.
background=NULL;
if(customTheme)
objThemes.removeTheme();
customTheme=NULL;
//If there's a (partial) theme bundled with the levelpack remove that as well.
if(levels->customTheme)
objThemes.removeTheme();
//delete current level (if any)
if(currentLevelNode){
delete currentLevelNode;
currentLevelNode=NULL;
}
//Reset the time.
time=timeSaved=0;
recordings=recordingsSaved=0;
recentSwap=recentSwapSaved=-10000;
//Set the music list back to the configured list.
getMusicManager()->setMusicList(getSettings()->getValue("musiclist"));
}
void Game::reloadMusic() {
//NOTE: level music is always enabled.
//Check if the levelpack has a prefered music list.
if (levels && !levels->levelpackMusicList.empty())
getMusicManager()->setMusicList(levels->levelpackMusicList);
//Check for the music to use.
string &s = editorData["music"];
if (!s.empty()) {
getMusicManager()->playMusic(s);
} else {
getMusicManager()->pickMusic();
}
}
void Game::loadLevelFromNode(ImageManager& imageManager,SDL_Renderer& renderer,TreeStorageNode* obj,const string& fileName){
//Make sure there's nothing left from any previous levels.
assert(levelObjects.empty() && levelObjectsSave.empty() && levelObjectsInitial.empty());
//set current level to loaded one.
currentLevelNode=obj;
//Set the level dimensions to the default, it will probably be changed by the editorData,
//but 800x600 is a fallback.
LEVEL_WIDTH=800;
LEVEL_HEIGHT=600;
currentCollectables=0;
totalCollectables=0;
currentCollectablesSaved=0;
//Load the additional data.
for(map<string,vector<string> >::iterator i=obj->attributes.begin();i!=obj->attributes.end();++i){
if(i->first=="size"){
//We found the size attribute.
if(i->second.size()>=2){
//Set the dimensions of the level.
LEVEL_WIDTH=atoi(i->second[0].c_str());
LEVEL_HEIGHT=atoi(i->second[1].c_str());
}
}else if(i->second.size()>0){
//Any other data will be put into the editorData.
editorData[i->first]=i->second[0];
}
}
//Get the theme.
{
//NOTE: level themes are always enabled.
//Check for bundled (partial) themes for level pack.
if (levels->customTheme){
if (objThemes.appendThemeFromFile(levels->levelpackPath + "/theme/theme.mnmstheme", imageManager, renderer) == NULL){
//The theme failed to load so set the customTheme boolean to false.
levels->customTheme = false;
}
}
//Check for the theme to use for this level. This has higher priority.
//Examples: %DATA%/themes/classic or %USER%/themes/Orange
string &s = editorData["theme"];
if (!s.empty()){
customTheme = objThemes.appendThemeFromFile(processFileName(s) + "/theme.mnmstheme", imageManager, renderer);
}
//Set the Appearance of the player and the shadow.
objThemes.getCharacter(false)->createInstance(&player.appearance, "standright");
player.appearanceInitial = player.appearanceSave = player.appearance;
objThemes.getCharacter(true)->createInstance(&shadow.appearance, "standright");
shadow.appearanceInitial = shadow.appearanceSave = shadow.appearance;
}
//Get the music.
reloadMusic();
//Load the data from the level node.
for(unsigned int i=0;i<obj->subNodes.size();i++){
TreeStorageNode* obj1=obj->subNodes[i];
if(obj1==NULL) continue;
if(obj1->name=="tile"){
Block* block=new Block(this);
if(!block->loadFromNode(imageManager,renderer,obj1)){
delete block;
continue;
}
//If the type is collectable, increase the number of totalCollectables
if(block->type==TYPE_COLLECTABLE)
totalCollectables++;
//Add the block to the levelObjects vector.
levelObjects.push_back(block);
}else if(obj1->name=="scenerylayer" && obj1->value.size()==1){
//Check if the layer exists.
if (sceneryLayers[obj1->value[0]] == NULL) {
sceneryLayers[obj1->value[0]] = new SceneryLayer();
}
//Load contents from node.
sceneryLayers[obj1->value[0]]->loadFromNode(this, imageManager, renderer, obj1);
}else if(obj1->name=="script" && !obj1->value.empty()){
map<string,int>::iterator it=Game::levelEventNameMap.find(obj1->value[0]);
if(it!=Game::levelEventNameMap.end()){
int eventType=it->second;
const std::string& script=obj1->attributes["script"][0];
if(!script.empty()) scripts[eventType]=script;
}
}
}
//Set the levelName to the name of the current level.
levelName=editorData["name"];
levelFile=fileName;
//Some extra stuff only needed when not in the levelEditor.
if(stateID!=STATE_LEVEL_EDITOR){
//We create a text with the text "Level <levelno> <levelName>".
//It will be shown in the left bottom corner of the screen.
string s;
if(levels->getLevelCount()>1 && levels->type!=COLLECTION){
s=tfm::format(_("Level %d %s"),levels->getCurrentLevel()+1,_CC(levels->getDictionaryManager(),editorData["name"]));
} else {
s = _CC(levels->getDictionaryManager(), editorData["name"]);
}
bmTips[0]=textureFromText(renderer, *fontText,s.c_str(),objThemes.getTextColor(true));
}
//Get the background
background=objThemes.getBackground(false);
//Now the loading is finished, we reset all objects to their initial states.
//Before doing that we swap the levelObjects to levelObjectsInitial.
std::swap(levelObjects, levelObjectsInitial);
reset(true, stateID == STATE_LEVEL_EDITOR);
}
void Game::loadLevel(ImageManager& imageManager,SDL_Renderer& renderer,std::string fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode *obj=new TreeStorageNode();
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),obj,true)){
cerr<<"ERROR: Can't load level file "<<s<<endl;
delete obj;
return;
}
}
//Now call another function.
loadLevelFromNode(imageManager,renderer,obj,fileName);
}
void Game::saveRecord(const char* fileName){
//check if current level is NULL (which should be impossible)
if(currentLevelNode==NULL) return;
TreeStorageNode obj;
POASerializer objSerializer;
//put current level to the node.
currentLevelNode->name="map";
obj.subNodes.push_back(currentLevelNode);
//serialize the game record using RLE compression.
#define PUSH_BACK \
if(j>0){ \
if(j>1){ \
sprintf(c,"%d*%d",last,j); \
}else{ \
sprintf(c,"%d",last); \
} \
v.push_back(c); \
}
vector<string> &v=obj.attributes["record"];
vector<int> *record=player.getRecord();
char c[64];
int i,j=0,last;
for(i=0;i<(int)record->size();i++){
int currentKey=(*record)[i];
if(j==0 || currentKey!=last){
PUSH_BACK;
last=currentKey;
j=1;
}else{
j++;
}
}
PUSH_BACK;
#undef PUSH_BACK
#ifdef RECORD_FILE_DEBUG
//add record file debug data.
{
obj.attributes["recordKeyPressLog"].push_back(player.keyPressLog());
vector<SDL_Rect> &playerPosition=player.playerPosition();
string s;
char c[32];
sprintf(c,"%d\n",int(playerPosition.size()));
s=c;
for(unsigned int i=0;i<playerPosition.size();i++){
SDL_Rect& r=playerPosition[i];
sprintf(c,"%d %d\n",r.x,r.y);
s+=c;
}
obj.attributes["recordPlayerPosition"].push_back(s);
}
#endif
//save it
objSerializer.saveNodeToFile(fileName,&obj,true,true);
//remove current level from node to prevent delete it.
obj.subNodes.clear();
}
void Game::loadRecord(ImageManager& imageManager, SDL_Renderer& renderer, const char* fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode obj;
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),&obj,true)){
cerr<<"ERROR: Can't load record file "<<s<<endl;
return;
}
}
//find the node named 'map'.
bool loaded=false;
for(unsigned int i=0;i<obj.subNodes.size();i++){
if(obj.subNodes[i]->name=="map"){
//load the level. (fileName=???)
loadLevelFromNode(imageManager,renderer,obj.subNodes[i],"???");
//remove this node to prevent delete it.
obj.subNodes[i]=NULL;
//over
loaded=true;
break;
}
}
if(!loaded){
cerr<<"ERROR: Can't find subnode named 'map' from record file"<<endl;
return;
}
//load the record.
{
vector<int> *record=player.getRecord();
record->clear();
vector<string> &v=obj.attributes["record"];
for(unsigned int i=0;i<v.size();i++){
string &s=v[i];
string::size_type pos=s.find_first_of('*');
if(pos==string::npos){
//1 item only.
int i=atoi(s.c_str());
record->push_back(i);
}else{
//contains many items.
int i=atoi(s.substr(0,pos).c_str());
int j=atoi(s.substr(pos+1).c_str());
for(;j>0;j--){
record->push_back(i);
}
}
}
}
#ifdef RECORD_FILE_DEBUG
//load the debug data
{
vector<string> &v=obj.attributes["recordPlayerPosition"];
vector<SDL_Rect> &playerPosition=player.playerPosition();
playerPosition.clear();
if(!v.empty()){
if(!v[0].empty()){
stringstream st(v[0]);
int m;
st>>m;
for(int i=0;i<m;i++){
SDL_Rect r;
st>>r.x>>r.y;
r.w=0;
r.h=0;
playerPosition.push_back(r);
}
}
}
}
#endif
//play the record.
//TODO: tell the level manager don't save the level progress.
player.playRecord();
shadow.playRecord(); //???
}
/////////////EVENT///////////////
void Game::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//First of all let the player handle input.
player.handleInput(&shadow);
//Check for an SDL_QUIT event.
if(event.type==SDL_QUIT){
//We need to quit so enter STATE_EXIT.
setNextState(STATE_EXIT);
}
//Check for the escape key.
if(stateID != STATE_LEVEL_EDITOR && inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
//Escape means we go one level up, to the level select state.
setNextState(STATE_LEVEL_SELECT);
//Save the progress.
levels->saveLevelProgress();
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}
//Check if 'R' is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_RESTART)){
//Restart game only if we are not watching a replay.
if (!player.isPlayFromRecord() || interlevel) {
//Reset the game at next frame.
isReset = true;
//Also delete any gui (most likely the interlevel gui). Only in game mode.
if (GUIObjectRoot && stateID != STATE_LEVEL_EDITOR){
delete GUIObjectRoot;
GUIObjectRoot = NULL;
}
//And set interlevel to false.
interlevel = false;
}
}
//Check for the next level buttons when in the interlevel popup.
if (inputMgr.isKeyDownEvent(INPUTMGR_SPACE) || inputMgr.isKeyDownEvent(INPUTMGR_SELECT)){
if(interlevel){
//The interlevel popup is shown so we need to delete it.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Now goto the next level.
gotoNextLevel(imageManager,renderer);
}
}
//Check if tab is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_TAB)){
//Switch the camera mode.
switch(cameraMode){
case CAMERA_PLAYER:
cameraMode=CAMERA_SHADOW;
break;
case CAMERA_SHADOW:
case CAMERA_CUSTOM:
cameraMode=CAMERA_PLAYER;
break;
}
}
}
/////////////////LOGIC///////////////////
void Game::logic(ImageManager& imageManager, SDL_Renderer& renderer){
//Add one tick to the time.
time++;
//NOTE: This code reverts some changes in commit 5f03ae5.
//This is part of old prepareFrame() code.
//This is needed since otherwise the script function block:setLocation() and block:moveTo() are completely broken.
//Later we should rewrite collision system completely which will remove this piece of code.
//NOTE: In new collision system the effect of dx/dy/xVel/yVel should only be restricted in one frame.
for (auto obj : levelObjects) {
switch (obj->type) {
default:
obj->dx = obj->dy = obj->xVel = obj->yVel = 0;
break;
case TYPE_PUSHABLE:
//NOTE: Currently the dx/dy/etc. of pushable blocks are still carry across frames, in order to make the collision system work correct.
break;
case TYPE_CONVEYOR_BELT: case TYPE_SHADOW_CONVEYOR_BELT:
//NOTE: We let the conveyor belt itself to reset its xVel/yVel.
obj->dx = obj->dy = 0;
break;
}
}
+ //NOTE2: The above code breaks pushable block with moving block in most cases,
+ //more precisely, if the pushable block is processed before the moving block then things may be broken.
+
//Process delay execution scripts.
getScriptExecutor()->processDelayExecution();
//Process any event in the queue.
for(unsigned int idx=0;idx<eventQueue.size();idx++){
//Get the event from the queue.
typeGameObjectEvent &e=eventQueue[idx];
//Check if the it has an id attached to it.
if(e.target){
//NOTE: Should we check if the target still exists???
e.target->onEvent(e.eventType);
}else if(e.flags&1){
//Loop through the levelObjects and give them the event if they have the right id.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
if(levelObjects[i]->id==e.id){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}else{
//Loop through the levelObjects and give them the event.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}
//Done processing the events so clear the queue.
eventQueue.clear();
//Check if we should save/load state.
//NOTE: This happens after event handling so no eventQueue has to be saved/restored.
if(saveStateNextTime){
saveState();
}else if(loadStateNextTime){
loadState();
}
saveStateNextTime=false;
loadStateNextTime=false;
//Loop through the gameobjects to update them.
for(unsigned int i=0;i<levelObjects.size();i++){
//Send GameObjectEvent_OnEnterFrame event to the script
levelObjects[i]->onEvent(GameObjectEvent_OnEnterFrame);
}
- for(unsigned int i=0;i<levelObjects.size();i++){
- //Let the gameobject handle movement.
- levelObjects[i]->move();
+ //Let the gameobject handle movement.
+ {
+ std::vector<Block*> pushableBlocks;
+
+ //First we process blocks which are not pushable blocks.
+ for (auto o : levelObjects) {
+ if (o->type == TYPE_PUSHABLE) {
+ pushableBlocks.push_back(o);
+ } else {
+ o->move();
+ }
+ }
+
+ //Sort pushable blocks by their position, which is an ad-hoc workaround for
+ //<https://forum.freegamedev.net/viewtopic.php?f=48&t=8047#p77692>.
+ std::stable_sort(pushableBlocks.begin(), pushableBlocks.end(),
+ [](const Block* obj1, const Block* obj2)->bool
+ {
+ SDL_Rect r1 = const_cast<Block*>(obj1)->getBox(), r2 = const_cast<Block*>(obj2)->getBox();
+ if (r1.y > r2.y) return true;
+ else if (r1.y < r2.y) return false;
+ else return r1.x < r2.x;
+ });
+
+ //Now we process pushable blocks.
+ for (auto o : pushableBlocks) {
+ o->move();
+ }
}
//Also update the scenery.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it){
it->second->updateAnimation();
}
//Let the player store his move, if recording.
player.shadowSetState();
//Let the player give his recording to the shadow, if configured.
player.shadowGiveState(&shadow);
//NOTE: to fix bugs regarding player/shadow swap, we should first process collision of player/shadow then move them
const SDL_Rect playerLastPosition = player.getBox();
const SDL_Rect shadowLastPosition = shadow.getBox();
//NOTE: The following is ad-hoc code to fix shadow on blocked player on conveyor belt bug
if (shadow.holdingOther) {
//We need to process shadow collision first if shadow is holding player.
//Let the shadow decide his move, if he's playing a recording.
shadow.moveLogic();
//Check collision for shadow.
shadow.collision(levelObjects, NULL);
//Get the new position of it.
const SDL_Rect r = shadow.getBox();
//Check collision for player. Transfer the velocity of shadow to it only if the shadow moves its position.
player.collision(levelObjects, (r.x != shadowLastPosition.x || r.y != shadowLastPosition.y) ? &shadow : NULL);
} else {
//Otherwise we process player first.
//Check collision for player.
player.collision(levelObjects, NULL);
//Get the new position of it.
const SDL_Rect r = player.getBox();
//Now let the shadow decide his move, if he's playing a recording.
shadow.moveLogic();
//Check collision for shadow. Transfer the velocity of player to it only if the player moves its position.
shadow.collision(levelObjects, (r.x != playerLastPosition.x || r.y != playerLastPosition.y) ? &player : NULL);
}
//Let the player move.
player.move(levelObjects, playerLastPosition.x, playerLastPosition.y);
//Let the shadow move.
shadow.move(levelObjects, shadowLastPosition.x, shadowLastPosition.y);
//Check collision and stuff for the shadow and player.
player.otherCheck(&shadow);
//Update the camera.
switch(cameraMode){
case CAMERA_PLAYER:
player.setMyCamera();
break;
case CAMERA_SHADOW:
shadow.setMyCamera();
break;
case CAMERA_CUSTOM:
//NOTE: The target is (should be) screen size independent so calculate the real target x and y here.
int targetX=cameraTarget.x-(SCREEN_WIDTH/2);
int targetY=cameraTarget.y-(SCREEN_HEIGHT/2);
//Move the camera to the cameraTarget.
if(camera.x>targetX){
camera.x-=(camera.x-targetX)>>4;
//Make sure we don't go too far.
if(camera.x<targetX)
camera.x=targetX;
}else if(camera.x<targetX){
camera.x+=(targetX-camera.x)>>4;
//Make sure we don't go too far.
if(camera.x>targetX)
camera.x=targetX;
}
if(camera.y>targetY){
camera.y-=(camera.y-targetY)>>4;
//Make sure we don't go too far.
if(camera.y<targetY)
camera.y=targetY;
}else if(camera.y<targetY){
camera.y+=(targetY-camera.y)>>4;
//Make sure we don't go too far.
if(camera.y>targetY)
camera.y=targetY;
}
break;
}
//Check if we won.
if(won){
//Check if it's playing from record
if(player.isPlayFromRecord() && !interlevel){
recordingEnded(imageManager,renderer);
}else{
//the string to store auto-save record path.
string bestTimeFilePath,bestRecordingFilePath;
//and if we can't get test path.
bool filePathError=false;
//Get current level
LevelPack::Level *level=levels->getLevel();
//Now check if we should update statistics
{
//Get previous and current medal
int oldMedal=level->won?1:0,newMedal=1;
int bestTime=level->time;
int targetTime=level->targetTime;
int bestRecordings=level->recordings;
int targetRecordings=level->targetRecordings;
if(oldMedal){
- if(targetTime<0){
- oldMedal=3;
- }else{
- if(bestTime>=0 && (targetTime<=0 || bestTime<=targetTime))
- oldMedal++;
- if(bestRecordings>=0 && (targetRecordings<0 || bestRecordings<=targetRecordings))
- oldMedal++;
- }
+ if(bestTime>=0 && (targetTime<0 || bestTime<=targetTime))
+ oldMedal++;
+ if(bestRecordings>=0 && (targetRecordings<0 || bestRecordings<=targetRecordings))
+ oldMedal++;
}else{
bestTime=time;
bestRecordings=recordings;
}
if(bestTime<0 || bestTime>time) bestTime=time;
if(bestRecordings<0 || bestRecordings>recordings) bestRecordings=recordings;
- if(targetTime<0){
- newMedal=3;
- }else{
- if(targetTime<=0 || bestTime<=targetTime)
- newMedal++;
- if(targetRecordings<0 || bestRecordings<=targetRecordings)
- newMedal++;
- }
+ if(targetTime<0 || bestTime<=targetTime)
+ newMedal++;
+ if(targetRecordings<0 || bestRecordings<=targetRecordings)
+ newMedal++;
//Check if we need to update statistics
if(newMedal>oldMedal){
switch(oldMedal){
case 0:
statsMgr.completedLevels++;
break;
case 2:
statsMgr.silverLevels--;
break;
}
switch(newMedal){
case 2:
statsMgr.silverLevels++;
break;
case 3:
statsMgr.goldLevels++;
break;
}
}
}
//Check the achievement "Complete a level with checkpoint, but without saving"
if (objLastCheckPoint.get() == NULL) {
for (auto obj : levelObjects) {
if (obj->type == TYPE_CHECKPOINT) {
statsMgr.newAchievement("withoutsave");
break;
}
}
}
//Set the current level won.
level->won=true;
if(level->time==-1 || level->time>time){
level->time=time;
//save the best-time game record.
if(bestTimeFilePath.empty()){
getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestTimeFilePath.empty()){
cerr<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
saveRecord(bestTimeFilePath.c_str());
}
}
if(level->recordings==-1 || level->recordings>recordings){
level->recordings=recordings;
//save the best-recordings game record.
if(bestRecordingFilePath.empty() && !filePathError){
getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestRecordingFilePath.empty()){
cerr<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
saveRecord(bestRecordingFilePath.c_str());
}
}
//Set the next level unlocked if it exists.
if(levels->getCurrentLevel()+1<levels->getLevelCount()){
levels->setLocked(levels->getCurrentLevel()+1);
}
//And save the progress.
levels->saveLevelProgress();
//Now go to the interlevel screen.
replayPlay(imageManager,renderer);
//Update achievements
if(levels->levelpackName=="tutorial") statsMgr.updateTutorialAchievements();
statsMgr.updateLevelAchievements();
//NOTE: We set isReset false to prevent the user from getting a best time of 0.00s and 0 recordings.
isReset = false;
}
}
won=false;
//Check if we should reset.
if (isReset) {
//NOTE: we don't need to reset save ??? it looks like that there are no bugs
reset(false, false);
}
isReset=false;
}
/////////////////RENDER//////////////////
void Game::render(ImageManager&,SDL_Renderer &renderer){
//First of all render the background.
{
//Get a pointer to the background.
ThemeBackground* bg=background;
//Check if the background is null, but there are themes.
if(bg==NULL && objThemes.themeCount()>0){
//Get the background from the first theme in the stack.
bg=objThemes[0]->getBackground(false);
}
//Check if the background isn't null.
if(bg){
//It isn't so draw it.
bg->draw(renderer);
//And if it's the loaded background then also update the animation.
//FIXME: Updating the animation in the render method?
if(bg==background)
bg->updateAnimation();
}else{
//There's no background so fill the screen with white.
SDL_SetRenderDrawColor(&renderer, 255,255,255,255);
SDL_RenderClear(&renderer);
}
}
//Now draw the blackground layers.
auto it = sceneryLayers.begin();
for (; it != sceneryLayers.end(); ++it){
if (it->first >= "f") break; // now we meet a foreground layer
it->second->show(renderer);
}
//Now we draw the levelObjects.
- for(unsigned int o=0; o<levelObjects.size(); o++){
- levelObjects[o]->show(renderer);
+ {
+ //NEW: always render the pushable blocks in front of other blocks
+ std::vector<Block*> pushableBlocks;
+
+ for (auto o : levelObjects) {
+ if (o->type == TYPE_PUSHABLE) {
+ pushableBlocks.push_back(o);
+ } else {
+ o->show(renderer);
+ }
+ }
+
+ for (auto o : pushableBlocks) {
+ o->show(renderer);
+ }
}
//Followed by the player and the shadow.
//NOTE: We draw the shadow first, because he needs to be behind the player.
shadow.show(renderer);
player.show(renderer);
//Now draw the foreground layers.
for (; it != sceneryLayers.end(); ++it){
it->second->show(renderer);
}
//Show the levelName if it isn't the level editor.
if(stateID!=STATE_LEVEL_EDITOR && bmTips[0]!=NULL && !interlevel){
withTexture(*bmTips[0], [&](SDL_Rect r){
drawGUIBox(-2,SCREEN_HEIGHT-r.h-4,r.w+8,r.h+6,renderer,0xFFFFFFFF);
applyTexture(2,SCREEN_HEIGHT-r.h,*bmTips[0],renderer,NULL);
});
}
//Check if there's a tooltip.
//NOTE: gameTipIndex 0 is used for the levelName, 1 for shadow death, 2 for restart text, 3 for restart+checkpoint.
if(gameTipIndex>3 && gameTipIndex<TYPE_MAX){
//Check if there's a tooltip for the type.
if(bmTips[gameTipIndex]==NULL){
//There isn't thus make it.
string s;
string keyCode = InputManagerKeyCode::describeTwo(inputMgr.getKeyCode(INPUTMGR_ACTION, false), inputMgr.getKeyCode(INPUTMGR_ACTION, true));
transform(keyCode.begin(),keyCode.end(),keyCode.begin(),::toupper);
switch(gameTipIndex){
case TYPE_CHECKPOINT:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to save the game."),keyCode);
break;
case TYPE_SWAP:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to swap the position of player and shadow."),keyCode);
break;
case TYPE_SWITCH:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to activate the switch."),keyCode);
break;
case TYPE_PORTAL:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to teleport."),keyCode);
break;
}
//If we have a string then it's a supported GameObject type.
if(!s.empty()){
bmTips[gameTipIndex]=textureFromText(renderer, *fontText, s.c_str(), objThemes.getTextColor(true));
}
}
//We already have a gameTip for this type so draw it.
if(bmTips[gameTipIndex]!=NULL){
withTexture(*bmTips[gameTipIndex], [&](SDL_Rect r){
drawGUIBox(-2,-2,r.w+8,r.h+6,renderer,0xFFFFFFFF);
applyTexture(2,2,*bmTips[gameTipIndex],renderer);
});
}
}
//Set the gameTip to 0.
gameTipIndex=0;
// Limit the scope of bm, as it's a borrowed pointer.
{
//Pointer to the sdl texture that will contain a message, if any.
SDL_Texture* bm=NULL;
//Check if the player is dead, meaning we draw a message.
if(player.dead){
//Get user configured restart key
string keyCodeRestart = InputManagerKeyCode::describeTwo(inputMgr.getKeyCode(INPUTMGR_RESTART, false), inputMgr.getKeyCode(INPUTMGR_RESTART, true));
transform(keyCodeRestart.begin(),keyCodeRestart.end(),keyCodeRestart.begin(),::toupper);
//The player is dead, check if there's a state that can be loaded.
if(player.canLoadState()){
//Now check if the tip is already made, if not make it.
if(bmTips[3]==NULL){
//Get user defined key for loading checkpoint
string keyCodeLoad = InputManagerKeyCode::describeTwo(inputMgr.getKeyCode(INPUTMGR_LOAD, false), inputMgr.getKeyCode(INPUTMGR_LOAD, true));
transform(keyCodeLoad.begin(),keyCodeLoad.end(),keyCodeLoad.begin(),::toupper);
//Draw string
bmTips[3]=textureFromText(renderer, *fontText,//TTF_RenderUTF8_Blended(fontText,
/// TRANSLATORS: Please do not remove %s from your translation:
/// - first %s means currently configured key to restart game
/// - Second %s means configured key to load from last save
tfm::format(_("Press %s to restart current level or press %s to load the game."),
keyCodeRestart,keyCodeLoad).c_str(),
objThemes.getTextColor(true));
}
bm=bmTips[3].get();
}else{
//Now check if the tip is already made, if not make it.
if(bmTips[2]==NULL){
bmTips[2]=textureFromText(renderer, *fontText,
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with currently configured key to restart game
tfm::format(_("Press %s to restart current level."),keyCodeRestart).c_str(),
objThemes.getTextColor(true));
}
bm=bmTips[2].get();
}
}
//Check if the shadow has died (and there's no other notification).
//NOTE: We use the shadow's jumptime as countdown, this variable isn't used when the shadow is dead.
if(shadow.dead && bm==NULL && shadow.jumpTime>0){
//Now check if the tip is already made, if not make it.
if(bmTips[1]==NULL){
bmTips[1]=textureFromText(renderer, *fontText,
_("Your shadow has died."),
objThemes.getTextColor(true));
}
bm=bmTips[1].get();
//NOTE: Logic in the render loop, we substract the shadow's jumptime by one.
shadow.jumpTime--;
//return view to player and keep it there
cameraMode=CAMERA_PLAYER;
}
//Draw the tip.
if(bm!=NULL){
const SDL_Rect textureSize = rectFromTexture(*bm);
int x=(SCREEN_WIDTH-textureSize.w)/2;
int y=32;
drawGUIBox(x-8,y-8,textureSize.w+16,textureSize.h+14,renderer,0xFFFFFFFF);
applyTexture(x,y,*bm,renderer);
}
}
//Show the number of collectables the user has collected if there are collectables in the level.
//We hide this when interlevel.
if(currentCollectables<=totalCollectables && totalCollectables!=0 && !interlevel && time>0){
if(collectablesTexture.needsUpdate(currentCollectables)) {
//Temp stringstream just to addup all the text nicely
std::stringstream temp;
temp << currentCollectables << "/" << totalCollectables;
collectablesTexture.update(currentCollectables,
textureFromText(renderer,
*fontText,
temp.str().c_str(),
objThemes.getTextColor(true)));
}
SDL_Rect bmSize = rectFromTexture(*collectablesTexture.get());
//Draw background
drawGUIBox(SCREEN_WIDTH-bmSize.w-34,SCREEN_HEIGHT-bmSize.h-4,bmSize.w+34+2,bmSize.h+4+2,renderer,0xFFFFFFFF);
//Draw the collectable icon
collectable.draw(renderer,SCREEN_WIDTH-50+12,SCREEN_HEIGHT-50+10);
//Draw text
applyTexture(SCREEN_WIDTH-50-bmSize.w+22,SCREEN_HEIGHT-bmSize.h,collectablesTexture.getTexture(),renderer);
}
//show time and records used in level editor.
if(stateID==STATE_LEVEL_EDITOR && time>0){
const SDL_Color fg=objThemes.getTextColor(true),bg={255,255,255,255};
const int alpha = 160;
if (recordingsTexture.needsUpdate(recordings)) {
recordingsTexture.update(recordings,
textureFromTextShaded(
renderer,
*fontText,
tfm::format(ngettext("%d recording","%d recordings",recordings).c_str(),recordings).c_str(),
fg,
bg
));
SDL_SetTextureAlphaMod(recordingsTexture.get(),alpha);
}
int y=SCREEN_HEIGHT - textureHeight(*recordingsTexture.get());
applyTexture(0,y,*recordingsTexture.get(), renderer);
if(timeTexture.needsUpdate(time)) {
const size_t len = 32;
char c[len];
- SDL_snprintf(c,len,"%-.2fs",time/40.0f);
+ SDL_snprintf(c,len,"%-.2fs",time/40.0);
timeTexture.update(time,
textureFromTextShaded(
renderer,
*fontText,
c,
fg,
bg
));
y-=textureHeight(*timeTexture.get());
}
applyTexture(0,y,*timeTexture.get(), renderer);
}
//Draw the current action in the upper right corner.
if(player.record){
const SDL_Rect r = { 0, 0, 50, 50 };
applyTexture(SCREEN_WIDTH - 50, 0, *action, renderer, &r);
} else if (shadow.state != 0){
const SDL_Rect r={50,0,50,50};
applyTexture(SCREEN_WIDTH-50,0,*action,renderer,&r);
}
//if the game is play from record then draw something indicates it
if(player.isPlayFromRecord()){
//Dim the screen if interlevel is true.
if( interlevel){
dimScreen(renderer,191);
}else if((time & 0x10)==0x10){
// FIXME: replace this ugly ad-hoc animation by a better one
const SDL_Rect r={50,0,50,50};
applyTexture(0,0,*action,renderer,&r);
applyTexture(0,SCREEN_HEIGHT-50,*action,renderer,&r);
applyTexture(SCREEN_WIDTH-50,SCREEN_HEIGHT-50,*action,renderer,&r);
}
}else if(auto blockId = player.objNotificationBlock.get()){
//If the player is in front of a notification block show the message.
//And it isn't a replay.
//Check if we need to update the notification message texture.
int maxWidth = 0;
int y = 20;
//We check against blockId rather than the full message, as blockId is most likely shorter.
if(notificationTexture.needsUpdate(blockId)) {
const std::string &untranslated_message=blockId->message;
std::string message=_CC(levels->getDictionaryManager(),untranslated_message);
std::vector<std::string> string_data;
//Trim the message.
{
size_t lps = message.find_first_not_of("\n\r \t");
if (lps == string::npos) {
message.clear(); // it's completely empty
} else {
message = message.substr(lps, message.find_last_not_of("\n\r \t") - lps + 1);
}
}
//Split the message into lines.
for (int lps = 0;;) {
// determine the end of line
int lpe = lps;
for (; message[lpe] != '\n' && message[lpe] != '\r' && message[lpe] != '\0'; lpe++);
string_data.push_back(message.substr(lps, lpe - lps));
// break if the string ends
if (message[lpe] == '\0') break;
// skip "\r\n" for Windows line ending
if (message[lpe] == '\r' && message[lpe + 1] == '\n') lpe++;
// point to the start of next line
lps = lpe + 1;
}
vector<SurfacePtr> lines;
//Create the image for each lines
for (int i = 0; i < (int)string_data.size(); i++) {
//Integer used to center the sentence horizontally.
int x = 0;
TTF_SizeUTF8(fontText, string_data[i].c_str(), &x, NULL);
//Find out largest width
if (x>maxWidth)
maxWidth = x;
lines.emplace_back(TTF_RenderUTF8_Blended(fontText, string_data[i].c_str(), objThemes.getTextColor(true)));
//Increase y with 25, about the height of the text.
y += 25;
}
maxWidth+=SCREEN_WIDTH*0.15;
SurfacePtr surf = createSurface(maxWidth, y);
int y1 = y;
for(SurfacePtr &s : lines) {
if(s) {
applySurface((surf->w-s->w)/2,surf->h - y1,s.get(),surf.get(),NULL);
}
y1 -= 25;
}
notificationTexture.update(blockId, textureUniqueFromSurface(renderer,std::move(surf)));
} else {
auto texSize = rectFromTexture(*notificationTexture.get());
maxWidth=texSize.w;
y=texSize.h;
}
drawGUIBox((SCREEN_WIDTH-maxWidth)/2,SCREEN_HEIGHT-y-25,maxWidth,y+20,renderer,0xFFFFFFBF);
applyTexture((SCREEN_WIDTH-maxWidth)/2,SCREEN_HEIGHT-y,notificationTexture.getTexture(),renderer);
}
}
void Game::resize(ImageManager&, SDL_Renderer& /*renderer*/){
//Check if the interlevel popup is shown.
if(interlevel && GUIObjectRoot){
GUIObjectRoot->left=(SCREEN_WIDTH-GUIObjectRoot->width)/2;
}
}
void Game::replayPlay(ImageManager& imageManager,SDL_Renderer& renderer){
//Set interlevel true.
interlevel=true;
//Make a copy of the playerButtons.
vector<int> recordCopy=player.recordButton;
//Reset the game.
//NOTE: We don't reset the saves. I'll see that if it will introduce bugs.
reset(false, false);
//Make the cursor visible when the interlevel popup is up.
SDL_ShowCursor(SDL_ENABLE);
//Set the copy of playerButtons back.
player.recordButton=recordCopy;
//Now play the recording.
player.playRecord();
//Create the gui if it isn't already done.
if(!GUIObjectRoot){
//Create a new GUIObjectRoot the size of the screen.
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Make child widgets change color properly according to theme.
GUIObjectRoot->inDialog=true;
//Create a GUIFrame for the upper frame.
GUIFrame* upperFrame=new GUIFrame(imageManager,renderer,0,4,0,74);
GUIObjectRoot->addChild(upperFrame);
//Render the You've finished: text and add it to a GUIImage.
//NOTE: The texture is managed by the GUIImage so no need to free it ourselfs.
auto bm = SharedTexture(textureFromText(renderer, *fontGUI,_("You've finished:"),objThemes.getTextColor(true)));
const SDL_Rect textureSize = rectFromTexture(*bm);
GUIImage* title=new GUIImage(imageManager,renderer,0,4-GUI_FONT_RAISE,textureSize.w,textureSize.h,bm);
upperFrame->addChild(title);
//Create the sub title.
string s;
if (levels->getLevelCount()>0){
/// TRANSLATORS: Please do not remove %s or %d from your translation:
/// - %d means the level number in a levelpack
/// - %s means the name of current level
s=tfm::format(_("Level %d %s"),levels->getCurrentLevel()+1,_CC(levels->getDictionaryManager(),levelName));
}
GUIObject* obj=new GUILabel(imageManager,renderer,0,44,0,28,s.c_str(),0,true,true,GUIGravityCenter);
upperFrame->addChild(obj);
obj->render(renderer,0,0,false);
//Determine the width the upper frame should have.
int width;
if(textureSize.w>obj->width)
width=textureSize.w+32;
else
width=obj->width+32;
//Set the left of the title.
title->left=(width-title->width)/2;
//Set the width of the level label to the width of the frame for centering.
obj->width=width;
//Now set the position and width of the frame.
upperFrame->width=width;
upperFrame->left=(SCREEN_WIDTH-width)/2;
//Now create a GUIFrame for the lower frame.
GUIFrame* lowerFrame=new GUIFrame(imageManager,renderer,0,SCREEN_HEIGHT-140,570,135);
GUIObjectRoot->addChild(lowerFrame);
//The different values.
int bestTime=levels->getLevel()->time;
int targetTime=levels->getLevel()->targetTime;
int bestRecordings=levels->getLevel()->recordings;
int targetRecordings=levels->getLevel()->targetRecordings;
int medal=1;
- if(targetTime<0){
- medal=3;
- }else{
- if(bestTime>=0 && (targetTime<=0 || bestTime<=targetTime))
- medal++;
- if(bestRecordings>=0 && (targetRecordings<0 || bestRecordings<=targetRecordings))
- medal++;
- }
-
+ if(bestTime>=0 && (targetTime<0 || bestTime<=targetTime))
+ medal++;
+ if(bestRecordings>=0 && (targetRecordings<0 || bestRecordings<=targetRecordings))
+ medal++;
+
int maxWidth=0;
int x=20;
//Is there a target time for this level?
int timeY=0;
bool isTargetTime=true;
- if(targetTime<=0){
+ if(targetTime<0){
isTargetTime=false;
timeY=12;
}
//Create the labels with the time and best time.
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
- obj=new GUILabel(imageManager,renderer,x,10+timeY,-1,36,tfm::format(_("Time: %-.2fs"),time/40.0f).c_str());
+ obj=new GUILabel(imageManager,renderer,x,10+timeY,-1,36,tfm::format(_("Time: %-.2fs"),time/40.0).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
- obj=new GUILabel(imageManager,renderer,x,34+timeY,-1,36,tfm::format(_("Best time: %-.2fs"),bestTime/40.0f).c_str());
+ obj=new GUILabel(imageManager,renderer,x,34+timeY,-1,36,tfm::format(_("Best time: %-.2fs"),bestTime/40.0).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
if(isTargetTime){
- obj=new GUILabel(imageManager,renderer,x,58,-1,36,tfm::format(_("Target time: %-.2fs"),targetTime/40.0f).c_str());
+ obj=new GUILabel(imageManager,renderer,x,58,-1,36,tfm::format(_("Target time: %-.2fs"),targetTime/40.0).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
}
x+=maxWidth+20;
//Is there target recordings for this level?
int recsY=0;
bool isTargetRecs=true;
- if(targetTime<0 || targetRecordings<0){
+ if(targetRecordings<0){
isTargetRecs=false;
recsY=12;
}
//Now the ones for the recordings.
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
obj=new GUILabel(imageManager,renderer,x,10+recsY,-1,36,tfm::format(_("Recordings: %d"),recordings).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
obj=new GUILabel(imageManager,renderer,x,34+recsY,-1,36,tfm::format(_("Best recordings: %d"),bestRecordings).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
if(isTargetRecs){
obj=new GUILabel(imageManager,renderer,x,58,-1,36,tfm::format(_("Target recordings: %d"),targetRecordings).c_str());
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
}
x+=maxWidth;
//The medal that is earned.
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with name of a prize medal (gold, silver or bronze)
string s1=tfm::format(_("You earned the %s medal"),(medal>1)?(medal==3)?_("GOLD"):_("SILVER"):_("BRONZE"));
obj=new GUILabel(imageManager,renderer,50,92,-1,36,s1.c_str(),0,true,true,GUIGravityCenter);
lowerFrame->addChild(obj);
obj->render(renderer,0,0,false);
if(obj->left+obj->width>x){
x=obj->left+obj->width+30;
}else{
obj->left=20+(x-20-obj->width)/2;
}
//Create the rectangle for the earned medal.
SDL_Rect r;
r.x=(medal-1)*30;
r.y=0;
r.w=30;
r.h=30;
//Create the medal on the left side.
obj=new GUIImage(imageManager,renderer,16,92,30,30,medals,r);
lowerFrame->addChild(obj);
//And the medal on the right side.
obj=new GUIImage(imageManager,renderer,x-24,92,30,30,medals,r);
lowerFrame->addChild(obj);
//Create the three buttons, Menu, Restart, Next.
/// TRANSLATORS: used as return to the level selector menu
GUIObject* b1=new GUIButton(imageManager,renderer,x,10,-1,36,_("Menu"),0,true,true,GUIGravityCenter);
b1->name="cmdMenu";
b1->eventCallback=this;
lowerFrame->addChild(b1);
b1->render(renderer,0,0,true);
/// TRANSLATORS: used as restart level
GUIObject* b2=new GUIButton(imageManager,renderer,x,50,-1,36,_("Restart"),0,true,true,GUIGravityCenter);
b2->name="cmdRestart";
b2->eventCallback=this;
lowerFrame->addChild(b2);
b2->render(renderer,0,0,true);
/// TRANSLATORS: used as next level
GUIObject* b3=new GUIButton(imageManager,renderer,x,90,-1,36,_("Next"),0,true,true,GUIGravityCenter);
b3->name="cmdNext";
b3->eventCallback=this;
lowerFrame->addChild(b3);
b3->render(renderer,0,0,true);
maxWidth=b1->width;
if(b2->width>maxWidth)
maxWidth=b2->width;
if(b3->width>maxWidth)
maxWidth=b3->width;
b1->left=b2->left=b3->left=x+maxWidth/2;
x+=maxWidth;
lowerFrame->width=x;
lowerFrame->left=(SCREEN_WIDTH-lowerFrame->width)/2;
}
}
void Game::recordingEnded(ImageManager& imageManager, SDL_Renderer& renderer){
//Check if it's a normal replay, if so just stop.
if(!interlevel){
//Show the cursor so that the user can press the ok button.
SDL_ShowCursor(SDL_ENABLE);
//Now show the message box.
msgBox(imageManager,renderer,_("Game replay is done."),MsgBoxOKOnly,_("Game Replay"));
//Go to the level select menu.
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}else{
//Instead of directly replaying we set won true to let the Game handle the replaying at the end of the update cycle.
won=true;
}
}
bool Game::canSaveState(){
return (player.canSaveState() && shadow.canSaveState());
}
bool Game::saveState(){
//Check if the player and shadow can save the current state.
if(canSaveState()){
//Let the player and the shadow save their state.
player.saveState();
shadow.saveState();
//Save the stats.
timeSaved=time;
recordingsSaved=recordings;
recentSwapSaved=recentSwap;
//Save the camera mode and target.
cameraModeSaved=cameraMode;
cameraTargetSaved=cameraTarget;
//Save the current collectables
currentCollectablesSaved=currentCollectables;
//Save scripts.
copyCompiledScripts(getScriptExecutor()->getLuaState(), compiledScripts, savedCompiledScripts);
//Save other state, for example moving blocks.
copyLevelObjects(levelObjects, levelObjectsSave, false);
//Also save states of scenery layers.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
it->second->saveAnimation();
}
//Also save the background animation, if any.
if(background)
background->saveAnimation();
if(!player.isPlayFromRecord() && !interlevel){
//Update achievements
Uint32 t=SDL_GetTicks()+5000; //Add a bias to prevent bugs
if(recentSave+1000>t){
statsMgr.newAchievement("panicSave");
}
recentSave=t;
//Update statistics.
statsMgr.saveTimes++;
//Update achievements
switch(statsMgr.saveTimes){
case 1000:
statsMgr.newAchievement("save1k");
break;
}
}
//Save the state for script executor.
getScriptExecutor()->saveState();
//Execute the onSave event.
executeScript(LevelEvent_OnSave);
//Return true.
return true;
}
//We can't save the state so return false.
return false;
}
bool Game::loadState(){
//Check if there's a state that can be loaded.
if(player.canLoadState() && shadow.canLoadState()){
//Let the player and the shadow load their state.
player.loadState();
shadow.loadState();
//Load the stats.
time=timeSaved;
recordings=recordingsSaved;
recentSwap=recentSwapSaved;
//Load the camera mode and target.
cameraMode=cameraModeSaved;
cameraTarget=cameraTargetSaved;
//Load the current collactbles
currentCollectables=currentCollectablesSaved;
//Load scripts.
copyCompiledScripts(getScriptExecutor()->getLuaState(), savedCompiledScripts, compiledScripts);
//Load other state, for example moving blocks.
copyLevelObjects(levelObjectsSave, levelObjects, true);
//Also load states of scenery layers.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
it->second->loadAnimation();
}
//Also load the background animation, if any.
if(background)
background->loadAnimation();
if(!player.isPlayFromRecord() && !interlevel){
//Update achievements.
Uint32 t=SDL_GetTicks()+5000; //Add a bias to prevent bugs
if(recentLoad+1000>t){
statsMgr.newAchievement("panicLoad");
}
recentLoad=t;
//Update statistics.
statsMgr.loadTimes++;
//Update achievements
switch(statsMgr.loadTimes){
case 1000:
statsMgr.newAchievement("load1k");
break;
}
}
//Load the state for script executor.
getScriptExecutor()->loadState();
//Execute the onLoad event, if any.
executeScript(LevelEvent_OnLoad);
//Return true.
return true;
}
//We can't load the state so return false.
return false;
}
void Game::reset(bool save,bool noScript){
//Some sanity check, i.e. if we switch from no-script mode to script mode, we should always reset the save
assert(noScript || getScriptExecutor() || save);
//We need to reset the game so we also reset the player and the shadow.
player.reset(save);
shadow.reset(save);
saveStateNextTime=false;
loadStateNextTime=false;
//Reset the stats if interlevel isn't true.
if(!interlevel){
time=0;
recordings=0;
}
recentSwap=-10000;
if(save) recentSwapSaved=-10000;
//Reset the camera.
cameraMode=CAMERA_PLAYER;
if(save) cameraModeSaved=CAMERA_PLAYER;
cameraTarget.x=cameraTarget.y=cameraTarget.w=cameraTarget.h=0;
if(save) cameraTargetSaved.x=cameraTargetSaved.y=cameraTargetSaved.w=cameraTargetSaved.h=0;
//Reset the number of collectables
currentCollectables=0;
if(save)
currentCollectablesSaved=0;
//Clear the event queue, since all the events are from before the reset.
eventQueue.clear();
//Reset states of scenery layers.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
it->second->resetAnimation(save);
}
//Also reset the background animation, if any.
if(background)
background->resetAnimation(save);
//Reset the cached notification block
notificationTexture.update(NULL, NULL);
//Reset the script environment if necessary.
if (noScript) {
//Destroys the script environment completely.
scriptExecutor->destroy();
//Clear the level script.
compiledScripts.clear();
savedCompiledScripts.clear();
initialCompiledScripts.clear();
//Clear the block script.
for (auto block : levelObjects){
block->compiledScripts.clear();
}
for (auto block : levelObjectsSave){
block->compiledScripts.clear();
}
for (auto block : levelObjectsInitial){
block->compiledScripts.clear();
}
} else if (save) {
//Create a new script environment.
getScriptExecutor()->reset(true);
//Recompile the level script.
compiledScripts.clear();
savedCompiledScripts.clear();
initialCompiledScripts.clear();
for (auto it = scripts.begin(); it != scripts.end(); ++it){
int index = getScriptExecutor()->compileScript(it->second);
compiledScripts[it->first] = index;
lua_rawgeti(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX, index);
savedCompiledScripts[it->first] = luaL_ref(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX);
lua_rawgeti(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX, index);
initialCompiledScripts[it->first] = luaL_ref(getScriptExecutor()->getLuaState(), LUA_REGISTRYINDEX);
}
//Recompile the block script.
for (auto block : levelObjects){
block->compiledScripts.clear();
}
for (auto block : levelObjectsSave){
block->compiledScripts.clear();
}
for (auto block : levelObjectsInitial) {
block->compiledScripts.clear();
for (auto it = block->scripts.begin(); it != block->scripts.end(); ++it){
int index = getScriptExecutor()->compileScript(it->second);
block->compiledScripts[it->first] = index;
}
}
} else {
assert(getScriptExecutor());
//Do a soft reset.
getScriptExecutor()->reset(false);
//Restore the level script to initial state.
copyCompiledScripts(getScriptExecutor()->getLuaState(), initialCompiledScripts, compiledScripts);
//NOTE: We don't need to restore the block script since it will be restored automatically when the block array is copied.
}
//We reset levelObjects here since we need to wait the compiledScripts being initialized.
copyLevelObjects(levelObjectsInitial, levelObjects, true);
if (save) {
copyLevelObjects(levelObjectsInitial, levelObjectsSave, false);
}
//Also reset the last checkpoint so set it to NULL.
if (save)
objLastCheckPoint = NULL;
//Call the level's onCreate event.
executeScript(LevelEvent_OnCreate);
//Send GameObjectEvent_OnCreate event to the script
for (auto block : levelObjects) {
block->onEvent(GameObjectEvent_OnCreate);
}
//Close exit(s) if there are any collectables
if (totalCollectables>0){
for (auto block : levelObjects){
if (block->type == TYPE_EXIT){
block->onEvent(GameObjectEvent_OnSwitchOff);
}
}
}
//Hide the cursor (if not the leveleditor).
if(stateID!=STATE_LEVEL_EDITOR)
SDL_ShowCursor(SDL_DISABLE);
}
void Game::executeScript(int eventType){
map<int,int>::iterator it;
//Check if there's a script for the given event.
it=compiledScripts.find(eventType);
if(it!=compiledScripts.end()){
//There is one so execute it.
getScriptExecutor()->executeScript(it->second);
}
}
void Game::broadcastObjectEvent(int eventType,int objectType,const char* id,GameObject* target){
//Create a typeGameObjectEvent that can be put into the queue.
typeGameObjectEvent e;
//Set the event type.
e.eventType=eventType;
//Set the object type.
e.objectType=objectType;
//By default flags=0.
e.flags=0;
//Unless there's an id.
if(id){
//Set flags to 0x1 and set the id.
e.flags|=1;
e.id=id;
}
//Or there's a target given.
if(target)
e.target=target;
else
e.target=NULL;
//Add the event to the queue.
eventQueue.push_back(e);
}
void Game::getCurrentLevelAutoSaveRecordPath(std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath){
levels->getLevelAutoSaveRecordPath(-1,bestTimeFilePath,bestRecordingFilePath,createPath);
}
void Game::gotoNextLevel(ImageManager& imageManager, SDL_Renderer& renderer){
//Goto the next level.
levels->nextLevel();
//Check if the level exists.
if(levels->getCurrentLevel()<levels->getLevelCount()){
setNextState(STATE_GAME);
}else{
if(!levels->congratulationText.empty()){
msgBox(imageManager,renderer,_CC(levels->getDictionaryManager(),levels->congratulationText),MsgBoxOKOnly,_("Congratulations"));
}else{
msgBox(imageManager,renderer,_("You have finished the levelpack!"),MsgBoxOKOnly,_("Congratulations"));
}
//Now go back to the levelselect screen.
setNextState(STATE_LEVEL_SELECT);
//And set the music back to menu.
getMusicManager()->playMusic("menu");
}
}
void Game::GUIEventCallback_OnEvent(ImageManager& imageManager,SDL_Renderer& renderer, string name,GUIObject* obj,int eventType){
if(name=="cmdMenu"){
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}else if(name=="cmdRestart"){
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
interlevel=false;
//And reset the game.
//NOTE: We don't need to clear the save game because in level replay the game won't be saved (??)
//TODO: it seems work (??); I'll see if it introduce bugs
reset(false, false);
}else if(name=="cmdNext"){
//No matter what, clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//And goto the next level.
gotoNextLevel(imageManager,renderer);
}
}
diff --git a/src/LevelEditor.cpp b/src/LevelEditor.cpp
index a5d12f3..d32af58 100644
--- a/src/LevelEditor.cpp
+++ b/src/LevelEditor.cpp
@@ -1,4844 +1,4891 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Block.h"
#include "Commands.h"
#include "CommandManager.h"
#include "GameState.h"
#include "Functions.h"
#include "GameObjects.h"
#include "ThemeManager.h"
#include "LevelPack.h"
#include "LevelEditor.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "GUIListBox.h"
#include "GUITextArea.h"
#include "GUIWindow.h"
#include "GUISpinBox.h"
#include "MusicManager.h"
#include "InputManager.h"
#include "StatisticsManager.h"
#include <fstream>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string.h>
#include <stdio.h>
#include <sstream>
#include <SDL_ttf.h>
#include "libs/tinyformat/tinyformat.h"
using namespace std;
static int levelTime,levelRecordings;
//Array containing translateble block names
static const char* blockNames[TYPE_MAX]={
__("Block"),__("Player Start"),__("Shadow Start"),
__("Exit"),__("Shadow Block"),__("Spikes"),
__("Checkpoint"),__("Swap"),__("Fragile"),
__("Moving Block"),__("Moving Shadow Block"),__("Moving Spikes"),
__("Teleporter"),__("Button"),__("Switch"),
__("Conveyor Belt"),__("Shadow Conveyor Belt"),__("Notification Block"),__("Collectable"),__("Pushable")
};
static const std::array<const char*, static_cast<size_t>(ToolTips::TooltipMax)> tooltipNames = {
__("Select"), __("Add"), __("Delete"), __("Play"), "", "", __("Level settings"), __("Save level"), __("Back to menu"), __("Configure")
};
+static const std::array<const char*, static_cast<size_t>(ToolTips::TooltipMax)> tooltipHotkey = {
+ "F2", "F3", "F4", "F5", "", "", "", "Ctrl+S", "", ""
+};
+
+static const std::array<int, static_cast<size_t>(ToolTips::TooltipMax)> tooltipHotkey2 = {
+ -1, -1, -1, -1, -1, -1, INPUTMGR_TAB, -1, INPUTMGR_ESCAPE, -1
+};
//Array indicates if block is linkable
static const bool isLinkable[TYPE_MAX]={
false,false,false,
false,false,false,
false,false,false,
true,true,true,
true,true,true,
false,false,false,false
};
static std::string describeSceneryName(const std::string& name) {
//Check if the input is a valid block name.
if (name.size() > 8 && name.substr(name.size() - 8) == "_Scenery") {
auto it = Game::blockNameMap.find(name.substr(0, name.size() - 8));
if (it != Game::blockNameMap.end()){
return tfm::format(_("%s (Scenery)"), _(blockNames[it->second]));
}
}
return name;
}
/////////////////LevelEditorActionsPopup/////////////////
class LevelEditorActionsPopup:private GUIEventCallback{
private:
//The parent object.
LevelEditor* parent;
//The position and size of window.
SDL_Rect rect;
//Array containing the actions in this popup.
GUIListBox* actions;
//GUI image.
SDL_Surface* bmGUI;
//Pointer to the object the actions apply to.
GameObject* target;
//The behaviour names.
vector<string> behaviour;
//The fragile block states.
vector<string> states;
//The separator texture.
//NOTE: It's created only when it's used. So don't access it directly!
SharedTexture separatorTexture;
//Get or create a new separator texture.
SharedTexture getOrCreateSeparatorTexture(SDL_Renderer& renderer) {
if (!separatorTexture) {
//NOTE: we use an arbitrart width since it will be updated soon.
createSeparatorTexture(renderer, 16);
}
return separatorTexture;
}
void createSeparatorTexture(SDL_Renderer& renderer, int width) {
//create surface
SurfacePtr surface = createSurface(width, 5);
//draw horizontal separator
const SDL_Rect r0 = { 4, 1, width - 8, 1 };
const SDL_Rect r1 = { 4, 3, width - 8, 1 };
const SDL_Rect r2 = { 4, 2, width - 8, 1 };
Uint32 c0 = SDL_MapRGB(surface->format, 192, 192, 192);
Uint32 c2 = SDL_MapRGB(surface->format, 64, 64, 64);
SDL_FillRect(surface.get(), &r0, c0);
SDL_FillRect(surface.get(), &r1, c0);
SDL_FillRect(surface.get(), &r2, c2);
//over
separatorTexture = textureFromSurface(renderer, std::move(surface));
}
void updateSeparators(SDL_Renderer& renderer) {
createSeparatorTexture(renderer, rect.w);
for (unsigned int i = 0; i < actions->item.size(); i++) {
if (actions->item[i] == "-") {
actions->updateItem(renderer, i, "-", separatorTexture);
}
}
}
public:
SDL_Rect getRect(){
return rect;
}
void dismiss(){
//Remove the actionsPopup from the parent.
if(parent!=NULL && parent->actionsPopup==this){
parent->actionsPopup=NULL;
}
//And delete ourself.
delete this;
}
SharedTexture createItem(SDL_Renderer& renderer,const char* caption,int icon,bool grayed=false){
//FIXME: Add some sort of caching?
//We draw using surfaces and convert to a texture in the end for now.
SDL_Color color = objThemes.getTextColor(true);
if (grayed) {
color.r = 128 + color.r / 2;
color.g = 128 + color.g / 2;
color.b = 128 + color.b / 2;
}
SurfacePtr tip(TTF_RenderUTF8_Blended(fontText,caption,color));
SDL_SetSurfaceBlendMode(tip.get(), SDL_BLENDMODE_NONE);
//Create the surface, we add 16px to the width for an icon,
//plus 8px for the border to make it looks better.
SurfacePtr item = createSurface(tip->w + 24 + (icon >= 0x100 ? 24 : 0), 24);
//Draw the text on the item surface.
applySurface(24 + (icon >= 0x100 ? 24 : 0), 0, tip.get(), item.get(), NULL);
//Temporarily set the blend mode of bmGUI to none, which simply copies RGBA channels to destination.
SDL_BlendMode oldBlendMode = SDL_BLENDMODE_BLEND;
SDL_GetSurfaceBlendMode(bmGUI, &oldBlendMode);
SDL_SetSurfaceBlendMode(bmGUI, SDL_BLENDMODE_NONE);
//Check if we should draw an icon.
if(icon>0){
//Draw the check (or not).
SDL_Rect r={0,0,16,16};
r.x=((icon-1)%8)*16;
r.y=(((icon-1)/8)%8)*16;
applySurface(4,3,bmGUI,item.get(),&r);
}
//Check if we should draw a secondary icon.
if (icon >= 0x100) {
SDL_Rect r = { 0, 0, 16, 16 };
r.x = ((icon / 0x100 - 1) % 8) * 16;
r.y = (((icon / 0x100 - 1) / 8) % 8) * 16;
applySurface(28, 3, bmGUI, item.get(), &r);
}
//Reset the blend mode of bmGUI.
SDL_SetSurfaceBlendMode(bmGUI, oldBlendMode);
//Check if we should update the width., 8px extra on the width is for four pixels spacing on either side.
if(item->w+8>rect.w) {
rect.w=item->w+8;
}
return textureFromSurface(renderer, std::move(item));
}
void updateListBoxSize() {
//Update the size of the GUIListBox.
actions->width = rect.w;
actions->height = rect.h;
int x = rect.x, y = rect.y;
if (x>SCREEN_WIDTH - rect.w) x = SCREEN_WIDTH - rect.w;
else if (x<0) x = 0;
if (y>SCREEN_HEIGHT - rect.h) y = SCREEN_HEIGHT - rect.h;
else if (y<0) y = 0;
rect.x = x;
rect.y = y;
}
void updateItem(SDL_Renderer& renderer,int index,const char* action,const char* caption,int icon=0,bool grayed=false){
auto item=createItem(renderer,caption,icon,grayed);
actions->updateItem(renderer, index,action,item,!grayed);
//Update the size of the GUIListBox.
updateListBoxSize();
}
void addItem(SDL_Renderer& renderer,const char* action,const char* caption,int icon=0,bool grayed=false){
auto item=createItem(renderer,caption,icon,grayed);
actions->addItem(renderer,action,item,!grayed);
//Update the height.
rect.h += 24;
//Update the size of the GUIListBox.
updateListBoxSize();
}
void addSeparator(SDL_Renderer& renderer) {
actions->addItem(renderer, "-", getOrCreateSeparatorTexture(renderer), false);
//Update the height.
rect.h += 5;
}
LevelEditorActionsPopup(ImageManager& imageManager,SDL_Renderer& renderer,LevelEditor* parent, GameObject* target, int x=0, int y=0){
this->parent=parent;
this->target=target;
rect.x = x;
rect.y = y;
//NOTE: The size gets set in the addItem method, height is already four to prevent a scrollbar.
rect.w=0;
rect.h=4;
//Load the gui images.
bmGUI=imageManager.loadImage(getDataPath()+"gfx/gui.png");
//Create the behaviour vector.
behaviour.push_back(_("On"));
behaviour.push_back(_("Off"));
behaviour.push_back(_("Toggle"));
//Create the states list.
states.push_back(_("Complete"));
states.push_back(_("One step"));
states.push_back(_("Two steps"));
states.push_back(_("Gone"));
//TODO: The width should be based on the longest option.
//Create default actions.
//NOTE: Width and height are determined later on when the options are rendered.
actions=new GUIListBox(imageManager,renderer,0,0,0,0);
actions->eventCallback=this;
//Check if it's a block or not.
if(target!=NULL)
addBlockItems(renderer);
else
addLevelItems(renderer);
}
static std::string getRepeatModeName(int mode) {
switch (mode) {
case Scenery::NEGATIVE_INFINITY:
return _("Negative infinity");
case Scenery::ZERO:
return _("Zero");
case Scenery::LEVEL_SIZE:
return _("Level size");
case Scenery::POSITIVE_INFINITY:
return _("Positive infinity");
default:
return _("Default");
}
}
void addBlockItems(SDL_Renderer& renderer){
//Check if the block is selected or not.
std::vector<GameObject*>::iterator it;
it=find(parent->selection.begin(),parent->selection.end(),target);
if(it!=parent->selection.end())
addItem(renderer,"Deselect",_("Deselect"));
else
addItem(renderer,"Select",_("Select"));
addItem(renderer,"Delete",_("Delete"),8);
addSeparator(renderer);
Scenery *scenery = dynamic_cast<Scenery*>(target);
if (scenery) {
// it is scenery block
addItem(renderer, "RepeatMode0", tfm::format(_("Horizontal repeat start: %s"),
getRepeatModeName(scenery->repeatMode & 0xFF)).c_str(), 8 * 2 + 3);
addItem(renderer, "RepeatMode1", tfm::format(_("Horizontal repeat end: %s"),
getRepeatModeName((scenery->repeatMode >> 8) & 0xFF)).c_str(), 8 * 2 + 4);
addItem(renderer, "RepeatMode2", tfm::format(_("Vertical repeat start: %s"),
getRepeatModeName((scenery->repeatMode >> 16) & 0xFF)).c_str(), 8 * 3 + 3);
addItem(renderer, "RepeatMode3", tfm::format(_("Vertical repeat end: %s"),
getRepeatModeName((scenery->repeatMode >> 24) & 0xFF)).c_str(), 8 * 3 + 4);
if (scenery->sceneryName_.empty()) {
addSeparator(renderer);
addItem(renderer, "CustomScenery", _("Custom scenery"), 8 + 4);
}
return;
}
addItem(renderer, "Visible", _("Visible"), (target->getEditorProperty("visible") == "1") ? 2 : 1);
//Get the type of the target.
int type = target->type;
//Determine what to do depending on the type.
if(isLinkable[type]){
//Check if it's a moving block type or trigger.
if(type==TYPE_BUTTON || type==TYPE_SWITCH || type==TYPE_PORTAL){
addItem(renderer, "Link", _("Link"), 8 * 2 + 8);
addItem(renderer, "Remove Links", _("Remove Links"), 8 * 2 + 7);
//Check if it's a portal, which contains a automatic option, and triggers a behaviour one.
if(type==TYPE_PORTAL){
addItem(renderer,"Automatic",_("Automatic"),(target->getEditorProperty("automatic")=="1")?2:1);
}else{
//Get the current behaviour.
int currentBehaviour=2;
if(target->getEditorProperty("behaviour")=="on"){
currentBehaviour=0;
}else if(target->getEditorProperty("behaviour")=="off"){
currentBehaviour=1;
}
addItem(renderer, "Behaviour", tfm::format(_("Behavior: %s"), behaviour[currentBehaviour]).c_str());
}
}else{
addItem(renderer, "Path", _("Path"), 8 + 5);
addItem(renderer, "Remove Path", _("Remove Path"), 8 * 4 + 2);
addItem(renderer,"Activated",_("Activated"),(target->getEditorProperty("activated")=="1")?2:1);
addItem(renderer,"Looping",_("Looping"),(target->getEditorProperty("loop")=="1")?2:1);
}
}
//Check for a conveyor belt.
if(type==TYPE_CONVEYOR_BELT || type==TYPE_SHADOW_CONVEYOR_BELT){
addItem(renderer,"Activated",_("Activated"),(target->getEditorProperty("activated")=="1")?2:1);
addItem(renderer,"Speed",_("Speed"));
}
//Check if it's a fragile block.
if(type==TYPE_FRAGILE){
//Get the current state.
int currentState=atoi(target->getEditorProperty("state").c_str());
addItem(renderer, "State", tfm::format(_("State: %s"), states[currentState]).c_str());
}
//Check if it's a notification block.
if(type==TYPE_NOTIFICATION_BLOCK)
addItem(renderer,"Message",_("Message"));
//Add the custom appearance menu item.
addItem(renderer, "Appearance", _("Appearance"), 8 + 4);
addSeparator(renderer);
//Finally add scripting to the bottom.
addItem(renderer,"Scripting",_("Scripting"),8*2+1);
}
void addLevelItems(SDL_Renderer& renderer){
// add the layers
{
// background layers.
auto it = parent->sceneryLayers.begin();
for (; it != parent->sceneryLayers.end(); ++it){
if (it->first >= "f") break; // now we meet a foreground layer
int icon = parent->layerVisibility[it->first] ? (8 * 3 + 1) : (8 * 3 + 2);
- icon |= (parent->selectedLayer == it->first ? 2 : 1) << 8;
+ icon |= (parent->selectedLayer == it->first ? 3 : 36) << 8;
std::string s = "_layer:" + it->first;
addItem(renderer, s.c_str(), tfm::format(_("Background layer: %s"), it->first).c_str(), icon);
}
// the Blocks layer.
{
int icon = parent->layerVisibility[std::string()] ? (8 * 3 + 1) : (8 * 3 + 2);
- icon |= (parent->selectedLayer.empty() ? 2 : 1) << 8;
+ icon |= (parent->selectedLayer.empty() ? 3 : 36) << 8;
addItem(renderer, "_layer:", _("Blocks layer"), icon);
}
// foreground layers.
for (; it != parent->sceneryLayers.end(); ++it){
int icon = parent->layerVisibility[it->first] ? (8 * 3 + 1) : (8 * 3 + 2);
- icon |= (parent->selectedLayer == it->first ? 2 : 1) << 8;
+ icon |= (parent->selectedLayer == it->first ? 3 : 36) << 8;
std::string s = "_layer:" + it->first;
addItem(renderer, s.c_str(), tfm::format(_("Foreground layer: %s"), it->first).c_str(), icon);
}
}
addSeparator(renderer);
addItem(renderer, "AddLayer", _("Add new layer"), 8 * 3 + 6);
addItem(renderer, "DeleteLayer", _("Delete selected layer"), 8 * 3 + 7, parent->selectedLayer.empty());
addItem(renderer, "LayerSettings", _("Configure selected layer"), 8 * 3 + 8, parent->selectedLayer.empty());
addItem(renderer, "MoveToLayer", _("Move selected object to layer"), 0, parent->selectedLayer.empty() || parent->selection.empty());
addSeparator(renderer);
addItem(renderer,"LevelSettings",_("Settings"),8*2);
addItem(renderer,"LevelScripting",_("Scripting"),8*2+1);
}
virtual ~LevelEditorActionsPopup(){
//bmGui is freed by imageManager.
if(actions)
delete actions;
}
void render(SDL_Renderer& renderer){
//Check if we need to resize the separator.
//NOTE: if separatorTexture is NULL then it means that we didn't use the separator at all, so we don't need to update it.
if (separatorTexture && textureWidth(*separatorTexture) < rect.w) {
updateSeparators(renderer);
}
//Draw the actions.
actions->render(renderer,rect.x,rect.y);
}
void handleEvents(SDL_Renderer& renderer){
//Check if a mouse is pressed outside the popup.
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
if(event.type==SDL_MOUSEBUTTONDOWN && !pointOnRect(mouse,rect)){
dismiss();
return;
}
//Let the listbox handle its events.
actions->handleEvents(renderer,rect.x,rect.y);
}
static void addLayerNameNote(ImageManager& imageManager, SDL_Renderer& renderer, GUIWindow *root, int yy = 148) {
std::string s = _("NOTE: the layers are sorted by name alphabetically.\nThe layer is background layer if its name is < 'f'\nby dictionary order, otherwise it's foreground layer.");
for (int lps = 0;;) {
size_t lpe = s.find_first_of('\n', lps);
GUIObject *obj = new GUILabel(imageManager, renderer, 40, yy, 520, 36,
lpe == string::npos ? (s.c_str() + lps) : s.substr(lps, lpe - lps).c_str());
root->addChild(obj);
if (lpe == string::npos) break;
lps = lpe + 1;
yy += 24;
}
}
void GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Skip next mouse up event since we're clicking a list box and possibly showing a new window.
GUISkipNextMouseUpEvent = true;
//NOTE: There should only be one GUIObject, so we know what event is fired.
//Get the selected entry.
std::string action=actions->item[actions->value];
if(action=="Select"){
//Add the target to the selection.
parent->selection.push_back(target);
dismiss();
return;
}else if(action=="Deselect"){
//Check if the block is in the selection.
std::vector<GameObject*>::iterator it;
it=find(parent->selection.begin(),parent->selection.end(),target);
if(it!=parent->selection.end()){
//Remove the object from the selection.
parent->selection.erase(it);
}
dismiss();
return;
}else if(action=="Delete"){
parent->commandManager->doCommand(new AddRemoveGameObjectCommand(parent, target, false));
dismiss();
return;
}else if(action=="Link"){
parent->linking=true;
parent->linkingTrigger=dynamic_cast<Block*>(target);
parent->tool=LevelEditor::SELECT;
dismiss();
return;
}else if(action=="Remove Links"){
//Remove all the links
Block *block = dynamic_cast<Block*>(target);
if (block) {
RemoveLinkCommand* pCommand = new RemoveLinkCommand(parent, block);
parent->commandManager->doCommand(pCommand);
}
dismiss();
return;
}else if(action=="Path"){
parent->moving=true;
parent->pauseMode = false;
parent->pauseTime = 0;
parent->movingBlock=dynamic_cast<Block*>(target);
parent->tool=LevelEditor::SELECT;
dismiss();
return;
}else if(action=="Remove Path"){
//Remove all the paths
Block *block = dynamic_cast<Block*>(target);
if (block) {
RemovePathCommand* pCommand = new RemovePathCommand(parent, block);
parent->commandManager->doCommand(pCommand);
}
dismiss();
return;
}else if(action=="Message"){
//Create the GUI.
GUIWindow* root=new GUIWindow(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-250)/2,600,250,true,true,_("Notification block"));
root->minWidth = root->width; root->minHeight = root->height;
root->name="notificationBlockWindow";
root->eventCallback=parent;
GUIObject* obj;
obj=new GUILabel(imageManager,renderer,40,50,240,36,_("Enter message here:"));
root->addChild(obj);
GUITextArea* textarea=new GUITextArea(imageManager,renderer,50,90,500,100);
textarea->gravityRight = textarea->gravityBottom = GUIGravityRight;
//Set the name of the text area, which is used to identify the object later on.
textarea->name="message";
string tmp=target->getEditorProperty("message");
//Change \n with the characters '\n'.
while(tmp.find("\\n")!=string::npos){
tmp=tmp.replace(tmp.find("\\n"),2,"\n");
}
textarea->setString(renderer, tmp);
root->addChild(textarea);
obj=new GUIButton(imageManager,renderer,root->width*0.3,250-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgNotificationBlockOK";
obj->eventCallback=root;
root->addChild(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.7,250-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
}else if(action=="Activated"){
//Get the previous state.
bool enabled=(target->getEditorProperty("activated")=="1");
//Switch the state.
enabled=!enabled;
parent->commandManager->doCommand(new SetEditorPropertyCommand(parent, imageManager, renderer,
target, "activated", enabled ? "1" : "0", _("Activated")));
updateItem(renderer,actions->value,"Activated",_("Activated"),enabled?2:1);
actions->value=-1;
return;
} else if (action == "Visible"){
//Get the previous state.
bool visible = (target->getEditorProperty("visible") == "1");
//Switch the state.
visible = !visible;
parent->commandManager->doCommand(new SetEditorPropertyCommand(parent, imageManager, renderer,
target, "visible", visible ? "1" : "0", _("Visible")));
updateItem(renderer, actions->value, "Visible", _("Visible"), visible ? 2 : 1);
actions->value = -1;
return;
} else if (action == "Looping"){
//Get the previous state.
bool loop=(target->getEditorProperty("loop")=="1");
//Switch the state.
loop=!loop;
parent->commandManager->doCommand(new SetEditorPropertyCommand(parent, imageManager, renderer,
target, "loop", loop ? "1" : "0", _("Looping")));
updateItem(renderer,actions->value,"Looping",_("Looping"),loop?2:1);
actions->value=-1;
return;
}else if(action=="Automatic"){
//Get the previous state.
bool automatic=(target->getEditorProperty("automatic")=="1");
//Switch the state.
automatic=!automatic;
parent->commandManager->doCommand(new SetEditorPropertyCommand(parent, imageManager, renderer,
target, "automatic", automatic ? "1" : "0", _("Automatic")));
updateItem(renderer,actions->value,"Automatic",_("Automatic"),automatic?2:1);
actions->value=-1;
return;
}else if(action=="Behaviour"){
//Get the current behaviour.
int currentBehaviour=2;
string behave=target->getEditorProperty("behaviour");
if(behave=="on"){
currentBehaviour=0;
}else if(behave=="off"){
currentBehaviour=1;
}
//Increase the behaviour.
currentBehaviour++;
if(currentBehaviour>2)
currentBehaviour=0;
//Update the data of the block.
parent->commandManager->doCommand(new SetEditorPropertyCommand(parent, imageManager, renderer,
target, "behaviour", behaviour[currentBehaviour], _("Behavior")));
//And update the item.
updateItem(renderer, actions->value, "Behaviour", tfm::format(_("Behavior: %s"), behaviour[currentBehaviour]).c_str());
actions->value=-1;
return;
}else if(action=="State"){
//Get the current state.
int currentState=atoi(target->getEditorProperty("state").c_str());
//Increase the state.
currentState++;
if(currentState>3)
currentState=0;
//Update the data of the block.
char s[64];
sprintf(s,"%d",currentState);
parent->commandManager->doCommand(new SetEditorPropertyCommand(parent, imageManager, renderer,
target, "state", s, _("State")));
//And update the item.
updateItem(renderer, actions->value, "State", tfm::format(_("State: %s"), states[currentState]).c_str());
actions->value=-1;
return;
}else if(action=="Speed"){
//Create the GUI.
GUIWindow* root=new GUIWindow(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-250)/2,600,250,true,true,_("Conveyor belt speed"));
root->minWidth = root->width; root->minHeight = root->height;
root->name="conveyorBlockWindow";
root->eventCallback=parent;
GUIObject* obj;
obj=new GUILabel(imageManager,renderer,40,100,240,36,_("Enter speed here:"));
root->addChild(obj);
GUISpinBox* obj2=new GUISpinBox(imageManager,renderer,240,100,320,36);
obj2->gravityRight = GUIGravityRight;
//Set the name of the text area, which is used to identify the object later on.
obj2->name="speed";
obj2->caption=target->getEditorProperty("speed10");
obj2->format = "%1.0f";
obj2->update();
root->addChild(obj2);
obj = new GUILabel(imageManager, renderer, 40, 160, 520, 36, _("NOTE: 1 Speed = 0.08 block/s"));
root->addChild(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.3,250-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgConveyorBlockOK";
obj->eventCallback=root;
root->addChild(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.7,250-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
}else if(action=="Scripting"){
//Create the GUI.
GUIWindow* root=new GUIWindow(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-500)/2,600,500,true,true,_("Scripting"));
root->minWidth = root->width; root->minHeight = root->height;
root->name="scriptingWindow";
root->eventCallback=parent;
GUIObject* obj;
obj=new GUILabel(imageManager,renderer,50,60,240,36,_("Id:"));
root->addChild(obj);
obj=new GUITextBox(imageManager,renderer,100,60,240,36,dynamic_cast<Block*>(target)->id.c_str());
obj->gravityRight = GUIGravityRight;
obj->name="id";
root->addChild(obj);
GUISingleLineListBox* list=new GUISingleLineListBox(imageManager,renderer,50,100,500,36);
list->gravityRight = GUIGravityRight;
std::map<std::string,int>::iterator it;
for(it=Game::gameObjectEventNameMap.begin();it!=Game::gameObjectEventNameMap.end();++it)
list->addItem(it->first);
list->name="cfgScriptingEventType";
list->value=0;
list->eventCallback=root;
root->addChild(list);
//Add a text area for each event type.
Block* block=dynamic_cast<Block*>(target);
for(unsigned int i=0;i<list->item.size();i++){
GUITextArea* text=new GUITextArea(imageManager,renderer,50,140,500,300);
text->gravityRight = text->gravityBottom = GUIGravityRight;
text->name=list->item[i].first;
text->setFont(fontMono);
//Only set the first one visible and enabled.
text->visible=(i==0);
text->enabled=(i==0);
map<int,string>::iterator it=block->scripts.find(Game::gameObjectEventNameMap[list->item[i].first]);
if(it!=block->scripts.end())
text->setString(renderer, it->second);
root->addChild(text);
}
obj=new GUIButton(imageManager,renderer,root->width*0.3,500-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgScriptingOK";
obj->eventCallback=root;
root->addChild(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.7,500-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
}else if(action=="LevelSettings"){
//Open the levelSettings window.
parent->levelSettings(imageManager,renderer);
//And dismiss this popup.
dismiss();
return;
}else if(action=="LevelScripting"){
//Create the GUI.
GUIWindow* root=new GUIWindow(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-500)/2,600,500,true,true,_("Level Scripting"));
root->minWidth = root->width; root->minHeight = root->height;
root->name="levelScriptingWindow";
root->eventCallback=parent;
GUIObject* obj;
GUISingleLineListBox* list=new GUISingleLineListBox(imageManager,renderer,50,60,500,36);
list->gravityRight = GUIGravityRight;
std::map<std::string,int>::iterator it;
for(it=Game::levelEventNameMap.begin();it!=Game::levelEventNameMap.end();++it)
list->addItem(it->first);
list->name="cfgLevelScriptingEventType";
list->value=0;
list->eventCallback=root;
root->addChild(list);
//Add a text area for each event type.
for(unsigned int i=0;i<list->item.size();i++){
GUITextArea* text=new GUITextArea(imageManager,renderer,50,100,500,340);
text->gravityRight = text->gravityBottom = GUIGravityRight;
text->name=list->item[i].first;
text->setFont(fontMono);
//Only set the first one visible and enabled.
text->visible=(i==0);
text->enabled=(i==0);
map<int,string>::iterator it=parent->scripts.find(Game::levelEventNameMap[list->item[i].first]);
if(it!=parent->scripts.end())
text->setString(renderer, it->second);
root->addChild(text);
}
obj=new GUIButton(imageManager,renderer,root->width*0.3,500-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgLevelScriptingOK";
obj->eventCallback=root;
root->addChild(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.7,500-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="cfgCancel";
obj->eventCallback=root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root]=target;
//And dismiss this popup.
dismiss();
return;
} else if (action.size() >= 7 && action.substr(0, 7) == "_layer:") {
std::string s0 = action.substr(7);
auto it = parent->layerVisibility.find(s0);
if (it != parent->layerVisibility.end()) {
int x;
SDL_GetMouseState(&x, NULL);
if (x < rect.x + 24) {
// toggle the visibility
it->second = !it->second;
if (parent->selectedLayer == it->first) {
// deselect all blocks if the visibility of current layer is changed
parent->deselectAll();
}
} else if (parent->selectedLayer != it->first) {
// deselect all blocks if the selected layer is changed
parent->deselectAll();
// uncheck the previously selected layer
std::string oldSelected = "_layer:" + parent->selectedLayer;
for (unsigned int idx = 0; idx < actions->item.size(); idx++) {
if (actions->item[idx] == oldSelected) {
int icon = parent->layerVisibility[parent->selectedLayer] ? (8 * 3 + 1) : (8 * 3 + 2);
- icon |= 1 << 8;
+ icon |= 36 << 8;
updateItem(renderer, idx, oldSelected.c_str(),
parent->selectedLayer.empty() ? _("Blocks layer") :
tfm::format((parent->selectedLayer < "f") ? _("Background layer: %s") : _("Foreground layer: %s"), parent->selectedLayer).c_str(),
icon);
break;
}
}
// change the selected layer
parent->selectedLayer = it->first;
} else {
actions->value = -1;
return;
}
int icon = it->second ? (8 * 3 + 1) : (8 * 3 + 2);
- icon |= (parent->selectedLayer == it->first ? 2 : 1) << 8;
+ icon |= (parent->selectedLayer == it->first ? 3 : 36) << 8;
std::string s = "_layer:" + it->first;
updateItem(renderer, actions->value, s.c_str(),
it->first.empty() ? _("Blocks layer") :
tfm::format((it->first < "f") ? _("Background layer: %s") : _("Foreground layer: %s"), it->first).c_str(),
icon);
// update some other menu items according to selection/visibility changes
for (unsigned int i = 0; i < actions->item.size(); i++) {
if (actions->item[i] == "DeleteLayer") {
updateItem(renderer, i, "DeleteLayer", _("Delete selected layer"), 8 * 3 + 7, parent->selectedLayer.empty());
} else if (actions->item[i] == "LayerSettings") {
updateItem(renderer, i, "LayerSettings", _("Configure selected layer"), 8 * 3 + 8, parent->selectedLayer.empty());
} else if (actions->item[i] == "MoveToLayer") {
updateItem(renderer, i, "MoveToLayer", _("Move selected object to layer"), 0, parent->selectedLayer.empty() || parent->selection.empty());
}
}
}
actions->value = -1;
return;
} else if (action == "AddLayer") {
//Create the add layer GUI.
GUIWindow* root = new GUIWindow(imageManager, renderer, (SCREEN_WIDTH - 600) / 2, (SCREEN_HEIGHT - 400) / 2, 600, 400, true, true, _("Add layer"));
root->minWidth = root->width; root->minHeight = root->height;
root->name = "addLayerWindow";
root->eventCallback = parent;
GUIObject* obj;
obj = new GUILabel(imageManager, renderer, 40, 64, 520, 36, _("Enter the layer name:"));
root->addChild(obj);
GUITextBox* obj2 = new GUITextBox(imageManager, renderer, 40, 100, 520, 36);
obj2->gravityRight = GUIGravityRight;
//Set the name of the text area, which is used to identify the object later on.
obj2->name = "layerName";
root->addChild(obj2);
addLayerNameNote(imageManager, renderer, root);
obj = new GUIButton(imageManager, renderer, root->width*0.3, 400 - 44, -1, 36, _("OK"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgAddLayerOK";
obj->eventCallback = root;
root->addChild(obj);
obj = new GUIButton(imageManager, renderer, root->width*0.7, 400 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgCancel";
obj->eventCallback = root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root] = target;
//And dismiss this popup.
dismiss();
return;
} else if (action == "DeleteLayer") {
// delete selected layer
if (parent->selectedLayer.empty()) {
// can't delete Blocks layer
actions->value = -1;
return;
}
if (parent->sceneryLayers.find(parent->selectedLayer) == parent->sceneryLayers.end()) {
// can't find the layer with given name
actions->value = -1;
return;
}
if (msgBox(imageManager, renderer,
tfm::format(_("Are you sure you want to delete layer '%s'?"), parent->selectedLayer).c_str(),
MsgBoxYesNo, _("Delete layer")) == MsgBoxYes)
{
// do the actual operation
parent->commandManager->doCommand(new AddRemoveLayerCommand(parent, parent->selectedLayer, false));
}
dismiss();
return;
} else if (action == "LayerSettings") {
// rename selected layer
if (parent->selectedLayer.empty()) {
// can't rename Blocks layer
actions->value = -1;
return;
}
auto it = parent->sceneryLayers.find(parent->selectedLayer);
if (it == parent->sceneryLayers.end()) {
// can't find the layer with given name
actions->value = -1;
return;
}
//Create the rename layer GUI.
GUIWindow* root = new GUIWindow(imageManager, renderer, (SCREEN_WIDTH - 600) / 2, (SCREEN_HEIGHT - 500) / 2, 600, 500, true, true, _("Layer settings"));
root->minWidth = root->width; root->minHeight = root->height;
root->name = "layerSettingsWindow";
root->eventCallback = parent;
GUIObject* obj;
obj = new GUILabel(imageManager, renderer, 40, 64, 520, 36, _("Layer name:"));
root->addChild(obj);
GUITextBox* textBox = new GUITextBox(imageManager, renderer, 40, 100, 520, 36, it->first.c_str());
textBox->gravityRight = GUIGravityRight;
//Set the name of the text area, which is used to identify the object later on.
textBox->name = "layerName";
root->addChild(textBox);
// A stupid code to save the old name
obj = new GUIObject(imageManager, renderer, 0, 0, 0, 0, it->first.c_str(), 0, false, false);
obj->name = "oldName";
root->addChild(obj);
addLayerNameNote(imageManager, renderer, root);
obj = new GUILabel(imageManager, renderer, 40, 284, 520, 36, _("Layer moving speed (1 speed = 0.8 block/s):"));
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 40, 320, 40, 36, "X");
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 320, 320, 40, 36, "Y");
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
root->addChild(obj);
GUISpinBox *spinBox = new GUISpinBox(imageManager, renderer, 80, 320, 200, 36);
spinBox->gravityRight = GUIGravityCenter;
spinBox->name = "speedX";
spinBox->caption = tfm::format("%g", it->second->speedX);
spinBox->format = "%g";
root->addChild(spinBox);
spinBox = new GUISpinBox(imageManager, renderer, 360, 320, 200, 36);
spinBox->gravityLeft = GUIGravityCenter; spinBox->gravityRight = GUIGravityRight;
spinBox->name = "speedY";
spinBox->caption = tfm::format("%g", it->second->speedY);
spinBox->format = "%g";
root->addChild(spinBox);
obj = new GUILabel(imageManager, renderer, 40, 364, 520, 36, _("Speed of following camera:"));
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 40, 400, 40, 36, "X");
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 320, 400, 40, 36, "Y");
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
root->addChild(obj);
spinBox = new GUISpinBox(imageManager, renderer, 80, 400, 200, 36);
spinBox->gravityRight = GUIGravityCenter;
spinBox->name = "cameraX";
spinBox->caption = tfm::format("%g", it->second->cameraX);
spinBox->format = "%g";
spinBox->change = 0.1f;
root->addChild(spinBox);
spinBox = new GUISpinBox(imageManager, renderer, 360, 400, 200, 36);
spinBox->gravityLeft = GUIGravityCenter; spinBox->gravityRight = GUIGravityRight;
spinBox->name = "cameraY";
spinBox->caption = tfm::format("%g", it->second->cameraY);
spinBox->format = "%g";
spinBox->change = 0.1f;
root->addChild(spinBox);
obj = new GUIButton(imageManager, renderer, root->width*0.3, 500 - 44, -1, 36, _("OK"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgLayerSettingsOK";
obj->eventCallback = root;
root->addChild(obj);
obj = new GUIButton(imageManager, renderer, root->width*0.7, 500 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgCancel";
obj->eventCallback = root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root] = target;
//And dismiss this popup.
dismiss();
return;
} else if (action == "MoveToLayer") {
// move the selected object to another layer
if (parent->selection.empty() || parent->selectedLayer.empty()) {
// either nothing selected, or can't move objects in Blocks layer
actions->value = -1;
return;
}
//Create the rename layer GUI.
GUIWindow* root = new GUIWindow(imageManager, renderer, (SCREEN_WIDTH - 600) / 2, (SCREEN_HEIGHT - 300) / 2, 600, 400, true, true, _("Move to layer"));
root->minWidth = root->width; root->minHeight = root->height;
root->name = "moveToLayerWindow";
root->eventCallback = parent;
GUIObject* obj;
obj = new GUILabel(imageManager, renderer, 40, 64, 520, 36, _("Enter the layer name (create new layer if necessary):"));
root->addChild(obj);
GUITextBox* obj2 = new GUITextBox(imageManager, renderer, 40, 100, 520, 36, parent->selectedLayer.c_str());
obj2->gravityRight = GUIGravityRight;
//Set the name of the text area, which is used to identify the object later on.
obj2->name = "layerName";
root->addChild(obj2);
// A stupid code to save the old name
obj = new GUIObject(imageManager, renderer, 0, 0, 0, 0, parent->selectedLayer.c_str(), 0, false, false);
obj->name = "oldName";
root->addChild(obj);
addLayerNameNote(imageManager, renderer, root);
obj = new GUIButton(imageManager, renderer, root->width*0.3, 400 - 44, -1, 36, _("OK"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgMoveToLayerOK";
obj->eventCallback = root;
root->addChild(obj);
obj = new GUIButton(imageManager, renderer, root->width*0.7, 400 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgCancel";
obj->eventCallback = root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root] = target;
//And dismiss this popup.
dismiss();
return;
} else if (action.size() > 10 && action.substr(0, 10) == "RepeatMode") {
Scenery *scenery = dynamic_cast<Scenery*>(target);
if (scenery) {
int index = atoi(action.c_str() + 10);
assert(index >= 0 && index < 4);
//Get the current repeat mode.
unsigned int repeatMode = scenery->repeatMode;
//Extract the value we want to modify.
unsigned int i = (repeatMode >> (index * 8)) & 0xFF;
repeatMode &= ~(0xFFu << (index * 8));
//Increase the value.
for (;;) {
i++;
if (i >= Scenery::REPEAT_MODE_MAX) i = 0;
// skip invalid values (POSITIVE_INFINITY for start, NEGATIVE_INFINITY for end)
if ((index & 1) == 0 && i == Scenery::POSITIVE_INFINITY) continue;
if ((index & 1) == 1 && i == Scenery::NEGATIVE_INFINITY) continue;
break;
}
//Update the repeat mode of the block.
char s[64];
sprintf(s, "%u", repeatMode | (i << (index * 8)));
parent->commandManager->doCommand(new SetEditorPropertyCommand(parent, imageManager, renderer,
target, "repeatMode", s, _("Repeat mode")));
//And update the item.
const char* ss[4] = {
__("Horizontal repeat start: %s"),
__("Horizontal repeat end: %s"),
__("Vertical repeat start: %s"),
__("Vertical repeat end: %s"),
};
const int icons[4] = {
8 * 2 + 3, 8 * 2 + 4, 8 * 3 + 3, 8 * 3 + 4,
};
updateItem(renderer, actions->value, action.c_str(), tfm::format(_(ss[index]), getRepeatModeName(i)).c_str(), icons[index]);
}
actions->value = -1;
return;
} else if (action == "CustomScenery") {
//Create the GUI.
GUIWindow* root = new GUIWindow(imageManager, renderer, (SCREEN_WIDTH - 600) / 2, (SCREEN_HEIGHT - 400) / 2, 600, 400, true, true, _("Custom scenery"));
root->minWidth = root->width; root->minHeight = root->height;
root->name = "customSceneryWindow";
root->eventCallback = parent;
GUIObject* obj;
obj = new GUILabel(imageManager, renderer, 50, 60, 240, 36, _("Custom scenery:"));
root->addChild(obj);
//Add a text area.
Scenery* scenery = dynamic_cast<Scenery*>(target);
GUITextArea* text = new GUITextArea(imageManager, renderer, 50, 100, 500, 240);
text->gravityRight = text->gravityBottom = GUIGravityRight;
text->name = "cfgCustomScenery";
text->setFont(fontMono);
//Only set the first one visible and enabled.
text->visible = true;
text->enabled = true;
// FIXME: an ad-hoc code
std::string s;
for (char c : scenery->customScenery_) {
if (c == '\t')
s.append(" ");
else
s.push_back(c);
}
text->setString(renderer, s);
root->addChild(text);
obj = new GUIButton(imageManager, renderer, root->width*0.3, 400 - 44, -1, 36, _("OK"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgCustomSceneryOK";
obj->eventCallback = root;
root->addChild(obj);
obj = new GUIButton(imageManager, renderer, root->width*0.7, 400 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgCancel";
obj->eventCallback = root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root] = target;
//And dismiss this popup.
dismiss();
return;
} else if (action == "Appearance"){
//Create the GUI.
GUIWindow* root = new GUIWindow(imageManager, renderer, (SCREEN_WIDTH - 600) / 2, (SCREEN_HEIGHT - 400) / 2, 600, 400, true, true, _("Appearance"));
root->minWidth = root->width; root->minHeight = root->height;
root->name = "appearanceWindow";
root->eventCallback = parent;
GUIObject* obj;
Block* block = dynamic_cast<Block*>(target);
//Create a list box showing all available scenery blocks.
GUIListBox *list = new GUIListBox(imageManager, renderer, 50, 60, 440, 280);
list->gravityRight = list->gravityBottom = GUIGravityRight;
list->name = "lstAppearance";
list->eventCallback = root;
root->addChild(list);
//TODO: Show the image along with the text in the list box.
//Currently I don't like to do that because this requires a lot of video memory since there are a lot of available scenery blocks.
list->addItem(renderer, _("(Use the default appearance for this block)"));
if (block->customAppearanceName.empty()) list->value = 0;
for (const std::string& s : parent->sceneryBlockNames) {
list->addItem(renderer, describeSceneryName(s));
if (block->customAppearanceName == s) list->value = list->item.size() - 1;
}
if (list->value < 0) {
std::cerr << "WARNING: The custom appearance '" << block->customAppearanceName << "' is unrecognized, added it to the list box anyway" << std::endl;
list->addItem(renderer, block->customAppearanceName);
list->value = list->item.size() - 1;
}
//Ask the list box to update scrollbar and we scroll the scrollbar to the correct position (FIXME: ad-hoc code)
list->render(renderer, 0, 0, false);
list->scrollScrollbar(list->value);
//Create an image widget showing the appearance of selected scenery block.
GUIImage *image = new GUIImage(imageManager, renderer, 500, 60, 50, 50);
image->gravityLeft = image->gravityRight = GUIGravityRight;
image->name = "imgAppearance";
root->addChild(image);
//Add a Change event to the list box which will be processed next frame, which is used to update the image widget.
GUIEventQueue.push_back(GUIEvent{ list->eventCallback, list->name, list, GUIEventChange });
obj = new GUIButton(imageManager, renderer, root->width*0.3, 400 - 44, -1, 36, _("OK"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgAppearanceOK";
obj->eventCallback = root;
root->addChild(obj);
obj = new GUIButton(imageManager, renderer, root->width*0.7, 400 - 44, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name = "cfgCancel";
obj->eventCallback = root;
root->addChild(obj);
//Add the window to the GUIObjectRoot and the objectWindows map.
GUIObjectRoot->addChild(root);
parent->objectWindows[root] = target;
//And dismiss this popup.
dismiss();
return;
}
}
};
/////////////////LevelEditorSelectionPopup/////////////////
class LevelEditorSelectionPopup{
private:
//The parent object.
LevelEditor* parent;
//The position of window.
SDL_Rect rect;
//GUI image.
SharedTexture bmGUI;
//The selection
std::vector<GameObject*> selection;
//The scrollbar.
GUIScrollBar* scrollBar;
//Highlighted object.
GameObject* highlightedObj;
//Highlighted button index. 0=none 1=select/deselect 2=delete 3=configure
int highlightedBtn;
public:
int startRow,showedRow;
//If selection is dirty
bool dirty;
public:
SDL_Rect getRect(){
return rect;
}
int width(){
return rect.w;
}
int height(){
return rect.h;
}
void updateScrollBar(ImageManager& imageManager, SDL_Renderer& renderer){
int m=selection.size()-showedRow;
if(m>0){
if(startRow<0) startRow=0;
else if(startRow>m) startRow=m;
if(scrollBar==NULL){
scrollBar=new GUIScrollBar(imageManager,renderer,0,0,16,rect.h-16,ScrollBarVertical,startRow,0,m,1,showedRow);
}
scrollBar->visible=true;
scrollBar->maxValue=m;
scrollBar->value=startRow;
}else{
startRow=0;
if(scrollBar){
scrollBar->visible=false;
scrollBar->value=0;
}
}
}
void updateSelection(ImageManager& imageManager, SDL_Renderer& renderer){
if(parent!=NULL){
std::vector<Block*>& v=parent->levelObjects;
for(int i=selection.size()-1;i>=0;i--){
if(find(v.begin(),v.end(),selection[i])==v.end()){
selection.erase(selection.begin()+i);
}
}
updateScrollBar(imageManager,renderer);
}
}
void dismiss(){
if(parent!=NULL && parent->selectionPopup==this){
parent->selectionPopup=NULL;
}
delete this;
}
LevelEditorSelectionPopup(LevelEditor* parent, ImageManager& imageManager, SDL_Renderer& renderer, std::vector<GameObject*>& selection, int x=0, int y=0){
this->parent=parent;
this->selection=selection;
dirty=false;
scrollBar=NULL;
highlightedObj=NULL;
highlightedBtn=0;
//calc window size
startRow=0;
showedRow=selection.size();
int m=SCREEN_HEIGHT/64-1;
if(showedRow>m) showedRow=m;
rect.w=320;
rect.h=showedRow*64+16;
if(x>SCREEN_WIDTH-rect.w) x=SCREEN_WIDTH-rect.w;
else if(x<0) x=0;
if(y>SCREEN_HEIGHT-rect.h) y=SCREEN_HEIGHT-rect.h;
else if(y<0) y=0;
rect.x=x;
rect.y=y;
updateScrollBar(imageManager,renderer);
//Load the gui images.
bmGUI=imageManager.loadTexture(getDataPath()+"gfx/gui.png",renderer);
}
virtual ~LevelEditorSelectionPopup(){
if(scrollBar)
delete scrollBar;
}
void move(int x,int y){
if(x>SCREEN_WIDTH-rect.w) x=SCREEN_WIDTH-rect.w;
else if(x<0) x=0;
if(y>SCREEN_HEIGHT-rect.h) y=SCREEN_HEIGHT-rect.h;
else if(y<0) y=0;
rect.x=x;
rect.y=y;
}
void render(ImageManager& imageManager, SDL_Renderer& renderer){
//Check dirty
if(dirty){
updateSelection(imageManager,renderer);
if(selection.empty()){
dismiss();
return;
}
dirty=false;
}
//background
drawGUIBox(rect.x,rect.y,rect.w,rect.h,renderer,0xFFFFFFFFU);
//get mouse position
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
//the tool tip of item
SDL_Rect tooltipRect;
//string tooltip;
if(scrollBar && scrollBar->visible){
startRow=scrollBar->value;
}
highlightedObj=NULL;
highlightedBtn=0;
ToolTips toolTip = ToolTips::TooltipMax;
int maxWidth = 0;
//draw avaliable item
for(int i=0;i<showedRow;i++){
int j=startRow+i;
if(j>=(int)selection.size()) break;
SDL_Rect r={rect.x+8,rect.y+i*64+8,rect.w-16,64};
if(scrollBar && scrollBar->visible) r.w-=24;
//check highlight
if(pointOnRect(mouse,r)){
highlightedObj=selection[j];
//0xCCCCCC
SDL_SetRenderDrawColor(&renderer,0xCC,0xCC,0xCC,0xFF);
SDL_RenderFillRect(&renderer,&r);
}
const int type = selection[j]->type;
Scenery *scenery = dynamic_cast<Scenery*>(selection[j]);
if (scenery) {
if (scenery->themeBlock == &(scenery->internalThemeBlock)) {
// custom scenery, draw an ad-hoc stupid icon
if (parent) {
const SDL_Rect srcRect = { 48, 16, 16, 16 };
const SDL_Rect dstRect = { r.x + 7, r.y + 7, 16, 16 };
SDL_RenderCopy(&renderer, parent->bmGUI.get(), &srcRect, &dstRect);
}
} else {
scenery->themeBlock->editorPicture.draw(renderer, r.x + 7, r.y + 7);
}
} else {
//draw tile picture
ThemeBlock* obj = objThemes.getBlock(type);
if (obj){
//obj->editorPicture.draw(screen,r.x+7,r.y+7);
obj->editorPicture.draw(renderer, r.x + 7, r.y + 7);
}
}
if(parent!=NULL){
//draw name
TexturePtr& tex = scenery ? (parent->getCachedTextTexture(renderer, scenery->sceneryName_.empty()
/// TRANSLATORS: Block name
? std::string(_("Custom scenery block")) : describeSceneryName(scenery->sceneryName_)))
: parent->typeTextTextures.at(type);
if (tex) {
const int w = textureWidth(tex) + 160;
if (w > maxWidth) maxWidth = w;
applyTexture(r.x + 64, r.y + (64 - textureHeight(tex)) / 2, tex, renderer);
}
//draw selected
{
std::vector<GameObject*> &v=parent->selection;
bool isSelected=find(v.begin(),v.end(),selection[j])!=v.end();
SDL_Rect r1={isSelected?16:0,0,16,16};
SDL_Rect r2={r.x+r.w-72,r.y+20,24,24};
if(pointOnRect(mouse,r2)){
drawGUIBox(r2.x,r2.y,r2.w,r2.h,renderer,0x999999FFU);
tooltipRect=r2;
//tooltip=_("Select");
highlightedBtn=1;
toolTip=ToolTips::Select;
}
r2.x+=4;
r2.y+=4;
r2.w=r1.w;
r2.h=r1.h;
SDL_RenderCopy(&renderer, bmGUI.get(),&r1,&r2);
}
//draw delete
{
SDL_Rect r1={112,0,16,16};
SDL_Rect r2={r.x+r.w-48,r.y+20,24,24};
if(pointOnRect(mouse,r2)){
drawGUIBox(r2.x,r2.y,r2.w,r2.h,renderer,0x999999FFU);
tooltipRect=r2;
//tooltip=_("Delete");
highlightedBtn=2;
toolTip=ToolTips::Delete;
}
r2.x+=4;
r2.y+=4;
r2.w=r1.w;
r2.h=r1.h;
SDL_RenderCopy(&renderer, bmGUI.get(),&r1,&r2);
}
//draw configure
{
SDL_Rect r1={112,16,16,16};
SDL_Rect r2={r.x+r.w-24,r.y+20,24,24};
if(pointOnRect(mouse,r2)){
drawGUIBox(r2.x,r2.y,r2.w,r2.h,renderer,0x999999FFU);
tooltipRect=r2;
//tooltip=_("Configure");
toolTip=ToolTips::Configure;
highlightedBtn=3;
}
r2.x+=4;
r2.y+=4;
r2.w=r1.w;
r2.h=r1.h;
SDL_RenderCopy(&renderer, bmGUI.get(),&r1,&r2);
}
}
}
//draw scrollbar
if(scrollBar && scrollBar->visible){
scrollBar->render(renderer,rect.x+rect.w-24,rect.y+8);
}
//draw tooltip
if(parent && int(toolTip) < parent->tooltipTextures.size()){
//Tool specific text.
TexturePtr& tip=parent->tooltipTextures.at(size_t(toolTip));
//Draw only if there's a tooltip available
if(tip){
const auto tipSize = rectFromTexture(tip);
tooltipRect.y-=4;
tooltipRect.h+=8;
if(tooltipRect.y+tooltipRect.h+tipSize.h>SCREEN_HEIGHT-20)
tooltipRect.y-=tipSize.h;
else
tooltipRect.y+=tooltipRect.h;
if(tooltipRect.x+tipSize.w>SCREEN_WIDTH-20)
tooltipRect.x=SCREEN_WIDTH-20-tipSize.w;
//Draw borders around text
Uint32 color=0xFFFFFF00|230;
drawGUIBox(tooltipRect.x-2,tooltipRect.y-2,tipSize.w+4,tipSize.h+4,renderer,color);
//Draw tooltip's text
applyTexture(tooltipRect.x,tooltipRect.y,tip,renderer);
}
}
//Resize the selection popup if necessary
if (maxWidth > rect.w) {
rect.w = maxWidth;
move(rect.x, rect.y);
}
}
void handleEvents(ImageManager& imageManager,SDL_Renderer& renderer){
//Check dirty
if(dirty){
updateSelection(imageManager,renderer);
if(selection.empty()){
dismiss();
return;
}
dirty=false;
}
//Check scrollbar event
if(scrollBar && scrollBar->visible){
if(scrollBar->handleEvents(renderer,rect.x+rect.w-24,rect.y+8)) return;
}
if(event.type==SDL_MOUSEBUTTONDOWN){
if(event.button.button==SDL_BUTTON_LEFT){
SDL_Rect mouse={event.button.x,event.button.y,0,0};
//Check if close it
if(!pointOnRect(mouse,rect)){
dismiss();
return;
}
//Check if item is clicked
if(highlightedObj!=NULL && highlightedBtn>0 && parent!=NULL){
//std::vector<Block*>& v=parent->levelObjects;
if(/*find(v.begin(),v.end(),highlightedObj)!=v.end()*/true/*???*/){
switch(highlightedBtn){
case 1:
{
std::vector<GameObject*>& v2=parent->selection;
std::vector<GameObject*>::iterator it=find(v2.begin(),v2.end(),highlightedObj);
if(it==v2.end()){
v2.push_back(highlightedObj);
}else{
v2.erase(it);
}
}
break;
case 2:
parent->commandManager->doCommand(new AddRemoveGameObjectCommand(parent, highlightedObj, false));
break;
case 3:
if(parent->actionsPopup)
delete parent->actionsPopup;
parent->actionsPopup=new LevelEditorActionsPopup(imageManager,renderer,parent,highlightedObj,mouse.x,mouse.y);
break;
}
}
}
}
}
else if(event.type == SDL_MOUSEWHEEL) {
//check mousewheel
if(event.wheel.y < 0){
startRow-=2;
updateScrollBar(imageManager,renderer);
return;
} else {
startRow+=2;
updateScrollBar(imageManager,renderer);
return;
}
}
}
};
/////////////////MovingPosition////////////////////////////
MovingPosition::MovingPosition(int x,int y,int time){
this->x=x;
this->y=y;
this->time=time;
}
MovingPosition::~MovingPosition(){}
void MovingPosition::updatePosition(int x,int y){
this->x=x;
this->y=y;
}
/////////////////LEVEL EDITOR//////////////////////////////
LevelEditor::LevelEditor(SDL_Renderer& renderer, ImageManager& imageManager):Game(renderer, imageManager){
//This will set some default settings.
reset();
//Create the GUI root.
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Load the toolbar.
toolbar=imageManager.loadTexture(getDataPath()+"gfx/menu/toolbar.png",renderer);
toolbarRect={(SCREEN_WIDTH-460)/2,SCREEN_HEIGHT-50,460,50};
selectionPopup=NULL;
actionsPopup=NULL;
movingSpeedWidth=-1;
//Load the selectionMark.
selectionMark=imageManager.loadTexture(getDataPath()+"gfx/menu/selection.png",renderer);
//Load the movingMark.
movingMark=imageManager.loadTexture(getDataPath()+"gfx/menu/moving.png",renderer);
//Load the gui images.
bmGUI=imageManager.loadTexture(getDataPath()+"gfx/gui.png",renderer);
toolboxText=textureFromText(renderer,*fontText,_("Toolbox"),objThemes.getTextColor(true));
for(size_t i = 0;i < typeTextTextures.size();++i) {
typeTextTextures[i] =
textureFromText(renderer,
*fontText,
_(blockNames[i]),
objThemes.getTextColor(true));
}
for(size_t i = 0;i < tooltipTextures.size();++i) {
if (tooltipNames[i][0]) {
- tooltipTextures[i] =
- textureFromText(renderer,
- *fontText,
- _(tooltipNames[i]),
- objThemes.getTextColor(true));
+ std::string s = _(tooltipNames[i]);
+ if (tooltipHotkey[i][0]) {
+ s += " (" + std::string(tooltipHotkey[i]) + ")";
+ } else if (tooltipHotkey2[i] >= 0) {
+ std::string s2 = InputManagerKeyCode::describeTwo(
+ inputMgr.getKeyCode((InputManagerKeys)tooltipHotkey2[i], false),
+ inputMgr.getKeyCode((InputManagerKeys)tooltipHotkey2[i], true));
+ if (!s2.empty()) s += " (" + s2 + ")";
+ }
+ tooltipTextures[i] = textureFromText(renderer, *fontText, s.c_str(), objThemes.getTextColor(true));
}
}
//Count the level editing time.
statsMgr.startLevelEdit();
//Create the command manager with a maximum of 100 commands.
commandManager = new CommandManager(100);
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string MoveGameObjectCommand::describe() {
if (objects.size() == 1) {
const bool isResize = oldPosition[0].w != newPosition[0].w || oldPosition[0].h != newPosition[0].h;
Scenery *scenery = dynamic_cast<Scenery*>(objects[0]);
return tfm::format(isResize ? _("Resize %s") : _("Move %s"), scenery ?
/// TRANSLATORS: Context: Resize/Move ...
(scenery->sceneryName_.empty() ? _("Custom scenery block")
: describeSceneryName(scenery->sceneryName_).c_str())
: _(blockNames[objects[0]->type]));
} else {
const size_t number_of_objects = objects.size();
/// TRANSLATORS: Context: Undo/Redo ...
return tfm::format(ngettext("Move %d object", "Move %d objects", number_of_objects).c_str(), number_of_objects);
}
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string AddRemoveGameObjectCommand::describe() {
if (objects.size() == 1) {
Scenery *scenery = dynamic_cast<Scenery*>(objects[0]);
return tfm::format(isAdd ? _("Add %s") : _("Remove %s"), scenery ? (scenery->sceneryName_.empty() ?
/// TRANSLATORS: Context: Add/Remove ...
_("Custom scenery block")
: describeSceneryName(scenery->sceneryName_).c_str())
: _(blockNames[objects[0]->type]));
} else {
const size_t number_of_objects = objects.size();
/// TRANSLATORS: Context: Undo/Redo ...
return tfm::format(isAdd ? ngettext("Add %d object", "Add %d objects", number_of_objects).c_str() :
/// TRANSLATORS: Context: Undo/Redo ...
ngettext("Remove %d object", "Remove %d objects", number_of_objects).c_str(), number_of_objects);
}
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string AddRemovePathCommand::describe() {
return tfm::format(isAdd ?
/// TRANSLATORS: Context: Undo/Redo ...
_("Add path to %s") :
/// TRANSLATORS: Context: Undo/Redo ...
_("Remove a path point from %s"), _(blockNames[target->type]));
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string RemovePathCommand::describe() {
/// TRANSLATORS: Context: Undo/Redo ...
return tfm::format(_("Remove all paths from %s"), _(blockNames[target->type]));
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string AddLinkCommand::describe() {
/// TRANSLATORS: Context: Undo/Redo ...
return tfm::format(_("Add link from %s to %s"), _(blockNames[target->type]), _(blockNames[clickedObj->type]));
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string RemoveLinkCommand::describe() {
/// TRANSLATORS: Context: Undo/Redo ...
return tfm::format(_("Remove all links from %s"), _(blockNames[target->type]));
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string SetEditorPropertyCommand::describe() {
Scenery *scenery = dynamic_cast<Scenery*>(target);
/// TRANSLATORS: Context: Undo/Redo ...
std::string s = _("Modify the %2 property of %1");
size_t lp = s.find("%1");
if (lp != string::npos) {
std::string s1 = scenery ?
(scenery->sceneryName_.empty() ?
/// TRANSLATORS: Context: Undo/Redo ...
_("Custom scenery block")
: describeSceneryName(scenery->sceneryName_).c_str())
: _(blockNames[target->type]);
s = s.substr(0, lp) + s1 + s.substr(lp + 2);
}
lp = s.find("%2");
if (lp != string::npos) {
s = s.substr(0, lp) + desc + s.substr(lp + 2);
}
return s;
}
// FIXME: I have to write this function here since we need to access the static variable levelTime and levelRecordings
SetLevelPropertyCommand::SetLevelPropertyCommand(LevelEditor* levelEditor, const LevelProperty& levelProperty)
: editor(levelEditor), newProperty(levelProperty)
{
oldProperty.levelName = editor->levelName;
oldProperty.levelTheme = editor->levelTheme;
oldProperty.levelMusic = editor->levelMusic;
oldProperty.levelTime = levelTime;
oldProperty.levelRecordings = levelRecordings;
}
// FIXME: I have to write this function here since we need to access the static variable levelTime and levelRecordings
void SetLevelPropertyCommand::setLevelProperty(const LevelProperty& levelProperty) {
bool musicChanged = editor->levelMusic != levelProperty.levelMusic;
editor->levelName = levelProperty.levelName;
editor->levelTheme = levelProperty.levelTheme;
editor->levelMusic = levelProperty.levelMusic;
levelTime = levelProperty.levelTime;
levelRecordings = levelProperty.levelRecordings;
if (musicChanged) {
#ifdef _DEBUG
printf("DEBUG: Level music is changed dynamically in level editor\n");
#endif
editor->editorData["music"] = editor->levelMusic;
editor->reloadMusic();
}
}
// FIXME: I have to write this function here since we need to access the static blockNames[]
std::string SetScriptCommand::describe() {
if (target) {
/// TRANSLATORS: Context: Undo/Redo ...
return tfm::format(_("Edit the script of %s"), _(blockNames[target->type]));
} else {
/// TRANSLATORS: Context: Undo/Redo ...
return _("Edit the script of level");
}
}
LevelEditor::~LevelEditor(){
//Delete the command manager.
delete commandManager;
// NOTE: We don't need to delete levelObjects, etc. since they are deleted in Game::~Game().
// Clear selection
selection.clear();
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Delete the popup
if(selectionPopup){
delete selectionPopup;
selectionPopup=NULL;
}
//Delete the popup
if(actionsPopup){
delete actionsPopup;
actionsPopup=NULL;
}
//Reset the camera.
camera.x=0;
camera.y=0;
//Count the level editing time.
statsMgr.endLevelEdit();
}
TexturePtr& LevelEditor::getCachedTextTexture(SDL_Renderer& renderer, const std::string& text) {
auto it = cachedTextTextures.find(text);
if (it != cachedTextTextures.end()) return it->second;
return (cachedTextTextures[text] = textureFromText(renderer,
*fontText,
text.c_str(),
objThemes.getTextColor(true)));
}
void LevelEditor::reset(){
//Set some default values.
playMode=false;
tool=ADD;
currentType=0;
toolboxVisible=false;
toolboxRect.x=-1;
toolboxRect.y=-1;
toolboxRect.w=0;
toolboxRect.h=0;
toolboxIndex=0;
pressedShift=false;
pressedLeftMouse=false;
dragging=false;
selectionDrag=-1;
dragCenter=NULL;
cameraXvel=0;
cameraYvel=0;
linking=false;
linkingTrigger=NULL;
currentId=0;
movingBlock=NULL;
moving=false;
movingSpeed=10;
pauseTime = 0;
tooltip=-1;
//Set the player and shadow to their starting position.
player.setLocation(player.fx,player.fy);
shadow.setLocation(shadow.fx,shadow.fy);
selection.clear();
clipboard.clear();
triggers.clear();
movingBlocks.clear();
//Delete any gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Clear the GUIWindow object map.
objectWindows.clear();
}
void LevelEditor::loadLevelFromNode(ImageManager& imageManager, SDL_Renderer& renderer,TreeStorageNode* obj, const std::string& fileName){
//call the method of base class.
Game::loadLevelFromNode(imageManager,renderer,obj,fileName);
//We swap the levelObjects and levelObjectsInitial again.
std::swap(levelObjects, levelObjectsInitial);
//now do our own stuff.
string s=editorData["time"];
if(s.empty() || !(s[0]>='0' && s[0]<='9')){
levelTime=-1;
}else{
levelTime=atoi(s.c_str());
}
s=editorData["recordings"];
if(s.empty() || !(s[0]>='0' && s[0]<='9')){
levelRecordings=-1;
}else{
levelRecordings=atoi(s.c_str());
}
levelTheme = editorData["theme"];
levelMusic = editorData["music"];
//NOTE: We set the camera here since we know the dimensions of the level.
if(LEVEL_WIDTH<SCREEN_WIDTH)
camera.x=-(SCREEN_WIDTH-LEVEL_WIDTH)/2;
else
camera.x=0;
if(LEVEL_HEIGHT<SCREEN_HEIGHT)
camera.y=-(SCREEN_HEIGHT-LEVEL_HEIGHT)/2;
else
camera.y=0;
//The level is loaded, so call postLoad.
postLoad();
}
void LevelEditor::saveLevel(string fileName){
//Create the output stream and check if it starts.
std::ofstream save(fileName.c_str());
if(!save) return;
//The dimensions of the level.
int maxX=0;
int maxY=0;
//The storageNode to put the level data in before writing it away.
TreeStorageNode node;
char s[64];
//The name of the level.
if(!levelName.empty()){
node.attributes["name"].push_back(levelName);
//Update the level name in the levelpack.
levels->getLevel()->name=levelName;
}
//The level theme.
if(!levelTheme.empty())
node.attributes["theme"].push_back(levelTheme);
//The level music.
if (!levelMusic.empty())
node.attributes["music"].push_back(levelMusic);
//target time and recordings.
{
char c[32];
- if(levelTime>=0){
- sprintf(c,"%d",levelTime);
- node.attributes["time"].push_back(c);
- //Update the target time the levelpack.
- levels->getLevel()->targetTime=levelTime;
- }
- if(levelRecordings>=0){
- sprintf(c,"%d",levelRecordings);
- node.attributes["recordings"].push_back(c);
+ sprintf(c, "%d", std::max(levelTime, -1));
+ node.attributes["time"].push_back(c);
- //Update the target recordings the levelpack.
- levels->getLevel()->targetRecordings=levelRecordings;
- }
+ //Update the target time the levelpack.
+ levels->getLevel()->targetTime = std::max(levelTime, -1);
+
+ sprintf(c, "%d", std::max(levelRecordings, -1));
+ node.attributes["recordings"].push_back(c);
+
+ //Update the target recordings the levelpack.
+ levels->getLevel()->targetRecordings = std::max(levelRecordings, -1);
}
//The width of the level.
maxX=LEVEL_WIDTH;
sprintf(s,"%d",maxX);
node.attributes["size"].push_back(s);
//The height of the level.
maxY=LEVEL_HEIGHT;
sprintf(s,"%d",maxY);
node.attributes["size"].push_back(s);
//Loop through the gameObjects and save them.
for(int o=0;o<(signed)levelObjects.size();o++){
int objectType=levelObjects[o]->type;
//Check if it's a legal gameObject type.
if(objectType>=0 && objectType<TYPE_MAX){
TreeStorageNode* obj1=new TreeStorageNode;
node.subNodes.push_back(obj1);
//It's a tile so name the node tile.
obj1->name="tile";
//Write away the type of the gameObject.
obj1->value.push_back(blockName[objectType]);
//Get the box for the location of the gameObject.
SDL_Rect box=levelObjects[o]->getBox(BoxType_Base);
//Put the location and size in the storageNode.
sprintf(s,"%d",box.x);
obj1->value.push_back(s);
sprintf(s,"%d",box.y);
obj1->value.push_back(s);
sprintf(s,"%d",box.w);
obj1->value.push_back(s);
sprintf(s,"%d",box.h);
obj1->value.push_back(s);
//Loop through the editor data and save it also.
vector<pair<string,string> > obj;
levelObjects[o]->getEditorData(obj);
for (const auto& o : obj) {
//Skip the data whose key or value is empty.
if (o.first.empty()) continue;
if (o.second.empty()) continue;
//Skip SOME data whose value is the default value. Currently only some boolean values are skipped.
//WARNING: When the default values are changed, these codes MUST be modified accordingly!!!
//NOTE: Currently we skip the "visible" property since it is used for every block and usually it's the default value.
if (o.first == "visible" && o.second == "1") continue;
#if 0
//NOTE: The following codes are more aggressive!!!
//if (o.first == "activated" && o.second == "1") continue; //moving blocks and conveyor belt // Don't use this because there was a "disabled" property
if (o.first == "loop" && o.second == "1") continue; //moving blocks and conveyor belt
if (o.first == "automatic" && o.second == "0") continue; //portal
#endif
//Save the data.
obj1->attributes[o.first].push_back(o.second);
}
//Loop through the scripts and add them to the storage node of the game object.
map<int,string>::iterator it;
Block* object=(dynamic_cast<Block*>(levelObjects[o]));
for(it=object->scripts.begin();it!=object->scripts.end();++it){
//Make sure the script isn't an empty string.
if(it->second.empty())
continue;
TreeStorageNode* script=new TreeStorageNode;
obj1->subNodes.push_back(script);
script->name="script";
script->value.push_back(gameObjectEventTypeMap[it->first]);
script->attributes["script"].push_back(it->second);
}
}
}
//Loop through the level scripts and save them.
for(auto it=scripts.begin();it!=scripts.end();++it){
//Make sure the script isn't an empty string.
if(it->second.empty())
continue;
TreeStorageNode* script=new TreeStorageNode;
node.subNodes.push_back(script);
script->name="script";
script->value.push_back(levelEventTypeMap[it->first]);
script->attributes["script"].push_back(it->second);
}
//Loop through the scenery layers and save them.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
TreeStorageNode* layer = new TreeStorageNode;
node.subNodes.push_back(layer);
layer->name = "scenerylayer";
layer->value.push_back(it->first);
it->second->saveToNode(layer);
}
//Create a POASerializer and write away the level node.
POASerializer objSerializer;
objSerializer.writeNode(&node,save,true,true);
}
void LevelEditor::deselectAll() {
selection.clear();
dragCenter = NULL;
selectionDrag = -1;
selectionDirty();
}
///////////////EVENT///////////////////
void LevelEditor::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//Check if we need to quit, if so we enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//If playing/testing we should the game handle the events.
if(playMode){
Game::handleEvents(imageManager,renderer);
//Also check if we should exit the playMode.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
//Reset the game and disable playMode, disable script.
Game::reset(true, true);
//We swap the levelObjects and levelObjectsInitial again.
std::swap(levelObjects, levelObjectsInitial);
playMode=false;
GUIObjectRoot->visible=true;
camera.x=cameraSave.x;
camera.y=cameraSave.y;
//NOTE: To prevent the mouse to still "be pressed" we set it to false.
pressedLeftMouse=false;
}
}else{
//Also check if we should exit the editor.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
std::string s;
//Check if the file is changed
if (commandManager->isChanged()) {
s = _("The level has unsaved changes.");
s.push_back('\n');
}
//Before we quit ask a make sure question.
if(msgBox(imageManager,renderer,s+_("Are you sure you want to quit?"),MsgBoxYesNo,_("Quit prompt"))==MsgBoxYes){
//We exit the level editor.
setNextState(STATE_LEVEL_EDIT_SELECT);
//Play the menu music again.
getMusicManager()->playMusic("menu");
//No need for handling other events, so return.
return;
}
}
//Check if we should redirect the event to the actions popup
if(actionsPopup!=NULL){
actionsPopup->handleEvents(renderer);
return;
}
//Check if we should redirect the event to selection popup
if(selectionPopup!=NULL){
if(event.type==SDL_MOUSEBUTTONDOWN
|| event.type==SDL_MOUSEBUTTONUP
|| event.type==SDL_MOUSEMOTION)
{
selectionPopup->handleEvents(imageManager,renderer);
return;
}
}
//TODO: Don't handle any Events when GUIWindows process them.
{
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x,y,0,0};
for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
SDL_Rect box={0,0,0,0};
box.x=GUIObjectRoot->childControls[i]->left;
box.y=GUIObjectRoot->childControls[i]->top;
box.w=GUIObjectRoot->childControls[i]->width;
box.h=GUIObjectRoot->childControls[i]->height;
if(pointOnRect(mouse,box))
return;
}
}
//Check if toolbar is clicked.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT && tooltip>=0){
int t=tooltip;
if(t<NUMBER_TOOLS){
+ //Show/hide toolbox if the current mode is ADD and the user clicked ADD again.
+ if (tool == ADD && t == ADD) {
+ toolboxVisible = !toolboxVisible;
+ }
+
tool=(Tools)t;
//Stop linking or moving if the mode is not SELECT.
if (tool != SELECT) {
if (linking) {
linking = false;
linkingTrigger = NULL;
}
if (moving) {
moving = false;
movingBlock = NULL;
}
}
}else{
//The selected button isn't a tool.
//Now check which button it is.
if (t == (int)ToolTips::Play){
enterPlayMode();
}
if (t == (int)ToolTips::LevelSettings){
//Open up level settings dialog
levelSettings(imageManager,renderer);
}
if (t == (int)ToolTips::BackToMenu){
//If the file is changed we show a confirmation dialog
if (commandManager->isChanged()) {
std::string s = _("The level has unsaved changes.");
s.push_back('\n');
if (msgBox(imageManager, renderer, s + _("Are you sure you want to quit?"), MsgBoxYesNo, _("Quit prompt")) != MsgBoxYes) return;
}
//Go back to the level selection screen of Level Editor
setNextState(STATE_LEVEL_EDIT_SELECT);
//Change the music back to menu music.
getMusicManager()->playMusic("menu");
}
if (t == (int)ToolTips::SaveLevel){
//Save current level
saveCurrentLevel(imageManager, renderer);
}
if (t == (int)ToolTips::UndoNoTooltip) {
commandManager->undo();
}
if (t == (int)ToolTips::RedoNoTooltip) {
commandManager->redo();
}
}
return;
}
//Check if tool box is clicked.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT && toolboxRect.w>0){
if(toolboxVisible){
if(event.button.y<64){
//Check if we need to hide it
if(event.button.x>=SCREEN_WIDTH-24 && event.button.x<SCREEN_WIDTH && event.button.y<20){
toolboxVisible=false;
return;
}
const int m = (SCREEN_WIDTH - 48) / 64;
//Check if a block is clicked.
if(event.button.x>=24 && event.button.x<SCREEN_WIDTH-24){
int i=(event.button.x-24)/64;
if(i<m && i+toolboxIndex<getEditorOrderMax()){
currentType=i+toolboxIndex;
}
}
//Check if move left button is clicked
if (event.button.x >= 0 && event.button.x < 24 && event.button.y >= 20 && event.button.y < 44) {
toolboxIndex -= m;
if (toolboxIndex < 0) toolboxIndex = 0;
}
//Check if move right button is clicked
if (event.button.x >= SCREEN_WIDTH - 24 && event.button.x < SCREEN_WIDTH && event.button.y >= 20 && event.button.y < 44) {
toolboxIndex += m;
if (toolboxIndex > getEditorOrderMax() - m) toolboxIndex = getEditorOrderMax() - m;
if (toolboxIndex < 0) toolboxIndex = 0;
}
return;
}
}else if(event.button.x>=toolboxRect.x && event.button.x<toolboxRect.x+toolboxRect.w
&& event.button.y>=toolboxRect.y && event.button.y<toolboxRect.y+toolboxRect.h)
{
toolboxVisible=true;
return;
}
}
//Check if shift is pressed.
pressedShift=inputMgr.isKeyDown(INPUTMGR_SHIFT);
//Check if delete is pressed.
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_DELETE){
if (!selection.empty()){
AddRemoveGameObjectCommand *command = new AddRemoveGameObjectCommand(this, selection, false);
//clear the selection vector first.
deselectAll();
//perform the actual deletion.
commandManager->doCommand(command);
}
}
//Check for copy (Ctrl+c) or cut (Ctrl+x).
if(event.type==SDL_KEYDOWN && (event.key.keysym.sym==SDLK_c || event.key.keysym.sym==SDLK_x) && (event.key.keysym.mod & KMOD_CTRL)){
//Check if the selection isn't empty.
if(!selection.empty()){
//Clear the current clipboard.
clipboard.clear();
//Loop through the selection to find the left-top block.
int x=selection[0]->getBox().x;
int y=selection[0]->getBox().y;
for(unsigned int o=1; o<selection.size(); o++){
if(selection[o]->getBox().x<x || selection[o]->getBox().y<y){
x=selection[o]->getBox().x;
y=selection[o]->getBox().y;
}
}
//Loop through the selection for the actual copying.
for(unsigned int o=0; o<selection.size(); o++){
//Get the editor data of the object.
vector<pair<string,string> > obj;
selection[o]->getEditorData(obj);
//Loop through the editor data and convert it.
map<string,string> objMap;
for(unsigned int i=0;i<obj.size();i++){
objMap[obj[i].first]=obj[i].second;
}
//Add some entries to the map.
char s[64];
SDL_Rect r = selection[o]->getBox();
sprintf(s, "%d", r.x - x);
objMap["x"]=s;
sprintf(s, "%d", r.y - y);
objMap["y"]=s;
sprintf(s, "%d", selection[o]->getBox().w);
objMap["w"] = s;
sprintf(s, "%d", selection[o]->getBox().h);
objMap["h"] = s;
Scenery *scenery = dynamic_cast<Scenery*>(selection[o]);
if (scenery) {
objMap["sceneryName"] = scenery->sceneryName_;
objMap["customScenery"] = scenery->customScenery_;
} else {
sprintf(s, "%d", selection[o]->type);
objMap["type"] = s;
}
//Overwrite the id to prevent triggers, portals, buttons, movingblocks, etc. from malfunctioning.
//We give an empty string as id, which is invalid and thus suitable.
objMap["id"]="";
//Do the same for destination if the type is portal.
if(selection[o]->type==TYPE_PORTAL){
objMap["destination"]="";
}
//And add the map to the clipboard vector.
clipboard.push_back(objMap);
}
//Cutting means deleting the game object.
if (event.key.keysym.sym == SDLK_x && !selection.empty()){
AddRemoveGameObjectCommand *command = new AddRemoveGameObjectCommand(this, selection, false);
//clear the selection vector first.
deselectAll();
//perform the actual deletion.
commandManager->doCommand(command);
}
}
}
//Check for paste (Ctrl+v).
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_v && (event.key.keysym.mod & KMOD_CTRL)){
//First make sure that the clipboard isn't empty.
if(!clipboard.empty()){
//Clear the current selection.
deselectAll();
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
std::vector<GameObject*> newObjects;
//Loop through the clipboard.
for(unsigned int o=0;o<clipboard.size();o++){
GameObject *obj = NULL;
if (clipboard[o].find("sceneryName") == clipboard[o].end()) {
// a normal block
if (!selectedLayer.empty()) continue;
obj = new Block(this, 0, 0, 50, 50, atoi(clipboard[o]["type"].c_str()));
} else {
// a scenery block
if (selectedLayer.empty()) continue;
Scenery *scenery = new Scenery(this, 0, 0, 50, 50, clipboard[o]["sceneryName"]);
if (clipboard[o]["sceneryName"].empty()) {
scenery->customScenery_ = clipboard[o]["customScenery"];
scenery->updateCustomScenery(imageManager, renderer);
}
obj = scenery;
}
obj->setBaseLocation(atoi(clipboard[o]["x"].c_str())+x,atoi(clipboard[o]["y"].c_str())+y);
obj->setBaseSize(atoi(clipboard[o]["w"].c_str()), atoi(clipboard[o]["h"].c_str()));
obj->setEditorData(clipboard[o]);
//add the object.
newObjects.push_back(obj);
//Also add the block to the selection.
selection.push_back(obj);
}
// Do the actual object insertion
if (!newObjects.empty()) {
commandManager->doCommand(new AddRemoveGameObjectCommand(this, newObjects, true));
}
}
}
//Check for the arrow keys, used for moving the camera when playMode=false.
if (inputMgr.isKeyDown(INPUTMGR_RIGHT)) {
if (cameraXvel < 5) cameraXvel = 5;
} else if (inputMgr.isKeyDown(INPUTMGR_LEFT)) {
if (cameraXvel > -5) cameraXvel = -5;
} else {
cameraXvel = 0;
}
if(inputMgr.isKeyDown(INPUTMGR_DOWN)){
if (cameraYvel < 5) cameraYvel = 5;
} else if (inputMgr.isKeyDown(INPUTMGR_UP)){
if (cameraYvel > -5) cameraYvel = -5;
} else {
cameraYvel = 0;
}
//Check if the left mouse button is pressed/holded.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT){
pressedLeftMouse=true;
}
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
pressedLeftMouse=false;
//We also need to check if dragging is true.
if(dragging){
//Set dragging false and call the onDrop event.
dragging=false;
int x,y;
SDL_GetMouseState(&x,&y);
//We call the drop event.
onDrop(x+camera.x,y+camera.y);
}
}
//Check if the mouse is dragging.
if(pressedLeftMouse && event.type==SDL_MOUSEMOTION) {
//Check if this is the start of the dragging.
if(!dragging){
//The mouse is moved enough so let's set dragging true.
dragging=true;
// NOTE: We start drag from previous mouse position to prevent resize area hit test bug
onDragStart(event.motion.x - event.motion.xrel + camera.x, event.motion.y - event.motion.yrel + camera.y);
onDrag(event.motion.xrel, event.motion.yrel);
} else {
//Dragging was already true meaning we call onDrag() instead of onDragStart().
onDrag(event.motion.xrel,event.motion.yrel);
}
}
//Update cursor.
if(dragging){
if (tool == REMOVE) {
currentCursor = CURSOR_REMOVE;
} else {
switch (selectionDrag) {
case 0: case 8:
currentCursor = CURSOR_SIZE_FDIAG;
break;
case 1: case 7:
currentCursor = CURSOR_SIZE_VER;
break;
case 2: case 6:
currentCursor = CURSOR_SIZE_BDIAG;
break;
case 3: case 5:
currentCursor = CURSOR_SIZE_HOR;
break;
case 4:
currentCursor = CURSOR_DRAG;
break;
default:
currentCursor = CURSOR_POINTER;
break;
}
}
}
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x,y,0,0};
//Check if we scroll up, meaning the currentType++;
if((event.type==SDL_MOUSEWHEEL && event.wheel.y > 0) || inputMgr.isKeyDownEvent(INPUTMGR_NEXT)){
switch(tool){
case ADD:
//Check if mouse is in tool box.
if(toolboxVisible && toolboxRect.w>0){
int x,y;
SDL_GetMouseState(&x,&y);
if(y<64){
const int m = getEditorOrderMax() - (SCREEN_WIDTH - 48) / 64;
toolboxIndex -= 2;
if (toolboxIndex>m) toolboxIndex = m;
if (toolboxIndex<0) toolboxIndex = 0;
break;
}
}
//Only change the current type when using the add tool.
currentType++;
if (currentType >= getEditorOrderMax()){
currentType=0;
}
break;
case SELECT:
//When configuring moving blocks.
if(moving){
if (pauseMode) {
//Here we have to use Ctrl because Shift means snap
pauseTime += (SDL_GetModState() & KMOD_CTRL) ? 10 : 1;
} else {
//Here we have to use Ctrl because Shift means snap
movingSpeed += (SDL_GetModState() & KMOD_CTRL) ? 10 : 1;
//The movingspeed is capped at 125 (10 block/s).
if (movingSpeed > 125){
movingSpeed = 125;
}
}
break;
}
//Fall through.
default:
//When in other mode, just scrolling the map
if (pressedShift) camera.x = clamp(camera.x - 200, -1000 - SCREEN_WIDTH, LEVEL_WIDTH + 1000);
else camera.y = clamp(camera.y - 200, -1000 - SCREEN_HEIGHT, LEVEL_HEIGHT + 1000);
break;
}
}
//Check if we scroll down, meaning the currentType--;
if((event.type==SDL_MOUSEWHEEL && event.wheel.y < 0) || inputMgr.isKeyDownEvent(INPUTMGR_PREVIOUS)){
switch(tool){
case ADD:
//Check if mouse is in tool box.
if(toolboxVisible && toolboxRect.w>0){
int x,y;
SDL_GetMouseState(&x,&y);
if(y<64){
const int m = getEditorOrderMax() - (SCREEN_WIDTH - 48) / 64;
toolboxIndex+=2;
if(toolboxIndex>m) toolboxIndex=m;
if(toolboxIndex<0) toolboxIndex=0;
break;
}
}
//Only change the current type when using the add tool.
currentType--;
if(currentType<0){
currentType = getEditorOrderMax() - 1;
}
break;
case SELECT:
//When configuring moving blocks.
if(moving){
if (pauseMode) {
//Here we have to use Ctrl because Shift means snap
pauseTime -= (SDL_GetModState() & KMOD_CTRL) ? 10 : 1;
if (pauseTime < -1){
pauseTime = -1;
}
} else {
//Here we have to use Ctrl because Shift means snap
movingSpeed -= (SDL_GetModState() & KMOD_CTRL) ? 10 : 1;
if (movingSpeed <= 0){
movingSpeed = 1;
}
}
break;
}
//Fall through.
default:
//When in other mode, just scrolling the map
if (pressedShift) camera.x = clamp(camera.x + 200, -1000 - SCREEN_WIDTH, LEVEL_WIDTH + 1000);
else camera.y = clamp(camera.y + 200, -1000 - SCREEN_HEIGHT, LEVEL_HEIGHT + 1000);
break;
}
}
if (event.type == SDL_KEYDOWN) {
bool unlink = false;
//Check if we should enter playMode.
if (event.key.keysym.sym == SDLK_F5){
enterPlayMode();
}
//Check for tool shortcuts.
if (event.key.keysym.sym == SDLK_F2){
tool = SELECT;
}
if (event.key.keysym.sym == SDLK_F3){
tool = ADD;
unlink = true;
}
if (event.key.keysym.sym == SDLK_F4){
tool = REMOVE;
unlink = true;
}
//Stop linking or moving if the mode is not SELECT.
if (unlink) {
if (linking) {
linking = false;
linkingTrigger = NULL;
}
if (moving) {
moving = false;
movingBlock = NULL;
}
}
}
//Check for certain events.
//First make sure the mouse isn't above the toolbar.
if(!pointOnRect(mouse,toolbarRect) && !pointOnRect(mouse,toolboxRect)){
mouse.x+=camera.x;
mouse.y+=camera.y;
//Boolean if there's a click event fired.
bool clickEvent=false;
//Check if a mouse button is pressed.
if(event.type==SDL_MOUSEBUTTONDOWN){
//Right click in path or link mode means return to normal mode.
if (event.button.button == SDL_BUTTON_RIGHT && (linking || moving)) {
//Stop linking.
linking = false;
linkingTrigger = NULL;
//Stop moving.
moving = false;
movingBlock = NULL;
//Stop processing further.
return;
}
std::vector<GameObject*> clickObjects;
//Loop through the objects to check collision.
if (selectedLayer.empty()) {
if (layerVisibility[selectedLayer]) {
for (unsigned int o = 0; o<levelObjects.size(); o++){
if (pointOnRect(mouse, levelObjects[o]->getBox()) == true){
clickObjects.push_back(levelObjects[o]);
}
}
}
} else {
auto it = sceneryLayers.find(selectedLayer);
if (it != sceneryLayers.end() && layerVisibility[selectedLayer]) {
for (auto o : it->second->objects){
if (pointOnRect(mouse, o->getBox()) == true){
clickObjects.push_back(o);
}
}
}
}
//Check if there are multiple objects above eachother or just one.
if(clickObjects.size()==1){
//We have collision meaning that the mouse is above an object.
std::vector<GameObject*>::iterator it;
it=find(selection.begin(),selection.end(),clickObjects[0]);
//Set event true since there's a click event.
clickEvent=true;
//Check if the clicked object is in the selection or not.
bool isSelected=(it!=selection.end());
if(event.button.button==SDL_BUTTON_LEFT){
onClickObject(clickObjects[0],isSelected);
}else if(event.button.button==SDL_BUTTON_RIGHT){
onRightClickObject(imageManager,renderer,clickObjects[0],isSelected);
}
}else if(clickObjects.size()>=1){
//There are more than one object under the mouse
//SDL2 port (never managed to trigger this without changing the parameters.
std::vector<GameObject*>::iterator it;
it=find(selection.begin(),selection.end(),clickObjects[0]);
//Set event true since there's a click event.
clickEvent=true;
//Check if the clicked object is in the selection or not.
bool isSelected=(it!=selection.end());
//Only show the selection popup when right clicking.
if(event.button.button==SDL_BUTTON_LEFT){
onClickObject(clickObjects[0],isSelected);
}else if(event.button.button==SDL_BUTTON_RIGHT){
//Remove the selection popup if there's one.
if(selectionPopup!=NULL)
delete selectionPopup;
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
selectionPopup=new LevelEditorSelectionPopup(this,imageManager,renderer,clickObjects,x,y);
}
}
}
//If event is false then we clicked on void.
if(!clickEvent){
if(event.type==SDL_MOUSEBUTTONDOWN){
if(event.button.button==SDL_BUTTON_LEFT){
//Left mouse button on void.
onClickVoid(mouse.x,mouse.y);
}else if(event.button.button==SDL_BUTTON_RIGHT){
onRightClickVoid(imageManager,renderer,mouse.x,mouse.y);
}
}
}
}
//Check for backspace when moving to remove a movingposition.
if(moving && event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_BACKSPACE){
if(movingBlocks[movingBlock].size()>0){
commandManager->doCommand(new AddRemovePathCommand(this, movingBlock, MovingPosition(0, 0, 0), false));
}
}
//Check for the tab key, level settings.
if(inputMgr.isKeyDownEvent(INPUTMGR_TAB)){
//Show the levelSettings.
levelSettings(imageManager,renderer);
}
//Check if we should save the level (Ctrl+s).
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_s && (event.key.keysym.mod & KMOD_CTRL)){
saveCurrentLevel(imageManager, renderer);
}
//Undo ctrl+z
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_z && (event.key.keysym.mod & KMOD_CTRL)){
undo();
}
//Redo ctrl+y
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_y && (event.key.keysym.mod & KMOD_CTRL)){
redo();
}
}
}
void LevelEditor::saveCurrentLevel(ImageManager& imageManager, SDL_Renderer& renderer) {
saveLevel(levelFile);
//Clear the dirty flag
commandManager->resetChange();
//And give feedback to the user.
if (levelName.empty())
msgBox(imageManager, renderer, tfm::format(_("Level \"%s\" saved"), fileNameFromPath(levelFile)), MsgBoxOKOnly, _("Saved"));
else
msgBox(imageManager, renderer, tfm::format(_("Level \"%s\" saved"), levelName), MsgBoxOKOnly, _("Saved"));
}
void LevelEditor::enterPlayMode(){
//Check if we are already in play mode.
if(playMode) return;
//Stop linking or moving.
if(linking){
linking=false;
linkingTrigger=NULL;
}
if(moving){
moving=false;
movingBlock=NULL;
}
//We need to reposition player and shadow here, since the related code is removed from object placement.
for (auto obj : levelObjects) {
//If the object is a player or shadow start then change the start position of the player or shadow.
if (obj->type == TYPE_START_PLAYER){
//Center the player horizontally.
player.fx = obj->getBox().x + (obj->getBox().w - player.getBox().w) / 2;
player.fy = obj->getBox().y;
}
if (obj->type == TYPE_START_SHADOW){
//Center the shadow horizontally.
shadow.fx = obj->getBox().x + (obj->getBox().w - shadow.getBox().w) / 2;
shadow.fy = obj->getBox().y;
}
}
//Change mode.
playMode=true;
GUIObjectRoot->visible=false;
cameraSave.x=camera.x;
cameraSave.y=camera.y;
//We swap the levelObjects and levelObjectsInitial again.
std::swap(levelObjects, levelObjectsInitial);
//Restart the game with scripting enabled.
Game::reset(true, false);
}
void LevelEditor::undo(){
commandManager->undo();
}
void LevelEditor::redo(){
commandManager->redo();
}
void LevelEditor::levelSettings(ImageManager& imageManager,SDL_Renderer& renderer){
//It isn't so open a popup asking for a name.
GUIWindow* root=new GUIWindow(imageManager,renderer,(SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-450)/2,600,450,true,true,_("Level settings"));
root->minWidth = root->width; root->minHeight = root->height;
root->name="lvlSettingsWindow";
root->eventCallback=this;
GUIObject* obj;
//Create the two textboxes with a label.
obj=new GUILabel(imageManager,renderer,40,60,240,36,_("Name:"));
root->addChild(obj);
obj=new GUITextBox(imageManager,renderer,140,60,410,36,levelName.c_str());
obj->gravityRight = GUIGravityRight;
obj->name="name";
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 40, 110, 240, 36, (std::string("* ") + _("Theme:")).c_str());
root->addChild(obj);
obj=new GUITextBox(imageManager,renderer,140,110,410,36,levelTheme.c_str());
obj->gravityRight = GUIGravityRight;
obj->name="theme";
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 40, 150, 510, 36, _("Examples: %DATA%/themes/classic"));
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 40, 174, 510, 36, _("or %USER%/themes/Orange"));
root->addChild(obj);
obj = new GUILabel(imageManager, renderer, 40, 210, 240, 36, _("Music:"));
root->addChild(obj);
obj = new GUITextBox(imageManager, renderer, 140, 210, 410, 36, levelMusic.c_str());
obj->gravityRight = GUIGravityRight;
obj->name = "music";
root->addChild(obj);
//target time and recordings.
{
- obj=new GUILabel(imageManager,renderer,40,260,240,36,_("Target time (s):"));
+ obj=new GUICheckBox(imageManager,renderer,40,260,240,36,_("Target time (s):"));
+ obj->name = "chkTime";
+ obj->value = levelTime >= 0 ? 1 : 0;
+ obj->eventCallback = root;
root->addChild(obj);
GUISpinBox* obj2=new GUISpinBox(imageManager,renderer,290,260,260,36);
obj2->gravityRight = GUIGravityRight;
obj2->name="time";
- ostringstream ss;
- ss << levelTime/40.0f;
- obj2->caption=ss.str();
+ char ss[128];
+ sprintf(ss, "%0.2f", double(levelTime >= 0 ? levelTime : ~levelTime) / 40.0);
+ obj2->caption=ss;
- obj2->limitMin=-1.0f;
- obj2->format = "%g";
+ obj2->visible = levelTime >= 0;
+ obj2->limitMin=0.0f;
+ obj2->limitMax = 1E+6f;
+ obj2->format = "%0.2f";
obj2->change=0.1f;
obj2->update();
root->addChild(obj2);
- obj=new GUILabel(imageManager,renderer,40,310,240,36,_("Target recordings:"));
+ obj=new GUICheckBox(imageManager,renderer,40,310,240,36,_("Target recordings:"));
+ obj->name = "chkRecordings";
+ obj->value = levelRecordings >= 0 ? 1 : 0;
+ obj->eventCallback = root;
root->addChild(obj);
obj2=new GUISpinBox(imageManager,renderer,290,310,260,36);
obj2->gravityRight = GUIGravityRight;
- ostringstream ss2;
- ss2 << levelRecordings;
- obj2->caption=ss2.str();
+ sprintf(ss, "%d", levelRecordings >= 0 ? levelRecordings : ~levelRecordings);
+ obj2->caption=ss;
- obj2->limitMin=-1.0f;
+ obj2->visible = levelRecordings >= 0;
+ obj2->limitMin=0.0f;
+ obj2->limitMax = 1E+6f;
obj2->format="%1.0f";
obj2->name="recordings";
obj2->update();
root->addChild(obj2);
}
obj = new GUILabel(imageManager, renderer, 40, 350, 510, 36, (std::string("* ") + _("Restart level editor is required")).c_str());
root->addChild(obj);
//Ok and cancel buttons.
obj=new GUIButton(imageManager,renderer,root->width*0.3,450-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="lvlSettingsOK";
obj->eventCallback=root;
root->addChild(obj);
obj=new GUIButton(imageManager,renderer,root->width*0.7,450-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->gravityLeft = obj->gravityRight = GUIGravityCenter;
obj->gravityTop = obj->gravityBottom = GUIGravityRight;
obj->name="lvlSettingsCancel";
obj->eventCallback=root;
root->addChild(obj);
GUIObjectRoot->addChild(root);
}
void LevelEditor::postLoad(){
//We need to find the triggers.
for(unsigned int o=0;o<levelObjects.size();o++){
//Check for the highest id.
unsigned int id=atoi(levelObjects[o]->getEditorProperty("id").c_str());
if(id>=currentId)
currentId=id+1;
switch(levelObjects[o]->type){
case TYPE_BUTTON:
case TYPE_SWITCH:
{
//Add the object to the triggers vector.
vector<GameObject*> linked;
triggers[levelObjects[o]]=linked;
//Now loop through the levelObjects in search for objects with the same id.
for(unsigned int oo=0;oo<levelObjects.size();oo++){
//Check if it isn't the same object but has the same id.
if(o!=oo && (dynamic_cast<Block*>(levelObjects[o]))->id==(dynamic_cast<Block*>(levelObjects[oo]))->id){
//Add the object to the link vector of the trigger.
triggers[levelObjects[o]].push_back(levelObjects[oo]);
}
}
break;
}
case TYPE_PORTAL:
{
//Add the object to the triggers vector.
vector<GameObject*> linked;
triggers[levelObjects[o]]=linked;
//If the destination is empty we return.
if((dynamic_cast<Block*>(levelObjects[o]))->destination.empty()){
break;
}
//Now loop through the levelObjects in search for objects with the same id as destination.
for(unsigned int oo=0;oo<levelObjects.size();oo++){
//Check if it isn't the same object but has the same id.
if(o!=oo && (dynamic_cast<Block*>(levelObjects[o]))->destination==(dynamic_cast<Block*>(levelObjects[oo]))->id){
//Add the object to the link vector of the trigger.
triggers[levelObjects[o]].push_back(levelObjects[oo]);
}
}
break;
}
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//Get the moving position.
const vector<SDL_Rect> &movingPos = levelObjects[o]->movingPos;
//Add the object to the movingBlocks vector.
movingBlocks[levelObjects[o]].clear();
for (int i = 0, m = movingPos.size(); i < m; i++) {
MovingPosition position(movingPos[i].x, movingPos[i].y, movingPos[i].w);
movingBlocks[levelObjects[o]].push_back(position);
}
break;
}
default:
break;
}
}
// Set the visibility of all layers to true
layerVisibility.clear();
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it) {
layerVisibility[it->first] = true;
}
layerVisibility[std::string()] = true;
// Also set the current layer to the Block layer
selectedLayer.clear();
// Get all available scenery blocks
std::set<std::string> tmpset;
objThemes.getSceneryBlockNames(tmpset);
sceneryBlockNames.clear();
sceneryBlockNames.insert(sceneryBlockNames.end(), tmpset.begin(), tmpset.end());
}
void LevelEditor::snapToGrid(int* x,int* y){
//Check if the x location is negative.
if(*x<0){
*x=-((abs(*x-50)/50)*50);
}else{
*x=(*x/50)*50;
}
//Now the y location.
if(*y<0){
*y=-((abs(*y-50)/50)*50);
}else{
*y=(*y/50)*50;
}
}
void LevelEditor::setCamera(const SDL_Rect* r,int count){
//SetCamera only works in the Level editor and when mouse is inside window.
if(stateID==STATE_LEVEL_EDITOR&&(SDL_GetMouseFocus() == sdlWindow)){
//Get the mouse coordinates.
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
//Don't continue here if mouse is inside one of the boxes given as parameter.
for(int i=0;i<count;i++){
if(pointOnRect(mouse,r[i]))
return;
}
//FIXME: two ad-hoc camera speed variables similar to cameraXvel and cameraYvel
static int cameraXvelB = 0, cameraYvelB = 0;
//Check if the mouse is near the left edge of the screen.
//Else check if the mouse is near the right edge.
if (x < 50) {
//We're near the left edge so move the camera.
if (cameraXvelB > -5) cameraXvelB = -5;
if (pressedShift) cameraXvelB--;
} else if (x > SCREEN_WIDTH - 50) {
//We're near the right edge so move the camera.
if (cameraXvelB < 5) cameraXvelB = 5;
if (pressedShift) cameraXvelB++;
} else {
cameraXvelB = 0;
}
//Check if the tool box is visible and we need to calc screen size correctly.
int y0=50;
if (toolboxVisible && toolboxRect.w > 0) y0 += toolbarRect.h;
//Check if the mouse is near the top edge of the screen.
//Else check if the mouse is near the bottom edge.
if (y < y0) {
//We're near the top edge so move the camera.
if (cameraYvelB > -5) cameraYvelB = -5;
if (pressedShift) cameraYvelB--;
} else if (y > SCREEN_HEIGHT - 50) {
//We're near the bottom edge so move the camera.
if (cameraYvelB < 5) cameraYvelB = 5;
if (pressedShift) cameraYvelB++;
} else {
cameraYvelB = 0;
}
camera.x = clamp(camera.x + cameraXvelB, -1000 - SCREEN_WIDTH, LEVEL_WIDTH + 1000);
camera.y = clamp(camera.y + cameraYvelB, -1000 - SCREEN_HEIGHT, LEVEL_HEIGHT + 1000);
}
}
void LevelEditor::onClickObject(GameObject* obj,bool selected){
switch(tool){
case SELECT:
{
//Check if we are linking.
if(linking){
//Check if the obj is valid to link to.
switch(obj->type){
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//It's only valid when not linking a portal.
if(linkingTrigger->type==TYPE_PORTAL){
//You can't link a portal to moving blocks, etc.
//Stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
}
break;
}
case TYPE_PORTAL:
{
//Make sure that the linkingTrigger is also a portal.
if(linkingTrigger->type!=TYPE_PORTAL){
//The linkingTrigger isn't a portal so stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
}
break;
}
default:
//It isn't valid so stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
break;
}
AddLinkCommand* pCommand = new AddLinkCommand(this, linkingTrigger, obj);
commandManager->doCommand(pCommand);
//We return to prevent configuring stuff like conveyor belts, etc...
linking=false;
linkingTrigger=NULL;
return;
}
//If we're moving add a movingposition.
if(moving){
//Get the current mouse location.
int x, y;
SDL_GetMouseState(&x, &y);
x += camera.x;
y += camera.y;
addMovingPosition(x, y);
return;
}
}
case ADD:
{
//Check if object is already selected.
if(!selected){
//First check if shift is pressed or not.
if(!pressedShift){
//Clear the selection.
deselectAll();
}
//Add the object to the selection.
selection.push_back(obj);
}
break;
}
case REMOVE:
{
//Remove the object.
commandManager->doCommand(new AddRemoveGameObjectCommand(this, obj, false));
break;
}
default:
break;
}
}
void LevelEditor::addMovingPosition(int x,int y) {
//Apply snap to grid.
if (!pressedShift){
snapToGrid(&x, &y);
} else{
x -= 25;
y -= 25;
}
x -= movingBlock->getBox().x;
y -= movingBlock->getBox().y;
//Calculate the length.
//First get the delta x and y.
int dx, dy;
if (movingBlocks[movingBlock].empty()){
dx = x;
dy = y;
} else{
dx = x - movingBlocks[movingBlock].back().x;
dy = y - movingBlocks[movingBlock].back().y;
}
AddRemovePathCommand* pCommand = NULL;
if (dx == 0 && dy == 0) {
// pause mode
if (pauseTime != 0) pCommand = new AddRemovePathCommand(this, movingBlock, MovingPosition(x, y, std::max(pauseTime, 0)), true);
pauseTime = 0;
} else {
// add new point mode
const double length = sqrt(double(dx*dx + dy*dy));
pCommand = new AddRemovePathCommand(this, movingBlock, MovingPosition(x, y, (int)(length*(10 / (double)movingSpeed))), true);
}
if (pCommand) commandManager->doCommand(pCommand);
}
void LevelEditor::onRightClickObject(ImageManager& imageManager,SDL_Renderer& renderer,GameObject* obj,bool){
//Create an actions popup for the game object.
if(actionsPopup==NULL){
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
actionsPopup=new LevelEditorActionsPopup(imageManager,renderer,this,obj,x,y);
return;
}
}
void LevelEditor::onClickVoid(int x,int y){
switch(tool){
case ADD:
{
//We need to clear the selection.
deselectAll();
//Now place an object.
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
if (currentType >= 0 && currentType < getEditorOrderMax()) {
GameObject *obj;
if (selectedLayer.empty()) {
obj = new Block(this, x, y, 50, 50, editorTileOrder[currentType]);
} else {
obj = new Scenery(this, x, y, 50, 50,
currentType < (int)sceneryBlockNames.size() ? sceneryBlockNames[currentType] : std::string());
}
commandManager->doCommand(new AddRemoveGameObjectCommand(this, obj, true));
}
break;
}
case SELECT:
{
//We need to clear the selection.
deselectAll();
//If we're linking we should stop, user abort.
if(linking){
linking=false;
linkingTrigger=NULL;
//And return.
return;
}
//If we're moving we should add a point.
if(moving){
addMovingPosition(x, y);
//And return.
return;
}
break;
}
default:
break;
}
}
void LevelEditor::onRightClickVoid(ImageManager& imageManager,SDL_Renderer& renderer,int,int){
//Create an actions popup for the game object.
if(actionsPopup==NULL){
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
actionsPopup=new LevelEditorActionsPopup(imageManager,renderer,this,NULL,x,y);
return;
}
}
void LevelEditor::onDragStart(int x,int y){
switch(tool){
case SELECT:
case ADD:
{
//We can drag the selection so check if the selection isn't empty.
if(!selection.empty()){
//The selection isn't empty so search the dragCenter.
//Create a mouse rectangle.
SDL_Rect mouse={x,y,0,0};
// record the drag start position
dragSrartPosition.x = x;
dragSrartPosition.y = y;
//Loop through the objects to check collision.
for(unsigned int o=0; o<selection.size(); o++){
SDL_Rect r = selection[o]->getBox();
if(pointOnRect(mouse, r)){
//We have collision so set the dragCenter.
dragCenter=selection[o];
// determine which part is dragged
selectionDrag = 4;
int midx = r.x + r.w / 2 - 2;
int midy = r.y + r.h / 2 - 2;
if (mouse.x >= r.x && mouse.x < r.x + 5) {
if (mouse.y >= r.y && mouse.y < r.y + 5) {
selectionDrag = 0;
} else if (mouse.y >= midy && mouse.y < midy + 5) {
selectionDrag = 3;
} else if (mouse.y >= r.y + r.h - 5 && mouse.y < r.y + r.h) {
selectionDrag = 6;
}
} else if (mouse.x >= midx && mouse.x < midx + 5) {
if (mouse.y >= r.y && mouse.y < r.y + 5) {
selectionDrag = 1;
} else if (mouse.y >= r.y + r.h - 5 && mouse.y < r.y + r.h) {
selectionDrag = 7;
}
} else if (mouse.x >= r.x + r.w - 5 && mouse.x < r.x + r.w) {
if (mouse.y >= r.y && mouse.y < r.y + 5) {
selectionDrag = 2;
} else if (mouse.y >= midy && mouse.y < midy + 5) {
selectionDrag = 5;
} else if (mouse.y >= r.y + r.h - 5 && mouse.y < r.y + r.h) {
selectionDrag = 8;
}
}
break;
}
}
}
break;
}
default:
break;
}
}
void LevelEditor::onDrag(int dx,int dy){
switch(tool){
case REMOVE:
{
//No matter what we delete the item the mouse is above.
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
currentCursor=CURSOR_REMOVE;
std::vector<GameObject*> objects;
//Loop through the objects to check collision.
if (selectedLayer.empty()) {
if (layerVisibility[selectedLayer]) {
for (unsigned int o = 0; o<levelObjects.size(); o++){
if (pointOnRect(mouse, levelObjects[o]->getBox()) == true){
objects.push_back(levelObjects[o]);
}
}
}
} else {
auto it = sceneryLayers.find(selectedLayer);
if (it != sceneryLayers.end() && layerVisibility[selectedLayer]) {
for (auto o : it->second->objects){
if (pointOnRect(mouse, o->getBox()) == true){
objects.push_back(o);
}
}
}
}
// Do the actual object deletion.
if (!objects.empty()) {
commandManager->doCommand(new AddRemoveGameObjectCommand(this, objects, false));
}
break;
}
default:
break;
}
}
void LevelEditor::onDrop(int x,int y){
switch(tool){
case SELECT:
case ADD:
{
//Check if the drag center isn't null.
if(dragCenter==NULL)
return;
//The location of the dragCenter.
SDL_Rect r=dragCenter->getBox();
if (selectionDrag == 4) { // dragging
//Apply snap to grid.
determineNewPosition(x, y);
commandManager->doCommand(new MoveGameObjectCommand(this, selection, x - r.x, y - r.y));
} else if (selectionDrag >= 0) { // resizing
determineNewSize(x, y, r);
commandManager->doCommand(new MoveGameObjectCommand(this, dragCenter, r.x, r.y, r.w, r.h));
}
//Make sure the dragCenter is null and set selectionDrag false.
dragCenter=NULL;
selectionDrag=-1;
break;
}
default:
break;
}
}
void LevelEditor::onCameraMove(int dx,int dy){
switch(tool){
case REMOVE:
{
//Only delete when the left mouse button is pressed.
if(pressedLeftMouse){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
std::vector<GameObject*> objects;
//Loop through the objects to check collision.
if (selectedLayer.empty()) {
if (layerVisibility[selectedLayer]) {
for (unsigned int o = 0; o<levelObjects.size(); o++){
if (pointOnRect(mouse, levelObjects[o]->getBox()) == true){
objects.push_back(levelObjects[o]);
}
}
}
} else {
auto it = sceneryLayers.find(selectedLayer);
if (it != sceneryLayers.end() && layerVisibility[selectedLayer]) {
for (auto o : it->second->objects){
if (pointOnRect(mouse, o->getBox()) == true){
objects.push_back(o);
}
}
}
}
// Do the actual object deletion.
if (!objects.empty()) {
commandManager->doCommand(new AddRemoveGameObjectCommand(this, objects, false));
}
}
break;
}
default:
break;
}
}
void LevelEditor::selectionDirty() {
if (selectionPopup != NULL) selectionPopup->dirty = true;
}
void LevelEditor::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Check if one of the windows is closed.
if (eventType == GUIEventClick && name.size() >= 6 && name.substr(name.size() - 6) == "Window") {
destroyWindow(obj);
return;
}
//Resize code for each GUIWindow.
if (name.size() >= 6 && name.substr(name.size() - 6) == "Window") {
//Currently we don't need to process custom resize code since they are already processed in GUIWindow::resize().
return;
}
//Check for GUI events.
//Notification block configure events.
if(name=="cfgNotificationBlockOK"){
//Get the configuredObject.
GameObject* configuredObject=objectWindows[obj];
if(configuredObject){
//Get the message textbox from the GUIWindow.
GUITextArea* message=(GUITextArea*)obj->getChild("message");
if(message){
//Set the message of the notification block.
commandManager->doCommand(new SetEditorPropertyCommand(this, imageManager, renderer,
configuredObject, "message", message->getString(), _("Message")));
}
}
}
//Conveyor belt block configure events.
else if(name=="cfgConveyorBlockOK"){
//Get the configuredObject.
GameObject* configuredObject=objectWindows[obj];
if(configuredObject){
//Get the speed textbox from the GUIWindow.
GUISpinBox* speed=(GUISpinBox*)obj->getChild("speed");
if(speed){
//Set the speed of the conveyor belt.
commandManager->doCommand(new SetEditorPropertyCommand(this, imageManager, renderer,
configuredObject, "speed10", speed->caption, _("Speed")));
}
}
}
+ else if (name == "chkTime") {
+ obj->getChild("time")->visible = obj->getChild("chkTime")->value ? 1 : 0;
+ return;
+ }
+ else if (name == "chkRecordings") {
+ obj->getChild("recordings")->visible = obj->getChild("chkRecordings")->value ? 1 : 0;
+ return;
+ }
//LevelSetting events.
else if(name=="lvlSettingsOK"){
SetLevelPropertyCommand::LevelProperty prop;
prop.levelTime = -1;
prop.levelRecordings = -1;
GUIObject* object=obj->getChild("name");
if(object)
prop.levelName=object->caption;
object=obj->getChild("theme");
if(object)
prop.levelTheme=object->caption;
object = obj->getChild("music");
if (object)
prop.levelMusic = object->caption;
//target time and recordings.
+ object = obj->getChild("chkTime");
GUISpinBox* object2 = dynamic_cast<GUISpinBox*>(obj->getChild("time"));
- if(object2){
- float number = atof(object2->caption.c_str());
- prop.levelTime = int(floor(number*40.0f + 0.5f));
- }
+ assert(object && object2);
+
+ double number = std::max(atof(object2->caption.c_str()), 0.0);
+ prop.levelTime = int(floor(number*40.0 + 0.5));
+ if (object->value == 0) prop.levelTime = ~prop.levelTime;
+ object = obj->getChild("chkRecordings");
object2 = dynamic_cast<GUISpinBox*>(obj->getChild("recordings"));
- if(object2){
- prop.levelRecordings = atoi(object2->caption.c_str());
- }
+ assert(object && object2);
+
+ prop.levelRecordings = std::max(atoi(object2->caption.c_str()), 0);
+ if (object->value == 0) prop.levelRecordings = ~prop.levelRecordings;
// Perform the level setting modification
commandManager->doCommand(new SetLevelPropertyCommand(this, prop));
}
//Level scripting window events.
else if(name=="cfgLevelScriptingEventType"){
//Get the script textbox from the GUIWindow.
GUISingleLineListBox* list=(GUISingleLineListBox*)obj->getChild("cfgLevelScriptingEventType");
if(list){
//Loop through the scripts.
for(unsigned int i=0;i<list->item.size();i++){
GUIObject* script=obj->getChild(list->item[i].first);
if(script){
script->visible=(script->name==list->item[list->value].first);
script->enabled=(script->name==list->item[list->value].first);
}
}
}
return;
}
else if(name=="cfgLevelScriptingOK"){
//Get the script textbox from the GUIWindow.
GUISingleLineListBox* list=(GUISingleLineListBox*)obj->getChild("cfgLevelScriptingEventType");
if(list){
std::map<int, std::string> newScript;
//Loop through the scripts.
for(unsigned int i=0;i<list->item.size();i++){
//Get the GUITextArea.
GUITextArea* script=dynamic_cast<GUITextArea*>(obj->getChild(list->item[i].first));
if(script){
//Set the script for the target block.
string str=script->getString();
if(!str.empty())
newScript[levelEventNameMap[script->name]]=str;
}
}
// Check achievement
if (!newScript.empty()) {
statsMgr.newAchievement("helloworld");
}
// Do the actual changes
commandManager->doCommand(new SetScriptCommand(this, NULL, newScript));
}
}
//Scripting window events.
else if (name == "cfgScriptingEventType"){
//TODO: Save any unsaved scripts? (Or keep track of all scripts and save upon cfgScriptingOK?)
//Get the configuredObject.
Block* configuredObject=dynamic_cast<Block*>(objectWindows[obj]);
if(configuredObject){
//Get the script textbox from the GUIWindow.
GUISingleLineListBox* list=dynamic_cast<GUISingleLineListBox*>(obj->getChild("cfgScriptingEventType"));
if(list){
//Loop through the scripts.
for(unsigned int i=0;i<list->item.size();i++){
GUIObject* script=obj->getChild(list->item[i].first);
if(script){
script->visible=script->enabled=(script->name==list->item[list->value].first);
}
}
}
}
return;
}
else if(name=="cfgScriptingOK"){
//Get the configuredObject.
Block* block = dynamic_cast<Block*>(objectWindows[obj]);
if (block){
std::map<int, std::string> newScript;
std::string newId;
//Get the script textbox from the GUIWindow.
GUISingleLineListBox* list=(GUISingleLineListBox*)obj->getChild("cfgScriptingEventType");
GUIObject* id=obj->getChild("id");
if (list){
//Loop through the scripts.
for (unsigned int i = 0; i < list->item.size(); i++){
//Get the GUITextArea.
GUITextArea* script = dynamic_cast<GUITextArea*>(obj->getChild(list->item[i].first));
if (script){
//Set the script for the target block.
string str = script->getString();
if (!str.empty())
newScript[gameObjectEventNameMap[script->name]] = str;
}
}
}
newId = block->id;
if (id){
newId = id->caption;
}
// Check achievement
if (!newScript.empty()) {
statsMgr.newAchievement("helloworld");
}
// now do the actual changes
commandManager->doCommand(new SetScriptCommand(this, block, newScript, newId));
}
}
else if (name == "cfgAddLayerOK") {
GUIObject* object = obj->getChild("layerName");
if (!object) return;
if (object->caption.empty()) {
msgBox(imageManager, renderer, _("Please enter a layer name."), MsgBoxOKOnly, _("Error"));
return;
}
if (sceneryLayers.find(object->caption) != sceneryLayers.end()) {
msgBox(imageManager, renderer, tfm::format(_("The layer '%s' already exists."), object->caption), MsgBoxOKOnly, _("Error"));
return;
}
// do the actual operation
commandManager->doCommand(new AddRemoveLayerCommand(this, object->caption, true));
}
else if (name == "cfgLayerSettingsOK") {
SetLayerPropertyCommand::LayerProperty prop;
GUIObject* object = obj->getChild("layerName");
if (!object) return;
prop.name = object->caption;
object = obj->getChild("speedX");
if (!object) return;
prop.speedX = atof(object->caption.c_str());
object = obj->getChild("speedY");
if (!object) return;
prop.speedY = atof(object->caption.c_str());
object = obj->getChild("cameraX");
if (!object) return;
prop.cameraX = atof(object->caption.c_str());
object = obj->getChild("cameraY");
if (!object) return;
prop.cameraY = atof(object->caption.c_str());
object = obj->getChild("oldName");
if (!object) return;
const std::string& oldName = object->caption;
if (prop.name.empty()) {
msgBox(imageManager, renderer, _("Please enter a layer name."), MsgBoxOKOnly, _("Error"));
return;
}
if (prop.name != oldName && sceneryLayers.find(prop.name) != sceneryLayers.end()) {
msgBox(imageManager, renderer, tfm::format(_("The layer '%s' already exists."), prop.name), MsgBoxOKOnly, _("Error"));
return;
}
// do the actual operation
commandManager->doCommand(new SetLayerPropertyCommand(this, oldName, prop));
}
else if (name == "cfgMoveToLayerOK") {
GUIObject* object = obj->getChild("layerName");
if (!object) return;
const std::string& layerName = object->caption;
object = obj->getChild("oldName");
if (!object) return;
const std::string& oldName = object->caption;
if (layerName.empty()) {
msgBox(imageManager, renderer, _("Please enter a layer name."), MsgBoxOKOnly, _("Error"));
return;
}
if (oldName == layerName) {
msgBox(imageManager, renderer, _("Source and destination layers are the same."), MsgBoxOKOnly, _("Error"));
return;
}
// do the actual operation
commandManager->doCommand(new MoveToLayerCommand(this, selection, oldName, layerName));
}
else if (name == "cfgCustomSceneryOK") {
//Get the configuredObject.
Scenery* configuredObject = dynamic_cast<Scenery*>(objectWindows[obj]);
if (configuredObject){
//Get the custom scenery from the GUIWindow.
GUITextArea* txt = (GUITextArea*)obj->getChild("cfgCustomScenery");
if (txt){
//Set the custom scenery.
commandManager->doCommand(new SetEditorPropertyCommand(this, imageManager, renderer,
configuredObject, "customScenery", txt->getString(), _("Scenery")));
}
}
}
else if (name == "lstAppearance") {
//Get the configuredObject.
Block *block = dynamic_cast<Block*>(objectWindows[obj]);
GUIListBox *list = dynamic_cast<GUIListBox*>(obj->getChild("lstAppearance"));
GUIImage *image = dynamic_cast<GUIImage*>(obj->getChild("imgAppearance"));
if (block && list && image) {
//Reset the image first.
image->setImage(NULL);
image->setClipRect(SDL_Rect{ 0, 0, 0, 0 });
//Get the appearance name.
std::string appearanceName;
if (list->value <= 0) {
//Do nothing since the selected is the default appearance.
} else if (list->value <= (int)sceneryBlockNames.size()) {
//A custom appearance is selected.
appearanceName = sceneryBlockNames[list->value - 1];
} else {
//The configured object has an invalid custom appearance name.
appearanceName = block->customAppearanceName;
}
//Try to find the theme block.
ThemeBlock *themeBlock = NULL;
if (!appearanceName.empty()) {
themeBlock = objThemes.getScenery(appearanceName);
}
if (themeBlock == NULL) {
themeBlock = objThemes.getBlock(block->type);
}
if (themeBlock) {
image->setImage(themeBlock->editorPicture.texture);
const auto& offsetData = themeBlock->editorPicture.offset.offsetData;
if (offsetData.size() > 0) {
const auto& r = offsetData[0];
image->setClipRect(SDL_Rect{ r.x, r.y, r.w, r.h });
}
}
}
return;
}
else if (name == "cfgAppearanceOK") {
//Get the configuredObject.
Block *block = dynamic_cast<Block*>(objectWindows[obj]);
GUIListBox *list = dynamic_cast<GUIListBox*>(obj->getChild("lstAppearance"));
if (block && list) {
//Get the appearance name.
std::string appearanceName;
if (list->value <= 0) {
//Do nothing since the selected is the default appearance.
} else if (list->value <= (int)sceneryBlockNames.size()) {
//A custom appearance is selected.
appearanceName = sceneryBlockNames[list->value - 1];
} else {
//The configured object has an invalid custom appearance name.
appearanceName = block->customAppearanceName;
}
//Update the block property if it's changed.
if (appearanceName != block->customAppearanceName) {
commandManager->doCommand(new SetEditorPropertyCommand(this, imageManager, renderer,
block, "appearance", appearanceName, _("Appearance")));
}
}
}
//NOTE: We assume every event came from a window
//and the event is either window closed event or OK/Cancel button click event, so we remove it.
destroyWindow(obj);
}
void LevelEditor::destroyWindow(GUIObject* window){
//Make sure the given pointer isn't null.
if(!window)
return;
//Remove the window from the GUIObject root.
if(GUIObjectRoot){
vector<GUIObject*>::iterator it;
it=find(GUIObjectRoot->childControls.begin(),GUIObjectRoot->childControls.end(),window);
if(it!=GUIObjectRoot->childControls.end()){
GUIObjectRoot->childControls.erase(it);
}
}
//Also remove the window from the objectWindows map.
map<GUIObject*,GameObject*>::iterator it;
it=objectWindows.find(window);
if(it!=objectWindows.end()){
objectWindows.erase(it);
}
//And delete the GUIWindow.
delete window;
}
////////////////LOGIC////////////////////
void LevelEditor::logic(ImageManager& imageManager, SDL_Renderer& renderer){
if(playMode){
//PlayMode so let the game do it's logic.
Game::logic(imageManager,renderer);
}else{
//Update animation even under edit mode. (There are checks in Block::move() which don't do game logic in edit mode.)
for (unsigned int i = 0; i<levelObjects.size(); i++){
//Let the gameobject handle movement.
levelObjects[i]->move();
}
//Also update the scenery.
for (auto it = sceneryLayers.begin(); it != sceneryLayers.end(); ++it){
it->second->updateAnimation();
}
//In case of a selection or actions popup prevent the camera from moving.
if(selectionPopup || actionsPopup)
return;
//Move the camera.
if (cameraXvel != 0 || cameraYvel != 0) {
if (pressedShift) {
if (cameraXvel > 0) cameraXvel++;
else if (cameraXvel < 0) cameraXvel--;
if (cameraYvel > 0) cameraYvel++;
else if (cameraYvel < 0) cameraYvel--;
}
camera.x = clamp(camera.x + cameraXvel, -1000 - SCREEN_WIDTH, LEVEL_WIDTH + 1000);
camera.y = clamp(camera.y + cameraYvel, -1000 - SCREEN_HEIGHT, LEVEL_HEIGHT + 1000);
//Call the onCameraMove event.
onCameraMove(cameraXvel, cameraYvel);
}
//Move the camera with the mouse.
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
{
//Check if the mouse isn't above a GUIObject (window).
bool inside=false;
for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
SDL_Rect box={0,0,0,0};
box.x=GUIObjectRoot->childControls[i]->left;
box.y=GUIObjectRoot->childControls[i]->top;
box.w=GUIObjectRoot->childControls[i]->width;
box.h=GUIObjectRoot->childControls[i]->height;
if(pointOnRect(mouse,box))
inside=true;
}
if(!inside){
SDL_Rect r[3]={toolbarRect,toolboxRect};
int m=2;
//TODO: Also call onCameraMove when moving using the mouse.
setCamera(r,m);
}
}
//It isn't playMode so the mouse should be checked.
tooltip=-1;
//We loop through the number of tools + the number of buttons.
for (int t = 0; t <= (int)ToolTips::BackToMenu; t++){
SDL_Rect toolRect={(SCREEN_WIDTH-460)/2+(t*40)+((t+1)*10),SCREEN_HEIGHT-45,40,40};
//Check for collision.
if(pointOnRect(mouse,toolRect)==true){
//Set the tooltip tool.
tooltip=t;
}
}
}
}
/////////////////RENDER//////////////////////
void LevelEditor::render(ImageManager& imageManager,SDL_Renderer& renderer){
//Let the game render the game when it is the play mode.
if (playMode) {
Game::render(imageManager, renderer);
} else {
// The following code are partially copied from Game::render()
//First of all render the background.
{
//Get a pointer to the background.
ThemeBackground* bg = background;
//Check if the background is null, but there are themes.
if (bg == NULL && objThemes.themeCount()>0){
//Get the background from the first theme in the stack.
bg = objThemes[0]->getBackground(false);
}
//Check if the background isn't null.
if (bg){
//It isn't so draw it.
bg->draw(renderer);
//And if it's the loaded background then also update the animation.
//FIXME: Updating the animation in the render method?
if (bg == background)
bg->updateAnimation();
} else{
//There's no background so fill the screen with white.
SDL_SetRenderDrawColor(&renderer, 255, 255, 255, 255);
SDL_RenderClear(&renderer);
}
}
//Now draw the background layers.
auto it = sceneryLayers.begin();
for (; it != sceneryLayers.end(); ++it){
if (it->first >= "f") break; // now we meet a foreground layer
if (layerVisibility[it->first]) {
it->second->show(renderer);
}
}
//Now we draw the levelObjects.
if (layerVisibility[std::string()]) {
- for (unsigned int o = 0; o < levelObjects.size(); o++){
- levelObjects[o]->show(renderer);
+ //NEW: always render the pushable blocks in front of other blocks
+ std::vector<Block*> pushableBlocks;
+
+ for (auto o : levelObjects) {
+ if (o->type == TYPE_PUSHABLE) {
+ pushableBlocks.push_back(o);
+ } else {
+ o->show(renderer);
+ }
+ }
+
+ for (auto o : pushableBlocks) {
+ o->show(renderer);
}
}
//We don't draw the player and the shadow at all.
//Now draw the foreground layers.
for (; it != sceneryLayers.end(); ++it){
if (layerVisibility[it->first]) {
it->second->show(renderer);
}
}
}
//Only render extra stuff like the toolbar, selection, etc.. when not in playMode.
if(!playMode){
//Get the current mouse location.
int x, y;
SDL_GetMouseState(&x, &y);
//Create the rectangle.
SDL_Rect mouse = { x + camera.x, y + camera.y, 0, 0 };
//Render the selectionmarks.
//TODO: Check if block is in sight.
for(unsigned int o=0; o<selection.size(); o++){
//Get the location to draw.
SDL_Rect r=selection[o]->getBox();
// Change the mouse cursor if necessary
if (selectionDrag < 0 && tool != REMOVE) {
int midx = r.x + r.w / 2 - 2;
int midy = r.y + r.h / 2 - 2;
if (mouse.x >= r.x && mouse.x < r.x + 5) {
if (mouse.y >= r.y && mouse.y < r.y + 5) {
currentCursor = CURSOR_SIZE_FDIAG;
} else if (mouse.y >= midy && mouse.y < midy + 5) {
currentCursor = CURSOR_SIZE_HOR;
} else if (mouse.y >= r.y + r.h - 5 && mouse.y < r.y + r.h) {
currentCursor = CURSOR_SIZE_BDIAG;
}
} else if (mouse.x >= midx && mouse.x < midx + 5) {
if (mouse.y >= r.y && mouse.y < r.y + 5) {
currentCursor = CURSOR_SIZE_VER;
} else if (mouse.y >= r.y + r.h - 5 && mouse.y < r.y + r.h) {
currentCursor = CURSOR_SIZE_VER;
}
} else if (mouse.x >= r.x + r.w - 5 && mouse.x < r.x + r.w) {
if (mouse.y >= r.y && mouse.y < r.y + 5) {
currentCursor = CURSOR_SIZE_BDIAG;
} else if (mouse.y >= midy && mouse.y < midy + 5) {
currentCursor = CURSOR_SIZE_HOR;
} else if (mouse.y >= r.y + r.h - 5 && mouse.y < r.y + r.h) {
currentCursor = CURSOR_SIZE_FDIAG;
}
}
}
bool mouseIn = pointOnRect(mouse, r);
r.x-=camera.x;
r.y-=camera.y;
drawGUIBox(r.x,r.y,r.w,r.h,renderer,0xFFFFFF33);
//Draw the selectionMarks.
applyTexture(r.x,r.y,selectionMark,renderer);
applyTexture(r.x+r.w-5,r.y,selectionMark,renderer);
applyTexture(r.x,r.y+r.h-5,selectionMark,renderer);
applyTexture(r.x+r.w-5,r.y+r.h-5,selectionMark,renderer);
// draw additional selection marks
if (mouseIn && selectionDrag < 0 && tool != REMOVE) {
applyTexture(r.x + r.w / 2 - 2, r.y, selectionMark, renderer);
applyTexture(r.x + r.w / 2 - 2, r.y + r.h - 5, selectionMark, renderer);
applyTexture(r.x, r.y + r.h / 2 - 2, selectionMark, renderer);
applyTexture(r.x + r.w - 5, r.y + r.h / 2 - 2, selectionMark, renderer);
}
}
//Set the color for the borders.
{
SDL_Color c = objThemes.getTextColor(false);
SDL_SetRenderDrawColor(&renderer, c.r, c.g, c.b, 115);
}
int leftWidth=0;
int rightWidth=0;
//Draw the dark areas marking the outside of the level.
SDL_Rect r{0,0,0,0};
if(camera.x<0){
//Draw left side.
r.x=0;
r.y=0;
r.w=0-camera.x;
leftWidth=r.w;
r.h=SCREEN_HEIGHT;
SDL_RenderFillRect(&renderer, &r);
}
if(camera.y<0){
//Draw the top.
r.x=leftWidth;
r.y=0;
r.w=SCREEN_WIDTH;
r.h=0-camera.y;
SDL_RenderFillRect(&renderer, &r);
} else {
r.h=0;
}
if(camera.x>LEVEL_WIDTH-SCREEN_WIDTH){
//Draw right side.
r.x=LEVEL_WIDTH-camera.x;
r.y=std::max(r.y+r.h,0);
r.w=SCREEN_WIDTH-(LEVEL_WIDTH-camera.x);
rightWidth=r.w;
r.h=SCREEN_HEIGHT;
SDL_RenderFillRect(&renderer, &r);
}
if(camera.y>LEVEL_HEIGHT-SCREEN_HEIGHT){
//Draw the bottom.
r.x=leftWidth;
r.y=LEVEL_HEIGHT-camera.y;
r.w=SCREEN_WIDTH-rightWidth-leftWidth;
r.h=SCREEN_HEIGHT-(LEVEL_HEIGHT-camera.y);
SDL_RenderFillRect(&renderer, &r);
}
//Check if we should draw on stuff.
showConfigure(renderer);
if (selectionDrag >= 0 && tool != REMOVE) {
showSelectionDrag(renderer);
}
//Find a block where the mouse is hovering on.
bool isMouseOnSomething = false;
if (selectedLayer.empty()){
if (layerVisibility[selectedLayer]) {
// Current layer is Blocks layer
for (unsigned int o = 0; o<levelObjects.size(); o++){
SDL_Rect rect = levelObjects[o]->getBox();
if (pointOnRect(mouse, rect) == true){
isMouseOnSomething = true;
if (tool == REMOVE){
drawGUIBox(rect.x - camera.x, rect.y - camera.y, rect.w, rect.h, renderer, 0xFF000055);
currentCursor = CURSOR_REMOVE;
} else{
drawGUIBox(rect.x - camera.x, rect.y - camera.y, rect.w, rect.h, renderer, 0xFFFFFF33);
}
}
}
}
} else {
auto it = sceneryLayers.find(selectedLayer);
if (it != sceneryLayers.end() && layerVisibility[selectedLayer]) {
// Current layer is scenery layer
for (auto o : it->second->objects){
SDL_Rect rect = o->getBox();
if (pointOnRect(mouse, rect) == true){
isMouseOnSomething = true;
if (tool == REMOVE){
drawGUIBox(rect.x - camera.x, rect.y - camera.y, rect.w, rect.h, renderer, 0xFF000055);
currentCursor = CURSOR_REMOVE;
} else{
drawGUIBox(rect.x - camera.x, rect.y - camera.y, rect.w, rect.h, renderer, 0xFFFFFF33);
}
}
}
}
}
// show current object only when mouse is not hover on any blocks
if (!isMouseOnSomething && tool == ADD && selectionDrag < 0) {
showCurrentObject(renderer);
}
//Draw the level borders.
drawRect(-camera.x,-camera.y,LEVEL_WIDTH,LEVEL_HEIGHT,renderer);
//Render the hud layer.
renderHUD(renderer);
//Render selection popup (if any).
if(selectionPopup!=NULL){
if(linking || moving){
//If we switch to linking mode then delete it
//FIXME: Logic in the render method.
delete selectionPopup;
selectionPopup=NULL;
}else{
selectionPopup->render(imageManager,renderer);
}
}
//Render actions popup (if any).
if(actionsPopup!=NULL){
actionsPopup->render(renderer);
}
}
}
void LevelEditor::renderHUD(SDL_Renderer& renderer){
//If moving show the moving speed in the top right corner.
if(moving){
//Calculate width of text "Movespeed: 125" to keep the same position with every value
if (movingSpeedWidth == -1){
int w;
TTF_SizeUTF8(fontText, tfm::format(_("Speed: %d = %0.2f block/s"), 125, 10.0f).c_str(), &w, NULL);
movingSpeedWidth = w + 4;
}
SDL_Texture *tex = NULL;
//Check which text should we use.
if (pauseMode) {
//Update the text if necessary.
if (pauseTimeTexture.needsUpdate(pauseTime)) {
if (pauseTime < 0) {
pauseTimeTexture.update(pauseTime,
textureFromText(renderer, *fontText,
_("Stop at this point"),
objThemes.getTextColor(true)));
} else {
pauseTimeTexture.update(pauseTime,
textureFromText(renderer, *fontText,
tfm::format(_("Pause: %d = %0.3fs"), pauseTime, float(pauseTime)*0.025f).c_str(),
objThemes.getTextColor(true)));
}
}
tex = pauseTimeTexture.get();
} else {
//Update the text if necessary.
if (movementSpeedTexture.needsUpdate(movingSpeed)) {
movementSpeedTexture.update(movingSpeed,
textureFromText(renderer, *fontText,
tfm::format(_("Speed: %d = %0.2f block/s"), movingSpeed, float(movingSpeed)*0.08f).c_str(),
objThemes.getTextColor(true)));
}
tex = movementSpeedTexture.get();
}
//Draw the text in the box.
drawGUIBox(SCREEN_WIDTH-movingSpeedWidth-2,-2,movingSpeedWidth+8,
textureHeight(*tex)+6,renderer,0xFFFFFFFF);
applyTexture(SCREEN_WIDTH-movingSpeedWidth,2,*tex,renderer,NULL);
}
//On top of all render the toolbar.
drawGUIBox(toolbarRect.x,toolbarRect.y,9*50+10,52,renderer,0xEDEDEDFF);
//Draw the first four options.
SDL_Rect srcRect={0,0,200,50};
SDL_Rect dstRect={toolbarRect.x+5, toolbarRect.y, srcRect.w, srcRect.h};
SDL_RenderCopy(&renderer, toolbar.get(), &srcRect, &dstRect);
//Draw the undo/redo button.
SDL_SetTextureAlphaMod(toolbar.get(), commandManager->canUndo() ? 255 : 128);
srcRect.x = 200;
srcRect.w = 50;
dstRect.x = toolbarRect.x + 205;
dstRect.w = srcRect.w;
SDL_RenderCopy(&renderer, toolbar.get(), &srcRect, &dstRect);
SDL_SetTextureAlphaMod(toolbar.get(), commandManager->canRedo() ? 255 : 128);
srcRect.x = 250;
srcRect.w = 50;
dstRect.x = toolbarRect.x + 255;
dstRect.w = srcRect.w;
SDL_RenderCopy(&renderer, toolbar.get(), &srcRect, &dstRect);
SDL_SetTextureAlphaMod(toolbar.get(), 255);
//And the last three.
srcRect.x=300;
srcRect.w=150;
dstRect.x=toolbarRect.x+305;
dstRect.w=srcRect.w;
SDL_RenderCopy(&renderer, toolbar.get(), &srcRect, &dstRect);
//Now render a tooltip.
if(tooltip>=0 && static_cast<std::size_t>(tooltip)<tooltipTextures.size()) {
SDL_Texture *tex = tooltipTextures.at(tooltip).get();
if (tooltip == (int)ToolTips::UndoNoTooltip) {
std::string s = commandManager->describeUndo();
if (undoTooltipTexture.needsUpdate(s)) {
undoTooltipTexture.update(s, textureFromText(renderer, *fontText, s.c_str(), objThemes.getTextColor(true)));
}
tex = undoTooltipTexture.get();
} else if (tooltip == (int)ToolTips::RedoNoTooltip) {
std::string s = commandManager->describeRedo();
if (redoTooltipTexture.needsUpdate(s)) {
redoTooltipTexture.update(s, textureFromText(renderer, *fontText, s.c_str(), objThemes.getTextColor(true)));
}
tex = redoTooltipTexture.get();
}
if(tex) {
const SDL_Rect texSize = rectFromTexture(*tex);
SDL_Rect r={(SCREEN_WIDTH-440)/2+(tooltip*40)+(tooltip*10),SCREEN_HEIGHT-45,40,40};
r.y=SCREEN_HEIGHT-50-texSize.h;
if(r.x+texSize.w>SCREEN_WIDTH-50)
r.x=SCREEN_WIDTH-50-texSize.w;
//Draw borders around text
Uint32 color=0xFFFFFF00|230;
drawGUIBox(r.x-2,r.y-2,texSize.w+4,texSize.h+4,renderer,color);
applyTexture(r.x, r.y, *tex, renderer);
}
}
// for toolbox button animation (0-31)
static int tick = 8;
const int mmm = getEditorOrderMax();
if (currentType >= mmm)currentType = mmm - 1;
if (currentType < 0) currentType = 0;
//Render the tool box.
if(!playMode && !moving && tool==ADD && selectionPopup==NULL && actionsPopup==NULL && objectWindows.empty()){
// get mouse position
int x, y;
SDL_GetMouseState(&x, &y);
if (toolboxVisible){
toolboxRect.x=0;
toolboxRect.y=0;
toolboxRect.w=SCREEN_WIDTH;
toolboxRect.h=64;
drawGUIBox(-2,-2,SCREEN_WIDTH+4,66,renderer,0xFFFFFF00|230);
bool isMouseOnSomething = false;
//Draw the hide icon.
SDL_Rect r={SCREEN_WIDTH-20,2,16,16};
SDL_Rect r2={80,0,r.w,r.h};
if (x >= SCREEN_WIDTH - 24 && x < SCREEN_WIDTH && y < 20) {
isMouseOnSomething = true;
tick = (tick + 1) & 31;
r.y -= (tick < 16) ? (tick / 4 - 2) : (6 - tick / 4);
}
SDL_RenderCopy(&renderer, bmGUI.get(), &r2, &r);
//Calculate the maximal number of blocks can be displayed.
const int m=(SCREEN_WIDTH-48)/64;
if(toolboxIndex>=mmm-m){
toolboxIndex = mmm - m;
}else{
//Draw an icon.
r.x=SCREEN_WIDTH-20;
r.y=24;
r2.x=96;
r2.y=16;
if (x >= SCREEN_WIDTH - 24 && x < SCREEN_WIDTH && y >= 20 && y < 44) {
isMouseOnSomething = true;
tick = (tick + 1) & 31;
r.x += (tick < 16) ? (tick / 4 - 2) : (6 - tick / 4);
}
SDL_RenderCopy(&renderer, bmGUI.get(),&r2,&r);
}
if(toolboxIndex<=0){
toolboxIndex=0;
}else{
//Draw an icon.
r.x=4;
r.y=24;
r2.x=80;
r2.y=16;
if (x >= 0 && x < 24 && y >= 20 && y < 44) {
isMouseOnSomething = true;
tick = (tick + 1) & 31;
r.x -= (tick < 16) ? (tick / 4 - 2) : (6 - tick / 4);
}
SDL_RenderCopy(&renderer, bmGUI.get(), &r2, &r);
}
// reset animation timer if there is no animation
if (!isMouseOnSomething) {
tick = 8;
}
//Draw available blocks.
for(int i=0;i<m;i++){
if (i + toolboxIndex >= mmm) break;
//Draw a rectangle around the current tool.
if(i+toolboxIndex==currentType){
drawGUIBox(i*64+24,3,64,58,renderer,0xDDDDDDFF);
}
if (selectedLayer.empty()) {
// show normal blocks
ThemeBlock* obj = objThemes.getBlock(editorTileOrder[i + toolboxIndex]);
if (obj){
obj->editorPicture.draw(renderer, i * 64 + 24 + 7, 7);
}
} else {
// show scenery blocks
if (i + toolboxIndex < (int)sceneryBlockNames.size()) {
ThemeBlock* obj = objThemes.getScenery(sceneryBlockNames[i + toolboxIndex]);
if (obj){
obj->editorPicture.draw(renderer, i * 64 + 24 + 7, 7);
}
} else {
// it's custom scenery block
// just draw a stupid icon
const SDL_Rect r = { 48, 16, 16, 16 };
const SDL_Rect dstRect = { i * 64 + 24 + 7, 7, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
}
}
}
//Draw a tool tip.
if(y<64 && x>=24 && x<24+m*64){
int i=(x-24)/64;
if (i + toolboxIndex < getEditorOrderMax()){
TexturePtr& tip = (!selectedLayer.empty())
? getCachedTextTexture(renderer, (i + toolboxIndex < (int)sceneryBlockNames.size())
? describeSceneryName(sceneryBlockNames[i + toolboxIndex]).c_str() : _("Custom scenery block"))
: typeTextTextures.at(editorTileOrder[i + toolboxIndex]);
const SDL_Rect tipSize = rectFromTexture(*tip);
SDL_Rect r = { 24 + i * 64, 64, 40, 40 };
if (r.x + tipSize.w>SCREEN_WIDTH - 50)
r.x = SCREEN_WIDTH - 50 - tipSize.w;
//Draw borders around text
Uint32 color = 0xFFFFFF00 | 230;
drawGUIBox(r.x - 2, r.y - 2, tipSize.w + 4, tipSize.h + 4, renderer, color);
//Draw tooltip's text
applyTexture(r.x, r.y, tip, renderer);
}
}
}else{
const SDL_Rect tbtSize = rectFromTexture(*toolboxText);
toolboxRect.x=SCREEN_WIDTH-tbtSize.w-28;
toolboxRect.y=0;
toolboxRect.w=tbtSize.w+28;
toolboxRect.h=tbtSize.h+4;
SDL_Rect r={SCREEN_WIDTH-tbtSize.w-24,2,16,16};
drawGUIBox(r.x-4,-2,tbtSize.w+32,tbtSize.h+6,renderer,0xFFFFFFFF);
//Draw "Toolbox" text.
applyTexture(r.x, r.y, toolboxText, renderer);
const SDL_Rect r2={96,0,16,16};
r.x=SCREEN_WIDTH-20;
r.w=r2.w;
r.h=r2.h;
// check if mouse is hovering on
if (x >= toolboxRect.x && x < toolboxRect.x + toolboxRect.w && y >= toolboxRect.y && y < toolboxRect.y + toolboxRect.h) {
tick = (tick + 1) & 31;
r.y += (tick < 16) ? (tick / 4 - 2) : (6 - tick / 4);
} else {
tick = 8;
}
//Draw arrow.
SDL_RenderCopy(&renderer, bmGUI.get(),&r2,&r);
}
}else{
toolboxRect.x=-1;
toolboxRect.y=-1;
toolboxRect.w=0;
toolboxRect.h=0;
}
//Draw a rectangle around the current tool.
Uint32 color=0xFFFFFF00;
drawGUIBox((SCREEN_WIDTH-440)/2+(tool*40)+(tool*10),SCREEN_HEIGHT-46,42,42,renderer,color);
}
void LevelEditor::showCurrentObject(SDL_Renderer& renderer){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
//Check if we should snap the block to grid or not.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
//Check if the currentType is a legal type.
if(currentType>=0 && currentType<getEditorOrderMax()){
if (selectedLayer.empty()) {
// show normal blocks
ThemeBlock* obj = objThemes.getBlock(editorTileOrder[currentType]);
if (obj){
obj->editorPicture.draw(renderer, x - camera.x, y - camera.y);
}
} else {
// show scenery blocks
if (currentType < (int)sceneryBlockNames.size()) {
ThemeBlock* obj = objThemes.getScenery(sceneryBlockNames[currentType]);
if (obj){
obj->editorPicture.draw(renderer, x - camera.x, y - camera.y);
}
} else {
// it's custom scenery block
// just draw a stupid icon
const SDL_Rect r = { 48, 16, 16, 16 };
const SDL_Rect dstRect = { x - camera.x, y - camera.y, 16, 16 };
SDL_RenderCopy(&renderer, bmGUI.get(), &r, &dstRect);
}
}
}
}
void LevelEditor::determineNewPosition(int& x, int& y) {
if (dragCenter) {
SDL_Rect r = dragCenter->getBox();
x -= dragSrartPosition.x - r.x;
y -= dragSrartPosition.y - r.y;
} else {
x -= 25;
y -= 25;
}
// Check if we should snap the block to grid or not.
if (!pressedShift) {
x = int(floor(x/50.0f + 0.5f)) * 50;
y = int(floor(y/50.0f + 0.5f)) * 50;
}
}
void LevelEditor::determineNewSize(int x, int y, SDL_Rect& r) {
switch (selectionDrag % 3) {
case 0:
if (x > r.x + r.w - 15) x = r.x + r.w - 15;
if (!pressedShift) {
x = int(floor(x/50.0f + 0.5f)) * 50;
while (x > r.x + r.w - 15) x -= 50;
}
r.w += r.x - x;
r.x = x;
break;
case 2:
if (x < r.x + 15) x = r.x + 15;
if (!pressedShift) {
x = int(floor(x/50.0f + 0.5f)) * 50;
while (x < r.x + 15) x += 50;
}
r.w = x - r.x;
break;
}
switch (selectionDrag / 3) {
case 0:
if (y > r.y + r.h - 15) y = r.y + r.h - 15;
if (!pressedShift) {
y = int(floor(y/50.0f + 0.5f)) * 50;
while (y > r.y + r.h - 15) y -= 50;
}
r.h += r.y - y;
r.y = y;
break;
case 2:
if (y < r.y + 15) y = r.y + 15;
if (!pressedShift) {
y = int(floor(y/50.0f + 0.5f)) * 50;
while (y < r.y + 15) y += 50;
}
r.h = y - r.y;
break;
}
}
void LevelEditor::showSelectionDrag(SDL_Renderer& renderer){
//Check if the drag center isn't null.
if (dragCenter == NULL) return;
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
x+=camera.x;
y+=camera.y;
//The location of the dragCenter.
SDL_Rect r = dragCenter->getBox();
if (selectionDrag == 4) { // dragging
// Check if we should snap the block to grid or not.
determineNewPosition(x, y);
//Loop through the selection.
//TODO: Check if block is in sight.
for (unsigned int o = 0; o < selection.size(); o++){
// FIXME: ad-hoc code which moves blocks temporarily, draw, and moves them back
const SDL_Rect r1 = selection[o]->getBox();
selection[o]->setBaseLocation((r1.x - r.x) + x, (r1.y - r.y) + y);
selection[o]->show(renderer);
selection[o]->setBaseLocation(r1.x, r1.y);
}
} else if (selectionDrag >= 0) { // resizing
// Check if we should snap the block to grid or not.
determineNewSize(x, y, r);
drawGUIBox(r.x - camera.x, r.y - camera.y, r.w, r.h, renderer, 0xFFFFFF33);
}
}
void LevelEditor::showConfigure(SDL_Renderer& renderer){
//arrow animation value. go through 0-65535 and loops.
static unsigned short arrowAnimation=0;
arrowAnimation++;
// skip if the Blocks layer is invisinble
if (!layerVisibility[std::string()]) return;
//Use theme color for arrows.
Uint32 color;
{
SDL_Color c = objThemes.getTextColor(false);
color = (Uint32(c.r) << 24) | (Uint32(c.g) << 16) | (Uint32(c.b) << 8) | 0xff;
}
//Draw the trigger lines.
{
map<Block*,vector<GameObject*> >::iterator it;
for(it=triggers.begin();it!=triggers.end();++it){
//Check if the trigger has linked targets.
if(!(*it).second.empty()){
//The location of the trigger.
SDL_Rect r=(*it).first->getBox();
//Loop through the targets.
for(unsigned int o=0;o<(*it).second.size();o++){
//Get the location of the target.
SDL_Rect r1=(*it).second[o]->getBox();
//Draw the line from the center of the trigger to the center of the target.
drawLineWithArrow(r.x-camera.x+25,r.y-camera.y+25,r1.x-camera.x+25,r1.y-camera.y+25,renderer,color,32,arrowAnimation%32);
//Also draw two selection marks.
applyTexture(r.x-camera.x+25-2,r.y-camera.y+25-2,selectionMark,renderer);
applyTexture(r1.x-camera.x+25-2,r1.y-camera.y+25-2,selectionMark,renderer);
}
}
}
//Draw a line to the mouse from the linkingTrigger when linking.
if(linking){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Draw the line from the center of the trigger to mouse.
drawLineWithArrow(linkingTrigger->getBox().x-camera.x+25,linkingTrigger->getBox().y-camera.y+25,x,y,renderer,color,32,arrowAnimation%32);
}
}
//This saves the stacked pause marks on each position.
std::map<std::pair<int, int>, int> stackedMarks;
//Draw the moving positions.
map<Block*,vector<MovingPosition> >::iterator it;
for(it=movingBlocks.begin();it!=movingBlocks.end();++it){
//Check if the block has positions.
if(!(*it).second.empty()){
//The location of the moving block.
SDL_Rect block=(*it).first->getBox();
block.x+=25-camera.x;
block.y+=25-camera.y;
//The location of the previous position.
//The first time it's the moving block's position self.
SDL_Rect r=block;
//Loop through the positions.
for(unsigned int o=0;o<(*it).second.size();o++){
//Draw the line from the center of the previous position to the center of the position.
//x and y are the coordinates for the current moving position.
int x=block.x+(*it).second[o].x;
int y=block.y+(*it).second[o].y;
//Check if we need to draw line
double dx=r.x-x;
double dy=r.y-y;
double d=sqrt(dx*dx+dy*dy);
if(d>0.001f){
if(it->second[o].time>0){
//Calculate offset to contain the moving speed.
int offset=int(d*arrowAnimation/it->second[o].time)%32;
drawLineWithArrow(r.x,r.y,x,y,renderer,color,32,offset);
}else{
//time==0 ???? so don't draw arrow at all
drawLine(r.x,r.y,x,y,renderer);
}
} else {
// distance==0 which means pause mode
// FIXME: it's ugly
SDL_Rect r1 = { 0, 0, 16, 16 }, r2 = { x - 25, y - 25 + 15 * (stackedMarks[std::pair<int, int>(x, y)]++), 16, 16 };
if (it->second[o].time) {
char s[64];
sprintf(s, "%gs", float(it->second[o].time) * 0.025f);
r1.x = 0; r1.y = 80;
SDL_RenderCopy(&renderer, bmGUI.get(), &r1, &r2);
r2.x += 16;
for (int i = 0; s[i]; i++) {
if (s[i] >= '0' && s[i] <= '9') {
r1.x = 16 + (s[i] - '0') * 8;
r1.w = 8;
} else if (s[i] == '.') {
r1.x = 96;
r1.w = 8;
} else if (s[i] == 's') {
r1.x = 104;
r1.w = 8;
} else {
// show some garbage
r1.x = 0;
r1.w = 1;
}
r2.w = 8;
SDL_RenderCopy(&renderer, bmGUI.get(), &r1, &r2);
r2.x += 8;
}
} else {
r1.x = 32; r1.y = 64;
SDL_RenderCopy(&renderer, bmGUI.get(), &r1, &r2);
}
}
//And draw a marker at the end.
applyTexture(x-13,y-13,movingMark,renderer);
//Get the box of the previous position.
SDL_Rect tmp={x,y,0,0};
r=tmp;
}
}
}
//Draw a line to the mouse from the previous moving pos.
if(moving){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Check if we should snap the block to grid or not.
if(!pressedShift){
x+=camera.x;
y+=camera.y;
snapToGrid(&x,&y);
x-=camera.x;
y-=camera.y;
}else{
x-=25;
y-=25;
}
int posX,posY;
//Check if there are moving positions for the moving block.
if(!movingBlocks[movingBlock].empty()){
//Draw the line from the center of the previouse moving positions to mouse.
posX=movingBlocks[movingBlock].back().x;
posY=movingBlocks[movingBlock].back().y;
posX-=camera.x;
posY-=camera.y;
posX+=movingBlock->getBox().x;
posY+=movingBlock->getBox().y;
}else{
//Draw the line from the center of the movingblock to mouse.
posX=movingBlock->getBox().x-camera.x;
posY=movingBlock->getBox().y-camera.y;
}
//Check if the current point is the same as the previous point
if (posX == x && posY == y) {
pauseMode = true;
} else {
pauseMode = false;
//Calculate offset to contain the moving speed.
int offset = int(double(arrowAnimation)*movingSpeed / 10.0) % 32;
//Draw the line.
drawLineWithArrow(posX + 25, posY + 25, x + 25, y + 25, renderer, color, 32, offset);
}
//Draw a marker.
applyTexture(x+12,y+12,movingMark,renderer);
}
}
void LevelEditor::resize(ImageManager &imageManager, SDL_Renderer &renderer){
//Call the resize method of the Game.
Game::resize(imageManager, renderer);
//Move the toolbar's position rect used for collision.
toolbarRect.x=(SCREEN_WIDTH-460)/2;
toolbarRect.y=SCREEN_HEIGHT-50;
}
//Filling the order array
const int LevelEditor::editorTileOrder[EDITOR_ORDER_MAX]={
TYPE_BLOCK,
TYPE_SHADOW_BLOCK,
TYPE_SPIKES,
TYPE_FRAGILE,
TYPE_MOVING_BLOCK,
TYPE_MOVING_SHADOW_BLOCK,
TYPE_MOVING_SPIKES,
TYPE_CONVEYOR_BELT,
TYPE_SHADOW_CONVEYOR_BELT,
TYPE_BUTTON,
TYPE_SWITCH,
TYPE_PORTAL,
TYPE_SWAP,
TYPE_CHECKPOINT,
TYPE_NOTIFICATION_BLOCK,
TYPE_START_PLAYER,
TYPE_START_SHADOW,
TYPE_EXIT,
TYPE_COLLECTABLE,
TYPE_PUSHABLE
};
diff --git a/src/LevelPlaySelect.cpp b/src/LevelPlaySelect.cpp
index 098e6b8..323e4ed 100644
--- a/src/LevelPlaySelect.cpp
+++ b/src/LevelPlaySelect.cpp
@@ -1,511 +1,503 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LevelPlaySelect.h"
#include "GameState.h"
#include "Functions.h"
#include "FileManager.h"
#include "Globals.h"
#include "LevelSelect.h"
#include "GUIObject.h"
#include "GUIListBox.h"
#include "GUIScrollBar.h"
#include "InputManager.h"
#include "ThemeManager.h"
#include "SoundManager.h"
#include "StatisticsManager.h"
#include "Game.h"
#include <stdio.h>
#include <string>
#include <sstream>
#include <iostream>
/////////////////////LEVEL SELECT/////////////////////
LevelPlaySelect::LevelPlaySelect(ImageManager& imageManager, SDL_Renderer& renderer)
:LevelSelect(imageManager,renderer,_("Select Level")),
levelInfoRender(imageManager,renderer,getDataPath(),*fontText,objThemes.getTextColor(false)){
//Load the play button if needed.
playButtonImage=imageManager.loadTexture(getDataPath()+"gfx/playbutton.png", renderer);
//Create the gui.
createGUI(imageManager,renderer, true);
//Show level list
refresh(imageManager,renderer);
}
LevelPlaySelect::~LevelPlaySelect(){
play=NULL;
//Clear the selected level.
if(selectedNumber!=NULL){
delete selectedNumber;
selectedNumber=NULL;
}
}
void LevelPlaySelect::createGUI(ImageManager& imageManager,SDL_Renderer &renderer, bool initial){
//Create the play button.
if(initial){
play=new GUIButton(imageManager,renderer,SCREEN_WIDTH-240,SCREEN_HEIGHT-60,240,32,_("Play"));
}else{
play->left=SCREEN_WIDTH-240;
play->top=SCREEN_HEIGHT-60;
}
play->name="cmdPlay";
play->eventCallback=this;
play->enabled=false;
if(initial)
GUIObjectRoot->addChild(play);
}
void LevelPlaySelect::refresh(ImageManager& imageManager, SDL_Renderer& renderer, bool /*change*/){
const int m=levels->getLevelCount();
numbers.clear();
levelInfoRender.resetText(renderer, *fontText, objThemes.getTextColor(false));
//Create the non selected number.
if (selectedNumber == NULL){
selectedNumber = new Number(imageManager, renderer);
}
SDL_Rect box={40,SCREEN_HEIGHT-130,50,50};
selectedNumber->init(renderer," ",box);
selectedNumber->setLocked(true);
selectedNumber->setMedal(0);
bestTimeFilePath.clear();
bestRecordingFilePath.clear();
//Disable the play button.
play->enabled=false;
for(int n=0; n<m; n++){
numbers.emplace_back(imageManager, renderer);
}
for(int n=0; n<m; n++){
SDL_Rect box={(n%LEVELS_PER_ROW)*64+static_cast<int>(SCREEN_WIDTH*0.2)/2,(n/LEVELS_PER_ROW)*64+184,0,0};
numbers[n].init(renderer,n,box);
numbers[n].setLocked(levels->getLocked(n));
int medal=levels->getLevel(n)->won?1:0;
if(medal){
const int targetTime = levels->getLevel(n)->targetTime;
const int targetRecordings = levels->getLevel(n)->targetRecordings;
- if (targetTime < 0) {
- medal = 3;
- } else {
- const int time = levels->getLevel(n)->time;
- const int recordings = levels->getLevel(n)->recordings;
- if (time >= 0 && (targetTime <= 0 || time <= targetTime))
- medal++;
- if (recordings >= 0 && (targetRecordings < 0 || recordings <= targetRecordings))
- medal++;
- }
+ const int time = levels->getLevel(n)->time;
+ const int recordings = levels->getLevel(n)->recordings;
+ if (time >= 0 && (targetTime < 0 || time <= targetTime))
+ medal++;
+ if (recordings >= 0 && (targetRecordings < 0 || recordings <= targetRecordings))
+ medal++;
}
numbers[n].setMedal(medal);
}
if(m>LEVELS_DISPLAYED_IN_SCREEN){
levelScrollBar->maxValue=(m-LEVELS_DISPLAYED_IN_SCREEN+(LEVELS_PER_ROW-1))/LEVELS_PER_ROW;
levelScrollBar->visible=true;
}else{
levelScrollBar->maxValue=0;
levelScrollBar->visible=false;
}
if (levels->levelpackPath == LEVELS_PATH || levels->levelpackPath == CUSTOM_LEVELS_PATH)
levelpackDescription->caption = _("Individual levels which are not contained in any level packs");
else if (!levels->levelpackDescription.empty())
levelpackDescription->caption = _CC(levels->getDictionaryManager(), levels->levelpackDescription);
else
levelpackDescription->caption = "";
}
void LevelPlaySelect::selectNumber(ImageManager& imageManager, SDL_Renderer& renderer, unsigned int number,bool selected){
if (selected) {
if (number >= 0 && number < levels->getLevelCount()) {
levels->setCurrentLevel(number);
setNextState(STATE_GAME);
}
}else{
displayLevelInfo(imageManager, renderer,number);
}
}
void LevelPlaySelect::checkMouse(ImageManager &imageManager, SDL_Renderer &renderer){
int x,y;
//Get the current mouse location.
SDL_GetMouseState(&x,&y);
//Check if we should replay the record.
if(selectedNumber!=NULL){
SDL_Rect mouse={x,y,0,0};
if(!bestTimeFilePath.empty()){
SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-130,372,32};
if(pointOnRect(mouse, box)){
Game::recordFile=bestTimeFilePath;
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_GAME);
return;
}
}
if(!bestRecordingFilePath.empty()){
SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-98,372,32};
if(pointOnRect(mouse, box)){
Game::recordFile=bestRecordingFilePath;
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_GAME);
return;
}
}
}
//Call the base method from the super class.
LevelSelect::checkMouse(imageManager, renderer);
}
void LevelPlaySelect::displayLevelInfo(ImageManager& imageManager, SDL_Renderer& renderer, int number){
//Update currently selected level
if(selectedNumber==NULL){
selectedNumber=new Number(imageManager, renderer);
}
SDL_Rect box={40,SCREEN_HEIGHT-130,50,50};
if (number >= 0 && number < levels->getLevelCount()) {
selectedNumber->init(renderer, number, box);
selectedNumber->setLocked(false);
//Show level medal
int medal = levels->getLevel(number)->won ? 1 : 0;
int time = levels->getLevel(number)->time;
int targetTime = levels->getLevel(number)->targetTime;
int recordings = levels->getLevel(number)->recordings;
int targetRecordings = levels->getLevel(number)->targetRecordings;
if (medal){
- if (targetTime < 0){
- medal = 3;
- } else{
- if (time >= 0 && (targetTime <= 0 || time <= targetTime))
- medal++;
- if (recordings >= 0 && (targetRecordings < 0 || recordings <= targetRecordings))
- medal++;
- }
+ if (time >= 0 && (targetTime < 0 || time <= targetTime))
+ medal++;
+ if (recordings >= 0 && (targetRecordings < 0 || recordings <= targetRecordings))
+ medal++;
}
selectedNumber->setMedal(medal);
std::string levelTime;
std::string levelRecs;
//Show best time and recordings
if (medal){
char s[64];
- if (time > 0)
- if (targetTime>0)
- sprintf(s, "%-.2fs / %-.2fs", time / 40.0f, targetTime / 40.0f);
+ if (time >= 0)
+ if (targetTime>=0)
+ sprintf(s, "%-.2fs / %-.2fs", time / 40.0, targetTime / 40.0);
else
- sprintf(s, "%-.2fs / -", time / 40.0f);
+ sprintf(s, "%-.2fs / -", time / 40.0);
else
s[0] = '\0';
levelTime = s;
if (recordings >= 0)
- if (targetTime >= 0 && targetRecordings >= 0)
+ if (targetRecordings >= 0)
sprintf(s, "%5d / %d", recordings, targetRecordings);
else
sprintf(s, "%5d / -", recordings);
else
s[0] = '\0';
levelRecs = s;
} else{
levelTime = "- / -";
levelRecs = "- / -";
}
//Show the play button.
play->enabled = true;
//Check if there is auto record file
levels->getLevelAutoSaveRecordPath(number, bestTimeFilePath, bestRecordingFilePath, false);
if (!bestTimeFilePath.empty()){
FILE *f;
f = fopen(bestTimeFilePath.c_str(), "rb");
if (f == NULL){
bestTimeFilePath.clear();
} else{
fclose(f);
}
}
if (!bestRecordingFilePath.empty()){
FILE *f;
f = fopen(bestRecordingFilePath.c_str(), "rb");
if (f == NULL){
bestRecordingFilePath.clear();
} else{
fclose(f);
}
}
//Show level description
levelInfoRender.update(renderer, *fontText, objThemes.getTextColor(false),
_CC(levels->getDictionaryManager(), levels->getLevelName(number)), levelTime, levelRecs);
} else {
levelInfoRender.resetText(renderer, *fontText, objThemes.getTextColor(false));
selectedNumber->init(renderer, " ", box);
selectedNumber->setLocked(true);
selectedNumber->setMedal(0);
bestTimeFilePath.clear();
bestRecordingFilePath.clear();
//Disable the play button.
play->enabled = false;
}
}
void LevelPlaySelect::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//Call handleEvents() of base class.
LevelSelect::handleEvents(imageManager, renderer);
//Check if the cheat code is input which is used to skip locked level.
//NOTE: The cheat code is NOT in plain text, since we don't want you to find it out immediately.
//NOTE: If you type it wrong, please press a key which is NOT a-z before retype it (as the code suggests).
if (event.type == SDL_KEYDOWN) {
static Uint32 hash = 0;
if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z) {
Uint32 c = event.key.keysym.sym - SDLK_a + 1;
hash = hash * 1296096U + c;
if (hash == 498506457U) {
if (selectedNumber) {
int n = selectedNumber->getNumber();
if (n >= 0 && n < (int)numbers.size() - 1 && numbers[n + 1].getLocked()) {
//unlock the level temporarily
numbers[n + 1].setLocked(false);
//play a sound effect
getSoundManager()->playSound("hit");
//new achievement
statsMgr.newAchievement("cheat");
}
}
hash = 0;
}
} else {
hash = 0;
}
}
if (section == 3) {
//Check focus movement
if (inputMgr.isKeyDownEvent(INPUTMGR_DOWN) || inputMgr.isKeyDownEvent(INPUTMGR_RIGHT)){
isKeyboardOnly = true;
section2++;
} else if (inputMgr.isKeyDownEvent(INPUTMGR_UP) || inputMgr.isKeyDownEvent(INPUTMGR_LEFT)){
isKeyboardOnly = true;
section2--;
}
if (section2 > 3) section2 = 1;
else if (section2 < 1) section2 = 3;
//Check if enter is pressed
if (isKeyboardOnly && inputMgr.isKeyDownEvent(INPUTMGR_SELECT) && selectedNumber) {
int n = selectedNumber->getNumber();
if (n >= 0) {
switch (section2) {
case 1:
if (!bestTimeFilePath.empty()) {
Game::recordFile = bestTimeFilePath;
levels->setCurrentLevel(n);
setNextState(STATE_GAME);
}
break;
case 2:
if (!bestRecordingFilePath.empty()) {
Game::recordFile = bestRecordingFilePath;
levels->setCurrentLevel(n);
setNextState(STATE_GAME);
}
break;
case 3:
selectNumber(imageManager, renderer, n, true);
break;
}
}
}
}
}
void LevelPlaySelect::render(ImageManager& imageManager, SDL_Renderer &renderer){
//First let the levelselect render.
LevelSelect::render(imageManager,renderer);
int x,y,dy=0;
//Get the current mouse location.
SDL_GetMouseState(&x,&y);
if(levelScrollBar)
dy=levelScrollBar->value;
//Upper bound of levels we'd like to display.
y+=dy*64;
SDL_Rect mouse={x,y,0,0};
//Show currently selected level (if any)
if(selectedNumber!=NULL){
selectedNumber->show(renderer, 0);
//Only show the replay button if the level is completed (won).
if(selectedNumber->getNumber()>=0 && selectedNumber->getNumber()<levels->getLevelCount()) {
if(levels->getLevel(selectedNumber->getNumber())->won){
if(!bestTimeFilePath.empty()){
SDL_Rect r={0,0,32,32};
const SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-130,372,32};
if (isKeyboardOnly ? (section == 3 && section2 == 1) : pointOnRect(mouse, box)){
r.x = 32;
drawGUIBox(box.x, box.y, box.w, box.h, renderer, 0xFFFFFF40);
}
const SDL_Rect dstRect = {SCREEN_WIDTH-80,SCREEN_HEIGHT-130,r.w,r.h};
SDL_RenderCopy(&renderer,playButtonImage.get(),&r, &dstRect);
}
if(!bestRecordingFilePath.empty()){
SDL_Rect r={0,0,32,32};
const SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-98,372,32};
if (isKeyboardOnly ? (section == 3 && section2 == 2) : pointOnRect(mouse, box)){
r.x = 32;
drawGUIBox(box.x, box.y, box.w, box.h, renderer, 0xFFFFFF40);
}
const SDL_Rect dstRect = {SCREEN_WIDTH-80,SCREEN_HEIGHT-98,r.w,r.h};
SDL_RenderCopy(&renderer,playButtonImage.get(),&r, &dstRect);
}
}
}
levelInfoRender.render(renderer);
}
//Draw highlight for play button.
if (isKeyboardOnly && play && play->enabled) {
play->state = (section == 3 && section2 == 3) ? 1 : 0;
}
}
void LevelPlaySelect::renderTooltip(SDL_Renderer &renderer, unsigned int number, int dy){
if (!toolTip.name || toolTip.number != number) {
const int SLEN = 64;
char s[SLEN];
//Render the name of the level.
toolTip.name = textureFromText(renderer, *fontText, _CC(levels->getDictionaryManager(), levels->getLevelName(number)), objThemes.getTextColor(true));
toolTip.time=nullptr;
toolTip.recordings=nullptr;
toolTip.number=number;
//The time it took.
if(levels->getLevel(number)->time>0){
- SDL_snprintf(s,SLEN,"%-.2fs",levels->getLevel(number)->time/40.0f);
+ SDL_snprintf(s,SLEN,"%-.2fs",levels->getLevel(number)->time/40.0);
toolTip.time = textureFromText(renderer, *fontText, s, objThemes.getTextColor(true));
}
//The number of recordings it took.
if(levels->getLevel(number)->recordings>=0){
SDL_snprintf(s,SLEN,"%d",levels->getLevel(number)->recordings);
toolTip.recordings = textureFromText(renderer, *fontText, s, objThemes.getTextColor(true));
}
}
const SDL_Rect nameSize = rectFromTexture(*toolTip.name);
//Now draw a square the size of the three texts combined.
SDL_Rect r=numbers[number].box;
r.y-=dy*64;
if(toolTip.time && toolTip.recordings){
const int recW = textureWidth(*toolTip.recordings);
const int timeW = textureWidth(*toolTip.time);
r.w=(nameSize.w)>(25+timeW+40+recW)?(nameSize.w):(25+timeW+40+recW);
r.h=nameSize.h+5+20;
}else{
r.w=nameSize.w;
r.h=nameSize.h;
}
//Make sure the tooltip doesn't go outside the window.
if(r.y>SCREEN_HEIGHT-200){
r.y-=nameSize.h+4;
}else{
r.y+=numbers[number].box.h+2;
}
if(r.x+r.w>SCREEN_WIDTH-50)
r.x=SCREEN_WIDTH-50-r.w;
//Draw a rectange
Uint32 color=0xFFFFFFFF;
drawGUIBox(r.x-5,r.y-5,r.w+10,r.h+10,renderer,color);
//Calc the position to draw.
SDL_Rect r2=r;
//Now we render the name if the surface isn't null.
if(toolTip.name){
//Draw the name.
applyTexture(r2.x, r2.y, toolTip.name, renderer);
}
//Increase the height to leave a gap between name and stats.
r2.y+=30;
if(toolTip.time){
//Now draw the time.
applyTexture(r2.x,r2.y,levelInfoRender.timeIcon,renderer);
r2.x+=25;
applyTexture(r2.x, r2.y, toolTip.time, renderer);
r2.x+=textureWidth(*toolTip.time)+15;
}
if(toolTip.recordings){
//Now draw the recordings.
applyTexture(r2.x,r2.y,levelInfoRender.recordingsIcon,renderer);
r2.x+=25;
applyTexture(r2.x, r2.y, toolTip.recordings, renderer);
}
}
void LevelPlaySelect::resize(ImageManager &imageManager, SDL_Renderer &renderer){
//Let the LevelSelect do his stuff.
LevelSelect::resize(imageManager, renderer);
//Now create our gui again.
createGUI(imageManager,renderer, false);
}
void LevelPlaySelect::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Let the level select handle his GUI events.
LevelSelect::GUIEventCallback_OnEvent(imageManager,renderer,name,obj,eventType);
//Check for the play button.
if(name=="cmdPlay"){
if(selectedNumber!=NULL){
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_GAME);
}
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 16, 7:19 PM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
63214
Default Alt Text
(513 KB)

Event Timeline