commit 3affd2d6
Modsupport (#26)
* Started on mod support. Now possible to start the game with "./blockattack --mod 1.3.0.bricks" to use a mod
* Add example mod which also uses a sprite file
* Added a mod file parser for processing modinfo-files
* Now save the modlist and use it again at next startup
* Add "player configuration" menu for the 4 player menu items. Freeing up 4 places in the menu.
* Started to add a mod config menu
* Add title to Mod Config page
* Add exit button to mod menu
* Different physfs approach
* List the availeble mods.
* Now packs the sample mod
* Now sorts the mod list
* update translation files
* astyle fixes
* Also look for user mods.
Changed files
diff --git a/.gitignore b/.gitignore
index 231c609..f3ca6de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@ nbproject
!.dockerignore
*.patch
Game/blockattack*
+Game/mods/1.3.0.bricks.data
cmake_install.cmake
CMake*
-CMakeLists.txt
diff --git a/Game/mods/1.3.0.bricks_unpacked.data/modinfo/1.3.0.bricks.json b/Game/mods/1.3.0.bricks_unpacked.data/modinfo/1.3.0.bricks.json
new file mode 100644
index 0000000..7045190
--- /dev/null
+++ b/Game/mods/1.3.0.bricks_unpacked.data/modinfo/1.3.0.bricks.json
@@ -0,0 +1,5 @@
+{
+ "name" : "1.3.0.bricks",
+ "description" : "Uses the bricks from version 1.3.0",
+ "sprites" : "bricks1.3.0"
+}
diff --git a/Game/mods/1.3.0.bricks_unpacked.data/sprites/bricks1.3.0.sprite b/Game/mods/1.3.0.bricks_unpacked.data/sprites/bricks1.3.0.sprite
new file mode 100644
index 0000000..23d5c16
--- /dev/null
+++ b/Game/mods/1.3.0.bricks_unpacked.data/sprites/bricks1.3.0.sprite
@@ -0,0 +1,73 @@
+{
+
+ "block_blue" : {
+ "texture" : "bricks130",
+ "topx" : 0,
+ "topy" : 0,
+ "height" : 50,
+ "width" : 50,
+ "number_of_frames" : 1,
+ "frame_time" : 1
+ },
+
+ "block_green" : {
+ "texture" : "bricks130",
+ "topx" : 50,
+ "topy" : 0,
+ "height" : 50,
+ "width" : 50,
+ "number_of_frames" : 1,
+ "frame_time" : 1
+ },
+
+ "block_gray" : {
+ "texture" : "bricks130",
+ "topx" : 100,
+ "topy" : 0,
+ "height" : 50,
+ "width" : 50,
+ "number_of_frames" : 1,
+ "frame_time" : 1
+ },
+
+ "block_purple" : {
+ "texture" : "bricks130",
+ "topx" : 150,
+ "topy" : 0,
+ "height" : 50,
+ "width" : 50,
+ "number_of_frames" : 1,
+ "frame_time" : 1
+ },
+
+ "block_red" : {
+ "texture" : "bricks130",
+ "topx" : 200,
+ "topy" : 0,
+ "height" : 50,
+ "width" : 50,
+ "number_of_frames" : 1,
+ "frame_time" : 1
+ },
+
+ "block_turkish" : {
+ "texture" : "bricks130",
+ "topx" : 250,
+ "topy" : 0,
+ "height" : 50,
+ "width" : 50,
+ "number_of_frames" : 1,
+ "frame_time" : 1
+ },
+
+ "block_yellow" : {
+ "texture" : "bricks130",
+ "topx" : 300,
+ "topy" : 0,
+ "height" : 50,
+ "width" : 50,
+ "number_of_frames" : 1,
+ "frame_time" : 1
+ }
+
+}
diff --git a/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_blue.png b/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_blue.png
new file mode 100755
index 0000000..22e30f8
Binary files /dev/null and b/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_blue.png differ
diff --git a/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_gray.png b/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_gray.png
new file mode 100755
index 0000000..89d2ca6
Binary files /dev/null and b/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_gray.png differ
diff --git a/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_green.png b/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_green.png
new file mode 100755
index 0000000..b5dba0c
Binary files /dev/null and b/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_green.png differ
diff --git a/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_purple.png b/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_purple.png
new file mode 100755
index 0000000..7b4d0b1
Binary files /dev/null and b/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_purple.png differ
diff --git a/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_red.png b/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_red.png
new file mode 100755
index 0000000..2729ff6
Binary files /dev/null and b/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_red.png differ
diff --git a/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_turkish.png b/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_turkish.png
new file mode 100755
index 0000000..fd56426
Binary files /dev/null and b/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_turkish.png differ
diff --git a/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_yellow.png b/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_yellow.png
new file mode 100755
index 0000000..6fdaf69
Binary files /dev/null and b/Game/mods/1.3.0.bricks_unpacked.data/textures/ball_yellow.png differ
diff --git a/Game/mods/1.3.0.bricks_unpacked.data/textures/bricks130.png b/Game/mods/1.3.0.bricks_unpacked.data/textures/bricks130.png
new file mode 100644
index 0000000..6e252c9
Binary files /dev/null and b/Game/mods/1.3.0.bricks_unpacked.data/textures/bricks130.png differ
diff --git a/packdata.sh b/packdata.sh
index 2212130..f71c41f 100755
--- a/packdata.sh
+++ b/packdata.sh
@@ -1,11 +1,17 @@
#!/bin/bash
set -e
zip -9jX Game/blockattack.data source/AUTH
-cd Game/data
+pushd Game/data
zip -9X $(find ../blockattack.data * | sort) -x \*svn*
-cd ../../man
+popd
+pushd Game/mods/1.3.0.bricks_unpacked.data/
+zip -9X ../1.3.0.bricks.data $(find ./* | sort)
+popd
+pushd man
gzip -9n -c blockattack.man > blockattack.6.gz
-cd ../source/misc/translation/
+popd
+pushd source/misc/translation/
./build_mo_all.sh
+popd
exit 0
diff --git a/source/code/ModConfigMenuState.cpp b/source/code/ModConfigMenuState.cpp
new file mode 100644
index 0000000..0845360
--- /dev/null
+++ b/source/code/ModConfigMenuState.cpp
@@ -0,0 +1,124 @@
+/*
+===========================================================================
+blockattack - Block Attack - Rise of the Blocks
+Copyright (C) 2005-2020 Poul Sander
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see http://www.gnu.org/licenses/
+
+Source information and contacts persons can be found at
+https://blockattack.net
+===========================================================================
+*/
+
+#include "ModConfigMenuState.hpp"
+#include "MenuSystem.h"
+#include "global.hpp"
+#include "MenuSystem.h"
+#include "sago/SagoMisc.hpp"
+#include "os.hpp"
+#include <boost/algorithm/string/predicate.hpp>
+
+const int buttonOffset = 160;
+extern sago::SagoSprite bExit;
+
+bool sort_mods_enabled_order (const Mod& i,const Mod& j) {
+ if (i.enabled && !j.enabled) {
+ //Enabled mods always goes before disabled ones
+ return true;
+ }
+ if (i.enabled) {
+ return i.order < j.order;
+ }
+ return i.name < j.name;
+}
+
+static void initMods(std::vector<Mod>& mod_list) {
+ for (size_t i=0; i < globalData.modList.size(); ++i) {
+ for (Mod& m : mod_list) {
+ if (m.name == globalData.modList[i]) {
+ m.order = i;
+ m.enabled = true;
+ }
+ }
+ }
+ std::sort(mod_list.begin(), mod_list.end(), sort_mods_enabled_order);
+}
+
+static void appendMods(const std::vector<std::string>& mod_files, const std::string& dir, std::vector<Mod>& mods_available) {
+ for (const std::string& mod : mod_files) {
+ if (!boost::ends_with(mod, ".data")) {
+ continue;
+ }
+ Mod m;
+ m.name = mod.substr(0, mod.length()-5);
+ m.filename = dir + "/" + mod;
+ mods_available.push_back(m);
+ }
+}
+
+
+ModConfigMenuState::ModConfigMenuState() {
+ mods_available.clear();
+ std::string baseMods = std::string(PHYSFS_getBaseDir())+ "/mods";
+ std::vector<std::string> baseModFiles = OsGetDirFileList(baseMods);
+ appendMods(baseModFiles, baseMods, mods_available);
+ std::string userMods = getPathToSaveFiles()+"/mods";
+ std::vector<std::string> userModFiles = OsGetDirFileList(userMods);
+ appendMods(userModFiles, userMods, mods_available);
+ initMods(mods_available);
+}
+
+ModConfigMenuState::~ModConfigMenuState() {}
+
+bool ModConfigMenuState::IsActive() {
+ return isActive;
+}
+
+void ModConfigMenuState::Draw(SDL_Renderer* target) {
+ DrawBackground(target);
+ standardButton.getLabel(_("Mod config"))->Draw(target, 50, 50);
+ for (size_t i = 0; i < mods_available.size(); ++i) {
+ standardButton.getLabel(mods_available[i].name)->Draw(target, 60, 160+30*i);
+ standardButton.getLabel(mods_available[i].enabled ? _("Yes") : _("No"))->Draw(target, 600, 160+30*i);
+ if (mods_available[i].enabled) {
+ standardButton.getLabel(std::to_string(mods_available[i].order))->Draw(target, 700, 160+30*i);
+ }
+ }
+ bExit.Draw(globalData.screen, SDL_GetTicks(), globalData.xsize-buttonOffset, globalData.ysize-buttonOffset);
+}
+
+void ModConfigMenuState::ProcessInput(const SDL_Event& event, bool& processed) {
+ if (isEscapeEvent(event)) {
+ isActive = false;
+ processed = true;
+ }
+}
+
+void ModConfigMenuState::Update() {
+ // If the mouse button is released, make bMouseUp equal true
+ if ( !(SDL_GetMouseState(nullptr, nullptr)&SDL_BUTTON(1)) ) {
+ bMouseUp=true;
+ }
+
+ if (SDL_GetMouseState(nullptr,nullptr)&SDL_BUTTON(1) && bMouseUp) {
+ bMouseUp = false;
+
+ //The Score button:
+ if ((globalData.mousex>globalData.xsize-buttonOffset) && (globalData.mousex<globalData.xsize-buttonOffset+bExit.GetWidth())
+ && (globalData.mousey>globalData.ysize-buttonOffset) && (globalData.mousey<globalData.ysize-buttonOffset+bExit.GetHeight())) {
+ isActive = false;
+ }
+
+ }
+}
diff --git a/source/code/ModConfigMenuState.hpp b/source/code/ModConfigMenuState.hpp
new file mode 100644
index 0000000..175b9b2
--- /dev/null
+++ b/source/code/ModConfigMenuState.hpp
@@ -0,0 +1,61 @@
+/*
+===========================================================================
+blockattack - Block Attack - Rise of the Blocks
+Copyright (C) 2005-2020 Poul Sander
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see http://www.gnu.org/licenses/
+
+Source information and contacts persons can be found at
+https://blockattack.net
+===========================================================================
+*/
+
+#ifndef MODCONFIG_HPP
+#define MODCONFIG_HPP
+
+#include "sago/GameStateInterface.hpp"
+#include "sago/SagoTextField.hpp"
+#include "sago/SagoTextBox.hpp"
+#include "global.hpp"
+#include "common.h"
+
+struct Mod {
+ std::string name;
+ std::string filename;
+ bool enabled = false;
+ int order = 0;
+};
+
+/**
+ * This will one day be the menu to configure mods.
+ * This will need to be able to scan the filesystem for mods
+ */
+class ModConfigMenuState : public sago::GameStateInterface {
+public:
+ ModConfigMenuState();
+ ModConfigMenuState(const ModConfigMenuState& orig) = delete;
+ virtual ~ModConfigMenuState();
+
+ bool IsActive() override;
+ void Draw(SDL_Renderer* target) override;
+ void ProcessInput(const SDL_Event& event, bool &processed) override;
+ void Update() override;
+
+private:
+ bool isActive = true;
+ bool bMouseUp = true;
+ std::vector<Mod> mods_available;
+};
+
+#endif /* MODCONFIG_HPP */
diff --git a/source/code/ModInfo.cpp b/source/code/ModInfo.cpp
new file mode 100644
index 0000000..da0b3f1
--- /dev/null
+++ b/source/code/ModInfo.cpp
@@ -0,0 +1,60 @@
+/*
+===========================================================================
+blockattack - Block Attack - Rise of the Blocks
+Copyright (C) 2005-2020 Poul Sander
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see http://www.gnu.org/licenses/
+
+Source information and contacts persons can be found at
+http://blockattack.net
+===========================================================================
+*/
+
+#include "ModInfo.hpp"
+#include "sago/SagoMisc.hpp"
+#include "rapidjson/document.h"
+#include <iostream>
+#include <boost/algorithm/string.hpp>
+
+void ModInfo::InitModList(const std::vector<std::string>& modlist) {
+ this->mod_list = modlist;
+ sprite_filename_list.clear();
+ for (const std::string& s : mod_list) {
+ std::string filename_info = std::string("modinfo/") + s + ".json";
+ if (sago::FileExists(filename_info.c_str())) {
+ std::string content = sago::GetFileContent(filename_info.c_str());
+ rapidjson::Document document;
+ document.Parse(content.c_str());
+ if ( !document.IsObject() ) {
+ std::cerr << "Failed to parse: " << filename_info << "\n";
+ continue;
+ }
+ const auto& t = document.GetObject().FindMember("sprites");
+ if (t != document.MemberEnd() && t->value.IsString()) {
+ std::string sprites = t->value.GetString();
+ std::vector<std::string> sprites_vector;
+ boost::split(sprites_vector, sprites, boost::is_any_of(","));
+ sprite_filename_list.insert(sprite_filename_list.end(), sprites_vector.begin(), sprites_vector.end());
+ }
+ }
+ }
+}
+
+const std::vector<std::string>& ModInfo::getModList() {
+ return this->mod_list;
+}
+
+const std::vector<std::string>& ModInfo::getModSpriteFiles() {
+ return this->sprite_filename_list;
+}
diff --git a/source/code/ModInfo.hpp b/source/code/ModInfo.hpp
new file mode 100644
index 0000000..0a94001
--- /dev/null
+++ b/source/code/ModInfo.hpp
@@ -0,0 +1,42 @@
+/*
+===========================================================================
+blockattack - Block Attack - Rise of the Blocks
+Copyright (C) 2005-2020 Poul Sander
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see http://www.gnu.org/licenses/
+
+Source information and contacts persons can be found at
+http://blockattack.net
+===========================================================================
+*/
+
+
+#ifndef MODINFO_HPP
+#define MODINFO_HPP
+
+#include <string>
+#include <vector>
+
+class ModInfo {
+public:
+ ModInfo() = default;
+ void InitModList(const std::vector<std::string>& modlist);
+ const std::vector<std::string>& getModList();
+ const std::vector<std::string>& getModSpriteFiles();
+private:
+ std::vector<std::string> mod_list;
+ std::vector<std::string> sprite_filename_list;
+};
+
+#endif
\ No newline at end of file
diff --git a/source/code/global.hpp b/source/code/global.hpp
index 351cbfb..9a583ae 100644
--- a/source/code/global.hpp
+++ b/source/code/global.hpp
@@ -31,6 +31,7 @@ http://www.blockattack.net
#include "TextManager.hpp"
#include "ExplosionManager.hpp"
#include "sago/SagoTextField.hpp"
+#include "ModInfo.hpp"
void MainMenu();
void SafeModeMenu();
@@ -75,6 +76,8 @@ struct GlobalData {
bool alwaysSixteenNine = false;
int verboseLevel = 0;
std::unique_ptr<sago::SagoSpriteHolder> spriteHolder;
+ std::vector<std::string> modList;
+ ModInfo modinfo;
TextManager theTextManager;
diff --git a/source/code/main.cpp b/source/code/main.cpp
index 83dc0ba..caf8c18 100644
--- a/source/code/main.cpp
+++ b/source/code/main.cpp
@@ -64,6 +64,8 @@ https://blockattack.net
#define PACKAGE "blockattack_roftb"
#endif
+#define MODLIST_TXT "modList.txt"
+
#include "highscore.h" //Stores highscores
#include "ReadKeyboard.h" //Reads text from keyboard
#include "stats.h" //Saves general stats
@@ -71,6 +73,7 @@ https://blockattack.net
#include "common.h"
#include "gamecontroller.h"
+#include <boost/algorithm/string.hpp>
#include <boost/program_options.hpp>
#include <fstream>
#include "levelselect.hpp"
@@ -90,6 +93,15 @@ static void FsSearchParthMainAppend(std::vector<std::string>& paths) {
paths.push_back((std::string)PHYSFS_getBaseDir()+"/data");
}
+static void FsSearchPathModAppend(std::vector<std::string>& paths, const std::vector<std::string>& modlist) {
+ for (const std::string& mod : modlist) {
+ std::string filename = std::string(SHAREDIR)+"/mods/"+mod+".data";
+ paths.push_back(filename);
+ filename = std::string(PHYSFS_getBaseDir())+"/mods/"+mod+".data";
+ paths.push_back(filename);
+ }
+}
+
static void PhysFsSetSearchPath(const std::vector<std::string>& paths, const std::string& savepath) {
for (const std::string& path : paths) {
PHYSFS_mount(path.c_str(), "/", 0);
@@ -264,6 +276,7 @@ void ResetFullscreen() {
SDL_RenderSetLogicalSize(globalData.screen, globalData.xsize, globalData.ysize);
dataHolder.invalidateAll(globalData.screen);
globalData.spriteHolder.reset(new sago::SagoSpriteHolder( dataHolder ) );
+ globalData.spriteHolder->ReadSprites(globalData.modinfo.getModSpriteFiles());
InitImages(*(globalData.spriteHolder.get()) );
SDL_ShowCursor(SDL_DISABLE);
}
@@ -866,6 +879,7 @@ static void ParseArguments(int argc, char* argv[], globalConfig& conf) {
"Default: \"%s\"", LOCALEDIR).c_str())
("homepath", boost::program_options::value<std::string>(), SPrintStringF("Set the home folder where settings are saved. The directory will be created if it does not exist."
" Default: \"%s\"", getPathToSaveFiles().c_str()).c_str())
+ ("mod,m", boost::program_options::value<std::vector<std::string> >(), "Loads a mod. Later mods have preference")
;
boost::program_options::variables_map vm;
@@ -957,9 +971,17 @@ static void ParseArguments(int argc, char* argv[], globalConfig& conf) {
if (vm.count("play-replay")) {
globalData.replayArgument = vm["play-replay"].as<std::string>();
}
+ if (vm.count("mod")) {
+ globalData.modList = vm["mod"].as<std::vector<std::string> >();
+ }
}
+//Physfs 2.0.z does not have PHYSFS_unmount
+#if (PHYSFS_VER_MAJOR <= 2) && (PHYSFS_VER_MINOR < 1)
+#define PHYSFS_unmount PHYSFS_removeFromSearchPath
+#endif
+
//Warning: the arguments to main must be "int argc, char* argv[]" NO CONST! or SDL_main will fail to find it
int main(int argc, char* argv[]) {
try {
@@ -979,6 +1001,16 @@ int main(int argc, char* argv[]) {
ParseArguments(argc, argv, config);
OsCreateSaveFolder();
PhysFsSetSearchPath(config.search_paths, config.savepath);
+ if (globalData.modList.empty() && sago::FileExists(MODLIST_TXT)) {
+ std::string modString = sago::GetFileContent(MODLIST_TXT);
+ boost::split(globalData.modList, modString, boost::is_any_of(","));
+ }
+ if (globalData.modList.size()>0) {
+ PHYSFS_unmount(config.savepath.c_str());
+ FsSearchPathModAppend(config.search_paths, globalData.modList);
+ globalData.modinfo.InitModList(globalData.modList);
+ PhysFsSetSearchPath(config.search_paths, config.savepath);
+ }
//Os create folders must be after the parameters because they can change the home folder
PhysFsCreateFolders();
bool gameShutdownProperly = true;
@@ -1245,11 +1277,19 @@ int main(int argc, char* argv[]) {
std::cout << SPrintStringF("Block Attack - Rise of the Blocks ran for: %i hours %i mins and %i secs", ct.hours, ct.minutes, ct.seconds) << "\n";
}
+ std::string modListString;
+ if (globalData.modList.size()>0) {
+ modListString = globalData.modList.at(0);
+ for (size_t i = 1; i < globalData.modList.size(); ++i) {
+ modListString += std::string(",")+globalData.modList[i];
+ }
+ }
ct = TimeHandler::addTime("totalTime",ct);
if (globalData.verboseLevel) {
std::cout << "Total run time is now: " << ct.days << " days " << ct.hours << " hours " << ct.minutes << " mins and " << ct.seconds << " secs" << "\n";
+ std::cout << "Mods loaded: " << modListString << "\n";
}
-
+ sago::WriteFileContent(MODLIST_TXT, modListString);
Stats::getInstance()->save();
Config::getInstance()->save();
}
diff --git a/source/code/menudef.cpp b/source/code/menudef.cpp
index 293ab75..04a61f2 100644
--- a/source/code/menudef.cpp
+++ b/source/code/menudef.cpp
@@ -29,6 +29,7 @@ http://blockattack.net
#include "HelpGamepadState.hpp"
#include "HelpAboutState.hpp"
#include "ShowFileState.hpp"
+#include "ModConfigMenuState.hpp"
#if 0
@@ -293,34 +294,52 @@ static void ChangeKeysMenu2() {
ChangeKeysMenu(2);
}
+static void PlayerConfigMenu() {
+ Menu pcm(globalData.screen,_("Player configuration"),true);
+ Button bPlayer1Name, bPlayer2Name;
+ Button bPlayer1Keys, bPlayer2Keys;
+ bPlayer1Name.setAction(buttonActionPlayer1Name);
+ bPlayer1Name.setLabel(_("Change player 1's name") );
+ bPlayer2Name.setAction(buttonActionPlayer2Name);
+ bPlayer2Name.setLabel(_("Change player 2's name") );
+ bPlayer1Keys.setAction(ChangeKeysMenu1);
+ bPlayer1Keys.setLabel(_("Change player 1's keys") );
+ bPlayer2Keys.setAction(ChangeKeysMenu2);
+ bPlayer2Keys.setLabel(_("Change player 2's keys") );
+ pcm.addButton(&bPlayer1Name);
+ pcm.addButton(&bPlayer2Name);
+ pcm.addButton(&bPlayer1Keys);
+ pcm.addButton(&bPlayer2Keys);
+ RunGameState(pcm);
+}
+
+static void runModConfigMenu() {
+ ModConfigMenuState modmenu;
+ RunGameState(modmenu);
+}
+
static void ConfigureMenu() {
Menu cm(globalData.screen,_("Configuration"),true);
- Button bPlayer1Name,bPlayer2Name;
- Button bPlayer1Keys, bPlayer2Keys;
AlwaysSoftwareRenderButton bSoftware;
MusicButton bMusic;
SoundButton bSound;
FullscreenButton buttonFullscreen;
+ Button bPlayerConfig;
+ Button bModConfig;
+ bPlayerConfig.setLabel(_("Player configuration") );
+ bPlayerConfig.setAction(PlayerConfigMenu);
+ bModConfig.setLabel(_("Configure mods"));
+ bModConfig.setAction(&runModConfigMenu);
SetAlwaysSoftwareLabel(&bSoftware);
SetMusicLabel(&bMusic);
SetSoundLabel(&bSound);
SetFullscreenLabel(&buttonFullscreen);
- bPlayer1Name.setAction(buttonActionPlayer1Name);
- bPlayer1Name.setLabel(_("Change player 1's name") );
- bPlayer2Name.setAction(buttonActionPlayer2Name);
- bPlayer2Name.setLabel(_("Change player 2's name") );
- bPlayer1Keys.setAction(ChangeKeysMenu1);
- bPlayer1Keys.setLabel(_("Change player 1's keys") );
- bPlayer2Keys.setAction(ChangeKeysMenu2);
- bPlayer2Keys.setLabel(_("Change player 2's keys") );
cm.addButton(&bMusic);
cm.addButton(&bSound);
cm.addButton(&bSoftware);
cm.addButton(&buttonFullscreen);
- cm.addButton(&bPlayer1Name);
- cm.addButton(&bPlayer2Name);
- cm.addButton(&bPlayer1Keys);
- cm.addButton(&bPlayer2Keys);
+ cm.addButton(&bPlayerConfig);
+ cm.addButton(&bModConfig);
RunGameState(cm);
}
diff --git a/source/code/os.cpp b/source/code/os.cpp
index 626c968..d821fb6 100644
--- a/source/code/os.cpp
+++ b/source/code/os.cpp
@@ -33,6 +33,8 @@ https://blockattack.net
#include <unistd.h>
#endif
+#include <dirent.h>
+
static sago::PlatformFolders pf;
static std::string overrideSavePath = "";
@@ -132,3 +134,23 @@ void OsCreateSaveFolder() {
std::string path = getPathToSaveFiles();
OsCreateFolder(path);
}
+
+std::vector<std::string> OsGetDirFileList(const std::string& path) {
+ //At the moment dirent.h is used for all OS. Should have a Unicode version on Windows.
+ std::vector<std::string> ret;
+ DIR* dir = opendir(path.c_str());
+ struct dirent* ent;
+ if (dir) {
+ while ((ent = readdir (dir)) ) {
+ if (ent->d_name[0] == '.') {
+ continue;
+ }
+ ret.push_back(ent->d_name);
+ }
+ closedir (dir);
+ }
+ else {
+ std::cerr << "Failed to list: " << path+"/" << "\n";
+ }
+ return ret;
+}
diff --git a/source/code/os.hpp b/source/code/os.hpp
index 42627bf..d0f60df 100644
--- a/source/code/os.hpp
+++ b/source/code/os.hpp
@@ -29,11 +29,9 @@ https://blockattack.net
#include "shlobj.h"
#endif
#include <string>
+#include <vector>
std::string getPathToSaveFiles();
-#if defined(_WIN32)
-std::string getMyDocumentsPath();
-#endif
std::string defaultPlayerName();
@@ -43,6 +41,21 @@ void OsCreateSaveFolder();
bool OsPathIsRelative(const std::string& path);
+/**
+ * Create a folder
+ * The folder path is created recursively
+ * Fails silently if failing to create the path
+ * @param path The folder to create
+ */
void OsCreateFolder(const std::string& path);
+/**
+ * Lists the content of a directory.
+ * Ignores files starting with a dot (".")
+ *
+ * @param path The path to list
+ * @return std::vector<std::string> Files names in directory
+ */
+std::vector<std::string> OsGetDirFileList(const std::string& path);
+
#endif /* OS_HPP */
diff --git a/source/code/sago/SagoDataHolder.hpp b/source/code/sago/SagoDataHolder.hpp
index 0abace8..9fbd9cd 100644
--- a/source/code/sago/SagoDataHolder.hpp
+++ b/source/code/sago/SagoDataHolder.hpp
@@ -117,7 +117,7 @@ public:
* @return A globally unique number.
*/
Uint64 getVersion() const;
-
+
~SagoDataHolder();
private:
SagoDataHolder(const SagoDataHolder& base) = delete;
diff --git a/source/code/sago/SagoSpriteHolder.cpp b/source/code/sago/SagoSpriteHolder.cpp
index 1e30d63..94c4c1c 100644
--- a/source/code/sago/SagoSpriteHolder.cpp
+++ b/source/code/sago/SagoSpriteHolder.cpp
@@ -131,6 +131,13 @@ void SagoSpriteHolder::ReadSprites() {
}
}
+void SagoSpriteHolder::ReadSprites(const std::vector<std::string>& extra_sprites) {
+ for (std::string item : extra_sprites) {
+ item+=".sprite";
+ ReadSpriteFile(item);
+ }
+}
+
const sago::SagoSprite& SagoSpriteHolder::GetSprite(const std::string& spritename) const {
std::unordered_map<std::string,std::shared_ptr<sago::SagoSprite>>::const_iterator got = data->sprites.find (spritename);
if ( got == data->sprites.end() ) {
diff --git a/source/code/sago/SagoSpriteHolder.hpp b/source/code/sago/SagoSpriteHolder.hpp
index 21b093e..be76b51 100644
--- a/source/code/sago/SagoSpriteHolder.hpp
+++ b/source/code/sago/SagoSpriteHolder.hpp
@@ -27,6 +27,7 @@ SOFTWARE.
#include "SagoDataHolder.hpp"
#include "SagoSprite.hpp"
+#include <vector>
namespace sago {
@@ -34,7 +35,14 @@ class SagoSpriteHolder final {
public:
explicit SagoSpriteHolder(const SagoDataHolder &texHolder);
~SagoSpriteHolder();
+ /**
+ * Reads all the sprites from the "sprites" directory in alphabetical order
+ **/
void ReadSprites();
+ /**
+ * Reads an additional sprites. Used to append mod data.
+ **/
+ void ReadSprites(const std::vector<std::string>& extra_sprites);
const sago::SagoSprite& GetSprite(const std::string &spritename) const;
const SagoDataHolder& GetDataHolder() const;
private:
diff --git a/source/misc/translation/po/da.po b/source/misc/translation/po/da.po
index 204dbca..2dda38c 100644
--- a/source/misc/translation/po/da.po
+++ b/source/misc/translation/po/da.po
@@ -7,15 +7,15 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-06-27 14:06+0200\n"
-"PO-Revision-Date: 2020-06-27 14:08+0200\n"
+"POT-Creation-Date: 2020-12-27 11:03+0100\n"
+"PO-Revision-Date: 2020-12-27 11:04+0100\n"
"Last-Translator: Poul Sander <poul@poulsander.com>\n"
"Language-Team: \n"
"Language: da\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 2.0.6\n"
+"X-Generator: Poedit 2.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: ../../code/DialogBox.cpp:94
@@ -26,90 +26,90 @@ msgstr "Bekræft med Enter"
msgid "Esc to cancel"
msgstr "Annuller med Esc"
-#: ../../code/HelpAboutState.cpp:68
+#: ../../code/HelpAboutState.cpp:62
msgid "No audio driver"
msgstr "Ingen lyd driver"
-#: ../../code/HelpAboutState.cpp:71
+#: ../../code/HelpAboutState.cpp:65
msgid "Name:"
msgstr "Navn:"
-#: ../../code/HelpAboutState.cpp:71 ../../code/main.cpp:329
+#: ../../code/HelpAboutState.cpp:65 ../../code/main.cpp:336
msgid "Block Attack - Rise of the Blocks"
msgstr "Block Attack - Blokkenes opstand"
-#: ../../code/HelpAboutState.cpp:72
+#: ../../code/HelpAboutState.cpp:66
msgid "Original name:"
msgstr "Originalt navn:"
-#: ../../code/HelpAboutState.cpp:73
+#: ../../code/HelpAboutState.cpp:67
msgid "Version:"
msgstr "Version:"
-#: ../../code/HelpAboutState.cpp:74
+#: ../../code/HelpAboutState.cpp:68
msgid "Homepage:"
msgstr "Hjemmeside:"
-#: ../../code/HelpAboutState.cpp:75
+#: ../../code/HelpAboutState.cpp:69
msgid "Github page:"
msgstr "Github side:"
-#: ../../code/HelpAboutState.cpp:76
+#: ../../code/HelpAboutState.cpp:70
msgid "SDL render:"
msgstr "SDL render:"
-#: ../../code/HelpAboutState.cpp:77
+#: ../../code/HelpAboutState.cpp:71
msgid "SDL audio driver:"
msgstr "SDL lyd driver:"
-#: ../../code/HelpAboutState.cpp:78
+#: ../../code/HelpAboutState.cpp:72
msgid "SDL compiled version:"
msgstr "SDL kompileret version:"
-#: ../../code/HelpAboutState.cpp:79
+#: ../../code/HelpAboutState.cpp:73
msgid "SDL linked version:"
msgstr "SDL lænket version:"
-#: ../../code/HelpAboutState.cpp:80
+#: ../../code/HelpAboutState.cpp:74
msgid "Save folder:"
msgstr "Mappe til gemte spil:"
-#: ../../code/HelpAboutState.cpp:81
+#: ../../code/HelpAboutState.cpp:75
msgid "Locale:"
msgstr "Lokalitet:"
-#: ../../code/HelpAboutState.cpp:82 ../../code/menudef.cpp:414
+#: ../../code/HelpAboutState.cpp:76 ../../code/menudef.cpp:428
msgid "About"
msgstr "Om"
-#: ../../code/HelpGamepadState.cpp:52
+#: ../../code/HelpGamepadState.cpp:35
+msgid ""
+"Only SDL2 compatible controllers are supported!\n"
+"Supported controllers: "
+msgstr ""
+"Kun SDL2 kompatible kontrollere er understøttet!\n"
+"Understøttede kontrollere: "
+
+#: ../../code/HelpGamepadState.cpp:47
msgid "Move cursor"
msgstr "Flyt markøren"
-#: ../../code/HelpGamepadState.cpp:53
+#: ../../code/HelpGamepadState.cpp:48
msgid "Push line"
msgstr "Skub linje"
-#: ../../code/HelpGamepadState.cpp:54
+#: ../../code/HelpGamepadState.cpp:49
msgid "Back (Menu)"
msgstr "Tilbage (Menu)"
-#: ../../code/HelpGamepadState.cpp:55
+#: ../../code/HelpGamepadState.cpp:50
msgid "Switch"
msgstr "Skift"
-#: ../../code/HelpGamepadState.cpp:56
+#: ../../code/HelpGamepadState.cpp:51
msgid "Confirm"
msgstr "Bekræft"
-#: ../../code/HelpGamepadState.cpp:57
-msgid ""
-"Only SDL2 compatible controllers are supported!\n"
-"Supported controllers: "
-msgstr ""
-"Kun SDL2 kompatible kontrollere er understøttet!\n"
-"Understøttede kontrollere: "
-
#: ../../code/HelpHowtoState.cpp:125
msgid "Switch block horizontally"
msgstr "Skift blokke horisontalt"
@@ -130,392 +130,412 @@ msgstr "Lad blokke falde!"
msgid "Create a chain effect"
msgstr "Lav en kæde effekt"
-#: ../../code/levelselect.cpp:86
+#: ../../code/levelselect.cpp:84
msgid "Select Puzzle"
msgstr "Vælg gåde"
-#: ../../code/levelselect.cpp:89
+#: ../../code/levelselect.cpp:87
msgid "Stage Clear Level Select"
msgstr "Vælg Stage Clear-bane"
-#: ../../code/levelselect.cpp:195
+#: ../../code/levelselect.cpp:193
#, c-format
msgid "Best score: %i"
msgstr "Bedste resultat: %i"
-#: ../../code/levelselect.cpp:196
+#: ../../code/levelselect.cpp:194
#, c-format
msgid "Time used: %s"
msgstr "Tid brugt: %s"
-#: ../../code/levelselect.cpp:199
+#: ../../code/levelselect.cpp:197
#, c-format
msgid "Time used: %d : %02d"
msgstr "Tid brugt: %d : %02d"
-#: ../../code/levelselect.cpp:204
+#: ../../code/levelselect.cpp:202
#, c-format
msgid "Total score: %i in %i:%02i"
msgstr "Sammenlagt: %i på %i:%02i"
-#: ../../code/main.cpp:158
+#: ../../code/main.cpp:164
msgid "GAME OVER"
msgstr "SPILLET ER SLUT"
-#: ../../code/main.cpp:159
+#: ../../code/main.cpp:165
msgid "WINNER"
msgstr "VINDER"
-#: ../../code/main.cpp:160
+#: ../../code/main.cpp:166
msgid "DRAW"
msgstr "UAFGJORT"
-#: ../../code/main.cpp:497 ../../code/main.cpp:628
+#: ../../code/main.cpp:504 ../../code/main.cpp:635
msgid "AI"
msgstr "Computer"
-#: ../../code/main.cpp:500
+#: ../../code/main.cpp:507
msgid "Playing field"
msgstr "Spilleflade"
-#: ../../code/main.cpp:574
+#: ../../code/main.cpp:581
msgid "Time Trial"
msgstr "På tid"
-#: ../../code/main.cpp:575
+#: ../../code/main.cpp:582
msgid "Score as much as possible in 2 minutes"
msgstr "Se hvor mange point du kan opnå på 2 minutter"
-#: ../../code/main.cpp:579
+#: ../../code/main.cpp:586
msgid "Stage Clear"
msgstr "Stage Clear"
-#: ../../code/main.cpp:580
+#: ../../code/main.cpp:587
msgid "You must clear a number of lines. Speed is rapidly increased."
msgstr "Fjern et specifik antal linjer. Hastigheden stiger hurtigt."
-#: ../../code/main.cpp:583
+#: ../../code/main.cpp:590
msgid "Puzzle"
msgstr "Gåde"
-#: ../../code/main.cpp:584
+#: ../../code/main.cpp:591
msgid "Clear the entire board with a limited number of moves."
msgstr "Fjern alle blokke med et begrænset antal flyt."
-#: ../../code/main.cpp:587
+#: ../../code/main.cpp:594
msgid "Endless"
msgstr "Evighed"
-#: ../../code/main.cpp:588
+#: ../../code/main.cpp:595
msgid "Score as much as possible. No time limit."
msgstr "Opnå så mange point som muligt. Ingen tidsbegrænsning."
-#: ../../code/main.cpp:598
+#: ../../code/main.cpp:605
msgid "Objective:"
msgstr "Formål:"
-#: ../../code/main.cpp:608
+#: ../../code/main.cpp:615
msgid "Movement keys:"
msgstr "Bevægelsestaster:"
-#: ../../code/main.cpp:610
+#: ../../code/main.cpp:617
msgid "Switch: "
msgstr "Skift: "
-#: ../../code/main.cpp:612
+#: ../../code/main.cpp:619
msgid "Restart: "
msgstr "Genstart: "
-#: ../../code/main.cpp:615
+#: ../../code/main.cpp:622
msgid "Push line: "
msgstr "Skub linje: "
-#: ../../code/main.cpp:1050
+#: ../../code/main.cpp:1076
msgid "Player 2"
msgstr "Spiller 2"
-#: ../../code/menudef.cpp:62
+#: ../../code/menudef.cpp:59
msgid "Up arrow"
msgstr "Pil op"
-#: ../../code/menudef.cpp:65
+#: ../../code/menudef.cpp:62
msgid "Down arrow"
msgstr "Pil ned"
-#: ../../code/menudef.cpp:68
+#: ../../code/menudef.cpp:65
msgid "Left arrow"
msgstr "Pil venstre"
-#: ../../code/menudef.cpp:71
+#: ../../code/menudef.cpp:68
msgid "Right arrow"
msgstr "Pil højre"
-#: ../../code/menudef.cpp:74
+#: ../../code/menudef.cpp:71
msgid "Right Ctrl"
msgstr "Højre Ctrl"
-#: ../../code/menudef.cpp:77
+#: ../../code/menudef.cpp:74
msgid "Left Ctrl"
msgstr "Venstre Ctrl"
-#: ../../code/menudef.cpp:80
+#: ../../code/menudef.cpp:77
msgid "Right shift"
msgstr "Højre skift"
-#: ../../code/menudef.cpp:83
+#: ../../code/menudef.cpp:80
msgid "Left shift"
msgstr "Venstre skift"
-#: ../../code/menudef.cpp:86
+#: ../../code/menudef.cpp:83
msgid "Right alt"
msgstr "AltGr"
-#: ../../code/menudef.cpp:89
+#: ../../code/menudef.cpp:86
msgid "Left alt"
msgstr "Alt"
-#: ../../code/menudef.cpp:92
+#: ../../code/menudef.cpp:89
msgid "Return"
msgstr "Enter"
-#: ../../code/menudef.cpp:95
+#: ../../code/menudef.cpp:92
msgid "Space"
msgstr "Mellemrum"
-#: ../../code/menudef.cpp:162
+#: ../../code/menudef.cpp:158
msgid "Single Player Endless"
msgstr "En spiller - Evighed"
-#: ../../code/menudef.cpp:169
+#: ../../code/menudef.cpp:165
msgid "Slow"
msgstr "Langsom"
-#: ../../code/menudef.cpp:170
+#: ../../code/menudef.cpp:166
msgid "Fast"
msgstr "Hurtig"
-#: ../../code/menudef.cpp:171
+#: ../../code/menudef.cpp:167
msgid "Faster"
msgstr "Hurtigere"
-#: ../../code/menudef.cpp:172
+#: ../../code/menudef.cpp:168
msgid "Even faster"
msgstr "Endnu hurtigere"
-#: ../../code/menudef.cpp:173
+#: ../../code/menudef.cpp:169
msgid "Fastest"
msgstr "Hurtigst"
-#: ../../code/menudef.cpp:215
+#: ../../code/menudef.cpp:211
msgid "Always use software render: On"
msgstr "Brug altid software tilstand: Til"
-#: ../../code/menudef.cpp:216
+#: ../../code/menudef.cpp:212
msgid "Always use software render: Off"
msgstr "Brug altid software tilstand: Fra"
-#: ../../code/menudef.cpp:220
+#: ../../code/menudef.cpp:216
msgid "Music: On"
msgstr "Musik: Til"
-#: ../../code/menudef.cpp:220
+#: ../../code/menudef.cpp:216
msgid "Music: Off"
msgstr "Musik: Fra"
-#: ../../code/menudef.cpp:224
+#: ../../code/menudef.cpp:220
msgid "Sound: On"
msgstr "Lyd: Til"
-#: ../../code/menudef.cpp:224
+#: ../../code/menudef.cpp:220
msgid "Sound: Off"
msgstr "Lyd: Fra"
-#: ../../code/menudef.cpp:228
+#: ../../code/menudef.cpp:224
msgid "Fullscreen: On"
msgstr "Fuldskærm: Til"
-#: ../../code/menudef.cpp:228
+#: ../../code/menudef.cpp:224
msgid "Fullscreen: Off"
msgstr "Fuldskærm: Fra"
-#: ../../code/menudef.cpp:261
+#: ../../code/menudef.cpp:257
msgid "Enter player 1 name:"
msgstr "Indtast navnet på spiller 1:"
-#: ../../code/menudef.cpp:267
+#: ../../code/menudef.cpp:263
msgid "Enter player 2 name:"
msgstr "Indtast navnet på spiller 2:"
-#: ../../code/menudef.cpp:277
+#: ../../code/menudef.cpp:273
msgid "Change key bindings"
msgstr "Skift taster"
-#: ../../code/menudef.cpp:278
+#: ../../code/menudef.cpp:274
msgid "Left"
msgstr "Venstre"
-#: ../../code/menudef.cpp:279
+#: ../../code/menudef.cpp:275
msgid "Right"
msgstr "Højre"
-#: ../../code/menudef.cpp:280
+#: ../../code/menudef.cpp:276
msgid "Up"
msgstr "Op"
-#: ../../code/menudef.cpp:281
+#: ../../code/menudef.cpp:277
msgid "Down"
msgstr "Ned"
-#: ../../code/menudef.cpp:282
+#: ../../code/menudef.cpp:278
msgid "Push"
msgstr "Skub"
-#: ../../code/menudef.cpp:283
+#: ../../code/menudef.cpp:279
msgid "Change"
msgstr "Skift"
-#: ../../code/menudef.cpp:302
-msgid "Configuration"
-msgstr "Konfiguration"
+#: ../../code/menudef.cpp:298 ../../code/menudef.cpp:329
+msgid "Player configuration"
+msgstr "Spiller konfiguration"
-#: ../../code/menudef.cpp:314
+#: ../../code/menudef.cpp:302
msgid "Change player 1's name"
msgstr "Skift navn på spiller 1"
-#: ../../code/menudef.cpp:316
+#: ../../code/menudef.cpp:304
msgid "Change player 2's name"
msgstr "Skift navn på spiller 2"
-#: ../../code/menudef.cpp:318
+#: ../../code/menudef.cpp:306
msgid "Change player 1's keys"
msgstr "Tildel taster til spiller 1"
-#: ../../code/menudef.cpp:320
+#: ../../code/menudef.cpp:308
msgid "Change player 2's keys"
msgstr "Tildel taster til spiller 2"
-#: ../../code/menudef.cpp:333
+#: ../../code/menudef.cpp:322
+msgid "Configuration"
+msgstr "Konfiguration"
+
+#: ../../code/menudef.cpp:331
+msgid "Configure mods"
+msgstr "Mods"
+
+#: ../../code/menudef.cpp:347
msgid "Single player VS"
msgstr "En spiller - VS"
-#: ../../code/menudef.cpp:349
+#: ../../code/menudef.cpp:363
msgid "Very easy"
msgstr "Meget let"
-#: ../../code/menudef.cpp:350
+#: ../../code/menudef.cpp:364
msgid "Easy"
msgstr "Let"
-#: ../../code/menudef.cpp:351
+#: ../../code/menudef.cpp:365
msgid "Below normal"
msgstr "Under normal"
-#: ../../code/menudef.cpp:352
+#: ../../code/menudef.cpp:366
msgid "Normal"
msgstr "Normal"
-#: ../../code/menudef.cpp:353
+#: ../../code/menudef.cpp:367
msgid "Above normal"
msgstr "Over normal"
-#: ../../code/menudef.cpp:354
+#: ../../code/menudef.cpp:368
msgid "Hard"
msgstr "Svær"
-#: ../../code/menudef.cpp:355
+#: ../../code/menudef.cpp:369
msgid "Hardest"
msgstr "Meget svær"
-#: ../../code/menudef.cpp:367
+#: ../../code/menudef.cpp:381
msgid "Multiplayer"
msgstr "Flerspiller"
-#: ../../code/menudef.cpp:369
+#: ../../code/menudef.cpp:383
msgid "Two player - time trial"
msgstr "To spillere - På tid"
-#: ../../code/menudef.cpp:371
+#: ../../code/menudef.cpp:385
msgid "Two player - vs"
msgstr "To spillere - VS"
-#: ../../code/menudef.cpp:395 ../../code/menudef.cpp:410
+#: ../../code/menudef.cpp:409 ../../code/menudef.cpp:424
msgid "Credits"
msgstr "Bidragydere"
-#: ../../code/menudef.cpp:400 ../../code/menudef.cpp:471
+#: ../../code/menudef.cpp:414 ../../code/menudef.cpp:487
msgid "Help"
msgstr "Hjælp"
-#: ../../code/menudef.cpp:402
+#: ../../code/menudef.cpp:416
msgid "How to"
msgstr "Guide"
-#: ../../code/menudef.cpp:406
+#: ../../code/menudef.cpp:420
msgid "Gamepad"
msgstr "Spilkontroller"
-#: ../../code/menudef.cpp:421 ../../code/menudef.cpp:463
+#: ../../code/menudef.cpp:435 ../../code/menudef.cpp:479
msgid "Single player"
msgstr "Enkeltspiller"
-#: ../../code/menudef.cpp:423
+#: ../../code/menudef.cpp:437
msgid "Single player - endless"
msgstr "En spiller - Evighed"
-#: ../../code/menudef.cpp:425
+#: ../../code/menudef.cpp:439
msgid "Single player - time trial"
msgstr "En spiller - På tid"
-#: ../../code/menudef.cpp:427
+#: ../../code/menudef.cpp:441
msgid "Single player - stage clear"
msgstr "En spiller - Stage Clear"
-#: ../../code/menudef.cpp:429
+#: ../../code/menudef.cpp:443
msgid "Single player - puzzle mode"
msgstr "En spiller - Gåde"
-#: ../../code/menudef.cpp:431
+#: ../../code/menudef.cpp:445
msgid "Single player - vs"
msgstr "En spiller - VS"
-#: ../../code/menudef.cpp:446
+#: ../../code/menudef.cpp:460
msgid "Game did not shutdown as it should"
msgstr "Spillet blev ikke afsluttet korrekt"
-#: ../../code/menudef.cpp:449
+#: ../../code/menudef.cpp:463
msgid "Use software renderer this time"
msgstr "Brug software tilstand denne gang"
-#: ../../code/menudef.cpp:450
+#: ../../code/menudef.cpp:464
msgid "Always use software renderer"
msgstr "Start altid i software tilstand"
-#: ../../code/menudef.cpp:461
+#: ../../code/menudef.cpp:477
msgid "Block Attack - Rise of the blocks"
msgstr "Block Attack - Blokkenes opstand"
-#: ../../code/menudef.cpp:465
+#: ../../code/menudef.cpp:481
msgid "Multi player"
msgstr "Flere spillere"
-#: ../../code/menudef.cpp:467
+#: ../../code/menudef.cpp:483
msgid "Configure"
msgstr "Instillinger"
-#: ../../code/menudef.cpp:469
+#: ../../code/menudef.cpp:485
msgid "Highscores"
msgstr "Bedste resultater"
-#: ../../code/MenuSystem.cpp:159 ../../code/MenuSystem.cpp:167
-#: ../../code/MenuSystem.cpp:179 ../../code/ScoresDisplay.cpp:220
+#: ../../code/MenuSystem.cpp:146 ../../code/MenuSystem.cpp:151
+#: ../../code/MenuSystem.cpp:160 ../../code/ScoresDisplay.cpp:220
msgid "Back"
msgstr "Tilbage"
-#: ../../code/MenuSystem.cpp:170 ../../code/MenuSystem.cpp:182
+#: ../../code/MenuSystem.cpp:154 ../../code/MenuSystem.cpp:163
msgid "Exit"
msgstr "Afslut"
-#: ../../code/os.cpp:101
+#: ../../code/ModConfigMenuState.cpp:84
+msgid "Mod config"
+msgstr "Mods"
+
+#: ../../code/ModConfigMenuState.cpp:87
+msgid "Yes"
+msgstr "Ja"
+
+#: ../../code/ModConfigMenuState.cpp:87
+msgid "No"
+msgstr "Nej"
+
+#: ../../code/os.cpp:103
msgid "Player 1"
msgstr "Spiller 1"
@@ -624,15 +644,15 @@ msgstr "Prøv igen"
msgid "Skip"
msgstr "Spring over"
-#: ../../code/BlockGameSdl.inc:328
+#: ../../code/BlockGameSdl.inc:318
msgid "Moves left: "
msgstr "Træk tilbage: "
-#: ../../code/BlockGameSdl.inc:351
+#: ../../code/BlockGameSdl.inc:341
msgid "Last puzzle"
msgstr "Sidste gåde"
-#: ../../code/BlockGameSdl.inc:371
+#: ../../code/BlockGameSdl.inc:361
msgid "Last stage"
msgstr "Sidste bane"
diff --git a/source/misc/translation/template/blockattack_roftb.pot b/source/misc/translation/template/blockattack_roftb.pot
index ac49a7a..818399d 100644
--- a/source/misc/translation/template/blockattack_roftb.pot
+++ b/source/misc/translation/template/blockattack_roftb.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-06-27 14:06+0200\n"
+"POT-Creation-Date: 2020-12-27 11:03+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -25,88 +25,88 @@ msgstr ""
msgid "Esc to cancel"
msgstr ""
-#: ../../code/HelpAboutState.cpp:68
+#: ../../code/HelpAboutState.cpp:62
msgid "No audio driver"
msgstr ""
-#: ../../code/HelpAboutState.cpp:71
+#: ../../code/HelpAboutState.cpp:65
msgid "Name:"
msgstr ""
-#: ../../code/HelpAboutState.cpp:71 ../../code/main.cpp:329
+#: ../../code/HelpAboutState.cpp:65 ../../code/main.cpp:336
msgid "Block Attack - Rise of the Blocks"
msgstr ""
-#: ../../code/HelpAboutState.cpp:72
+#: ../../code/HelpAboutState.cpp:66
msgid "Original name:"
msgstr ""
-#: ../../code/HelpAboutState.cpp:73
+#: ../../code/HelpAboutState.cpp:67
msgid "Version:"
msgstr ""
-#: ../../code/HelpAboutState.cpp:74
+#: ../../code/HelpAboutState.cpp:68
msgid "Homepage:"
msgstr ""
-#: ../../code/HelpAboutState.cpp:75
+#: ../../code/HelpAboutState.cpp:69
msgid "Github page:"
msgstr ""
-#: ../../code/HelpAboutState.cpp:76
+#: ../../code/HelpAboutState.cpp:70
msgid "SDL render:"
msgstr ""
-#: ../../code/HelpAboutState.cpp:77
+#: ../../code/HelpAboutState.cpp:71
msgid "SDL audio driver:"
msgstr ""
-#: ../../code/HelpAboutState.cpp:78
+#: ../../code/HelpAboutState.cpp:72
msgid "SDL compiled version:"
msgstr ""
-#: ../../code/HelpAboutState.cpp:79
+#: ../../code/HelpAboutState.cpp:73
msgid "SDL linked version:"
msgstr ""
-#: ../../code/HelpAboutState.cpp:80
+#: ../../code/HelpAboutState.cpp:74
msgid "Save folder:"
msgstr ""
-#: ../../code/HelpAboutState.cpp:81
+#: ../../code/HelpAboutState.cpp:75
msgid "Locale:"
msgstr ""
-#: ../../code/HelpAboutState.cpp:82 ../../code/menudef.cpp:414
+#: ../../code/HelpAboutState.cpp:76 ../../code/menudef.cpp:428
msgid "About"
msgstr ""
-#: ../../code/HelpGamepadState.cpp:52
+#: ../../code/HelpGamepadState.cpp:35
+msgid ""
+"Only SDL2 compatible controllers are supported!\n"
+"Supported controllers: "
+msgstr ""
+
+#: ../../code/HelpGamepadState.cpp:47
msgid "Move cursor"
msgstr ""
-#: ../../code/HelpGamepadState.cpp:53
+#: ../../code/HelpGamepadState.cpp:48
msgid "Push line"
msgstr ""
-#: ../../code/HelpGamepadState.cpp:54
+#: ../../code/HelpGamepadState.cpp:49
msgid "Back (Menu)"
msgstr ""
-#: ../../code/HelpGamepadState.cpp:55
+#: ../../code/HelpGamepadState.cpp:50
msgid "Switch"
msgstr ""
-#: ../../code/HelpGamepadState.cpp:56
+#: ../../code/HelpGamepadState.cpp:51
msgid "Confirm"
msgstr ""
-#: ../../code/HelpGamepadState.cpp:57
-msgid ""
-"Only SDL2 compatible controllers are supported!\n"
-"Supported controllers: "
-msgstr ""
-
#: ../../code/HelpHowtoState.cpp:125
msgid "Switch block horizontally"
msgstr ""
@@ -127,392 +127,412 @@ msgstr ""
msgid "Create a chain effect"
msgstr ""
-#: ../../code/levelselect.cpp:86
+#: ../../code/levelselect.cpp:84
msgid "Select Puzzle"
msgstr ""
-#: ../../code/levelselect.cpp:89
+#: ../../code/levelselect.cpp:87
msgid "Stage Clear Level Select"
msgstr ""
-#: ../../code/levelselect.cpp:195
+#: ../../code/levelselect.cpp:193
#, c-format
msgid "Best score: %i"
msgstr ""
-#: ../../code/levelselect.cpp:196
+#: ../../code/levelselect.cpp:194
#, c-format
msgid "Time used: %s"
msgstr ""
-#: ../../code/levelselect.cpp:199
+#: ../../code/levelselect.cpp:197
#, c-format
msgid "Time used: %d : %02d"
msgstr ""
-#: ../../code/levelselect.cpp:204
+#: ../../code/levelselect.cpp:202
#, c-format
msgid "Total score: %i in %i:%02i"
msgstr ""
-#: ../../code/main.cpp:158
+#: ../../code/main.cpp:164
msgid "GAME OVER"
msgstr ""
-#: ../../code/main.cpp:159
+#: ../../code/main.cpp:165
msgid "WINNER"
msgstr ""
-#: ../../code/main.cpp:160
+#: ../../code/main.cpp:166
msgid "DRAW"
msgstr ""
-#: ../../code/main.cpp:497 ../../code/main.cpp:628
+#: ../../code/main.cpp:504 ../../code/main.cpp:635
msgid "AI"
msgstr ""
-#: ../../code/main.cpp:500
+#: ../../code/main.cpp:507
msgid "Playing field"
msgstr ""
-#: ../../code/main.cpp:574
+#: ../../code/main.cpp:581
msgid "Time Trial"
msgstr ""
-#: ../../code/main.cpp:575
+#: ../../code/main.cpp:582
msgid "Score as much as possible in 2 minutes"
msgstr ""
-#: ../../code/main.cpp:579
+#: ../../code/main.cpp:586
msgid "Stage Clear"
msgstr ""
-#: ../../code/main.cpp:580
+#: ../../code/main.cpp:587
msgid "You must clear a number of lines. Speed is rapidly increased."
msgstr ""
-#: ../../code/main.cpp:583
+#: ../../code/main.cpp:590
msgid "Puzzle"
msgstr ""
-#: ../../code/main.cpp:584
+#: ../../code/main.cpp:591
msgid "Clear the entire board with a limited number of moves."
msgstr ""
-#: ../../code/main.cpp:587
+#: ../../code/main.cpp:594
msgid "Endless"
msgstr ""
-#: ../../code/main.cpp:588
+#: ../../code/main.cpp:595
msgid "Score as much as possible. No time limit."
msgstr ""
-#: ../../code/main.cpp:598
+#: ../../code/main.cpp:605
msgid "Objective:"
msgstr ""
-#: ../../code/main.cpp:608
+#: ../../code/main.cpp:615
msgid "Movement keys:"
msgstr ""
-#: ../../code/main.cpp:610
+#: ../../code/main.cpp:617
msgid "Switch: "
msgstr ""
-#: ../../code/main.cpp:612
+#: ../../code/main.cpp:619
msgid "Restart: "
msgstr ""
-#: ../../code/main.cpp:615
+#: ../../code/main.cpp:622
msgid "Push line: "
msgstr ""
-#: ../../code/main.cpp:1050
+#: ../../code/main.cpp:1076
msgid "Player 2"
msgstr ""
-#: ../../code/menudef.cpp:62
+#: ../../code/menudef.cpp:59
msgid "Up arrow"
msgstr ""
-#: ../../code/menudef.cpp:65
+#: ../../code/menudef.cpp:62
msgid "Down arrow"
msgstr ""
-#: ../../code/menudef.cpp:68
+#: ../../code/menudef.cpp:65
msgid "Left arrow"
msgstr ""
-#: ../../code/menudef.cpp:71
+#: ../../code/menudef.cpp:68
msgid "Right arrow"
msgstr ""
-#: ../../code/menudef.cpp:74
+#: ../../code/menudef.cpp:71
msgid "Right Ctrl"
msgstr ""
-#: ../../code/menudef.cpp:77
+#: ../../code/menudef.cpp:74
msgid "Left Ctrl"
msgstr ""
-#: ../../code/menudef.cpp:80
+#: ../../code/menudef.cpp:77
msgid "Right shift"
msgstr ""
-#: ../../code/menudef.cpp:83
+#: ../../code/menudef.cpp:80
msgid "Left shift"
msgstr ""
-#: ../../code/menudef.cpp:86
+#: ../../code/menudef.cpp:83
msgid "Right alt"
msgstr ""
-#: ../../code/menudef.cpp:89
+#: ../../code/menudef.cpp:86
msgid "Left alt"
msgstr ""
-#: ../../code/menudef.cpp:92
+#: ../../code/menudef.cpp:89
msgid "Return"
msgstr ""
-#: ../../code/menudef.cpp:95
+#: ../../code/menudef.cpp:92
msgid "Space"
msgstr ""
-#: ../../code/menudef.cpp:162
+#: ../../code/menudef.cpp:158
msgid "Single Player Endless"
msgstr ""
-#: ../../code/menudef.cpp:169
+#: ../../code/menudef.cpp:165
msgid "Slow"
msgstr ""
-#: ../../code/menudef.cpp:170
+#: ../../code/menudef.cpp:166
msgid "Fast"
msgstr ""
-#: ../../code/menudef.cpp:171
+#: ../../code/menudef.cpp:167
msgid "Faster"
msgstr ""
-#: ../../code/menudef.cpp:172
+#: ../../code/menudef.cpp:168
msgid "Even faster"
msgstr ""
-#: ../../code/menudef.cpp:173
+#: ../../code/menudef.cpp:169
msgid "Fastest"
msgstr ""
-#: ../../code/menudef.cpp:215
+#: ../../code/menudef.cpp:211
msgid "Always use software render: On"
msgstr ""
-#: ../../code/menudef.cpp:216
+#: ../../code/menudef.cpp:212
msgid "Always use software render: Off"
msgstr ""
-#: ../../code/menudef.cpp:220
+#: ../../code/menudef.cpp:216
msgid "Music: On"
msgstr ""
-#: ../../code/menudef.cpp:220
+#: ../../code/menudef.cpp:216
msgid "Music: Off"
msgstr ""
-#: ../../code/menudef.cpp:224
+#: ../../code/menudef.cpp:220
msgid "Sound: On"
msgstr ""
-#: ../../code/menudef.cpp:224
+#: ../../code/menudef.cpp:220
msgid "Sound: Off"
msgstr ""
-#: ../../code/menudef.cpp:228
+#: ../../code/menudef.cpp:224
msgid "Fullscreen: On"
msgstr ""
-#: ../../code/menudef.cpp:228
+#: ../../code/menudef.cpp:224
msgid "Fullscreen: Off"
msgstr ""
-#: ../../code/menudef.cpp:261
+#: ../../code/menudef.cpp:257
msgid "Enter player 1 name:"
msgstr ""
-#: ../../code/menudef.cpp:267
+#: ../../code/menudef.cpp:263
msgid "Enter player 2 name:"
msgstr ""
-#: ../../code/menudef.cpp:277
+#: ../../code/menudef.cpp:273
msgid "Change key bindings"
msgstr ""
-#: ../../code/menudef.cpp:278
+#: ../../code/menudef.cpp:274
msgid "Left"
msgstr ""
-#: ../../code/menudef.cpp:279
+#: ../../code/menudef.cpp:275
msgid "Right"
msgstr ""
-#: ../../code/menudef.cpp:280
+#: ../../code/menudef.cpp:276
msgid "Up"
msgstr ""
-#: ../../code/menudef.cpp:281
+#: ../../code/menudef.cpp:277
msgid "Down"
msgstr ""
-#: ../../code/menudef.cpp:282
+#: ../../code/menudef.cpp:278
msgid "Push"
msgstr ""
-#: ../../code/menudef.cpp:283
+#: ../../code/menudef.cpp:279
msgid "Change"
msgstr ""
-#: ../../code/menudef.cpp:302
-msgid "Configuration"
+#: ../../code/menudef.cpp:298 ../../code/menudef.cpp:329
+msgid "Player configuration"
msgstr ""
-#: ../../code/menudef.cpp:314
+#: ../../code/menudef.cpp:302
msgid "Change player 1's name"
msgstr ""
-#: ../../code/menudef.cpp:316
+#: ../../code/menudef.cpp:304
msgid "Change player 2's name"
msgstr ""
-#: ../../code/menudef.cpp:318
+#: ../../code/menudef.cpp:306
msgid "Change player 1's keys"
msgstr ""
-#: ../../code/menudef.cpp:320
+#: ../../code/menudef.cpp:308
msgid "Change player 2's keys"
msgstr ""
-#: ../../code/menudef.cpp:333
+#: ../../code/menudef.cpp:322
+msgid "Configuration"
+msgstr ""
+
+#: ../../code/menudef.cpp:331
+msgid "Configure mods"
+msgstr ""
+
+#: ../../code/menudef.cpp:347
msgid "Single player VS"
msgstr ""
-#: ../../code/menudef.cpp:349
+#: ../../code/menudef.cpp:363
msgid "Very easy"
msgstr ""
-#: ../../code/menudef.cpp:350
+#: ../../code/menudef.cpp:364
msgid "Easy"
msgstr ""
-#: ../../code/menudef.cpp:351
+#: ../../code/menudef.cpp:365
msgid "Below normal"
msgstr ""
-#: ../../code/menudef.cpp:352
+#: ../../code/menudef.cpp:366
msgid "Normal"
msgstr ""
-#: ../../code/menudef.cpp:353
+#: ../../code/menudef.cpp:367
msgid "Above normal"
msgstr ""
-#: ../../code/menudef.cpp:354
+#: ../../code/menudef.cpp:368
msgid "Hard"
msgstr ""
-#: ../../code/menudef.cpp:355
+#: ../../code/menudef.cpp:369
msgid "Hardest"
msgstr ""
-#: ../../code/menudef.cpp:367
+#: ../../code/menudef.cpp:381
msgid "Multiplayer"
msgstr ""
-#: ../../code/menudef.cpp:369
+#: ../../code/menudef.cpp:383
msgid "Two player - time trial"
msgstr ""
-#: ../../code/menudef.cpp:371
+#: ../../code/menudef.cpp:385
msgid "Two player - vs"
msgstr ""
-#: ../../code/menudef.cpp:395 ../../code/menudef.cpp:410
+#: ../../code/menudef.cpp:409 ../../code/menudef.cpp:424
msgid "Credits"
msgstr ""
-#: ../../code/menudef.cpp:400 ../../code/menudef.cpp:471
+#: ../../code/menudef.cpp:414 ../../code/menudef.cpp:487
msgid "Help"
msgstr ""
-#: ../../code/menudef.cpp:402
+#: ../../code/menudef.cpp:416
msgid "How to"
msgstr ""
-#: ../../code/menudef.cpp:406
+#: ../../code/menudef.cpp:420
msgid "Gamepad"
msgstr ""
-#: ../../code/menudef.cpp:421 ../../code/menudef.cpp:463
+#: ../../code/menudef.cpp:435 ../../code/menudef.cpp:479
msgid "Single player"
msgstr ""
-#: ../../code/menudef.cpp:423
+#: ../../code/menudef.cpp:437
msgid "Single player - endless"
msgstr ""
-#: ../../code/menudef.cpp:425
+#: ../../code/menudef.cpp:439
msgid "Single player - time trial"
msgstr ""
-#: ../../code/menudef.cpp:427
+#: ../../code/menudef.cpp:441
msgid "Single player - stage clear"
msgstr ""
-#: ../../code/menudef.cpp:429
+#: ../../code/menudef.cpp:443
msgid "Single player - puzzle mode"
msgstr ""
-#: ../../code/menudef.cpp:431
+#: ../../code/menudef.cpp:445
msgid "Single player - vs"
msgstr ""
-#: ../../code/menudef.cpp:446
+#: ../../code/menudef.cpp:460
msgid "Game did not shutdown as it should"
msgstr ""
-#: ../../code/menudef.cpp:449
+#: ../../code/menudef.cpp:463
msgid "Use software renderer this time"
msgstr ""
-#: ../../code/menudef.cpp:450
+#: ../../code/menudef.cpp:464
msgid "Always use software renderer"
msgstr ""
-#: ../../code/menudef.cpp:461
+#: ../../code/menudef.cpp:477
msgid "Block Attack - Rise of the blocks"
msgstr ""
-#: ../../code/menudef.cpp:465
+#: ../../code/menudef.cpp:481
msgid "Multi player"
msgstr ""
-#: ../../code/menudef.cpp:467
+#: ../../code/menudef.cpp:483
msgid "Configure"
msgstr ""
-#: ../../code/menudef.cpp:469
+#: ../../code/menudef.cpp:485
msgid "Highscores"
msgstr ""
-#: ../../code/MenuSystem.cpp:159 ../../code/MenuSystem.cpp:167
-#: ../../code/MenuSystem.cpp:179 ../../code/ScoresDisplay.cpp:220
+#: ../../code/MenuSystem.cpp:146 ../../code/MenuSystem.cpp:151
+#: ../../code/MenuSystem.cpp:160 ../../code/ScoresDisplay.cpp:220
msgid "Back"
msgstr ""
-#: ../../code/MenuSystem.cpp:170 ../../code/MenuSystem.cpp:182
+#: ../../code/MenuSystem.cpp:154 ../../code/MenuSystem.cpp:163
msgid "Exit"
msgstr ""
-#: ../../code/os.cpp:101
+#: ../../code/ModConfigMenuState.cpp:84
+msgid "Mod config"
+msgstr ""
+
+#: ../../code/ModConfigMenuState.cpp:87
+msgid "Yes"
+msgstr ""
+
+#: ../../code/ModConfigMenuState.cpp:87
+msgid "No"
+msgstr ""
+
+#: ../../code/os.cpp:103
msgid "Player 1"
msgstr ""
@@ -621,15 +641,15 @@ msgstr ""
msgid "Skip"
msgstr ""
-#: ../../code/BlockGameSdl.inc:328
+#: ../../code/BlockGameSdl.inc:318
msgid "Moves left: "
msgstr ""
-#: ../../code/BlockGameSdl.inc:351
+#: ../../code/BlockGameSdl.inc:341
msgid "Last puzzle"
msgstr ""
-#: ../../code/BlockGameSdl.inc:371
+#: ../../code/BlockGameSdl.inc:361
msgid "Last stage"
msgstr ""