Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
773 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/AUTHORS b/AUTHORS
index 2747e95..ad7f6b8 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,56 +1,57 @@
Active developers
acme_pjz
Edward Lii
MCMic
odamite
oyvindln (SDL2 port)
Tedium (Cloudscape theme)
Former developers
Luka Horvat
O. Bahri Gordebak
Contributors
AapoRantalainen
ctdabomb (Testing, levelmaking)
davy
emarshall85
hasufell
Sauer2
squarecross
TermiT
worldcitizen
Yann Soubeyrand
Ports/Packaging
AapoRantalainen - Maemo port
acme_pjz - Windows version
amdmi3 - FreeBSD port
Artur_J - AmigaOS port
Edward Lii - linux binary, openSUSE packaging
hasufell - gentoo packaging
hcf - Xbox port
kirpken - Web port
Knitter - MacOS X port
mcobit - Pandora port
MCMic - Arch Linux packaging
odamite - Ubuntu packaging, Windows installer
petos - Mageia packaging
Translators
acme_pjz - Simplified Chinese
Akien, Poussinou - French
BioHazardX - Italian
+ eugeneloza - Ukrainian
GunChleoc - Scottish Gaelic
- KroArtem, mesnevi - Russian
+ KroArtem, mesnevi, eugeneloza - Russian
mdtrooper - Spanish
ming.yan2 - Traditional Chinese
odamite - Finnish
Petter Reinholdtsen, comradekingu - Norwegian Bokmål
SanskritFritz - Hungarian
Tedium - Dutch
Wuzzy (with help of Sauer2) - German
For an up to date list and contact information see:
http://meandmyshadow.sourceforge.net/wiki/index.php/Authors
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9658b62..29f565d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,190 +1,192 @@
Project (meandmyshadow)
CMake_Minimum_Required (VERSION 3.1)
Set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
Option (DEBUG_MODE "Compile the game with debug mode enabled" OFF)
Option (DISABLED_DEBUG_STUFF "Enable this you'll see a lot of annoying script debug messages which will lag the game." OFF)
#Find the required libraries.
Find_Package (SDL2 REQUIRED)
Find_Package (SDL2_image REQUIRED)
Find_Package (Freetype REQUIRED)
Find_Package (SDL2_mixer REQUIRED)
Find_Package (CURL REQUIRED)
Find_Package (LibArchive REQUIRED)
Find_Package (Lua 5.3 REQUIRED)
if (NOT SDL2_FOUND)
message (FATAL_ERROR "SDL2 library could not be found!")
endif (NOT SDL2_FOUND)
if (NOT SDL2_IMAGE_FOUND)
message (FATAL_ERROR "SDL2_image library could not be found!")
endif (NOT SDL2_IMAGE_FOUND)
if (NOT FREETYPE_FOUND)
message (FATAL_ERROR "Freetype library could not be found!")
endif (NOT FREETYPE_FOUND)
if (NOT SDL2_MIXER_FOUND)
message (FATAL_ERROR "SDL2_mixer library could not be found!")
endif (NOT SDL2_MIXER_FOUND)
if (NOT CURL_FOUND)
message(FATAL_ERROR "CURL library could not be found!")
endif (NOT CURL_FOUND)
if (NOT LibArchive_FOUND)
message (FATAL_ERROR "LibArchive library could not be found!")
endif (NOT LibArchive_FOUND)
if (NOT LUA_FOUND)
message (FATAL_ERROR "Lua library could not be found!")
endif (NOT LUA_FOUND)
if (LUA_VERSION_STRING VERSION_LESS "5.3")
message (FATAL_ERROR "Lua version too old ${LUA_VERSION_STRING}, expected at least 5.3!")
endif ()
# check version from Globals.h
file(READ "${PROJECT_SOURCE_DIR}/src/Globals.h" GLOBALS_H)
string(REGEX MATCH "version[ ]*=[ ]*\"[^\"]*\"" MNMS_VERSION_STR ${GLOBALS_H})
string(REGEX REPLACE "^[^\"]*\"([^\"]*)\".*$" "\\1" MNMS_VERSION_STR ${MNMS_VERSION_STR})
message(STATUS "The version read from Globals.h is: ${MNMS_VERSION_STR}")
string(REGEX REPLACE "^V([0-9.]+).*$" "\\1" MNMS_VERSION_NUM ${MNMS_VERSION_STR})
set(MNMS_VERSION_NUM "${MNMS_VERSION_NUM}.0.0.0.0")
string(REGEX REPLACE "^([0-9]+)[.]([0-9]+)[.]([0-9]+)[.]([0-9]+).*$" "\\1,\\2,\\3,\\4" MNMS_VERSION_NUM ${MNMS_VERSION_NUM})
message(STATUS "which is: ${MNMS_VERSION_NUM}")
# check version from git
find_package(Git)
if(GIT_FOUND)
exec_program(${GIT_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR} ARGS "describe"
OUTPUT_VARIABLE MNMS_GIT_VERSION RETURN_VALUE GIT_RETURN_VALUE)
if(GIT_RETURN_VALUE STREQUAL "0")
set(MNMS_VERSION_STR "${MNMS_VERSION_STR} (${MNMS_GIT_VERSION})")
message(STATUS "The version read from git is: ${MNMS_GIT_VERSION}")
else()
# possibly there are no any tags
exec_program(${GIT_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR} ARGS "rev-parse --short HEAD"
OUTPUT_VARIABLE MNMS_GIT_VERSION RETURN_VALUE GIT_RETURN_VALUE)
if(GIT_RETURN_VALUE STREQUAL "0")
set(MNMS_VERSION_STR "${MNMS_VERSION_STR} (git ${MNMS_GIT_VERSION})")
message(STATUS "The version read from git is: ${MNMS_GIT_VERSION}")
endif()
endif()
endif()
# show version information on Windows
Set(WIN32_RESOURCES )
if(WIN32)
Configure_File (
"${PROJECT_SOURCE_DIR}/icons/windows-icon/res.rc.in"
"${PROJECT_BINARY_DIR}/res.rc"
)
Set(WIN32_RESOURCES ${PROJECT_BINARY_DIR}/res.rc)
Include_Directories(${PROJECT_SOURCE_DIR}/icons/windows-icon/)
SOURCE_GROUP("Source Files\\Resources" FILES ${WIN32_RESOURCES})
endif()
#Parse the configure file.
Configure_File (
"${PROJECT_SOURCE_DIR}/src/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
#Add some missing libraries to Windows.
if(WIN32)
include_directories(${PROJECT_SOURCE_DIR}/src/libs/dirent)
endif(WIN32)
#Disable some annoying warnings.
if(MSVC)
# warning C4996: '***': This function or variable may be unsafe
add_definitions(/wd4996)
# force the source code encoding to UTF-8 (which is available since VC2015)
if (NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.0))
add_definitions(-utf-8)
endif()
else()
# Assume it's gcc or clang
# warning: '***' overrides a member function but is not marked 'override'
add_definitions(-Wno-inconsistent-missing-override)
endif()
#Define some debug stuff.
if(DEBUG_MODE)
add_definitions(-DDEBUG)
add_definitions(-D_DEBUG)
endif(DEBUG_MODE)
if(DISABLED_DEBUG_STUFF)
add_definitions(-DDISABLED_DEBUG_STUFF)
endif(DISABLED_DEBUG_STUFF)
#Add the include directories of the (found) libraries.
Include_Directories(
${PROJECT_BINARY_DIR}
${SDL2_INCLUDE_DIR}
${SDL2_IMAGE_INCLUDE_DIR}
${FREETYPE_INCLUDE_DIRS}
${SDL2_MIXER_INCLUDE_DIR}
${CURL_INCLUDE_DIR}
${LibArchive_INCLUDE_DIR}
${LUA_INCLUDE_DIR}
${PROJECT_SOURCE_DIR}/src/libs
${PROJECT_SOURCE_DIR}/src/libs/SDL2_ttf
)
#Set the output path and the source path.
Set (EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR})
Set (SRC_DIR ${PROJECT_SOURCE_DIR}/src)
#List the source files.
File (GLOB SOURCES ${SRC_DIR}/*.cpp)
+File (GLOB TINYFORMAT ${SRC_DIR}/libs/tinyformat/*.cpp)
File (GLOB TINYGETTEXT ${SRC_DIR}/libs/tinygettext/*.cpp)
File (GLOB FINDLOCALE ${SRC_DIR}/libs/findlocale/*.cpp)
File (GLOB SDL2TTF ${SRC_DIR}/libs/SDL2_ttf/*.c)
#Always use SDL_iconv in tinygettext
add_definitions(-DHAVE_SDL)
+SOURCE_GROUP("Source Files\\tinyformat" FILES ${TINYFORMAT})
SOURCE_GROUP("Source Files\\tinygettext" FILES ${TINYGETTEXT})
SOURCE_GROUP("Source Files\\findlocale" FILES ${FINDLOCALE})
SOURCE_GROUP("Source Files\\SDL2_ttf" FILES ${SDL2TTF})
-Add_Executable (meandmyshadow ${SOURCES} ${TINYGETTEXT} ${FINDLOCALE} ${WIN32_RESOURCES} ${SDL2TTF})
+Add_Executable (meandmyshadow ${SOURCES} ${TINYFORMAT} ${TINYGETTEXT} ${FINDLOCALE} ${WIN32_RESOURCES} ${SDL2TTF})
set_property(TARGET meandmyshadow PROPERTY CXX_STANDARD 11)
Target_Link_Libraries (
meandmyshadow
${SDL2_LIBRARY}
${SDL2_IMAGE_LIBRARY}
${FREETYPE_LIBRARIES}
${SDL2_MIXER_LIBRARY}
${SDL2MAIN_LIBRARY}
${CURL_LIBRARY}
${LibArchive_LIBRARY}
${LUA_LIBRARIES}
)
#Path options
Set (BINDIR "bin" CACHE STRING "Where to install binaries")
Set (DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE STRING "Sets the root of data directories to a non-default location")
Set (ICONDIR "${DATAROOTDIR}/icons" CACHE STRING "Sets the icon directory for desktop entry to a non-default location.")
Set (DESKTOPDIR "${DATAROOTDIR}/applications" CACHE STRING "Sets the desktop file directory for desktop entry to a non-default location.")
#Install locations
Install (DIRECTORY ${PROJECT_SOURCE_DIR}/data DESTINATION ${DATAROOTDIR}/meandmyshadow/)
Install (FILES AUTHORS DESTINATION ${DATAROOTDIR}/meandmyshadow/)
Install (TARGETS meandmyshadow RUNTIME DESTINATION ${BINDIR})
if ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")
Install (FILES meandmyshadow.desktop DESTINATION ${DESKTOPDIR})
Install (FILES icons/16x16/meandmyshadow.png DESTINATION ${ICONDIR}/hicolor/16x16/apps/)
Install (FILES icons/32x32/meandmyshadow.png DESTINATION ${ICONDIR}/hicolor/32x32/apps/)
Install (FILES icons/48x48/meandmyshadow.png DESTINATION ${ICONDIR}/hicolor/48x48/apps/)
Install (FILES icons/64x64/meandmyshadow.png DESTINATION ${ICONDIR}/hicolor/64x64/apps/)
endif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")
diff --git a/data/levelpacks/classic/locale/ru.po b/data/levelpacks/classic/locale/ru.po
index 988f5e0..47cdf9e 100644
--- a/data/levelpacks/classic/locale/ru.po
+++ b/data/levelpacks/classic/locale/ru.po
@@ -1,126 +1,126 @@
# 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.
#
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: 2018-09-29 21:34+0000\n"
"Last-Translator: mesnevi <shams@airpost.net>\n"
"Language-Team: Russian <https://hosted.weblate.org/projects/me-and-my-shadow/"
"levels-classic/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"
"X-Generator: Weblate 3.2-dev\n"
#: classic\Tower.map:1
msgid "Tower"
msgstr "Башня"
#: classic\Credits.map:1
msgid "Credits"
msgstr "Благодарности"
#: classic\Lab.map:1
msgid "Lab"
msgstr "Лаборатория"
#: classic\Shadow.map:1
msgid "Shadow"
msgstr "Тень"
#: classic\ShadowBlocks.map:1
msgid "ShadowBlocks"
msgstr "Блоки для тени"
#: classic\levels.lst:1
msgid "classic"
msgstr "классический"
#: classic\Timing.map:1
msgid "Timing"
msgstr "На время"
#: classic\FreeFall.map:1
msgid "FreeFall"
msgstr "Свободное падение"
#: classic\SomeSpikes.map:1
msgid "SomeSpikes"
msgstr "Немножко шипов"
#: classic\BabySteps.map:1
msgid "BabySteps"
msgstr "Первые шаги"
#: classic\Tricky.map:1
msgid "Tricky"
msgstr "С подвохом"
#: classic\Road.map:1
msgid "Road"
msgstr "Дорога"
#: classic\Jumper.map:1
msgid "Jumper"
msgstr "Прыгун"
#: classic\FirstSpikes.map:1
msgid "FirstSpikes"
msgstr "Первые шипы"
#: classic\Jumping.map:1
msgid "Jumping"
msgstr "Прыг-скок"
#: classic\levels.lst:2
msgid "Default level pack"
msgstr "Набор уровней по умолчанию"
#: classic\LittleHelp.map:1
msgid "LittleHelp"
msgstr "Мало пользы"
#: classic\End.map:1
msgid "End"
msgstr "Конец"
#: classic\FreeFall2.map:1
msgid "FreeFall2"
msgstr "Свободное падение-2"
#: classic\Control.map:1
msgid "Control"
msgstr "Контроль"
#: classic\UpDown.map:1
msgid "UpDown"
msgstr "Вверх-вниз"
#: classic\Spiky.map:1
msgid "Spiky"
-msgstr "Колючий"
+msgstr "Шипастый"
#: classic\Here.map:1
msgid "Here"
msgstr "Здесь"
#: classic\Carry.map:1
msgid "Carry"
msgstr "Ноша"
#: classic\LeftRight.map:1
msgid "LeftRight"
msgstr "Влево-вправо"
#: classic\Headache.map:1
msgid "Headache"
msgstr "Неприятность"
diff --git a/data/levelpacks/classic/locale/ru.po b/data/levelpacks/classic/locale/uk.po
similarity index 57%
copy from data/levelpacks/classic/locale/ru.po
copy to data/levelpacks/classic/locale/uk.po
index 988f5e0..b1651de 100644
--- a/data/levelpacks/classic/locale/ru.po
+++ b/data/levelpacks/classic/locale/uk.po
@@ -1,126 +1,125 @@
# 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: 2018-09-29 21:34+0000\n"
-"Last-Translator: mesnevi <shams@airpost.net>\n"
-"Language-Team: Russian <https://hosted.weblate.org/projects/me-and-my-shadow/"
-"levels-classic/ru/>\n"
-"Language: ru\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Jz Pan <acme.pjz@gmail.com>, 2018\n"
+"Language-Team: Ukrainian (https://www.transifex.com/acmepjz/teams/88148/uk/)\n"
+"Language: uk\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"
-"X-Generator: Weblate 3.2-dev\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"
#: classic\Tower.map:1
msgid "Tower"
-msgstr "Башня"
+msgstr "Вежа"
#: classic\Credits.map:1
msgid "Credits"
-msgstr "Благодарности"
+msgstr "Подяки"
#: classic\Lab.map:1
msgid "Lab"
-msgstr "Лаборатория"
+msgstr "Лабораторія"
#: classic\Shadow.map:1
msgid "Shadow"
-msgstr "Тень"
+msgstr "Тінь"
#: classic\ShadowBlocks.map:1
msgid "ShadowBlocks"
-msgstr "Блоки для тени"
+msgstr "Блоки для тіні"
#: classic\levels.lst:1
msgid "classic"
-msgstr "классический"
+msgstr "класика"
#: classic\Timing.map:1
msgid "Timing"
-msgstr "На время"
+msgstr "На швидкість"
#: classic\FreeFall.map:1
msgid "FreeFall"
-msgstr "Свободное падение"
+msgstr "Вільне падіння"
#: classic\SomeSpikes.map:1
msgid "SomeSpikes"
-msgstr "Немножко шипов"
+msgstr "Трохи шипів"
#: classic\BabySteps.map:1
msgid "BabySteps"
-msgstr "Первые шаги"
+msgstr "Перші кроки"
#: classic\Tricky.map:1
msgid "Tricky"
-msgstr "С подвохом"
+msgstr "Хитромудрий"
#: classic\Road.map:1
msgid "Road"
-msgstr "Дорога"
+msgstr "Шлях"
#: classic\Jumper.map:1
msgid "Jumper"
-msgstr "Прыгун"
+msgstr "Стрибунець"
#: classic\FirstSpikes.map:1
msgid "FirstSpikes"
-msgstr "Первые шипы"
+msgstr "Перші шипи"
#: classic\Jumping.map:1
msgid "Jumping"
-msgstr "Прыг-скок"
+msgstr "Стрибки"
#: classic\levels.lst:2
msgid "Default level pack"
-msgstr "Набор уровней по умолчанию"
+msgstr "Звичайна збірка рівнів"
#: classic\LittleHelp.map:1
msgid "LittleHelp"
-msgstr "Мало пользы"
+msgstr "Трохи допомоги"
#: classic\End.map:1
msgid "End"
-msgstr "Конец"
+msgstr "Кінець"
#: classic\FreeFall2.map:1
msgid "FreeFall2"
-msgstr "Свободное падение-2"
+msgstr "Вільне падіння - 2"
#: classic\Control.map:1
msgid "Control"
-msgstr "Контроль"
+msgstr "Керування"
#: classic\UpDown.map:1
msgid "UpDown"
-msgstr "Вверх-вниз"
+msgstr "Вгору-вниз"
#: classic\Spiky.map:1
msgid "Spiky"
-msgstr "Колючий"
+msgstr "Шипастий"
#: classic\Here.map:1
msgid "Here"
-msgstr "Здесь"
+msgstr "Тут"
#: classic\Carry.map:1
msgid "Carry"
-msgstr "Ноша"
+msgstr "Носій"
#: classic\LeftRight.map:1
msgid "LeftRight"
-msgstr "Влево-вправо"
+msgstr "Ліворуч-праворуч"
#: classic\Headache.map:1
msgid "Headache"
-msgstr "Неприятность"
+msgstr "Головний біль"
diff --git a/data/levelpacks/default/locale/ru.po b/data/levelpacks/default/locale/ru.po
index eb25fe5..a671a83 100644
--- a/data/levelpacks/default/locale/ru.po
+++ b/data/levelpacks/default/locale/ru.po
@@ -1,106 +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.
#
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: 2018-09-29 21:34+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"
"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 "Как вверх, так и вниз"
+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 "Небоскребы"
+msgstr "Небоскрёбы"
#: default\QuickSwap.map:1
msgid "Quick swap"
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 "Прыг-скок с сюрпризами"
+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/default/locale/ru.po b/data/levelpacks/default/locale/uk.po
similarity index 57%
copy from data/levelpacks/default/locale/ru.po
copy to data/levelpacks/default/locale/uk.po
index eb25fe5..216bf29 100644
--- a/data/levelpacks/default/locale/ru.po
+++ b/data/levelpacks/default/locale/uk.po
@@ -1,106 +1,105 @@
# 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: 2018-09-29 21:34+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"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Jz Pan <acme.pjz@gmail.com>, 2018\n"
+"Language-Team: Ukrainian (https://www.transifex.com/acmepjz/teams/88148/uk/)\n"
+"Language: uk\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"
-"X-Generator: Weblate 3.2-dev\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"
#: default\Regroup.map:1
msgid "Regroup"
-msgstr "Перестройка"
+msgstr "Перегрупування"
#: default\3.map:1
msgid "Running in the Sky"
-msgstr "Беги по небу"
+msgstr "Небесні мандри"
#: default\4.map:1
msgid "Both Up and Down"
-msgstr "Как вверх, так и вниз"
+msgstr "І вгору, і вниз"
#: default\map02.map:1
msgid "Snail"
-msgstr "Улитка"
+msgstr "Равлик"
#: default\Towers.map:1
msgid "Towers"
-msgstr "Башни"
+msgstr "Вежі"
#: default\map01.map:1
msgid "Simple"
-msgstr "Раз плюнуть"
+msgstr "Просто"
#: default\map04.map:1
msgid "Double trouble"
-msgstr "Дважды в дураках"
+msgstr "Подвійні проблеми"
#: default\levels.lst:1
msgid "default"
-msgstr "стандартный"
+msgstr "звичайний"
#: default\Skyscrapers.map:1
msgid "Skyscrapers"
-msgstr "Небоскребы"
+msgstr "Хмарочоси"
#: default\QuickSwap.map:1
msgid "Quick swap"
-msgstr "Мигом"
+msgstr "Швидка зміна"
#: default\5.map:1
msgid "stopping the spikes"
-msgstr "Шипам — нет"
+msgstr "зупиняючи шипи"
#: default\levels.lst:2
msgid "Default"
-msgstr "Стандартный"
+msgstr "Звичайний"
#: default\Remote.map:1
msgid "Remote control"
-msgstr "Дистанционное управление"
+msgstr "Дистанційне керування"
#: default\map03.map:1
msgid "Spiky travel"
-msgstr "Прогулки по шипам"
+msgstr "Мандрівка шипами"
#: default\2.map:1
msgid "Tricky Jumping"
-msgstr "Прыг-скок с сюрпризами"
+msgstr "Стрибки ізпідвипідвертом"
#: default\1.map:1
msgid "Building Teamwork"
-msgstr "Построй команду"
+msgstr "Побудова командної роботи"
#: default\Timing.map:1
msgid "Timing"
-msgstr "На время"
+msgstr "На швидкість"
#: default\Volcano.map:1
msgid "Volcano"
msgstr "Вулкан"
#: default\Sweeper.map:1
msgid "Sweeper"
-msgstr "Дворник"
+msgstr "Прибиральник"
#: default\Switches.map:1
msgid "Switches"
-msgstr "Выключатели"
+msgstr "Вимикачі"
#: default\map05.map:1
msgid "Wall breaking"
-msgstr "Ломая стены"
+msgstr "Ламаючи стіну"
diff --git a/data/levelpacks/tutorial/locale/ru.po b/data/levelpacks/tutorial/locale/ru.po
index 95bb9c8..3404bea 100644
--- a/data/levelpacks/tutorial/locale/ru.po
+++ b/data/levelpacks/tutorial/locale/ru.po
@@ -1,478 +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.
#
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: 2018-09-29 22:36+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"
"X-Generator: Weblate 3.2-dev\n"
#: tutorial\tut12.map:17
msgid "Now you'll need to help your shadow cross.\n"
-msgstr "Теперь надо помочь твоей тени перебраться на ту сторону.\n"
+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."
+"Прогресс на уровне можно сохранять с помощью\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"
"Это можно сделать только один раз, поэтому если\n"
-"не получится, придется начать сначала (клавиша 'R')."
+"не получится - придётся перезапустить уровень с помощью клавиши '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'."
+"Если тень упадёт, придётся начать уровень сначала\n"
+"с помощью клавиши 'R'."
#: tutorial\levels.lst:1
msgid "tutorial"
msgstr "обучение"
#: tutorial\tut23.map:1
msgid "Collecting Keys"
msgstr "Собери ключи"
#: tutorial\tut10.map:46
msgid "You've chosen the right way."
-msgstr "Верной дорогой идешь, товарищ."
+msgstr "Правильным путём идёте, товарищи."
#: tutorial\tut22.map:1
msgid "Shadow swap"
-msgstr "Поменяйся с тенью"
+msgstr "Поменяться с тенью"
#: tutorial\tut20.map:187
msgid ""
"A dead end, you'd better go back and choose\n"
"the other portal."
msgstr ""
-"Здесь тупик, лучше вернуться назад и выбрать\n"
+"Здесь тупик, следует вернуться назад и выбрать\n"
"другой портал."
#: tutorial\tut03.map:13
msgid ""
"See those gaps over there?\n"
"Reach the finish without falling."
msgstr ""
-"Видишь вон те провалы?\n"
-"Доберись до финиша, не упав."
+"Впереди - пропасти.\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'."
+"Если тень умрёт, уровень придётся начать сначала\n"
+"с помощью клавиши 'R'."
#: tutorial\tut02.map:18
msgid ""
"You can jump using the up key.\n"
"Try jumping over these blocks."
msgstr ""
-"Можно прыгать, используя клавишу 'вверх'.\n"
-"Попробуй перепрыгнуть через эти блоки."
+"Придётся перепрыгнуть через эти блоки.\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"
-"Постарайся добраться до выхода через эти дебри."
+"Порталы ведут в другой портал (или на пустое место).\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"
-"Можно поделить это на несколько записей поменьше."
+"Следует спланировать действия для тени,\n"
+"а затем записать эти движения.\n"
+"Можно разбить большую запись на несколько записей поменьше."
#: tutorial\tut11.map:17
msgid ""
"Until now the levels were static.\n"
"There are however moving blocks."
msgstr ""
-"До сих пор все уровни были неподвижными.\n"
-"Однако, бывают движущиеся блоки."
+"До сих пор все уровни были статичными.\n"
+"Впрочем, существуют и подвижные блоки."
#: tutorial\tut10.map:32
msgid "Save your progress here."
-msgstr "Сохрани свой прогресс здесь."
+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 "Переключатель"
+msgstr "Выключатель"
#: tutorial\tut25.map:13
msgid "The very best of luck!"
msgstr "Удачи!"
#: tutorial\tut20.map:156
msgid "Now choose one of the two."
-msgstr "Выбери один из двух."
+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"
-"Он сработает, если через него пройти."
+"Он устроен по-другому: нажимать на клавишу 'вниз' не надо.\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."
+"Придётся вернуться предыдущему чекпоинту, нажав клавишу 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"
-"можно записать прыжки для тени."
+"Хоть на эти блоки и невозможно запрыгнуть,\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"
-"чтобы активировать его."
+"Теперь время взглянуть на порталы.\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"
-"Если часто на них вставать, они сломаются."
+"Эти блоки хрупкие.\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"
-"(или спуститься самому)."
+"Поменяйтесь местами, чтобы спустить тень вниз\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"
-"с тенью активируюте их, вы меняетесь местами."
+"А теперь время для чего-то совершенно\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"
-"или переключать их."
+"Мы уже познакомились с триггерами, которые\n"
+"активируют другие блоки, но они также могут\n"
+"деактивировать или переключать их."
#: tutorial\tut14.map:68
msgid ""
"When standing on conveyor belts you'll\n"
"move without walking."
msgstr ""
-"Стоя на конвейере, ты будешь\n"
-"непроизвольно двигаться."
+"Конвеер постоянно перемещает всё,\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"
-"Если она упадёт, придется начать сначала."
+"Тенью необходимо стать перед стеной для тени.\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"
-"клавишу 'вниз' и пусть тень повторяет за тобой."
+"Когда тень будет в ловушке, нужно стать справа\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"
+"Пусть тень пройдёт уровень, добравшись до финиша.\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"
-"Выход закрыт до тех пор, пока не соберёшь все ключи."
+"Ещё одна вещь перед тем, как закончить: ключи.\n"
+"Иногда они распределены по всему уровню.\n"
+"Выход закрыт до тех пор, пока не получится собрать все ключи."
#: tutorial\tut03.map:34
msgid "Good job!"
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"
+"Этот переключатель активирует подъёмник,\n"
"который поможет добраться до выхода."
#: tutorial\tut09.map:1
msgid "Fragile"
-msgstr "Непрочные блоки"
+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"
+"Настало время проверить полученные навыки.\n"
+"Нужно добраться до выхода, чтобы перейти на последний уровень.\n"
"Удачи!"
#: tutorial\tut25.map:459
msgid "Where could your shadow be?"
-msgstr "Где же может быть эта тень?"
+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"
"\n"
"Удачи!"
#: tutorial\tut15.map:1
msgid "Triggering"
msgstr "Триггер"
#: tutorial\tut03.map:1
msgid "Jumping around"
msgstr "Прыгая тут и там"
#: tutorial\tut10.map:1
msgid "Checkpoints"
-msgstr "Контрольные точки"
+msgstr "Чекпоинты"
#: tutorial\tut07.map:19
msgid ""
"Spikes are not only deadly for you,\n"
"but also for your shadow."
msgstr ""
-"Шипы убивают не только тебя,\n"
-"но и тень."
+"Шипы смертельны не только для игрока,\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"
+"Игроку и тени надо работать сообща.\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 "Введение, шаг за шагом"
+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"
-"повторить записанные движения."
+"Игрок не может добраться до выхода, а тень может.\n"
+"Нажатие клавиши 'пробел' начнёт запись движений.\n"
+"Повторное нажатие клавиши 'пробел' запустит\n"
+"воспроизведение записи тенью."
#: tutorial\levels.lst:2
msgid "You have finished the tutorial!"
-msgstr "Ты прошел обучение!"
+msgstr "Обучение завершено!"
diff --git a/data/levelpacks/tutorial/locale/uk.po b/data/levelpacks/tutorial/locale/uk.po
new file mode 100644
index 0000000..7cff3ae
--- /dev/null
+++ b/data/levelpacks/tutorial/locale/uk.po
@@ -0,0 +1,477 @@
+# 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: Ukrainian (https://www.transifex.com/acmepjz/teams/88148/uk/)\n"
+"Language: uk\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"
+
+#: tutorial\tut12.map:17
+msgid "Now you'll need to help your shadow cross.\n"
+msgstr "Тепер треба допомогти тіні пройти перешкоди."
+
+#: 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 "навчання"
+
+#: 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 "Відмінно спрацьовано!"
+
+#: 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/nb.po b/data/locale/nb.po
index b8b1482..0a936b5 100644
--- a/data/locale/nb.po
+++ b/data/locale/nb.po
@@ -1,2162 +1,2151 @@
# 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-10-03 20:32+0000\n"
-"Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n"
+"PO-Revision-Date: 2018-10-05 04:29+0000\n"
+"Last-Translator: Petter Reinholdtsen <pere-weblate@hungry.com>\n"
"Language-Team: Norwegian Bokmål <https://hosted.weblate.org/projects/"
"me-and-my-shadow/translations/nb/>\n"
"Language: nb\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 "Nybegynner"
#: ../src/AchievementList.cpp:43
msgid "Complete a level."
msgstr "Fullføre nivå."
#: ../src/AchievementList.cpp:44
msgid "Experienced player"
msgstr "Erfaren spiller"
#: ../src/AchievementList.cpp:44
msgid "Complete 50 levels."
msgstr "Fullføre 50 nivåer."
#: ../src/AchievementList.cpp:45
msgid "Good job!"
msgstr "Godt gjort!"
#: ../src/AchievementList.cpp:45
msgid "Receive a gold medal."
msgstr "Motta en gullmedalje."
#: ../src/AchievementList.cpp:46
msgid "Expert"
msgstr "Ekspert"
#: ../src/AchievementList.cpp:46
msgid "Earn 50 gold medal."
msgstr "Motta 50 gullmedaljer."
#: ../src/AchievementList.cpp:48
msgid "Graduate"
msgstr "Avanser"
#: ../src/AchievementList.cpp:48
msgid "Complete the tutorial level pack."
msgstr "Fullfør veiledningsnivåpakken."
#: ../src/AchievementList.cpp:49
msgid "Outstanding graduate"
msgstr "Fremragende disippel"
#: ../src/AchievementList.cpp:49
msgid "Complete the tutorial level pack with gold for all levels."
msgstr "Fullfør veiledningsnivåpakken med gull på alle nivåer."
#: ../src/AchievementList.cpp:51
msgid "Hooked"
msgstr "Hektet"
#: ../src/AchievementList.cpp:51
#, fuzzy
msgid "Play Me and My Shadow for more than 2 hours."
msgstr "Spill Jeg og skyggen min i mer enn to timer."
#: ../src/AchievementList.cpp:52
msgid "Loyal fan of Me and My Shadow"
msgstr "Lojal tilhenger av Jeg og skyggen min"
#: ../src/AchievementList.cpp:52
#, fuzzy
msgid "Play Me and My Shadow for more than 24 hours."
msgstr "Spill Jeg og skyggen min i mer enn 24 timer."
#: ../src/AchievementList.cpp:54
msgid "Constructor"
msgstr "Byggmester"
#: ../src/AchievementList.cpp:54
msgid "Use the level editor for more than 2 hours."
msgstr "Bruk nivåredigereren i mer enn to timer."
#: ../src/AchievementList.cpp:55
msgid "The creator"
msgstr "Skaperen"
#: ../src/AchievementList.cpp:55
msgid "Use the level editor for more than 24 hours."
msgstr "Bruk nivåredigereren i mer enn 24 timer."
#: ../src/AchievementList.cpp:57
msgid "Look, cute level!"
msgstr "Se, søtt nivå!"
#: ../src/AchievementList.cpp:57
msgid "Create a level for the first time."
msgstr "Lag et nivå for første gang."
#: ../src/AchievementList.cpp:58
msgid "The level museum"
msgstr "Nivåmuseet"
#: ../src/AchievementList.cpp:58
msgid "Create 50 levels."
msgstr "Lag 50 nivåer."
#: ../src/AchievementList.cpp:60
msgid "Hello, World!"
msgstr "Hei, verden!"
#: ../src/AchievementList.cpp:60
msgid "Write a script for the first time."
msgstr "Skriv et skript for første gang."
#: ../src/AchievementList.cpp:62
msgid "Frog"
msgstr "Frosk"
#: ../src/AchievementList.cpp:62
msgid "Jump 1000 times."
msgstr "Hopp 1000 ganger."
#: ../src/AchievementList.cpp:64
msgid "Wanderer"
msgstr "Vandrer"
#: ../src/AchievementList.cpp:64
msgid "Travel 100 meters."
msgstr "Reis 100 meter."
#: ../src/AchievementList.cpp:65
msgid "Runner"
msgstr "Løper"
#: ../src/AchievementList.cpp:65
msgid "Travel 1 kilometer."
msgstr "Reis én kilometer."
#: ../src/AchievementList.cpp:66
msgid "Long distance runner"
msgstr "Langdistanseløper"
#: ../src/AchievementList.cpp:66
msgid "Travel 10 kilometers."
msgstr "Reis ti kilometer."
#: ../src/AchievementList.cpp:67
msgid "Marathon runner"
msgstr "Maratonløper"
#: ../src/AchievementList.cpp:67
msgid "Travel 42,195 meters."
msgstr "Reis 42195 meter"
#: ../src/AchievementList.cpp:69
msgid "Be careful!"
-msgstr "Vær forsiktig."
+msgstr "Vær forsiktig!"
#: ../src/AchievementList.cpp:69
msgid "Die for the first time."
msgstr "Dø for første gang."
#: ../src/AchievementList.cpp:70
-#, fuzzy
msgid "It doesn't matter..."
msgstr "Det gjør ikke noe…"
#: ../src/AchievementList.cpp:70
msgid "Die 50 times."
msgstr "Dø 50 ganger."
#: ../src/AchievementList.cpp:71
msgid "Expert of trial and error"
msgstr "Ekspert på prøving og feiling"
#: ../src/AchievementList.cpp:71
msgid "Die 1000 times."
msgstr "Dø 1000 ganger."
#: ../src/AchievementList.cpp:73
msgid "Keep an eye for moving blocks!"
msgstr "Hold øye med bevegelige blokker!"
#: ../src/AchievementList.cpp:73
msgid "Get squashed for the first time."
msgstr "Bli most for første gang."
#: ../src/AchievementList.cpp:74
msgid "Potato masher"
msgstr "Potetstamper"
#: ../src/AchievementList.cpp:74
msgid "Get squashed 50 times."
msgstr "Bli most 50 ganger."
#: ../src/AchievementList.cpp:76
msgid "Double kill"
msgstr "Dobbeltdrap"
#: ../src/AchievementList.cpp:76
-#, fuzzy
msgid "Get both the player and the shadow dead."
msgstr "Få både karakteren din og skyggen dens drept."
#: ../src/AchievementList.cpp:78
msgid "Bad luck"
msgstr "Uflaks"
#: ../src/AchievementList.cpp:78
msgid "Die 5 times in under 5 seconds."
msgstr "Dø fem ganger på under fem sekunder."
#: ../src/AchievementList.cpp:79
msgid "This level is too dangerous"
msgstr "Dette nivået er for farlig"
#: ../src/AchievementList.cpp:79
msgid "Die 10 times in under 5 seconds."
msgstr "Dø ti ganger på under fem sekunder."
#: ../src/AchievementList.cpp:81
msgid "You forgot your friend"
msgstr "Du glemte vennen din"
#: ../src/AchievementList.cpp:81
msgid "Finish the level with the player or the shadow dead."
msgstr "Fullfør nivået med spilleren eller skyggen dø."
#: ../src/AchievementList.cpp:82
msgid "Just in time"
msgstr "Akkurat tidsnok"
#: ../src/AchievementList.cpp:82
msgid "Reach the exit with the player and the shadow simultaneously."
msgstr "Nå utgangen med spilleren og skyggen samtidig."
#: ../src/AchievementList.cpp:84
msgid "Recorder"
msgstr "Opptaker"
#: ../src/AchievementList.cpp:84
msgid "Record 100 times."
msgstr "Ta opp 100 ganger."
#: ../src/AchievementList.cpp:85
msgid "Shadowmaster"
msgstr "Skyggemester"
#: ../src/AchievementList.cpp:85
msgid "Record 1000 times."
msgstr "Ta opp 1000 ganger."
#: ../src/AchievementList.cpp:87
msgid "Switch puller"
msgstr "Brytervender"
#: ../src/AchievementList.cpp:87
msgid "Pull the switch 100 times."
msgstr "Vend bryteren 100 ganger."
#: ../src/AchievementList.cpp:88
msgid "The switch is broken!"
msgstr "Bryteren er kaputt!"
#: ../src/AchievementList.cpp:88
msgid "Pull the switch 1000 times."
msgstr "Vend bryteren 1000 ganger."
#: ../src/AchievementList.cpp:90
#, fuzzy
msgid "Swapper"
msgstr "Stedveksler"
#: ../src/AchievementList.cpp:90
msgid "Swap 100 times."
msgstr "Bytt sted 100 ganger."
#: ../src/AchievementList.cpp:91
-#, fuzzy
msgid "Player to shadow to player to shadow..."
msgstr "Spiller til skygge til spiller til skygge…"
#: ../src/AchievementList.cpp:91
#, fuzzy
msgid "Swap 1000 times."
msgstr "Bytt sted 1000 ganger."
#: ../src/AchievementList.cpp:93
msgid "Play it save"
msgstr "Ro det i land"
#: ../src/AchievementList.cpp:93
msgid "Save 1000 times."
msgstr "Lagre 1000 ganger."
#: ../src/AchievementList.cpp:94
msgid "This game is too hard"
msgstr "Dette spilet er for vanskelig"
#: ../src/AchievementList.cpp:94
msgid "Load the game 1000 times."
msgstr "Last spillet 1000 ganger."
#: ../src/AchievementList.cpp:96
msgid "No, thanks"
msgstr "Nei takk"
#: ../src/AchievementList.cpp:96
msgid "Complete a level with checkpoint, but without saving."
msgstr "Fullfør et nivå med sjekkpunkt, men uten å lagre."
#: ../src/AchievementList.cpp:98
msgid "Panic save"
msgstr "Panikklagring"
#: ../src/AchievementList.cpp:98
msgid "Save twice in 1 second."
msgstr "Lagre to ganger på ett sekund"
#: ../src/AchievementList.cpp:99
msgid "Panic load"
msgstr "Panikklasting"
#: ../src/AchievementList.cpp:99
msgid "Load twice in 1 second."
msgstr "Last inn to ganger på ett sekund."
#: ../src/AchievementList.cpp:101
msgid "Bad saving position"
msgstr "Dårlig lagringsposisjon"
#: ../src/AchievementList.cpp:101
msgid "Load the game and die within 1 second."
msgstr "Last spillet og dø på mindre enn ett sekund."
#: ../src/AchievementList.cpp:102
msgid "This level is too hard"
msgstr "Dette nivået er for vanskelig"
#: ../src/AchievementList.cpp:102
msgid "Load the same save and die 100 times."
msgstr "Last samme lagring og dø 100 ganger."
#: ../src/AchievementList.cpp:104
msgid "Quick swap"
msgstr "Raskt bytte"
#: ../src/AchievementList.cpp:104
msgid "Swap twice in under a second."
msgstr "Bytt sted to ganger på under ett sekund."
#: ../src/AchievementList.cpp:107
msgid "Horizontal confusion"
msgstr "Vannrett forvirring"
#: ../src/AchievementList.cpp:107
-#, fuzzy
msgid "Press left and right simultaneously."
msgstr "Trykk ← og → samtidig."
#: ../src/AchievementList.cpp:109
msgid "Cheater"
msgstr "Jukser"
#: ../src/AchievementList.cpp:109
msgid "Cheat in game."
msgstr "Juks i spillet."
#: ../src/AchievementList.cpp:111
msgid "Programmer"
msgstr "Programmerer"
#: ../src/AchievementList.cpp:111
msgid "Play the development version of Me and My Shadow."
msgstr "Spill utviklingsversjonen av Jeg og skyggen min."
#: ../src/Addons.cpp:44 ../src/LevelPackManager.cpp:108
msgid "Levels"
msgstr "Nivå"
#: ../src/Addons.cpp:44
-#, fuzzy
msgid "Single level which usually contain demanding puzzles"
msgstr "Enkeltnivå som vanligvis inneholder utfordrende gåter"
#: ../src/Addons.cpp:45
msgid "Levelpacks"
msgstr "Nivåpakker"
#: ../src/Addons.cpp:45
msgid "Collection of levels with the same author or style"
msgstr "Samling av nivåer laget av samme utvikler eller i samme stil"
#: ../src/Addons.cpp:46
msgid "Themes"
msgstr "Drakter"
#: ../src/Addons.cpp:46
msgid "Give every block and background a new look and feel"
msgstr "Gi hver blokk og bakgrunn nytt utseende og oppførsel"
#: ../src/Addons.cpp:55 ../src/TitleMenu.cpp:46
msgid "Addons"
msgstr "Programtillegg"
#: ../src/Addons.cpp:87
msgid "Unable to initialize addon menu:"
msgstr "Kunne ikke starte programtilleggsmeny:"
#: ../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 "Tilbake"
#: ../src/Addons.cpp:169
-#, fuzzy
msgid "ERROR: unable to download addons file!"
-msgstr "FEIL: Kunne ikke laste programtilleggsfil!"
+msgstr "FEIL: Kunne ikke laste ned programtilleggsfil!"
# 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 "FEIL: Kunne ikke laste inn addon_list-fil!"
#: ../src/Addons.cpp:193
msgid "ERROR: Invalid file format of addons file!"
msgstr "FEIL: Ugyldig filformat for programtilleggsfil!"
#: ../src/Addons.cpp:205
-#, fuzzy
msgid "ERROR: Addon list version is unsupported!"
msgstr "FEIL: Programtilleggsversjonen støttes ikke!"
# TRANSLATORS: installed_addons is the name of a file and should not be translated.
#: ../src/Addons.cpp:226
-#, fuzzy
msgid "ERROR: Unable to create the installed_addons file."
-msgstr "FEIL: Kunne ikke opprette programtilleggsfil for installasjoner."
+msgstr "FEIL: Kunne ikke opprette filen installed_addons."
#: ../src/Addons.cpp:238
msgid "ERROR: Invalid file format of the installed_addons!"
msgstr "FEIL: Ugyldig filformat for programtilleggsfil for installasjoner."
# TRANSLATORS: indicates the author of an addon.
#: ../src/Addons.cpp:389 ../src/Addons.cpp:621
#, c-format
msgid "by %s"
msgstr "av %s"
#: ../src/Addons.cpp:397
msgid "Installed"
msgstr "Installert"
#: ../src/Addons.cpp:402
-#, fuzzy
msgid "Updatable"
msgstr "Oppdaterbar"
#: ../src/Addons.cpp:412
msgid "Not installed"
msgstr "Ikke installert"
#: ../src/Addons.cpp:625
#, c-format
msgid "Version: %d\n"
msgstr "Versjon: %d\n"
#: ../src/Addons.cpp:627
#, c-format
msgid "Installed version: %d\n"
msgstr "Installert versjon: %d\n"
#: ../src/Addons.cpp:630
#, c-format
msgid "License: %s\n"
msgstr "Lisens: %s\n"
#: ../src/Addons.cpp:633
#, c-format
msgid "Website: %s\n"
msgstr "Nettside: %s\n"
#: ../src/Addons.cpp:637
msgid "(No descriptions provided)"
msgstr "(Ingen beskrivelser medfølger)"
#: ../src/Addons.cpp:657 ../src/Addons.cpp:684
msgid "Remove"
msgstr "Fjern"
#: ../src/Addons.cpp:673
-#, fuzzy
msgid "Update"
msgstr "Oppdater"
#: ../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 "Dette programtillegget kan ikke fjernes fordi det trengs av %s."
#: ../src/Addons.cpp:774 ../src/Addons.cpp:1051
msgid "Dependency"
msgstr "Avhengighet"
#: ../src/Addons.cpp:803
#, c-format
msgid "WARNING: File '%s' appears to have been removed already."
msgstr "ADVARSEL: Filen \"%s\" later til å ha blitt fjernet allerede."
#: ../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 "Programtilleggsfeil"
#: ../src/Addons.cpp:810
#, c-format
msgid "ERROR: Unable to remove file '%s'!"
msgstr "FEIL: Kunne ikke fjerne filen \"%s\"."
#: ../src/Addons.cpp:818
#, c-format
msgid "WARNING: Directory '%s' appears to have been removed already."
msgstr "ADVARSEL: Mappen \"%s\" later til å ha blitt fjernet allerede."
#: ../src/Addons.cpp:825
#, c-format
msgid "ERROR: Unable to remove directory '%s'!"
msgstr "FEIL: Kunne ikke fjerne mappen \"%s\"."
#: ../src/Addons.cpp:834
#, c-format
msgid "WARNING: Level '%s' appears to have been removed already."
msgstr "ADVARSEL: Nivået \"%s\" later til å ha blitt fjernet allerede."
#: ../src/Addons.cpp:840
#, c-format
msgid "ERROR: Unable to remove level '%s'!"
msgstr "FEIL: Kunne ikke fjerne nivået \"%s\"!"
#: ../src/Addons.cpp:859
#, c-format
msgid "WARNING: Levelpack directory '%s' appears to have been removed already."
msgstr "ADVARSEL: Nivåpakkemappen \"%s\" later til å ha blitt fjernet allerede."
#: ../src/Addons.cpp:866
#, c-format
msgid "ERROR: Unable to remove levelpack directory '%s'!"
msgstr "FEIL: Kunne ikke fjerne nivåpakkemappen \"%s\"."
#: ../src/Addons.cpp:893
#, c-format
msgid "ERROR: Unable to download addon file %s."
msgstr "FEIL: Kunne ikke laste ned programtilleggsfilen %s."
#: ../src/Addons.cpp:900
#, c-format
msgid "ERROR: Unable to extract addon file %s."
msgstr "FEIL: Kunne ikke pakke ut programtilleggsfilen %s."
#: ../src/Addons.cpp:907
msgid "ERROR: Addon is missing metadata!"
msgstr "FEIL: Programtillegget mangler metadata!"
#: ../src/Addons.cpp:918
msgid "ERROR: Invalid file format for metadata file!"
msgstr "FEIL: Ugyldig filformat for metadatafil!"
#: ../src/Addons.cpp:947
#, c-format
msgid "WARNING: File '%s' already exists, addon may be broken or not working!"
msgstr ""
"ADVARSEL: Filen \"%s\" finnes allerede, programtillegget kan være skadet "
"eller ha sluttet å fungere."
#: ../src/Addons.cpp:952
#, c-format
msgid ""
"WARNING: Unable to copy file '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
"ADVARSEL: Kunne ikke kopiere filen \"%s\" til \"%s\" programtillegget kan "
"være skadet eller ha sluttet å virke."
#: ../src/Addons.cpp:962
#, c-format
msgid ""
"WARNING: Destination directory '%s' already exists, addon may be broken or "
"not working!"
msgstr ""
"ADVARSEL: Målmappen \"%s\" finnes allerede, programtillegget kan være "
"skadet, eller ha sluttet å virke."
#: ../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 ""
"ADVARSEL: Kunne ikke flytte mappen \"%s\" til \"%s\", programtillegget kan "
"være skadet, eller ha sluttet å virke."
#: ../src/Addons.cpp:981
#, c-format
msgid "WARNING: Level '%s' already exists, addon may be broken or not working!"
msgstr ""
"ADVARSEL: Nivået \"%s\" finnes allerede, programtillegget kan være skadet, "
"eller ha sluttet å virke."
#: ../src/Addons.cpp:986
#, c-format
msgid ""
"WARNING: Unable to copy level '%s' to '%s', addon may be broken or not "
"working!"
msgstr ""
"ADVARSEL: Kunne ikke kopiere nivået \"%s\" til \"%s\", programtillegget kan "
"være skadet, eller ha sluttet å virke."
#: ../src/Addons.cpp:1008
#, c-format
msgid ""
"WARNING: Levelpack directory '%s' already exists, addon may be broken or not "
"working!"
msgstr ""
"ADVARSEL: Nivåpakkemappen \"%s\" finnes allerede, programtillegget kan være "
"skadet, eller ha sluttet å virke."
#: ../src/Addons.cpp:1044
#, c-format
msgid "ERROR: Addon requires another addon (%s) which can't be found!"
msgstr ""
"FEIL: Programtillegget krever et annet programtillegg (%s) som ikke ble "
"funnet."
#: ../src/Addons.cpp:1051
#, c-format
msgid "The addon %s is needed and will be installed now."
msgstr "Programtillegget %s er nødvendig, og vil bli installert nå."
#: ../src/Block.cpp:822 ../src/LevelEditor.cpp:265
msgid "On"
msgstr "På"
#: ../src/Block.cpp:823 ../src/LevelEditor.cpp:266
msgid "Off"
msgstr "Av"
#: ../src/CommandManager.cpp:41
#, c-format
msgid "Undo %s"
msgstr "Angre %s"
#: ../src/CommandManager.cpp:43
msgid "Can't undo"
msgstr "Kan ikke angre"
#: ../src/CommandManager.cpp:49
#, c-format
msgid "Redo %s"
msgstr "Gjenta %s"
#: ../src/CommandManager.cpp:51
msgid "Can't redo"
msgstr "Kan ikke gjenta"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:190
msgid "Resize level"
msgstr "Endre nivåstørrelse"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:807
msgid "Modify level property"
msgstr "Endre nivåegenskap"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:919
#, c-format
msgid "Add scenery layer %s"
msgstr "Legg til omgivelseslag %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:921
#, c-format
msgid "Delete scenery layer %s"
msgstr "Slett omgivelseslag %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:951
#, c-format
msgid "Modify the property of scenery layer %s"
msgstr "Endre egenskapene på omgivelseslaget %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] "Flytt %d objekter fra lag %s til lag %s"
msgstr[1] "Flytt %d objekter fra lag %s til lag %s"
#: ../src/CreditsMenu.cpp:35 ../src/TitleMenu.cpp:53
msgid "Credits"
msgstr "Bidragsytere"
# 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
-#, fuzzy
msgid "Loading..."
msgstr "Laster…"
#: ../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 "Avbryt"
#: ../src/Functions.cpp:1248
msgid "Abort"
msgstr "Angre"
#: ../src/Functions.cpp:1249 ../src/Functions.cpp:1265
msgid "Retry"
msgstr "Prøv igjen"
#: ../src/Functions.cpp:1250
msgid "Ignore"
msgstr "Ignorer"
#: ../src/Functions.cpp:1254 ../src/Functions.cpp:1260
msgid "Yes"
msgstr "Ja"
#: ../src/Functions.cpp:1255 ../src/Functions.cpp:1261
msgid "No"
msgstr "Nei"
# 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 "NIvå %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 "Trykk %s-tasten for å lagre spillet."
# TRANSLATORS: Please do not remove %s from your translation:
# - %s will be replaced with current action key
#: ../src/Game.cpp:920
#, c-format, fuzzy
msgid "Press %s key to swap the position of player and shadow."
msgstr "Trykk %s-tasten for å bytte sted mellom karakteren og skyggen."
# 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 "Trykk %s-tasten for å aktivere bryteren."
# 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 "Trykk %s-tasten for å teleportere."
# 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 ""
"Trykk %s for å starte nåværende nivå igjen, eller trykk %s for å laste inn "
"spillet."
# 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 "Trykk %s for å starte nåværende nivå igjen."
#: ../src/Game.cpp:996
msgid "Your shadow has died."
msgstr "Skyggen din har dødd."
#: ../src/Game.cpp:1052
#, c-format
msgid "%d recording"
msgid_plural "%d recordings"
msgstr[0] "%d opptak"
msgstr[1] "%d opptak"
#: ../src/Game.cpp:1224
msgid "You've finished:"
msgstr "Du har fullført:"
# 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 "Tid: %-.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 "Beste tid: %-.2fs"
#: ../src/Game.cpp:1311
#, c-format
msgid "Target time: %-.2fs"
msgstr "Mål: %-.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 "Opptak: %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 opptak: %d"
#: ../src/Game.cpp:1350
#, c-format
msgid "Target recordings: %d"
msgstr "Mål-opptak: %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 "Du har gjort deg fortjent til %s-medaljen"
#: ../src/Game.cpp:1363
msgid "GOLD"
msgstr "GULL"
#: ../src/Game.cpp:1363
msgid "SILVER"
msgstr "SØLV"
#: ../src/Game.cpp:1363
msgid "BRONZE"
msgstr "BRONSE"
# TRANSLATORS: used as return to the level selector menu
#: ../src/Game.cpp:1390
msgid "Menu"
msgstr "Meny"
# TRANSLATORS: used as restart level
#: ../src/Game.cpp:1397 ../src/InputManager.cpp:47
msgid "Restart"
msgstr "Start igjen"
# TRANSLATORS: used as next level
#: ../src/Game.cpp:1404
msgid "Next"
msgstr "Neste"
#: ../src/Game.cpp:1430
msgid "Game replay is done."
msgstr "Spillopptaket er ferdigspilt."
#: ../src/Game.cpp:1430
msgid "Game Replay"
msgstr "Spillopptaksavspilling"
#: ../src/Game.cpp:1767 ../src/Game.cpp:1769
msgid "Congratulations"
msgstr "Gratulerer"
#: ../src/Game.cpp:1769
msgid "You have finished the levelpack!"
msgstr "Du har fullført nivåpakken."
#: ../src/InputManager.cpp:46
msgid "Up (in menu)"
msgstr "Opp (i menyen)"
#: ../src/InputManager.cpp:46
msgid "Down (in menu)"
msgstr "Ned (i menyen)"
#: ../src/InputManager.cpp:46
msgid "Left"
msgstr "Venstre"
#: ../src/InputManager.cpp:46
msgid "Right"
msgstr "Høyre"
#: ../src/InputManager.cpp:46
msgid "Jump"
msgstr "Hopp"
#: ../src/InputManager.cpp:46
msgid "Action"
msgstr "Handling"
#: ../src/InputManager.cpp:46
msgid "Space (Record)"
msgstr "Mellomrom (ta opp)"
#: ../src/InputManager.cpp:46
msgid "Cancel recording"
msgstr "Avbryt opptak"
#: ../src/InputManager.cpp:47
msgid "Escape"
msgstr "Esc"
#: ../src/InputManager.cpp:47
msgid "Tab (View shadow/Level prop.)"
msgstr "Tab (vis skygge/nivåegenskap)"
#: ../src/InputManager.cpp:47
msgid "Save game (in editor)"
msgstr "Lagre spill (i redigerer)"
#: ../src/InputManager.cpp:47
msgid "Load game"
msgstr "Last inn spill"
#: ../src/InputManager.cpp:47
msgid "Swap (in editor)"
msgstr "Veksle (i redigerer)"
#: ../src/InputManager.cpp:48
msgid "Teleport (in editor)"
msgstr "Teleporter (i redigerer)"
#: ../src/InputManager.cpp:48
msgid "Suicide (in editor)"
msgstr "Selvmord (i redigerer)"
#: ../src/InputManager.cpp:48
#, fuzzy
msgid "Shift (in editor)"
msgstr "Bytt (i redigerer)"
#: ../src/InputManager.cpp:48
msgid "Next block type (in Editor)"
msgstr "Neste blokktype (i redigerer)"
#: ../src/InputManager.cpp:49
msgid "Previous block type (in editor)"
msgstr "Forrige blokktype (i redigerer)"
#: ../src/InputManager.cpp:49
msgid "Select (in menu)"
msgstr "Velg (i meny)"
# TRANSLAOTRS: This is used when the name of the key code is not found.
#: ../src/InputManager.cpp:156
#, c-format
msgid "(Key %d)"
msgstr "(Tast %d)"
#: ../src/InputManager.cpp:163
#, c-format
msgid "Joystick axis %d %s"
msgstr "Spillstikkeakse %d %s"
#: ../src/InputManager.cpp:166
#, c-format
msgid "Joystick button %d"
msgstr "Spillstikkeknapp %d"
#: ../src/InputManager.cpp:171
#, c-format, fuzzy
msgid "Joystick hat %d left"
msgstr "Spillstikkepinne %d venstre"
#: ../src/InputManager.cpp:174
#, c-format, fuzzy
msgid "Joystick hat %d right"
msgstr "Spillstikkepinne %d høyre"
#: ../src/InputManager.cpp:177
#, c-format, fuzzy
msgid "Joystick hat %d up"
msgstr "Spillstikkepinne %d oppover"
#: ../src/InputManager.cpp:180
#, c-format, fuzzy
msgid "Joystick hat %d down"
msgstr "Spillstikkepinne %d nedover"
# TRANSLAOTRS: This is used when the JOYSTICK_HAT value is invalid.
#: ../src/InputManager.cpp:185
#, c-format, fuzzy
msgid "Joystick hat %d %d"
msgstr "Spillstikkepinne %d %d"
#: ../src/InputManager.cpp:202
msgid "OR"
msgstr "ELLER"
#: ../src/InputManager.cpp:416
msgid "Select an item and press a key to change it."
msgstr "Velg et element og trykk en tast for å endre det."
#: ../src/InputManager.cpp:419
#, fuzzy
msgid "Press backspace to clear the selected item."
msgstr "Trykk rettetast for å tømme valgt element."
#: ../src/LevelEditor.cpp:56
msgid "Block"
msgstr "Blokk"
#: ../src/LevelEditor.cpp:56
msgid "Player Start"
msgstr "Spillerstart"
#: ../src/LevelEditor.cpp:56
msgid "Shadow Start"
msgstr "Skyggestart"
#: ../src/LevelEditor.cpp:57
msgid "Exit"
msgstr "Avslutt"
#: ../src/LevelEditor.cpp:57
msgid "Shadow Block"
msgstr "Skyggeblokk"
#: ../src/LevelEditor.cpp:57
msgid "Spikes"
msgstr "Tagger"
#: ../src/LevelEditor.cpp:58
msgid "Checkpoint"
msgstr "Sjekkpunkt"
#: ../src/LevelEditor.cpp:58 ../src/LevelEditSelect.cpp:312
#, fuzzy
msgid "Swap"
msgstr "Bytt"
#: ../src/LevelEditor.cpp:58
msgid "Fragile"
msgstr "Skjør"
#: ../src/LevelEditor.cpp:59
msgid "Moving Block"
msgstr "Bevegende blokk"
#: ../src/LevelEditor.cpp:59
msgid "Moving Shadow Block"
msgstr "Bevegende skyggeblokk"
#: ../src/LevelEditor.cpp:59
msgid "Moving Spikes"
msgstr "Bevegende tagger"
#: ../src/LevelEditor.cpp:60
msgid "Teleporter"
msgstr "Teleportør"
#: ../src/LevelEditor.cpp:60
msgid "Button"
msgstr "Knapp"
#: ../src/LevelEditor.cpp:60
msgid "Switch"
msgstr "Bryter"
#: ../src/LevelEditor.cpp:61
msgid "Conveyor Belt"
msgstr "Samlebånd"
#: ../src/LevelEditor.cpp:61
msgid "Shadow Conveyor Belt"
msgstr "Skyggesamlebånd"
#: ../src/LevelEditor.cpp:61
msgid "Notification Block"
msgstr "Merknadsblokk"
#: ../src/LevelEditor.cpp:61
msgid "Collectable"
msgstr "Sankbar"
#: ../src/LevelEditor.cpp:61
msgid "Pushable"
msgstr "Trykkbar"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:310
msgid "Select"
msgstr "Velg"
#: ../src/LevelEditor.cpp:65
msgid "Add"
msgstr "Legg til"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:311
msgid "Delete"
msgstr "Slett"
#: ../src/LevelEditor.cpp:65 ../src/LevelPlaySelect.cpp:66
#: ../src/TitleMenu.cpp:43
msgid "Play"
msgstr "Spill"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:2852
msgid "Level settings"
msgstr "Nivåinnstillinger"
#: ../src/LevelEditor.cpp:65
msgid "Save level"
msgstr "Lagre nivå"
#: ../src/LevelEditor.cpp:65
msgid "Back to menu"
msgstr "Tilbake til nivå"
#: ../src/LevelEditor.cpp:65
msgid "Configure"
msgstr "Sett opp"
#: ../src/LevelEditor.cpp:84
#, c-format, fuzzy
msgid "%s (Scenery)"
msgstr "%s (naturutsikt)"
#: ../src/LevelEditor.cpp:267
msgid "Toggle"
msgstr "Veksle"
#: ../src/LevelEditor.cpp:270
msgid "Complete"
msgstr "Fullfør"
#: ../src/LevelEditor.cpp:271
msgid "One step"
msgstr "Ett steg"
#: ../src/LevelEditor.cpp:272
msgid "Two steps"
msgstr "To steg"
#: ../src/LevelEditor.cpp:273
#, fuzzy
msgid "Gone"
msgstr "Borte"
#: ../src/LevelEditor.cpp:291
msgid "Negative infinity"
msgstr "Negativ uendelighet"
#: ../src/LevelEditor.cpp:293
msgid "Zero"
msgstr "Null"
#: ../src/LevelEditor.cpp:295
msgid "Level size"
msgstr "Nivåstørrelse"
#: ../src/LevelEditor.cpp:297
msgid "Positive infinity"
msgstr "Positiv uendelighet"
#: ../src/LevelEditor.cpp:299
msgid "Default"
msgstr "Forvalg"
#: ../src/LevelEditor.cpp:308
msgid "Deselect"
msgstr "Fravelg"
#: ../src/LevelEditor.cpp:318 ../src/LevelEditor.cpp:1136
#, c-format
msgid "Horizontal repeat start: %s"
msgstr "Vannrett gjentagelsesstart: %s"
#: ../src/LevelEditor.cpp:320 ../src/LevelEditor.cpp:1137
#, c-format
msgid "Horizontal repeat end: %s"
msgstr "Vannrett gjentagelsesslutt: %s"
#: ../src/LevelEditor.cpp:322 ../src/LevelEditor.cpp:1138
#, c-format
msgid "Vertical repeat start: %s"
msgstr "Vannrett gjentagelsesstart: %s"
#: ../src/LevelEditor.cpp:324 ../src/LevelEditor.cpp:1139
#, c-format
msgid "Vertical repeat end: %s"
msgstr "Vannrett gjentagelsesslutt: %s"
#: ../src/LevelEditor.cpp:329 ../src/LevelEditor.cpp:1150
#, fuzzy
msgid "Custom scenery"
msgstr "Egendefinert omgivelsesmiljø"
#: ../src/LevelEditor.cpp:335 ../src/LevelEditor.cpp:600
#: ../src/LevelEditor.cpp:602
msgid "Visible"
msgstr "Synlig"
#: ../src/LevelEditor.cpp:344
msgid "Link"
msgstr "Lenke"
#: ../src/LevelEditor.cpp:345
msgid "Remove Links"
msgstr "Fjern lenker"
#: ../src/LevelEditor.cpp:349 ../src/LevelEditor.cpp:624
#: ../src/LevelEditor.cpp:626
msgid "Automatic"
msgstr "Automatisk"
#: ../src/LevelEditor.cpp:359 ../src/LevelEditor.cpp:649
#, c-format
msgid "Behavior: %s"
msgstr "Oppførsel: %s"
#: ../src/LevelEditor.cpp:362
msgid "Path"
msgstr "Sti"
#: ../src/LevelEditor.cpp:363
msgid "Remove Path"
msgstr "Fjern sti"
#: ../src/LevelEditor.cpp:365 ../src/LevelEditor.cpp:371
#: ../src/LevelEditor.cpp:587 ../src/LevelEditor.cpp:589
msgid "Activated"
msgstr "Aktivert"
#: ../src/LevelEditor.cpp:366 ../src/LevelEditor.cpp:612
#: ../src/LevelEditor.cpp:614
#, fuzzy
msgid "Looping"
msgstr "Sløyfe"
#: ../src/LevelEditor.cpp:372 ../src/LevelEditor.cpp:3526
msgid "Speed"
msgstr "Hastighet"
#: ../src/LevelEditor.cpp:378 ../src/LevelEditor.cpp:668
#, c-format
msgid "State: %s"
msgstr "Tilstand: %s"
#: ../src/LevelEditor.cpp:382 ../src/LevelEditor.cpp:3511
msgid "Message"
msgstr "Melding"
#: ../src/LevelEditor.cpp:384 ../src/LevelEditor.cpp:1202
#: ../src/LevelEditor.cpp:3825
msgid "Appearance"
msgstr "Utseende"
#: ../src/LevelEditor.cpp:389 ../src/LevelEditor.cpp:431
#: ../src/LevelEditor.cpp:715
msgid "Scripting"
msgstr "Skripting"
#: ../src/LevelEditor.cpp:402 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Background layer: %s"
msgstr "Bakgrunnslag: %s"
#: ../src/LevelEditor.cpp:409 ../src/LevelEditor.cpp:866
#: ../src/LevelEditor.cpp:884
msgid "Blocks layer"
msgstr "Blokklag"
#: ../src/LevelEditor.cpp:417 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Foreground layer: %s"
msgstr "Forgrunnslag: %s"
#: ../src/LevelEditor.cpp:423
msgid "Add new layer"
msgstr "Legg til nytt lag"
#: ../src/LevelEditor.cpp:424
msgid "Delete selected layer"
msgstr "Slett valgt lag"
#: ../src/LevelEditor.cpp:425
msgid "Configure selected layer"
msgstr "Sett opp valgt lag"
#: ../src/LevelEditor.cpp:426
msgid "Move selected object to layer"
msgstr "Flytt valgt objekt til lag"
#: ../src/LevelEditor.cpp:430 ../src/OptionsMenu.cpp:55
msgid "Settings"
msgstr "Innstillinger"
#: ../src/LevelEditor.cpp:463
#, fuzzy
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 ""
"MERK: Lagene sorteres etter navn alfabetisk.\n"
"Laget er et bakgrunnslag hvis dets navn er < 'f'\n"
"etter ordboksanordning, ellers er det forgrunnslaget."
#: ../src/LevelEditor.cpp:539
msgid "Notification block"
msgstr "Merknadsblokk"
#: ../src/LevelEditor.cpp:545
msgid "Enter message here:"
msgstr "Skriv inn melding her:"
#: ../src/LevelEditor.cpp:646
msgid "Behavior"
msgstr "Oppførsel"
#: ../src/LevelEditor.cpp:665
msgid "State"
msgstr "Tilstand"
#: ../src/LevelEditor.cpp:673
msgid "Conveyor belt speed"
msgstr "Samlebåndshastighet"
#: ../src/LevelEditor.cpp:679
msgid "Enter speed here:"
msgstr "Skriv inn hastighet her:"
#: ../src/LevelEditor.cpp:690
#, fuzzy
msgid "NOTE: 1 Speed = 0.08 block/s"
msgstr "Merk: 1 fartsenhet = 0.08 blokk/er"
#: ../src/LevelEditor.cpp:721
#, fuzzy
msgid "Id:"
msgstr "ID:"
#: ../src/LevelEditor.cpp:787
msgid "Level Scripting"
msgstr "Nivåskript"
#: ../src/LevelEditor.cpp:892
msgid "Add layer"
msgstr "Legg til lag"
#: ../src/LevelEditor.cpp:898
msgid "Enter the layer name:"
msgstr "Skriv inn lagnavn:"
#: ../src/LevelEditor.cpp:943
#, c-format
msgid "Are you sure you want to delete layer '%s'?"
msgstr "Er du sikker på at du vil slette laget \"%s\"?"
#: ../src/LevelEditor.cpp:944
msgid "Delete layer"
msgstr "Slett lag"
#: ../src/LevelEditor.cpp:968
msgid "Layer settings"
msgstr "Laginnstillinger"
#: ../src/LevelEditor.cpp:974
msgid "Layer name:"
msgstr "Lagnavn:"
#: ../src/LevelEditor.cpp:989
msgid "Layer moving speed (1 speed = 0.8 block/s):"
msgstr "Lagbevegelseshastighet (1 hastighet = 0,8 blokker/s):"
#: ../src/LevelEditor.cpp:1010
msgid "Speed of following camera:"
msgstr "Hastighet på følgekamera:"
#: ../src/LevelEditor.cpp:1062
msgid "Move to layer"
msgstr "Flytt til lag"
#: ../src/LevelEditor.cpp:1068
msgid "Enter the layer name (create new layer if necessary):"
msgstr "Skriv innlagnavn (opprett nytt lag hvis nødvendig):"
#: ../src/LevelEditor.cpp:1132
msgid "Repeat mode"
msgstr "Gjentagelsesmodus"
#: ../src/LevelEditor.cpp:1156
msgid "Custom scenery:"
msgstr "Egendefinert omgivelsesmiljø:"
#: ../src/LevelEditor.cpp:1219
msgid "(Use the default appearance for this block)"
msgstr "(Bruk forvalgt utseende for denne blokken)"
# 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 "Egendefinert omgivelsesmiljøblokk"
#: ../src/LevelEditor.cpp:1673
msgid "Toolbox"
msgstr "Verktøyskasse"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Resize %s"
msgstr "Endre størrelse på %s"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Move %s"
msgstr "Flytt %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1713
#, c-format
msgid "Move %d object"
msgid_plural "Move %d objects"
msgstr[0] "Flytt %d objekt"
msgstr[1] "Flytt %d objekter"
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Add %s"
msgstr "Legg til %s"
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Remove %s"
msgstr "Fjern %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1729
#, c-format
msgid "Add %d object"
msgid_plural "Add %d objects"
msgstr[0] "Legg til %d objekt"
msgstr[1] "Legg til %d objekter"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1731
#, c-format
msgid "Remove %d object"
msgid_plural "Remove %d objects"
msgstr[0] "Fjern %d objekt"
msgstr[1] "Fjern %d objekter"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1739
#, c-format
msgid "Add path to %s"
msgstr "Legg til sti i %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1741
#, c-format
msgid "Remove a path point from %s"
msgstr "Fjern stipunkt fra %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1747
#, c-format
msgid "Remove all paths from %s"
msgstr "Fjern alle stier fra %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1753
#, c-format
msgid "Add link from %s to %s"
msgstr "Legg til lenke fra %s til %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1759
#, c-format
msgid "Remove all links from %s"
msgstr "Fjern alle lenker fra %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1766
#, fuzzy
msgid "Modify the %2 property of %1"
msgstr "Endre %s-egenskap for %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1818
#, c-format
msgid "Edit the script of %s"
msgstr "Rediger skriptet for %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1821
msgid "Edit the script of level"
msgstr "Rediger nivåets skript"
#: ../src/LevelEditor.cpp:2146 ../src/LevelEditor.cpp:2226
msgid "The level has unsaved changes."
msgstr "Nivået har ulagrede endringer."
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Are you sure you want to quit?"
msgstr "Er du sikker på at du vil avslutte?"
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Quit prompt"
msgstr "Avslutt spørring"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
#, c-format
msgid "Level \"%s\" saved"
msgstr "Nivået \"%s\" har blitt lagret"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
msgid "Saved"
msgstr "Lagret"
#: ../src/LevelEditor.cpp:2859 ../src/LevelEditSelect.cpp:208
msgid "Name:"
msgstr "Navn:"
#: ../src/LevelEditor.cpp:2866
msgid "Theme:"
msgstr "Drakt:"
#: ../src/LevelEditor.cpp:2873
#, fuzzy
msgid "Examples: %DATA%/themes/classic"
msgstr "Eksempler: %DATA%/themes/classic"
#: ../src/LevelEditor.cpp:2875
msgid "or %USER%/themes/Orange"
msgstr "eller %USER%/themes/Orange"
#: ../src/LevelEditor.cpp:2878
msgid "Music:"
msgstr "Musikk:"
#: ../src/LevelEditor.cpp:2887
msgid "Target time (s):"
msgstr "Måltid (s):"
#: ../src/LevelEditor.cpp:2903
msgid "Target recordings:"
msgstr "Målopptak:"
#: ../src/LevelEditor.cpp:2919
msgid "Restart level editor is required"
msgstr "Omstart av nivåredigerer kreves"
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3718
#: ../src/LevelEditor.cpp:3739
msgid "Please enter a layer name."
msgstr "Skriv inn et lagnavn."
#: ../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 "Feil"
#: ../src/LevelEditor.cpp:3683 ../src/LevelEditor.cpp:3722
#, c-format
msgid "The layer '%s' already exists."
msgstr "Laget \"%s\" finnes allerede."
#: ../src/LevelEditor.cpp:3743
msgid "Source and destination layers are the same."
msgstr "Kilde og mållag er de samme."
#: ../src/LevelEditor.cpp:3760
msgid "Scenery"
msgstr "Omgivelsesmiljø"
#: ../src/LevelEditor.cpp:4190 ../src/LevelEditor.cpp:4218
#, c-format
msgid "Speed: %d = %0.2f block/s"
msgstr "Hastighet: %d = %0.2f blokker/s"
#: ../src/LevelEditor.cpp:4203
msgid "Stop at this point"
msgstr "Stopp på dette punktet"
#: ../src/LevelEditor.cpp:4208
#, c-format
msgid "Pause: %d = %0.3fs"
msgstr "Pause: %d = %0.3fs"
#: ../src/LevelEditSelect.cpp:41 ../src/TitleMenu.cpp:45
msgid "Map Editor"
msgstr "Kartredigerer"
#: ../src/LevelEditSelect.cpp:66
msgid "New Levelpack"
msgstr "Ny nivåpakke"
#: ../src/LevelEditSelect.cpp:71
msgid "Pack Properties"
msgstr "Pakkeegenskaper"
#: ../src/LevelEditSelect.cpp:76
msgid "Remove Pack"
msgstr "Fjern pakke"
#: ../src/LevelEditSelect.cpp:81
msgid "Move Map"
msgstr "Flytt kart"
#: ../src/LevelEditSelect.cpp:89
msgid "Remove Map"
msgstr "FJern kart"
#: ../src/LevelEditSelect.cpp:94
msgid "Edit Map"
msgstr "Rediger kart"
#: ../src/LevelEditSelect.cpp:205
msgid "Properties"
msgstr "Egenskaper"
#: ../src/LevelEditSelect.cpp:217
msgid "Description:"
msgstr "Beskrivelse:"
#: ../src/LevelEditSelect.cpp:226
msgid "Congratulation text:"
msgstr "Gratuleringstekst:"
#: ../src/LevelEditSelect.cpp:235
msgid "Music list:"
msgstr "Musikkliste:"
#: ../src/LevelEditSelect.cpp:265 ../src/LevelEditSelect.cpp:485
msgid "Add level"
msgstr "Legg til nivå"
#: ../src/LevelEditSelect.cpp:268
#, fuzzy
msgid "File name:"
msgstr "Filnavn:"
#: ../src/LevelEditSelect.cpp:293
msgid "Move level"
msgstr "Flytt nivå"
#: ../src/LevelEditSelect.cpp:296
#, fuzzy
msgid "Level: "
msgstr "Nivå: "
#: ../src/LevelEditSelect.cpp:310
msgid "Before"
msgstr "Før"
#: ../src/LevelEditSelect.cpp:311
msgid "After"
msgstr "Etter"
#: ../src/LevelEditSelect.cpp:368 ../src/LevelPlaySelect.cpp:124
msgid "Individual levels which are not contained in any level packs"
msgstr "Individuelle nivå som ikke finnes i noen nivåpakker"
#: ../src/LevelEditSelect.cpp:577
#, c-format
msgid "Are you sure remove the level pack '%s'?"
msgstr "Er du sikker på at du vil fjerne nivåpakken \"%s\"?"
#: ../src/LevelEditSelect.cpp:577 ../src/LevelEditSelect.cpp:607
msgid "Remove prompt"
msgstr "Fjern spørring"
#: ../src/LevelEditSelect.cpp:607
#, c-format
msgid "Are you sure remove the map '%s'?"
msgstr "Er du sikker på at du vil fjerne kartet \"%s\"?"
#: ../src/LevelEditSelect.cpp:644
msgid "Levelpack name cannot be empty."
msgstr "Nivåpakkenavn kan ikke være tomt."
#: ../src/LevelEditSelect.cpp:683
#, c-format
msgid "The levelpack directory '%s' already exists!"
msgstr "Nivåpakkemappen \"%s\" finnes allerede."
#: ../src/LevelEditSelect.cpp:688
#, c-format
msgid "Unable to create levelpack directory '%s'!"
msgstr "Kunne ikke opprette nivåpakkemappe \"%s\"."
#: ../src/LevelEditSelect.cpp:693
#, c-format
msgid "The levelpack file '%s' already exists!"
msgstr "Nivåpakkefilen \"%s\" finnes allerede."
#: ../src/LevelEditSelect.cpp:698
#, c-format
msgid "Unable to create levelpack file '%s'!"
msgstr "Kunne ikke opprette nivåpakkefil \"%s\"."
#: ../src/LevelEditSelect.cpp:758
#, fuzzy
msgid "No file name given for the new level."
msgstr "Inget filnavn gitt for nytt nivå."
#: ../src/LevelEditSelect.cpp:758
#, fuzzy
msgid "Missing file name"
msgstr "Mangler filnavn"
#: ../src/LevelEditSelect.cpp:796
#, c-format
msgid "The file %s already exists."
msgstr "Filen %s finnes allerede."
#: ../src/LevelEditSelect.cpp:849
msgid "The entered level number isn't valid!"
-msgstr "Innskrevet nivånummer er ikke gyldig."
+msgstr "Innskrevet nivånummer er ikke gyldig!"
#: ../src/LevelEditSelect.cpp:849
msgid "Illegal number"
msgstr "Ulovlig nummer"
#: ../src/LevelInfoRender.cpp:19
msgid "Choose a level"
msgstr "Velg et nivå"
#: ../src/LevelInfoRender.cpp:20
msgid "Time:"
msgstr "Tid:"
#: ../src/LevelInfoRender.cpp:21 ../src/StatisticsScreen.cpp:259
msgid "Recordings:"
msgstr "Opptak:"
#: ../src/LevelPackManager.cpp:124
msgid "Custom Levels"
msgstr "Egendefinerte nivå"
#: ../src/LevelPlaySelect.cpp:41
msgid "Select Level"
msgstr "Velg nivå"
# TRANSLATORS: Used for button which clear any level progress like unlocked levels and highscores.
#: ../src/OptionsMenu.cpp:66
msgid "Clear Progress"
msgstr "Opphev framdrift"
#: ../src/OptionsMenu.cpp:109
msgid "General"
msgstr "Generelt"
#: ../src/OptionsMenu.cpp:110
msgid "Controls"
msgstr "Kontroller"
#: ../src/OptionsMenu.cpp:121
msgid "Music"
msgstr "Musikk"
#: ../src/OptionsMenu.cpp:129
msgid "Sound"
msgstr "Lyd"
#: ../src/OptionsMenu.cpp:137
msgid "Resolution"
msgstr "Oppløsning"
#: ../src/OptionsMenu.cpp:177
msgid "Language"
msgstr "Språk"
# TRANSLATORS: as detect user's language automatically
#: ../src/OptionsMenu.cpp:185
msgid "Auto-Detect"
msgstr "Oppdag automatisk"
#: ../src/OptionsMenu.cpp:209
msgid "Theme"
msgstr "Drakt"
#: ../src/OptionsMenu.cpp:247
msgid "Internet proxy"
msgstr "Internettmellomtjener"
#: ../src/OptionsMenu.cpp:256
msgid "Fullscreen"
msgstr "Fullskjermsvisning"
#: ../src/OptionsMenu.cpp:261
msgid "Quick record"
msgstr "Raskt opptak"
#: ../src/OptionsMenu.cpp:266
msgid "Internet"
msgstr "Internett"
#: ../src/OptionsMenu.cpp:271
#, fuzzy
msgid "Fade transition"
msgstr "Overgangsutmykning"
#: ../src/OptionsMenu.cpp:294
msgid "Save Changes"
msgstr "Lagre endringer"
#: ../src/OptionsMenu.cpp:513
msgid "Do you really want to reset level progress?"
msgstr "Ønsker du virkelig å tilbakestille nivåfremdriften?"
#: ../src/OptionsMenu.cpp:513
msgid "Warning"
msgstr "Advarsel"
#: ../src/StatisticsManager.cpp:386
#, fuzzy
msgid "New achievement:"
msgstr "Ny oppnåelse:"
#: ../src/StatisticsManager.cpp:394
#, c-format, fuzzy
msgid "Achieved on %s"
msgstr "Oppnådd den %s"
#: ../src/StatisticsManager.cpp:400
msgid "Unknown achievement"
msgstr "Ukjent oppnåelse"
#: ../src/StatisticsManager.cpp:406
#, c-format, fuzzy
msgid "Achieved %1.0f%%"
msgstr "Oppnådd %1.0f%%"
#: ../src/StatisticsManager.cpp:410
msgid "Not achieved"
msgstr "Ikke oppnådd"
#: ../src/StatisticsScreen.cpp:57 ../src/TitleMenu.cpp:55
#, fuzzy
msgid "Achievements and Statistics"
msgstr "Oppnåelser og statistikk"
#: ../src/StatisticsScreen.cpp:166
#, fuzzy
msgid "Achievements"
msgstr "Oppnåelser"
#: ../src/StatisticsScreen.cpp:167
msgid "Statistics"
msgstr "Statistikk"
#: ../src/StatisticsScreen.cpp:234
msgid "Total"
msgstr "Totalt"
#: ../src/StatisticsScreen.cpp:246
msgid "Traveling distance (m)"
msgstr "Reist distanse (m)"
#: ../src/StatisticsScreen.cpp:247
#, fuzzy
msgid "Jump times"
msgstr "Hopp"
#: ../src/StatisticsScreen.cpp:248
#, fuzzy
msgid "Die times"
msgstr "Dødsfall"
#: ../src/StatisticsScreen.cpp:249
msgid "Squashed times"
msgstr "Most"
#: ../src/StatisticsScreen.cpp:260
#, fuzzy
msgid "Switch pulled times:"
msgstr "Bryteraktuasjoner"
#: ../src/StatisticsScreen.cpp:261
#, fuzzy
msgid "Swap times:"
msgstr "Bytter"
#: ../src/StatisticsScreen.cpp:262
#, fuzzy
msgid "Save times:"
msgstr "Lagringer"
#: ../src/StatisticsScreen.cpp:263
msgid "Load times:"
msgstr "Innlastinger"
#: ../src/StatisticsScreen.cpp:268
msgid "Completed levels:"
msgstr "Fullførte nivå"
#: ../src/StatisticsScreen.cpp:306
msgid "In-game time:"
msgstr "Spilltid"
#: ../src/StatisticsScreen.cpp:308
msgid "Level editing time:"
msgstr "Redigeringstid"
#: ../src/StatisticsScreen.cpp:310
msgid "Created levels:"
msgstr "Opprettede nivå"
#: ../src/TitleMenu.cpp:44
msgid "Options"
msgstr "Valg"
#: ../src/TitleMenu.cpp:47
msgid "Quit"
msgstr "Avslutt"
#: ../src/TitleMenu.cpp:131
#, fuzzy
msgid "Enable internet in order to install addons."
msgstr "Tilknytt deg Internett for å installere programtillegg."
#: ../src/TitleMenu.cpp:131
#, fuzzy
msgid "Internet disabled"
msgstr "Ikke tilknyttet Internett"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Return"
msgstr "Tilbake"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Escape"
msgstr "Esc"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Backspace"
msgstr "Rettetast"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Tab"
msgstr "Tab"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Space"
msgstr "Mellomrom"
# TRANSLATORS: name of a key
#, fuzzy
msgctxt "keys"
msgid "CapsLock"
msgstr "Caps Lock"
# TRANSLATORS: name of a key
#, fuzzy
msgctxt "keys"
msgid "PrintScreen"
msgstr "Print Screen"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "ScrollLock"
msgstr "Scroll Lock"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Pause"
msgstr "Pause"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Insert"
msgstr "Insert"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Home"
msgstr "Home"
# TRANSLATORS: name of a key
#, fuzzy
msgctxt "keys"
msgid "PageUp"
msgstr "Page Up"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Delete"
msgstr "Del"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "End"
msgstr "End"
# TRANSLATORS: name of a key
#, fuzzy
msgctxt "keys"
msgid "PageDown"
msgstr "Page Down"
# 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 "Num Lock"
# TRANSLATORS: name of a key
#, fuzzy
msgctxt "keys"
msgid "SysReq"
msgstr "Sys Req"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Ctrl"
msgstr "Venstre Ctrl"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Shift"
msgstr "Venstre Shift"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Alt"
msgstr "Venstre Alt"
# TRANSLATORS: name of a key
#, fuzzy
msgctxt "keys"
msgid "Left GUI"
msgstr "Venstre visuelle grensesnitt"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Ctrl"
msgstr "Høyre Ctrl"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Shift"
msgstr "Høyre Shift"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Alt"
msgstr "Høyre Alt"
# TRANSLATORS: name of a key
#, fuzzy
msgctxt "keys"
msgid "Right GUI"
msgstr "Høyre visuelle grensesnitt"
diff --git a/data/locale/ru.po b/data/locale/ru.po
index cad8cbd..27d0e48 100644
--- a/data/locale/ru.po
+++ b/data/locale/ru.po
@@ -1,2032 +1,2027 @@
# Russian translation for Me and My Shadow strings
# Copyright (C)
# This file is distributed under the same license as the meandmyshadow package.
# Jz Pan <acme_pjz@hotmail.com>, 2012.
#
msgid ""
msgstr ""
"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-29 22:36+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=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 "Новичок"
#: ../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 "Пройдено 50 уровней."
+msgstr "Завершить 50 уровней."
#: ../src/AchievementList.cpp:45
msgid "Good job!"
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 "Получено 50 золотых медалей."
+msgstr "Получить 50 золотых медалей."
#: ../src/AchievementList.cpp:48
msgid "Graduate"
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 "За игрой прошло больше 2 часов."
+msgstr "За игрой прошло более двух часов."
#: ../src/AchievementList.cpp:52
msgid "Loyal fan of Me and My Shadow"
msgstr "Преданный поклонник"
#: ../src/AchievementList.cpp:52
msgid "Play Me and My Shadow for more than 24 hours."
msgstr "За игрой прошло более суток."
#: ../src/AchievementList.cpp:54
msgid "Constructor"
-msgstr "Творец"
+msgstr "Конструктор"
#: ../src/AchievementList.cpp:54
msgid "Use the level editor for more than 2 hours."
-msgstr "Редактор уровней использовался более 2 часов."
+msgstr "Использовать редактор уровней более двух часов."
#: ../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 "Ах, какая прелесть!"
#: ../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 "Вы создали 50 уровней."
+msgstr "Создать 50 уровней."
#: ../src/AchievementList.cpp:60
msgid "Hello, World!"
-msgstr "Кто здесь?!"
+msgstr "Hello, World!"
#: ../src/AchievementList.cpp:60
msgid "Write a script for the first time."
-msgstr "Вы написали свой первый скрипт."
+msgstr "Написать свой первый скрипт."
#: ../src/AchievementList.cpp:62
msgid "Frog"
msgstr "Лягушка-попрыгушка"
#: ../src/AchievementList.cpp:62
msgid "Jump 1000 times."
-msgstr "Вы подпрыгнули 1000 раз."
+msgstr "Прыгнуть 1000 раз."
#: ../src/AchievementList.cpp:64
msgid "Wanderer"
msgstr "Пешеход"
#: ../src/AchievementList.cpp:64
msgid "Travel 100 meters."
-msgstr "Вы прошли 100 метров."
+msgstr "Пройти 100 метров."
#: ../src/AchievementList.cpp:65
msgid "Runner"
-msgstr "Стариковская трусца"
+msgstr "Бродилка"
#: ../src/AchievementList.cpp:65
msgid "Travel 1 kilometer."
-msgstr "Вы прошли 1 километр."
+msgstr "Пройти 1 километр."
#: ../src/AchievementList.cpp:66
msgid "Long distance runner"
-msgstr "Забег на длинную дистанцию"
+msgstr "Дальнобойщик"
#: ../src/AchievementList.cpp:66
msgid "Travel 10 kilometers."
-msgstr "Вы прошли 10 километров."
+msgstr "Пройти 10 километров."
#: ../src/AchievementList.cpp:67
msgid "Marathon runner"
-msgstr "Марафон"
+msgstr "Марафонец"
#: ../src/AchievementList.cpp:67
msgid "Travel 42,195 meters."
-msgstr "Вы прошли 42 километра 195 метров."
+msgstr "Пройти 42195 метров."
#: ../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 "Ну и ладно..."
#: ../src/AchievementList.cpp:70
msgid "Die 50 times."
-msgstr "Вы умерли 50 раз."
+msgstr "Умереть 50 раз."
#: ../src/AchievementList.cpp:71
msgid "Expert of trial and error"
-msgstr "Гуру метода проб и ошибок"
+msgstr "Лауреат премии Дарвина"
#: ../src/AchievementList.cpp:71
msgid "Die 1000 times."
-msgstr "Вы умерли 1000 раз."
+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 "Вас раздавило 50 раз."
+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 "Тотальное невезение"
#: ../src/AchievementList.cpp:78
msgid "Die 5 times in under 5 seconds."
-msgstr "Вы умерли 5 раз за 5 секунд."
+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 "Вы умерли 10 раз за 5 секунд."
+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 "Сделано 100 записей."
+msgstr "Выполнить запись 100 раз."
#: ../src/AchievementList.cpp:85
msgid "Shadowmaster"
-msgstr "Повелитель теней"
+msgstr "У меня все ходы записаны!"
#: ../src/AchievementList.cpp:85
msgid "Record 1000 times."
-msgstr "Сделано 1000 записей."
+msgstr "Выполнить запись 1000 раз."
#: ../src/AchievementList.cpp:87
msgid "Switch puller"
-msgstr "Нажми на кнопку"
+msgstr "Выключателевключатель"
#: ../src/AchievementList.cpp:87
msgid "Pull the switch 100 times."
-msgstr "Вы поменяли положение переключателя 100 раз."
+msgstr "Включить выключатель 100 раз."
#: ../src/AchievementList.cpp:88
msgid "The switch is broken!"
-msgstr "Сейчас сломается!"
+msgstr "Выключателеломатель"
#: ../src/AchievementList.cpp:88
msgid "Pull the switch 1000 times."
-msgstr "Вы поменяли положение перключателя 1000 раз."
+msgstr "Включить выключатель 1000 раз."
#: ../src/AchievementList.cpp:90
msgid "Swapper"
-msgstr "Поменяться местами"
+msgstr "Обменный пункт"
#: ../src/AchievementList.cpp:90
msgid "Swap 100 times."
-msgstr "Вы переключились между игроком и тенью 100 раз."
+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 "Вы переключились между игроком и тенью 1000 раз."
+msgstr "Поменяться 1000 раз."
#: ../src/AchievementList.cpp:93
msgid "Play it save"
-msgstr "Осторожность лишней не бывает"
+msgstr "Хорошо сохранился"
#: ../src/AchievementList.cpp:93
msgid "Save 1000 times."
-msgstr "Игра сохранялась 1000 раз."
+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 "Игра загружалась 1000 раз."
+msgstr "Загрузить игру 1000 раз."
#: ../src/AchievementList.cpp:96
msgid "No, thanks"
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 "Дважды сохранить игру за 1 секунду."
+msgstr "Сохраниться дважды за секунду."
#: ../src/AchievementList.cpp:99
msgid "Panic load"
-msgstr "Не туда"
+msgstr "Паническая загрузка"
#: ../src/AchievementList.cpp:99
msgid "Load twice in 1 second."
-msgstr "Дважды загрузить игру за 1 секунду."
+msgstr "Загрузиться дважды за секунду."
#: ../src/AchievementList.cpp:101
msgid "Bad saving position"
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 "Да что ж такое"
#: ../src/AchievementList.cpp:102
msgid "Load the same save and die 100 times."
-msgstr "Загрузить один и тот же сейв и умерть 100 раз."
+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 "Сено-солома"
#: ../src/AchievementList.cpp:107
msgid "Press left and right simultaneously."
-msgstr "Одновременно нажимать направо и налево."
+msgstr "Нажать влево и вправо одновременно."
#: ../src/AchievementList.cpp:109
msgid "Cheater"
msgstr "Жулик"
#: ../src/AchievementList.cpp:109
msgid "Cheat in game."
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 "Уровни"
#: ../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 ""
-"Несколько уровней, сделанных одним и тем же автором или в похожем стиле"
+msgstr "Несколько уровней, созданных единым автором или в подобном стиле"
#: ../src/Addons.cpp:46
msgid "Themes"
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 "Не удалось запустить меню дополнений:"
#: ../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 "ОШИБКА: не удалось загрузить файл дополнения!"
#: ../src/Addons.cpp:182
msgid "ERROR: unable to load addon_list file!"
msgstr "ОШИБКА: не удалось загрузить файл addon_list!"
#: ../src/Addons.cpp:193
msgid "ERROR: Invalid file format of addons file!"
msgstr "ОШИБКА: Неверный формат файла дополнения!"
#: ../src/Addons.cpp:205
msgid "ERROR: Addon list version is unsupported!"
msgstr "ОШИБКА: версия списка дополнений не поддерживается!"
#: ../src/Addons.cpp:226
msgid "ERROR: Unable to create the installed_addons file."
-msgstr "ОШИБКА: невозможно создать список дополнений installed_addons."
+msgstr "ОШИБКА: не удалось создать список дополнений installed_addons."
#: ../src/Addons.cpp:238
msgid "ERROR: Invalid file format of the installed_addons!"
msgstr "ОШИБКА: у списка дополнений installed_addons неправильный формат!"
#: ../src/Addons.cpp:389 ../src/Addons.cpp:621
#, c-format
msgid "by %s"
msgstr "автор %"
#: ../src/Addons.cpp:397
msgid "Installed"
msgstr "Установлено"
#: ../src/Addons.cpp:402
msgid "Updatable"
msgstr "Есть обновления"
#: ../src/Addons.cpp:412
msgid "Not installed"
msgstr "Не установлено"
#: ../src/Addons.cpp:625
#, c-format
msgid "Version: %d\n"
msgstr "Версия: %d\n"
#: ../src/Addons.cpp:627
#, c-format
msgid "Installed version: %d\n"
msgstr "Установленная версия: %d\n"
#: ../src/Addons.cpp:630
#, c-format
msgid "License: %s\n"
msgstr "Лицензия: %s\n"
#: ../src/Addons.cpp:633
#, c-format
msgid "Website: %s\n"
msgstr "Веб-сайт: %s\n"
#: ../src/Addons.cpp:637
msgid "(No descriptions provided)"
-msgstr "(Описания нет)"
+msgstr "(Нет описания)"
#: ../src/Addons.cpp:657 ../src/Addons.cpp:684
msgid "Remove"
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 "Это дополнение удалить нельзя, оно нужно для правильной работы %s."
+msgstr "Это дополнение нельзя удалить, оно необходимо для правильной работы %s."
#: ../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 "ВНИМАНИЕ: файл '%s' уже удален."
+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 "Ошибка дополнения"
#: ../src/Addons.cpp:810
#, c-format
msgid "ERROR: Unable to remove file '%s'!"
msgstr "ОШИБКА: не получается удалить файл '%s'!"
#: ../src/Addons.cpp:818
#, c-format
msgid "WARNING: Directory '%s' appears to have been removed already."
-msgstr "ВНИМАНИЕ: Директорию '%s' уже удалили."
+msgstr "ВНИМАНИЕ: папка '%s' уже удалена."
#: ../src/Addons.cpp:825
#, c-format
msgid "ERROR: Unable to remove directory '%s'!"
-msgstr "ОШИБКА: не получается удалить директорию '%s'!"
+msgstr "ОШИБКА: не получается удалить папку '%s'!"
#: ../src/Addons.cpp:834
#, c-format
msgid "WARNING: Level '%s' appears to have been removed already."
-msgstr "ВНИМАНИЕ: Уровень '%s' уже удалили."
+msgstr "ВНИМАНИЕ: уровень '%s' уже удалён."
#: ../src/Addons.cpp:840
#, c-format
msgid "ERROR: Unable to remove level '%s'!"
msgstr "ОШИБКА: не получается удалить уровень '%s'!"
#: ../src/Addons.cpp:859
#, c-format
msgid "WARNING: Levelpack directory '%s' appears to have been removed already."
-msgstr "ВНИМАНИЕ: Набор уровней '%s' уже удалили."
+msgstr "ВНИМАНИЕ: папка со сборником уровней '%s' уже удалена."
#: ../src/Addons.cpp:866
#, c-format
msgid "ERROR: Unable to remove levelpack directory '%s'!"
msgstr "ОШИБКА: не получается удалить набор уровней '%s'!"
#: ../src/Addons.cpp:893
#, c-format
msgid "ERROR: Unable to download addon file %s."
msgstr "ОШИБКА: не получается загрузить файл дополнения %s."
#: ../src/Addons.cpp:900
#, c-format
msgid "ERROR: Unable to extract addon file %s."
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 "ОШИБКА: неверный формат файла метаданных!"
#: ../src/Addons.cpp:947
#, c-format
msgid "WARNING: File '%s' already exists, addon may be broken or not working!"
-msgstr ""
-"ВНИМАНИЕ: Файл '%s' уже существует. Возможно, дополнение глючит или не "
-"работает!"
+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'. Возможно, дополнение "
-"глючит или не работает!"
+"ВНИМАНИЕ: не получается скопировать файл '%s' в '%s', "
+"дополнение может глючить или не работать!"
#: ../src/Addons.cpp:962
#, c-format
msgid ""
"WARNING: Destination directory '%s' already exists, addon may be broken or "
"not working!"
msgstr ""
-"ВНИМАНИЕ: директория '%s' уже существует. Возможно, дополнение глючит или не "
-"работает!"
+"ВНИМАНИЕ: папка '%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'. Возможно, "
-"дополнение глючит или не работает!"
+"ВНИМАНИЕ: не получается переместить папку '%s' в '%s', "
+"дополнение может глючить или не работать!"
#: ../src/Addons.cpp:981
#, c-format
msgid "WARNING: Level '%s' already exists, addon may be broken or not working!"
-msgstr ""
-"ВНИМАНИЕ: Уровень '%s' уже существует. Возможно, дополнение глючит или не "
-"работает!"
+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'. Возможно, "
-"дополнение глючит или не работает!"
+"ВНИМАНИЕ: не получается скопировать уровень '%s' в '%s', "
+"дополнение может глючить или не работать!"
#: ../src/Addons.cpp:1008
#, c-format
msgid ""
"WARNING: Levelpack directory '%s' already exists, addon may be broken or not "
"working!"
msgstr ""
-"ВНИМАНИЕ: Директория с набором уровней '%s' уже существует. Возможно, "
-"дополнение глючит или не работает!"
+"ВНИМАНИЕ: папка со сборником уровней '%s' уже существует, "
+"дополнение может глючить или не работать!"
#: ../src/Addons.cpp:1044
#, c-format
msgid "ERROR: Addon requires another addon (%s) which can't be found!"
msgstr ""
"ОШИБКА: для правильной работы дополнения необходимо установить другое "
-"дополнение (%s)!"
+"дополнение (%s), однако его не удалось найти!"
#: ../src/Addons.cpp:1051
#, c-format
msgid "The addon %s is needed and will be installed now."
-msgstr "Необходимо установить дополнение %s. Установка сейчас начнется."
+msgstr "Необходимо установить дополнение %s. Установка сейчас начнётся."
#: ../src/Block.cpp:822 ../src/LevelEditor.cpp:265
msgid "On"
-msgstr "Вкл"
+msgstr "Вкл."
#: ../src/Block.cpp:823 ../src/LevelEditor.cpp:266
msgid "Off"
-msgstr "Выкл"
+msgstr "Выкл."
#: ../src/CommandManager.cpp:41
#, c-format
msgid "Undo %s"
msgstr "Отменить %s"
#: ../src/CommandManager.cpp:43
msgid "Can't undo"
-msgstr "Отменить не получается"
+msgstr "Невозможно отменить"
#: ../src/CommandManager.cpp:49
#, c-format
msgid "Redo %s"
msgstr "Повторить %s"
#: ../src/CommandManager.cpp:51
msgid "Can't redo"
-msgstr "Повторить не получается"
+msgstr "Невозможно повторить"
#: ../src/Commands.cpp:190
msgid "Resize level"
-msgstr "Изменение размеров уровня"
+msgstr "Изменить размер уровня"
#: ../src/Commands.cpp:807
msgid "Modify level property"
-msgstr "Изменение свойств уровня"
+msgstr "Изменить свойства уровня"
#: ../src/Commands.cpp:919
#, c-format
msgid "Add scenery layer %s"
-msgstr "Добавление слоя ландшафта %s"
+msgstr "Добавить фоновый слой %s"
#: ../src/Commands.cpp:921
#, c-format
msgid "Delete scenery layer %s"
-msgstr "Удаление слоя ландшафта %s"
+msgstr "Удалить фоновый слой %s"
#: ../src/Commands.cpp:951
#, c-format
msgid "Modify the property of scenery layer %s"
-msgstr "Изменение свойств слоя ландшафта %s"
+msgstr "Изменить свойства фонового слоя %s"
#: ../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 объект из слоя %s в слой %s"
msgstr[1] "Переместить %d объекта из слоя %s в слой %s"
msgstr[2] "Переместить %d объектов из слоя %s в слой %s"
-msgstr[3] "объекта"
+msgstr[3] "Переместить %d объектов из слоя %s в слой %s"
#: ../src/CreditsMenu.cpp:35 ../src/TitleMenu.cpp:53
msgid "Credits"
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"
#: ../src/Functions.cpp:575
msgid "Blokletters-Viltstift"
msgstr "DejaVuSansCondensed-Oblique"
#: ../src/Functions.cpp:674
msgid "Loading..."
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 "ОК"
+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 "Пропустить"
#: ../src/Functions.cpp:1254 ../src/Functions.cpp:1260
msgid "Yes"
msgstr "Да"
#: ../src/Functions.cpp:1255 ../src/Functions.cpp:1261
msgid "No"
msgstr "Нет"
#: ../src/Game.cpp:280 ../src/Game.cpp:1236
#, c-format
msgid "Level %d %s"
msgstr "Уровень %d %s"
#: ../src/Game.cpp:915
#, c-format
msgid "Press %s key to save the game."
msgstr "Чтобы сохранить игру, нажмите клавишу %s."
#: ../src/Game.cpp:920
#, c-format
msgid "Press %s key to swap the position of player and shadow."
msgstr "Чтобы поменять позицию игрока и тени, нажмите клавишу %s."
#: ../src/Game.cpp:925
#, c-format
msgid "Press %s key to activate the switch."
msgstr "Чтобы активировать переключатель, нажмите клавишу %s."
#: ../src/Game.cpp:930
#, c-format
msgid "Press %s key to teleport."
msgstr "Чтобы телепортироваться, нажмите клавишу %s."
#: ../src/Game.cpp:972
#, c-format
msgid "Press %s to restart current level or press %s to load the game."
-msgstr ""
-"Чтобы перезапустить этот уровень, нажмите %s, или нажмите %s, чтобы "
-"загрузить игру."
+msgstr "Чтобы перезапустить этот уровень, нажмите %s, или нажмите %s, чтобы загрузить игру."
#: ../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] "%d запись"
msgstr[1] "%d записи"
msgstr[2] "%d записей"
-msgstr[3] "%d записи"
+msgstr[3] "%d записей"
#: ../src/Game.cpp:1224
msgid "You've finished:"
-msgstr "Завершенных уровней:"
+msgstr "Завершённых уровней:"
#: ../src/Game.cpp:1291
#, c-format
msgid "Time: %-.2fs"
msgstr "Время: %-.2fs"
#: ../src/Game.cpp:1300
#, c-format
msgid "Best time: %-.2fs"
msgstr "Лучшее время: %-.2fs"
#: ../src/Game.cpp:1311
#, c-format
msgid "Target time: %-.2fs"
msgstr "Необходимое время: %-.2fs"
#: ../src/Game.cpp:1332
#, c-format
msgid "Recordings: %d"
msgstr "Записей: %d"
#: ../src/Game.cpp:1340
#, c-format
msgid "Best recordings: %d"
msgstr "Лучшая запись: %d"
#: ../src/Game.cpp:1350
#, c-format
msgid "Target recordings: %d"
msgstr "Необходимо записей: %d"
#: ../src/Game.cpp:1363
#, c-format
msgid "You earned the %s medal"
msgstr "Вы получили %s медаль"
#: ../src/Game.cpp:1363
msgid "GOLD"
-msgstr "золотую"
+msgstr "ЗОЛОТУЮ"
#: ../src/Game.cpp:1363
msgid "SILVER"
-msgstr "серебряную"
+msgstr "СЕРЕБРЯНУЮ"
#: ../src/Game.cpp:1363
msgid "BRONZE"
-msgstr "бронзовую"
+msgstr "БРОНЗОВУЮ"
#: ../src/Game.cpp:1390
msgid "Menu"
msgstr "Меню"
#: ../src/Game.cpp:1397 ../src/InputManager.cpp:47
msgid "Restart"
msgstr "Снова"
#: ../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 "Вы закончили этот набор уровней!"
+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 "Пробел (запись)"
+msgstr "Пробел (Запись)"
#: ../src/InputManager.cpp:46
msgid "Cancel recording"
msgstr "Отменить запись"
#: ../src/InputManager.cpp:47
msgid "Escape"
msgstr "Выход"
#: ../src/InputManager.cpp:47
msgid "Tab (View shadow/Level prop.)"
-msgstr "Tab (смотреть на тень/настройки уровня)"
+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 "Перестановка (в редакторе)"
+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 "Выбрать (в меню)"
#: ../src/InputManager.cpp:156
#, c-format
msgid "(Key %d)"
-msgstr ""
+msgstr "(Клавиша %d)"
#: ../src/InputManager.cpp:163
#, c-format
msgid "Joystick axis %d %s"
-msgstr ""
+msgstr "Ось джойстика %d %s"
#: ../src/InputManager.cpp:166
#, c-format
msgid "Joystick button %d"
-msgstr ""
+msgstr "Кнопка джойстика %d"
#: ../src/InputManager.cpp:171
#, c-format
msgid "Joystick hat %d left"
-msgstr ""
+msgstr "Джойстик %d влево"
#: ../src/InputManager.cpp:174
#, c-format
msgid "Joystick hat %d right"
-msgstr ""
+msgstr "Джойстик %d вправо"
#: ../src/InputManager.cpp:177
#, c-format
msgid "Joystick hat %d up"
-msgstr ""
+msgstr "Джойстик %d вверх"
#: ../src/InputManager.cpp:180
#, c-format
msgid "Joystick hat %d down"
-msgstr ""
+msgstr "Джойстик %d вниз"
#: ../src/InputManager.cpp:185
#, c-format
msgid "Joystick hat %d %d"
-msgstr ""
+msgstr "Джойстик %d %d"
#: ../src/InputManager.cpp:202
msgid "OR"
-msgstr ""
+msgstr "ИЛИ"
#: ../src/InputManager.cpp:416
msgid "Select an item and press a key to change it."
-msgstr ""
+msgstr "Выберите элемент и нажмите клавишу, чтобы изменить его."
#: ../src/InputManager.cpp:419
msgid "Press backspace to clear the selected item."
-msgstr ""
+msgstr "Нажмите backspace, чтобы очистить выбранный элемент."
#: ../src/LevelEditor.cpp:56
msgid "Block"
msgstr "Блок"
#: ../src/LevelEditor.cpp:56
msgid "Player Start"
-msgstr "Точка начала"
+msgstr "Здесь начинает игрок"
#: ../src/LevelEditor.cpp:56
msgid "Shadow Start"
-msgstr "Точка начала тени"
+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 "Контрольная точка"
+msgstr "Чекпоинт"
#: ../src/LevelEditor.cpp:58 ../src/LevelEditSelect.cpp:312
msgid "Swap"
-msgstr "Перестановка"
+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 "Телепорт"
+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 "Ключ"
+msgstr "Подбираемый предмет"
#: ../src/LevelEditor.cpp:61
msgid "Pushable"
-msgstr ""
+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 ""
+msgstr "%s (Фон)"
#: ../src/LevelEditor.cpp:267
msgid "Toggle"
msgstr "Переключать"
#: ../src/LevelEditor.cpp:270
msgid "Complete"
-msgstr "Целый"
+msgstr "Завершённый"
#: ../src/LevelEditor.cpp:271
msgid "One step"
-msgstr "На два шага"
+msgstr "Один шаг"
#: ../src/LevelEditor.cpp:272
msgid "Two steps"
-msgstr "На один шаг"
+msgstr "Два шага"
#: ../src/LevelEditor.cpp:273
msgid "Gone"
msgstr "Разрушен"
#: ../src/LevelEditor.cpp:291
msgid "Negative infinity"
-msgstr ""
+msgstr "Минус бесконечность"
#: ../src/LevelEditor.cpp:293
msgid "Zero"
-msgstr ""
+msgstr "Ноль"
#: ../src/LevelEditor.cpp:295
msgid "Level size"
-msgstr ""
+msgstr "Размер уровня"
#: ../src/LevelEditor.cpp:297
msgid "Positive infinity"
-msgstr ""
+msgstr "Плюс бесконечность"
#: ../src/LevelEditor.cpp:299
msgid "Default"
msgstr "Стандартный"
#: ../src/LevelEditor.cpp:308
msgid "Deselect"
-msgstr ""
+msgstr "Отменить выбор"
#: ../src/LevelEditor.cpp:318 ../src/LevelEditor.cpp:1136
#, c-format
msgid "Horizontal repeat start: %s"
-msgstr ""
+msgstr "Начать горизонтальное повторение: %s"
#: ../src/LevelEditor.cpp:320 ../src/LevelEditor.cpp:1137
#, c-format
msgid "Horizontal repeat end: %s"
-msgstr ""
+msgstr "Завершить горизонтальное повторение: %s"
#: ../src/LevelEditor.cpp:322 ../src/LevelEditor.cpp:1138
#, c-format
msgid "Vertical repeat start: %s"
-msgstr ""
+msgstr "Начать вертикальное повторение: %s"
#: ../src/LevelEditor.cpp:324 ../src/LevelEditor.cpp:1139
#, c-format
msgid "Vertical repeat end: %s"
-msgstr ""
+msgstr "Завершить вертикальное повторение: %s"
#: ../src/LevelEditor.cpp:329 ../src/LevelEditor.cpp:1150
msgid "Custom scenery"
-msgstr ""
+msgstr "Настроить фон"
#: ../src/LevelEditor.cpp:335 ../src/LevelEditor.cpp:600
#: ../src/LevelEditor.cpp:602
msgid "Visible"
-msgstr ""
+msgstr "Видимый"
#: ../src/LevelEditor.cpp:344
msgid "Link"
-msgstr ""
+msgstr "Ссылка"
#: ../src/LevelEditor.cpp:345
msgid "Remove Links"
-msgstr ""
+msgstr "Убрать ссылки"
#: ../src/LevelEditor.cpp:349 ../src/LevelEditor.cpp:624
#: ../src/LevelEditor.cpp:626
msgid "Automatic"
-msgstr ""
+msgstr "Авто"
#: ../src/LevelEditor.cpp:359 ../src/LevelEditor.cpp:649
#, c-format
msgid "Behavior: %s"
-msgstr ""
+msgstr "Поведение: %s"
#: ../src/LevelEditor.cpp:362
msgid "Path"
msgstr "Путь"
#: ../src/LevelEditor.cpp:363
msgid "Remove Path"
-msgstr ""
+msgstr "Удалить путь"
#: ../src/LevelEditor.cpp:365 ../src/LevelEditor.cpp:371
#: ../src/LevelEditor.cpp:587 ../src/LevelEditor.cpp:589
msgid "Activated"
-msgstr ""
+msgstr "Активировано"
#: ../src/LevelEditor.cpp:366 ../src/LevelEditor.cpp:612
#: ../src/LevelEditor.cpp:614
msgid "Looping"
-msgstr ""
+msgstr "Повторять"
#: ../src/LevelEditor.cpp:372 ../src/LevelEditor.cpp:3526
msgid "Speed"
-msgstr ""
+msgstr "Скорость"
#: ../src/LevelEditor.cpp:378 ../src/LevelEditor.cpp:668
#, c-format
msgid "State: %s"
-msgstr ""
+msgstr "Состояние: %s"
#: ../src/LevelEditor.cpp:382 ../src/LevelEditor.cpp:3511
msgid "Message"
-msgstr ""
+msgstr "Сообщение"
#: ../src/LevelEditor.cpp:384 ../src/LevelEditor.cpp:1202
#: ../src/LevelEditor.cpp:3825
msgid "Appearance"
-msgstr ""
+msgstr "Внешний вид"
#: ../src/LevelEditor.cpp:389 ../src/LevelEditor.cpp:431
#: ../src/LevelEditor.cpp:715
msgid "Scripting"
-msgstr ""
+msgstr "Скрипты"
#: ../src/LevelEditor.cpp:402 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Background layer: %s"
-msgstr ""
+msgstr "Слой фона: %s"
#: ../src/LevelEditor.cpp:409 ../src/LevelEditor.cpp:866
#: ../src/LevelEditor.cpp:884
msgid "Blocks layer"
-msgstr ""
+msgstr "Сбой блоков"
#: ../src/LevelEditor.cpp:417 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Foreground layer: %s"
-msgstr ""
+msgstr "Передний план: %s"
#: ../src/LevelEditor.cpp:423
msgid "Add new layer"
-msgstr ""
+msgstr "Добавить слой"
#: ../src/LevelEditor.cpp:424
msgid "Delete selected layer"
-msgstr ""
+msgstr "Удалить выбранный слой"
#: ../src/LevelEditor.cpp:425
msgid "Configure selected layer"
-msgstr ""
+msgstr "Настроить выбранный слой"
#: ../src/LevelEditor.cpp:426
msgid "Move selected object to layer"
-msgstr ""
+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 ""
+msgstr "ЗАМЕТКА: слои отсортированы по названию в алфавитном порядке.\n"
+"Слой является фоновым слоем, если его имя < 'f'\n"
+"по словарному порядку, в противоположном случае - это слой переднего плана."
#: ../src/LevelEditor.cpp:539
msgid "Notification block"
msgstr "Блок оповещения"
#: ../src/LevelEditor.cpp:545
msgid "Enter message here:"
msgstr "Введите сообщение:"
#: ../src/LevelEditor.cpp:646
msgid "Behavior"
-msgstr ""
+msgstr "Поведение"
#: ../src/LevelEditor.cpp:665
msgid "State"
-msgstr ""
+msgstr "Состояние"
#: ../src/LevelEditor.cpp:673
msgid "Conveyor belt speed"
-msgstr ""
+msgstr "Скорость конвейера"
#: ../src/LevelEditor.cpp:679
msgid "Enter speed here:"
msgstr "Введите скорость:"
#: ../src/LevelEditor.cpp:690
msgid "NOTE: 1 Speed = 0.08 block/s"
-msgstr ""
+msgstr "ПРИМ.: 1 Скорость = 0,08 блоков/с"
#: ../src/LevelEditor.cpp:721
msgid "Id:"
-msgstr ""
+msgstr "Ид:"
#: ../src/LevelEditor.cpp:787
msgid "Level Scripting"
-msgstr ""
+msgstr "Скрипты уровня"
#: ../src/LevelEditor.cpp:892
msgid "Add layer"
-msgstr ""
+msgstr "Добавить слой"
#: ../src/LevelEditor.cpp:898
msgid "Enter the layer name:"
-msgstr ""
+msgstr "Введите название слоя:"
#: ../src/LevelEditor.cpp:943
#, c-format
msgid "Are you sure you want to delete layer '%s'?"
-msgstr ""
+msgstr "Вы уверены, что Вы хотите удалить слой '%s'?"
#: ../src/LevelEditor.cpp:944
msgid "Delete layer"
-msgstr ""
+msgstr "Удалить слой"
#: ../src/LevelEditor.cpp:968
msgid "Layer settings"
-msgstr ""
+msgstr "Настроить слой"
#: ../src/LevelEditor.cpp:974
msgid "Layer name:"
-msgstr ""
+msgstr "Название слоя:"
#: ../src/LevelEditor.cpp:989
msgid "Layer moving speed (1 speed = 0.8 block/s):"
-msgstr ""
+msgstr "Скорость перемещения слоя (1 скорость = 0,08 блоков/с):"
#: ../src/LevelEditor.cpp:1010
msgid "Speed of following camera:"
-msgstr ""
+msgstr "Скорость камеры:"
#: ../src/LevelEditor.cpp:1062
msgid "Move to layer"
-msgstr ""
+msgstr "Переместить в слой"
#: ../src/LevelEditor.cpp:1068
msgid "Enter the layer name (create new layer if necessary):"
-msgstr ""
+msgstr "Введите название слоя (создать новый слой при необходимости):"
#: ../src/LevelEditor.cpp:1132
msgid "Repeat mode"
-msgstr ""
+msgstr "Режим повторения"
#: ../src/LevelEditor.cpp:1156
msgid "Custom scenery:"
-msgstr ""
+msgstr "Фон:"
#: ../src/LevelEditor.cpp:1219
msgid "(Use the default appearance for this block)"
-msgstr ""
+msgstr "(Использовать обычный вид этого блока)"
#: ../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 ""
+msgstr "Блок фона"
#: ../src/LevelEditor.cpp:1673
msgid "Toolbox"
-msgstr "Инструментарий"
+msgstr "Инструменты"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Resize %s"
-msgstr ""
+msgstr "Изменить размер %s"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Move %s"
-msgstr ""
+msgstr "Переместить %s"
#: ../src/LevelEditor.cpp:1713
#, c-format
msgid "Move %d object"
msgid_plural "Move %d objects"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "Переместить %d объект"
+msgstr[1] "Переместить %d объекта"
+msgstr[2] "Переместить %d объектов"
+msgstr[3] "Переместить %d объектов"
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Add %s"
-msgstr ""
+msgstr "Добавить %s"
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Remove %s"
-msgstr ""
+msgstr "Удалить %s"
#: ../src/LevelEditor.cpp:1729
#, c-format
msgid "Add %d object"
msgid_plural "Add %d objects"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "Добавить %d объект"
+msgstr[1] "Добавить %d объекта"
+msgstr[2] "Добавить %d объектов"
+msgstr[3] "Добавить %d объектов"
#: ../src/LevelEditor.cpp:1731
#, c-format
msgid "Remove %d object"
msgid_plural "Remove %d objects"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "Удалить %d объект"
+msgstr[1] "Удалить %d объекта"
+msgstr[2] "Удалить %d объектов"
+msgstr[3] "Удалить %d объектов"
#: ../src/LevelEditor.cpp:1739
#, c-format
msgid "Add path to %s"
-msgstr ""
+msgstr "Добавить путь к %s"
#: ../src/LevelEditor.cpp:1741
#, c-format
msgid "Remove a path point from %s"
-msgstr ""
+msgstr "Удалить точку пути из %s"
#: ../src/LevelEditor.cpp:1747
#, c-format
msgid "Remove all paths from %s"
-msgstr ""
+msgstr "Удалить все пути из %s"
#: ../src/LevelEditor.cpp:1753
#, c-format
msgid "Add link from %s to %s"
-msgstr ""
+msgstr "Добавить сслыку между %s и %s"
#: ../src/LevelEditor.cpp:1759
#, c-format
msgid "Remove all links from %s"
-msgstr ""
+msgstr "Удалить все ссылки из %s"
#: ../src/LevelEditor.cpp:1766
msgid "Modify the %2 property of %1"
-msgstr ""
+msgstr "Изменить у %1 свойство %2"
#: ../src/LevelEditor.cpp:1818
#, c-format
msgid "Edit the script of %s"
-msgstr ""
+msgstr "Редактировать скрипт %s"
#: ../src/LevelEditor.cpp:1821
msgid "Edit the script of level"
-msgstr ""
+msgstr "Редактировать скрипт уровня"
#: ../src/LevelEditor.cpp:2146 ../src/LevelEditor.cpp:2226
msgid "The level has unsaved changes."
-msgstr ""
+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 "Выход"
+msgstr "Подтвердить выход"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
#, c-format
msgid "Level \"%s\" saved"
-msgstr "Уровень \"%s\" сохранен"
+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 ""
+msgstr "Примеры: %DATA%/themes/classic"
#: ../src/LevelEditor.cpp:2875
msgid "or %USER%/themes/Orange"
-msgstr ""
+msgstr "или %USER%/themes/Orange"
#: ../src/LevelEditor.cpp:2878
msgid "Music:"
-msgstr ""
+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 ""
+msgstr "Необходимо перезапустить редактор уровней"
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3718
#: ../src/LevelEditor.cpp:3739
msgid "Please enter a layer name."
-msgstr ""
+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 ""
+msgstr "Слой '%s' уже существует."
#: ../src/LevelEditor.cpp:3743
msgid "Source and destination layers are the same."
-msgstr ""
+msgstr "Исходный и конечный слои совпадают."
#: ../src/LevelEditor.cpp:3760
msgid "Scenery"
-msgstr ""
+msgstr "Фон"
#: ../src/LevelEditor.cpp:4190 ../src/LevelEditor.cpp:4218
#, c-format
msgid "Speed: %d = %0.2f block/s"
-msgstr ""
+msgstr "Скорость: %d = %0.2f блоков/с"
#: ../src/LevelEditor.cpp:4203
msgid "Stop at this point"
-msgstr ""
+msgstr "Остановиться в этой точке"
#: ../src/LevelEditor.cpp:4208
#, c-format
msgid "Pause: %d = %0.3fs"
-msgstr ""
+msgstr "Пауза: %d = %0.3fс"
#: ../src/LevelEditSelect.cpp:41 ../src/TitleMenu.cpp:45
msgid "Map Editor"
msgstr "Редактор карт"
#: ../src/LevelEditSelect.cpp:66
msgid "New Levelpack"
-msgstr "Новый набор уровней"
+msgstr "Новый сборник уровней"
#: ../src/LevelEditSelect.cpp:71
msgid "Pack Properties"
-msgstr "Свойства набора"
+msgstr "Свойства сборника"
#: ../src/LevelEditSelect.cpp:76
msgid "Remove Pack"
-msgstr "Удалить набор"
+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 ""
+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 "Уровень:"
+msgstr "Уровень: "
#: ../src/LevelEditSelect.cpp:310
msgid "Before"
-msgstr "До:"
+msgstr "До"
#: ../src/LevelEditSelect.cpp:311
msgid "After"
-msgstr "После:"
+msgstr "После"
#: ../src/LevelEditSelect.cpp:368 ../src/LevelPlaySelect.cpp:124
msgid "Individual levels which are not contained in any level packs"
-msgstr ""
+msgstr "Отдельные уровни, которые не содержатся ни в одном сборнике уровней"
#: ../src/LevelEditSelect.cpp:577
#, c-format
msgid "Are you sure remove the level pack '%s'?"
-msgstr ""
+msgstr "Вы уверены, что хотите удалить сборник уровней '%s'?"
#: ../src/LevelEditSelect.cpp:577 ../src/LevelEditSelect.cpp:607
msgid "Remove prompt"
-msgstr "Удаление"
+msgstr "Подтвердить удаление"
#: ../src/LevelEditSelect.cpp:607
#, c-format
msgid "Are you sure remove the map '%s'?"
-msgstr ""
+msgstr "Вы уверены, что хотите удалить карту '%s'?"
#: ../src/LevelEditSelect.cpp:644
msgid "Levelpack name cannot be empty."
-msgstr ""
+msgstr "Название сборника уровней не может быть пустым."
#: ../src/LevelEditSelect.cpp:683
#, c-format
msgid "The levelpack directory '%s' already exists!"
-msgstr ""
+msgstr "Папка сборника уровней '%s' уже существует!"
#: ../src/LevelEditSelect.cpp:688
#, c-format
msgid "Unable to create levelpack directory '%s'!"
-msgstr ""
+msgstr "Не удалось создать папку сборника уровней '%s'!"
#: ../src/LevelEditSelect.cpp:693
#, c-format
msgid "The levelpack file '%s' already exists!"
-msgstr ""
+msgstr "Файл сборника уровней '%s' уже существует!"
#: ../src/LevelEditSelect.cpp:698
#, c-format
msgid "Unable to create levelpack file '%s'!"
-msgstr ""
+msgstr "Не удалось создать файл сборника уровней '%s'!"
#: ../src/LevelEditSelect.cpp:758
msgid "No file name given for the new level."
-msgstr "Новому уровню не было присвоено имени файла."
+msgstr "Новому уровню не было присвоено имя файла."
#: ../src/LevelEditSelect.cpp:758
msgid "Missing file name"
msgstr "Отсутствует имя файла"
#: ../src/LevelEditSelect.cpp:796
#, c-format
msgid "The file %s already exists."
-msgstr ""
+msgstr "Файл %s уже существует."
#: ../src/LevelEditSelect.cpp:849
msgid "The entered level number isn't valid!"
-msgstr "Введенный номер уровня некорректен!"
+msgstr "Введённый номер уровня некорректен!"
#: ../src/LevelEditSelect.cpp:849
msgid "Illegal number"
-msgstr "Некорректный номер"
+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 ""
+msgstr "Другие уровни"
#: ../src/LevelPlaySelect.cpp:41
msgid "Select Level"
msgstr "Выберите уровень"
#: ../src/OptionsMenu.cpp:66
msgid "Clear Progress"
msgstr "Очистить прогресс"
#: ../src/OptionsMenu.cpp:109
msgid "General"
-msgstr ""
+msgstr "Общие"
#: ../src/OptionsMenu.cpp:110
msgid "Controls"
-msgstr ""
+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 "Язык"
#: ../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 ""
+msgstr "Быстрая запись"
#: ../src/OptionsMenu.cpp:266
msgid "Internet"
msgstr "Интернет"
#: ../src/OptionsMenu.cpp:271
msgid "Fade transition"
-msgstr ""
+msgstr "Плавный переход"
#: ../src/OptionsMenu.cpp:294
msgid "Save Changes"
msgstr "Сохранить изменения"
#: ../src/OptionsMenu.cpp:513
msgid "Do you really want to reset level progress?"
-msgstr "Вы действительно хотите очистить прогресс?"
+msgstr "Вы действительно хотите очистить прогресс уровня?"
#: ../src/OptionsMenu.cpp:513
msgid "Warning"
msgstr "Внимание"
#: ../src/StatisticsManager.cpp:386
msgid "New achievement:"
-msgstr ""
+msgstr "Новое достижение:"
#: ../src/StatisticsManager.cpp:394
#, c-format
msgid "Achieved on %s"
-msgstr ""
+msgstr "Достижение %s"
#: ../src/StatisticsManager.cpp:400
msgid "Unknown achievement"
-msgstr ""
+msgstr "Неизвестное достижение"
#: ../src/StatisticsManager.cpp:406
#, c-format
msgid "Achieved %1.0f%%"
-msgstr ""
+msgstr "Достижение %1.0f%%"
#: ../src/StatisticsManager.cpp:410
msgid "Not achieved"
-msgstr ""
+msgstr "Не достигнуто"
#: ../src/StatisticsScreen.cpp:57 ../src/TitleMenu.cpp:55
msgid "Achievements and Statistics"
-msgstr ""
+msgstr "Достижения и статистика"
#: ../src/StatisticsScreen.cpp:166
msgid "Achievements"
-msgstr ""
+msgstr "Достижения"
#: ../src/StatisticsScreen.cpp:167
msgid "Statistics"
-msgstr ""
+msgstr "Статистика"
#: ../src/StatisticsScreen.cpp:234
msgid "Total"
-msgstr ""
+msgstr "Всего"
#: ../src/StatisticsScreen.cpp:246
msgid "Traveling distance (m)"
-msgstr ""
+msgstr "Пройденное расстояние (м)"
#: ../src/StatisticsScreen.cpp:247
msgid "Jump times"
-msgstr ""
+msgstr "Прыжков"
#: ../src/StatisticsScreen.cpp:248
msgid "Die times"
-msgstr ""
+msgstr "Смертей"
#: ../src/StatisticsScreen.cpp:249
msgid "Squashed times"
-msgstr ""
+msgstr "Раздавлен"
#: ../src/StatisticsScreen.cpp:260
msgid "Switch pulled times:"
-msgstr ""
+msgstr "Включено выключателей:"
#: ../src/StatisticsScreen.cpp:261
msgid "Swap times:"
-msgstr ""
+msgstr "Поменялся:"
#: ../src/StatisticsScreen.cpp:262
msgid "Save times:"
-msgstr ""
+msgstr "Сохранений:"
#: ../src/StatisticsScreen.cpp:263
msgid "Load times:"
-msgstr ""
+msgstr "Загрузок:"
#: ../src/StatisticsScreen.cpp:268
msgid "Completed levels:"
-msgstr ""
+msgstr "Завершённых уровней:"
#: ../src/StatisticsScreen.cpp:306
msgid "In-game time:"
-msgstr ""
+msgstr "Время в игре:"
#: ../src/StatisticsScreen.cpp:308
msgid "Level editing time:"
-msgstr ""
+msgstr "Время в редакторе:"
#: ../src/StatisticsScreen.cpp:310
msgid "Created levels:"
-msgstr ""
+msgstr "Создано уровней:"
#: ../src/TitleMenu.cpp:44
msgid "Options"
-msgstr "Опции"
+msgstr "Настройки"
#: ../src/TitleMenu.cpp:47
msgid "Quit"
msgstr "Выход"
#: ../src/TitleMenu.cpp:131
msgid "Enable internet in order to install addons."
-msgstr "Чтобы устанавливать дополнения, разрешите подключение к интернету."
+msgstr "Чтобы устанавливать дополнения, разрешите подключение к Интернету."
#: ../src/TitleMenu.cpp:131
msgid "Internet disabled"
-msgstr "Подключение к интернету запрещено"
+msgstr "Подключение к Интернету запрещено"
msgctxt "keys"
msgid "Return"
-msgstr ""
+msgstr "Return"
msgctxt "keys"
msgid "Escape"
-msgstr ""
+msgstr "Escape"
msgctxt "keys"
msgid "Backspace"
-msgstr ""
+msgstr "Backspace"
msgctxt "keys"
msgid "Tab"
-msgstr ""
+msgstr "Tab"
msgctxt "keys"
msgid "Space"
-msgstr ""
+msgstr "Пробел"
msgctxt "keys"
msgid "CapsLock"
-msgstr ""
+msgstr "CapsLock"
msgctxt "keys"
msgid "PrintScreen"
-msgstr ""
+msgstr "PrintScreen"
msgctxt "keys"
msgid "ScrollLock"
-msgstr ""
+msgstr "ScrollLock"
msgctxt "keys"
msgid "Pause"
-msgstr ""
+msgstr "Pause"
msgctxt "keys"
msgid "Insert"
-msgstr ""
+msgstr "Insert"
msgctxt "keys"
msgid "Home"
-msgstr ""
+msgstr "Home"
msgctxt "keys"
msgid "PageUp"
-msgstr ""
+msgstr "PageUp"
msgctxt "keys"
msgid "Delete"
-msgstr ""
+msgstr "Delete"
msgctxt "keys"
msgid "End"
-msgstr ""
+msgstr "End"
msgctxt "keys"
msgid "PageDown"
-msgstr ""
+msgstr "PageDown"
msgctxt "keys"
msgid "Right"
-msgstr ""
+msgstr "Вправо"
msgctxt "keys"
msgid "Left"
-msgstr ""
+msgstr "Влево"
msgctxt "keys"
msgid "Down"
-msgstr ""
+msgstr "Вниз"
msgctxt "keys"
msgid "Up"
-msgstr ""
+msgstr "Вверх"
msgctxt "keys"
msgid "Numlock"
-msgstr ""
+msgstr "Numlock"
msgctxt "keys"
msgid "SysReq"
-msgstr ""
+msgstr "SysReq"
msgctxt "keys"
msgid "Left Ctrl"
-msgstr ""
+msgstr "Левый Ctrl"
msgctxt "keys"
msgid "Left Shift"
-msgstr ""
+msgstr "Левый Shift"
msgctxt "keys"
msgid "Left Alt"
-msgstr ""
+msgstr "Левый Alt"
msgctxt "keys"
msgid "Left GUI"
-msgstr ""
+msgstr "Левый GUI"
msgctxt "keys"
msgid "Right Ctrl"
-msgstr ""
+msgstr "Правый Ctrl"
msgctxt "keys"
msgid "Right Shift"
-msgstr ""
+msgstr "Правый Shift"
msgctxt "keys"
msgid "Right Alt"
-msgstr ""
+msgstr "Правый Alt"
msgctxt "keys"
msgid "Right GUI"
-msgstr ""
+msgstr "Правый GUI"
diff --git a/data/locale/nb.po b/data/locale/uk.po
similarity index 61%
copy from data/locale/nb.po
copy to data/locale/uk.po
index b8b1482..3b17f28 100644
--- a/data/locale/nb.po
+++ b/data/locale/uk.po
@@ -1,2162 +1,2110 @@
# 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-10-03 20:32+0000\n"
-"Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n"
-"Language-Team: Norwegian Bokmål <https://hosted.weblate.org/projects/"
-"me-and-my-shadow/translations/nb/>\n"
-"Language: nb\n"
+"PO-Revision-Date: 2018-09-13 12:13+0000\n"
+"Last-Translator: Jz Pan <acme_pjz@hotmail.com>\n"
+"Language-Team: Ukrainian <https://hosted.weblate.org/projects/me-and-my-shadow/"
+"translations/uk/>\n"
+"Language: uk\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=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"
#: ../src/AchievementList.cpp:43
msgid "Newbie"
-msgstr "Nybegynner"
+msgstr "Початківець"
#: ../src/AchievementList.cpp:43
msgid "Complete a level."
-msgstr "Fullføre nivå."
+msgstr "Завершити один рівень."
#: ../src/AchievementList.cpp:44
msgid "Experienced player"
-msgstr "Erfaren spiller"
+msgstr "Досвідчений гравець"
#: ../src/AchievementList.cpp:44
msgid "Complete 50 levels."
-msgstr "Fullføre 50 nivåer."
+msgstr "Завершити 50 рівнів."
#: ../src/AchievementList.cpp:45
msgid "Good job!"
-msgstr "Godt gjort!"
+msgstr "Молодець!"
#: ../src/AchievementList.cpp:45
msgid "Receive a gold medal."
-msgstr "Motta en gullmedalje."
+msgstr "Отримати золоту медаль."
#: ../src/AchievementList.cpp:46
msgid "Expert"
-msgstr "Ekspert"
+msgstr "Експерт"
#: ../src/AchievementList.cpp:46
msgid "Earn 50 gold medal."
-msgstr "Motta 50 gullmedaljer."
+msgstr "Отримати 50 золотих медалей."
#: ../src/AchievementList.cpp:48
msgid "Graduate"
-msgstr "Avanser"
+msgstr "Випускник"
#: ../src/AchievementList.cpp:48
msgid "Complete the tutorial level pack."
-msgstr "Fullfør veiledningsnivåpakken."
+msgstr "Завершити збірник навчальних рівнів."
#: ../src/AchievementList.cpp:49
msgid "Outstanding graduate"
-msgstr "Fremragende disippel"
+msgstr "Відмінник"
#: ../src/AchievementList.cpp:49
msgid "Complete the tutorial level pack with gold for all levels."
-msgstr "Fullfør veiledningsnivåpakken med gull på alle nivåer."
+msgstr "Завершити усі навчальні рівні із золотими медалями."
#: ../src/AchievementList.cpp:51
msgid "Hooked"
-msgstr "Hektet"
+msgstr "Засів надовго"
#: ../src/AchievementList.cpp:51
-#, fuzzy
msgid "Play Me and My Shadow for more than 2 hours."
-msgstr "Spill Jeg og skyggen min i mer enn to timer."
+msgstr "Грати у Me and My Shadow більше двох годин."
#: ../src/AchievementList.cpp:52
msgid "Loyal fan of Me and My Shadow"
-msgstr "Lojal tilhenger av Jeg og skyggen min"
+msgstr "Вірний фанат"
#: ../src/AchievementList.cpp:52
-#, fuzzy
msgid "Play Me and My Shadow for more than 24 hours."
-msgstr "Spill Jeg og skyggen min i mer enn 24 timer."
+msgstr "Грати у Me and My Shadow довше 24 годин."
#: ../src/AchievementList.cpp:54
msgid "Constructor"
-msgstr "Byggmester"
+msgstr "Конструктор"
#: ../src/AchievementList.cpp:54
msgid "Use the level editor for more than 2 hours."
-msgstr "Bruk nivåredigereren i mer enn to timer."
+msgstr "Використовувати редактор рівнів більше двох годин."
#: ../src/AchievementList.cpp:55
msgid "The creator"
-msgstr "Skaperen"
+msgstr "Творець"
#: ../src/AchievementList.cpp:55
msgid "Use the level editor for more than 24 hours."
-msgstr "Bruk nivåredigereren i mer enn 24 timer."
+msgstr "Використовувати редактор рівнів більше 24 годин."
#: ../src/AchievementList.cpp:57
msgid "Look, cute level!"
-msgstr "Se, søtt nivå!"
+msgstr "Ой, яке ж воно файне!"
#: ../src/AchievementList.cpp:57
msgid "Create a level for the first time."
-msgstr "Lag et nivå for første gang."
+msgstr "Створити свій перший рівень."
#: ../src/AchievementList.cpp:58
msgid "The level museum"
-msgstr "Nivåmuseet"
+msgstr "Музей рівнебудівництва"
#: ../src/AchievementList.cpp:58
msgid "Create 50 levels."
-msgstr "Lag 50 nivåer."
+msgstr "Створити 50 рівнів."
#: ../src/AchievementList.cpp:60
msgid "Hello, World!"
-msgstr "Hei, verden!"
+msgstr "Hello, World!"
#: ../src/AchievementList.cpp:60
msgid "Write a script for the first time."
-msgstr "Skriv et skript for første gang."
+msgstr "Написати свій перший скрипт."
#: ../src/AchievementList.cpp:62
msgid "Frog"
-msgstr "Frosk"
+msgstr "Коник-стрибунець"
#: ../src/AchievementList.cpp:62
msgid "Jump 1000 times."
-msgstr "Hopp 1000 ganger."
+msgstr "Стрибнути 1000 разів."
#: ../src/AchievementList.cpp:64
msgid "Wanderer"
-msgstr "Vandrer"
+msgstr "Пішохід"
#: ../src/AchievementList.cpp:64
msgid "Travel 100 meters."
-msgstr "Reis 100 meter."
+msgstr "Пройти 100 метрів."
#: ../src/AchievementList.cpp:65
msgid "Runner"
-msgstr "Løper"
+msgstr "Мандрівник"
#: ../src/AchievementList.cpp:65
msgid "Travel 1 kilometer."
-msgstr "Reis én kilometer."
+msgstr "Пройти 1 кілометр."
#: ../src/AchievementList.cpp:66
msgid "Long distance runner"
-msgstr "Langdistanseløper"
+msgstr "Дальнобійник"
#: ../src/AchievementList.cpp:66
msgid "Travel 10 kilometers."
-msgstr "Reis ti kilometer."
+msgstr "Пройти 10 кілометрів."
#: ../src/AchievementList.cpp:67
msgid "Marathon runner"
-msgstr "Maratonløper"
+msgstr "Марафонець"
#: ../src/AchievementList.cpp:67
msgid "Travel 42,195 meters."
-msgstr "Reis 42195 meter"
+msgstr "Пройти 42195 метрів."
#: ../src/AchievementList.cpp:69
msgid "Be careful!"
-msgstr "Vær forsiktig."
+msgstr "Обережно!"
#: ../src/AchievementList.cpp:69
msgid "Die for the first time."
-msgstr "Dø for første gang."
+msgstr "Вмерти вперше."
#: ../src/AchievementList.cpp:70
-#, fuzzy
msgid "It doesn't matter..."
-msgstr "Det gjør ikke noe…"
+msgstr "Я вже звикаю..."
#: ../src/AchievementList.cpp:70
msgid "Die 50 times."
-msgstr "Dø 50 ganger."
+msgstr "Померти 50 разів."
#: ../src/AchievementList.cpp:71
msgid "Expert of trial and error"
-msgstr "Ekspert på prøving og feiling"
+msgstr "Досвідчений мрець"
#: ../src/AchievementList.cpp:71
msgid "Die 1000 times."
-msgstr "Dø 1000 ganger."
+msgstr "Померти 1000 разів."
#: ../src/AchievementList.cpp:73
msgid "Keep an eye for moving blocks!"
-msgstr "Hold øye med bevegelige blokker!"
+msgstr "Стережись рухомих блоків!"
#: ../src/AchievementList.cpp:73
msgid "Get squashed for the first time."
-msgstr "Bli most for første gang."
+msgstr "Бути розчавленим уперше."
#: ../src/AchievementList.cpp:74
msgid "Potato masher"
-msgstr "Potetstamper"
+msgstr "Коцюрба"
#: ../src/AchievementList.cpp:74
msgid "Get squashed 50 times."
-msgstr "Bli most 50 ganger."
+msgstr "Бути розчавленим 50 разів."
#: ../src/AchievementList.cpp:76
msgid "Double kill"
-msgstr "Dobbeltdrap"
+msgstr "За двома зайцями"
#: ../src/AchievementList.cpp:76
-#, fuzzy
msgid "Get both the player and the shadow dead."
-msgstr "Få både karakteren din og skyggen dens drept."
+msgstr "І гравець і тінь померли."
#: ../src/AchievementList.cpp:78
msgid "Bad luck"
-msgstr "Uflaks"
+msgstr "От, халепа"
#: ../src/AchievementList.cpp:78
msgid "Die 5 times in under 5 seconds."
-msgstr "Dø fem ganger på under fem sekunder."
+msgstr "Померти 5 разів менш ніж за 5 секунд."
#: ../src/AchievementList.cpp:79
msgid "This level is too dangerous"
-msgstr "Dette nivået er for farlig"
+msgstr "Хай йому грець"
#: ../src/AchievementList.cpp:79
msgid "Die 10 times in under 5 seconds."
-msgstr "Dø ti ganger på under fem sekunder."
+msgstr "Померти 10 разів менш ніж за 5 секунд."
#: ../src/AchievementList.cpp:81
msgid "You forgot your friend"
-msgstr "Du glemte vennen din"
+msgstr "Друзів не лишають у біді"
#: ../src/AchievementList.cpp:81
msgid "Finish the level with the player or the shadow dead."
-msgstr "Fullfør nivået med spilleren eller skyggen dø."
+msgstr "Завершити рівень при тому, що або гравець, або тінь померли."
#: ../src/AchievementList.cpp:82
msgid "Just in time"
-msgstr "Akkurat tidsnok"
+msgstr "У самісіньке яблучко"
#: ../src/AchievementList.cpp:82
msgid "Reach the exit with the player and the shadow simultaneously."
-msgstr "Nå utgangen med spilleren og skyggen samtidig."
+msgstr "Одночасно досягнути виходу і гравцем, і тінню."
#: ../src/AchievementList.cpp:84
msgid "Recorder"
-msgstr "Opptaker"
+msgstr "Рекордсмен"
#: ../src/AchievementList.cpp:84
msgid "Record 100 times."
-msgstr "Ta opp 100 ganger."
+msgstr "Поставити рекорд 100 разів."
#: ../src/AchievementList.cpp:85
msgid "Shadowmaster"
-msgstr "Skyggemester"
+msgstr "Чемпіон"
#: ../src/AchievementList.cpp:85
msgid "Record 1000 times."
-msgstr "Ta opp 1000 ganger."
+msgstr "Поставити рекорд 1000 разів."
#: ../src/AchievementList.cpp:87
msgid "Switch puller"
-msgstr "Brytervender"
+msgstr "Вмикач вимикачів"
#: ../src/AchievementList.cpp:87
msgid "Pull the switch 100 times."
-msgstr "Vend bryteren 100 ganger."
+msgstr "Увімкнути вимикач 100 разів."
#: ../src/AchievementList.cpp:88
msgid "The switch is broken!"
-msgstr "Bryteren er kaputt!"
+msgstr "Псувач вимикачів"
#: ../src/AchievementList.cpp:88
msgid "Pull the switch 1000 times."
-msgstr "Vend bryteren 1000 ganger."
+msgstr "Увімкнути вимикач 1000 разів."
#: ../src/AchievementList.cpp:90
-#, fuzzy
msgid "Swapper"
-msgstr "Stedveksler"
+msgstr "Ся міняю"
#: ../src/AchievementList.cpp:90
msgid "Swap 100 times."
-msgstr "Bytt sted 100 ganger."
+msgstr "Помінятися 100 разів."
#: ../src/AchievementList.cpp:91
-#, fuzzy
msgid "Player to shadow to player to shadow..."
-msgstr "Spiller til skygge til spiller til skygge…"
+msgstr "Туди-сюдитель"
#: ../src/AchievementList.cpp:91
-#, fuzzy
msgid "Swap 1000 times."
-msgstr "Bytt sted 1000 ganger."
+msgstr "Помінятися 1000 разів."
#: ../src/AchievementList.cpp:93
msgid "Play it save"
-msgstr "Ro det i land"
+msgstr "Добре зберігся"
#: ../src/AchievementList.cpp:93
msgid "Save 1000 times."
-msgstr "Lagre 1000 ganger."
+msgstr "Зберегти гру 1000 разів."
#: ../src/AchievementList.cpp:94
msgid "This game is too hard"
-msgstr "Dette spilet er for vanskelig"
+msgstr "Ох і важко ж"
#: ../src/AchievementList.cpp:94
msgid "Load the game 1000 times."
-msgstr "Last spillet 1000 ganger."
+msgstr "Завантажити гру 1000 разів."
#: ../src/AchievementList.cpp:96
msgid "No, thanks"
-msgstr "Nei takk"
+msgstr "Ні, дякую"
#: ../src/AchievementList.cpp:96
msgid "Complete a level with checkpoint, but without saving."
-msgstr "Fullfør et nivå med sjekkpunkt, men uten å lagre."
+msgstr "Пройти рівень із чекпоінтом, але без збережень гри."
#: ../src/AchievementList.cpp:98
msgid "Panic save"
-msgstr "Panikklagring"
+msgstr "Самозбережник"
#: ../src/AchievementList.cpp:98
msgid "Save twice in 1 second."
-msgstr "Lagre to ganger på ett sekund"
+msgstr "Зберегти гру двічі за секунду."
#: ../src/AchievementList.cpp:99
msgid "Panic load"
-msgstr "Panikklasting"
+msgstr "Панічне завантаження"
#: ../src/AchievementList.cpp:99
msgid "Load twice in 1 second."
-msgstr "Last inn to ganger på ett sekund."
+msgstr "Завантажити гру двічі за секунду."
#: ../src/AchievementList.cpp:101
msgid "Bad saving position"
-msgstr "Dårlig lagringsposisjon"
+msgstr "Кепське місце для збереження"
#: ../src/AchievementList.cpp:101
msgid "Load the game and die within 1 second."
-msgstr "Last spillet og dø på mindre enn ett sekund."
+msgstr "Завантажити гру й померти протягом одної секунди."
#: ../src/AchievementList.cpp:102
msgid "This level is too hard"
-msgstr "Dette nivået er for vanskelig"
+msgstr "Заважкий рівень"
#: ../src/AchievementList.cpp:102
msgid "Load the same save and die 100 times."
-msgstr "Last samme lagring og dø 100 ganger."
+msgstr "Завантажити гру і померти 100 разів."
#: ../src/AchievementList.cpp:104
msgid "Quick swap"
-msgstr "Raskt bytte"
+msgstr "Швидка зміна"
#: ../src/AchievementList.cpp:104
msgid "Swap twice in under a second."
-msgstr "Bytt sted to ganger på under ett sekund."
+msgstr "Помінятися двічі на секунду."
#: ../src/AchievementList.cpp:107
msgid "Horizontal confusion"
-msgstr "Vannrett forvirring"
+msgstr "Забув де право, а де ліво"
#: ../src/AchievementList.cpp:107
-#, fuzzy
msgid "Press left and right simultaneously."
-msgstr "Trykk ← og → samtidig."
+msgstr "Натиснути ліворуч і праворуч одночасно."
#: ../src/AchievementList.cpp:109
msgid "Cheater"
-msgstr "Jukser"
+msgstr "Нечемний"
#: ../src/AchievementList.cpp:109
msgid "Cheat in game."
-msgstr "Juks i spillet."
+msgstr "Змухлювати у грі."
#: ../src/AchievementList.cpp:111
msgid "Programmer"
-msgstr "Programmerer"
+msgstr "Тижпрограміст"
#: ../src/AchievementList.cpp:111
msgid "Play the development version of Me and My Shadow."
-msgstr "Spill utviklingsversjonen av Jeg og skyggen min."
+msgstr "Грати у версію у розробці."
#: ../src/Addons.cpp:44 ../src/LevelPackManager.cpp:108
msgid "Levels"
-msgstr "Nivå"
+msgstr "Рівні"
#: ../src/Addons.cpp:44
-#, fuzzy
msgid "Single level which usually contain demanding puzzles"
-msgstr "Enkeltnivå som vanligvis inneholder utfordrende gåter"
+msgstr "Один рівень, що зазвичай містить головоломки"
#: ../src/Addons.cpp:45
msgid "Levelpacks"
-msgstr "Nivåpakker"
+msgstr "Збірка рівнів"
#: ../src/Addons.cpp:45
msgid "Collection of levels with the same author or style"
-msgstr "Samling av nivåer laget av samme utvikler eller i samme stil"
+msgstr "Колекція рівнів від одного автора чи із подібним стилем"
#: ../src/Addons.cpp:46
msgid "Themes"
-msgstr "Drakter"
+msgstr "Оформлення"
#: ../src/Addons.cpp:46
msgid "Give every block and background a new look and feel"
-msgstr "Gi hver blokk og bakgrunn nytt utseende og oppførsel"
+msgstr "Надати кожному блоку й тлу новий вигляд і відчуття"
#: ../src/Addons.cpp:55 ../src/TitleMenu.cpp:46
msgid "Addons"
-msgstr "Programtillegg"
+msgstr "Застосунки"
#: ../src/Addons.cpp:87
msgid "Unable to initialize addon menu:"
-msgstr "Kunne ikke starte programtilleggsmeny:"
+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 "Tilbake"
+msgstr "Повернутися"
#: ../src/Addons.cpp:169
-#, fuzzy
msgid "ERROR: unable to download addons file!"
-msgstr "FEIL: Kunne ikke laste programtilleggsfil!"
+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 "FEIL: Kunne ikke laste inn addon_list-fil!"
+msgstr "ПОМИЛКА: неможливо завантажити файл addon_list!"
#: ../src/Addons.cpp:193
msgid "ERROR: Invalid file format of addons file!"
-msgstr "FEIL: Ugyldig filformat for programtilleggsfil!"
+msgstr "ПОМИЛКА: Невірний формат файлу застосунку!"
#: ../src/Addons.cpp:205
-#, fuzzy
msgid "ERROR: Addon list version is unsupported!"
-msgstr "FEIL: Programtilleggsversjonen støttes ikke!"
+msgstr "ПОМИЛКА: Не підтримується версія списку застосунків!"
-# TRANSLATORS: installed_addons is the name of a file and should not be translated.
+# TRANSLATORS: installed_addons is the name of a file and should not be
+# translated.
#: ../src/Addons.cpp:226
-#, fuzzy
msgid "ERROR: Unable to create the installed_addons file."
-msgstr "FEIL: Kunne ikke opprette programtilleggsfil for installasjoner."
+msgstr "ПОМИЛКА: Неможливо створити файл installed_addons."
#: ../src/Addons.cpp:238
msgid "ERROR: Invalid file format of the installed_addons!"
-msgstr "FEIL: Ugyldig filformat for programtilleggsfil for installasjoner."
+msgstr "ПОМИЛКА: Невірний формат файлу installed_addons!"
# TRANSLATORS: indicates the author of an addon.
#: ../src/Addons.cpp:389 ../src/Addons.cpp:621
#, c-format
msgid "by %s"
-msgstr "av %s"
+msgstr "від %s"
#: ../src/Addons.cpp:397
msgid "Installed"
-msgstr "Installert"
+msgstr "Установлено"
#: ../src/Addons.cpp:402
-#, fuzzy
msgid "Updatable"
-msgstr "Oppdaterbar"
+msgstr "Оновити"
#: ../src/Addons.cpp:412
msgid "Not installed"
-msgstr "Ikke installert"
+msgstr "Не встановлено"
#: ../src/Addons.cpp:625
#, c-format
msgid "Version: %d\n"
-msgstr "Versjon: %d\n"
+msgstr "Версія: %d\n"
#: ../src/Addons.cpp:627
#, c-format
msgid "Installed version: %d\n"
-msgstr "Installert versjon: %d\n"
+msgstr "Установлена версія: %d\n"
#: ../src/Addons.cpp:630
#, c-format
msgid "License: %s\n"
-msgstr "Lisens: %s\n"
+msgstr "Ліцензія: %s\n"
#: ../src/Addons.cpp:633
#, c-format
msgid "Website: %s\n"
-msgstr "Nettside: %s\n"
+msgstr "Інтернет-сторінка: %s\n"
#: ../src/Addons.cpp:637
msgid "(No descriptions provided)"
-msgstr "(Ingen beskrivelser medfølger)"
+msgstr "(Немає опису)"
#: ../src/Addons.cpp:657 ../src/Addons.cpp:684
msgid "Remove"
-msgstr "Fjern"
+msgstr "Видалити"
#: ../src/Addons.cpp:673
-#, fuzzy
msgid "Update"
-msgstr "Oppdater"
+msgstr "Оновити"
#: ../src/Addons.cpp:679
msgid "Install"
-msgstr "Installer"
+msgstr "Установити"
#: ../src/Addons.cpp:774
#, c-format
msgid "This addon can't be removed because it's needed by %s."
-msgstr "Dette programtillegget kan ikke fjernes fordi det trengs av %s."
+msgstr "Цей застосунок неможливо видалити, оскільки його потребує %s."
#: ../src/Addons.cpp:774 ../src/Addons.cpp:1051
msgid "Dependency"
-msgstr "Avhengighet"
+msgstr "Залежить"
#: ../src/Addons.cpp:803
#, c-format
msgid "WARNING: File '%s' appears to have been removed already."
-msgstr "ADVARSEL: Filen \"%s\" later til å ha blitt fjernet allerede."
+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 "Programtilleggsfeil"
+msgstr "Помилка застосунку"
#: ../src/Addons.cpp:810
#, c-format
msgid "ERROR: Unable to remove file '%s'!"
-msgstr "FEIL: Kunne ikke fjerne filen \"%s\"."
+msgstr "ПОМИЛКА: Неможливо видалити файл '%s'!"
#: ../src/Addons.cpp:818
#, c-format
msgid "WARNING: Directory '%s' appears to have been removed already."
-msgstr "ADVARSEL: Mappen \"%s\" later til å ha blitt fjernet allerede."
+msgstr "УВАГА: Схоже, що директорія '%s' вже була видалена."
#: ../src/Addons.cpp:825
#, c-format
msgid "ERROR: Unable to remove directory '%s'!"
-msgstr "FEIL: Kunne ikke fjerne mappen \"%s\"."
+msgstr "ПОМИЛКА: Неможливо видалити директорію '%s'!"
#: ../src/Addons.cpp:834
#, c-format
msgid "WARNING: Level '%s' appears to have been removed already."
-msgstr "ADVARSEL: Nivået \"%s\" later til å ha blitt fjernet allerede."
+msgstr "УВАГА: Схоже, що рівень '%s' вже був видалений."
#: ../src/Addons.cpp:840
#, c-format
msgid "ERROR: Unable to remove level '%s'!"
-msgstr "FEIL: Kunne ikke fjerne nivået \"%s\"!"
+msgstr "ПОМИЛКА: Неможливо видалити рівень '%s'!"
#: ../src/Addons.cpp:859
#, c-format
msgid "WARNING: Levelpack directory '%s' appears to have been removed already."
-msgstr "ADVARSEL: Nivåpakkemappen \"%s\" later til å ha blitt fjernet allerede."
+msgstr "УВАГА: Схоже, що директорія збірки рівнів '%s' вже була видалена."
#: ../src/Addons.cpp:866
#, c-format
msgid "ERROR: Unable to remove levelpack directory '%s'!"
-msgstr "FEIL: Kunne ikke fjerne nivåpakkemappen \"%s\"."
+msgstr "ПОМИЛКА: Неможливо створити директорію збірки рівнів '%s'!"
#: ../src/Addons.cpp:893
#, c-format
msgid "ERROR: Unable to download addon file %s."
-msgstr "FEIL: Kunne ikke laste ned programtilleggsfilen %s."
+msgstr "ПОМИЛКА: Неможливо зкачати файл застосунку %s."
#: ../src/Addons.cpp:900
#, c-format
msgid "ERROR: Unable to extract addon file %s."
-msgstr "FEIL: Kunne ikke pakke ut programtilleggsfilen %s."
+msgstr "ПОМИЛКА: Неможливо розпакувати файл застосунку %s."
#: ../src/Addons.cpp:907
msgid "ERROR: Addon is missing metadata!"
-msgstr "FEIL: Programtillegget mangler metadata!"
+msgstr "ПОМИЛКА: Застосунок не має метаданих!"
#: ../src/Addons.cpp:918
msgid "ERROR: Invalid file format for metadata file!"
-msgstr "FEIL: Ugyldig filformat for metadatafil!"
+msgstr "ПОМИЛКА: Невірний формат файлу метаданих!"
#: ../src/Addons.cpp:947
#, c-format
msgid "WARNING: File '%s' already exists, addon may be broken or not working!"
-msgstr ""
-"ADVARSEL: Filen \"%s\" finnes allerede, programtillegget kan være skadet "
-"eller ha sluttet å fungere."
+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 ""
-"ADVARSEL: Kunne ikke kopiere filen \"%s\" til \"%s\" programtillegget kan "
-"være skadet eller ha sluttet å virke."
+msgstr "УВАГА: Неможливо скопіювати файл '%s' до '%s', застосунок може бути пошкодженим або непрацездатним!"
#: ../src/Addons.cpp:962
#, c-format
msgid ""
"WARNING: Destination directory '%s' already exists, addon may be broken or "
"not working!"
-msgstr ""
-"ADVARSEL: Målmappen \"%s\" finnes allerede, programtillegget kan være "
-"skadet, eller ha sluttet å virke."
+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 ""
-"ADVARSEL: Kunne ikke flytte mappen \"%s\" til \"%s\", programtillegget kan "
-"være skadet, eller ha sluttet å virke."
+msgstr "УВАГА: Неможливо перемістити директорію '%s' до '%s', застосунок може бути пошкодженим або непрацездатним!"
#: ../src/Addons.cpp:981
#, c-format
msgid "WARNING: Level '%s' already exists, addon may be broken or not working!"
-msgstr ""
-"ADVARSEL: Nivået \"%s\" finnes allerede, programtillegget kan være skadet, "
-"eller ha sluttet å virke."
+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 ""
-"ADVARSEL: Kunne ikke kopiere nivået \"%s\" til \"%s\", programtillegget kan "
-"være skadet, eller ha sluttet å virke."
+msgstr "УВАГА: Неможливо скопіювати рівень '%s' до '%s', застосунок може бути пошкодженим або непрацездатним!"
#: ../src/Addons.cpp:1008
#, c-format
msgid ""
"WARNING: Levelpack directory '%s' already exists, addon may be broken or not "
"working!"
-msgstr ""
-"ADVARSEL: Nivåpakkemappen \"%s\" finnes allerede, programtillegget kan være "
-"skadet, eller ha sluttet å virke."
+msgstr "УВАГА: Директорія зі збіркою рівнів '%s' вже існує, застосунок може бути пошкодженим або непрацездатним!"
#: ../src/Addons.cpp:1044
#, c-format
msgid "ERROR: Addon requires another addon (%s) which can't be found!"
-msgstr ""
-"FEIL: Programtillegget krever et annet programtillegg (%s) som ikke ble "
-"funnet."
+msgstr "ПОМИЛКА: Застосунок потребує інший застосунок (%s), який не вдалося знайти!"
#: ../src/Addons.cpp:1051
#, c-format
msgid "The addon %s is needed and will be installed now."
-msgstr "Programtillegget %s er nødvendig, og vil bli installert nå."
+msgstr "Потрібний застосунок %s буде зараз установлено."
#: ../src/Block.cpp:822 ../src/LevelEditor.cpp:265
msgid "On"
-msgstr "På"
+msgstr "Увімк."
#: ../src/Block.cpp:823 ../src/LevelEditor.cpp:266
msgid "Off"
-msgstr "Av"
+msgstr "Вимк."
#: ../src/CommandManager.cpp:41
#, c-format
msgid "Undo %s"
-msgstr "Angre %s"
+msgstr "Відмінити %s"
#: ../src/CommandManager.cpp:43
msgid "Can't undo"
-msgstr "Kan ikke angre"
+msgstr "Неможливо відмінити"
#: ../src/CommandManager.cpp:49
#, c-format
msgid "Redo %s"
-msgstr "Gjenta %s"
+msgstr "Повернути %s"
#: ../src/CommandManager.cpp:51
msgid "Can't redo"
-msgstr "Kan ikke gjenta"
+msgstr "Неможливо повернути"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:190
msgid "Resize level"
-msgstr "Endre nivåstørrelse"
+msgstr "Змінити розмір рівня"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:807
msgid "Modify level property"
-msgstr "Endre nivåegenskap"
+msgstr "Змінити властивості рівня"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:919
#, c-format
msgid "Add scenery layer %s"
-msgstr "Legg til omgivelseslag %s"
+msgstr "Додати фоновий шар %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:921
#, c-format
msgid "Delete scenery layer %s"
-msgstr "Slett omgivelseslag %s"
+msgstr "Видалити фоновий шар %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/Commands.cpp:951
#, c-format
msgid "Modify the property of scenery layer %s"
-msgstr "Endre egenskapene på omgivelseslaget %s"
+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] "Flytt %d objekter fra lag %s til lag %s"
-msgstr[1] "Flytt %d objekter fra lag %s til lag %s"
+msgstr[0] "Перемістити %d об'єкт із шару %s у шар %s"
+msgstr[1] "Перемістити %d об'єкти із шару %s у шар %s"
+msgstr[2] "Перемістити %d об'єктів із шару %s у шар %s"
+msgstr[3] "Перемістити %d об'єктів із шару %s у шар %s"
#: ../src/CreditsMenu.cpp:35 ../src/TitleMenu.cpp:53
msgid "Credits"
-msgstr "Bidragsytere"
+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 "knewave"
+msgstr "DejaVuSansCondensed-Oblique"
# TRANSLATORS: Font used for normal text:
-# - Use "Blokletters-Viltstift" for languages using Latin and Latin-derived alphabets
+# - 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"
+msgstr "DejaVuSansCondensed-Oblique"
#: ../src/Functions.cpp:674
-#, fuzzy
msgid "Loading..."
-msgstr "Laster…"
+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 "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 "Avbryt"
+msgstr "Відміна"
#: ../src/Functions.cpp:1248
msgid "Abort"
-msgstr "Angre"
+msgstr "Перервати"
#: ../src/Functions.cpp:1249 ../src/Functions.cpp:1265
msgid "Retry"
-msgstr "Prøv igjen"
+msgstr "Спробувати знову"
#: ../src/Functions.cpp:1250
msgid "Ignore"
-msgstr "Ignorer"
+msgstr "Ігнорувати"
#: ../src/Functions.cpp:1254 ../src/Functions.cpp:1260
msgid "Yes"
-msgstr "Ja"
+msgstr "Так"
#: ../src/Functions.cpp:1255 ../src/Functions.cpp:1261
msgid "No"
-msgstr "Nei"
+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 "NIvå %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 "Trykk %s-tasten for å lagre spillet."
+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, fuzzy
+#, c-format
msgid "Press %s key to swap the position of player and shadow."
-msgstr "Trykk %s-tasten for å bytte sted mellom karakteren og skyggen."
+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 "Trykk %s-tasten for å aktivere bryteren."
+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 "Trykk %s-tasten for å teleportere."
+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 ""
-"Trykk %s for å starte nåværende nivå igjen, eller trykk %s for å laste inn "
-"spillet."
+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 "Trykk %s for å starte nåværende nivå igjen."
+msgstr "Натисніть клавішу %s, щоб перезапустити поточний рівень."
#: ../src/Game.cpp:996
msgid "Your shadow has died."
-msgstr "Skyggen din har dødd."
+msgstr "Ваша тінь померла."
#: ../src/Game.cpp:1052
#, c-format
msgid "%d recording"
msgid_plural "%d recordings"
-msgstr[0] "%d opptak"
-msgstr[1] "%d opptak"
+msgstr[0] "%d запис"
+msgstr[1] "%d записи"
+msgstr[2] "%d записів"
+msgstr[3] "%d записів"
#: ../src/Game.cpp:1224
msgid "You've finished:"
-msgstr "Du har fullført:"
+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 "Tid: %-.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 "Beste tid: %-.2fs"
+msgstr "Найкращий час: %-.2fs"
#: ../src/Game.cpp:1311
#, c-format
msgid "Target time: %-.2fs"
-msgstr "Mål: %-.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 "Opptak: %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 "Beste opptak: %d"
+msgstr "Найкращі записи: %d"
#: ../src/Game.cpp:1350
#, c-format
msgid "Target recordings: %d"
-msgstr "Mål-opptak: %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 "Du har gjort deg fortjent til %s-medaljen"
+msgstr "Ви отримали %s медаль"
#: ../src/Game.cpp:1363
msgid "GOLD"
-msgstr "GULL"
+msgstr "ЗОЛОТУ"
#: ../src/Game.cpp:1363
msgid "SILVER"
-msgstr "SØLV"
+msgstr "СРІБНУ"
#: ../src/Game.cpp:1363
msgid "BRONZE"
-msgstr "BRONSE"
+msgstr "БРОНЗОВУ"
# TRANSLATORS: used as return to the level selector menu
#: ../src/Game.cpp:1390
msgid "Menu"
-msgstr "Meny"
+msgstr "Меню"
# TRANSLATORS: used as restart level
#: ../src/Game.cpp:1397 ../src/InputManager.cpp:47
msgid "Restart"
-msgstr "Start igjen"
+msgstr "Перезапустити"
# TRANSLATORS: used as next level
#: ../src/Game.cpp:1404
msgid "Next"
-msgstr "Neste"
+msgstr "Далі"
#: ../src/Game.cpp:1430
msgid "Game replay is done."
-msgstr "Spillopptaket er ferdigspilt."
+msgstr "Відтворення завершене."
#: ../src/Game.cpp:1430
msgid "Game Replay"
-msgstr "Spillopptaksavspilling"
+msgstr "Відтворення гри"
#: ../src/Game.cpp:1767 ../src/Game.cpp:1769
msgid "Congratulations"
-msgstr "Gratulerer"
+msgstr "Вітання"
#: ../src/Game.cpp:1769
msgid "You have finished the levelpack!"
-msgstr "Du har fullført nivåpakken."
+msgstr "Ви закінчили цю збірку рівнів!"
#: ../src/InputManager.cpp:46
msgid "Up (in menu)"
-msgstr "Opp (i menyen)"
+msgstr "Вгору (в меню)"
#: ../src/InputManager.cpp:46
msgid "Down (in menu)"
-msgstr "Ned (i menyen)"
+msgstr "Вниз (в меню)"
#: ../src/InputManager.cpp:46
msgid "Left"
-msgstr "Venstre"
+msgstr "Ліворуч"
#: ../src/InputManager.cpp:46
msgid "Right"
-msgstr "Høyre"
+msgstr "Праворуч"
#: ../src/InputManager.cpp:46
msgid "Jump"
-msgstr "Hopp"
+msgstr "Стрибок"
#: ../src/InputManager.cpp:46
msgid "Action"
-msgstr "Handling"
+msgstr "Дія"
#: ../src/InputManager.cpp:46
msgid "Space (Record)"
-msgstr "Mellomrom (ta opp)"
+msgstr "Пробіл (Запис)"
#: ../src/InputManager.cpp:46
msgid "Cancel recording"
-msgstr "Avbryt opptak"
+msgstr "Відмінити запис"
#: ../src/InputManager.cpp:47
msgid "Escape"
-msgstr "Esc"
+msgstr "Вихід"
#: ../src/InputManager.cpp:47
msgid "Tab (View shadow/Level prop.)"
-msgstr "Tab (vis skygge/nivåegenskap)"
+msgstr "Tab (Див. тінь/Налашт. рівня)"
#: ../src/InputManager.cpp:47
msgid "Save game (in editor)"
-msgstr "Lagre spill (i redigerer)"
+msgstr "Зберегти гру (в редакторі)"
#: ../src/InputManager.cpp:47
msgid "Load game"
-msgstr "Last inn spill"
+msgstr "Завантажити гру"
#: ../src/InputManager.cpp:47
msgid "Swap (in editor)"
-msgstr "Veksle (i redigerer)"
+msgstr "Помінятися (в редакторі)"
#: ../src/InputManager.cpp:48
msgid "Teleport (in editor)"
-msgstr "Teleporter (i redigerer)"
+msgstr "Телепортація (в редакторі)"
#: ../src/InputManager.cpp:48
msgid "Suicide (in editor)"
-msgstr "Selvmord (i redigerer)"
+msgstr "Самогубство (в редакторі)"
#: ../src/InputManager.cpp:48
-#, fuzzy
msgid "Shift (in editor)"
-msgstr "Bytt (i redigerer)"
+msgstr "Зсув (в редакторі)"
#: ../src/InputManager.cpp:48
msgid "Next block type (in Editor)"
-msgstr "Neste blokktype (i redigerer)"
+msgstr "Наступний тип блока (в редакторі)"
#: ../src/InputManager.cpp:49
msgid "Previous block type (in editor)"
-msgstr "Forrige blokktype (i redigerer)"
+msgstr "Попередній тип блока (в редакторі)"
#: ../src/InputManager.cpp:49
msgid "Select (in menu)"
-msgstr "Velg (i meny)"
+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 "(Tast %d)"
+msgstr "(Клавіша %d)"
#: ../src/InputManager.cpp:163
#, c-format
msgid "Joystick axis %d %s"
-msgstr "Spillstikkeakse %d %s"
+msgstr "Вісь джойстика %d %s"
#: ../src/InputManager.cpp:166
#, c-format
msgid "Joystick button %d"
-msgstr "Spillstikkeknapp %d"
+msgstr "Кнопка джойстика %d"
#: ../src/InputManager.cpp:171
-#, c-format, fuzzy
+#, c-format
msgid "Joystick hat %d left"
-msgstr "Spillstikkepinne %d venstre"
+msgstr "Джойстик %d ліворуч"
#: ../src/InputManager.cpp:174
-#, c-format, fuzzy
+#, c-format
msgid "Joystick hat %d right"
-msgstr "Spillstikkepinne %d høyre"
+msgstr "Джойстик %d праворуч"
#: ../src/InputManager.cpp:177
-#, c-format, fuzzy
+#, c-format
msgid "Joystick hat %d up"
-msgstr "Spillstikkepinne %d oppover"
+msgstr "Джойстик %d вгору"
#: ../src/InputManager.cpp:180
-#, c-format, fuzzy
+#, c-format
msgid "Joystick hat %d down"
-msgstr "Spillstikkepinne %d nedover"
+msgstr "Джойстик %d вниз"
# TRANSLAOTRS: This is used when the JOYSTICK_HAT value is invalid.
#: ../src/InputManager.cpp:185
-#, c-format, fuzzy
+#, c-format
msgid "Joystick hat %d %d"
-msgstr "Spillstikkepinne %d %d"
+msgstr "Джойстик %d %d"
#: ../src/InputManager.cpp:202
msgid "OR"
-msgstr "ELLER"
+msgstr "АБО"
#: ../src/InputManager.cpp:416
msgid "Select an item and press a key to change it."
-msgstr "Velg et element og trykk en tast for å endre det."
+msgstr "Оберіть елемент і натисніть клавішу, щоб змінити його."
#: ../src/InputManager.cpp:419
-#, fuzzy
msgid "Press backspace to clear the selected item."
-msgstr "Trykk rettetast for å tømme valgt element."
+msgstr "Натисніть backspace, щоб очистити обраний елемент."
#: ../src/LevelEditor.cpp:56
msgid "Block"
-msgstr "Blokk"
+msgstr "Блок"
#: ../src/LevelEditor.cpp:56
msgid "Player Start"
-msgstr "Spillerstart"
+msgstr "Тут починає гравець"
#: ../src/LevelEditor.cpp:56
msgid "Shadow Start"
-msgstr "Skyggestart"
+msgstr "Тут починає тінь"
#: ../src/LevelEditor.cpp:57
msgid "Exit"
-msgstr "Avslutt"
+msgstr "Вихід"
#: ../src/LevelEditor.cpp:57
msgid "Shadow Block"
-msgstr "Skyggeblokk"
+msgstr "Блок для тіні"
#: ../src/LevelEditor.cpp:57
msgid "Spikes"
-msgstr "Tagger"
+msgstr "Шипи"
#: ../src/LevelEditor.cpp:58
msgid "Checkpoint"
-msgstr "Sjekkpunkt"
+msgstr "Чекпоінт"
#: ../src/LevelEditor.cpp:58 ../src/LevelEditSelect.cpp:312
-#, fuzzy
msgid "Swap"
-msgstr "Bytt"
+msgstr "Помінятися"
#: ../src/LevelEditor.cpp:58
msgid "Fragile"
-msgstr "Skjør"
+msgstr "Крихкий"
#: ../src/LevelEditor.cpp:59
msgid "Moving Block"
-msgstr "Bevegende blokk"
+msgstr "Рухомий блок"
#: ../src/LevelEditor.cpp:59
msgid "Moving Shadow Block"
-msgstr "Bevegende skyggeblokk"
+msgstr "Рухомий блок для тіні"
#: ../src/LevelEditor.cpp:59
msgid "Moving Spikes"
-msgstr "Bevegende tagger"
+msgstr "Рухомі шипи"
#: ../src/LevelEditor.cpp:60
msgid "Teleporter"
-msgstr "Teleportør"
+msgstr "Телепортер"
#: ../src/LevelEditor.cpp:60
msgid "Button"
-msgstr "Knapp"
+msgstr "Кнопка"
#: ../src/LevelEditor.cpp:60
msgid "Switch"
-msgstr "Bryter"
+msgstr "Вимикач"
#: ../src/LevelEditor.cpp:61
msgid "Conveyor Belt"
-msgstr "Samlebånd"
+msgstr "Конвеєр"
#: ../src/LevelEditor.cpp:61
msgid "Shadow Conveyor Belt"
-msgstr "Skyggesamlebånd"
+msgstr "Конвеєр для тіні"
#: ../src/LevelEditor.cpp:61
msgid "Notification Block"
-msgstr "Merknadsblokk"
+msgstr "Інформаційний блок"
#: ../src/LevelEditor.cpp:61
msgid "Collectable"
-msgstr "Sankbar"
+msgstr "Колекційний предмет"
#: ../src/LevelEditor.cpp:61
msgid "Pushable"
-msgstr "Trykkbar"
+msgstr "Блок, що можна штовхати"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:310
msgid "Select"
-msgstr "Velg"
+msgstr "Обрати"
#: ../src/LevelEditor.cpp:65
msgid "Add"
-msgstr "Legg til"
+msgstr "Додати"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:311
msgid "Delete"
-msgstr "Slett"
+msgstr "Видалити"
#: ../src/LevelEditor.cpp:65 ../src/LevelPlaySelect.cpp:66
#: ../src/TitleMenu.cpp:43
msgid "Play"
-msgstr "Spill"
+msgstr "Грати"
#: ../src/LevelEditor.cpp:65 ../src/LevelEditor.cpp:2852
msgid "Level settings"
-msgstr "Nivåinnstillinger"
+msgstr "Налаштування рівня"
#: ../src/LevelEditor.cpp:65
msgid "Save level"
-msgstr "Lagre nivå"
+msgstr "Зберегти рівень"
#: ../src/LevelEditor.cpp:65
msgid "Back to menu"
-msgstr "Tilbake til nivå"
+msgstr "Назад до меню"
#: ../src/LevelEditor.cpp:65
msgid "Configure"
-msgstr "Sett opp"
+msgstr "Налаштування"
#: ../src/LevelEditor.cpp:84
-#, c-format, fuzzy
+#, c-format
msgid "%s (Scenery)"
-msgstr "%s (naturutsikt)"
+msgstr "%s (Тло)"
#: ../src/LevelEditor.cpp:267
msgid "Toggle"
-msgstr "Veksle"
+msgstr "Перемикач"
#: ../src/LevelEditor.cpp:270
msgid "Complete"
-msgstr "Fullfør"
+msgstr "Завершений"
#: ../src/LevelEditor.cpp:271
msgid "One step"
-msgstr "Ett steg"
+msgstr "Один крок"
#: ../src/LevelEditor.cpp:272
msgid "Two steps"
-msgstr "To steg"
+msgstr "Два кроки"
#: ../src/LevelEditor.cpp:273
-#, fuzzy
msgid "Gone"
-msgstr "Borte"
+msgstr "Зник"
#: ../src/LevelEditor.cpp:291
msgid "Negative infinity"
-msgstr "Negativ uendelighet"
+msgstr "Мінус нескінченність"
#: ../src/LevelEditor.cpp:293
msgid "Zero"
-msgstr "Null"
+msgstr "Нуль"
#: ../src/LevelEditor.cpp:295
msgid "Level size"
-msgstr "Nivåstørrelse"
+msgstr "Розмір рівня"
#: ../src/LevelEditor.cpp:297
msgid "Positive infinity"
-msgstr "Positiv uendelighet"
+msgstr "Плюс нескінченність"
#: ../src/LevelEditor.cpp:299
msgid "Default"
-msgstr "Forvalg"
+msgstr "За замовчуванням"
#: ../src/LevelEditor.cpp:308
msgid "Deselect"
-msgstr "Fravelg"
+msgstr "Зняти вибір"
#: ../src/LevelEditor.cpp:318 ../src/LevelEditor.cpp:1136
#, c-format
msgid "Horizontal repeat start: %s"
-msgstr "Vannrett gjentagelsesstart: %s"
+msgstr "Розпочати горизонтальний повтор: %s"
#: ../src/LevelEditor.cpp:320 ../src/LevelEditor.cpp:1137
#, c-format
msgid "Horizontal repeat end: %s"
-msgstr "Vannrett gjentagelsesslutt: %s"
+msgstr "Завершити горизонтальний повтор: %s"
#: ../src/LevelEditor.cpp:322 ../src/LevelEditor.cpp:1138
#, c-format
msgid "Vertical repeat start: %s"
-msgstr "Vannrett gjentagelsesstart: %s"
+msgstr "Розпочати вертикальний повтор: %s"
#: ../src/LevelEditor.cpp:324 ../src/LevelEditor.cpp:1139
#, c-format
msgid "Vertical repeat end: %s"
-msgstr "Vannrett gjentagelsesslutt: %s"
+msgstr "Завершити вертикальний повтор: %s"
#: ../src/LevelEditor.cpp:329 ../src/LevelEditor.cpp:1150
-#, fuzzy
msgid "Custom scenery"
-msgstr "Egendefinert omgivelsesmiljø"
+msgstr "Налаштувати тло"
#: ../src/LevelEditor.cpp:335 ../src/LevelEditor.cpp:600
#: ../src/LevelEditor.cpp:602
msgid "Visible"
-msgstr "Synlig"
+msgstr "Видимий"
#: ../src/LevelEditor.cpp:344
msgid "Link"
-msgstr "Lenke"
+msgstr "Посилання"
#: ../src/LevelEditor.cpp:345
msgid "Remove Links"
-msgstr "Fjern lenker"
+msgstr "Прибрати посилання"
#: ../src/LevelEditor.cpp:349 ../src/LevelEditor.cpp:624
#: ../src/LevelEditor.cpp:626
msgid "Automatic"
-msgstr "Automatisk"
+msgstr "Авто"
#: ../src/LevelEditor.cpp:359 ../src/LevelEditor.cpp:649
#, c-format
msgid "Behavior: %s"
-msgstr "Oppførsel: %s"
+msgstr "Поведінка: %s"
#: ../src/LevelEditor.cpp:362
msgid "Path"
-msgstr "Sti"
+msgstr "Шлях"
#: ../src/LevelEditor.cpp:363
msgid "Remove Path"
-msgstr "Fjern sti"
+msgstr "Прибрати шлях"
#: ../src/LevelEditor.cpp:365 ../src/LevelEditor.cpp:371
#: ../src/LevelEditor.cpp:587 ../src/LevelEditor.cpp:589
msgid "Activated"
-msgstr "Aktivert"
+msgstr "Активовано"
#: ../src/LevelEditor.cpp:366 ../src/LevelEditor.cpp:612
#: ../src/LevelEditor.cpp:614
-#, fuzzy
msgid "Looping"
-msgstr "Sløyfe"
+msgstr "Повторювати"
#: ../src/LevelEditor.cpp:372 ../src/LevelEditor.cpp:3526
msgid "Speed"
-msgstr "Hastighet"
+msgstr "Швидкість"
#: ../src/LevelEditor.cpp:378 ../src/LevelEditor.cpp:668
#, c-format
msgid "State: %s"
-msgstr "Tilstand: %s"
+msgstr "Стан: %s"
#: ../src/LevelEditor.cpp:382 ../src/LevelEditor.cpp:3511
msgid "Message"
-msgstr "Melding"
+msgstr "Повідомлення"
#: ../src/LevelEditor.cpp:384 ../src/LevelEditor.cpp:1202
#: ../src/LevelEditor.cpp:3825
msgid "Appearance"
-msgstr "Utseende"
+msgstr "Зовнішній вигляд"
#: ../src/LevelEditor.cpp:389 ../src/LevelEditor.cpp:431
#: ../src/LevelEditor.cpp:715
msgid "Scripting"
-msgstr "Skripting"
+msgstr "Скрипти"
#: ../src/LevelEditor.cpp:402 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Background layer: %s"
-msgstr "Bakgrunnslag: %s"
+msgstr "Шар тла: %s"
#: ../src/LevelEditor.cpp:409 ../src/LevelEditor.cpp:866
#: ../src/LevelEditor.cpp:884
msgid "Blocks layer"
-msgstr "Blokklag"
+msgstr "Шар блоків"
#: ../src/LevelEditor.cpp:417 ../src/LevelEditor.cpp:867
#: ../src/LevelEditor.cpp:885
#, c-format
msgid "Foreground layer: %s"
-msgstr "Forgrunnslag: %s"
+msgstr "Шар переднього плану: %s"
#: ../src/LevelEditor.cpp:423
msgid "Add new layer"
-msgstr "Legg til nytt lag"
+msgstr "Додати новий шар"
#: ../src/LevelEditor.cpp:424
msgid "Delete selected layer"
-msgstr "Slett valgt lag"
+msgstr "Видалити обрані шари"
#: ../src/LevelEditor.cpp:425
msgid "Configure selected layer"
-msgstr "Sett opp valgt lag"
+msgstr "Налаштувати обраний шар"
#: ../src/LevelEditor.cpp:426
msgid "Move selected object to layer"
-msgstr "Flytt valgt objekt til lag"
+msgstr "Перемістити обраний об'єкт у шар"
#: ../src/LevelEditor.cpp:430 ../src/OptionsMenu.cpp:55
msgid "Settings"
-msgstr "Innstillinger"
+msgstr "Налаштування"
#: ../src/LevelEditor.cpp:463
-#, fuzzy
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 ""
-"MERK: Lagene sorteres etter navn alfabetisk.\n"
-"Laget er et bakgrunnslag hvis dets navn er < 'f'\n"
-"etter ordboksanordning, ellers er det forgrunnslaget."
+msgstr "ПРИМІТКА: шари сортуються за назвою у алфавітному порядку.\n"
+"Шар є тлом, якщо його ім'я < 'f'\n"
+"у словниковому порядку, інакше - це шар переднього плану."
#: ../src/LevelEditor.cpp:539
msgid "Notification block"
-msgstr "Merknadsblokk"
+msgstr "Інформаційний блок"
#: ../src/LevelEditor.cpp:545
msgid "Enter message here:"
-msgstr "Skriv inn melding her:"
+msgstr "Введіть повідомлення тут:"
#: ../src/LevelEditor.cpp:646
msgid "Behavior"
-msgstr "Oppførsel"
+msgstr "Поведінка"
#: ../src/LevelEditor.cpp:665
msgid "State"
-msgstr "Tilstand"
+msgstr "Стан"
#: ../src/LevelEditor.cpp:673
msgid "Conveyor belt speed"
-msgstr "Samlebåndshastighet"
+msgstr "Швидкість конвеєра"
#: ../src/LevelEditor.cpp:679
msgid "Enter speed here:"
-msgstr "Skriv inn hastighet her:"
+msgstr "Введіть швидкість:"
#: ../src/LevelEditor.cpp:690
-#, fuzzy
msgid "NOTE: 1 Speed = 0.08 block/s"
-msgstr "Merk: 1 fartsenhet = 0.08 blokk/er"
+msgstr "ПРИМ.: 1 Швидкість = 0,08 блоків/с"
#: ../src/LevelEditor.cpp:721
-#, fuzzy
msgid "Id:"
-msgstr "ID:"
+msgstr "Ід:"
#: ../src/LevelEditor.cpp:787
msgid "Level Scripting"
-msgstr "Nivåskript"
+msgstr "Скрипти рівня"
#: ../src/LevelEditor.cpp:892
msgid "Add layer"
-msgstr "Legg til lag"
+msgstr "Додати шар"
#: ../src/LevelEditor.cpp:898
msgid "Enter the layer name:"
-msgstr "Skriv inn lagnavn:"
+msgstr "Введіть назву шару:"
#: ../src/LevelEditor.cpp:943
#, c-format
msgid "Are you sure you want to delete layer '%s'?"
-msgstr "Er du sikker på at du vil slette laget \"%s\"?"
+msgstr "Ви впевнені, що хочете видалити шар '%s'?"
#: ../src/LevelEditor.cpp:944
msgid "Delete layer"
-msgstr "Slett lag"
+msgstr "Видалити шар"
#: ../src/LevelEditor.cpp:968
msgid "Layer settings"
-msgstr "Laginnstillinger"
+msgstr "Налаштувати шар"
#: ../src/LevelEditor.cpp:974
msgid "Layer name:"
-msgstr "Lagnavn:"
+msgstr "Назва шару:"
#: ../src/LevelEditor.cpp:989
msgid "Layer moving speed (1 speed = 0.8 block/s):"
-msgstr "Lagbevegelseshastighet (1 hastighet = 0,8 blokker/s):"
+msgstr "Швидкість переміщення шару (1 швидкість = 0,08 блоків/с):"
#: ../src/LevelEditor.cpp:1010
msgid "Speed of following camera:"
-msgstr "Hastighet på følgekamera:"
+msgstr "Швидкість камери:"
#: ../src/LevelEditor.cpp:1062
msgid "Move to layer"
-msgstr "Flytt til lag"
+msgstr "Перемістити у шар"
#: ../src/LevelEditor.cpp:1068
msgid "Enter the layer name (create new layer if necessary):"
-msgstr "Skriv innlagnavn (opprett nytt lag hvis nødvendig):"
+msgstr "Введіть назву шару (створити новий шар за необхідності):"
#: ../src/LevelEditor.cpp:1132
msgid "Repeat mode"
-msgstr "Gjentagelsesmodus"
+msgstr "Режим повтору"
#: ../src/LevelEditor.cpp:1156
msgid "Custom scenery:"
-msgstr "Egendefinert omgivelsesmiljø:"
+msgstr "Тло:"
#: ../src/LevelEditor.cpp:1219
msgid "(Use the default appearance for this block)"
-msgstr "(Bruk forvalgt utseende for denne blokken)"
+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 "Egendefinert omgivelsesmiljøblokk"
+msgstr "Блок тла"
#: ../src/LevelEditor.cpp:1673
msgid "Toolbox"
-msgstr "Verktøyskasse"
+msgstr "Інструменти"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Resize %s"
-msgstr "Endre størrelse på %s"
+msgstr "Змінити розмір %s"
#: ../src/LevelEditor.cpp:1705
#, c-format
msgid "Move %s"
-msgstr "Flytt %s"
+msgstr "Перемістити %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1713
#, c-format
msgid "Move %d object"
msgid_plural "Move %d objects"
-msgstr[0] "Flytt %d objekt"
-msgstr[1] "Flytt %d objekter"
+msgstr[0] "Перемістити %d об'єкт"
+msgstr[1] "Перемістити %d об'єкти"
+msgstr[2] "Перемістити %d об'єктів"
+msgstr[3] "Перемістити %d об'єктів"
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Add %s"
-msgstr "Legg til %s"
+msgstr "Додати %s"
#: ../src/LevelEditor.cpp:1721
#, c-format
msgid "Remove %s"
-msgstr "Fjern %s"
+msgstr "Видалити %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1729
#, c-format
msgid "Add %d object"
msgid_plural "Add %d objects"
-msgstr[0] "Legg til %d objekt"
-msgstr[1] "Legg til %d objekter"
+msgstr[0] "Додати %d об'єкт"
+msgstr[1] "Додати %d об'єкти"
+msgstr[2] "Додати %d об'єктів"
+msgstr[3] "Додати %d об'єктів"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1731
#, c-format
msgid "Remove %d object"
msgid_plural "Remove %d objects"
-msgstr[0] "Fjern %d objekt"
-msgstr[1] "Fjern %d objekter"
+msgstr[0] "Видалити %d об'єкт"
+msgstr[1] "Видалити %d об'єкти"
+msgstr[2] "Видалити %d об'єктів"
+msgstr[3] "Видалити %d об'єктів"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1739
#, c-format
msgid "Add path to %s"
-msgstr "Legg til sti i %s"
+msgstr "Додати шлях до %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1741
#, c-format
msgid "Remove a path point from %s"
-msgstr "Fjern stipunkt fra %s"
+msgstr "Видалити точку шляху з %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1747
#, c-format
msgid "Remove all paths from %s"
-msgstr "Fjern alle stier fra %s"
+msgstr "Видалити усі шляхи з %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1753
#, c-format
msgid "Add link from %s to %s"
-msgstr "Legg til lenke fra %s til %s"
+msgstr "Додати зв'язок між %s і %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1759
#, c-format
msgid "Remove all links from %s"
-msgstr "Fjern alle lenker fra %s"
+msgstr "Видалити усі зв'язки з %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1766
-#, fuzzy
msgid "Modify the %2 property of %1"
-msgstr "Endre %s-egenskap for %s"
+msgstr "Змінити у %1 властивість %2"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1818
#, c-format
msgid "Edit the script of %s"
-msgstr "Rediger skriptet for %s"
+msgstr "Редагувати скрипт %s"
# TRANSLATORS: Context: Undo/Redo ...
#: ../src/LevelEditor.cpp:1821
msgid "Edit the script of level"
-msgstr "Rediger nivåets skript"
+msgstr "Редагувати скрипт рівня"
#: ../src/LevelEditor.cpp:2146 ../src/LevelEditor.cpp:2226
msgid "The level has unsaved changes."
-msgstr "Nivået har ulagrede endringer."
+msgstr "На рівні є незбережені зміни."
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Are you sure you want to quit?"
-msgstr "Er du sikker på at du vil avslutte?"
+msgstr "Ви дійсно хочете вийти?"
#: ../src/LevelEditor.cpp:2150 ../src/LevelEditor.cpp:2228
msgid "Quit prompt"
-msgstr "Avslutt spørring"
+msgstr "Вихід"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
#, c-format
msgid "Level \"%s\" saved"
-msgstr "Nivået \"%s\" har blitt lagret"
+msgstr "Рівень \"%s\" збережено"
#: ../src/LevelEditor.cpp:2797 ../src/LevelEditor.cpp:2799
msgid "Saved"
-msgstr "Lagret"
+msgstr "Збережено"
#: ../src/LevelEditor.cpp:2859 ../src/LevelEditSelect.cpp:208
msgid "Name:"
-msgstr "Navn:"
+msgstr "Назва:"
#: ../src/LevelEditor.cpp:2866
msgid "Theme:"
-msgstr "Drakt:"
+msgstr "Оформлення:"
#: ../src/LevelEditor.cpp:2873
-#, fuzzy
msgid "Examples: %DATA%/themes/classic"
-msgstr "Eksempler: %DATA%/themes/classic"
+msgstr "Приклади: %DATA%/themes/classic"
#: ../src/LevelEditor.cpp:2875
msgid "or %USER%/themes/Orange"
-msgstr "eller %USER%/themes/Orange"
+msgstr "або %USER%/themes/Orange"
#: ../src/LevelEditor.cpp:2878
msgid "Music:"
-msgstr "Musikk:"
+msgstr "Музика:"
#: ../src/LevelEditor.cpp:2887
msgid "Target time (s):"
-msgstr "Måltid (s):"
+msgstr "Необхідний час (с):"
#: ../src/LevelEditor.cpp:2903
msgid "Target recordings:"
-msgstr "Målopptak:"
+msgstr "Необхідно записів:"
#: ../src/LevelEditor.cpp:2919
msgid "Restart level editor is required"
-msgstr "Omstart av nivåredigerer kreves"
+msgstr "Необхідно перезавантажити редактор рівнів"
#: ../src/LevelEditor.cpp:3679 ../src/LevelEditor.cpp:3718
#: ../src/LevelEditor.cpp:3739
msgid "Please enter a layer name."
-msgstr "Skriv inn et lagnavn."
+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 "Feil"
+msgstr "Помилка"
#: ../src/LevelEditor.cpp:3683 ../src/LevelEditor.cpp:3722
#, c-format
msgid "The layer '%s' already exists."
-msgstr "Laget \"%s\" finnes allerede."
+msgstr "Шар '%s' вже існує."
#: ../src/LevelEditor.cpp:3743
msgid "Source and destination layers are the same."
-msgstr "Kilde og mållag er de samme."
+msgstr "Вихідний та кінцевий шари співпадають."
#: ../src/LevelEditor.cpp:3760
msgid "Scenery"
-msgstr "Omgivelsesmiljø"
+msgstr "Тло"
#: ../src/LevelEditor.cpp:4190 ../src/LevelEditor.cpp:4218
#, c-format
msgid "Speed: %d = %0.2f block/s"
-msgstr "Hastighet: %d = %0.2f blokker/s"
+msgstr "Швидкість: %d = %0.2f блоків/с"
#: ../src/LevelEditor.cpp:4203
msgid "Stop at this point"
-msgstr "Stopp på dette punktet"
+msgstr "Зупинитися у цій точці"
#: ../src/LevelEditor.cpp:4208
#, c-format
msgid "Pause: %d = %0.3fs"
-msgstr "Pause: %d = %0.3fs"
+msgstr "Пауза: %d = %0.3fs"
#: ../src/LevelEditSelect.cpp:41 ../src/TitleMenu.cpp:45
msgid "Map Editor"
-msgstr "Kartredigerer"
+msgstr "Редактор карт"
#: ../src/LevelEditSelect.cpp:66
msgid "New Levelpack"
-msgstr "Ny nivåpakke"
+msgstr "Нова збірка рівнів"
#: ../src/LevelEditSelect.cpp:71
msgid "Pack Properties"
-msgstr "Pakkeegenskaper"
+msgstr "Властивості збірки"
#: ../src/LevelEditSelect.cpp:76
msgid "Remove Pack"
-msgstr "Fjern pakke"
+msgstr "Видалити збірку"
#: ../src/LevelEditSelect.cpp:81
msgid "Move Map"
-msgstr "Flytt kart"
+msgstr "Перемістити карту"
#: ../src/LevelEditSelect.cpp:89
msgid "Remove Map"
-msgstr "FJern kart"
+msgstr "Видалити карту"
#: ../src/LevelEditSelect.cpp:94
msgid "Edit Map"
-msgstr "Rediger kart"
+msgstr "Редагувати карту"
#: ../src/LevelEditSelect.cpp:205
msgid "Properties"
-msgstr "Egenskaper"
+msgstr "Властивості"
#: ../src/LevelEditSelect.cpp:217
msgid "Description:"
-msgstr "Beskrivelse:"
+msgstr "Опис:"
#: ../src/LevelEditSelect.cpp:226
msgid "Congratulation text:"
-msgstr "Gratuleringstekst:"
+msgstr "Текст привітання:"
#: ../src/LevelEditSelect.cpp:235
msgid "Music list:"
-msgstr "Musikkliste:"
+msgstr "Перелік музики:"
#: ../src/LevelEditSelect.cpp:265 ../src/LevelEditSelect.cpp:485
msgid "Add level"
-msgstr "Legg til nivå"
+msgstr "Додати рівень"
#: ../src/LevelEditSelect.cpp:268
-#, fuzzy
msgid "File name:"
-msgstr "Filnavn:"
+msgstr "Назва файлу:"
#: ../src/LevelEditSelect.cpp:293
msgid "Move level"
-msgstr "Flytt nivå"
+msgstr "Перемістити рівень"
#: ../src/LevelEditSelect.cpp:296
-#, fuzzy
msgid "Level: "
-msgstr "Nivå: "
+msgstr "Рівень: "
#: ../src/LevelEditSelect.cpp:310
msgid "Before"
-msgstr "Før"
+msgstr "До"
#: ../src/LevelEditSelect.cpp:311
msgid "After"
-msgstr "Etter"
+msgstr "Після"
#: ../src/LevelEditSelect.cpp:368 ../src/LevelPlaySelect.cpp:124
msgid "Individual levels which are not contained in any level packs"
-msgstr "Individuelle nivå som ikke finnes i noen nivåpakker"
+msgstr "Окремі рівні, що не включено до жодної збірки рівнів"
#: ../src/LevelEditSelect.cpp:577
#, c-format
msgid "Are you sure remove the level pack '%s'?"
-msgstr "Er du sikker på at du vil fjerne nivåpakken \"%s\"?"
+msgstr "Ви впевнені, що хочете видалити збірку рівнів '%s'?"
#: ../src/LevelEditSelect.cpp:577 ../src/LevelEditSelect.cpp:607
msgid "Remove prompt"
-msgstr "Fjern spørring"
+msgstr "Видалення"
#: ../src/LevelEditSelect.cpp:607
#, c-format
msgid "Are you sure remove the map '%s'?"
-msgstr "Er du sikker på at du vil fjerne kartet \"%s\"?"
+msgstr "Ви впевнені, що хочете видалити карту '%s'?"
#: ../src/LevelEditSelect.cpp:644
msgid "Levelpack name cannot be empty."
-msgstr "Nivåpakkenavn kan ikke være tomt."
+msgstr "Назва збірки рівнів не може бути порожньою."
#: ../src/LevelEditSelect.cpp:683
#, c-format
msgid "The levelpack directory '%s' already exists!"
-msgstr "Nivåpakkemappen \"%s\" finnes allerede."
+msgstr "Директорія збірки рівнів '%s' вже існує!"
#: ../src/LevelEditSelect.cpp:688
#, c-format
msgid "Unable to create levelpack directory '%s'!"
-msgstr "Kunne ikke opprette nivåpakkemappe \"%s\"."
+msgstr "Неможливо створити директорію збірки рівнів '%s'!"
#: ../src/LevelEditSelect.cpp:693
#, c-format
msgid "The levelpack file '%s' already exists!"
-msgstr "Nivåpakkefilen \"%s\" finnes allerede."
+msgstr "Файл збірки рівнів '%s' вже існує!"
#: ../src/LevelEditSelect.cpp:698
#, c-format
msgid "Unable to create levelpack file '%s'!"
-msgstr "Kunne ikke opprette nivåpakkefil \"%s\"."
+msgstr "Неможливо створити файл збірки рівнів '%s'!"
#: ../src/LevelEditSelect.cpp:758
-#, fuzzy
msgid "No file name given for the new level."
-msgstr "Inget filnavn gitt for nytt nivå."
+msgstr "Новому рівню не було призначене ім'я файлу."
#: ../src/LevelEditSelect.cpp:758
-#, fuzzy
msgid "Missing file name"
-msgstr "Mangler filnavn"
+msgstr "Відсутнє ім'я файлу"
#: ../src/LevelEditSelect.cpp:796
#, c-format
msgid "The file %s already exists."
-msgstr "Filen %s finnes allerede."
+msgstr "Файл %s вже існує."
#: ../src/LevelEditSelect.cpp:849
msgid "The entered level number isn't valid!"
-msgstr "Innskrevet nivånummer er ikke gyldig."
+msgstr "Введений номер рівня невірний!"
#: ../src/LevelEditSelect.cpp:849
msgid "Illegal number"
-msgstr "Ulovlig nummer"
+msgstr "Невірний номер"
#: ../src/LevelInfoRender.cpp:19
msgid "Choose a level"
-msgstr "Velg et nivå"
+msgstr "Оберіть рівень"
#: ../src/LevelInfoRender.cpp:20
msgid "Time:"
-msgstr "Tid:"
+msgstr "Час:"
#: ../src/LevelInfoRender.cpp:21 ../src/StatisticsScreen.cpp:259
msgid "Recordings:"
-msgstr "Opptak:"
+msgstr "Записів:"
#: ../src/LevelPackManager.cpp:124
msgid "Custom Levels"
-msgstr "Egendefinerte nivå"
+msgstr "Інші рівні"
#: ../src/LevelPlaySelect.cpp:41
msgid "Select Level"
-msgstr "Velg nivå"
+msgstr "Оберіть рівень"
-# TRANSLATORS: Used for button which clear any level progress like unlocked levels and highscores.
+# TRANSLATORS: Used for button which clear any level progress like unlocked
+# levels and highscores.
#: ../src/OptionsMenu.cpp:66
msgid "Clear Progress"
-msgstr "Opphev framdrift"
+msgstr "Скинути прогрес"
#: ../src/OptionsMenu.cpp:109
msgid "General"
-msgstr "Generelt"
+msgstr "Загальні"
#: ../src/OptionsMenu.cpp:110
msgid "Controls"
-msgstr "Kontroller"
+msgstr "Керування"
#: ../src/OptionsMenu.cpp:121
msgid "Music"
-msgstr "Musikk"
+msgstr "Музика"
#: ../src/OptionsMenu.cpp:129
msgid "Sound"
-msgstr "Lyd"
+msgstr "Звуки"
#: ../src/OptionsMenu.cpp:137
msgid "Resolution"
-msgstr "Oppløsning"
+msgstr "Роздільна здатність"
#: ../src/OptionsMenu.cpp:177
msgid "Language"
-msgstr "Språk"
+msgstr "Мова"
# TRANSLATORS: as detect user's language automatically
#: ../src/OptionsMenu.cpp:185
msgid "Auto-Detect"
-msgstr "Oppdag automatisk"
+msgstr "Автовизначення"
#: ../src/OptionsMenu.cpp:209
msgid "Theme"
-msgstr "Drakt"
+msgstr "Оформлення"
#: ../src/OptionsMenu.cpp:247
msgid "Internet proxy"
-msgstr "Internettmellomtjener"
+msgstr "Інтернет-проксі"
#: ../src/OptionsMenu.cpp:256
msgid "Fullscreen"
-msgstr "Fullskjermsvisning"
+msgstr "На весь екран"
#: ../src/OptionsMenu.cpp:261
msgid "Quick record"
-msgstr "Raskt opptak"
+msgstr "Швидкий запис"
#: ../src/OptionsMenu.cpp:266
msgid "Internet"
-msgstr "Internett"
+msgstr "Інтернет"
#: ../src/OptionsMenu.cpp:271
-#, fuzzy
msgid "Fade transition"
-msgstr "Overgangsutmykning"
+msgstr "Плавний перехід"
#: ../src/OptionsMenu.cpp:294
msgid "Save Changes"
-msgstr "Lagre endringer"
+msgstr "Зберегти зміни"
#: ../src/OptionsMenu.cpp:513
msgid "Do you really want to reset level progress?"
-msgstr "Ønsker du virkelig å tilbakestille nivåfremdriften?"
+msgstr "Ви дійсно хочете скинути прогрес рівня?"
#: ../src/OptionsMenu.cpp:513
msgid "Warning"
-msgstr "Advarsel"
+msgstr "Попередження"
#: ../src/StatisticsManager.cpp:386
-#, fuzzy
msgid "New achievement:"
-msgstr "Ny oppnåelse:"
+msgstr "Нове досягнення:"
#: ../src/StatisticsManager.cpp:394
-#, c-format, fuzzy
+#, c-format
msgid "Achieved on %s"
-msgstr "Oppnådd den %s"
+msgstr "Досягнуто %s"
#: ../src/StatisticsManager.cpp:400
msgid "Unknown achievement"
-msgstr "Ukjent oppnåelse"
+msgstr "Невідоме досягнення"
#: ../src/StatisticsManager.cpp:406
-#, c-format, fuzzy
+#, c-format
msgid "Achieved %1.0f%%"
-msgstr "Oppnådd %1.0f%%"
+msgstr "Досягнуто %1.0f%%"
#: ../src/StatisticsManager.cpp:410
msgid "Not achieved"
-msgstr "Ikke oppnådd"
+msgstr "Не досягнуто"
#: ../src/StatisticsScreen.cpp:57 ../src/TitleMenu.cpp:55
-#, fuzzy
msgid "Achievements and Statistics"
-msgstr "Oppnåelser og statistikk"
+msgstr "Досягнення і статистика"
#: ../src/StatisticsScreen.cpp:166
-#, fuzzy
msgid "Achievements"
-msgstr "Oppnåelser"
+msgstr "Досягнення"
#: ../src/StatisticsScreen.cpp:167
msgid "Statistics"
-msgstr "Statistikk"
+msgstr "Статистика"
#: ../src/StatisticsScreen.cpp:234
msgid "Total"
-msgstr "Totalt"
+msgstr "Загалом"
#: ../src/StatisticsScreen.cpp:246
msgid "Traveling distance (m)"
-msgstr "Reist distanse (m)"
+msgstr "Пройдена відстань (м)"
#: ../src/StatisticsScreen.cpp:247
-#, fuzzy
msgid "Jump times"
-msgstr "Hopp"
+msgstr "Стрибків"
#: ../src/StatisticsScreen.cpp:248
-#, fuzzy
msgid "Die times"
-msgstr "Dødsfall"
+msgstr "Смертей"
#: ../src/StatisticsScreen.cpp:249
msgid "Squashed times"
-msgstr "Most"
+msgstr "Разів розчавлено"
#: ../src/StatisticsScreen.cpp:260
-#, fuzzy
msgid "Switch pulled times:"
-msgstr "Bryteraktuasjoner"
+msgstr "Увімкнено вимикачів:"
#: ../src/StatisticsScreen.cpp:261
-#, fuzzy
msgid "Swap times:"
-msgstr "Bytter"
+msgstr "Змін:"
#: ../src/StatisticsScreen.cpp:262
-#, fuzzy
msgid "Save times:"
-msgstr "Lagringer"
+msgstr "Збережень:"
#: ../src/StatisticsScreen.cpp:263
msgid "Load times:"
-msgstr "Innlastinger"
+msgstr "Завантажень:"
#: ../src/StatisticsScreen.cpp:268
msgid "Completed levels:"
-msgstr "Fullførte nivå"
+msgstr "Завершених рівнів:"
#: ../src/StatisticsScreen.cpp:306
msgid "In-game time:"
-msgstr "Spilltid"
+msgstr "Час у грі:"
#: ../src/StatisticsScreen.cpp:308
msgid "Level editing time:"
-msgstr "Redigeringstid"
+msgstr "Час у редакторі:"
#: ../src/StatisticsScreen.cpp:310
msgid "Created levels:"
-msgstr "Opprettede nivå"
+msgstr "Створено рівнів:"
#: ../src/TitleMenu.cpp:44
msgid "Options"
-msgstr "Valg"
+msgstr "Налаштування"
#: ../src/TitleMenu.cpp:47
msgid "Quit"
-msgstr "Avslutt"
+msgstr "Вихід"
#: ../src/TitleMenu.cpp:131
-#, fuzzy
msgid "Enable internet in order to install addons."
-msgstr "Tilknytt deg Internett for å installere programtillegg."
+msgstr "Щоб встановлювати застосунки, дозвольте підключення до Інтернету."
#: ../src/TitleMenu.cpp:131
-#, fuzzy
msgid "Internet disabled"
-msgstr "Ikke tilknyttet Internett"
+msgstr "Підключення до інтернету вимкнуто"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Return"
-msgstr "Tilbake"
+msgstr "Return"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Escape"
-msgstr "Esc"
+msgstr "Escape"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Backspace"
-msgstr "Rettetast"
+msgstr "Backspace"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Tab"
msgstr "Tab"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Space"
-msgstr "Mellomrom"
+msgstr "Пробіл"
# TRANSLATORS: name of a key
-#, fuzzy
msgctxt "keys"
msgid "CapsLock"
-msgstr "Caps Lock"
+msgstr "CapsLock"
# TRANSLATORS: name of a key
-#, fuzzy
msgctxt "keys"
msgid "PrintScreen"
-msgstr "Print Screen"
+msgstr "PrintScreen"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "ScrollLock"
-msgstr "Scroll Lock"
+msgstr "ScrollLock"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Pause"
msgstr "Pause"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Insert"
msgstr "Insert"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Home"
msgstr "Home"
# TRANSLATORS: name of a key
-#, fuzzy
msgctxt "keys"
msgid "PageUp"
-msgstr "Page Up"
+msgstr "PageUp"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Delete"
-msgstr "Del"
+msgstr "Delete"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "End"
msgstr "End"
# TRANSLATORS: name of a key
-#, fuzzy
msgctxt "keys"
msgid "PageDown"
-msgstr "Page Down"
+msgstr "PageDown"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right"
-msgstr "→"
+msgstr "Праворуч"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left"
-msgstr "←"
+msgstr "Ліворуч"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Down"
-msgstr "↓"
+msgstr "Вниз"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Up"
-msgstr "↑"
+msgstr "Угору"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Numlock"
-msgstr "Num Lock"
+msgstr "Numlock"
# TRANSLATORS: name of a key
-#, fuzzy
msgctxt "keys"
msgid "SysReq"
-msgstr "Sys Req"
+msgstr "SysReq"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Ctrl"
-msgstr "Venstre Ctrl"
+msgstr "Лівий Ctrl"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Shift"
-msgstr "Venstre Shift"
+msgstr "Лівий Shift"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Left Alt"
-msgstr "Venstre Alt"
+msgstr "Лівий Alt"
# TRANSLATORS: name of a key
-#, fuzzy
msgctxt "keys"
msgid "Left GUI"
-msgstr "Venstre visuelle grensesnitt"
+msgstr "Лівий GUI"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Ctrl"
-msgstr "Høyre Ctrl"
+msgstr "Правий Ctrl"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Shift"
-msgstr "Høyre Shift"
+msgstr "Правий Shift"
# TRANSLATORS: name of a key
msgctxt "keys"
msgid "Right Alt"
-msgstr "Høyre Alt"
+msgstr "Правий Alt"
# TRANSLATORS: name of a key
-#, fuzzy
msgctxt "keys"
msgid "Right GUI"
-msgstr "Høyre visuelle grensesnitt"
+msgstr "Правий GUI"
diff --git a/src/Addons.cpp b/src/Addons.cpp
index 8d98c9a..a84606a 100644
--- a/src/Addons.cpp
+++ b/src/Addons.cpp
@@ -1,1082 +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());
+ GUIObject* root=new GUIFrame(imageManager,renderer,(SCREEN_WIDTH-760)/2,(SCREEN_HEIGHT-560)/2,760,560,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);
+ GUIObject* obj=new GUILabel(imageManager,renderer,0,45,760,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);
+ GUITextArea* description=new GUITextArea(imageManager,renderer,10,90,530,390);
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);
+ GUIImage* img=new GUIImage(imageManager,renderer,550,90,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);
+ GUIObject* bRemove=new GUIButton(imageManager,renderer,root->width*0.95,510,-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 = new GUIButton(imageManager, renderer, root->width*0.05, 510, -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,
+ (int)floor((cancelButton->left + cancelButton->width + bRemove->left - bRemove->width)*0.5), 510,
-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=new GUIButton(imageManager,renderer,root->width*0.9,510,-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=new GUIButton(imageManager,renderer,root->width*0.9,510,-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 = new GUIButton(imageManager, renderer, root->width*0.1, 510, -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/Commands.cpp b/src/Commands.cpp
index f64b957..58b1191 100644
--- a/src/Commands.cpp
+++ b/src/Commands.cpp
@@ -1,1067 +1,1063 @@
/*
* 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 "Commands.h"
#include "GameObjects.h"
#include "Block.h"
#include "LevelEditor.h"
#include "Globals.h"
#include "Functions.h"
#include <algorithm>
#include <string>
#include <vector>
#include <map>
#include <stdio.h>
#include "libs/tinyformat/tinyformat.h"
Command::~Command() {
}
//////////////////////////////MoveGameObjectCommand///////////////////////////////////
MoveGameObjectCommand::MoveGameObjectCommand(LevelEditor* levelEditor, GameObject* gameObject, int x, int y, int w, int h)
: editor(levelEditor), objects(1, gameObject)
, resizeCommand(NULL)
{
SDL_Rect r = gameObject->getBox();
r.x = x;
r.y = y;
if (w >= 0) r.w = w;
if (h >= 0) r.h = h;
newPosition.push_back(r);
init();
}
MoveGameObjectCommand::MoveGameObjectCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, int dx, int dy)
: editor(levelEditor), objects(gameObjects)
, resizeCommand(NULL)
{
for (auto obj : objects) {
SDL_Rect r = obj->getBox();
r.x += dx;
r.y += dy;
newPosition.push_back(r);
}
init();
}
void MoveGameObjectCommand::init() {
//Initialize old position.
for (auto obj : objects) {
SDL_Rect r = obj->getBox();
oldPosition.push_back(r);
}
// Create resize command if necessary.
resizeCommand = ResizeLevelCommand::createAndShiftIfNecessary(editor, newPosition);
}
MoveGameObjectCommand::~MoveGameObjectCommand(){
if (resizeCommand) delete resizeCommand;
}
void MoveGameObjectCommand::execute(){
// First resize the level if necessary.
if (resizeCommand) resizeCommand->execute();
// Set the obj at its new position.
for (int i = 0; i < (int)objects.size(); i++) {
const SDL_Rect &r = newPosition[i];
GameObject *obj = objects[i];
obj->setBaseLocation(r.x, r.y);
obj->setBaseSize(r.w, r.h);
}
}
void MoveGameObjectCommand::unexecute() {
// First undo the resize if necessary.
if (resizeCommand) resizeCommand->unexecute();
// Set the obj at its old position.
for (int i = 0; i < (int)objects.size(); i++) {
const SDL_Rect &r = oldPosition[i];
GameObject *obj = objects[i];
obj->setBaseLocation(r.x, r.y);
obj->setBaseSize(r.w, r.h);
}
}
ResizeLevelCommand::ResizeLevelCommand(LevelEditor* levelEditor, int newWidth, int newHeight, int diffx, int diffy)
: editor(levelEditor), newLevelWidth(newWidth), newLevelHeight(newHeight), diffx(diffx), diffy(diffy)
{
oldLevelWidth = LEVEL_WIDTH;
oldLevelHeight = LEVEL_HEIGHT;
}
ResizeLevelCommand::~ResizeLevelCommand() {
}
void ResizeLevelCommand::execute() {
resizeLevel(editor, newLevelWidth, newLevelHeight, diffx, diffy);
}
void ResizeLevelCommand::unexecute() {
resizeLevel(editor, oldLevelWidth, oldLevelHeight, -diffx, -diffy);
}
void ResizeLevelCommand::resizeLevel(LevelEditor* levelEditor, int newWidth, int newHeight, int diffx, int diffy) {
LEVEL_WIDTH = newWidth;
LEVEL_HEIGHT = newHeight;
if (diffx != 0 || diffy != 0) {
camera.x += diffx;
camera.y += diffy;
for (unsigned int o = 0; o < levelEditor->levelObjects.size(); o++){
SDL_Rect r = levelEditor->levelObjects[o]->getBox();
levelEditor->levelObjects[o]->setBaseLocation(r.x + diffx, r.y + diffy);
}
for (auto it = levelEditor->sceneryLayers.begin(); it != levelEditor->sceneryLayers.end(); ++it) {
for (auto o : it->second->objects){
SDL_Rect r = o->getBox();
o->setBaseLocation(r.x + diffx, r.y + diffy);
}
}
}
}
ResizeLevelCommand* ResizeLevelCommand::createAndShiftIfNecessary(LevelEditor* levelEditor, std::vector<SDL_Rect>& position) {
// Calculate new level size, shift, etc.
int newLevelWidth = LEVEL_WIDTH;
int newLevelHeight = LEVEL_HEIGHT;
int diffx = 0, diffy = 0;
for (int i = 0; i < (int)position.size(); i++) {
const SDL_Rect &r = position[i];
if (r.x + r.w > newLevelWidth) {
newLevelWidth = r.x + r.w;
}
if (r.y + r.h > newLevelHeight) {
newLevelHeight = r.y + r.h;
}
if (r.x + diffx < 0) diffx = -r.x;
if (r.y + diffy < 0) diffy = -r.y;
}
newLevelWidth += diffx;
newLevelHeight += diffy;
if (newLevelWidth != LEVEL_WIDTH || newLevelHeight != LEVEL_HEIGHT || diffx || diffy) {
if (diffx || diffy) {
for (int i = 0; i < (int)position.size(); i++) {
SDL_Rect &r = position[i];
r.x += diffx;
r.y += diffy;
}
}
return new ResizeLevelCommand(levelEditor, newLevelWidth, newLevelHeight, diffx, diffy);
} else {
return NULL;
}
}
std::string ResizeLevelCommand::describe() {
/// TRANSLATORS: Context: Undo/Redo ...
return _("Resize level");
}
//////////////////////////////AddRemoveGameObjectCommand///////////////////////////////////
AddRemoveGameObjectCommand::AddRemoveGameObjectCommand(LevelEditor* levelEditor, GameObject* gameObject, bool isAdd_)
: editor(levelEditor), objects(1, gameObject), isAdd(isAdd_), ownObject(isAdd_)
, resizeCommand(NULL), removeStartCommand(NULL), oldTriggers(NULL)
{
init();
}
AddRemoveGameObjectCommand::AddRemoveGameObjectCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, bool isAdd_)
: editor(levelEditor), objects(gameObjects), isAdd(isAdd_), ownObject(isAdd_)
, resizeCommand(NULL), removeStartCommand(NULL), oldTriggers(NULL)
{
init();
}
void AddRemoveGameObjectCommand::init() {
if (isAdd) {
theLayer = editor->selectedLayer;
// adjust the object position if necessary
std::vector<SDL_Rect> position;
for (auto obj : objects) {
position.push_back(obj->getBox());
}
resizeCommand = ResizeLevelCommand::createAndShiftIfNecessary(editor, position);
if (resizeCommand) {
for (int i = 0; i < (int)objects.size(); i++) {
const SDL_Rect &r = position[i];
objects[i]->setBaseLocation(r.x, r.y);
}
}
// remove the existing player/shadow start if necessary
std::vector<bool> typesToBeRemoved((int)TYPE_MAX, false);
bool removeSomething = false;
for (auto obj : objects) {
if (obj->type == TYPE_START_PLAYER || obj->type == TYPE_START_SHADOW) {
typesToBeRemoved[obj->type] = true;
removeSomething = true;
}
}
if (removeSomething) {
std::vector<GameObject*> objectsToBeRemoved;
for (auto obj : editor->levelObjects) {
if (typesToBeRemoved[obj->type]) objectsToBeRemoved.push_back(obj);
}
if (!objectsToBeRemoved.empty()) {
removeStartCommand = new AddRemoveGameObjectCommand(editor, objectsToBeRemoved, false);
}
}
}
}
void AddRemoveGameObjectCommand::backupTriggers() {
if (oldTriggers == NULL) oldTriggers = new LevelEditor::Triggers(editor->triggers);
}
AddRemoveGameObjectCommand::~AddRemoveGameObjectCommand(){
//Remove the objects if we own them.
if (ownObject) {
for (auto obj : objects) {
delete obj;
}
}
//Delete internal commands.
if (resizeCommand) delete resizeCommand;
if (removeStartCommand) delete removeStartCommand;
//Delete old triggers
if (oldTriggers) delete oldTriggers;
}
void AddRemoveGameObjectCommand::addGameObject(){
// some sanity check
assert(ownObject);
// Remove old start position if necessary.
if (removeStartCommand) removeStartCommand->execute();
// Resize the level if necessary.
if (resizeCommand) resizeCommand->execute();
// Add each object to level
for (int index = 0; index < (int)objects.size(); index++) {
GameObject *obj = objects[index];
//Increase totalCollectables everytime we add a new collectable
if (obj->type == TYPE_COLLECTABLE) {
editor->totalCollectables++;
}
Block* block = dynamic_cast<Block*>(obj);
Scenery* scenery = dynamic_cast<Scenery*>(obj);
//Add it to the levelObjects.
if (theLayer.empty()) {
assert(block != NULL);
editor->levelObjects.push_back(block);
} else {
assert(scenery != NULL);
auto it = editor->sceneryLayers.find(theLayer);
assert(it != editor->sceneryLayers.end());
it->second->objects.push_back(scenery);
}
//GameObject type specific stuff.
switch (obj->type){
case TYPE_BUTTON:
case TYPE_SWITCH:
case TYPE_PORTAL:
{
//If block doesn't have an id (new object).
if (block->getEditorProperty("id").empty()) {
//Give it it's own id.
char s[64];
sprintf(s, "%u", editor->currentId);
editor->currentId++;
block->setEditorProperty("id", s);
}
break;
}
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//Add the object to the moving blocks.
vector<MovingPosition> positions;
editor->movingBlocks[block] = positions;
//Get the moving position.
const vector<SDL_Rect> &movingPos = block->movingPos;
//Add the object to the movingBlocks vector.
editor->movingBlocks[block].clear();
for (int i = 0, m = movingPos.size(); i < m; i++) {
MovingPosition position(movingPos[i].x, movingPos[i].y, movingPos[i].w);
editor->movingBlocks[block].push_back(position);
}
//If block doesn't have an id.
if (block->getEditorProperty("id").empty()) {
//Give it it's own id.
char s[64];
sprintf(s, "%u", editor->currentId);
editor->currentId++;
block->setEditorProperty("id", s);
}
break;
}
default:
break;
}
}
// now we doesn't own the object anymore.
ownObject = false;
}
void AddRemoveGameObjectCommand::removeGameObject(){
// some sanity check
assert(!ownObject);
//Make sure we don't access the removed object through
//moving/linking.
editor->movingBlock = nullptr;
editor->linkingTrigger = nullptr;
editor->moving = false;
editor->linking = false;
// Remove objects
for (int index = 0; index < (int)objects.size(); index++) {
GameObject *obj = objects[index];
std::vector<GameObject*>::iterator it;
std::map<Block*, vector<GameObject*> >::iterator mapIt;
//Decrease totalCollectables everytime we remove a collectable
if (obj->type == TYPE_COLLECTABLE){
editor->totalCollectables--;
}
//Check if the object is in the selection.
it = find(editor->selection.begin(), editor->selection.end(), obj);
if (it != editor->selection.end()){
//It is so we delete it.
editor->selection.erase(it);
}
Block *theBlock = dynamic_cast<Block*>(obj);
Scenery *theScenery = dynamic_cast<Scenery*>(obj);
//Check if the object is in the triggers.
if (theBlock) {
mapIt = editor->triggers.find(theBlock);
if (mapIt != editor->triggers.end()){
//Back up triggers if it's remove mode.
if (!isAdd) backupTriggers();
//Remove the object from triggers.
editor->triggers.erase(mapIt);
}
}
//Boolean if it could be a target.
if (obj->type == TYPE_MOVING_BLOCK || obj->type == TYPE_MOVING_SHADOW_BLOCK || obj->type == TYPE_MOVING_SPIKES
|| obj->type == TYPE_CONVEYOR_BELT || obj->type == TYPE_SHADOW_CONVEYOR_BELT || obj->type == TYPE_PORTAL)
{
for (mapIt = editor->triggers.begin(); mapIt != editor->triggers.end(); ++mapIt){
//Now loop the target vector.
for (int o = mapIt->second.size() - 1; o >= 0; o--){
//Check if the obj is in the target vector.
if (mapIt->second[o] == obj){
//Back up triggers if it's remove mode.
if (!isAdd) backupTriggers();
//Remove the object from triggers.
mapIt->second.erase(mapIt->second.begin() + o);
}
}
}
}
//Check if the object is in the movingObjects.
if (theBlock) {
std::map<Block*, vector<MovingPosition> >::iterator movIt;
movIt = editor->movingBlocks.find(theBlock);
if (movIt != editor->movingBlocks.end()){
//It is so we remove it.
editor->movingBlocks.erase(movIt);
}
}
//Check if the block isn't being configured with a window one way or another.
for (;;) {
std::map<GUIObject*, GameObject*>::iterator confIt;
for (confIt = editor->objectWindows.begin(); confIt != editor->objectWindows.end(); ++confIt){
if (confIt->second == obj) break;
}
if (confIt == editor->objectWindows.end()) break;
editor->destroyWindow(confIt->first);
}
//Now we remove the object from the levelObjects and/or scenery.
if (theBlock){
std::vector<Block*>::iterator it;
it = find(editor->levelObjects.begin(), editor->levelObjects.end(), theBlock);
if (it != editor->levelObjects.end()){
editor->levelObjects.erase(it);
theLayer.clear(); // record the layer of this object
}
} else if (theScenery) {
for (auto it = editor->sceneryLayers.begin(); it != editor->sceneryLayers.end(); ++it){
auto it2 = find(it->second->objects.begin(), it->second->objects.end(), theScenery);
if (it2 != it->second->objects.end()) {
it->second->objects.erase(it2);
theLayer = it->first; // record the layer of this object
break;
}
}
}
}
// Resize the level if necessary.
if (resizeCommand) resizeCommand->unexecute();
// Restore old start position if necessary.
if (removeStartCommand) removeStartCommand->unexecute();
// now we own this object
ownObject = true;
//Set dirty of selection popup
editor->selectionDirty();
}
void AddRemoveGameObjectCommand::execute(){
if (isAdd) {
addGameObject();
} else {
removeGameObject();
}
}
void AddRemoveGameObjectCommand::unexecute(){
if (isAdd) {
removeGameObject();
} else {
addGameObject();
}
// Restore old triggers if necessary
if (oldTriggers) editor->triggers = *oldTriggers;
}
//////////////////////////////AddRemovePathCommand///////////////////////////////////
AddRemovePathCommand::AddRemovePathCommand(LevelEditor* levelEditor, Block* movingBlock, MovingPosition movingPosition, bool isAdd_)
:editor(levelEditor), target(movingBlock), movePos(movingPosition), isAdd(isAdd_)
{
if (!isAdd) {
if (target->movingPos.empty()) {
assert(!"movingBlock->movingPos is empty!");
} else {
const SDL_Rect& r = target->movingPos.back();
movePos.x = r.x;
movePos.y = r.y;
movePos.time = r.w;
}
}
}
AddRemovePathCommand::~AddRemovePathCommand(){
}
void AddRemovePathCommand::execute(){
if (isAdd) addPath();
else removePath();
}
void AddRemovePathCommand::unexecute(){
if (isAdd) removePath();
else addPath();
}
void AddRemovePathCommand::addPath() {
//Add movePos to the path.
editor->movingBlocks[target].push_back(movePos);
//Write the path to the moving block.
setEditorData();
}
void AddRemovePathCommand::removePath() {
//Remove the last point in the path.
editor->movingBlocks[target].pop_back();
//Write the path to the moving block.
setEditorData();
}
void AddRemovePathCommand::setEditorData() {
target->movingPos.clear();
for (const MovingPosition& p : editor->movingBlocks[target]) {
SDL_Rect r = { p.x, p.y, p.time };
target->movingPos.push_back(r);
}
}
//////////////////////////////RemovePathCommand///////////////////////////////////
RemovePathCommand::RemovePathCommand(LevelEditor* levelEditor, Block* targetBlock)
:editor(levelEditor), target(targetBlock){
}
RemovePathCommand::~RemovePathCommand(){
}
void RemovePathCommand::execute(){
//Set the number of movingPositions to 0.
target->setEditorProperty("MovingPosCount","0");
std::map<Block*,vector<MovingPosition> >::iterator it;
//Check if target is in movingBlocks.
it = editor->movingBlocks.find(target);
if(it!=editor->movingBlocks.end()){
//Store the movingPositions for unexecute.
movePositions = it->second;
//Clear all its movingPositions
it->second.clear();
}
}
void RemovePathCommand::unexecute(){
std::map<Block*,vector<MovingPosition> >::iterator it;
//Find target in movingBlocks
it = editor->movingBlocks.find(target);
if(it!=editor->movingBlocks.end()){
//Restore its movingPositions
it->second = movePositions;
//Write the path to the moving block.
std::map<std::string,std::string> editorData;
char s[64], s0[64];
sprintf(s,"%d",int(editor->movingBlocks[target].size()));
editorData["MovingPosCount"]=s;
//Loop through the positions.
for(unsigned int o=0; o<editor->movingBlocks[target].size(); o++) {
sprintf(s0+1,"%u",o);
sprintf(s,"%d",editor->movingBlocks[target][o].x);
s0[0]='x';
editorData[s0]=s;
sprintf(s,"%d",editor->movingBlocks[target][o].y);
s0[0]='y';
editorData[s0]=s;
sprintf(s,"%d",editor->movingBlocks[target][o].time);
s0[0]='t';
editorData[s0]=s;
}
target->setEditorData(editorData);
}
}
//////////////////////////////AddLinkCommand///////////////////////////////////
AddLinkCommand::AddLinkCommand(LevelEditor* levelEditor, Block* linkingTrigger, GameObject* clickedObject)
:editor(levelEditor), target(linkingTrigger), clickedObj(clickedObject),oldPortalLink(NULL), destination(""), id(""), oldTrigger(NULL){
}
AddLinkCommand::~AddLinkCommand(){
}
void AddLinkCommand::execute(){
//Check if the target can handle multiple or only one link.
switch(target->type) {
case TYPE_PORTAL: {
//Store the last portal link.
if(!editor->triggers[target].empty()){
oldPortalLink = editor->triggers[target].back();
}
//Portals can only link to one so remove all existing links.
editor->triggers[target].clear();
editor->triggers[target].push_back(clickedObj);
break;
}
default: {
//The others can handle multiple links.
editor->triggers[target].push_back(clickedObj);
break;
}
}
//Check if it's a portal.
if(target->type==TYPE_PORTAL) {
//Store the previous destination.
destination = target->getEditorProperty("destination");
//Portals need to get the id of the other instead of give it's own id.
- char s[64];
- sprintf(s,"%d",atoi(clickedObj->getEditorProperty("id").c_str()));
- target->setEditorProperty("destination",s);
+ target->setEditorProperty("destination", clickedObj->getEditorProperty("id"));
} else{
//Store the previous id.
id = clickedObj->getEditorProperty("id");
if(!id.empty()) {
//Another block might already be set as trigger for the clickedObj.
for(auto block : editor->levelObjects) {
std::string otherId = block->getEditorProperty("id");
if(id.compare(otherId) == 0) {
//IDs match, so either one of the trigger itself or one of its (other) targets.
std::vector<GameObject*>::iterator it;
//Find the clickedObj in the block's triggers.
it = std::find(editor->triggers[block].begin(), editor->triggers[block].end(), clickedObj);
if(it != editor->triggers[block].end()){
oldTrigger = block;
editor->triggers[block].erase(it);
break;
}
}
}
}
//Give the clickedObject the same id as the trigger.
- char s[64];
- sprintf(s,"%d",atoi(target->getEditorProperty("id").c_str()));
- clickedObj->setEditorProperty("id",s);
+ clickedObj->setEditorProperty("id", target->getEditorProperty("id"));
}
}
void AddLinkCommand::unexecute(){
//Check if the target can handle multiple or only one link.
switch(target->type) {
case TYPE_PORTAL: {
//Portals can only link to one so remove all existing links.
editor->triggers[target].clear();
//Put the previous portal link back.
if(oldPortalLink != NULL)
editor->triggers[target].push_back(oldPortalLink);
break;
}
default:{
std::vector<GameObject*>::iterator it;
//Find the clickedObj in the target's triggers.
it = std::find(editor->triggers[target].begin(), editor->triggers[target].end(), clickedObj);
if(it != editor->triggers[target].end()){
//Remove it.
editor->triggers[target].erase(it);
}
//Restore old trigger if applicable
if(oldTrigger != NULL){
editor->triggers[oldTrigger].push_back(clickedObj);
}
break;
}
}
//Check if it's a portal.
if(target->type==TYPE_PORTAL) {
//Restore previous destination.
target->setEditorProperty("destination",destination);
} else{
//Restore clickedObj's previous id.
clickedObj->setEditorProperty("id",id);
}
}
//////////////////////////////RemoveLinkCommand///////////////////////////////////
RemoveLinkCommand::RemoveLinkCommand(LevelEditor* levelEditor, Block* targetBlock)
: editor(levelEditor), target(targetBlock), destination(""), id(""){
}
RemoveLinkCommand::~RemoveLinkCommand(){
}
void RemoveLinkCommand::execute(){
std::map<Block*,vector<GameObject*> >::iterator it;
//Find target in triggers.
it = editor->triggers.find(target);
if(it!=editor->triggers.end()) {
//Copy the objects the target was linked to.
links = it->second;
//Remove the links.
it->second.clear();
}
//In case of a portal remove its destination field.
if(target->type==TYPE_PORTAL) {
//Copy its previous destination.
destination = target->getEditorProperty("destination");
//Erase its destination.
target->setEditorProperty("destination","");
} else{
//Copy its previous id.
id = target->getEditorProperty("id");
//Give the trigger a new id to prevent activating unlinked targets.
char s[64];
sprintf(s,"%u",editor->currentId);
editor->currentId++;
target->setEditorProperty("id",s);
}
}
void RemoveLinkCommand::unexecute(){
std::map<Block*,vector<GameObject*> >::iterator it;
//Find target in triggers.
it = editor->triggers.find(target);
if(it!=editor->triggers.end()) {
//Restore objects it was linked to.
it->second = links;
}
if(target->type== TYPE_PORTAL){
//Restore old destination.
target->setEditorProperty("destination", destination);
} else{
//Restore old id.
target->setEditorProperty("id", id);
}
}
SetEditorPropertyCommand::SetEditorPropertyCommand(LevelEditor* levelEditor, ImageManager& imageManager, SDL_Renderer& renderer, GameObject* targetBlock,
const std::string& propertyName, const std::string& propertyValue, const std::string& propertyDescription)
: editor(levelEditor), imageManager(imageManager), renderer(renderer)
, target(targetBlock), prop(propertyName), newValue(propertyValue), desc(propertyDescription)
{
oldValue = target->getEditorProperty(prop);
}
SetEditorPropertyCommand::~SetEditorPropertyCommand() {
}
void SetEditorPropertyCommand::execute() {
target->setEditorProperty(prop, newValue);
updateCustomScenery();
}
void SetEditorPropertyCommand::unexecute() {
target->setEditorProperty(prop, oldValue);
updateCustomScenery();
}
void SetEditorPropertyCommand::updateCustomScenery() {
Scenery *scenery = dynamic_cast<Scenery*>(target);
if (target && prop == "customScenery") {
scenery->updateCustomScenery(imageManager, renderer);
}
}
SetLevelPropertyCommand::~SetLevelPropertyCommand() {
}
void SetLevelPropertyCommand::execute() {
setLevelProperty(newProperty);
}
void SetLevelPropertyCommand::unexecute() {
setLevelProperty(oldProperty);
}
std::string SetLevelPropertyCommand::describe() {
/// TRANSLATORS: Context: Undo/Redo ...
return _("Modify level property");
}
SetScriptCommand::SetScriptCommand(LevelEditor* levelEditor, Block* targetBlock, const std::map<int, std::string>& script, const std::string& id)
: editor(levelEditor), target(targetBlock), newScript(script), id(id)
{
if (target) {
oldScript = target->scripts;
oldId = target->id;
} else {
oldScript = editor->scripts;
}
}
SetScriptCommand::~SetScriptCommand() {
}
void SetScriptCommand::execute() {
setScript(newScript, id);
}
void SetScriptCommand::unexecute() {
setScript(oldScript, oldId);
}
void SetScriptCommand::setScript(const std::map<int, std::string>& script, const std::string& id) {
if (target) {
target->scripts = script;
//Set the new id for the target block.
//TODO: Check for trigger links etc...
target->id = id;
} else {
editor->scripts = script;
}
}
AddRemoveLayerCommand::AddRemoveLayerCommand(LevelEditor* levelEditor, const std::string& layerName, bool isAdd_)
: editor(levelEditor), layer(NULL), theLayer(layerName), isAdd(isAdd_), ownObject(isAdd_)
{
if (isAdd) {
layer = new SceneryLayer;
} else {
auto it = editor->sceneryLayers.find(theLayer);
if (it != editor->sceneryLayers.end()) {
layer = it->second;
}
}
}
void AddRemoveLayerCommand::execute() {
if (isAdd) {
addLayer();
} else {
removeLayer();
}
}
void AddRemoveLayerCommand::unexecute() {
if (isAdd) {
removeLayer();
} else {
addLayer();
}
}
void AddRemoveLayerCommand::addLayer() {
assert(ownObject && layer);
assert(editor->sceneryLayers.find(theLayer) == editor->sceneryLayers.end());
// add this layer
editor->sceneryLayers[theLayer] = layer;
// show and select the newly created layer
editor->layerVisibility[theLayer] = true;
editor->selectedLayer = theLayer;
// deselect all
editor->deselectAll();
// change the ownership
ownObject = false;
}
void AddRemoveLayerCommand::removeLayer() {
assert(!ownObject);
assert(editor->sceneryLayers.find(theLayer) != editor->sceneryLayers.end());
assert(editor->sceneryLayers.find(theLayer)->second == layer);
// deselect all
editor->deselectAll();
// select the Blocks layer
editor->selectedLayer.clear();
// remove this layer
editor->layerVisibility.erase(theLayer);
editor->sceneryLayers.erase(theLayer);
// change the ownership
ownObject = true;
}
AddRemoveLayerCommand::~AddRemoveLayerCommand() {
if (ownObject) {
delete layer;
}
}
std::string AddRemoveLayerCommand::describe() {
return tfm::format(isAdd ?
/// TRANSLATORS: Context: Undo/Redo ...
_("Add scenery layer %s") :
/// TRANSLATORS: Context: Undo/Redo ...
_("Delete scenery layer %s"), theLayer);
}
SetLayerPropertyCommand::SetLayerPropertyCommand(LevelEditor* levelEditor, const std::string& oldName, const LayerProperty& newProperty)
: editor(levelEditor), newProperty(newProperty)
{
auto it = editor->sceneryLayers.find(oldName);
assert(it != editor->sceneryLayers.end() && it->second);
oldProperty.name = oldName;
oldProperty.speedX = it->second->speedX;
oldProperty.speedY = it->second->speedY;
oldProperty.cameraX = it->second->cameraX;
oldProperty.cameraY = it->second->cameraY;
}
void SetLayerPropertyCommand::execute() {
setLayerProperty(oldProperty.name, newProperty);
}
void SetLayerPropertyCommand::unexecute() {
setLayerProperty(newProperty.name, oldProperty);
}
SetLayerPropertyCommand::~SetLayerPropertyCommand() {
}
std::string SetLayerPropertyCommand::describe() {
/// TRANSLATORS: Context: Undo/Redo ...
return tfm::format(_("Modify the property of scenery layer %s"), oldProperty.name);
}
void SetLayerPropertyCommand::setLayerProperty(const std::string& oldName, const LayerProperty& newProperty) {
SceneryLayer *tmp = NULL;
// Find the existing layer
{
auto it = editor->sceneryLayers.find(oldName);
assert(it != editor->sceneryLayers.end() && it->second);
tmp = it->second;
}
// Check if we need to rename scenery layer
if (oldName != newProperty.name) {
assert(editor->sceneryLayers.find(newProperty.name) == editor->sceneryLayers.end());
// remove the old layer
editor->sceneryLayers.erase(oldName);
// then save the temp variable to the new layer
editor->sceneryLayers[newProperty.name] = tmp;
// show and select the newly created layer
editor->layerVisibility[newProperty.name] = editor->layerVisibility[oldName];
editor->layerVisibility.erase(oldName);
editor->selectedLayer = newProperty.name;
}
// Now we update the properties of the layer
tmp->speedX = newProperty.speedX;
tmp->speedY = newProperty.speedY;
tmp->cameraX = newProperty.cameraX;
tmp->cameraY = newProperty.cameraY;
}
MoveToLayerCommand::MoveToLayerCommand(LevelEditor* levelEditor, std::vector<GameObject*>& gameObjects, const std::string& oldName, const std::string& newName)
: editor(levelEditor), oldName(oldName), newName(newName), createNewLayer(NULL)
{
if (editor->sceneryLayers.find(newName) == editor->sceneryLayers.end()) {
createNewLayer = new AddRemoveLayerCommand(levelEditor, newName, true);
}
for (auto obj : gameObjects) {
Scenery *scenery = dynamic_cast<Scenery*>(obj);
if (scenery) objects.push_back(scenery);
}
}
void MoveToLayerCommand::execute() {
if (createNewLayer) {
createNewLayer->execute();
}
removeGameObject();
addGameObject(newName);
// show and select the new layer
editor->layerVisibility[newName] = true;
editor->selectedLayer = newName;
// deselect all
editor->deselectAll();
}
void MoveToLayerCommand::unexecute() {
removeGameObject();
addGameObject(oldName);
if (createNewLayer) {
createNewLayer->unexecute();
}
// show and select the old layer
editor->layerVisibility[oldName] = true;
editor->selectedLayer = oldName;
// deselect all
editor->deselectAll();
}
MoveToLayerCommand::~MoveToLayerCommand() {
if (createNewLayer) {
delete createNewLayer;
}
}
std::string MoveToLayerCommand::describe() {
const size_t number_of_objects = objects.size();
/// TRANSLATORS: Context: Undo/Redo ...
return tfm::format(ngettext("Move %d object from layer %s to layer %s", "Move %d objects from layer %s to layer %s", number_of_objects).c_str(),
number_of_objects, oldName, newName);
}
void MoveToLayerCommand::removeGameObject() {
// Remove objects
for (int index = 0; index < (int)objects.size(); index++) {
Scenery *scenery = objects[index];
//Now we remove the object from scenery.
for (auto it = editor->sceneryLayers.begin(); it != editor->sceneryLayers.end(); ++it){
auto it2 = find(it->second->objects.begin(), it->second->objects.end(), scenery);
if (it2 != it->second->objects.end()) {
it->second->objects.erase(it2);
break;
}
}
}
}
void MoveToLayerCommand::addGameObject(const std::string& layer) {
// Add objects
const auto& target = editor->sceneryLayers[layer];
for (int index = 0; index < (int)objects.size(); index++) {
target->objects.push_back(objects[index]);
}
}
diff --git a/src/EasterEggScreen.cpp b/src/EasterEggScreen.cpp
index 2d9102b..c73c67c 100644
--- a/src/EasterEggScreen.cpp
+++ b/src/EasterEggScreen.cpp
@@ -1,431 +1,431 @@
/*
* 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/>.
*/
#include "EasterEggScreen.h"
#include "SoundManager.h"
#include "Render.h"
#include "Globals.h"
#include "Functions.h"
#include "InputManager.h"
#include <SDL_mixer.h>
#include <SDL_ttf.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <string>
#include <map>
//Define this to enable fake "ls -la" screen.
#define SCAM
#ifdef SCAM
const unsigned int randMaxPlusOne = (unsigned int)(RAND_MAX) + 1;
struct FakeSoundEffectState {
Sint16 buffer[4096];
int state;
int randContext;
int rand() {
randContext = (randContext * 1103515245 + 12345) & 0x7FFFFFFF;
return ((randContext << 15) | (randContext >> 16)) & RAND_MAX;
}
};
static void SDLCALL fakeSoundEffect(void *udata, Uint8 *stream_, int len) {
FakeSoundEffectState *state = (FakeSoundEffectState*)udata;
Sint16 *stream = (Sint16*)stream_;
int len2 = len / 2; // NOTE: len is in bytes, not in samples !!!
int len3 = len2;
if (len3 > sizeof(state->buffer) / sizeof(state->buffer[0])) {
len3 = sizeof(state->buffer) / sizeof(state->buffer[0]);
}
if (state->state & 0xFF) {
state->state--;
} else {
state->state = int(float(state->rand()) / float(randMaxPlusOne) * 9.0f) << 8;
if (state->state) state->state |= int(float(state->rand()) / float(randMaxPlusOne) * 10.0f);
else state->state |= int(float(state->rand()) / float(randMaxPlusOne) * 80.0f);
switch (state->state >> 8) {
case 1:
memcpy(state->buffer, stream, len3 * sizeof(Sint16));
break;
}
}
int i;
switch (state->state >> 8) {
case 0:
break;
case 1:
memcpy(stream, state->buffer, len3 * sizeof(Sint16));
break;
case 2:
for (i = 0; i < len2; i++) {
stream[i] = stream[i] >= 0 ? 0x7FFF : 0x8000;
}
break;
case 3:
for (i = 1; i < len2; i++) {
stream[i] ^= stream[i - 1];
}
break;
case 4:
for (i = 0; i < len2 / 2; i++) {
std::swap(stream[i], stream[len2 - 1 - i]);
}
break;
case 5:
for (i = 0; i < len2; i++) {
bool b = (stream[i] & 0x1000) != 0;
if (stream[i] < 0) b = !b;
stream[i] <<= 3;
if (b) stream[i] = ~stream[i];
}
break;
case 6:
memcpy(state->buffer, stream, len3 * sizeof(Sint16));
for (i = 0; i < len3; i += 2) {
stream[i / 2] = state->buffer[i];
}
for (i = 1; i < len3; i += 2) {
stream[(len3 + i) / 2] = state->buffer[i];
}
case 7:
for (i = 0; i < len2; i++) {
float f = float(stream[i]);
stream[i] = Sint32(f * f * f / 1073741824.0f);
}
break;
case 8:
for (i = 0; i < len2; i++) {
stream[i] = 0;
}
break;
}
}
FakeSoundEffectState fakeSoundEffectState;
static void scamDrawText(ImageManager& imageManager, SDL_Renderer& renderer,
const std::map<int, TexturePtr>& cache,
int fontWidth, int fontHeight,
int x, int y, const char* text)
{
int color = 0, x0 = x;
for (int i = 0;; i++) {
int c = (int)(unsigned char)text[i];
if (c == 0) break;
if (c == '\n') {
x = x0;
y += fontHeight;
} else if (c == ' ') {
x += fontWidth;
} else if (c >= 0x10 && c < 0x20) {
color = c & 0xF;
} else {
const int key = c | (color << 8);
auto it = cache.find(key);
if (it != cache.end()) {
applyTexture(x, y, const_cast<TexturePtr&>(it->second), renderer);
x += fontWidth;
}
}
}
}
//Show a fake never-ending "ls -la" screen unless the user press Ctrl+C.
bool easterEggScreen(ImageManager& imageManager, SDL_Renderer& renderer) {
//Some colors.
SDL_Color colors[] = {
{ 0xC0, 0xC0, 0xC0, 0xFF }, //lightgray
{ 0x00, 0xFF, 0x00, 0xFF }, //green
{ 0x00, 0x00, 0xFF, 0xFF }, //blue
{ 0x00, 0xFF, 0xFF, 0xFF }, //cyan
{ 0xFF, 0x00, 0xFF, 0xFF }, //magenta
};
const int numberOfColors = sizeof(colors) / sizeof(colors[0]);
int fontWidth = 0;
TTF_GlyphMetrics(fontMono, 'W', NULL, NULL, NULL, NULL, &fontWidth);
//Initialize some textures.
std::map<int, TexturePtr> cache;
for (int i = 0; i < numberOfColors; i++) {
for (int c = 33; c <= 126; c++) {
- const char s[2] = { c, 0 };
+ const char s[2] = { (char)c, 0 };
const int key = c | (i << 8);
cache[key] = textureFromText(renderer, *fontMono, s, colors[i]);
}
}
const char* extensions[] = {
"\x11sh", "\x11py",
"\x14png", "\x14jpg",
"txt", "c", "cpp", "h", "map", "lst", "lua",
};
const int numberOfExtensions = sizeof(extensions) / sizeof(extensions[0]);
const char* users[] = {
"root", "user", "me", "shadow"
};
const int numberOfUsers = sizeof(users) / sizeof(users[0]);
const char* months[12] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
};
const int numOfDays[12] = {
31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31,
};
const int fontHeight = TTF_FontHeight(fontMono);
//Keep the last resize event, this is to only process one.
SDL_Event lastResize = {};
bool ret = false, isRunning = true;
char s0[72] = {}, s1[72] = {}, s2[256] = {};
for (int i = 0; i < 60; i++) {
s0[i] = int(float(rand()) / float(randMaxPlusOne) * 15.0f);
}
if (getSettings()->getBoolValue("music")) {
fakeSoundEffectState.state = int(float(rand()) / float(randMaxPlusOne) * 80.0f);
fakeSoundEffectState.randContext = rand() ^ (rand() << 16);
Mix_SetPostMix(fakeSoundEffect, &fakeSoundEffectState);
}
for (int t = 0; isRunning; t++) {
while (SDL_PollEvent(&event)) {
//Check if we need to quit, if so enter the exit state.
if (event.type == SDL_QUIT){
setNextState(STATE_EXIT);
isRunning = false;
}
//Check for a resize event.
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
lastResize = event;
continue;
}
//Check Ctrl+C.
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_c
&& (event.key.keysym.mod & KMOD_CTRL) != 0 && (event.key.keysym.mod & ~KMOD_CTRL) == 0)
{
ret = true;
isRunning = false;
}
//Set the cursor type to the default one, the GUI can change that if needed.
currentCursor = CURSOR_POINTER;
//Let the input manager handle the events.
inputMgr.updateState(true);
}
//Process the resize event.
if (lastResize.type == SDL_WINDOWEVENT){
//TODO - used to be SDL_VIDEORESIZE
// so this may trigger on more events than intended
event = lastResize;
onVideoResize(imageManager, renderer);
//After resize we erase the event type
//TODO - used to be SDL_NOEVENT
lastResize.type = SDL_FIRSTEVENT;
}
//update input state (??)
inputMgr.updateState(false);
//Don't update the screen when the sound glitches.
if (fakeSoundEffectState.state >> 8) {
SDL_Delay(1000 / FPS);
continue;
}
//Clear the screen.
SDL_SetRenderDrawColor(&renderer, 0, 0, 0, 255);
SDL_RenderClear(&renderer);
const int rows = SCREEN_HEIGHT / fontHeight - 1;
for (int row = 0; row < rows; row++) {
//Generate some random text.
int m = int(float(rand()) / float(randMaxPlusOne) * 56.0f);
int carry = 0;
for (int i = 0; i < 60; i++) {
int c = s0[59 - i] + carry;
if (i <= m) c += int(float(rand()) / float(randMaxPlusOne) * 15.0f);
carry = 0;
while (c >= 15) {
c -= 15;
carry++;
}
s0[59 - i] = c;
}
int lp;
for (lp = 0; s0[lp]; lp++) {
char c = s0[lp] - 4;
if (c >= 0 && c < 10) {
s1[lp] = c + '0';
} else if (c == 10) {
s1[lp] = '_';
} else {
break;
}
}
s1[lp] = 0;
if (lp < 60) s0[lp] = 4;
//Choose a random extension.
int extension = int(float(rand()) / float(randMaxPlusOne) * 20.0f);
const char* ext = NULL;
int color = 0;
if (extension < numberOfExtensions) {
ext = extensions[extension];
if (ext[0] >= 0x10 && ext[0] < 0x20) {
color = ext[0] & 0xF;
ext++;
}
}
if (ext) {
s1[lp] = '.';
s1[lp + 1] = 0;
strcat(s1 + lp, ext);
lp = strlen(s1);
}
//Choose a random color.
if (extension >= numberOfExtensions) {
int r = int(float(rand()) / float(randMaxPlusOne) * 10.0f);
if (r <= 2) color = r;
}
bool isLink = int(float(rand()) / float(randMaxPlusOne) * 10.0f) == 0;
//Choose a random user.
const char* user = users[int(float(rand()) / float(randMaxPlusOne) * float(numberOfUsers))];
//Choose a random permission
const char* permission = NULL;
switch (int(float(rand()) / float(randMaxPlusOne) * 10.0f)) {
case 0:
permission = (color == 1 || color == 2) ? "rwxrwxrwx" : "rw-rw-rw-";
break;
case 1:
permission = (color == 1 || color == 2) ? "rwx------" : "rw-------";
break;
default:
permission = (color == 1 || color == 2) ? "rwxr-xr-x" : "rw-r--r--";
break;
}
//Choose a random size
int size = 0;
if (isLink) {
size = 3 + lp;
} else if (color == 2) {
size = 4096;
} else {
size = int(float(rand()) / float(randMaxPlusOne) * 10000.0f);
}
int num = 1;
if (color == 2) {
num = int(float(rand()) / float(randMaxPlusOne) * 10.0f);
}
//Choose a random date
char date[8];
{
int d = int(float(rand()) / float(randMaxPlusOne) * 365.0f);
int m = 0;
while (d >= numOfDays[m]) {
d -= numOfDays[m];
m++;
}
sprintf(date, "%s %2d", months[m], d + 1);
}
//Choose a random year or time
char year[8];
if (int(float(rand()) / float(randMaxPlusOne) * 10.0f) == 0) {
sprintf(year, "%d", 1970 + int(float(rand()) / float(randMaxPlusOne) * 100.0f));
} else {
sprintf(year, "%02d:%02d", int(float(rand()) / float(randMaxPlusOne) * 24.0f), int(float(rand()) / float(randMaxPlusOne) * 60.0f));
}
//Put them together
if (isLink) {
sprintf(s2, "l%s %d %-6s %-6s %4d %s %5s \x13.%s\x10 -> %c../%s",
permission, num, user, user, size, date, year, s1, 0x10 + color, s1
);
} else {
sprintf(s2, "%c%s %d %-6s %-6s %4d %s %5s %c.%s",
color == 2 ? 'd' : '-', permission, num, user, user, size, date, year, 0x10 + color, s1
);
}
//Show text
scamDrawText(imageManager, renderer, cache, fontWidth, fontHeight, 0, row * fontHeight, s2);
}
//Show a caret.
if (t & 0x10) {
SDL_Rect r = { 0, rows * fontHeight, fontWidth, fontHeight };
SDL_SetRenderDrawColor(&renderer, 0x80, 0xFF, 0, 0xFF);
SDL_RenderDrawRect(&renderer, &r);
}
//display it
flipScreen(renderer);
SDL_Delay(1000 / FPS);
}
Mix_SetPostMix(NULL, NULL);
return ret;
}
#else
// Only play a sound.
bool easterEggScreen(ImageManager& imageManager, SDL_Renderer& renderer) {
//play a sound effect
getSoundManager()->playSound("hit");
return true;
}
#endif
diff --git a/src/Functions.cpp b/src/Functions.cpp
index 82df602..cf99121 100644
--- a/src/Functions.cpp
+++ b/src/Functions.cpp
@@ -1,1594 +1,1610 @@
/*
* 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 <stdio.h>
#include <math.h>
#include <locale.h>
#include <string.h>
#include <algorithm>
#include <SDL.h>
#include <SDL_mixer.h>
#include <SDL_syswm.h>
#include <SDL_ttf.h>
#include <string>
#include "Globals.h"
#include "Functions.h"
#include "FontManager.h"
#include "FileManager.h"
#include "GameObjects.h"
#include "LevelPack.h"
#include "TitleMenu.h"
#include "OptionsMenu.h"
#include "CreditsMenu.h"
#include "LevelEditSelect.h"
#include "LevelEditor.h"
#include "Game.h"
#include "LevelPlaySelect.h"
#include "Addons.h"
#include "InputManager.h"
#include "ImageManager.h"
#include "MusicManager.h"
#include "SoundManager.h"
#include "ScriptExecutor.h"
#include "LevelPackManager.h"
#include "ThemeManager.h"
#include "GUIListBox.h"
#include "GUIOverlay.h"
#include "StatisticsManager.h"
#include "StatisticsScreen.h"
#include "Cursors.h"
#include "ScriptAPI.h"
#include "libs/tinyformat/tinyformat.h"
#include "libs/tinygettext/tinygettext.hpp"
#include "libs/tinygettext/log.hpp"
#include "libs/findlocale/findlocale.h"
using namespace std;
#ifdef WIN32
#include <windows.h>
#include <shellapi.h>
#include <shlobj.h>
#define TO_UTF8(SRC, DEST) WideCharToMultiByte(CP_UTF8, 0, SRC, -1, DEST, sizeof(DEST), NULL, NULL)
#define TO_UTF16(SRC, DEST) MultiByteToWideChar(CP_UTF8, 0, SRC, -1, DEST, sizeof(DEST)/sizeof(DEST[0]))
#else
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#endif
//Initialise the musicManager.
//The MusicManager is used to prevent loading music files multiple times and for playing/fading music.
MusicManager musicManager;
//Initialise the soundManager.
//The SoundManager is used to keep track of the sfx in the game.
SoundManager soundManager;
//Initialise the levelPackManager.
//The LevelPackManager is used to prevent loading levelpacks multiple times and for the game to know which levelpacks there are.
LevelPackManager levelPackManager;
//Map containing changed settings using command line arguments.
map<string,string> tmpSettings;
//Pointer to the settings object.
//It is used to load and save the settings file and change the settings.
Settings* settings=nullptr;
SDL_Renderer* sdlRenderer=nullptr;
std::string ngettext(const std::string& message,const std::string& messageplural,int num) {
if (dictionaryManager) {
return dictionaryManager->get_dictionary().translate_plural(message, messageplural, num);
} else {
//Assume it's of English plural rule
return (num != 1) ? messageplural : message;
}
}
void applySurface(int x,int y,SDL_Surface* source,SDL_Surface* dest,SDL_Rect* clip){
//The offset is needed to draw at the right location.
SDL_Rect offset;
offset.x=x;
offset.y=y;
//Let SDL do the drawing of the surface.
SDL_BlitSurface(source,clip,dest,&offset);
}
void drawRect(int x,int y,int w,int h,SDL_Renderer& renderer,Uint32 color){
//NOTE: We let SDL_gfx render it.
SDL_SetRenderDrawColor(&renderer,color >> 24,color >> 16,color >> 8,255);
//rectangleRGBA(&renderer,x,y,x+w,y+h,color >> 24,color >> 16,color >> 8,255);
const SDL_Rect r{x,y,w,h};
SDL_RenderDrawRect(&renderer,&r);
}
//Draw a box with anti-aliased borders using SDL_gfx.
void drawGUIBox(int x,int y,int w,int h,SDL_Renderer& renderer,Uint32 color){
SDL_Renderer* rd = &renderer;
//FIXME, this may get the wrong color on system with different endianness.
//Fill content's background color from function parameter
SDL_SetRenderDrawColor(rd,color >> 24,color >> 16,color >> 8,color >> 0);
{
const SDL_Rect r{x+1,y+1,w-2,h-2};
SDL_RenderFillRect(rd, &r);
}
SDL_SetRenderDrawColor(rd,0,0,0,255);
//Draw first black borders around content and leave 1 pixel in every corner
SDL_RenderDrawLine(rd,x+1,y,x+w-2,y);
SDL_RenderDrawLine(rd,x+1,y+h-1,x+w-2,y+h-1);
SDL_RenderDrawLine(rd,x,y+1,x,y+h-2);
SDL_RenderDrawLine(rd,x+w-1,y+1,x+w-1,y+h-2);
//Fill the corners with transperent color to create anti-aliased borders
SDL_SetRenderDrawColor(rd,0,0,0,160);
SDL_RenderDrawPoint(rd,x,y);
SDL_RenderDrawPoint(rd,x,y+h-1);
SDL_RenderDrawPoint(rd,x+w-1,y);
SDL_RenderDrawPoint(rd,x+w-1,y+h-1);
//Draw second lighter border around content
SDL_SetRenderDrawColor(rd,0,0,0,64);
{
const SDL_Rect r{x+1,y+1,w-2,h-2};
SDL_RenderDrawRect(rd,&r);
}
SDL_SetRenderDrawColor(rd,0,0,0,50);
//Create anti-aliasing in corners of second border
SDL_RenderDrawPoint(rd,x+1,y+1);
SDL_RenderDrawPoint(rd,x+1,y+h-2);
SDL_RenderDrawPoint(rd,x+w-2,y+1);
SDL_RenderDrawPoint(rd,x+w-2,y+h-2);
}
void drawLine(int x1,int y1,int x2,int y2,SDL_Renderer& renderer,Uint32 color){
SDL_SetRenderDrawColor(&renderer,color >> 24,color >> 16,color >> 8,255);
//NOTE: We let SDL_gfx render it.
//lineRGBA(&renderer,x1,y1,x2,y2,color >> 24,color >> 16,color >> 8,255);
SDL_RenderDrawLine(&renderer,x1,y1,x2,y2);
}
void drawLineWithArrow(int x1,int y1,int x2,int y2,SDL_Renderer& renderer,Uint32 color,int spacing,int offset,int xsize,int ysize){
//Draw line first
drawLine(x1,y1,x2,y2,renderer,color);
//calc delta and length
double dx=x2-x1;
double dy=y2-y1;
double length=sqrt(dx*dx+dy*dy);
if(length<0.001) return;
//calc the unit vector
dx/=length; dy/=length;
//Now draw arrows on it
for(double p=offset;p<length;p+=spacing){
drawLine(int(x1+p*dx+0.5),int(y1+p*dy+0.5),
int(x1+(p-xsize)*dx-ysize*dy+0.5),int(y1+(p-xsize)*dy+ysize*dx+0.5),renderer,color);
drawLine(int(x1+p*dx+0.5),int(y1+p*dy+0.5),
int(x1+(p-xsize)*dx+ysize*dy+0.5),int(y1+(p-xsize)*dy-ysize*dx+0.5),renderer,color);
}
}
ScreenData creationFailed() {
return ScreenData{ nullptr };
}
ScreenData createScreen(){
//Check if we are going fullscreen.
if(settings->getBoolValue("fullscreen"))
pickFullscreenResolution();
//Set the screen_width and height.
SCREEN_WIDTH=atoi(settings->getValue("width").c_str());
SCREEN_HEIGHT=atoi(settings->getValue("height").c_str());
//Update the camera.
camera.w=SCREEN_WIDTH;
camera.h=SCREEN_HEIGHT;
//Set the flags.
Uint32 flags = 0;
Uint32 currentFlags = SDL_GetWindowFlags(sdlWindow);
//#if !defined(ANDROID)
// flags |= SDL_DOUBLEBUF;
//#endif
if(settings->getBoolValue("fullscreen")) {
flags|=SDL_WINDOW_FULLSCREEN; //TODO with SDL2 we can also do SDL_WINDOW_FULLSCREEN_DESKTOP
}
else if(settings->getBoolValue("resizable"))
flags|=SDL_WINDOW_RESIZABLE;
//Create the window and renderer if they don't exist and check if there weren't any errors.
if (!sdlWindow && !sdlRenderer) {
SDL_CreateWindowAndRenderer(SCREEN_WIDTH, SCREEN_HEIGHT, flags, &sdlWindow, &sdlRenderer);
if(!sdlWindow || !sdlRenderer){
std::cerr << "FATAL ERROR: SDL_CreateWindowAndRenderer failed.\nError: " << SDL_GetError() << std::endl;
return creationFailed();
}
SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BlendMode::SDL_BLENDMODE_BLEND);
// White background so we see the menu on failure.
SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 255, 255);
} else if (sdlWindow) {
// Try changing to/from fullscreen
if(SDL_SetWindowFullscreen(sdlWindow, flags & SDL_WINDOW_FULLSCREEN) != 0) {
std::cerr << "WARNING: Failed to switch to fullscreen: " << SDL_GetError() << std::endl;
};
currentFlags = SDL_GetWindowFlags(sdlWindow);
// Change fullscreen resolution
if((currentFlags & SDL_WINDOW_FULLSCREEN ) || (currentFlags & SDL_WINDOW_FULLSCREEN_DESKTOP)) {
SDL_DisplayMode m{0,0,0,0,nullptr};
SDL_GetWindowDisplayMode(sdlWindow,&m);
m.w = SCREEN_WIDTH;
m.h = SCREEN_HEIGHT;
if(SDL_SetWindowDisplayMode(sdlWindow, &m) != 0) {
std::cerr << "WARNING: Failed to set display mode: " << SDL_GetError() << std::endl;
}
} else {
SDL_SetWindowSize(sdlWindow, SCREEN_WIDTH, SCREEN_HEIGHT);
}
}
//Now configure the newly created window (if windowed).
if(settings->getBoolValue("fullscreen")==false)
configureWindow();
//Set the the window caption.
SDL_SetWindowTitle(sdlWindow, ("Me and My Shadow "+version).c_str());
//FIXME Seems to be obsolete
// SDL_EnableUNICODE(1);
//Nothing went wrong so return true.
return ScreenData{sdlRenderer};
}
-vector<_res> getResolutionList(){
+vector<SDL_Point> getResolutionList(){
//Vector that will hold the resolutions to choose from.
- vector<_res> resolutionList;
+ vector<SDL_Point> resolutionList;
//Enumerate available resolutions using SDL_ListModes()
//NOTE: we enumerate fullscreen resolutions because
// windowed resolutions always can be arbitrary
if(resolutionList.empty()){
// SDL_Rect **modes=SDL_ListModes(NULL,SDL_FULLSCREEN|SCREEN_FLAGS|SDL_ANYFORMAT);
//NOTe - currently only using the first display (0)
int numDisplayModes = SDL_GetNumDisplayModes(0);
if(numDisplayModes < 1){
cerr<<"ERROR: Can't enumerate available screen resolutions."
" Use predefined screen resolutions list instead."<<endl;
- static const _res predefinedResolutionList[] = {
+ static const SDL_Point predefinedResolutionList[] = {
{800,600},
{1024,600},
{1024,768},
{1152,864},
{1280,720},
{1280,768},
{1280,800},
{1280,960},
{1280,1024},
{1360,768},
{1366,768},
{1440,900},
{1600,900},
{1600,1200},
{1680,1080},
{1920,1080},
{1920,1200},
{2560,1440},
{3840,2160}
};
//Fill the resolutionList.
- for(unsigned int i=0;i<sizeof(predefinedResolutionList)/sizeof(_res);i++){
+ for (unsigned int i = 0; i<sizeof(predefinedResolutionList) / sizeof(SDL_Point); i++){
resolutionList.push_back(predefinedResolutionList[i]);
}
}else{
//Fill the resolutionList.
for(int i=0;i < numDisplayModes; ++i){
SDL_DisplayMode mode;
int error = SDL_GetDisplayMode(0, i, &mode);
if(error < 0) {
//We failed to get a display mode. Should we crash here?
std::cerr << "ERROR: Failed to get display mode " << i << " " << std::endl;
}
//Check if the resolution is higher than the minimum (800x600).
if(mode.w >= 800 && mode.h >= 600){
- _res res={mode.w, mode.h};
+ SDL_Point res = { mode.w, mode.h };
resolutionList.push_back(res);
}
}
//Reverse it so that we begin with the lowest resolution.
reverse(resolutionList.begin(),resolutionList.end());
}
}
//Return the resolution list.
return resolutionList;
}
void pickFullscreenResolution(){
//Get the resolution list.
- vector<_res> resolutionList=getResolutionList();
-
+ vector<SDL_Point> resolutionList=getResolutionList();
+
//The resolution that will hold the final result, we start with the minimum (800x600).
- _res closestMatch={800,600};
+ SDL_Point closestMatch = { 800, 600 };
int width=atoi(getSettings()->getValue("width").c_str());
- //int height=atoi(getSettings()->getValue("height").c_str());
-
+ int height=atoi(getSettings()->getValue("height").c_str());
+ int delta = 0x40000000;
+
//Now loop through the resolutionList.
- for(int i=0;i<(int)resolutionList.size();i++){
- //The delta between the closestMatch and the resolution from the list.
- int dM=(closestMatch.w-resolutionList[i].w);
- //The delta between the target width and the resolution from the list.
- int dT=(width-resolutionList[i].w);
-
- //Since the resolutions are getting higher the lower (more negative) the further away it is.
- //That's why we check if the deltaMatch is lower than the the deltaTarget.
- if((dM)<(dT)){
- closestMatch.w=resolutionList[i].w;
- closestMatch.h=resolutionList[i].h;
- }
+ for (int i = 0; i < (int)resolutionList.size(); i++){
+ int dx = width - resolutionList[i].x;
+ if (dx < 0) dx = -dx;
+ int dy = height - resolutionList[i].y;
+ if (dy < 0) dy = -dy;
+
+ if (dx + dy < delta){
+ delta = dx + dy;
+ closestMatch.x = resolutionList[i].x;
+ closestMatch.y = resolutionList[i].y;
+ }
}
-
+
//Now set the resolution to the closest match.
char s[64];
- sprintf(s,"%d",closestMatch.w);
+ sprintf(s,"%d",closestMatch.x);
getSettings()->setValue("width",s);
- sprintf(s,"%d",closestMatch.h);
+ sprintf(s,"%d",closestMatch.y);
getSettings()->setValue("height",s);
}
void configureWindow(){
//We only need to configure the window if it's resizable.
if(!getSettings()->getBoolValue("resizable"))
return;
//We use a new function in SDL2 to restrict minimum window size
SDL_SetWindowMinimumSize(sdlWindow, 800, 600);
}
void onVideoResize(ImageManager& imageManager, SDL_Renderer &renderer){
//Check if the resize event isn't malformed.
if(event.window.data1<=0 || event.window.data2<=0)
return;
//Check the size limit.
//TODO: SDL2 porting note: This may break on systems non-X11 or Windows systems as the window size won't be limited
//there.
if(event.window.data1<800)
event.window.data1=800;
if(event.window.data2<600)
event.window.data2=600;
//Check if it really resizes.
if(SCREEN_WIDTH==event.window.data1 && SCREEN_HEIGHT==event.window.data2)
return;
char s[32];
//Set the new width and height.
SDL_snprintf(s,32,"%d",event.window.data1);
getSettings()->setValue("width",s);
SDL_snprintf(s,32,"%d",event.window.data2);
getSettings()->setValue("height",s);
//FIXME: THIS doesn't work properly.
//Do resizing.
SCREEN_WIDTH = event.window.data1;
SCREEN_HEIGHT = event.window.data2;
//Update the camera.
camera.w=SCREEN_WIDTH;
camera.h=SCREEN_HEIGHT;
//Tell the theme to resize.
if(!loadTheme(imageManager,renderer,""))
return;
//And let the currentState update it's GUI to the new resolution.
currentState->resize(imageManager, renderer);
}
ScreenData init(){
//Initialze SDL.
if(SDL_Init(SDL_INIT_EVERYTHING)==-1) {
std::cerr << "FATAL ERROR: SDL_Init failed\nError: " << SDL_GetError() << std::endl;
return creationFailed();
}
//Initialze SDL_mixer (audio).
//Note for SDL2 port: Changed frequency from 22050 to 44100.
//22050 caused some sound artifacts on my system, and I'm not sure
//why one would use it in this day and age anyhow.
//unless it's for compatability with some legacy system.
if(Mix_OpenAudio(44100,MIX_DEFAULT_FORMAT,2,1024)==-1){
std::cerr << "FATAL ERROR: Mix_OpenAudio failed\nError: " << Mix_GetError() << std::endl;
return creationFailed();
}
//Set the volume.
Mix_Volume(-1,atoi(settings->getValue("sound").c_str()));
//Increase the number of channels.
soundManager.setNumberOfChannels(48);
//Initialze SDL_ttf (fonts).
if(TTF_Init()==-1){
std::cerr << "FATAL ERROR: TTF_Init failed\nError: " << TTF_GetError() << std::endl;
return creationFailed();
}
//Create the screen.
ScreenData screenData(createScreen());
if(!screenData) {
return creationFailed();
}
//Load key config. Then initialize joystick support.
inputMgr.loadConfig();
inputMgr.openAllJoysitcks();
//Init tinygettext for translations for the right language
dictionaryManager = new tinygettext::DictionaryManager();
dictionaryManager->set_use_fuzzy(false);
dictionaryManager->add_directory(getDataPath()+"locale");
dictionaryManager->set_charset("UTF-8");
//Check if user have defined own language. If not, find it out for the player using findlocale
string lang=getSettings()->getValue("lang");
if(lang.length()>0){
printf("Locale set by user to %s\n",lang.c_str());
language=lang;
}else{
FL_Locale *locale;
FL_FindLocale(&locale,FL_MESSAGES);
printf("Locale isn't set by user: %s\n",locale->lang);
language=locale->lang;
if(locale->country!=NULL){
language+=string("_")+string(locale->country);
}
if(locale->variant!=NULL){
language+=string("@")+string(locale->variant);
}
FL_FreeLocale(&locale);
}
//Now set the language in the dictionaryManager.
dictionaryManager->set_language(tinygettext::Language::from_name(language));
+#ifdef WIN32
+ //Some ad-hoc fix for Windows since it accepts "zh-CN" but not "zh_CN"
+ std::string language2;
+ for (auto c : language) {
+ if (isalnum(c)) language2.push_back(c);
+ else if (c == '_') language2.push_back('-');
+ else break;
+ }
+ const char* languagePtr = language2.c_str();
+#else
+ const char* languagePtr = language.c_str();
+#endif
+
+ //Also set the language for tinyformat.
+ tfm::setLocale(languagePtr);
+
+ //Set time format.
+ setlocale(LC_TIME, languagePtr);
+
//Disable annoying 'Couldn't translate: blah blah blah'
tinygettext::Log::set_log_info_callback(NULL);
-
- //Set time format to the user-preference of the system.
- setlocale(LC_TIME,"");
//Create the types of blocks.
for(int i=0;i<TYPE_MAX;i++){
Game::blockNameMap[Game::blockName[i]]=i;
}
//Structure that holds the event type/name pair.
struct EventTypeName{
int type;
const char* name;
};
//Create the types of game object event types.
{
const EventTypeName types[]={
{GameObjectEvent_PlayerWalkOn,"playerWalkOn"},
{GameObjectEvent_PlayerIsOn,"playerIsOn"},
{GameObjectEvent_PlayerLeave,"playerLeave"},
{GameObjectEvent_OnCreate,"onCreate"},
{GameObjectEvent_OnEnterFrame,"onEnterFrame"},
{ GameObjectEvent_OnPlayerInteraction, "onPlayerInteraction" },
{GameObjectEvent_OnToggle,"onToggle"},
{GameObjectEvent_OnSwitchOn,"onSwitchOn"},
{GameObjectEvent_OnSwitchOff,"onSwitchOff"},
{0,NULL}
};
for(int i=0;types[i].name;i++){
Game::gameObjectEventNameMap[types[i].name]=types[i].type;
Game::gameObjectEventTypeMap[types[i].type]=types[i].name;
}
}
//Create the types of level event types.
{
const EventTypeName types[]={
{LevelEvent_OnCreate,"onCreate"},
{LevelEvent_OnSave,"onSave"},
{LevelEvent_OnLoad,"onLoad"},
{0,NULL}
};
for(int i=0;types[i].name;i++){
Game::levelEventNameMap[types[i].name]=types[i].type;
Game::levelEventTypeMap[types[i].type]=types[i].name;
}
}
//Nothing went wrong so we return true.
return screenData;
}
bool loadFonts(){
//Load the fonts.
//NOTE: This is a separate method because it will be called separately when re-initing in case of language change.
//NOTE2: Since the font fallback is implemented, the font will not be loaded again if call loadFonts() twice.
if (fontMgr) {
return true;
}
fontMgr = new FontManager;
fontMgr->loadFonts();
fontTitle = fontMgr->getFont("fontTitle");
fontGUI = fontMgr->getFont("fontGUI");
fontGUISmall = fontMgr->getFont("fontGUISmall");
fontText = fontMgr->getFont("fontText");
fontMono = fontMgr->getFont("fontMono");
if (fontTitle == NULL || fontGUI == NULL || fontGUISmall == NULL || fontText == NULL || fontMono == NULL){
printf("FATAL ERROR: Unable to load fonts!\n");
return false;
}
//Nothing went wrong so return true.
return true;
}
//Generate small arrows used for some GUI widgets.
static void generateArrows(SDL_Renderer& renderer){
TTF_Font* fontArrow = fontMgr->getFont("fontArrow");
arrowLeft1=textureFromText(renderer,*fontArrow,"<",objThemes.getTextColor(false));
arrowRight1=textureFromText(renderer,*fontArrow,">",objThemes.getTextColor(false));
arrowLeft2=textureFromText(renderer,*fontArrow,"<",objThemes.getTextColor(true));
arrowRight2=textureFromText(renderer,*fontArrow,">",objThemes.getTextColor(true));
}
bool loadTheme(ImageManager& imageManager,SDL_Renderer& renderer,std::string name){
//Load default fallback theme if it isn't loaded yet
if(objThemes.themeCount()==0){
if(objThemes.appendThemeFromFile(getDataPath()+"themes/Cloudscape/theme.mnmstheme", imageManager, renderer)==NULL){
printf("ERROR: Can't load default theme file\n");
return false;
}
}
//Resize background or load specific theme
bool success=true;
if(name==""||name.empty()){
objThemes.scaleToScreen();
}else{
string theme=processFileName(name);
if(objThemes.appendThemeFromFile(theme+"/theme.mnmstheme", imageManager, renderer)==NULL){
printf("ERROR: Can't load theme %s\n",theme.c_str());
success=false;
}
}
generateArrows(renderer);
//Everything went fine so return true.
return success;
}
static SDL_Cursor* loadCursor(const char* image[]){
int i,row,col;
//The array that holds the data (0=white 1=black)
Uint8 data[4*32];
//The array that holds the alpha mask (0=transparent 1=visible)
Uint8 mask[4*32];
//The coordinates of the hotspot of the cursor.
int hotspotX, hotspotY;
i=-1;
//Loop through the rows and columns.
//NOTE: We assume a cursor size of 32x32.
for(row=0;row<32;++row){
for(col=0; col<32;++col){
if(col % 8) {
data[i]<<=1;
mask[i]<<=1;
}else{
++i;
data[i]=mask[i]=0;
}
switch(image[4+row][col]){
case '+':
data[i] |= 0x01;
mask[i] |= 0x01;
break;
case '.':
mask[i] |= 0x01;
break;
default:
break;
}
}
}
//Get the hotspot x and y locations from the last line of the cursor.
sscanf(image[4+row],"%d,%d",&hotspotX,&hotspotY);
return SDL_CreateCursor(data,mask,32,32,hotspotX,hotspotY);
}
bool loadFiles(ImageManager& imageManager, SDL_Renderer& renderer){
//Load the fonts.
if(!loadFonts())
return false;
//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_RenderCopy(sdlRenderer, loadingTexture.get(), NULL, &loadingRect);
SDL_RenderPresent(sdlRenderer);
SDL_RenderClear(sdlRenderer);
}
musicManager.destroy();
//Load the music and play it.
if(musicManager.loadMusic((getDataPath()+"music/menu.music")).empty()){
printf("WARNING: Unable to load background music! \n");
}
musicManager.playMusic("menu",false);
//Load all the music lists from the data and user data path.
{
vector<string> musicLists=enumAllFiles((getDataPath()+"music/"),"list",true);
for(unsigned int i=0;i<musicLists.size();i++)
getMusicManager()->loadMusicList(musicLists[i]);
musicLists=enumAllFiles((getUserPath(USER_DATA)+"music/"),"list",true);
for(unsigned int i=0;i<musicLists.size();i++)
getMusicManager()->loadMusicList(musicLists[i]);
}
//Set the list to the configured one.
getMusicManager()->setMusicList(getSettings()->getValue("musiclist"));
//Check if music is enabled.
if(getSettings()->getBoolValue("music"))
getMusicManager()->setEnabled();
//Load the sound effects
soundManager.loadSound((getDataPath()+"sfx/jump.wav").c_str(),"jump");
soundManager.loadSound((getDataPath()+"sfx/hit.wav").c_str(),"hit");
soundManager.loadSound((getDataPath()+"sfx/checkpoint.wav").c_str(),"checkpoint");
soundManager.loadSound((getDataPath()+"sfx/swap.wav").c_str(),"swap");
soundManager.loadSound((getDataPath()+"sfx/toggle.ogg").c_str(),"toggle");
soundManager.loadSound((getDataPath()+"sfx/error.wav").c_str(),"error");
soundManager.loadSound((getDataPath()+"sfx/collect.wav").c_str(),"collect");
soundManager.loadSound((getDataPath()+"sfx/achievement.ogg").c_str(),"achievement");
//Load the cursor images from the Cursor.h file.
cursors[CURSOR_POINTER]=loadCursor(pointer);
cursors[CURSOR_CARROT]=loadCursor(ibeam);
cursors[CURSOR_DRAG]=loadCursor(closedhand);
cursors[CURSOR_SIZE_HOR]=loadCursor(size_hor);
cursors[CURSOR_SIZE_VER]=loadCursor(size_ver);
cursors[CURSOR_SIZE_FDIAG]=loadCursor(size_fdiag);
cursors[CURSOR_SIZE_BDIAG]=loadCursor(size_bdiag);
cursors[CURSOR_REMOVE]=loadCursor(remove_cursor);
cursors[CURSOR_POINTING_HAND] = loadCursor(pointing_hand);
//Set the default cursor right now.
SDL_SetCursor(cursors[CURSOR_POINTER]);
levelPackManager.destroy();
//Now sum up all the levelpacks.
vector<string> v=enumAllDirs(getDataPath()+"levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getDataPath()+"levelpacks/"+*i);
}
v=enumAllDirs(getUserPath(USER_DATA)+"levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getUserPath(USER_DATA)+"levelpacks/"+*i);
}
v=enumAllDirs(getUserPath(USER_DATA)+"custom/levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getUserPath(USER_DATA)+"custom/levelpacks/"+*i);
}
//Now we add a special levelpack that will contain the levels not in a levelpack.
LevelPack* levelsPack=new LevelPack;
levelsPack->levelpackName="Levels";
levelsPack->levelpackPath=LEVELS_PATH;
levelsPack->type=COLLECTION;
LevelPack* customLevelsPack=new LevelPack;
customLevelsPack->levelpackName="Custom Levels";
customLevelsPack->levelpackPath=CUSTOM_LEVELS_PATH;
customLevelsPack->type=COLLECTION;
//List the main levels and add them one for one.
v = enumAllFiles(getDataPath() + "levels/");
for (vector<string>::iterator i = v.begin(); i != v.end(); ++i){
levelsPack->addLevel(getDataPath() + "levels/" + *i);
levelsPack->setLocked(levelsPack->getLevelCount() - 1);
}
//List the addon levels and add them one for one.
v=enumAllFiles(getUserPath(USER_DATA)+"levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelsPack->addLevel(getUserPath(USER_DATA)+"levels/"+*i);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
}
//List the custom levels and add them one for one.
v=enumAllFiles(getUserPath(USER_DATA)+"custom/levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelsPack->addLevel(getUserPath(USER_DATA)+"custom/levels/"+*i);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
customLevelsPack->addLevel(getUserPath(USER_DATA)+"custom/levels/"+*i);
customLevelsPack->setLocked(customLevelsPack->getLevelCount()-1);
}
//Add them to the manager.
levelPackManager.addLevelPack(levelsPack);
levelPackManager.addLevelPack(customLevelsPack);
//Load statistics
statsMgr.loadPicture(renderer, imageManager);
statsMgr.registerAchievements(imageManager);
statsMgr.loadFile(getUserPath(USER_CONFIG)+"statistics");
//Do something ugly and slow
statsMgr.reloadCompletedLevelsAndAchievements();
statsMgr.reloadOtherAchievements();
//Load the theme, both menu and default.
//NOTE: Loading theme may fail and returning false would stop everything, default theme will be used instead.
if (!loadTheme(imageManager,renderer,getSettings()->getValue("theme"))){
getSettings()->setValue("theme","%DATA%/themes/Cloudscape");
saveSettings();
}
//Nothing failed so return true.
return true;
}
bool loadSettings(){
//Check the version of config file.
int version = 0;
std::string cfgV05 = getUserPath(USER_CONFIG) + "meandmyshadow_V0.5.cfg";
std::string cfgV04 = getUserPath(USER_CONFIG) + "meandmyshadow.cfg";
if (fileExists(cfgV05.c_str())) {
//We find a config file of current version.
version = 0x000500;
} else if (fileExists(cfgV04.c_str())) {
//We find a config file of V0.4 version or earlier.
copyFile(cfgV04.c_str(), cfgV05.c_str());
version = 0x000400;
} else {
//No config file found, just create a new one.
version = 0x000500;
}
settings=new Settings(cfgV05);
settings->parseFile(version);
//Now apply settings changed through command line arguments, if any.
map<string,string>::iterator it;
for(it=tmpSettings.begin();it!=tmpSettings.end();++it){
settings->setValue(it->first,it->second);
}
tmpSettings.clear();
//Always return true?
return true;
}
bool saveSettings(){
return settings->save();
}
Settings* getSettings(){
return settings;
}
MusicManager* getMusicManager(){
return &musicManager;
}
SoundManager* getSoundManager(){
return &soundManager;
}
LevelPackManager* getLevelPackManager(){
return &levelPackManager;
}
void flipScreen(SDL_Renderer& renderer){
// Render the data from the back buffer.
SDL_RenderPresent(&renderer);
}
void clean(){
//Save statistics
statsMgr.saveFile(getUserPath(USER_CONFIG)+"statistics");
//We delete the settings.
if(settings){
delete settings;
settings=NULL;
}
//Delete dictionaryManager.
delete dictionaryManager;
//Get rid of the currentstate.
//NOTE: The state is probably already deleted by the changeState function.
if(currentState)
delete currentState;
//Destroy the GUI if present.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//These calls to destroy makes sure stuff is
//deleted before SDL is uninitialised (as these managers are stack allocated
//globals.)
//Destroy the musicManager.
musicManager.destroy();
//Destroy all sounds
soundManager.destroy();
//Destroy the cursors.
for(int i=0;i<CURSOR_MAX;i++){
SDL_FreeCursor(cursors[i]);
cursors[i]=NULL;
}
//Destroy the levelPackManager.
levelPackManager.destroy();
levels=NULL;
//Close all joysticks.
inputMgr.closeAllJoysticks();
//Close the fonts and quit SDL_ttf.
delete fontMgr;
fontMgr = NULL;
TTF_Quit();
//Remove the temp surface.
SDL_DestroyRenderer(sdlRenderer);
SDL_DestroyWindow(sdlWindow);
arrowLeft1.reset(nullptr);
arrowLeft2.reset(nullptr);
arrowRight1.reset(nullptr);
arrowRight2.reset(nullptr);
//Stop audio.and quit
Mix_CloseAudio();
//SDL2 porting note. Not sure why this was only done on apple.
//#ifndef __APPLE__
Mix_Quit();
//#endif
//And finally quit SDL.
SDL_Quit();
}
void setNextState(int newstate){
//Only change the state when we aren't already exiting.
if(nextState!=STATE_EXIT){
nextState=newstate;
}
}
void changeState(ImageManager& imageManager, SDL_Renderer& renderer, int fade){
//Check if there's a nextState.
if(nextState!=STATE_NULL){
//Fade out, if fading is enabled.
if (currentState && settings->getBoolValue("fading")) {
for (; fade >= 0; fade -= 17) {
currentState->render(imageManager, renderer);
//TODO: Shouldn't the gamestate take care of rendering the GUI?
if (GUIObjectRoot) GUIObjectRoot->render(renderer);
dimScreen(renderer, static_cast<Uint8>(255 - fade));
//draw new achievements (if any) as overlay
statsMgr.render(imageManager, renderer);
flipScreen(renderer);
SDL_Delay(1000/FPS);
}
}
//Delete the currentState.
delete currentState;
currentState=NULL;
//Set the currentState to the nextState.
stateID=nextState;
nextState=STATE_NULL;
//Init the state.
switch(stateID){
case STATE_GAME:
{
currentState=NULL;
Game* game=new Game(renderer, imageManager);
currentState=game;
//Check if we should load record file or a level.
if(!Game::recordFile.empty()){
game->loadRecord(imageManager,renderer,Game::recordFile.c_str());
Game::recordFile.clear();
}else{
game->loadLevel(imageManager,renderer,levels->getLevelFile());
levels->saveLevelProgress();
}
}
break;
case STATE_MENU:
currentState=new Menu(imageManager, renderer);
break;
case STATE_LEVEL_SELECT:
currentState=new LevelPlaySelect(imageManager, renderer);
break;
case STATE_LEVEL_EDIT_SELECT:
currentState=new LevelEditSelect(imageManager, renderer);
break;
case STATE_LEVEL_EDITOR:
{
currentState=NULL;
LevelEditor* levelEditor=new LevelEditor(renderer, imageManager);
currentState=levelEditor;
//Load the selected level.
levelEditor->loadLevel(imageManager,renderer,levels->getLevelFile());
}
break;
case STATE_OPTIONS:
currentState=new Options(imageManager, renderer);
break;
case STATE_ADDONS:
currentState=new Addons(renderer, imageManager);
break;
case STATE_CREDITS:
currentState=new Credits(imageManager,renderer);
break;
case STATE_STATISTICS:
currentState=new StatisticsScreen(imageManager,renderer);
break;
}
//NOTE: STATE_EXIT isn't mentioned, meaning that currentState is null.
//This way the game loop will break and the program will exit.
}
}
void musicStoppedHook(){
//We just call the musicStopped method of the MusicManager.
musicManager.musicStopped();
}
void channelFinishedHook(int channel){
soundManager.channelFinished(channel);
}
bool checkCollision(const SDL_Rect& a,const SDL_Rect& b){
//Check if the left side of box a isn't past the right side of b.
if(a.x>=b.x+b.w){
return false;
}
//Check if the right side of box a isn't left of the left side of b.
if(a.x+a.w<=b.x){
return false;
}
//Check if the top side of box a isn't under the bottom side of b.
if(a.y>=b.y+b.h){
return false;
}
//Check if the bottom side of box a isn't above the top side of b.
if(a.y+a.h<=b.y){
return false;
}
//We have collision.
return true;
}
bool pointOnRect(const SDL_Rect& point, const SDL_Rect& rect) {
if(point.x >= rect.x && point.x < rect.x + rect.w
&& point.y >= rect.y && point.y < rect.y + rect.h) {
return true;
}
return false;
}
int parseArguments(int argc, char** argv){
//Loop through all arguments.
//We start at one since 0 is the command itself.
for(int i=1;i<argc;i++){
string argument=argv[i];
//Check if the argument is the data-dir.
if(argument=="--data-dir"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Configure the dataPath with the given path.
dataPath=argv[i];
if(!getDataPath().empty()){
char c=dataPath[dataPath.size()-1];
if(c!='/'&&c!='\\') dataPath+="/";
}
}else if(argument=="--user-dir"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Configure the userPath with the given path.
userPath=argv[i];
if(!userPath.empty()){
char c=userPath[userPath.size()-1];
if(c!='/'&&c!='\\') userPath+="/";
}
}else if(argument=="-f" || argument=="-fullscreen" || argument=="--fullscreen"){
tmpSettings["fullscreen"]="1";
}else if(argument=="-w" || argument=="-windowed" || argument=="--windowed"){
tmpSettings["fullscreen"]="0";
}else if(argument=="-mv" || argument=="-music" || argument=="--music"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Now set the music volume.
tmpSettings["music"]=argv[i];
}else if(argument=="-sv" || argument=="-sound" || argument=="--sound"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Now set sound volume.
tmpSettings["sound"]=argv[i];
}else if(argument=="-set" || argument=="--set"){
//We need a second and a third argument so we increase i.
i+=2;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//And set the setting.
tmpSettings[argv[i-1]]=argv[i];
}else if(argument=="-v" || argument=="-version" || argument=="--version"){
//Print the version.
printf("%s\n",version.c_str());
return 0;
}else if(argument=="-h" || argument=="-help" || argument=="--help"){
//If the help is requested we'll return false without printing an error.
//This way the usage/help text will be printed.
return -1;
}else{
//Any other argument is unknow so we return false.
printf("ERROR: Unknown argument %s\n\n",argument.c_str());
return -1;
}
}
//If everything went well we can return true.
return 1;
}
//Special structure that will recieve the GUIEventCallbacks of the messagebox.
struct msgBoxHandler:public GUIEventCallback{
public:
//Integer containing the ret(urn) value of the messageBox.
int ret;
public:
//Constructor.
msgBoxHandler():ret(0){}
void GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Make sure it's a click event.
if(eventType==GUIEventClick){
//Set the return value.
ret=obj->value;
//After a click event we can delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
}
};
msgBoxResult msgBox(ImageManager& imageManager,SDL_Renderer& renderer, const string& prompt,msgBoxButtons buttons,const string& title){
//Create the event handler.
msgBoxHandler objHandler;
//Create the GUIObjectRoot, the height and y location is temp.
//It depends on the content what it will be.
GUIObject* root=new GUIFrame(imageManager,renderer,(SCREEN_WIDTH-600)/2,200,600,200,title.c_str());
//Integer containing the current y location used to grow dynamic depending on the content.
int y=50;
//Now process the prompt.
{
//NOTE: We shouldn't modify the cotents in the c_str() of a string,
//since it's said that at least in g++ the std::string is copy-on-write
//hence if we modify the content it may break
//The copy of the prompt.
std::vector<char> copyOfPrompt(prompt.begin(), prompt.end());
//Append another '\0' to it.
copyOfPrompt.push_back(0);
//Pointer to the string.
char* lps = &(copyOfPrompt[0]);
//Pointer to a character.
char* lp=NULL;
//The list of labels.
std::vector<GUIObject*> labels;
//We keep looping forever.
//The only way out is with the break statement.
for(;;){
//As long as it's still the same sentence we continue.
//It will stop when there's a newline or end of line.
for(lp=lps;*lp!='\n'&&*lp!='\r'&&*lp!=0;lp++);
//Store the character we stopped on. (End or newline)
char c=*lp;
//Set the character in the string to 0, making lps a string containing one sentence.
*lp=0;
//Add a GUIObjectLabel with the sentence.
GUIObject *label = new GUILabel(imageManager, renderer, 0, y, root->width, 25, lps, 0, true, true, GUIGravityCenter);
labels.push_back(label);
root->addChild(label);
//Calculate the width of the text.
int w = 0;
TTF_SizeUTF8(fontText, lps, &w, NULL);
w += 20;
if (w > root->width) root->width = w;
//Increase y with 25, about the height of the text.
y+=25;
//Check the stored character if it was a stop.
if(c==0){
//It was so break out of the for loop.
lps=lp;
break;
}
//It wasn't meaning more will follow.
//We set lps to point after the "newline" forming a new string.
lps=lp+1;
}
//Shrink the dialog if it's too big.
if (root->width > SCREEN_WIDTH - 20) root->width = SCREEN_WIDTH - 20;
root->left = (SCREEN_WIDTH - root->width) / 2;
//Move labels to their correct locations.
for (auto label : labels) {
label->width = root->width;
}
}
//Add 70 to y to leave some space between the content and the buttons.
y+=70;
//Recalc the size of the message box.
root->top=(SCREEN_HEIGHT-y)/2;
root->height=y;
//Now we need to add the buttons.
//Integer containing the number of buttons to add.
int count=0;
//Array with the return codes for the buttons.
int value[3]={0};
//Array containing the captation for the buttons.
string button[3]={"","",""};
switch(buttons){
case MsgBoxOKCancel:
count=2;
button[0]=_("OK");value[0]=MsgBoxOK;
button[1]=_("Cancel");value[1]=MsgBoxCancel;
break;
case MsgBoxAbortRetryIgnore:
count=3;
button[0]=_("Abort");value[0]=MsgBoxAbort;
button[1]=_("Retry");value[1]=MsgBoxRetry;
button[2]=_("Ignore");value[2]=MsgBoxIgnore;
break;
case MsgBoxYesNoCancel:
count=3;
button[0]=_("Yes");value[0]=MsgBoxYes;
button[1]=_("No");value[1]=MsgBoxNo;
button[2]=_("Cancel");value[2]=MsgBoxCancel;
break;
case MsgBoxYesNo:
count=2;
button[0]=_("Yes");value[0]=MsgBoxYes;
button[1]=_("No");value[1]=MsgBoxNo;
break;
case MsgBoxRetryCancel:
count=2;
button[0]=_("Retry");value[0]=MsgBoxRetry;
button[1]=_("Cancel");value[1]=MsgBoxCancel;
break;
default:
count=1;
button[0]=_("OK");value[0]=MsgBoxOK;
break;
}
//Now we start making the buttons.
{
//Reduce y so that the buttons fit inside the frame.
y-=40;
double places[3]={0.0};
if(count==1){
places[0]=0.5;
}else if(count==2){
places[0]=0.35;
places[1]=0.65;
}else if(count==3){
places[0]=0.25;
places[1]=0.5;
places[2]=0.75;
}
std::vector<GUIButton*> buttons;
//Loop to add the buttons.
for(int i=0;i<count;i++){
GUIButton* obj = new GUIButton(imageManager, renderer, root->width*places[i], y, -1, 36, button[i].c_str(), value[i], true, true, GUIGravityCenter);
obj->eventCallback=&objHandler;
buttons.push_back(obj);
root->addChild(obj);
}
//Update widgets
for (int i = 0; i < count; i++) {
buttons[i]->render(renderer, 0, 0, false);
}
bool overlap = false;
//Check if they overlap
if (buttons[0]->left - buttons[0]->gravityX < 5 ||
buttons[count - 1]->left - buttons[count - 1]->gravityX + buttons[count - 1]->width > root->width - 5)
{
overlap = true;
} else {
for (int i = 0; i < count - 1; i++) {
if (buttons[i]->left - buttons[i]->gravityX + buttons[i]->width >= buttons[i + 1]->left - buttons[i + 1]->gravityX) {
overlap = true;
break;
}
}
}
//Shrink the font size if any buttons are overlap
if (overlap) {
for (int i = 0; i < count; i++) {
buttons[i]->smallFont = true;
buttons[i]->width = -1;
}
}
}
//Now we dim the screen and keep the GUI rendering/updating.
GUIOverlay* overlay=new GUIOverlay(renderer,root);
overlay->keyboardNavigationMode = LeftRightFocus | UpDownFocus | TabFocus | ((count == 1) ? 0 : ReturnControls);
overlay->enterLoop(imageManager, renderer, true, count == 1);
//And return the result.
return (msgBoxResult)objHandler.ret;
}
// A helper function to read a character from utf8 string
// s: the string
// p [in,out]: the position
// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
int utf8ReadForward(const char* s, int& p) {
int ch = (unsigned char)s[p];
if (ch < 0x80){
if (ch) p++;
return ch;
} else if (ch < 0xC0){
// skip invalid characters
while (((unsigned char)s[p] & 0xC0) == 0x80) p++;
return -1;
} else if (ch < 0xE0){
int c2 = (unsigned char)s[++p];
if ((c2 & 0xC0) != 0x80) return -1;
ch = ((ch & 0x1F) << 6) | (c2 & 0x3F);
p++;
return ch;
} else if (ch < 0xF0){
int c2 = (unsigned char)s[++p];
if ((c2 & 0xC0) != 0x80) return -1;
int c3 = (unsigned char)s[++p];
if ((c3 & 0xC0) != 0x80) return -1;
ch = ((ch & 0xF) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
p++;
return ch;
} else if (ch < 0xF8){
int c2 = (unsigned char)s[++p];
if ((c2 & 0xC0) != 0x80) return -1;
int c3 = (unsigned char)s[++p];
if ((c3 & 0xC0) != 0x80) return -1;
int c4 = (unsigned char)s[++p];
if ((c4 & 0xC0) != 0x80) return -1;
ch = ((ch & 0x7) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F);
if (ch >= 0x110000) ch = -1;
p++;
return ch;
} else {
p++;
return -1;
}
}
// A helper function to read a character backward from utf8 string (experimental)
// s: the string
// p [in,out]: the position
// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
int utf8ReadBackward(const char* s, int& p) {
if (p <= 0) return 0;
do {
p--;
} while (p > 0 && ((unsigned char)s[p] & 0xC0) == 0x80);
int tmp = p;
return utf8ReadForward(s, tmp);
}
#ifndef WIN32
// ad-hoc function to check if a program is installed
static bool programExists(const std::string& program) {
std::string p = tfm::format("which \"%s\" 2>&1", program);
const int BUFSIZE = 128;
char buf[BUFSIZE];
FILE *fp;
if ((fp = popen(p.c_str(), "r")) == NULL) {
return false;
}
while (fgets(buf, BUFSIZE, fp) != NULL) {
// Drop all outputs since 'which' returns -1 when the program is not found
}
if (pclose(fp)) {
return false;
}
return true;
}
#endif
void openWebsite(const std::string& url) {
#ifdef WIN32
wchar_t ws[4096];
TO_UTF16(url.c_str(), ws);
SDL_SysWMinfo info = {};
SDL_VERSION(&info.version);
SDL_GetWindowWMInfo(sdlWindow, &info);
ShellExecuteW(info.info.win.window, L"open", ws, NULL, NULL, SW_SHOW);
#else
static int method = -1;
// Some of these methods are copied from https://stackoverflow.com/questions/5116473/
const char* methods[] = {
"xdg-open", "xdg-open \"%s\"",
"gnome-open", "gnome-open \"%s\"",
"kde-open", "kde-open \"%s\"",
"open", "open \"%s\"",
"python", "python -m webbrowser \"%s\"",
"sensible-browser", "sensible-browser \"%s\"",
"x-www-browser", "x-www-browser \"%s\"",
NULL,
};
if (method < 0) {
for (method = 0; methods[method]; method += 2) {
if (programExists(methods[method])) break;
}
}
if (methods[method]) {
std::string p = tfm::format(methods[method + 1], url);
system(p.c_str());
} else {
fprintf(stderr, "TODO: openWebsite is not implemented on your system\n");
}
#endif
}
std::string appendURLToLicense(const std::string& license) {
// if the license doesn't include url, try to detect it from a predefined list
if (license.find("://") == std::string::npos) {
std::string normalized;
for (char c : license) {
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')) {
normalized.push_back(c);
} else if (c >= 'a' && c <= 'z') {
normalized.push_back(c + ('A' - 'a'));
}
}
const char* licenses[] = {
// AGPL
"AGPL1", "AGPLV1", NULL, "http://www.affero.org/oagpl.html",
"AGPL2", "AGPLV2", NULL, "http://www.affero.org/agpl2.html",
"AGPL", NULL, "https://gnu.org/licenses/agpl.html",
// LGPL
"LGPL21", "LGPLV21", NULL, "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html",
"LGPL2", "LGPLV2", NULL, "https://www.gnu.org/licenses/old-licenses/lgpl-2.0.html",
"LGPL", NULL, "https://www.gnu.org/copyleft/lesser.html",
// GPL
"GPL1", "GPLV1", NULL, "https://www.gnu.org/licenses/old-licenses/gpl-1.0.html",
"GPL2", "GPLV2", NULL, "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html",
"GPL", NULL, "https://gnu.org/licenses/gpl.html",
// CC BY-NC-ND
"CCBYNCND1", "CCBYNDNC1", NULL, "https://creativecommons.org/licenses/by-nd-nc/1.0",
"CCBYNCND25", "CCBYNDNC25", NULL, "https://creativecommons.org/licenses/by-nc-nd/2.5",
"CCBYNCND2", "CCBYNDNC2", NULL, "https://creativecommons.org/licenses/by-nc-nd/2.0",
"CCBYNCND3", "CCBYNDNC3", NULL, "https://creativecommons.org/licenses/by-nc-nd/3.0",
"CCBYNCND", "CCBYNDNC", NULL, "https://creativecommons.org/licenses/by-nc-nd/4.0",
// CC BY-NC-SA
"CCBYNCSA1", NULL, "https://creativecommons.org/licenses/by-nc-sa/1.0",
"CCBYNCSA25", NULL, "https://creativecommons.org/licenses/by-nc-sa/2.5",
"CCBYNCSA2", NULL, "https://creativecommons.org/licenses/by-nc-sa/2.0",
"CCBYNCSA3", NULL, "https://creativecommons.org/licenses/by-nc-sa/3.0",
"CCBYNCSA", NULL, "https://creativecommons.org/licenses/by-nc-sa/4.0",
// CC BY-ND
"CCBYND1", NULL, "https://creativecommons.org/licenses/by-nd/1.0",
"CCBYND25", NULL, "https://creativecommons.org/licenses/by-nd/2.5",
"CCBYND2", NULL, "https://creativecommons.org/licenses/by-nd/2.0",
"CCBYND3", NULL, "https://creativecommons.org/licenses/by-nd/3.0",
"CCBYND", NULL, "https://creativecommons.org/licenses/by-nd/4.0",
// CC BY-NC
"CCBYNC1", NULL, "https://creativecommons.org/licenses/by-nc/1.0",
"CCBYNC25", NULL, "https://creativecommons.org/licenses/by-nc/2.5",
"CCBYNC2", NULL, "https://creativecommons.org/licenses/by-nc/2.0",
"CCBYNC3", NULL, "https://creativecommons.org/licenses/by-nc/3.0",
"CCBYNC", NULL, "https://creativecommons.org/licenses/by-nc/4.0",
// CC BY-SA
"CCBYSA1", NULL, "https://creativecommons.org/licenses/by-sa/1.0",
"CCBYSA25", NULL, "https://creativecommons.org/licenses/by-sa/2.5",
"CCBYSA2", NULL, "https://creativecommons.org/licenses/by-sa/2.0",
"CCBYSA3", NULL, "https://creativecommons.org/licenses/by-sa/3.0",
"CCBYSA", NULL, "https://creativecommons.org/licenses/by-sa/4.0",
// CC BY
"CCBY1", NULL, "https://creativecommons.org/licenses/by/1.0",
"CCBY25", NULL, "https://creativecommons.org/licenses/by/2.5",
"CCBY2", NULL, "https://creativecommons.org/licenses/by/2.0",
"CCBY3", NULL, "https://creativecommons.org/licenses/by/3.0",
"CCBY", NULL, "https://creativecommons.org/licenses/by/4.0",
// CC0
"CC0", NULL, "https://creativecommons.org/publicdomain/zero/1.0",
// WTFPL
"WTFPL", NULL, "http://www.wtfpl.net/",
// end
NULL,
};
for (int i = 0; licenses[i]; i++) {
bool found = false;
for (; licenses[i]; i++) {
if (normalized.find(licenses[i]) != std::string::npos) found = true;
}
i++;
if (found) {
return license + tfm::format(" <%s>", licenses[i]);
}
}
}
return license;
}
int getKeyboardRepeatDelay() {
static int ret = -1;
if (ret < 0) {
#ifdef WIN32
int i = 0;
SystemParametersInfoW(SPI_GETKEYBOARDDELAY, 0, &i, 0);
// NOTE: these weird numbers are derived from Microsoft's documentation explaining the return value of SystemParametersInfo.
i = clamp(i, 0, 3);
ret = (i + 1) * 10;
#else
// TODO: platform-dependent code
// Assume it's 250ms, i.e. 10 frames
ret = 10;
#endif
// Debug
#ifdef _DEBUG
printf("getKeyboardRepeatDelay() = %d\n", ret);
#endif
}
return ret;
}
int getKeyboardRepeatInterval() {
static int ret = -1;
if (ret < 0) {
#ifdef WIN32
int i = 0;
SystemParametersInfoW(SPI_GETKEYBOARDSPEED, 0, &i, 0);
// NOTE: these weird numbers are derived from Microsoft's documentation explaining the return value of SystemParametersInfo.
i = clamp(i, 0, 31);
ret = (int)floor(40.0f / (2.5f + 0.887097f * (float)i) + 0.5f);
#else
// TODO: platform-dependent code
// Assume it's 25ms, i.e. 1 frame
ret = 1;
#endif
// Debug
#ifdef _DEBUG
printf("getKeyboardRepeatInterval() = %d\n", ret);
#endif
}
return ret;
}
diff --git a/src/Functions.h b/src/Functions.h
index 04fd988..d86784b 100644
--- a/src/Functions.h
+++ b/src/Functions.h
@@ -1,300 +1,294 @@
/*
* 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/>.
*/
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
#include "Settings.h"
#include "Globals.h"
#include <SDL.h>
#include <string>
#include <vector>
class MusicManager;
class SoundManager;
//gettext function
//message: The message to translate.
//Returns: The translated string or the original string if there is not translation available.
#define _(message) (dictionaryManager!=NULL?dictionaryManager->get_dictionary().translate(message).c_str():std::string(message).c_str())
//gettext function
//NOTE: "_C" is conflict to some Android macros so we change its name.
//dictionaryManager: Pointer to the dictionaryManager to use for the translation.
//message: The message to translate.
//Returns: The translated string or the original string if there is not translation available.
#define _CC(dictionaryManager, message) ((dictionaryManager)!=NULL?(dictionaryManager)->get_dictionary().translate(message).c_str():std::string(message).c_str())
//dummy function for xgettext
//message: The message to translate.
//Returns: message parameter
#define __(message) (message)
class ImageManager;
struct SDL_Texture;
class LevelPackManager;
//gettext function for plural forms
//message: The singular version of the message to translate.
//messageplural: The plural version of the message to translate.
//num: The number to fetch the plural form for
//Returns: The translated string or the original string if there are no translations available.
std::string ngettext(const std::string& message, const std::string& messageplural, int num);
//Method for drawing an SDL_Surface onto another.
//x: The x location to draw the source on the desination.
//y: The y location to draw the source on the desination.
//source: The SDL_Surface to draw.
//dest: The SDL_Surface to draw on.
//clip: Rectangle which part of the source should be drawn.
void applySurface(int x,int y,SDL_Surface* source,SDL_Surface* dest,SDL_Rect* clip);
//Method used to draw an rectangle.
//x: The top left x location of the rectangle.
//y: The top left y location of the rectangle.
//w: The width of the rectangle,
//h: The height of the rectangle.
//dest: The SDL_Surface to draw on.
//color: The color of the rectangle border to draw.
void drawRect(int x, int y, int w, int h, SDL_Renderer &renderer, Uint32 color=0);
//Method used to draw filled boxes with an anti-alliased border.
//Mostly used for GUI components.
//x: The top left x location of the box.
//y: The top left y location of the box.
//w: The width of the box,
//h: The height of the box.
//renderer: The SDL_Renderer to render on..
//color: The color of the rectangle background to draw.
void drawGUIBox(int x,int y,int w,int h,SDL_Renderer& renderer,Uint32 color);
//Method used to draw a line.
//x1: The x location of the start point.
//y1: The y location of the start point.
//x2: The x location of the end point.
//y2: The y location of the end point.
//dest: The SDL_Surface to draw on.
//color: The color of the line to draw.
void drawLine(int x1,int y1,int x2,int y2,SDL_Renderer &renderer,Uint32 color=0);
//Method used to draw a line with some arrows on it.
//x1: The x location of the start point.
//y1: The y location of the start point.
//x2: The x location of the end point.
//y2: The y location of the end point.
//dest: The SDL_Surface to draw on.
//color: The color of the line to draw.
//spacing: The spacing between arrows.
//offset: Offset of first arrow relative to the start point.
//xize, ysize: The size of arrow.
void drawLineWithArrow(int x1, int y1, int x2, int y2, SDL_Renderer &renderer, Uint32 color=0, int spacing=16, int offset=0, int xsize=5, int ysize=5);
//Method that will load the fonts needed for the game.
//NOTE: It's separate from loadFiles(), since it might get called separatly from the code when changing the language.
bool loadFonts();
//Method that will load the default theme again.
//name: name of the theme to load or empty for scaling background
//NOTE: It's separate from loadFiles(), since it might get called separatly from the code when changing resolution.
bool loadTheme(ImageManager& imageManager, SDL_Renderer& renderer, std::string name);
struct ScreenData{
SDL_Renderer* renderer;
explicit operator bool() const {
return renderer!=nullptr;
}
};
//This method will attempt to create the screen/window.
//NOTE: It's separate from init(), since it might get called separatly from the code when changing resolution.
ScreenData createScreen();
-
-//A very simple structure for resolutions.
-struct _res{
- int w,h;
-};
-
//Method for retrieving a list of resolutions.
-std::vector<_res> getResolutionList();
+std::vector<SDL_Point> getResolutionList();
//Method that is called when a fullscreen window is created.
//It will choose the resolution that is closest to the configured one.
void pickFullscreenResolution();
//This method is used to configure the window that is created by createScreen.
//NOTE: It will do it in a WM specific way, so if the wm is unkown it will do nothing.
void configureWindow();
//Call this method when receive SDL_VIDEORESIZE event.
void onVideoResize(ImageManager &imageManager, SDL_Renderer& renderer);
//Initialises the game. This is done almost at the beginning of the program.
//It initialises: SDL, SDL_Mixer, SDL_ttf, the screen and the block types.
//Returns: True if everything goes well.
ScreenData init();
//Loads some important files, like the background music and the default theme.
//Returns: True if everything goes well.
bool loadFiles(ImageManager &imageManager, SDL_Renderer &renderer);
//This method will load the settings from the settings file.
//Returns: False if there's an error while loading.
bool loadSettings();
//This method will save the settings to the settings file.
//Returns: False if there's an error while saving.
bool saveSettings();
//Method used to get a pointer to the settings object.
//Returns: A pointer to the settings object.
Settings* getSettings();
//Method used to get a pointer to the MusicManager object.
//Returns: A pointer to the MusicManager object.
MusicManager* getMusicManager();
//Method used to get a pointer to the SoundManager object.
//Returns: A pointer to the SoundManager object.
SoundManager* getSoundManager();
//Method used to get a pointer to the LevelPackManager object.
//Returns: A pointer to the LevelPackManager object.
LevelPackManager* getLevelPackManager();
//Method that will, depending on the rendering backend, draw the screen surface to the screen.
void flipScreen(SDL_Renderer& renderer);
//Method used to clean up before quiting meandmyshadow.
void clean();
//Sets what the nextState will be.
//newstate: Integer containing the id of the newstate.
void setNextState(int newstate);
//Method that will perform the state change.
//It will fade out (but not fade in).
void changeState(ImageManager &imageManager, SDL_Renderer &renderer, int fade = 255);
//This method is called when music is stopped.
//NOTE: This method is outside the MusicManager because it can't be called otherwise by SDL_Mixer.
//Do not call this method anywhere in the code!
void musicStoppedHook();
//This method is called when a sfx finished playing.
//NOTE: This method is outside the SoundManager because it can't be called otherwise by SDL_Mixer.
//Do not call this method anywhere in the code!
//channel: The number of the channel that is finished.
void channelFinishedHook(int channel);
//Checks collision between two SDL_Rects.
//a: The first rectangle.
//b: The second rectangle.
//Returns: True if the two rectangles collide.
bool checkCollision(const SDL_Rect& a,const SDL_Rect& b);
//Checks if a given point lays on an SDL_Rect
//point: The point to check.
//rect: The rectangle to check.
//Returns: True if the point is on the rectangle.
bool pointOnRect(const SDL_Rect& a,const SDL_Rect& b);
//Parse the commandline arguments.
//argc: Integer containing the number of aruguments there are.
//argv: The arguments.
//Returns: -1 if something goes wrong while parsing,
// 0 if version is shown,
// 1 if everything is alright
int parseArguments(int argc, char** argv);
//From http://en.wikipedia.org/wiki/Clamping_(graphics)
//x: The value to clamp.
//min: The minimum x can be.
//max: The maximum x can be.
//Returns: Integer containing the clamped value.
int inline clamp(int x,int min,int max){
return (x>max)?max:(x<min)?min:x;
}
//Enumeration containing the different messagebox button combinations.
enum msgBoxButtons{
//Only one button with the text OK.
MsgBoxOKOnly=0,
//Two buttons, one saying OK, the other Cancel.
MsgBoxOKCancel=1,
//Three buttons, Abort, Retry, Ignore.
MsgBoxAbortRetryIgnore=2,
//Three buttons, Yes, No or Cancel.
MsgBoxYesNoCancel=3,
//Two buttons, one saying Yes, the other No.
MsgBoxYesNo=4,
//Two buttons, one saying Retry, the other Cancel.
MsgBoxRetryCancel=5,
};
//Enumeration containing the different result that can be retrieved from a messagebox.
//It represents the button that has been pressed.
enum msgBoxResult{
//The OK button.
MsgBoxOK=1,
//The cancel button.
MsgBoxCancel=2,
//The abort button.
MsgBoxAbort=3,
//The retry button.
MsgBoxRetry=4,
//The ignore button.
MsgBoxIgnore=5,
//The yes button.
MsgBoxYes=6,
//The no button.
MsgBoxNo=7,
};
//Method that prompts the user with a notification and/or question.
//prompt: The message the user is prompted with.
//buttons: Which buttons the messagebox should have.
//title: The title of the message box.
//Returns: A msgBoxResult which button has been pressed.
msgBoxResult msgBox(ImageManager& imageManager,SDL_Renderer& renderer, const std::string& prompt,msgBoxButtons buttons,const std::string& title);
// A helper function to read a character from utf8 string
// s: the string
// p [in,out]: the position
// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
int utf8ReadForward(const char* s, int& p);
// A helper function to read a character backward from utf8 string (experimental)
// s: the string
// p [in,out]: the position
// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
int utf8ReadBackward(const char* s, int& p);
// Open the website using default web browser.
// url: The url of the website. Currently only http and https are supported.
// Also we assume that the url only contains ASCII characters.
// WARNING: Passing other url may result in arbitrary behavior (esp. passing '*.exe' on Windows).
void openWebsite(const std::string& url);
// Append a URL to a license if the license doesn't include URL (try to detect it from a predefined list).
// license: The license.
// Return value: The license appended with a URL if we detect the license successfully.
std::string appendURLToLicense(const std::string& license);
// Retrieves the (approximate) keyboard repeat delay time, in frames (NOTE: frame rate is hardcoded as 40).
int getKeyboardRepeatDelay();
// Retrieves the (approximate) keyboard repeat interval time, in frames (NOTE: frame rate is hardcoded as 40).
int getKeyboardRepeatInterval();
#endif
diff --git a/src/Game.cpp b/src/Game.cpp
index 3c8a769..ed14fc5 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -1,1892 +1,1891 @@
/*
* 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), currentCollectablesSaved(0), currentCollectablesInitial(0)
, totalCollectables(0), totalCollectablesSaved(0), totalCollectablesInitial(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 = currentCollectablesSaved = currentCollectablesInitial = 0;
totalCollectables = totalCollectablesSaved = totalCollectablesInitial = 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) {
totalCollectablesSaved = totalCollectablesInitial = ++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.
//Therefore later we must process other blocks before moving pushable block.
//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();
//Remove levelObjects whose isDelete is true.
{
int j = 0;
for (int i = 0; i < (int)levelObjects.size(); i++) {
if (levelObjects[i] == NULL) {
j++;
} else if (levelObjects[i]->isDelete) {
delete levelObjects[i];
levelObjects[i] = NULL;
j++;
} else if (j > 0) {
levelObjects[i - j] = levelObjects[i];
}
}
if (j > 0) levelObjects.resize(levelObjects.size() - j);
}
//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);
}
//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(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 || 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.
{
//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) && !interlevel && time>0){
if (collectablesTexture.needsUpdate(currentCollectables ^ (totalCollectables << 16))) {
- //Temp stringstream just to addup all the text nicely
- std::stringstream temp;
- temp << currentCollectables << "/" << totalCollectables;
collectablesTexture.update(currentCollectables ^ (totalCollectables << 16),
textureFromText(renderer,
*fontText,
- temp.str().c_str(),
+ tfm::format("%d/%d", currentCollectables, totalCollectables).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){
+ //show time and records used in level editor or during replay.
+ if((stateID==STATE_LEVEL_EDITOR || (!interlevel && player.isPlayFromRecord())) && 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());
+ if (stateID != STATE_LEVEL_EDITOR && bmTips[0] != NULL && !interlevel) {
+ y -= textureHeight(bmTips[0]) + 4;
+ }
- applyTexture(0,y,*recordingsTexture.get(), renderer);
+ 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.0);
timeTexture.update(time,
textureFromTextShaded(
renderer,
*fontText,
- c,
+ tfm::format("%-.2fs", time / 40.0).c_str(),
fg,
bg
));
- y-=textureHeight(*timeTexture.get());
}
+ 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(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){
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.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.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.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(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;
totalCollectablesSaved = totalCollectables;
//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;
totalCollectables = totalCollectablesSaved;
//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 = currentCollectablesInitial;
totalCollectables = totalCollectablesInitial;
if (save) {
currentCollectablesSaved = currentCollectablesInitial;
totalCollectablesSaved = totalCollectablesInitial;
}
//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);
}
}
void Game::invalidateNotificationTexture(Block *block) {
if (block == NULL || block == notificationTexture.getId()) {
notificationTexture.update(NULL, NULL);
}
}
diff --git a/src/LevelEditor.cpp b/src/LevelEditor.cpp
index e18a096..5a6e2e2 100644
--- a/src/LevelEditor.cpp
+++ b/src/LevelEditor.cpp
@@ -1,4938 +1,4944 @@
/*
* 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 "LevelPackManager.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"),
__("Select"), __("Delete"), __("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, -1, -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 ? 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() ? 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 ? 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 |= 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 ? 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);
+ char s[128];
+
GUISpinBox *spinBox = new GUISpinBox(imageManager, renderer, 80, 320, 200, 36);
spinBox->gravityRight = GUIGravityCenter;
spinBox->name = "speedX";
- spinBox->caption = tfm::format("%g", it->second->speedX);
+ sprintf(s, "%g", it->second->speedX);
+ spinBox->caption = s;
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);
+ sprintf(s, "%g", it->second->speedY);
+ spinBox->caption = s;
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);
+ sprintf(s, "%g", it->second->cameraX);
+ spinBox->caption = s;
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);
+ sprintf(s, "%g", it->second->cameraY);
+ spinBox->caption = s;
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_UsedInSelectionPopup;
}
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_UsedInSelectionPopup;
}
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_UsedInSelectionPopup;
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]) {
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 current level.
LevelPack::Level *currentLevel = levels->getLevel(), *currentLevel2 = NULL;
//Check if the current level is individual level,
//in this case the level are both in "Levels" and "Custom Levels" level packs.
if (levels->type == COLLECTION) {
assert(levels->levelpackPath == CUSTOM_LEVELS_PATH);
if (auto levels2 = getLevelPackManager()->getLevelPack(LEVELS_PATH)) {
for (int i = 0, m = levels2->getLevelCount(); i < m; i++) {
if (levels2->getLevel(i)->file == currentLevel->file) {
currentLevel2 = levels2->getLevel(i);
break;
}
}
if (currentLevel2 == NULL) {
fprintf(stderr, "BUG: The custom level '%s' is not conatined in 'Levels' pack!\n", currentLevel->file.c_str());
}
}
}
//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.
currentLevel->name = levelName;
if (currentLevel2) currentLevel2->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];
sprintf(c, "%d", std::max(levelTime, -1));
node.attributes["time"].push_back(c);
//Update the target time the levelpack.
currentLevel->targetTime = std::max(levelTime, -1);
if (currentLevel2) currentLevel2->targetTime = std::max(levelTime, -1);
sprintf(c, "%d", std::max(levelRecordings, -1));
node.attributes["recordings"].push_back(c);
//Update the target recordings the levelpack.
currentLevel->targetRecordings = std::max(levelRecordings, -1);
if (currentLevel2) currentLevel2->targetRecordings = std::max(levelRecordings, -1);
}
//The width of the level.
sprintf(s, "%d", LEVEL_WIDTH);
node.attributes["size"].push_back(s);
//The height of the level.
sprintf(s, "%d", LEVEL_HEIGHT);
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;
if (Scenery *scenery = dynamic_cast<Scenery*>(selection[o])) {
objMap["sceneryName"] = scenery->sceneryName_;
objMap["customScenery"] = scenery->customScenery_;
} else {
sprintf(s, "%d", selection[o]->type);
objMap["type"] = s;
//Save scripts for block.
if (Block *block = dynamic_cast<Block*>(selection[o])) {
for (auto it = block->scripts.begin(); it != block->scripts.end(); ++it) {
sprintf(s, "_script.%d", it->first);
objMap[s] = it->second;
}
}
}
//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;
Block *block = new Block(this, 0, 0, 50, 50, atoi(clipboard[o]["type"].c_str()));
obj = block;
// load the script for the block
for (auto it = clipboard[o].begin(); it != clipboard[o].end(); ++it) {
if (it->first.find("_script.") == 0) {
int eventType = atoi(it->first.c_str() + 8);
block->scripts[eventType] = it->second;
}
}
} 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){
//Show/hide toolbox if the current mode is ADD and the user clicked ADD again.
if (tool == ADD) {
toolboxVisible = !toolboxVisible;
}
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;
}
//Recalculate the number of collectibles.
totalCollectables = totalCollectablesSaved = totalCollectablesInitial = 0;
//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;
}
if (obj->type == TYPE_COLLECTABLE) {
totalCollectablesSaved = totalCollectablesInitial = ++totalCollectables;
}
}
//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 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";
char ss[128];
sprintf(ss, "%0.2f", double(levelTime >= 0 ? levelTime : ~levelTime) / 40.0);
obj2->caption=ss;
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 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;
sprintf(ss, "%d", levelRecordings >= 0 ? levelRecordings : ~levelRecordings);
obj2->caption=ss;
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"));
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"));
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()]) {
//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 cfae54a..81fb401 100644
--- a/src/LevelPlaySelect.cpp
+++ b/src/LevelPlaySelect.cpp
@@ -1,503 +1,500 @@
/*
* 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>
+#include "libs/tinyformat/tinyformat.h"
+
/////////////////////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(n>0 && 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;
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 (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.0, targetTime / 40.0);
+ if (targetTime >= 0)
+ levelTime = tfm::format("%-.2fs / %-.2fs", time / 40.0, targetTime / 40.0);
else
- sprintf(s, "%-.2fs / -", time / 40.0);
+ levelTime = tfm::format("%-.2fs / -", time / 40.0);
else
- s[0] = '\0';
- levelTime = s;
+ levelTime.clear();
if (recordings >= 0)
if (targetRecordings >= 0)
- sprintf(s, "%5d / %d", recordings, targetRecordings);
+ levelRecs = tfm::format("%5d / %d", recordings, targetRecordings);
else
- sprintf(s, "%5d / -", recordings);
+ levelRecs = tfm::format("%5d / -", recordings);
else
- s[0] = '\0';
- levelRecs = s;
+ levelRecs.clear();
} 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.0);
- toolTip.time = textureFromText(renderer, *fontText, s, objThemes.getTextColor(true));
+ toolTip.time = textureFromText(renderer, *fontText,
+ tfm::format("%-.2fs", levels->getLevel(number)->time / 40.0).c_str(),
+ 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));
+ toolTip.recordings = textureFromText(renderer, *fontText,
+ tfm::format("%d", levels->getLevel(number)->recordings).c_str(),
+ 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);
}
}
}
diff --git a/src/Main.cpp b/src/Main.cpp
index 02f9e81..c36269b 100644
--- a/src/Main.cpp
+++ b/src/Main.cpp
@@ -1,261 +1,262 @@
/*
* 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 "Functions.h"
#include "Globals.h"
#include "TitleMenu.h"
#include "GUIObject.h"
#include "InputManager.h"
#include "StatisticsManager.h"
#include "Timer.h"
#include <SDL.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
// the following piece of code fixes the combination of VC2015 with official SDL1.2 binary
#if defined(_MSC_VER) && (_MSC_VER >= 1900)
#pragma comment(lib, "legacy_stdio_definitions.lib")
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
#endif
static ImageManager *_imageManager = NULL;
static SDL_Renderer *_renderer = NULL;
ImageManager& getImageManager() {
return *_imageManager;
}
SDL_Renderer& getRenderer() {
return *_renderer;
}
int main(int argc, char** argv) {
#ifdef _MSC_VER
//Fix the non-latin file name bug under Visual Studio
setlocale(LC_ALL,"");
+ setlocale(LC_NUMERIC, "C");
#endif
//Relocate the standard output for debug purpose (?)
#if defined(ANDROID)
freopen("stdout.txt","w",stdout);
freopen("stderr.txt","w",stderr);
#endif
//First parse the comand line arguments.
int s=parseArguments(argc,argv);
if(s==-1){
printf("Usage: %s [OPTIONS] ...\n",argv[0]);
printf("%s","Available options:\n");
printf(" %-5s%-30s %s\n","","--data-dir <dir>","Specifies the data directory.");
printf(" %-5s%-30s %s\n","","--user-dir <dir>","Specifies the user preferences directory.");
printf(" %-5s%-30s %s\n","-f,","--fullscreen","Run the game fullscreen.");
printf(" %-5s%-30s %s\n","-w,","--windowed","Run the game windowed.");
printf(" %-5s%-30s %s\n","-mv,","--music <volume>","Set the music volume.");
printf(" %-5s%-30s %s\n","-sv,","--sound <volume>","Set the sound volume.");
printf(" %-5s%-30s %s\n","-s,","--set <setting> <value>","Change a setting to a given value.");
printf(" %-5s%-30s %s\n","-v,","--version","Display the version and quit.");
printf(" %-5s%-30s %s\n","-h,","--help","Display this help message.");
return 0;
}else if(s==0){
return 0;
}
//Try to configure the dataPath, userPath, etc...
if(configurePaths()==false){
fprintf(stderr,"FATAL ERROR: Failed to configure paths.\n");
return 1;
}
//Load the settings.
if(loadSettings()==false){
fprintf(stderr,"ERROR: Unable to load config file, default values will be used.\n");
}
ScreenData screenData = init();
//Initialise some stuff like SDL, the window, SDL_Mixer.
if(!screenData) {
fprintf(stderr,"FATAL ERROR: Failed to initialize game.\n");
return 1;
}
SDL_Renderer& renderer = *screenData.renderer;
_renderer = screenData.renderer;
//Initialise the imagemanager.
//The ImageManager is used to prevent loading images multiple times.
ImageManager imageManager;
_imageManager = &imageManager;
//Load some important files like the background music, default theme.
if(loadFiles(imageManager,renderer)==false){
fprintf(stderr,"FATAL ERROR: Failed to load necessary files.\n");
return 1;
}
//Seed random.
srand((unsigned)time(NULL));
//Set the currentState id to the main menu and create it.
stateID=STATE_MENU;
currentState=new Menu(imageManager,renderer);
//Set the fadeIn value to zero.
int fadeIn=255;
//Keep the last resize event, this is to only process one.
SDL_Event lastResize={};
Timer timer;
//Start the game loop.
while(stateID!=STATE_EXIT){
//We start the timer.
timer.start();
//Loop the SDL events.
while(SDL_PollEvent(&event)){
//Check if user resizes the window.
if(event.type==SDL_WINDOWEVENT){
if(event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
{
lastResize=event;
//Don't let other objects process this event (?)
continue;
}
}
//Check if it's mouse event. If it's true then we quit the keyboard-only mode.
if (event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
isKeyboardOnly = false;
}
//Check if the fullscreen toggle shortcut is pressed (Alt+Enter).
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_RETURN && (event.key.keysym.mod & KMOD_ALT)){
getSettings()->setValue("fullscreen",getSettings()->getBoolValue("fullscreen")?"0":"1");
//We need to create a new screen.
if(!createScreen()){
//Screen creation failed so set to safe settings.
getSettings()->setValue("fullscreen","0");
getSettings()->setValue("width","800");
getSettings()->setValue("height","600");
//Try it with the safe settings.
if(!createScreen()){
//Everything fails so quit.
setNextState(STATE_EXIT);
std::cerr<<"ERROR: Unable to create screen."<<std::endl;
}
}
//The screen is created, now load the (menu) theme.
if(!loadTheme(imageManager,renderer,"")){
//Loading the theme failed so quit.
setNextState(STATE_EXIT);
std::cerr<<"ERROR: Unable to load theme after toggling fullscreen."<<std::endl;
}
//Don't let other objects process this event.
continue;
}
//Set the cursor type to the default one, the GUI can change that if needed.
currentCursor=CURSOR_POINTER;
//Let the input manager handle the events.
inputMgr.updateState(true);
//Let the currentState handle the events.
currentState->handleEvents(imageManager,renderer);
//Also pass the events to the GUI.
GUIObjectHandleEvents(imageManager,renderer);
}
//Process the resize event.
if(lastResize.type==SDL_WINDOWEVENT){
//TODO - used to be SDL_VIDEORESIZE
// so this may trigger on more events than intended
event=lastResize;
onVideoResize(imageManager,renderer);
//After resize we erase the event type
//TODO - used to be SDL_NOEVENT
lastResize.type=SDL_FIRSTEVENT;
}
//maybe we should add a check here (??) to fix some bugs (ticket #47)
if(nextState!=STATE_NULL){
changeState(imageManager, renderer, fadeIn);
//Check if fading is enabled.
if(getSettings()->getBoolValue("fading"))
fadeIn=17;
else
fadeIn=255;
}
if(stateID==STATE_EXIT) break;
//update input state (??)
inputMgr.updateState(false);
//Now it's time for the state to do his logic.
currentState->logic(imageManager,renderer);
currentState->render(imageManager,renderer);
//TODO: Shouldn't the gamestate take care of rendering the GUI?
if(GUIObjectRoot) GUIObjectRoot->render(renderer);
//draw fading effect
if(fadeIn>0&&fadeIn<255){
dimScreen(renderer, static_cast<Uint8>(255-fadeIn));
fadeIn+=17;
}
//draw new achievements (if any) as overlay
statsMgr.render(imageManager, renderer);
//Before flipping the screen set the cursor.
SDL_SetCursor(cursors[currentCursor]);
//And draw the screen surface to the actual screen.
flipScreen(renderer);
//Now calcualte how long we need to wait to keep a constant framerate.
int t=timer.getTicks();
t=(1000/FPS)-t;
if(t>0){
SDL_Delay(t);
}
}
//The game has ended, save the settings just to be sure.
if(!saveSettings()){
fprintf(stderr,"ERROR: Unable to save settings in config file.\n");
}
//Clean everything up.
clean();
//End of program.
return 0;
}
diff --git a/src/OptionsMenu.cpp b/src/OptionsMenu.cpp
index 0bc258d..6eda36f 100644
--- a/src/OptionsMenu.cpp
+++ b/src/OptionsMenu.cpp
@@ -1,592 +1,610 @@
/*
* 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 "Functions.h"
#include "GameState.h"
#include "OptionsMenu.h"
#include "ThemeManager.h"
#include "GUIListBox.h"
#include "GUISlider.h"
#include "InputManager.h"
#include "LevelPackManager.h"
#include "StatisticsManager.h"
#include "MusicManager.h"
#include "SoundManager.h"
#include <iostream>
#include <sstream>
+#include "libs/tinyformat/tinyformat.h"
#include "libs/tinygettext/tinygettext.hpp"
#include "libs/findlocale/findlocale.h"
using namespace std;
/////////////////////////OPTIONS_MENU//////////////////////////////////
//Some variables for the options.
static bool fullscreen,internet,fade,quickrec;
static string themeName,languageName;
static int lastLang,lastRes;
static bool useProxy;
static string internetProxy;
static bool restartFlag;
-static _res currentRes;
-static vector<_res> resolutionList;
+static SDL_Point currentRes;
+static vector<SDL_Point> resolutionList;
Options::Options(ImageManager& imageManager,SDL_Renderer& renderer){
//Render the title.
title = titleTextureFromText(renderer, _("Settings"), objThemes.getTextColor(false), SCREEN_WIDTH);
//Initialize variables.
lastJumpSound=0;
clearIconHower=false;
section = 2;
section2 = 1;
//Load icon image and tooltip text.
clearIcon=imageManager.loadTexture(getDataPath()+"gfx/menu/clear-progress.png",renderer);
/// TRANSLATORS: Used for button which clear any level progress like unlocked levels and highscores.
clearTooltip=textureFromText(renderer, *fontText, _("Clear Progress"), objThemes.getTextColor(true));
//Set some default settings.
fullscreen=getSettings()->getBoolValue("fullscreen");
languageName=getSettings()->getValue("lang");
themeName=processFileName(getSettings()->getValue("theme"));
internet=getSettings()->getBoolValue("internet");
internetProxy=getSettings()->getValue("internet-proxy");
useProxy=!internetProxy.empty();
fade=getSettings()->getBoolValue("fading");
quickrec=getSettings()->getBoolValue("quickrecord");
//Set the restartFlag false.
restartFlag=false;
//Now create the gui.
createGUI(imageManager,renderer);
}
Options::~Options(){
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
void Options::createGUI(ImageManager& imageManager,SDL_Renderer& renderer){
//Variables for positioning
const int columnW=SCREEN_WIDTH*0.3;
const int column1X=SCREEN_WIDTH*0.15;
const int column2X=SCREEN_WIDTH*0.55;
const int lineHeight=40;
//Create the root element of the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Single line list for different tabs.
GUISingleLineListBox* listBox=new GUISingleLineListBox(imageManager,renderer,(SCREEN_WIDTH-500)/2,104,500,32);
listBox->addItem(_("General"));
listBox->addItem(_("Controls"));
listBox->value=0;
listBox->name="lstTabs";
listBox->eventCallback=this;
GUIObjectRoot->addChild(listBox);
//Create general tab.
tabGeneral=new GUIObject(imageManager,renderer,0,150,SCREEN_WIDTH,SCREEN_HEIGHT);
GUIObjectRoot->addChild(tabGeneral);
//Now we create GUIObjects for every option.
GUIObject* obj=new GUILabel(imageManager,renderer,column1X,0,columnW,36,_("Music"));
tabGeneral->addChild(obj);
musicSlider=new GUISlider(imageManager,renderer,column2X,0,columnW,36,atoi(getSettings()->getValue("music").c_str()),0,128,15);
musicSlider->name="sldMusic";
musicSlider->eventCallback=this;
tabGeneral->addChild(musicSlider);
obj=new GUILabel(imageManager,renderer,column1X,lineHeight,columnW,36,_("Sound"));
tabGeneral->addChild(obj);
soundSlider=new GUISlider(imageManager,renderer,column2X,lineHeight,columnW,36,atoi(getSettings()->getValue("sound").c_str()),0,128,15);
soundSlider->name="sldSound";
soundSlider->eventCallback=this;
tabGeneral->addChild(soundSlider);
obj=new GUILabel(imageManager,renderer,column1X,2*lineHeight,columnW,36,_("Resolution"));
obj->name="lstResolution";
tabGeneral->addChild(obj);
//Create list with many different resolutions.
resolutions = new GUISingleLineListBox(imageManager,renderer,column2X,2*lineHeight,columnW,36);
resolutions->value=-1;
//Only get the resolution list if it hasn't been done before.
if(resolutionList.empty()){
resolutionList=getResolutionList();
}
//Get current resolution from config file. Thus it can be user defined.
- currentRes.w=atoi(getSettings()->getValue("width").c_str());
- currentRes.h=atoi(getSettings()->getValue("height").c_str());
+ currentRes.x=atoi(getSettings()->getValue("width").c_str());
+ currentRes.y=atoi(getSettings()->getValue("height").c_str());
for(int i=0; i<(int)resolutionList.size();i++){
//Create a string from width and height and then add it to list.
ostringstream out;
- out << resolutionList[i].w << "x" << resolutionList[i].h;
+ out << resolutionList[i].x << "x" << resolutionList[i].y;
resolutions->addItem(out.str());
//Check if current resolution matches, select it.
- if(resolutionList[i].w==currentRes.w && resolutionList[i].h==currentRes.h){
+ if(resolutionList[i].x==currentRes.x && resolutionList[i].y==currentRes.y){
resolutions->value=i;
}
}
//Add current resolution if it isn't already in the list.
if(resolutions->value==-1){
ostringstream out;
- out << currentRes.w << "x" << currentRes.h;
+ out << currentRes.x << "x" << currentRes.y;
resolutions->addItem(out.str());
resolutions->value=resolutions->item.size()-1;
}
lastRes=resolutions->value;
tabGeneral->addChild(resolutions);
obj=new GUILabel(imageManager,renderer,column1X,3*lineHeight,columnW,36,_("Language"));
tabGeneral->addChild(obj);
//Create GUI list with available languages.
langs = new GUISingleLineListBox(imageManager,renderer,column2X,3*lineHeight,columnW,36);
langs->name="lstLanguages";
/// TRANSLATORS: as detect user's language automatically
langs->addItem("",_("Auto-Detect"));
langs->addItem("en","English");
//Get a list of every available language.
set<tinygettext::Language> languages = dictionaryManager->get_languages();
for (set<tinygettext::Language>::iterator s0 = languages.begin(); s0 != languages.end(); ++s0){
//If language in loop is the same in config file, then select it
if(getSettings()->getValue("lang")==s0->str()){
lastLang=distance(languages.begin(),s0)+2;
}
//Add language in loop to list and listbox.
langs->addItem(s0->str(),s0->get_localized_name());
}
//If Auto or English are selected.
if(getSettings()->getValue("lang")==""){
lastLang=0;
}else if(getSettings()->getValue("lang")=="en"){
lastLang=1;
}
langs->value=lastLang;
tabGeneral->addChild(langs);
obj=new GUILabel(imageManager,renderer,column1X,4*lineHeight,columnW,36,_("Theme"));
obj->name="theme";
tabGeneral->addChild(obj);
//Create the theme option gui element.
theme=new GUISingleLineListBox(imageManager,renderer,column2X,4*lineHeight,columnW,36);
theme->name="lstTheme";
//Vector containing the theme locations and names.
vector<pair<string,string> > themes;
vector<string> v=enumAllDirs(getUserPath(USER_DATA)+"themes/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
string location=getUserPath(USER_DATA)+"themes/"+*i;
themes.push_back(pair<string,string>(location,*i));
}
vector<string> v2=enumAllDirs(getDataPath()+"themes/");
for(vector<string>::iterator i=v2.begin(); i!=v2.end(); ++i){
string location=getDataPath()+"themes/"+*i;
themes.push_back(pair<string,string>(location,*i));
}
//Try to find the configured theme so we can display it.
int value=-1;
for(vector<pair<string,string> >::iterator i=themes.begin(); i!=themes.end(); ++i){
if(i->first==themeName) {
value=i-themes.begin();
}
}
theme->addItems(themes);
if(value==-1)
value=theme->item.size()-1;
theme->value=value;
//NOTE: We call the event handling method to correctly set the themename.
GUIEventCallback_OnEvent(imageManager,renderer,"lstTheme",theme,GUIEventChange);
theme->eventCallback=this;
tabGeneral->addChild(theme);
//Proxy settings.
obj=new GUILabel(imageManager,renderer,column1X,5*lineHeight,columnW,36,_("Internet proxy"));
obj->name="chkProxy";
obj->eventCallback=this;
tabGeneral->addChild(obj);
obj=new GUITextBox(imageManager,renderer,column2X,5*lineHeight,columnW,36,internetProxy.c_str());
obj->name="txtProxy";
obj->eventCallback=this;
tabGeneral->addChild(obj);
obj=new GUICheckBox(imageManager,renderer,column1X,6*lineHeight,columnW,36,_("Fullscreen"),fullscreen?1:0);
obj->name="chkFullscreen";
obj->eventCallback=this;
tabGeneral->addChild(obj);
obj = new GUICheckBox(imageManager, renderer, column1X, 7 * lineHeight, columnW, 36, _("Quick record"), quickrec ? 1 : 0);
obj->name = "chkQuickRec";
obj->eventCallback = this;
tabGeneral->addChild(obj);
obj=new GUICheckBox(imageManager,renderer,column2X,6*lineHeight,columnW,36,_("Internet"),internet?1:0);
obj->name="chkInternet";
obj->eventCallback=this;
tabGeneral->addChild(obj);
obj=new GUICheckBox(imageManager,renderer,column2X,7*lineHeight,columnW,36,_("Fade transition"),fade?1:0);
obj->name="chkFade";
obj->eventCallback=this;
tabGeneral->addChild(obj);
//Create the controls tab.
tabControls=inputMgr.showConfig(imageManager,renderer,SCREEN_HEIGHT-210);
tabControls->top=140;
tabControls->visible=false;
GUIObjectRoot->addChild(tabControls);
//Save original keys.
for(int i=0;i<INPUTMGR_MAX;i++){
tmpKeys[i]=inputMgr.getKeyCode((InputManagerKeys)i,false);
tmpAlternativeKeys[i]=inputMgr.getKeyCode((InputManagerKeys)i,true);
}
//Create buttons.
cmdBack = new GUIButton(imageManager, renderer, SCREEN_WIDTH*0.3, SCREEN_HEIGHT - 60, -1, 36, _("Cancel"), 0, true, true, GUIGravityCenter);
cmdBack->name = "cmdBack";
cmdBack->eventCallback = this;
GUIObjectRoot->addChild(cmdBack);
cmdSave = new GUIButton(imageManager, renderer, SCREEN_WIDTH*0.7, SCREEN_HEIGHT - 60, -1, 36, _("Save Changes"), 0, true, true, GUIGravityCenter);
cmdSave->name = "cmdSave";
cmdSave->eventCallback = this;
GUIObjectRoot->addChild(cmdSave);
}
static string convertInt(int i){
stringstream ss;
ss << i;
return ss.str();
}
void Options::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Check what type of event it was.
if(eventType==GUIEventClick){
if(name=="cmdBack"){
//Reset the key changes.
for(int i=0;i<INPUTMGR_MAX;i++){
inputMgr.setKeyCode((InputManagerKeys)i,tmpKeys[i],false);
inputMgr.setKeyCode((InputManagerKeys)i,tmpAlternativeKeys[i],true);
}
//Reset the music volume.
getMusicManager()->setVolume(atoi(getSettings()->getValue("music").c_str()));
Mix_Volume(-1,atoi(getSettings()->getValue("sound").c_str()));
//And goto the main menu.
setNextState(STATE_MENU);
}else if(name=="cmdSave"){
//Save is pressed thus save
char s[64];
sprintf(s,"%d",soundSlider->value);
getSettings()->setValue("sound",s);
sprintf(s,"%d",musicSlider->value);
getSettings()->setValue("music",s);
getMusicManager()->setEnabled(musicSlider->value>0);
Mix_Volume(-1,soundSlider->value);
getSettings()->setValue("fullscreen",fullscreen?"1":"0");
getSettings()->setValue("internet",internet?"1":"0");
getSettings()->setValue("theme",themeName);
getSettings()->setValue("fading",fade?"1":"0");
getSettings()->setValue("quickrecord",quickrec?"1":"0");
//Before loading the theme remove the previous one from the stack.
objThemes.removeTheme();
loadTheme(imageManager,renderer,themeName);
if(!useProxy)
internetProxy.clear();
getSettings()->setValue("internet-proxy",internetProxy);
getSettings()->setValue("lang",langs->getName());
//Is resolution from the list or is it user defined in config file
if(resolutions->value<(int)resolutionList.size()){
- getSettings()->setValue("width",convertInt(resolutionList[resolutions->value].w));
- getSettings()->setValue("height",convertInt(resolutionList[resolutions->value].h));
+ getSettings()->setValue("width",convertInt(resolutionList[resolutions->value].x));
+ getSettings()->setValue("height",convertInt(resolutionList[resolutions->value].y));
}else{
- getSettings()->setValue("width",convertInt(currentRes.w));
- getSettings()->setValue("height",convertInt(currentRes.h));
+ getSettings()->setValue("width",convertInt(currentRes.x));
+ getSettings()->setValue("height",convertInt(currentRes.y));
}
//Save the key configuration.
inputMgr.saveConfig();
//Save the settings.
saveSettings();
//Before we return check if some .
if(restartFlag || resolutions->value!=lastRes){
//The resolution changed so we need to recreate the screen.
if(!createScreen()){
//Screen creation failed so set to safe settings.
getSettings()->setValue("fullscreen","0");
- getSettings()->setValue("width",convertInt(resolutionList[lastRes].w));
- getSettings()->setValue("height",convertInt(resolutionList[lastRes].h));
+ getSettings()->setValue("width",convertInt(resolutionList[lastRes].x));
+ getSettings()->setValue("height",convertInt(resolutionList[lastRes].y));
if(!createScreen()){
//Everything fails so quit.
setNextState(STATE_EXIT);
return;
}
}
//The screen is created, now load the (menu) theme.
if(!loadTheme(imageManager,renderer,"")){
//Loading the theme failed so quit.
setNextState(STATE_EXIT);
return;
}
}
if(langs->value!=lastLang){
//We set the language.
language=langs->getName();
if (language.empty()) {
// The language is set to auto-detect.
FL_Locale *locale;
FL_FindLocale(&locale, FL_MESSAGES);
language = locale->lang;
if (locale->country != NULL){
language += string("_") + string(locale->country);
}
if (locale->variant != NULL){
language += string("@") + string(locale->variant);
}
FL_FreeLocale(&locale);
}
dictionaryManager->set_language(tinygettext::Language::from_name(language));
+
+#ifdef WIN32
+ //Some ad-hoc fix for Windows since it accepts "zh-CN" but not "zh_CN"
+ std::string language2;
+ for (auto c : language) {
+ if (isalnum(c)) language2.push_back(c);
+ else if (c == '_') language2.push_back('-');
+ else break;
+ }
+ const char* languagePtr = language2.c_str();
+#else
+ const char* languagePtr = language.c_str();
+#endif
+
+ tfm::setLocale(languagePtr);
+ setlocale(LC_TIME, languagePtr);
+
getLevelPackManager()->updateLanguage();
-
+
//And reload the font.
if(!loadFonts()){
//Loading failed so quit.
setNextState(STATE_EXIT);
return;
}
}
//Now return to the main menu.
setNextState(STATE_MENU);
}else if(name=="chkFullscreen"){
fullscreen=obj->value?true:false;
//Check if fullscreen changed.
if(fullscreen==getSettings()->getBoolValue("fullscreen")){
//We disable the restart message flag.
restartFlag=false;
}else{
//We set the restart message flag.
restartFlag=true;
}
}else if(name=="chkInternet"){
internet=obj->value?true:false;
}else if(name=="chkProxy"){
useProxy=obj->value?true:false;
}else if(name=="chkFade"){
fade=obj->value?true:false;
}else if(name=="chkQuickRec"){
quickrec=obj->value?true:false;
}
}
if(name=="lstTheme"){
if(theme!=NULL && theme->value>=0 && theme->value<(int)theme->item.size()){
//Convert the themeName to contain %DATA%, etc...
themeName=compressFileName(theme->item[theme->value].first);
}
}else if(name=="txtProxy"){
internetProxy=obj->caption;
//Check if the internetProxy field is empty.
useProxy=!internetProxy.empty();
}else if(name=="sldMusic"){
getMusicManager()->setEnabled(musicSlider->value>0);
getMusicManager()->setVolume(musicSlider->value);
}else if(name=="sldSound"){
Mix_Volume(-1,soundSlider->value);
if(lastJumpSound==0){
getSoundManager()->playSound("jump");
lastJumpSound=15;
}
}
if(name=="lstTabs"){
if(obj->value==0){
tabGeneral->visible=true;
tabControls->visible=false;
}else{
tabGeneral->visible=false;
tabControls->visible=true;
}
}
}
void Options::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//Check keyboard navigation.
if (tabGeneral && tabGeneral->visible) {
if (inputMgr.isKeyDownEvent(INPUTMGR_TAB)) {
isKeyboardOnly = true;
section = (section == 2) ? 3 : 2;
//Update selection.
if (section == 2) {
tabGeneral->selectNextControl(1, -1);
} else {
tabGeneral->setSelectedControl(-1);
}
}
if (section == 2) {
tabGeneral->handleKeyboardNavigationEvents(imageManager, renderer, UpDownFocus | ReturnControls | LeftRightControls);
} else if (section == 3) {
if (inputMgr.isKeyDownEvent(INPUTMGR_DOWN) || inputMgr.isKeyDownEvent(INPUTMGR_RIGHT)) {
isKeyboardOnly = true;
section2++;
if (section2 > 3) section2 = 1;
} else if (inputMgr.isKeyDownEvent(INPUTMGR_UP) || inputMgr.isKeyDownEvent(INPUTMGR_LEFT)) {
isKeyboardOnly = true;
section2--;
if (section2 < 1) section2 = 3;
}
if (isKeyboardOnly && inputMgr.isKeyDownEvent(INPUTMGR_SELECT) && section == 3) {
if (section2 == 1) {
GUIEventCallback_OnEvent(imageManager, renderer, cmdBack->name, cmdBack, GUIEventClick);
} else if (section2 == 2) {
GUIEventCallback_OnEvent(imageManager, renderer, cmdSave->name, cmdSave, GUIEventClick);
}
}
}
}
//Process mouse event only when it's not keyboard only mode.
if (!isKeyboardOnly) {
//Get the x and y location of the mouse.
int x, y;
SDL_GetMouseState(&x, &y);
//Check icon.
if (event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEBUTTONDOWN){
if (y >= SCREEN_HEIGHT - 56 && y < SCREEN_HEIGHT - 8 && x >= SCREEN_WIDTH - 56)
clearIconHower = true;
else
clearIconHower = false;
}
}
//Update highlight on keyboard only mode.
if (isKeyboardOnly) {
cmdBack->state = (section == 3 && section2 == 1) ? 1 : 0;
cmdSave->state = (section == 3 && section2 == 2) ? 1 : 0;
clearIconHower = (section == 3 && section2 == 3);
}
if ((isKeyboardOnly ? inputMgr.isKeyDownEvent(INPUTMGR_SELECT) :
(event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT)) && clearIconHower)
{
if(msgBox(imageManager,renderer,_("Do you really want to reset level progress?"),MsgBoxYesNo,_("Warning"))==MsgBoxYes){
//We delete the progress folder.
#ifdef WIN32
removeDirectory((getUserPath()+"progress").c_str());
createDirectory((getUserPath()+"progress").c_str());
#else
removeDirectory((getUserPath(USER_DATA)+"/progress").c_str());
createDirectory((getUserPath(USER_DATA)+"/progress").c_str());
#endif
//Reset statistics.
statsMgr.reloadCompletedLevelsAndAchievements();
}
}
//Check if we need to quit, if so enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//Check if the escape button is pressed, if so go back to the main menu.
if (inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE) && (tabControls == NULL || !tabControls->visible)) {
setNextState(STATE_MENU);
}
}
void Options::logic(ImageManager&, SDL_Renderer&){
//Increase the lastJumpSound variable if needed.
if(lastJumpSound!=0){
lastJumpSound--;
}
}
void Options::render(ImageManager&, SDL_Renderer& renderer){
//Draw background.
objThemes.getBackground(true)->draw(renderer);
objThemes.getBackground(true)->updateAnimation();
//Now render the title.
drawTitleTexture(SCREEN_WIDTH, *title, renderer);
//Check if an icon is selected/highlighted and draw tooltip
if(clearIconHower){
const SDL_Rect texSize = rectFromTexture(*clearTooltip);
drawGUIBox(-2,SCREEN_HEIGHT-texSize.h-2,texSize.w+4,texSize.h+4,renderer,0xFFFFFFFF);
applyTexture(0,SCREEN_HEIGHT-texSize.h,clearTooltip,renderer);
}
//Draw border of icon if it's keyboard only mode.
if (isKeyboardOnly && clearIconHower) {
drawGUIBox(SCREEN_WIDTH - 52, SCREEN_HEIGHT - 52, 40, 40, renderer, 0xFFFFFF40);
}
//Draw icon.
applyTexture(SCREEN_WIDTH-48,SCREEN_HEIGHT-48,*clearIcon,renderer);
//NOTE: The rendering of the GUI is done in Main.
}
void Options::resize(ImageManager& imageManager, SDL_Renderer& renderer){
//Recreate the gui to fit the new resolution.
createGUI(imageManager,renderer);
}
diff --git a/src/Settings.cpp b/src/Settings.cpp
index c6e0def..bdc988d 100644
--- a/src/Settings.cpp
+++ b/src/Settings.cpp
@@ -1,276 +1,265 @@
/*
* 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 "Settings.h"
#include "FileManager.h"
#include <SDL.h>
#include <string>
#include <stdio.h>
using namespace std;
// Hardcode the addon url unless specified by command line arguments
#define DEFAULT_ADDON_URL "https://github.com/acmepjz/meandmyshadow-addons/raw/master/addons05"
Settings::Settings(const string& fileName): fileName(fileName){
char s[32];
settings["sound"]="128";
settings["music"]="128";
settings["musiclist"]="default";
settings["fullscreen"]="0";
settings["width"]="800";
settings["height"]="600";
settings["resizable"]="1";
settings["theme"]="%DATA%/themes/Cloudscape";
settings["internet"]="1";
settings["lastlevelpack"]="tutorial";
settings["internet-proxy"]="";
settings["lang"]="";
settings["addon_url"] = DEFAULT_ADDON_URL;
//The record mode.
settings["quickrecord"]="0";
//Boolean if fading between states is enabled.
settings["fading"]="1";
//Key settings.
sprintf(s,"%d",(int)SDLK_UP);
settings["key_up"]=settings["key_jump"]=s;
sprintf(s,"%d",(int)SDLK_DOWN);
settings["key_down"]=settings["key_action"]=s; //SDLK_DOWN
sprintf(s,"%d",(int)SDLK_LEFT);
settings["key_left"]=s; //SDLK_LEFT
sprintf(s,"%d",(int)SDLK_RIGHT);
settings["key_right"]=s; //SDLK_RIGHT
sprintf(s,"%d",(int)SDLK_SPACE);
settings["key_space"]=s; //SDLK_SPACE
settings["key_cancelRecording"]="0"; //not set by default
sprintf(s,"%d",(int)SDLK_ESCAPE);
settings["key_escape"]=s; //SDLK_ESCAPE
sprintf(s,"%d",(int)SDLK_r);
settings["key_restart"]=s; //SDLK_r
sprintf(s,"%d",(int)SDLK_TAB);
settings["key_tab"]=s;
sprintf(s,"%d",(int)SDLK_F2);
settings["key_save"]=s; //SDLK_F2
sprintf(s,"%d",(int)SDLK_F3);
settings["key_load"]=s; //SDLK_F3
sprintf(s,"%d",(int)SDLK_F4);
settings["key_swap"]=s; //SDLK_F4
sprintf(s,"%d",(int)SDLK_F5);
settings["key_teleport"]=s; //SDLK_F5
sprintf(s,"%d",(int)SDLK_F12);
settings["key_suicide"]=s;
sprintf(s,"%d",(int)SDLK_RSHIFT);
settings["key_shift"]=s; //SDLK_RSHIFT
sprintf(s,"%d",(int)SDLK_PAGEUP);
settings["key_next"]=s; //SDLK_PAGEUP
sprintf(s,"%d",(int)SDLK_PAGEDOWN);
settings["key_previous"]=s; //SDLK_PAGEDOWN
sprintf(s,"%d",(int)SDLK_RETURN);
settings["key_select"]=s; //SDLK_RETURN
settings["key_up2"]=settings["key_down2"]=settings["key_left2"]=settings["key_right2"]=
settings["key_jump2"]=settings["key_action2"]=
settings["key_space2"]=settings["key_cancelRecording2"]=
settings["key_escape2"]=settings["key_restart2"]=settings["key_tab2"]=
settings["key_save2"]=settings["key_load2"]=settings["key_swap2"]=settings["key_teleport2"]=
settings["key_suicide2"]=settings["key_shift2"]=settings["key_next2"]=settings["key_previous2"]=
settings["key_select2"]="0";
}
void Settings::parseFile(int version){
//Open the config file for reading.
ifstream file;
file.open(fileName.c_str());
if(!file){
//Check if the file exists.
if(fileExists(fileName.c_str())){
cerr<<"ERROR: Unable to read config file, default values will be used!"<<endl;
}else{
cout<<"Creating a new config file."<<endl;
createFile();
}
//No need to parse the unreadable or newly created config file.
return;
}
//Read the config file line by line.
string line;
while(getline(file,line)){
string temp = line;
unComment(temp);
if(temp.empty() || empty(temp))
continue;
//The line is good so we parse it.
parseLine(temp,version);
}
//And close the file.
file.close();
// Hardcode the addon url unless specified by command line arguments
settings["addon_url"] = DEFAULT_ADDON_URL;
}
void Settings::parseLine(const string &line,int version){
if((line.find('=') == line.npos) || !validLine(line))
cerr<<"WARNING: illegal line in config file!"<<endl;
string temp = line;
temp.erase(0, temp.find_first_not_of("\t "));
int seperator = temp.find('=');
//Get the key and trim it.
string key, value;
key = line.substr(0, seperator);
if(key.find('\t')!=line.npos || key.find(' ')!=line.npos)
key.erase(key.find_first_of("\t "));
//Get the value and trim it.
value = line.substr(seperator + 1);
value.erase(0, value.find_first_not_of("\t "));
value.erase(value.find_last_not_of("\t ") + 1);
//Check the version.
//Currently in V0.5 we don't read key config saved in versions which is earlier than V0.5, since they are incompatible (SDL1.2 vs SDL2).
if (version < 0x000500) {
if (key.size() >= 4 && key.substr(0, 4) == "key_") {
return;
}
}
//Add the setting to the settings map.
setValue(key,value);
}
bool Settings::validLine(const string &line){
string temp = line;
temp.erase(0, temp.find_first_not_of("\t "));
if(temp[0] == '=')
return false;
for(size_t i = temp.find('=') + 1; i < temp.length(); i++)
return true;
return false;
}
void Settings::unComment(string &line){
if(line.find('#') != line.npos)
line.erase(line.find('#'));
}
bool Settings::empty(const string &line){
return (line.find_first_not_of(' ')==line.npos);
}
string Settings::getValue(const string &key){
if(settings.find(key) == settings.end()){
cerr<<"WARNING: Key "<<key<<" couldn't be found!"<<endl;
return "";
}
return settings[key];
}
bool Settings::getBoolValue(const string &key){
if(settings.find(key) == settings.end()){
cerr<<"WARNING: Key "<<key<<" couldn't be found!"<<endl;
return false;
}
return (settings[key] != "0");
}
void Settings::setValue(const string &key, const string &value){
settings[key]=value;
}
void Settings::createFile(){
ofstream file;
file.open(fileName.c_str());
//Default Config file.
- file<<"#MeAndMyShadow config file. Created on "<<endl;
+ file<<"# MeAndMyShadow config file"<<endl;
map<string, string>::iterator iter;
for(iter = settings.begin(); iter != settings.end(); ++iter){
file << iter->first << " = " << iter->second << endl;
}
//And close the file.
file.close();
}
bool Settings::save(){
//Open the settings file.
ofstream file;
file.open(fileName.c_str());
//Check if the file could be opened.
if(!file)
return false;
-
- //First get the date and time.
- time_t rawtime;
- struct tm* timedate;
- char str_time[80];
-
- time(&rawtime);
- timedate=localtime(&rawtime);
-
- //NOTE: Function asctime() is marked obsolete in POSIX. So we're using strftime() instead.
- strftime(str_time,80,"%a %b %d %H:%M:%S %Y",timedate);
-
+
//Now write it to the first line of the config file.
- file<<"#MeAndMyShadow config file. Created on "<<str_time<<endl;
+ file<<"# MeAndMyShadow config file"<<endl;
//Loop through the settings and save them.
map<string,string>::const_iterator iter;
for(iter=settings.begin(); iter!=settings.end(); ++iter){
if (iter->first == "addon_url") {
// Hardcode the addon url
file << iter->first << " = " DEFAULT_ADDON_URL << endl;
} else {
file << iter->first << " = " << iter->second << endl;
}
}
file.close();
//No errors so return true.
return true;
}
diff --git a/src/StatisticsManager.cpp b/src/StatisticsManager.cpp
index f63fb2d..4dd015c 100644
--- a/src/StatisticsManager.cpp
+++ b/src/StatisticsManager.cpp
@@ -1,810 +1,804 @@
/*
* Copyright (C) 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 "StatisticsManager.h"
#include "FileManager.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "Functions.h"
#include "LevelPackManager.h"
#include "MusicManager.h"
#include "SoundManager.h"
#include "ThemeManager.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include "libs/tinyformat/tinyformat.h"
#include <SDL_ttf.h>
#if defined(WIN32)
#define PRINTF_LONGLONG "%I64d"
#else
#define PRINTF_LONGLONG "%lld"
#endif
using namespace std;
StatisticsManager statsMgr;
static const int achievementDisplayTime=(FPS*4500)/1000;
static const int achievementIntervalTime=achievementDisplayTime+(FPS*500)/1000;
static map<string,AchievementInfo*> avaliableAchievements;
//================================================================
StatisticsManager::StatisticsManager(){
bmDropShadow=NULL;
bmQuestionMark=NULL;
bmAchievement=NULL;
startTime=time(NULL);
tutorialLevels=0;
clear();
}
void StatisticsManager::clear(){
playerTravelingDistance=shadowTravelingDistance=0.0f;
playerJumps=shadowJumps
=playerDies=shadowDies
=playerSquashed=shadowSquashed
=completedLevels=silverLevels=goldLevels
=recordTimes=switchTimes=swapTimes=saveTimes=loadTimes
=playTime=levelEditTime
=createdLevels=tutorialCompleted=tutorialGold=0;
achievements.clear();
queuedAchievements.clear();
achievementTime=0;
currentAchievement=0;
if(bmAchievement){
bmAchievement.reset();
}
}
#define LOAD_STATS(var,func) { \
vector<string> &v=node.attributes[ #var ]; \
if(!v.empty() && !v[0].empty()) \
var=func(v[0].c_str()); \
}
void StatisticsManager::loadFile(const std::string& fileName){
clear();
ifstream file(fileName.c_str());
if(!file) return;
TreeStorageNode node;
POASerializer serializer;
if(!serializer.readNode(file,&node,true)) return;
//load statistics
LOAD_STATS(playerTravelingDistance,atof);
LOAD_STATS(shadowTravelingDistance,atof);
LOAD_STATS(playerJumps,atoi);
LOAD_STATS(shadowJumps,atoi);
LOAD_STATS(playerDies,atoi);
LOAD_STATS(shadowDies,atoi);
LOAD_STATS(playerSquashed,atoi);
LOAD_STATS(shadowSquashed,atoi);
LOAD_STATS(recordTimes,atoi);
LOAD_STATS(switchTimes,atoi);
LOAD_STATS(swapTimes,atoi);
LOAD_STATS(saveTimes,atoi);
LOAD_STATS(loadTimes,atoi);
LOAD_STATS(playTime,atoi);
LOAD_STATS(levelEditTime,atoi);
LOAD_STATS(createdLevels,atoi);
//load achievements.
//format is: name;time,name;time,...
{
vector<string> &v=node.attributes["achievements"];
for(unsigned int i=0;i<v.size();i++){
string s=v[i];
time_t t=0;
string::size_type lps=s.find(';');
if(lps!=string::npos){
string s1=s.substr(lps+1);
s=s.substr(0,lps);
long long n;
sscanf(s1.c_str(),PRINTF_LONGLONG,&n);
t=(time_t)n;
}
map<string,AchievementInfo*>::iterator it=avaliableAchievements.find(s);
if(it!=avaliableAchievements.end()){
OwnedAchievement ach={t,it->second};
achievements[it->first]=ach;
}
}
}
}
//Call when level edit is start
void StatisticsManager::startLevelEdit(){
levelEditStartTime=time(NULL);
}
//Call when level edit is end
void StatisticsManager::endLevelEdit(){
levelEditTime+=time(NULL)-levelEditStartTime;
}
//update in-game time
void StatisticsManager::updatePlayTime(){
time_t endTime=time(NULL);
playTime+=endTime-startTime;
startTime=endTime;
}
#define SAVE_STATS(var,pattern) { \
sprintf(s,pattern,var); \
node.attributes[ #var ].push_back(s); \
}
void StatisticsManager::saveFile(const std::string& fileName){
char s[64];
//update in-game time
updatePlayTime();
ofstream file(fileName.c_str());
if(!file) return;
TreeStorageNode node;
//save statistics
SAVE_STATS(playerTravelingDistance,"%.2f");
SAVE_STATS(shadowTravelingDistance,"%.2f");
SAVE_STATS(playerJumps,"%d");
SAVE_STATS(shadowJumps,"%d");
SAVE_STATS(playerDies,"%d");
SAVE_STATS(shadowDies,"%d");
SAVE_STATS(playerSquashed,"%d");
SAVE_STATS(shadowSquashed,"%d");
SAVE_STATS(recordTimes,"%d");
SAVE_STATS(switchTimes,"%d");
SAVE_STATS(swapTimes,"%d");
SAVE_STATS(saveTimes,"%d");
SAVE_STATS(loadTimes,"%d");
SAVE_STATS(playTime,"%d");
SAVE_STATS(levelEditTime,"%d");
SAVE_STATS(createdLevels,"%d");
//save achievements.
//format is: name;time,name;time,...
{
vector<string>& v=node.attributes["achievements"];
for(map<string,OwnedAchievement>::iterator it=achievements.begin();it!=achievements.end();++it){
stringstream strm;
char s[32];
long long n=it->second.achievedTime;
sprintf(s,PRINTF_LONGLONG,n);
strm<<it->first<<";"<<s;
v.push_back(strm.str());
}
}
POASerializer serializer;
serializer.writeNode(&node,file,true,true);
}
void StatisticsManager::loadPicture(SDL_Renderer& renderer, ImageManager& imageManager){
//Load drop shadow picture
bmDropShadow=imageManager.loadTexture(getDataPath()+"gfx/dropshadow.png", renderer);
bmQuestionMark=imageManager.loadImage(getDataPath()+"gfx/menu/questionmark.png");
}
void StatisticsManager::registerAchievements(ImageManager& imageManager){
if(!avaliableAchievements.empty()) return;
for(int i=0;achievementList[i].id!=NULL;i++){
avaliableAchievements[achievementList[i].id]=&achievementList[i];
if(achievementList[i].imageFile!=NULL){
achievementList[i].imageSurface = imageManager.loadImage(getDataPath()+achievementList[i].imageFile);
}
}
}
void StatisticsManager::render(ImageManager&,SDL_Renderer &renderer){
if(achievementTime==0 && !bmAchievement && currentAchievement<(int)queuedAchievements.size()){
//create surface
bmAchievement=createAchievementSurface(renderer, queuedAchievements[currentAchievement++]);
//FIXME: Draw the box.
//drawGUIBox(0,0,bmAchievement->w,bmAchievement->h,bmAchievement,0xFFFFFF00);
//check if queue is empty
if(currentAchievement>=(int)queuedAchievements.size()){
queuedAchievements.clear();
currentAchievement=0;
}
//play a sound
getSoundManager()->playSound("achievement", 1, false, 32);
}
//check if we need to display achievements
if(bmAchievement){
achievementTime++;
if(achievementTime<=0){
return;
}else if(achievementTime<=5){
drawAchievement(renderer,achievementTime);
}else if(achievementTime<=achievementDisplayTime-5){
drawAchievement(renderer,5);
}else if(achievementTime<achievementDisplayTime){
drawAchievement(renderer,achievementDisplayTime-achievementTime);
}else if(achievementTime>=achievementIntervalTime){
if(bmAchievement){
bmAchievement.reset();
}
achievementTime=0;
}
}
}
void StatisticsManager::newAchievement(const std::string& id,bool save){
//check avaliable achievements
map<string,AchievementInfo*>::iterator it=avaliableAchievements.find(id);
if(it==avaliableAchievements.end()) return;
//check if already have this achievement
if(save){
map<string,OwnedAchievement>::iterator it2=achievements.find(id);
if(it2!=achievements.end()) return;
OwnedAchievement ach={time(NULL),it->second};
achievements[id]=ach;
}
//add it to queue
queuedAchievements.push_back(it->second);
}
time_t StatisticsManager::achievedTime(const std::string& id) {
auto it = achievements.find(id);
if (it == achievements.end()) return 0;
else return it->second.achievedTime;
}
float StatisticsManager::getAchievementProgress(AchievementInfo* info){
if(!strcmp(info->id,"experienced")){
return float(completedLevels)/50.0f*100.0f;
}
if(!strcmp(info->id,"expert")){
return float(goldLevels)/50.0f*100.0f;
}
if(!strcmp(info->id,"tutorial")){
if(tutorialLevels>0)
return float(tutorialCompleted)/float(tutorialLevels)*100.0f;
else
return 0.0f;
}
if(!strcmp(info->id,"tutorialGold")){
if(tutorialLevels>0)
return float(tutorialGold)/float(tutorialLevels)*100.0f;
else
return 0.0f;
}
if(!strcmp(info->id,"create50")){
return float(createdLevels)/50.0f*100.0f;
}
if(!strcmp(info->id,"frog")){
return float(playerJumps+shadowJumps)/1000.0f*100.0f;
}
if(!strcmp(info->id,"die50")){
return float(playerDies+shadowDies)/50.0f*100.0f;
}
if(!strcmp(info->id,"die1000")){
return float(playerDies+shadowDies)/1000.0f*100.0f;
}
if(!strcmp(info->id,"suqash50")){
return float(playerSquashed+shadowSquashed)/50.0f*100.0f;
}
if(!strcmp(info->id,"travel100")){
return (playerTravelingDistance+shadowTravelingDistance)/100.0f*100.0f;
}
if(!strcmp(info->id,"travel1k")){
return (playerTravelingDistance+shadowTravelingDistance)/1000.0f*100.0f;
}
if(!strcmp(info->id,"travel10k")){
return (playerTravelingDistance+shadowTravelingDistance)/10000.0f*100.0f;
}
if(!strcmp(info->id,"travel42k")){
return (playerTravelingDistance+shadowTravelingDistance)/42195.0f*100.0f;
}
if(!strcmp(info->id,"record100")){
return float(recordTimes)/100.0f*100.0f;
}
if(!strcmp(info->id,"record1k")){
return float(recordTimes)/1000.0f*100.0f;
}
if(!strcmp(info->id,"switch100")){
return float(switchTimes)/100.0f*100.0f;
}
if(!strcmp(info->id,"switch1k")){
return float(switchTimes)/1000.0f*100.0f;
}
if(!strcmp(info->id,"swap100")){
return float(swapTimes)/100.0f*100.0f;
}
if(!strcmp(info->id,"swap1k")){
return float(swapTimes)/1000.0f*100.0f;
}
//not found
return 0.0f;
}
SharedTexture StatisticsManager::createAchievementSurface(SDL_Renderer& renderer, AchievementInfo* info,SDL_Rect* rect,bool showTip,const time_t *achievedTime){
if(info==NULL || info->id==NULL) return NULL;
//prepare text
SurfacePtr title0(nullptr);
SurfacePtr title1(nullptr);
vector<SDL_Surface*> descSurfaces;
SDL_Color fg = objThemes.getTextColor(true);
int fontHeight=TTF_FontLineSkip(fontText);
bool showDescription=false;
bool showImage=false;
float achievementProgress=0.0f;
if(showTip){
title0.reset(TTF_RenderUTF8_Blended(fontText,_("New achievement:"),fg));
title1.reset(TTF_RenderUTF8_Blended(fontGUISmall,_(info->name),fg));
showDescription=showImage=true;
}else if(achievedTime){
- char s[128];
+ char s[256];
strftime(s,sizeof(s),"%c",localtime(achievedTime));
- stringstream strm;
- tinyformat::format(strm,_("Achieved on %s"),s);
-
title0.reset(TTF_RenderUTF8_Blended(fontGUISmall,_(info->name),fg));
- title1.reset(TTF_RenderUTF8_Blended(fontText,strm.str().c_str(),fg));
+ title1.reset(TTF_RenderUTF8_Blended(fontText, tfm::format(_("Achieved on %s"), (char*)s).c_str(), fg));
showDescription=showImage=true;
}else if(info->displayStyle==ACHIEVEMENT_HIDDEN){
title0.reset(TTF_RenderUTF8_Blended(fontGUISmall,_("Unknown achievement"),fg));
}else{
if(info->displayStyle==ACHIEVEMENT_PROGRESS){
achievementProgress=getAchievementProgress(info);
- stringstream strm;
- tinyformat::format(strm,_("Achieved %1.0f%%"),achievementProgress);
-
- title1.reset(TTF_RenderUTF8_Blended(fontText,strm.str().c_str(),fg));
+ title1.reset(TTF_RenderUTF8_Blended(fontText, tfm::format(_("Achieved %1.0f%%"), achievementProgress).c_str(), fg));
}else{
title1.reset(TTF_RenderUTF8_Blended(fontText,_("Not achieved"),fg));
}
title0.reset(TTF_RenderUTF8_Blended(fontGUISmall,_(info->name),fg));
showDescription= info->displayStyle==ACHIEVEMENT_ALL || info->displayStyle==ACHIEVEMENT_PROGRESS;
showImage=true;
}
if(info->description!=NULL && showDescription){
string description=_(info->description);
string::size_type lps=0,lpe;
for(;;){
lpe=description.find('\n',lps);
if(lpe==string::npos){
descSurfaces.push_back(TTF_RenderUTF8_Blended(fontText,(description.substr(lps)+' ').c_str(),fg));
break;
}else{
descSurfaces.push_back(TTF_RenderUTF8_Blended(fontText,(description.substr(lps,lpe-lps)+' ').c_str(),fg));
lps=lpe+1;
}
}
}
//calculate the size
int w=0,h=0,w1=8,h1=0;
if(title0!=NULL){
if(title0->w>w) w=title0->w;
h1+=title0->h;
}
if(title1!=NULL){
if(title1->w>w) w=title1->w;
h1+=title1->h;
/*//calc progress bar size
if(!showTip && !achievedTime && info->displayStyle==ACHIEVEMENT_PROGRESS){
h1+=4;
}*/
}
const int preferredImageWidth = 50;
const int preferredImageHeight = 50;
if(showImage){
if(info->imageSurface!=NULL){
// NEW: we have the preferred image size
const int width = std::max(info->r.w, preferredImageWidth);
const int height = std::max(info->r.h, preferredImageHeight);
w1+=width+8;
w+=width+8;
if(height>h1) h1=height;
}
}else{
w1+=bmQuestionMark->w+8;
w+=bmQuestionMark->w+8;
if(bmQuestionMark->h>h1) h1=bmQuestionMark->h;
}
h=h1+8;
for(unsigned int i=0;i<descSurfaces.size();i++){
if(descSurfaces[i]!=NULL){
if(descSurfaces[i]->w>w) w=descSurfaces[i]->w;
}
}
h+=descSurfaces.size()*fontHeight;
w+=16;
h+=16;
//check if size is specified
int left=0,top=0;
if(rect!=NULL){
//NOTE: SDL2 port. This was never used.
/* if(surface!=NULL){
left=rect->x;
top=rect->y;
}*/
if(rect->w>0) w=rect->w;
else rect->w=w;
rect->h=h;
}
//create surface if necessary
SurfacePtr surface = createSurface(w, h);
std::unique_ptr<SDL_Renderer,decltype(&SDL_DestroyRenderer)> surfaceRenderer(
SDL_CreateSoftwareRenderer(surface.get()), &SDL_DestroyRenderer);
//draw background
const SDL_Rect r={left,top,w,h};
if(showTip || achievedTime){
SDL_FillRect(surface.get(),&r,SDL_MapRGB(surface->format,255,255,255));
}else{
SDL_FillRect(surface.get(),&r,SDL_MapRGB(surface->format,192,192,192));
}
//draw horizontal separator
//FIXME: this is moved from StatisticsScreen::createGUI
if (!showTip) {
const SDL_Rect r0 = { left, top, w, 1 };
const SDL_Rect r1 = { left, top + h - 2, w, 1 };
const SDL_Rect r2 = { left, top + h - 1, w, 1 };
Uint32 c0 = achievedTime ? SDL_MapRGB(surface->format, 224, 224, 224) : SDL_MapRGB(surface->format, 168, 168, 168);
Uint32 c2 = achievedTime ? SDL_MapRGB(surface->format, 128, 128, 128) : SDL_MapRGB(surface->format, 96, 96, 96);
SDL_FillRect(surface.get(), &r0, c0);
SDL_FillRect(surface.get(), &r1, c0);
SDL_FillRect(surface.get(), &r2, c2);
}
//draw picture
if(showImage){
if(info->imageSurface){
// NEW: we have the preferred image size
SDL_Rect r={left+8,top+8+(h1-info->r.h)/2,0,0};
if (info->r.w < preferredImageWidth) r.x += (preferredImageWidth - info->r.w) / 2;
SDL_BlitSurface(info->imageSurface,&info->r,surface.get(),&r);
}
}else{
SDL_Rect r={left+8,top+8+(h1-bmQuestionMark->h)/2,0,0};
SDL_BlitSurface(bmQuestionMark,NULL,surface.get(),&r);
}
//draw text
h=8;
if(title0){
SDL_Rect r={left+w1,top+h,0,0};
SDL_BlitSurface(title0.get(),NULL,surface.get(),&r);
h+=title0->h;
}
if(title1){
SDL_Rect r={left+w1,top+h,0,0};
//Draw progress bar.
if(!showTip && !achievedTime && info->displayStyle==ACHIEVEMENT_PROGRESS){
//Draw borders.
SDL_Rect r1={r.x,r.y,w-8-r.x,title1->h};
drawGUIBox(r1.x,r1.y,r1.w,r1.h,*surfaceRenderer,0x1D);
//Draw progress.
r1.x++;
r1.y++;
r1.w=int(achievementProgress/100.0f*float(r1.w-2)+0.5f);
r1.h-=2;
SDL_SetRenderDrawColor(surfaceRenderer.get(),0,0,0,100);
SDL_RenderFillRect(surfaceRenderer.get(),&r1);
//shift the text a little bit (???)
r.x+=2;
r.y+=2;
}
//Draw text.
SDL_BlitSurface(title1.get(),NULL,surface.get(),&r);
}
h=h1+16;
for(unsigned int i=0;i<descSurfaces.size();i++){
if(descSurfaces[i]!=NULL){
SDL_Rect r={left+8,top+h+static_cast<int>(i)*fontHeight,0,0};
SDL_BlitSurface(descSurfaces[i],NULL,surface.get(),&r);
}
}
//clean up
for(unsigned int i=0;i<descSurfaces.size();i++){
if(descSurfaces[i]!=NULL){
SDL_FreeSurface(descSurfaces[i]);
}
}
//FIXME: Should we clear the vector here?
//over
return textureFromSurface(renderer, std::move(surface));
}
void StatisticsManager::drawAchievement(SDL_Renderer& renderer,int alpha){
if(!bmAchievement || alpha<=0) {
return;
}
if(alpha>5) alpha=5;
SDL_Rect r = rectFromTexture(*bmAchievement);
int w=0,h=0;
SDL_GetRendererOutputSize(&renderer, &w, &h);
r.x = w-32-r.w;
r.y = 32;
SDL_SetTextureAlphaMod(bmAchievement.get(), alpha*40);
applyTexture(r.x, r.y,bmAchievement, renderer);
if(!bmDropShadow) {
return;
}
//draw drop shadow - corner
{
int w1=r.w/2,w2=r.w-w1,h1=r.h/2,h2=r.h-h1;
if(w1>16) w1=16;
if(w2>16) w2=16;
if(h1>16) h1=16;
if(h2>16) h2=16;
const int x=(5-alpha)*64;
//top-left
SDL_Rect r1={x,0,w1+16,h1+16};//),r2={r.x-16,r.y-16,0,0};
SDL_Rect r2 ={r.x-16, r.y-16, r1.w, r1.h};
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//top-right
r1.x=x+48-w2;r2.w=r1.w =w2+16;r2.x=r.x+r.w-w2;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//bottom-right
r1.y=48-h2;r2.h=r1.h=h2+16;r2.y=r.y+r.h-h2;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//bottom-left
r1.x=x;r2.w=r1.w=w1+16;r2.x=r.x-16;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
}
//draw drop shadow - border
int i=r.w-32;
while(i>0){
const int ii=i>128?128:i;
//top
SDL_Rect r1={0,256-alpha*16,ii,16};
SDL_Rect r2={r.x+r.w-16-i,r.y-16,r1.w,r1.h};
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//bottom
r1.x=128;r2.y=r.y+r.h;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
i-=ii;
}
i=r.h-32;
while(i>0){
const int ii=i>128?128:i;
//top
SDL_Rect r1={512-alpha*16,0,16,ii};
SDL_Rect r2={r.x-16,r.y+r.h-16-i, r1.w, r1.h};
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
//bottom
r1.y=128;r2.x=r.x+r.w;
SDL_RenderCopy(&renderer, bmDropShadow.get(), &r1, &r2);
i-=ii;
}
}
void StatisticsManager::reloadCompletedLevelsAndAchievements(){
completedLevels=silverLevels=goldLevels=0;
LevelPackManager *lpm=getLevelPackManager();
vector<pair<string,string> > v=lpm->enumLevelPacks();
bool tutorial=false,tutorialIsGold=false;
for(unsigned int i=0;i<v.size();i++){
string& s=v[i].first;
LevelPack *levels=lpm->getLevelPack(s);
levels->loadProgress();
bool b=false;
if(s==lpm->tutorialLevelPackPath){
tutorialLevels=levels->getLevelCount();
tutorialCompleted=tutorialGold=0;
b=tutorial=tutorialIsGold=true;
}
for(int n=0,m=levels->getLevelCount();n<m;n++){
LevelPack::Level *lv=levels->getLevel(n);
int medal=lv->won;
if(medal){
if(lv->targetTime<0 || lv->time<=lv->targetTime)
medal++;
if(lv->targetRecordings<0 || lv->recordings<=lv->targetRecordings)
medal++;
completedLevels++;
if(b) tutorialCompleted++;
if(medal==2) silverLevels++;
if(medal==3){
goldLevels++;
if(b) tutorialGold++;
}
if(medal!=3 && b) tutorialIsGold=false;
}else if(b){
tutorial=tutorialIsGold=false;
}
}
}
//upadte achievements
updateLevelAchievements();
updateTutorialAchievementsInternal((tutorial?1:0)|(tutorialIsGold?2:0));
}
void StatisticsManager::reloadOtherAchievements(){
int i;
if(playTime>=7200) newAchievement("addicted");
if(playTime>=86400) newAchievement("loyalFan");
if(levelEditTime>=7200) newAchievement("constructor");
if(levelEditTime>=86400) newAchievement("constructor2");
if(createdLevels>=1) newAchievement("create1");
if(createdLevels>=50) newAchievement("create50");
i=playerJumps+shadowJumps;
if(i>=1000) newAchievement("frog");
i=playerDies+shadowDies;
if(i>=1) newAchievement("die1");
if(i>=50) newAchievement("die50");
if(i>=1000) newAchievement("die1000");
i=playerSquashed+shadowSquashed;
if(i>=1) newAchievement("squash1");
if(i>=50) newAchievement("squash50");
float d=playerTravelingDistance+shadowTravelingDistance;
if(d>=100.0f) newAchievement("travel100");
if(d>=1000.0f) newAchievement("travel1k");
if(d>=10000.0f) newAchievement("travel10k");
if(d>=42195.0f) newAchievement("travel42k");
if(recordTimes>=100) newAchievement("record100");
if(recordTimes>=1000) newAchievement("record1k");
if(switchTimes>=100) newAchievement("switch100");
if(switchTimes>=1000) newAchievement("switch1k");
if(swapTimes>=100) newAchievement("swap100");
if(swapTimes>=1000) newAchievement("swap1k");
if(saveTimes>=1000) newAchievement("save1k");
if(loadTimes>=1000) newAchievement("load1k");
if (version.find("Development") != string::npos
|| version.find("Alpha") != string::npos
|| version.find("Beta") != string::npos
|| version.find("RC") != string::npos
|| version.find("Candidate") != string::npos)
{
newAchievement("programmer");
}
}
//Update level specified achievements.
//Make sure the completed level count is correct.
void StatisticsManager::updateLevelAchievements(){
if(completedLevels>=1) newAchievement("newbie");
if(goldLevels>=1) newAchievement("goodjob");
if(completedLevels>=50) newAchievement("experienced");
if(goldLevels>=50) newAchievement("expert");
}
//Update tutorial specified achievements.
//Make sure the level progress of tutorial is correct.
void StatisticsManager::updateTutorialAchievements(){
//find tutorial level pack
LevelPackManager *lpm=getLevelPackManager();
LevelPack *levels=lpm->getTutorialLevelPack();
if(levels==NULL) return;
bool tutorial=true,tutorialIsGold=true;
tutorialLevels=levels->getLevelCount();
tutorialCompleted=tutorialGold=0;
for(int n=0,m=levels->getLevelCount();n<m;n++){
LevelPack::Level *lv=levels->getLevel(n);
int medal=lv->won;
if(medal){
if(lv->targetTime<0 || lv->time<=lv->targetTime)
medal++;
if(lv->targetRecordings<0 || lv->recordings<=lv->targetRecordings)
medal++;
tutorialCompleted++;
if(medal!=3) tutorialIsGold=false;
else tutorialGold++;
}else{
tutorial=tutorialIsGold=false;
break;
}
}
//upadte achievements
updateTutorialAchievementsInternal((tutorial?1:0)|(tutorialIsGold?2:0));
}
//internal function
//flags: a bit-field value indicates which achievements we have.
void StatisticsManager::updateTutorialAchievementsInternal(int flags){
if(flags&1) newAchievement("tutorial");
if(flags&2) newAchievement("tutorialGold");
}
diff --git a/src/StatisticsScreen.cpp b/src/StatisticsScreen.cpp
index f4307de..7cd7997 100644
--- a/src/StatisticsScreen.cpp
+++ b/src/StatisticsScreen.cpp
@@ -1,411 +1,416 @@
/*
* Copyright (C) 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 <stdio.h>
#include <string>
#include <vector>
#include <map>
#include "StatisticsManager.h"
#include "StatisticsScreen.h"
#include "Globals.h"
#include "Functions.h"
#include "ThemeManager.h"
#include "InputManager.h"
#include "GUIListBox.h"
#include "GUIScrollBar.h"
#include "EasterEggScreen.h"
#include <SDL_ttf.h>
-#include <array>
+
+#include "libs/tinyformat/tinyformat.h"
using namespace std;
//GUI events are handled here.
//name: The name of the element that invoked the event.
//obj: Pointer to the object that invoked the event.
//eventType: Integer containing the type of event.
void StatisticsScreen::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Check what type of event it was.
if(eventType==GUIEventClick){
if(name=="cmdBack"){
//Goto the main menu.
setNextState(STATE_MENU);
}
}
}
//Constructor.
StatisticsScreen::StatisticsScreen(ImageManager& imageManager, SDL_Renderer& renderer){
//Update in-game time.
statsMgr.updatePlayTime();
//Render the title.
title = titleTextureFromText(renderer, _("Achievements and Statistics"), objThemes.getTextColor(false), SCREEN_WIDTH);
//Create GUI.
createGUI(imageManager, renderer);
}
//Destructor.
StatisticsScreen::~StatisticsScreen(){
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//we are so lazy that we just use height of the first text, ignore the others
#define DRAW_PLAYER_STATISTICS(name,var,fmt) { \
SurfacePtr surface(TTF_RenderUTF8_Blended(fontGUISmall,name,objThemes.getTextColor(true))); \
SurfacePtr stats = createSurface(w,surface->h); \
SDL_FillRect(stats.get(),NULL,-1); \
applySurface(4,0,surface.get(),stats.get(),NULL); \
y=surface->h; \
- SDL_snprintf(formatString.data(),formatString.size(),fmt,statsMgr.player##var+statsMgr.shadow##var); \
- surface.reset(TTF_RenderUTF8_Blended(fontText,formatString.data(),objThemes.getTextColor(true))); \
+ surface.reset(TTF_RenderUTF8_Blended(fontText, \
+ tfm::format(fmt,statsMgr.player##var+statsMgr.shadow##var).c_str(), \
+ objThemes.getTextColor(true))); \
applySurface(w-260-surface->w,(y-surface->h)/2,surface.get(),stats.get(),NULL); \
- SDL_snprintf(formatString.data(),formatString.size(),fmt,statsMgr.player##var); \
- surface.reset(TTF_RenderUTF8_Blended(fontText,formatString.data(),objThemes.getTextColor(true))); \
+ surface.reset(TTF_RenderUTF8_Blended(fontText, \
+ tfm::format(fmt,statsMgr.player##var).c_str(), \
+ objThemes.getTextColor(true))); \
applySurface(w-140-surface->w,(y-surface->h)/2,surface.get(),stats.get(),NULL); \
- SDL_snprintf(formatString.data(),formatString.size(),fmt,statsMgr.shadow##var); \
- surface.reset(TTF_RenderUTF8_Blended(fontText,formatString.data(),objThemes.getTextColor(true))); \
+ surface.reset(TTF_RenderUTF8_Blended(fontText, \
+ tfm::format(fmt,statsMgr.shadow##var).c_str(), \
+ objThemes.getTextColor(true))); \
applySurface(w-20-surface->w,(y-surface->h)/2,surface.get(),stats.get(),NULL); \
list->addItem(renderer,"",textureFromSurface(renderer, std::move(stats))); /* add it to list box */ \
}
//Add an item to the listbox, that displays "name1", and "var1" formatted with "format"
//we are so lazy that we just use height of the first text, ignore the others
template <class T1>
static void drawMiscStatistics1(SDL_Renderer& renderer, int w,GUIListBox *list,const char* name1,const T1 var1,const char* format1){
//create new surface
SurfacePtr nameSurface(TTF_RenderUTF8_Blended(fontGUISmall,name1,objThemes.getTextColor(true)));
SurfacePtr stats=createSurface(w, nameSurface->h);
SDL_FillRect(stats.get(),NULL,-1);
applySurface(4,0,nameSurface.get(),stats.get(),NULL);
const int x=nameSurface->w+8;
const int y=nameSurface->h;
- //draw value
- //char s[1024];
- std::array<char, 1024> s;
- SDL_snprintf(s.data(),s.size(),format1,var1);
- SurfacePtr formatSurface(TTF_RenderUTF8_Blended(fontText,s.data(),objThemes.getTextColor(true)));
+ SurfacePtr formatSurface(TTF_RenderUTF8_Blended(fontText,
+ tfm::format(format1,var1).c_str(),
+ objThemes.getTextColor(true)));
//NOTE: SDL2 port. Not halving the y value here as this ends up looking better.
applySurface(x,y-formatSurface->h,formatSurface.get(),stats.get(),NULL);
//add it to list box
list->addItem(renderer, "",textureFromSurface(renderer, std::move(stats)));
//over
//return stats;
}
//NOTE: Disabled this for the SDL2 port for now. It looks a bit off anyhow.
//Might want to make a more general method that draws as many "cells" as there is space.
//Draws two stats on one line if there is space.
//we are so lazy that we just use height of the first text, ignore the others
/*template <class T1,class T2>
static void drawMiscStatistics2(int w,GUIListBox *list,const char* name1,const T1 var1,const char* format1,const char* name2,const T2 var2,const char* format2){
SDL_Surface* stats=drawMiscStatistics1(w,list,name1,var1,format1);
//Check if the width is enough
if(w>=800){
//draw name
SDL_Surface* surface=TTF_RenderUTF8_Blended(fontGUISmall,name2,objThemes.getTextColor(true));
applySurface(w/2-8,stats->h-surface->h,surface,stats,NULL);
int x=surface->w+w/2;
SDL_FreeSurface(surface);
//draw value
char s[1024];
- sprintf(s,format2,var2);
+ //FIXME: Use tfm::format instead of sprintf to enable locale support
+ FIXME_sprintf(s,format2,var2);
surface=TTF_RenderUTF8_Blended(fontText,s,objThemes.getTextColor(true));
applySurface(x,(stats->h-surface->h)/2,surface,stats,NULL);
SDL_FreeSurface(surface);
}else{
//Split into two rows
drawMiscStatistics1(w,list,name2,var2,format2);
}
}*/
void StatisticsScreen::addAchievements(ImageManager& imageManager, SDL_Renderer &renderer, GUIListBox *list, bool revealUnknownAchievements) {
for (int idx = 0; achievementList[idx].id != NULL; ++idx) {
time_t *lpt = NULL;
map<string, OwnedAchievement>::iterator it = statsMgr.achievements.find(achievementList[idx].id);
if (it != statsMgr.achievements.end()) {
lpt = &it->second.achievedTime;
}
AchievementInfo info = achievementList[idx];
if (revealUnknownAchievements) {
if (info.displayStyle == ACHIEVEMENT_HIDDEN || info.displayStyle == ACHIEVEMENT_TITLE) {
info.displayStyle = ACHIEVEMENT_ALL;
}
}
SDL_Rect r;
r.x = r.y = 0;
r.w = list->width - 16;
auto surface = statsMgr.createAchievementSurface(renderer, &info, &r, false, lpt);
if (surface){
list->addItem(renderer, "", surface);
}
}
}
//Method that will create the GUI.
void StatisticsScreen::createGUI(ImageManager& imageManager, SDL_Renderer &renderer){
//Create the root element of the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Create back button.
GUIObject* obj=new GUIButton(imageManager,renderer,SCREEN_WIDTH*0.5,SCREEN_HEIGHT-60,-1,36,_("Back"),0,true,true,GUIGravityCenter);
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
//Create list box.
listBox=new GUISingleLineListBox(imageManager,renderer,(SCREEN_WIDTH-500)/2,104,500,32);
listBox->addItem(_("Achievements"));
listBox->addItem(_("Statistics"));
listBox->value=0;
GUIObjectRoot->addChild(listBox);
//Create list box for achievements.
GUIListBox *list=new GUIListBox(imageManager,renderer,64,150,SCREEN_WIDTH-128,SCREEN_HEIGHT-150-72);
list->selectable=false;
GUIObjectRoot->addChild(list);
lists.clear();
lists.push_back(list);
addAchievements(imageManager, renderer, list);
//Now create list box for statistics.
list=new GUIListBox(imageManager,renderer,64,150,SCREEN_WIDTH-128,SCREEN_HEIGHT-150-72,true,false);
list->selectable=false;
GUIObjectRoot->addChild(list);
lists.push_back(list);
//Load needed pictures.
//FIXME: hard-coded image path
//TODO: Might want to consider not caching these as most other stuff use textures now.
SDL_Surface* bmPlayer=imageManager.loadImage(getDataPath()+"themes/Cloudscape/characters/player.png");
SDL_Surface* bmShadow=imageManager.loadImage(getDataPath()+"themes/Cloudscape/characters/shadow.png");
SDL_Surface* bmMedal=imageManager.loadImage(getDataPath()+"gfx/medals.png");
- //Render stats.
- //char s[64],s2[64];
- std::array<char, 64> formatString;
-
SDL_Rect r;
int x,y,w=SCREEN_WIDTH-128;
SharedTexture h_bar = [&](){
//The horizontal bar.
SurfacePtr h_bar(createSurface(w,2));
SDL_Color c = objThemes.getTextColor(true);
Uint32 clr=SDL_MapRGB(h_bar->format,c.r,c.g,c.b);
SDL_FillRect(h_bar.get(),NULL,clr);
return textureFromSurface(renderer, std::move(h_bar));
}();
//Player and shadow specific statistics
//The header.
{
SurfacePtr stats = createSurface(w, 44);
SDL_FillRect(stats.get(),NULL,-1);
SurfacePtr surface(TTF_RenderUTF8_Blended(fontGUISmall,_("Total"),objThemes.getTextColor(true)));
applySurface(w-260-surface->w,stats->h-surface->h,surface.get(),stats.get(),NULL);
//FIXME: hard-coded player and shadow images
r.x=0;r.y=0;r.w=23;r.h=40;
applySurface(w-140-r.w,stats.get()->h-40,bmPlayer,stats.get(),&r);
applySurface(w-20-r.w,stats.get()->h-40,bmShadow,stats.get(),&r);
list->addItem(renderer, "",textureFromSurface(renderer, std::move(stats)));
}
//Each items.
{
DRAW_PLAYER_STATISTICS(_("Traveling distance (m)"),TravelingDistance,"%0.1f");
DRAW_PLAYER_STATISTICS(_("Jump times"),Jumps,"%d");
DRAW_PLAYER_STATISTICS(_("Die times"),Dies,"%d");
DRAW_PLAYER_STATISTICS(_("Squashed times"),Squashed,"%d");
}
//Game specific statistics.
list->addItem(renderer, "",h_bar);
auto drawMiscStats = [&](const char* name1,const int var1,const char* format1) {
drawMiscStatistics1(renderer, w, list, name1, var1, format1);
};
drawMiscStats(_("Recordings:"),statsMgr.recordTimes,"%d");
drawMiscStats(_("Switch pulled times:"),statsMgr.switchTimes,"%d");
drawMiscStats(_("Swap times:"),statsMgr.swapTimes,"%d");
drawMiscStats(_("Save times:"),statsMgr.saveTimes,"%d");
drawMiscStats(_("Load times:"),statsMgr.loadTimes,"%d");
//Level specific statistics
list->addItem(renderer, "",h_bar);
{
SurfacePtr surface(TTF_RenderUTF8_Blended(fontGUISmall,_("Completed levels:"),objThemes.getTextColor(true)));
SurfacePtr stats = createSurface(w, surface->h);
SDL_FillRect(stats.get(),NULL,-1);
applySurface(4,0,surface.get(),stats.get(),NULL);
x=surface->w+8;
y=surface->h;
- SDL_snprintf(formatString.data(), formatString.size(),"%d",statsMgr.completedLevels);
- surface.reset(TTF_RenderUTF8_Blended(fontText,formatString.data(),objThemes.getTextColor(true)));
+ surface.reset(TTF_RenderUTF8_Blended(fontText,
+ tfm::format("%d", statsMgr.completedLevels).c_str(),
+ objThemes.getTextColor(true)));
applySurface(x,(y-surface->h),surface.get(),stats.get(),NULL);
- SDL_snprintf(formatString.data(), formatString.size(),"%d",statsMgr.completedLevels-statsMgr.goldLevels-statsMgr.silverLevels);
- surface.reset(TTF_RenderUTF8_Blended(fontText,formatString.data(),objThemes.getTextColor(true)));
+ surface.reset(TTF_RenderUTF8_Blended(fontText,
+ tfm::format("%d", statsMgr.completedLevels - statsMgr.goldLevels - statsMgr.silverLevels).c_str(),
+ objThemes.getTextColor(true)));
applySurface(w-260-surface->w,(y-surface->h)/2,surface.get(),stats.get(),NULL);
r.x=0;r.y=0;r.w=30;r.h=30;
applySurface(w-260-surface->w-30,(y-30)/2,bmMedal,stats.get(),&r);
- SDL_snprintf(formatString.data(), formatString.size(),"%d",statsMgr.silverLevels);
- surface.reset(TTF_RenderUTF8_Blended(fontText,formatString.data(),objThemes.getTextColor(true)));
+ surface.reset(TTF_RenderUTF8_Blended(fontText,
+ tfm::format("%d", statsMgr.silverLevels).c_str(),
+ objThemes.getTextColor(true)));
applySurface(w-140-surface->w,(y-surface->h)/2,surface.get(),stats.get(),NULL);
r.x+=30;
applySurface(w-140-surface->w-30,(y-30)/2,bmMedal,stats.get(),&r);
- SDL_snprintf(formatString.data(), formatString.size(),"%d",statsMgr.goldLevels);
- surface.reset(TTF_RenderUTF8_Blended(fontText,formatString.data(),objThemes.getTextColor(true)));
+ surface.reset(TTF_RenderUTF8_Blended(fontText,
+ tfm::format("%d", statsMgr.goldLevels).c_str(),
+ objThemes.getTextColor(true)));
applySurface(w-20-surface->w,(y-surface->h)/2,surface.get(),stats.get(),NULL);
r.x+=30;
applySurface(w-20-surface->w-30,(y-30)/2,bmMedal,stats.get(),&r);
list->addItem(renderer,"",textureFromSurface(renderer, std::move(stats)));
}
//Other statistics.
list->addItem(renderer, "",h_bar);
- SDL_snprintf(formatString.data(), formatString.size(),"%02d:%02d:%02d",statsMgr.playTime/3600,(statsMgr.playTime/60)%60,statsMgr.playTime%60);
- drawMiscStatistics1(renderer,w,list,_("In-game time:"),formatString.data(),"%s");
- SDL_snprintf(formatString.data(), formatString.size(),"%02d:%02d:%02d",statsMgr.levelEditTime/3600,(statsMgr.levelEditTime/60)%60,statsMgr.levelEditTime%60);
- drawMiscStatistics1(renderer,w,list,_("Level editing time:"),formatString.data(),"%s");
+ drawMiscStatistics1(renderer,w,list,_("In-game time:"),
+ tfm::format("%02d:%02d:%02d", statsMgr.playTime / 3600, (statsMgr.playTime / 60) % 60, statsMgr.playTime % 60),
+ "%s");
+ drawMiscStatistics1(renderer,w,list,_("Level editing time:"),
+ tfm::format("%02d:%02d:%02d", statsMgr.levelEditTime / 3600, (statsMgr.levelEditTime / 60) % 60, statsMgr.levelEditTime % 60),
+ "%s");
drawMiscStats(_("Created levels:"),statsMgr.createdLevels,"%d");
}
//In this method all the key and mouse events should be handled.
//NOTE: The GUIEvents won't be handled here.
void StatisticsScreen::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//Check if we need to quit, if so enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//Check horizontal movement
int value = listBox->value;
if (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT)){
isKeyboardOnly = true;
value++;
if (value >= (int)listBox->item.size()) value = 0;
} else if (inputMgr.isKeyDownEvent(INPUTMGR_LEFT)){
isKeyboardOnly = true;
value--;
if (value < 0) value = listBox->item.size() - 1;
}
listBox->value = value;
//Check vertical movement
if (value >= 0 && value < (int)lists.size()) {
if (inputMgr.isKeyDownEvent(INPUTMGR_UP)){
isKeyboardOnly = true;
lists[value]->scrollScrollbar(-1);
} else if (inputMgr.isKeyDownEvent(INPUTMGR_DOWN)){
isKeyboardOnly = true;
lists[value]->scrollScrollbar(1);
}
}
//Yet another cheat "ls -la" which reveals all unknown achievements
static char input[6];
static int inputLen = 0;
if (value == 0) {
if (event.type == SDL_KEYDOWN) {
if (event.key.keysym.sym >= 32 && event.key.keysym.sym <= 126) {
if (inputLen < sizeof(input)) input[inputLen] = event.key.keysym.sym;
inputLen++;
} else {
if (event.key.keysym.sym == SDLK_RETURN && inputLen == 6 &&
input[0] == 'l' && input[1] == 's' && input[2] == ' ' && input[3] == '-' && input[4] == 'l' && input[5] == 'a')
{
if (easterEggScreen(imageManager, renderer)) {
//new achievement
statsMgr.newAchievement("cheat");
//reload achievement list with hidden achievements revealed
lists[0]->clearItems();
addAchievements(imageManager, renderer, lists[0], true);
}
}
inputLen = 0;
}
}
} else {
inputLen = 0;
}
//Check if the escape button is pressed, if so go back to the main menu.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
}
//All the logic that needs to be done should go in this method.
void StatisticsScreen::logic(ImageManager&, SDL_Renderer&){
}
//This method handles all the rendering.
void StatisticsScreen::render(ImageManager&, SDL_Renderer& renderer){
//Draw background.
objThemes.getBackground(true)->draw(renderer);
objThemes.getBackground(true)->updateAnimation();
//Draw title.
drawTitleTexture(SCREEN_WIDTH, *title, renderer);
//Draw statistics.
int value=listBox->value;
for(unsigned int i=0;i<lists.size();i++){
lists[i]->visible=(i==value);
}
}
//Method that will be called when the screen size has been changed in runtime.
void StatisticsScreen::resize(ImageManager &imageManager, SDL_Renderer &renderer){
//Recreate the gui to fit the new resolution.
createGUI(imageManager, renderer);
}
diff --git a/src/libs/tinyformat/tinyformat.cpp b/src/libs/tinyformat/tinyformat.cpp
new file mode 100644
index 0000000..6392833
--- /dev/null
+++ b/src/libs/tinyformat/tinyformat.cpp
@@ -0,0 +1,21 @@
+#include "tinyformat.h"
+#include <locale>
+
+namespace tinyformat {
+
+std::locale locale = std::locale::classic();
+
+void setLocale(const char* std_name) {
+ try {
+ tfm::locale = std::locale(std_name);
+ } catch (...) {
+ std::cerr << "ERROR: Failed to create std::locale for '" << std_name << "'!" << std::endl;
+ tfm::locale = std::locale::classic();
+ }
+}
+
+void imbue(std::ostream& o) {
+ o.imbue(tfm::locale);
+}
+
+}
diff --git a/src/libs/tinyformat/tinyformat.h b/src/libs/tinyformat/tinyformat.h
index 0449875..aa95a01 100644
--- a/src/libs/tinyformat/tinyformat.h
+++ b/src/libs/tinyformat/tinyformat.h
@@ -1,1039 +1,1050 @@
// tinyformat.h
// Copyright (C) 2011, Chris Foster [chris42f (at) gmail (d0t) com]
//
// Boost Software License - Version 1.0
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//------------------------------------------------------------------------------
// Tinyformat: A minimal type safe printf replacement
//
// tinyformat.h is a type safe printf replacement library in a single C++
// header file. Design goals include:
//
// * Type safety and extensibility for user defined types.
// * C99 printf() compatibility, to the extent possible using std::ostream
// * Simplicity and minimalism. A single header file to include and distribute
// with your projects.
// * Augment rather than replace the standard stream formatting mechanism
// * C++98 support, with optional C++11 niceties
//
//
// Main interface example usage
// ----------------------------
//
// To print a date to std::cout:
//
// std::string weekday = "Wednesday";
// const char* month = "July";
// size_t day = 27;
// long hour = 14;
// int min = 44;
//
// tfm::printf("%s, %s %d, %.2d:%.2d\n", weekday, month, day, hour, min);
//
// The strange types here emphasize the type safety of the interface; it is
// possible to print a std::string using the "%s" conversion, and a
// size_t using the "%d" conversion. A similar result could be achieved
// using either of the tfm::format() functions. One prints on a user provided
// stream:
//
// tfm::format(std::cerr, "%s, %s %d, %.2d:%.2d\n",
// weekday, month, day, hour, min);
//
// The other returns a std::string:
//
// std::string date = tfm::format("%s, %s %d, %.2d:%.2d\n",
// weekday, month, day, hour, min);
// std::cout << date;
//
// These are the three primary interface functions. There is also a
// convenience function printfln() which appends a newline to the usual result
// of printf() for super simple logging.
//
//
// User defined format functions
// -----------------------------
//
// Simulating variadic templates in C++98 is pretty painful since it requires
// writing out the same function for each desired number of arguments. To make
// this bearable tinyformat comes with a set of macros which are used
// internally to generate the API, but which may also be used in user code.
//
// The three macros TINYFORMAT_ARGTYPES(n), TINYFORMAT_VARARGS(n) and
// TINYFORMAT_PASSARGS(n) will generate a list of n argument types,
// type/name pairs and argument names respectively when called with an integer
// n between 1 and 16. We can use these to define a macro which generates the
// desired user defined function with n arguments. To generate all 16 user
// defined function bodies, use the macro TINYFORMAT_FOREACH_ARGNUM. For an
// example, see the implementation of printf() at the end of the source file.
//
// Sometimes it's useful to be able to pass a list of format arguments through
// to a non-template function. The FormatList class is provided as a way to do
// this by storing the argument list in a type-opaque way. Continuing the
// example from above, we construct a FormatList using makeFormatList():
//
// FormatListRef formatList = tfm::makeFormatList(weekday, month, day, hour, min);
//
// The format list can now be passed into any non-template function and used
// via a call to the vformat() function:
//
// tfm::vformat(std::cout, "%s, %s %d, %.2d:%.2d\n", formatList);
//
//
// Additional API information
// --------------------------
//
// Error handling: Define TINYFORMAT_ERROR to customize the error handling for
// format strings which are unsupported or have the wrong number of format
// specifiers (calls assert() by default).
//
// User defined types: Uses operator<< for user defined types by default.
// Overload formatValue() for more control.
#ifndef TINYFORMAT_H_INCLUDED
#define TINYFORMAT_H_INCLUDED
namespace tinyformat {}
//------------------------------------------------------------------------------
// Config section. Customize to your liking!
// Namespace alias to encourage brevity
namespace tfm = tinyformat;
// Error handling; calls assert() by default.
// #define TINYFORMAT_ERROR(reasonString) your_error_handler(reasonString)
// Define for C++11 variadic templates which make the code shorter & more
// general. If you don't define this, C++11 support is autodetected below.
// #define TINYFORMAT_USE_VARIADIC_TEMPLATES
//------------------------------------------------------------------------------
// Implementation details.
#include <algorithm>
#include <cassert>
#include <iostream>
#include <sstream>
#ifndef TINYFORMAT_ERROR
# define TINYFORMAT_ERROR(reason) assert(0 && reason)
#endif
#if !defined(TINYFORMAT_USE_VARIADIC_TEMPLATES) && !defined(TINYFORMAT_NO_VARIADIC_TEMPLATES)
# ifdef __GXX_EXPERIMENTAL_CXX0X__
# define TINYFORMAT_USE_VARIADIC_TEMPLATES
# endif
#endif
#if defined(__GLIBCXX__) && __GLIBCXX__ < 20080201
// std::showpos is broken on old libstdc++ as provided with OSX. See
// http://gcc.gnu.org/ml/libstdc++/2007-11/msg00075.html
# define TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND
#endif
#ifdef __APPLE__
// Workaround OSX linker warning: xcode uses different default symbol
// visibilities for static libs vs executables (see issue #25)
# define TINYFORMAT_HIDDEN __attribute__((visibility("hidden")))
#else
# define TINYFORMAT_HIDDEN
#endif
namespace tinyformat {
+// Set the locale for tinyformat which is mainly used to format float numbers.
+void setLocale(const char* std_name);
+
+// Internal function.
+void imbue(std::ostream& o);
+
//------------------------------------------------------------------------------
namespace detail {
// Test whether type T1 is convertible to type T2
template <typename T1, typename T2>
struct is_convertible
{
private:
// two types of different size
struct fail { char dummy[2]; };
struct succeed { char dummy; };
// Try to convert a T1 to a T2 by plugging into tryConvert
static fail tryConvert(...);
static succeed tryConvert(const T2&);
static const T1& makeT1();
public:
# ifdef _MSC_VER
// Disable spurious loss of precision warnings in tryConvert(makeT1())
# pragma warning(push)
# pragma warning(disable:4244)
# pragma warning(disable:4267)
# endif
// Standard trick: the (...) version of tryConvert will be chosen from
// the overload set only if the version taking a T2 doesn't match.
// Then we compare the sizes of the return types to check which
// function matched. Very neat, in a disgusting kind of way :)
static const bool value =
sizeof(tryConvert(makeT1())) == sizeof(succeed);
# ifdef _MSC_VER
# pragma warning(pop)
# endif
};
// Detect when a type is not a wchar_t string
template<typename T> struct is_wchar { typedef int tinyformat_wchar_is_not_supported; };
template<> struct is_wchar<wchar_t*> {};
template<> struct is_wchar<const wchar_t*> {};
template<int n> struct is_wchar<const wchar_t[n]> {};
template<int n> struct is_wchar<wchar_t[n]> {};
// Format the value by casting to type fmtT. This default implementation
// should never be called.
template<typename T, typename fmtT, bool convertible = is_convertible<T, fmtT>::value>
struct formatValueAsType
{
static void invoke(std::ostream& /*out*/, const T& /*value*/) { assert(0); }
};
// Specialized version for types that can actually be converted to fmtT, as
// indicated by the "convertible" template parameter.
template<typename T, typename fmtT>
struct formatValueAsType<T,fmtT,true>
{
static void invoke(std::ostream& out, const T& value)
{ out << static_cast<fmtT>(value); }
};
#ifdef TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND
template<typename T, bool convertible = is_convertible<T, int>::value>
struct formatZeroIntegerWorkaround
{
static bool invoke(std::ostream& /**/, const T& /**/) { return false; }
};
template<typename T>
struct formatZeroIntegerWorkaround<T,true>
{
static bool invoke(std::ostream& out, const T& value)
{
if (static_cast<int>(value) == 0 && out.flags() & std::ios::showpos)
{
out << "+0";
return true;
}
return false;
}
};
#endif // TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND
// Convert an arbitrary type to integer. The version with convertible=false
// throws an error.
template<typename T, bool convertible = is_convertible<T,int>::value>
struct convertToInt
{
static int invoke(const T& /*value*/)
{
TINYFORMAT_ERROR("tinyformat: Cannot convert from argument type to "
"integer for use as variable width or precision");
return 0;
}
};
// Specialization for convertToInt when conversion is possible
template<typename T>
struct convertToInt<T,true>
{
static int invoke(const T& value) { return static_cast<int>(value); }
};
// Format at most ntrunc characters to the given stream.
template<typename T>
inline void formatTruncated(std::ostream& out, const T& value, int ntrunc)
{
std::ostringstream tmp;
+ tfm::imbue(tmp);
tmp << value;
std::string result = tmp.str();
out.write(result.c_str(), (std::min)(ntrunc, static_cast<int>(result.size())));
}
#define TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(type) \
inline void formatTruncated(std::ostream& out, type* value, int ntrunc) \
{ \
std::streamsize len = 0; \
while(len < ntrunc && value[len] != 0) \
++len; \
out.write(value, len); \
}
// Overload for const char* and char*. Could overload for signed & unsigned
// char too, but these are technically unneeded for printf compatibility.
TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(const char)
TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(char)
#undef TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR
} // namespace detail
//------------------------------------------------------------------------------
// Variable formatting functions. May be overridden for user-defined types if
// desired.
/// Format a value into a stream, delegating to operator<< by default.
///
/// Users may override this for their own types. When this function is called,
/// the stream flags will have been modified according to the format string.
/// The format specification is provided in the range [fmtBegin, fmtEnd). For
/// truncating conversions, ntrunc is set to the desired maximum number of
/// characters, for example "%.7s" calls formatValue with ntrunc = 7.
///
/// By default, formatValue() uses the usual stream insertion operator
/// operator<< to format the type T, with special cases for the %c and %p
/// conversions.
template<typename T>
inline void formatValue(std::ostream& out, const char* /*fmtBegin*/,
const char* fmtEnd, int ntrunc, const T& value)
{
#ifndef TINYFORMAT_ALLOW_WCHAR_STRINGS
// Since we don't support printing of wchar_t using "%ls", make it fail at
// compile time in preference to printing as a void* at runtime.
typedef typename detail::is_wchar<T>::tinyformat_wchar_is_not_supported DummyType;
(void) DummyType(); // avoid unused type warning with gcc-4.8
#endif
// The mess here is to support the %c and %p conversions: if these
// conversions are active we try to convert the type to a char or const
// void* respectively and format that instead of the value itself. For the
// %p conversion it's important to avoid dereferencing the pointer, which
// could otherwise lead to a crash when printing a dangling (const char*).
const bool canConvertToChar = detail::is_convertible<T,char>::value;
const bool canConvertToVoidPtr = detail::is_convertible<T, const void*>::value;
if(canConvertToChar && *(fmtEnd-1) == 'c')
detail::formatValueAsType<T, char>::invoke(out, value);
else if(canConvertToVoidPtr && *(fmtEnd-1) == 'p')
detail::formatValueAsType<T, const void*>::invoke(out, value);
#ifdef TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND
else if(detail::formatZeroIntegerWorkaround<T>::invoke(out, value)) /**/;
#endif
else if(ntrunc >= 0)
{
// Take care not to overread C strings in truncating conversions like
// "%.4s" where at most 4 characters may be read.
detail::formatTruncated(out, value, ntrunc);
}
else
out << value;
}
// Overloaded version for char types to support printing as an integer
#define TINYFORMAT_DEFINE_FORMATVALUE_CHAR(charType) \
inline void formatValue(std::ostream& out, const char* /*fmtBegin*/, \
const char* fmtEnd, int /**/, charType value) \
{ \
switch(*(fmtEnd-1)) \
{ \
case 'u': case 'd': case 'i': case 'o': case 'X': case 'x': \
out << static_cast<int>(value); break; \
default: \
out << value; break; \
} \
}
// per 3.9.1: char, signed char and unsigned char are all distinct types
TINYFORMAT_DEFINE_FORMATVALUE_CHAR(char)
TINYFORMAT_DEFINE_FORMATVALUE_CHAR(signed char)
TINYFORMAT_DEFINE_FORMATVALUE_CHAR(unsigned char)
#undef TINYFORMAT_DEFINE_FORMATVALUE_CHAR
//------------------------------------------------------------------------------
// Tools for emulating variadic templates in C++98. The basic idea here is
// stolen from the boost preprocessor metaprogramming library and cut down to
// be just general enough for what we need.
#define TINYFORMAT_ARGTYPES(n) TINYFORMAT_ARGTYPES_ ## n
#define TINYFORMAT_VARARGS(n) TINYFORMAT_VARARGS_ ## n
#define TINYFORMAT_PASSARGS(n) TINYFORMAT_PASSARGS_ ## n
#define TINYFORMAT_PASSARGS_TAIL(n) TINYFORMAT_PASSARGS_TAIL_ ## n
// To keep it as transparent as possible, the macros below have been generated
// using python via the excellent cog.py code generation script. This avoids
// the need for a bunch of complex (but more general) preprocessor tricks as
// used in boost.preprocessor.
//
// To rerun the code generation in place, use `cog.py -r tinyformat.h`
// (see http://nedbatchelder.com/code/cog). Alternatively you can just create
// extra versions by hand.
/*[[[cog
maxParams = 16
def makeCommaSepLists(lineTemplate, elemTemplate, startInd=1):
for j in range(startInd,maxParams+1):
list = ', '.join([elemTemplate % {'i':i} for i in range(startInd,j+1)])
cog.outl(lineTemplate % {'j':j, 'list':list})
makeCommaSepLists('#define TINYFORMAT_ARGTYPES_%(j)d %(list)s',
'class T%(i)d')
cog.outl()
makeCommaSepLists('#define TINYFORMAT_VARARGS_%(j)d %(list)s',
'const T%(i)d& v%(i)d')
cog.outl()
makeCommaSepLists('#define TINYFORMAT_PASSARGS_%(j)d %(list)s', 'v%(i)d')
cog.outl()
cog.outl('#define TINYFORMAT_PASSARGS_TAIL_1')
makeCommaSepLists('#define TINYFORMAT_PASSARGS_TAIL_%(j)d , %(list)s',
'v%(i)d', startInd = 2)
cog.outl()
cog.outl('#define TINYFORMAT_FOREACH_ARGNUM(m) \\\n ' +
' '.join(['m(%d)' % (j,) for j in range(1,maxParams+1)]))
]]]*/
#define TINYFORMAT_ARGTYPES_1 class T1
#define TINYFORMAT_ARGTYPES_2 class T1, class T2
#define TINYFORMAT_ARGTYPES_3 class T1, class T2, class T3
#define TINYFORMAT_ARGTYPES_4 class T1, class T2, class T3, class T4
#define TINYFORMAT_ARGTYPES_5 class T1, class T2, class T3, class T4, class T5
#define TINYFORMAT_ARGTYPES_6 class T1, class T2, class T3, class T4, class T5, class T6
#define TINYFORMAT_ARGTYPES_7 class T1, class T2, class T3, class T4, class T5, class T6, class T7
#define TINYFORMAT_ARGTYPES_8 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8
#define TINYFORMAT_ARGTYPES_9 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9
#define TINYFORMAT_ARGTYPES_10 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10
#define TINYFORMAT_ARGTYPES_11 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11
#define TINYFORMAT_ARGTYPES_12 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12
#define TINYFORMAT_ARGTYPES_13 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13
#define TINYFORMAT_ARGTYPES_14 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14
#define TINYFORMAT_ARGTYPES_15 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15
#define TINYFORMAT_ARGTYPES_16 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16
#define TINYFORMAT_VARARGS_1 const T1& v1
#define TINYFORMAT_VARARGS_2 const T1& v1, const T2& v2
#define TINYFORMAT_VARARGS_3 const T1& v1, const T2& v2, const T3& v3
#define TINYFORMAT_VARARGS_4 const T1& v1, const T2& v2, const T3& v3, const T4& v4
#define TINYFORMAT_VARARGS_5 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5
#define TINYFORMAT_VARARGS_6 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6
#define TINYFORMAT_VARARGS_7 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7
#define TINYFORMAT_VARARGS_8 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8
#define TINYFORMAT_VARARGS_9 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9
#define TINYFORMAT_VARARGS_10 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10
#define TINYFORMAT_VARARGS_11 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11
#define TINYFORMAT_VARARGS_12 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12
#define TINYFORMAT_VARARGS_13 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12, const T13& v13
#define TINYFORMAT_VARARGS_14 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12, const T13& v13, const T14& v14
#define TINYFORMAT_VARARGS_15 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12, const T13& v13, const T14& v14, const T15& v15
#define TINYFORMAT_VARARGS_16 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12, const T13& v13, const T14& v14, const T15& v15, const T16& v16
#define TINYFORMAT_PASSARGS_1 v1
#define TINYFORMAT_PASSARGS_2 v1, v2
#define TINYFORMAT_PASSARGS_3 v1, v2, v3
#define TINYFORMAT_PASSARGS_4 v1, v2, v3, v4
#define TINYFORMAT_PASSARGS_5 v1, v2, v3, v4, v5
#define TINYFORMAT_PASSARGS_6 v1, v2, v3, v4, v5, v6
#define TINYFORMAT_PASSARGS_7 v1, v2, v3, v4, v5, v6, v7
#define TINYFORMAT_PASSARGS_8 v1, v2, v3, v4, v5, v6, v7, v8
#define TINYFORMAT_PASSARGS_9 v1, v2, v3, v4, v5, v6, v7, v8, v9
#define TINYFORMAT_PASSARGS_10 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10
#define TINYFORMAT_PASSARGS_11 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11
#define TINYFORMAT_PASSARGS_12 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12
#define TINYFORMAT_PASSARGS_13 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13
#define TINYFORMAT_PASSARGS_14 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14
#define TINYFORMAT_PASSARGS_15 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15
#define TINYFORMAT_PASSARGS_16 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16
#define TINYFORMAT_PASSARGS_TAIL_1
#define TINYFORMAT_PASSARGS_TAIL_2 , v2
#define TINYFORMAT_PASSARGS_TAIL_3 , v2, v3
#define TINYFORMAT_PASSARGS_TAIL_4 , v2, v3, v4
#define TINYFORMAT_PASSARGS_TAIL_5 , v2, v3, v4, v5
#define TINYFORMAT_PASSARGS_TAIL_6 , v2, v3, v4, v5, v6
#define TINYFORMAT_PASSARGS_TAIL_7 , v2, v3, v4, v5, v6, v7
#define TINYFORMAT_PASSARGS_TAIL_8 , v2, v3, v4, v5, v6, v7, v8
#define TINYFORMAT_PASSARGS_TAIL_9 , v2, v3, v4, v5, v6, v7, v8, v9
#define TINYFORMAT_PASSARGS_TAIL_10 , v2, v3, v4, v5, v6, v7, v8, v9, v10
#define TINYFORMAT_PASSARGS_TAIL_11 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11
#define TINYFORMAT_PASSARGS_TAIL_12 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12
#define TINYFORMAT_PASSARGS_TAIL_13 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13
#define TINYFORMAT_PASSARGS_TAIL_14 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14
#define TINYFORMAT_PASSARGS_TAIL_15 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15
#define TINYFORMAT_PASSARGS_TAIL_16 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16
#define TINYFORMAT_FOREACH_ARGNUM(m) \
m(1) m(2) m(3) m(4) m(5) m(6) m(7) m(8) m(9) m(10) m(11) m(12) m(13) m(14) m(15) m(16)
//[[[end]]]
namespace detail {
// Type-opaque holder for an argument to format(), with associated actions on
// the type held as explicit function pointers. This allows FormatArg's for
// each argument to be allocated as a homogenous array inside FormatList
// whereas a naive implementation based on inheritance does not.
class FormatArg
{
public:
FormatArg() {}
template<typename T>
FormatArg(const T& value)
: m_value(static_cast<const void*>(&value)),
m_formatImpl(&formatImpl<T>),
m_toIntImpl(&toIntImpl<T>)
{ }
void format(std::ostream& out, const char* fmtBegin,
const char* fmtEnd, int ntrunc) const
{
m_formatImpl(out, fmtBegin, fmtEnd, ntrunc, m_value);
}
int toInt() const
{
return m_toIntImpl(m_value);
}
private:
template<typename T>
TINYFORMAT_HIDDEN static void formatImpl(std::ostream& out, const char* fmtBegin,
const char* fmtEnd, int ntrunc, const void* value)
{
formatValue(out, fmtBegin, fmtEnd, ntrunc, *static_cast<const T*>(value));
}
template<typename T>
TINYFORMAT_HIDDEN static int toIntImpl(const void* value)
{
return convertToInt<T>::invoke(*static_cast<const T*>(value));
}
const void* m_value;
void (*m_formatImpl)(std::ostream& out, const char* fmtBegin,
const char* fmtEnd, int ntrunc, const void* value);
int (*m_toIntImpl)(const void* value);
};
// Parse and return an integer from the string c, as atoi()
// On return, c is set to one past the end of the integer.
inline int parseIntAndAdvance(const char*& c)
{
int i = 0;
for(;*c >= '0' && *c <= '9'; ++c)
i = 10*i + (*c - '0');
return i;
}
// Print literal part of format string and return next format spec
// position.
//
// Skips over any occurrences of '%%', printing a literal '%' to the
// output. The position of the first % character of the next
// nontrivial format spec is returned, or the end of string.
inline const char* printFormatStringLiteral(std::ostream& out, const char* fmt)
{
const char* c = fmt;
for(;; ++c)
{
switch(*c)
{
case '\0':
out.write(fmt, c - fmt);
return c;
case '%':
out.write(fmt, c - fmt);
if(*(c+1) != '%')
return c;
// for "%%", tack trailing % onto next literal section.
fmt = ++c;
break;
default:
break;
}
}
}
// Parse a format string and set the stream state accordingly.
//
// The format mini-language recognized here is meant to be the one from C99,
// with the form "%[flags][width][.precision][length]type".
//
// Formatting options which can't be natively represented using the ostream
// state are returned in spacePadPositive (for space padded positive numbers)
// and ntrunc (for truncating conversions). argIndex is incremented if
// necessary to pull out variable width and precision . The function returns a
// pointer to the character after the end of the current format spec.
inline const char* streamStateFromFormat(std::ostream& out, bool& spacePadPositive,
int& ntrunc, const char* fmtStart,
const detail::FormatArg* formatters,
int& argIndex, int numFormatters)
{
if(*fmtStart != '%')
{
TINYFORMAT_ERROR("tinyformat: Not enough conversion specifiers in format string");
return fmtStart;
}
// Reset stream state to defaults.
out.width(0);
out.precision(6);
out.fill(' ');
// Reset most flags; ignore irrelevant unitbuf & skipws.
out.unsetf(std::ios::adjustfield | std::ios::basefield |
std::ios::floatfield | std::ios::showbase | std::ios::boolalpha |
std::ios::showpoint | std::ios::showpos | std::ios::uppercase);
bool precisionSet = false;
bool widthSet = false;
int widthExtra = 0;
const char* c = fmtStart + 1;
// 1) Parse flags
for(;; ++c)
{
switch(*c)
{
case '#':
out.setf(std::ios::showpoint | std::ios::showbase);
continue;
case '0':
// overridden by left alignment ('-' flag)
if(!(out.flags() & std::ios::left))
{
// Use internal padding so that numeric values are
// formatted correctly, eg -00010 rather than 000-10
out.fill('0');
out.setf(std::ios::internal, std::ios::adjustfield);
}
continue;
case '-':
out.fill(' ');
out.setf(std::ios::left, std::ios::adjustfield);
continue;
case ' ':
// overridden by show positive sign, '+' flag.
if(!(out.flags() & std::ios::showpos))
spacePadPositive = true;
continue;
case '+':
out.setf(std::ios::showpos);
spacePadPositive = false;
widthExtra = 1;
continue;
default:
break;
}
break;
}
// 2) Parse width
if(*c >= '0' && *c <= '9')
{
widthSet = true;
out.width(parseIntAndAdvance(c));
}
if(*c == '*')
{
widthSet = true;
int width = 0;
if(argIndex < numFormatters)
width = formatters[argIndex++].toInt();
else
TINYFORMAT_ERROR("tinyformat: Not enough arguments to read variable width");
if(width < 0)
{
// negative widths correspond to '-' flag set
out.fill(' ');
out.setf(std::ios::left, std::ios::adjustfield);
width = -width;
}
out.width(width);
++c;
}
// 3) Parse precision
if(*c == '.')
{
++c;
int precision = 0;
if(*c == '*')
{
++c;
if(argIndex < numFormatters)
precision = formatters[argIndex++].toInt();
else
TINYFORMAT_ERROR("tinyformat: Not enough arguments to read variable precision");
}
else
{
if(*c >= '0' && *c <= '9')
precision = parseIntAndAdvance(c);
else if(*c == '-') // negative precisions ignored, treated as zero.
parseIntAndAdvance(++c);
}
out.precision(precision);
precisionSet = true;
}
// 4) Ignore any C99 length modifier
while(*c == 'l' || *c == 'h' || *c == 'L' ||
*c == 'j' || *c == 'z' || *c == 't')
++c;
// 5) We're up to the conversion specifier character.
// Set stream flags based on conversion specifier (thanks to the
// boost::format class for forging the way here).
bool intConversion = false;
switch(*c)
{
case 'u': case 'd': case 'i':
out.setf(std::ios::dec, std::ios::basefield);
intConversion = true;
break;
case 'o':
out.setf(std::ios::oct, std::ios::basefield);
intConversion = true;
break;
case 'X':
out.setf(std::ios::uppercase);
case 'x': case 'p':
out.setf(std::ios::hex, std::ios::basefield);
intConversion = true;
break;
case 'E':
out.setf(std::ios::uppercase);
case 'e':
out.setf(std::ios::scientific, std::ios::floatfield);
out.setf(std::ios::dec, std::ios::basefield);
break;
case 'F':
out.setf(std::ios::uppercase);
case 'f':
out.setf(std::ios::fixed, std::ios::floatfield);
break;
case 'G':
out.setf(std::ios::uppercase);
case 'g':
out.setf(std::ios::dec, std::ios::basefield);
// As in boost::format, let stream decide float format.
out.flags(out.flags() & ~std::ios::floatfield);
break;
case 'a': case 'A':
TINYFORMAT_ERROR("tinyformat: the %a and %A conversion specs "
"are not supported");
break;
case 'c':
// Handled as special case inside formatValue()
break;
case 's':
if(precisionSet)
ntrunc = static_cast<int>(out.precision());
// Make %s print booleans as "true" and "false"
out.setf(std::ios::boolalpha);
break;
case 'n':
// Not supported - will cause problems!
TINYFORMAT_ERROR("tinyformat: %n conversion spec not supported");
break;
case '\0':
TINYFORMAT_ERROR("tinyformat: Conversion spec incorrectly "
"terminated by end of string");
return c;
default:
break;
}
if(intConversion && precisionSet && !widthSet)
{
// "precision" for integers gives the minimum number of digits (to be
// padded with zeros on the left). This isn't really supported by the
// iostreams, but we can approximately simulate it with the width if
// the width isn't otherwise used.
out.width(out.precision() + widthExtra);
out.setf(std::ios::internal, std::ios::adjustfield);
out.fill('0');
}
return c+1;
}
//------------------------------------------------------------------------------
inline void formatImpl(std::ostream& out, const char* fmt,
const detail::FormatArg* formatters,
int numFormatters)
{
// Saved stream state
std::streamsize origWidth = out.width();
std::streamsize origPrecision = out.precision();
std::ios::fmtflags origFlags = out.flags();
char origFill = out.fill();
for (int argIndex = 0; argIndex < numFormatters; ++argIndex)
{
// Parse the format string
fmt = printFormatStringLiteral(out, fmt);
bool spacePadPositive = false;
int ntrunc = -1;
const char* fmtEnd = streamStateFromFormat(out, spacePadPositive, ntrunc, fmt,
formatters, argIndex, numFormatters);
if (argIndex >= numFormatters)
{
// Check args remain after reading any variable width/precision
TINYFORMAT_ERROR("tinyformat: Not enough format arguments");
return;
}
const FormatArg& arg = formatters[argIndex];
// Format the arg into the stream.
if(!spacePadPositive)
arg.format(out, fmt, fmtEnd, ntrunc);
else
{
// The following is a special case with no direct correspondence
// between stream formatting and the printf() behaviour. Simulate
// it crudely by formatting into a temporary string stream and
// munging the resulting string.
std::ostringstream tmpStream;
+ tfm::imbue(tmpStream);
tmpStream.copyfmt(out);
tmpStream.setf(std::ios::showpos);
arg.format(tmpStream, fmt, fmtEnd, ntrunc);
std::string result = tmpStream.str(); // allocates... yuck.
for(size_t i = 0, iend = result.size(); i < iend; ++i)
if(result[i] == '+') result[i] = ' ';
out << result;
}
fmt = fmtEnd;
}
// Print remaining part of format string.
fmt = printFormatStringLiteral(out, fmt);
if(*fmt != '\0')
TINYFORMAT_ERROR("tinyformat: Too many conversion specifiers in format string");
// Restore stream state
out.width(origWidth);
out.precision(origPrecision);
out.flags(origFlags);
out.fill(origFill);
}
} // namespace detail
/// List of template arguments format(), held in a type-opaque way.
///
/// A const reference to FormatList (typedef'd as FormatListRef) may be
/// conveniently used to pass arguments to non-template functions: All type
/// information has been stripped from the arguments, leaving just enough of a
/// common interface to perform formatting as required.
class FormatList
{
public:
FormatList(detail::FormatArg* formatters, int N)
: m_formatters(formatters), m_N(N) { }
friend void vformat(std::ostream& out, const char* fmt,
const FormatList& list);
private:
const detail::FormatArg* m_formatters;
int m_N;
};
/// Reference to type-opaque format list for passing to vformat()
typedef const FormatList& FormatListRef;
namespace detail {
// Format list subclass with fixed storage to avoid dynamic allocation
template<int N>
class FormatListN : public FormatList
{
public:
#ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES
template<typename... Args>
FormatListN(const Args&... args)
: FormatList(&m_formatterStore[0], N),
m_formatterStore { FormatArg(args)... }
{ static_assert(sizeof...(args) == N, "Number of args must be N"); }
#else // C++98 version
void init(int) {}
# define TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR(n) \
\
template<TINYFORMAT_ARGTYPES(n)> \
FormatListN(TINYFORMAT_VARARGS(n)) \
: FormatList(&m_formatterStore[0], n) \
{ assert(n == N); init(0, TINYFORMAT_PASSARGS(n)); } \
\
template<TINYFORMAT_ARGTYPES(n)> \
void init(int i, TINYFORMAT_VARARGS(n)) \
{ \
m_formatterStore[i] = FormatArg(v1); \
init(i+1 TINYFORMAT_PASSARGS_TAIL(n)); \
}
TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR)
# undef TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR
#endif
private:
FormatArg m_formatterStore[N];
};
// Special 0-arg version - MSVC says zero-sized C array in struct is nonstandard
template<> class FormatListN<0> : public FormatList
{
public: FormatListN() : FormatList(0, 0) {}
};
} // namespace detail
//------------------------------------------------------------------------------
// Primary API functions
#ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES
/// Make type-agnostic format list from list of template arguments.
///
/// The exact return type of this function is an implementation detail and
/// shouldn't be relied upon. Instead it should be stored as a FormatListRef:
///
/// FormatListRef formatList = makeFormatList( /*...*/ );
template<typename... Args>
detail::FormatListN<sizeof...(Args)> makeFormatList(const Args&... args)
{
return detail::FormatListN<sizeof...(args)>(args...);
}
#else // C++98 version
inline detail::FormatListN<0> makeFormatList()
{
return detail::FormatListN<0>();
}
#define TINYFORMAT_MAKE_MAKEFORMATLIST(n) \
template<TINYFORMAT_ARGTYPES(n)> \
detail::FormatListN<n> makeFormatList(TINYFORMAT_VARARGS(n)) \
{ \
return detail::FormatListN<n>(TINYFORMAT_PASSARGS(n)); \
}
TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_MAKEFORMATLIST)
#undef TINYFORMAT_MAKE_MAKEFORMATLIST
#endif
/// Format list of arguments to the stream according to the given format string.
///
/// The name vformat() is chosen for the semantic similarity to vprintf(): the
/// list of format arguments is held in a single function argument.
inline void vformat(std::ostream& out, const char* fmt, FormatListRef list)
{
detail::formatImpl(out, fmt, list.m_formatters, list.m_N);
}
#ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES
/// Format list of arguments to the stream according to given format string.
template<typename... Args>
void format(std::ostream& out, const char* fmt, const Args&... args)
{
vformat(out, fmt, makeFormatList(args...));
}
/// Format list of arguments according to the given format string and return
/// the result as a string.
template<typename... Args>
std::string format(const char* fmt, const Args&... args)
{
std::ostringstream oss;
+ tfm::imbue(oss);
format(oss, fmt, args...);
return oss.str();
}
/// Format list of arguments to std::cout, according to the given format string
template<typename... Args>
void printf(const char* fmt, const Args&... args)
{
format(std::cout, fmt, args...);
}
template<typename... Args>
void printfln(const char* fmt, const Args&... args)
{
format(std::cout, fmt, args...);
std::cout << '\n';
}
#else // C++98 version
inline void format(std::ostream& out, const char* fmt)
{
vformat(out, fmt, makeFormatList());
}
inline std::string format(const char* fmt)
{
std::ostringstream oss;
+ tfm::imbue(oss);
format(oss, fmt);
return oss.str();
}
inline void printf(const char* fmt)
{
format(std::cout, fmt);
}
inline void printfln(const char* fmt)
{
format(std::cout, fmt);
std::cout << '\n';
}
#define TINYFORMAT_MAKE_FORMAT_FUNCS(n) \
\
template<TINYFORMAT_ARGTYPES(n)> \
void format(std::ostream& out, const char* fmt, TINYFORMAT_VARARGS(n)) \
{ \
vformat(out, fmt, makeFormatList(TINYFORMAT_PASSARGS(n))); \
} \
\
template<TINYFORMAT_ARGTYPES(n)> \
std::string format(const char* fmt, TINYFORMAT_VARARGS(n)) \
{ \
std::ostringstream oss; \
+ tfm::imbue(oss); \
format(oss, fmt, TINYFORMAT_PASSARGS(n)); \
return oss.str(); \
} \
\
template<TINYFORMAT_ARGTYPES(n)> \
void printf(const char* fmt, TINYFORMAT_VARARGS(n)) \
{ \
format(std::cout, fmt, TINYFORMAT_PASSARGS(n)); \
} \
\
template<TINYFORMAT_ARGTYPES(n)> \
void printfln(const char* fmt, TINYFORMAT_VARARGS(n)) \
{ \
format(std::cout, fmt, TINYFORMAT_PASSARGS(n)); \
std::cout << '\n'; \
}
TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_FORMAT_FUNCS)
#undef TINYFORMAT_MAKE_FORMAT_FUNCS
#endif
} // namespace tinyformat
#endif // TINYFORMAT_H_INCLUDED

File Metadata

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

Event Timeline