Merge new Cheese map
authorWuzzy <almikes@aol.com>
Sun, 20 Nov 2016 00:43:12 +0100
changeset 11961 2370f679c8a8
parent 11960 c933f2257020 (diff)
parent 11947 3d1c56286ae1 (current diff)
child 11962 209a66df2340
Merge new Cheese map
--- a/ChangeLog.txt	Sat Nov 19 17:26:16 2016 -0500
+++ b/ChangeLog.txt	Sun Nov 20 00:43:12 2016 +0100
@@ -62,9 +62,12 @@
  * Fixed Capture the Flag error: Flag of first team spawned at second hog instead of first
 
 Frontend:
+ + Campaign screen shows which campaigns and missions you've completed so far
  + Menu screens got few new icons and other tweaks, e.g. larger dropdown lists for easier access
  + Additional button for just randomizing theme that will not change your selected map
  + Randomizing map/theme in online-mode will not include DLC-content
+ * Campaign screen does no longer show AI-controlled teams
+ * Campaign names and campaign mission names can now be translated
  * Creating randomized teams now randomizes (almost) every aspect
  * Fixed mostly broken descriptions for multiplayer mission maps
  * Clicking on "New" in weapon scheme editor now creates empty weapon scheme instead of default
--- a/QTfrontend/campaign.cpp	Sat Nov 19 17:26:16 2016 -0500
+++ b/QTfrontend/campaign.cpp	Sun Nov 20 00:43:12 2016 +0100
@@ -23,68 +23,129 @@
 #include <QObject>
 #include <QLocale>
 
-QList<MissionInfo> getCampMissionList(QString & campaignName, QString & teamName)
+QSettings* getCampTeamFile(QString & campaignName, QString & teamName)
 {
-    QList<MissionInfo> missionInfoList;
-    QSettings teamfile(cfgdir->absolutePath() + "/Teams/" + teamName + ".hwt", QSettings::IniFormat, 0);
-    teamfile.setIniCodec("UTF-8");
-
+    QSettings* teamfile = new QSettings(cfgdir->absolutePath() + "/Teams/" + teamName + ".hwt", QSettings::IniFormat, 0);
+    teamfile->setIniCodec("UTF-8");
     // if entry not found check if there is written without _
     // if then is found rename it to use _
     QString spaceCampName = campaignName;
     spaceCampName = spaceCampName.replace(QString("_"),QString(" "));
-    if (!teamfile.childGroups().contains("Campaign " + campaignName) and
-            teamfile.childGroups().contains("Campaign " + spaceCampName)){
-        teamfile.beginGroup("Campaign " + spaceCampName);
-        QStringList keys = teamfile.childKeys();
-        teamfile.endGroup();
+    if (!teamfile->childGroups().contains("Campaign " + campaignName) and
+            teamfile->childGroups().contains("Campaign " + spaceCampName)){
+        teamfile->beginGroup("Campaign " + spaceCampName);
+        QStringList keys = teamfile->childKeys();
+        teamfile->endGroup();
         for (int i=0;i<keys.size();i++) {
-            QVariant value = teamfile.value("Campaign " + spaceCampName + "/" + keys[i]);
-            teamfile.setValue("Campaign " + campaignName + "/" + keys[i], value);
+            QVariant value = teamfile->value("Campaign " + spaceCampName + "/" + keys[i]);
+            teamfile->setValue("Campaign " + campaignName + "/" + keys[i], value);
         }
-        teamfile.remove("Campaign " + spaceCampName);
+        teamfile->remove("Campaign " + spaceCampName);
     }
 
-    int progress = teamfile.value("Campaign " + campaignName + "/Progress", 0).toInt();
-    int unlockedMissions = teamfile.value("Campaign " + campaignName + "/UnlockedMissions", 0).toInt();
+    return teamfile;
+}
+
+/**
+    Returns true if the specified mission has been completed
+    campaignName: Name of the campaign in question
+    missionInList: QComboBox index of the mission as selected in the mission widget
+    teamName: Name of the playing team
+*/
+bool isMissionWon(QString & campaignName, int missionInList, QString & teamName)
+{
+    QSettings* teamfile = getCampTeamFile(campaignName, teamName);
+    int progress = teamfile->value("Campaign " + campaignName + "/Progress", 0).toInt();
+    int unlockedMissions = teamfile->value("Campaign " + campaignName + "/UnlockedMissions", 0).toInt();
+    if(progress>0 and unlockedMissions==0)
+    {
+        QSettings campfile("physfs://Missions/Campaign/" + campaignName + "/campaign.ini", QSettings::IniFormat, 0);
+        campfile.setIniCodec("UTF-8");
+        int totalMissions = campfile.value("MissionNum", 1).toInt();
+        return (progress > (progress - missionInList)) || (progress >= totalMissions);
+    }
+    else if(unlockedMissions>0)
+    {
+        int fileMissionId = missionInList + 1;
+        int actualMissionId = teamfile->value(QString("Campaign %1/Mission%2").arg(campaignName, QString::number(fileMissionId)), false).toInt();
+        return teamfile->value(QString("Campaign %1/Mission%2Won").arg(campaignName, QString::number(actualMissionId)), false).toBool();
+    }
+    else
+        return false;
+}
+
+/** Returns true if the campaign has been won by the team */
+bool isCampWon(QString & campaignName, QString & teamName)
+{
+    QSettings* teamfile = getCampTeamFile(campaignName, teamName);
+    bool won = teamfile->value("Campaign " + campaignName + "/Won", false).toBool();
+    return won;
+}
+
+QSettings* getCampMetaInfo()
+{
+    DataManager & dataMgr = DataManager::instance();
+    // get locale
+    QSettings settings(dataMgr.settingsFileName(),
+    QSettings::IniFormat);
+    QString loc = settings.value("misc/locale", "").toString();
+    if (loc.isEmpty())
+        loc = QLocale::system().name();
+    QString campaignDescFile = QString("physfs://Locale/campaigns_" + loc + ".txt");
+    // if file is non-existant try with language only
+    if (!QFile::exists(campaignDescFile))
+    campaignDescFile = QString("physfs://Locale/campaigns_" + loc.remove(QRegExp("_.*$")) + ".txt");
+
+    // fallback if file for current locale is non-existant
+    if (!QFile::exists(campaignDescFile))
+        campaignDescFile = QString("physfs://Locale/campaigns_en.txt");
+
+    QSettings* m_info = new QSettings(campaignDescFile, QSettings::IniFormat, 0);
+    m_info->setIniCodec("UTF-8");
+
+    return m_info;
+}
+
+/** Returns the localized campaign name */
+QString getRealCampName(QString & campaignName)
+{
+    QString campaignNameOrig = campaignName;
+    QString campaignNameSpaces = campaignName.replace(QString("_"), QString(" "));
+    return getCampMetaInfo()->value(campaignNameOrig+".name", campaignNameSpaces).toString();
+}
+
+QList<MissionInfo> getCampMissionList(QString & campaignName, QString & teamName)
+{
+    QList<MissionInfo> missionInfoList;
+    QSettings* teamfile = getCampTeamFile(campaignName, teamName);
+
+    int progress = teamfile->value("Campaign " + campaignName + "/Progress", 0).toInt();
+    int unlockedMissions = teamfile->value("Campaign " + campaignName + "/UnlockedMissions", 0).toInt();
 
     QSettings campfile("physfs://Missions/Campaign/" + campaignName + "/campaign.ini", QSettings::IniFormat, 0);
     campfile.setIniCodec("UTF-8");
 
-    DataManager & dataMgr = DataManager::instance();
-        // get locale
-        QSettings settings(dataMgr.settingsFileName(),
-                           QSettings::IniFormat);
-        QString loc = settings.value("misc/locale", "").toString();
-        if (loc.isEmpty())
-            loc = QLocale::system().name();
-        QString campaignDescFile = QString("physfs://Locale/campaigns_" + loc + ".txt");
-        // if file is non-existant try with language only
-        if (!QFile::exists(campaignDescFile))
-            campaignDescFile = QString("physfs://Locale/campaigns_" + loc.remove(QRegExp("_.*$")) + ".txt");
-
-        // fallback if file for current locale is non-existant
-        if (!QFile::exists(campaignDescFile))
-            campaignDescFile = QString("physfs://Locale/campaigns_en.txt");
-
-        QSettings m_info(campaignDescFile, QSettings::IniFormat, 0);
-        m_info.setIniCodec("UTF-8");
+    QSettings* m_info = getCampMetaInfo();
 
     if(progress>=0 and unlockedMissions==0)
     {
         for(unsigned int i=progress+1;i>0;i--)
         {
             MissionInfo missionInfo;
-            missionInfo.name = campfile.value(QString("Mission %1/Name").arg(i)).toString();
             QString script = campfile.value(QString("Mission %1/Script").arg(i)).toString();
-            missionInfo.script = script;
-            missionInfo.description = m_info.value(campaignName+"-"+ script.replace(QString(".lua"),QString("")) + ".desc",
+            if(!script.isNull()) {
+                missionInfo.script = script;
+                missionInfo.name = campfile.value(QString("Mission %1/Name").arg(i)).toString();
+                QString scriptPrefix = campaignName+"-"+ script.replace(QString(".lua"),QString(""));
+                missionInfo.realName = m_info->value(scriptPrefix+".name", missionInfo.name).toString();
+                missionInfo.description = m_info->value(scriptPrefix + ".desc",
                                             QObject::tr("No description available")).toString();
-            QString image = campfile.value(QString("Mission %1/Script").arg(i)).toString().replace(QString(".lua"),QString(".png"));
-            missionInfo.image = ":/res/campaign/"+campaignName+"/"+image;
-            if (!QFile::exists(missionInfo.image))
-                missionInfo.image = ":/res/CampaignDefault.png";
-            missionInfoList.append(missionInfo);
+                QString image = campfile.value(QString("Mission %1/Script").arg(i)).toString().replace(QString(".lua"),QString(".png"));
+                missionInfo.image = ":/res/campaign/"+campaignName+"/"+image;
+                if (!QFile::exists(missionInfo.image))
+                    missionInfo.image = ":/res/CampaignDefault.png";
+                missionInfoList.append(missionInfo);
+            }
         }
     }
     else if(unlockedMissions>0)
@@ -92,12 +153,14 @@
         for(int i=1;i<=unlockedMissions;i++)
         {
             QString missionNum = QString("%1").arg(i);
-            int missionNumber = teamfile.value("Campaign " + campaignName + "/Mission"+missionNum, -1).toInt();
+            int missionNumber = teamfile->value("Campaign " + campaignName + "/Mission"+missionNum, -1).toInt();
             MissionInfo missionInfo;
-            missionInfo.name = campfile.value(QString("Mission %1/Name").arg(missionNumber)).toString();
             QString script = campfile.value(QString("Mission %1/Script").arg(missionNumber)).toString();
             missionInfo.script = script;
-            missionInfo.description = m_info.value(campaignName+"-"+ script.replace(QString(".lua"),QString("")) + ".desc",
+            missionInfo.name = campfile.value(QString("Mission %1/Name").arg(missionNumber)).toString();
+            QString scriptPrefix = campaignName+"-"+ script.replace(QString(".lua"),QString(""));
+            missionInfo.realName = m_info->value(scriptPrefix+".name", missionInfo.name).toString();
+            missionInfo.description = m_info->value(scriptPrefix + ".desc",
                                             QObject::tr("No description available")).toString();
             QString image = campfile.value(QString("Mission %1/Script").arg(missionNumber)).toString().replace(QString(".lua"),QString(".png"));
             missionInfo.image = ":/res/campaign/"+campaignName+"/"+image;
--- a/QTfrontend/campaign.h	Sat Nov 19 17:26:16 2016 -0500
+++ b/QTfrontend/campaign.h	Sun Nov 20 00:43:12 2016 +0100
@@ -20,16 +20,24 @@
 #define CAMPAIGN_H
 
 #include <QString>
+#include <QSettings>
 
 class MissionInfo
 {
     public:
         QString name;
+        QString realName;
         QString description;
         QString script;
         QString image;
 };
 
+
+QSettings* getCampTeamFile(QString & campaignName, QString & teamName);
+QSettings* getCampMetaInfo();
+bool isCampWon(QString & campaignName, QString & teamName);
+bool isMissionWon(QString & campaignName, int missionInList, QString & teamName);
+QString getRealCampName(QString & campaignName);
 QList<MissionInfo> getCampMissionList(QString & campaignName, QString & teamName);
 
 #endif
--- a/QTfrontend/hedgewars.qrc	Sat Nov 19 17:26:16 2016 -0500
+++ b/QTfrontend/hedgewars.qrc	Sun Nov 20 00:43:12 2016 +0100
@@ -85,6 +85,8 @@
         <file>res/delete.png</file>
         <file>res/checked.png</file>
         <file>res/unchecked.png</file>
+        <file>res/missionFinished.png</file>
+        <file>res/missionFinishedSelected.png</file>
         <file>res/graphicsicon.png</file>
         <file>res/miscicon.png</file>
         <file>res/Load.png</file>
--- a/QTfrontend/hwform.cpp	Sat Nov 19 17:26:16 2016 -0500
+++ b/QTfrontend/hwform.cpp	Sun Nov 20 00:43:12 2016 +0100
@@ -130,6 +130,7 @@
 bool frontendEffects = true;
 QString playerHash;
 
+QIcon finishedIcon;
 GameUIConfig* HWForm::config = NULL;
 
 HWForm::HWForm(QWidget *parent, QString styleSheet)
@@ -167,6 +168,9 @@
     frontendEffects = config->value("frontend/effects", true).toBool();
     playerHash = QString(QCryptographicHash::hash(config->value("net/nick",tr("Guest")+QString("%1").arg(rand())).toString().toUtf8(), QCryptographicHash::Md5).toHex());
 
+    finishedIcon.addFile(":/res/missionFinished.png", QSize(), QIcon::Normal, QIcon::On);
+    finishedIcon.addFile(":/res/missionFinishedSelected.png", QSize(), QIcon::Selected, QIcon::On);
+
     ui.pageRoomsList->setSettings(config);
     ui.pageNetGame->setSettings(config);
     ui.pageNetGame->chatWidget->setSettings(config);
@@ -208,6 +212,7 @@
     UpdateTeamsLists();
     InitCampaignPage();
     UpdateCampaignPage(0);
+    UpdateCampaignPageTeam(0);
     UpdateCampaignPageMission(0);
     UpdateWeapons();
 
@@ -318,6 +323,7 @@
     connect(ui.pageCampaign->BtnStartCampaign, SIGNAL(clicked()), this, SLOT(StartCampaign()));
     connect(ui.pageCampaign->btnPreview, SIGNAL(clicked()), this, SLOT(StartCampaign()));
     connect(ui.pageCampaign->CBTeam, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateCampaignPage(int)));
+    connect(ui.pageCampaign->CBTeam, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateCampaignPageTeam(int)));
     connect(ui.pageCampaign->CBCampaign, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateCampaignPage(int)));
     connect(ui.pageCampaign->CBMission, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateCampaignPageMission(int)));
 
@@ -460,7 +466,16 @@
     ui.pageOptions->CBTeamName->clear();
     ui.pageOptions->CBTeamName->addItems(teamslist);
     ui.pageCampaign->CBTeam->clear();
-    ui.pageCampaign->CBTeam->addItems(teamslist);
+    /* Only show human teams in campaign page */
+    for(int i=0; i<teamslist.length(); i++)
+    {
+        HWTeam testTeam = HWTeam(teamslist[i]);
+        testTeam.loadFromFile();
+        if(testTeam.difficulty() == 0)
+        {
+            ui.pageCampaign->CBTeam->addItem(teamslist[i]);
+        }
+    }
 }
 
 void HWForm::GoToSelectNewWeapon()
@@ -1696,7 +1711,7 @@
 void HWForm::StartCampaign()
 {
     CreateGame(0, 0, 0);
-    QString camp = ui.pageCampaign->CBCampaign->currentText().replace(QString(" "),QString("_"));
+    QString camp = ui.pageCampaign->CBCampaign->itemData(ui.pageCampaign->CBCampaign->currentIndex()).toString();
     QString miss = campaignMissionInfo[ui.pageCampaign->CBMission->currentIndex()].script;
     QString campTeam = ui.pageCampaign->CBTeam->currentText();
     game->StartCampaign(camp, miss, campTeam);
@@ -1855,9 +1870,12 @@
                               );
 
     unsigned int n = entries.count();
+
     for(unsigned int i = 0; i < n; i++)
     {
-        ui.pageCampaign->CBCampaign->addItem(QString(entries[i]).replace(QString("_"),QString(" ")), QString(entries[i]).replace(QString("_"),QString(" ")));
+        QString campaignName = QString(entries[i]);
+        QString tName = team.name();
+        ui.pageCampaign->CBCampaign->addItem(getRealCampName(campaignName), campaignName);
     }
 }
 
@@ -1865,7 +1883,7 @@
 {
     Q_UNUSED(index);
     HWTeam team(ui.pageCampaign->CBTeam->currentText());
-    QString campaignName = ui.pageCampaign->CBCampaign->currentText().replace(QString(" "),QString("_"));
+    QString campaignName = ui.pageCampaign->CBCampaign->itemData(ui.pageCampaign->CBCampaign->currentIndex()).toString();
     QString tName = team.name();
 
     campaignMissionInfo = getCampMissionList(campaignName,tName);
@@ -1873,14 +1891,42 @@
 
     for(int i=0;i<campaignMissionInfo.size();i++)
     {
-        ui.pageCampaign->CBMission->addItem(QString(campaignMissionInfo[i].name), QString(campaignMissionInfo[i].name));
+        ui.pageCampaign->CBMission->addItem(QString(campaignMissionInfo[i].realName), QString(campaignMissionInfo[i].name));
+        if(isMissionWon(campaignName, i, tName))
+            ui.pageCampaign->CBMission->setItemIcon(i, finishedIcon);
+        else
+            ui.pageCampaign->CBMission->setItemIcon(i, QIcon());
+    }
+}
+
+void HWForm::UpdateCampaignPageTeam(int index)
+{
+    Q_UNUSED(index);
+    HWTeam team(ui.pageCampaign->CBTeam->currentText());
+    QString tName = team.name();
+
+    QStringList entries = DataManager::instance().entryList(
+                                  "Missions/Campaign",
+                                  QDir::Dirs,
+                                  QStringList("[^\\.]*")
+                              );
+
+    unsigned int n = entries.count();
+
+    for(unsigned int i = 0; i < n; i++)
+    {
+        QString campaignName = QString(entries[i]).replace(QString(" "),QString("_"));
+        if(isCampWon(campaignName, tName))
+            ui.pageCampaign->CBCampaign->setItemIcon(i, finishedIcon);
+        else
+            ui.pageCampaign->CBCampaign->setItemIcon(i, QIcon());
     }
 }
 
 void HWForm::UpdateCampaignPageMission(int index)
 {
     // update thumbnail and description
-    QString campaignName = ui.pageCampaign->CBCampaign->currentText().replace(QString(" "),QString("_"));
+    QString campaignName = ui.pageCampaign->CBCampaign->itemData(ui.pageCampaign->CBCampaign->currentIndex()).toString();
     // when campaign changes the UpdateCampaignPageMission is triggered with wrong values
     // this will cause segfault. This check prevents illegal memory reads
     if(index > -1 && index < campaignMissionInfo.count()) {
@@ -1898,12 +1944,20 @@
     UpdateCampaignPage(0);
     for(int i=0;i<ui.pageCampaign->CBMission->count();i++)
     {
-        if (ui.pageCampaign->CBMission->itemText(i)==missionTitle)
+        if (ui.pageCampaign->CBMission->itemData(i)==missionTitle)
         {
             ui.pageCampaign->CBMission->setCurrentIndex(i);
             break;
         }
     }
+    int i = ui.pageCampaign->CBCampaign->currentIndex();
+    QString campaignName = ui.pageCampaign->CBCampaign->itemData(i).toString();
+    HWTeam team(ui.pageCampaign->CBTeam->currentText());
+    QString tName = team.name();
+    if(isCampWon(campaignName, tName))
+        ui.pageCampaign->CBCampaign->setItemIcon(i, finishedIcon);
+    else
+        ui.pageCampaign->CBCampaign->setItemIcon(i, QIcon());
 }
 
 // used for --set-everything [screen width] [screen height] [color dept] [volume] [enable music] [enable sounds] [language file] [full screen] [show FPS] [alternate damage] [timer value] [reduced quality]
--- a/QTfrontend/hwform.h	Sat Nov 19 17:26:16 2016 -0500
+++ b/QTfrontend/hwform.h	Sun Nov 20 00:43:12 2016 +0100
@@ -127,6 +127,7 @@
         void onFrontendEffects(bool value);
         void Music(bool checked);
         void UpdateCampaignPage(int index);
+        void UpdateCampaignPageTeam(int index);
         void UpdateCampaignPageProgress(int index);
         void UpdateCampaignPageMission(int index);
         void InitCampaignPage();
Binary file QTfrontend/res/missionFinished.png has changed
Binary file QTfrontend/res/missionFinishedSelected.png has changed
--- a/QTfrontend/ui/page/pagecampaign.cpp	Sat Nov 19 17:26:16 2016 -0500
+++ b/QTfrontend/ui/page/pagecampaign.cpp	Sun Nov 20 00:43:12 2016 +0100
@@ -26,9 +26,10 @@
 QLayout * PageCampaign::bodyLayoutDefinition()
 {
     QGridLayout * pageLayout = new QGridLayout();
-    pageLayout->setColumnStretch(0, 1);
-    pageLayout->setColumnStretch(1, 2);
-    pageLayout->setColumnStretch(2, 1);
+    pageLayout->setColumnStretch(0, 5);
+    pageLayout->setColumnStretch(1, 1);
+    pageLayout->setColumnStretch(2, 9);
+    pageLayout->setColumnStretch(3, 5);
     pageLayout->setRowStretch(0, 1);
     pageLayout->setRowStretch(3, 1);
 
@@ -52,6 +53,10 @@
     lbltitle = new QLabel();
     lbltitle->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
 
+    QLabel* lblteam = new QLabel(tr("Team:"));
+    QLabel* lblcampaign = new QLabel(tr("Campaign:"));
+    QLabel* lblmission = new QLabel(tr("Mission:"));
+
     CBTeam = new QComboBox(this);
     CBMission = new QComboBox(this);
     CBCampaign = new QComboBox(this);
@@ -63,19 +68,39 @@
     infoLayout->addWidget(lbltitle,0,2,1,2);
     infoLayout->addWidget(lbldescription,1,2,1,2);
 
-    pageLayout->addLayout(infoLayout, 0, 0, 2, 3);
-    pageLayout->addWidget(CBTeam, 2, 1);
-    pageLayout->addWidget(CBCampaign, 3, 1);
-    pageLayout->addWidget(CBMission, 4, 1);
+    pageLayout->addLayout(infoLayout, 0, 0, 2, 4);
+    pageLayout->addWidget(lblteam, 2, 1);
+    pageLayout->addWidget(lblcampaign, 3, 1);
+    pageLayout->addWidget(lblmission, 4, 1);
+    pageLayout->addWidget(CBTeam, 2, 2);
+    pageLayout->addWidget(CBCampaign, 3, 2);
+    pageLayout->addWidget(CBMission, 4, 2);
 
-    BtnStartCampaign = new QPushButton(this);
-    BtnStartCampaign->setFont(*font14);
-    BtnStartCampaign->setText(QPushButton::tr("Go!"));
-    pageLayout->addWidget(BtnStartCampaign, 3, 2);
 
     return pageLayout;
 }
 
+QLayout * PageCampaign::footerLayoutDefinition()
+{
+    QHBoxLayout * footerLayout = new QHBoxLayout();
+
+    const QIcon& lp = QIcon(":/res/Start.png");
+    QSize sz = lp.actualSize(QSize(65535, 65535));
+    BtnStartCampaign = new QPushButton();
+    BtnStartCampaign->setText(QPushButton::tr("Start"));
+    BtnStartCampaign->setMinimumWidth(sz.width() + 60);
+    BtnStartCampaign->setIcon(lp);
+    BtnStartCampaign->setFixedHeight(50);
+    BtnStartCampaign->setIconSize(sz);
+    BtnStartCampaign->setFlat(true);
+    BtnStartCampaign->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+
+    footerLayout->addStretch();
+    footerLayout->addWidget(BtnStartCampaign);
+
+    return footerLayout;
+}
+
 PageCampaign::PageCampaign(QWidget* parent) : AbstractPage(parent)
 {
     initPage();
--- a/QTfrontend/ui/page/pagecampaign.h	Sat Nov 19 17:26:16 2016 -0500
+++ b/QTfrontend/ui/page/pagecampaign.h	Sun Nov 20 00:43:12 2016 +0100
@@ -38,6 +38,9 @@
 
     protected:
         QLayout * bodyLayoutDefinition();
+
+    private:
+        QLayout * footerLayoutDefinition();
 };
 
 #endif
--- a/share/hedgewars/Data/Locale/campaigns_de.txt	Sat Nov 19 17:26:16 2016 -0500
+++ b/share/hedgewars/Data/Locale/campaigns_de.txt	Sun Nov 20 00:43:12 2016 +0100
@@ -1,43 +1,75 @@
+A_Classic_Fairytale.name="Ein klassisches Märchen"
+
+A_Classic_Fairytale-first_blood.name="1. Mission: Der erste Zusammenstoß"
 A_Classic_Fairytale-first_blood.desc="Hilf Undichte Stelle dabei, sein Training zu absolvieren und zu einem richtigen Igelkrieger zu werden. Du wirst in der Kunst des Seils, Fallschirms, Shoryukens und der Desert Eagle trainiert."
 
+A_Classic_Fairytale-shadow.name="2. Mission: Die Schattenfälle"
 A_Classic_Fairytale-shadow.desc="Undichte Stelle und Dichte Wolke gehen für die Jagd raus. Sei auf Gefahren im Wald gefasst. Denk dran, bedenke deine Entscheidungen gut."
 
+A_Classic_Fairytale-journey.name="3. Mission: Dir Rückreise"
 A_Classic_Fairytale-journey.desc="Undichte Stelle muss zur anderen Seite der Insel gehen. Sei schnell und vorsichtig."
 
+A_Classic_Fairytale-united.name="4. Mission: Gemeinsam sind wir stark"
 A_Classic_Fairytale-united.desc="Nach seiner langen Reise kehrte Undichte Stelle endlich wieder zum Dorf zurück. Allerdings gibt es keine Zeit zum Ausruhen. Du musst das Dorf von der Rage der Kannibalen verteidigen."
 
+A_Classic_Fairytale-backstab.name="5. Mission: Verrat"
 A_Classic_Fairytale-backstab.desc="Die monströsen Kannibalen jagen Undichte Stelle und seine Freunde. Besiege sie erneut und beschütze deine Freunde. Benutze deine Ressourcen entsprechend, um die eintreffenden Feinde zu besiegen!"
 
+A_Classic_Fairytale-dragon.name="6. Mission: Die Höhle des Löwen"
 A_Classic_Fairytale-dragon.desc="Undichte Stelle muss auf die andere Seite des Sees kommen. Werd zum Seilprofi und vermeide es, von feindlichen Schüssen getroffen zu werden."
 
+A_Classic_Fairytale-family.name="7. Mission: Familientreffen"
 A_Classic_Fairytale-family.desc="Undichte Stelle muss erneut seine Freunde retten. Eliminiere die feindlichen Igel und befreie deine Kameraden. Benutze deine Ressourcen vorsichtig, weil sie begrenzt sind. Bohr ein paar Löcher an den richtigen Stellen und nähere dich der Prinzessin."
 
+A_Classic_Fairytale-queen.name="8. Mission: Lang lebe die Königin"
 A_Classic_Fairytale-queen.desc="Undichte Stelle muss noch einmal kämpfen. Um zu gewinnen, muss er den Veräräter bekämpfen und alle verfügbaren Ressourcen benutzen. Besieg den Feind!"
 
+A_Classic_Fairytale-enemy.name="9. Mission: Der Feind meines Feindes"
 A_Classic_Fairytale-enemy.desc="Was für eine umwerfende Wendung! Undichte Stelle muss mit den … »Kannibalen« gegen den gemeinsamen Feind – die bösen Cyborgs – kämpfen!"
 
+A_Classic_Fairytale-epil.name="10. Mission: Epilog"
 A_Classic_Fairytale-epil.desc="Gratulation! Undichte Stelle kann endlich in Frieden gehen und von seinen neuen Freunden und seinem Stamm angepriesen werden. Sei stolz auf das, was du erreicht hast! Du kannst vorherige Missionen wieder spielen und andere mögliche Enden sehen."
 
+A_Space_Adventure.name="Ein Weltraumabenteuer"
+
+A_Space_Adventure-cosmos.name="Menü: Weltraumreise"
 A_Space_Adventure-cosmos.desc="Hogera, der Igelplanet, wird bald von einem riesigen Meteroid getroffen. In diesem Wettlauf ums Überleben musst du PAdIs besten Piloten, Igel Einsam, in einer Weltraumreise um die Nachbarplaneten führen, um alle 4 Teil des lang verschollenem Antigravitationsgeräts zu finden!"
 
+A_Space_Adventure-moon01.name="Hauptmission: Der erste Halt"
 A_Space_Adventure-moon01.desc="Igel Einsam ist auf dem Mond gelandet, um seine fliegende Untertasse aufzutanken, aber Prof. Bösigel war zuerst da und hat einen Hinterhalt aufgestellt! Rette die gefangenen PAdI-Forscher und verscheuche Prof. Bösigel!"
 
+A_Space_Adventure-moon02.name="Nebenmission: Jag' den blauen Igel"
 A_Space_Adventure-moon02.desc="Igel Einsam besucht einen Eremiten, einen alten PAdI-Veteran, der im Mond lebt, um Prof. Bösigel auszuspionieren. Allerdings muss er den Eremiten, Verrückter Renner, zuerst in einem Wettlauf besiegen!"
 
+A_Space_Adventure-ice01.name="Hauptmission: Ein frostiges Abenteuer"
 A_Space_Adventure-ice01.desc="Willkommen auf dem Planeten des Eises. Hier ist es so kalt, dass die meisten Waffen von Igel Einsam nicht funktionieren werden. Du musst dir das verlorene Teil von dem Banditenanführer Thanta ergattern, indem du die Waffen, die du hier findest, verwendest!"
 
+A_Space_Adventure-ice02.name="Nebenmission: Schwerer Flug"
 A_Space_Adventure-ice02.desc="Igel Einsam konnt nicht einfach nur den Eisplaneten besuchen, ohne das Olympiastadion des Untertassenfliegens zu besuchen! In dieser Mission kannst du deine Flugkünste unter Beweis stellen und deinen Platz unter den Besten einnehmen!"
 
+A_Space_Adventure-desert01.name="Hauptmission: Suche im Staub"
 A_Space_Adventure-desert01.desc="Du must auf dem Planeten aus Sand gelandet! Igel Einsam muss das fehlende Teil in den Berkwerksstollen finden. Sei vorsichtig, weil bösartige Schmuggler nur darauf warten, dich anzugreifen und auszurauben!"
 
+A_Space_Adventure-desert02.name="Nebenmission: Ums Überleben laufen"
 A_Space_Adventure-desert02.desc="Igel Einsam suchte nach dem Teil in diesem Tunnel, als er unerwarteterweise anfing, geflutet zu werden! Komm so schnell wie möglich zur Oberfläche und pass auf, keine Mine auszulösen."
+
+A_Space_Adventure-desert03.name="Nebenmission: Präzisionsfliegen"
 A_Space_Adventure-desert03.desc="Igel Einsam hat etwas Zeit, um sein Funkflugzeug zu fliegen und etwas Spaß zu haben. Flieg das Funkflugzeug und triff alle Ziele!"
+
+A_Space_Adventure-fruit01.name="Hauptmission: Schlechtes Timing"
 A_Space_Adventure-fruit01.desc="Auf dem Obstplaneten laufen die Dinge nicht so gut. Igel sammeln kein Obst, sondern sie bereiten sich auf den Kampf vor. Du musst dich entscheiden, ob du kämpfen oder fliehen wirst."
+
+A_Space_Adventure-fruit02.name="Hauptmission: Zum Greifen nah"
 A_Space_Adventure-fruit02.desc="Igel Einsam nähert sich dem verlorenen Teil des Obstplaneten. Wird ihn Leutnant Limone dabei helfen, das Teil zu besorgen? Oder nicht?"
 
+A_Space_Adventure-fruit03.name="Nebenmission: Präzisionsschießen"
 A_Space_Adventure-fruit03.desc="Igel Einsam has sich verlaufen und ist in den Hinterhalt der Roten Erdbeeren geraten. Hilf ihm, sie zu eliminieren, um etwas zusätzliche Munition für die Mission »Zum Greifen nah« zu gewinnen."
 
+A_Space_Adventure-death01.name="Hauptmission: Das letzte Gefecht" 
 A_Space_Adventure-death01.desc="Auf dem Todesplaneten, dem sterilsten Planeten in der Gegend, ist Igel Einsam ganz kurz davor, das letzte Teil des Geräts zu holen! Allerdings erwartet ihn eine unangenehme Überraschnug."
 
+A_Space_Adventure-death02.name="Nebenmission: Die Spezialisten töten"
 A_Space_Adventure-death02.desc="Igel Einsam ist wieder in eine schwierige Situation geraten. Hilf ihm, die »5 tödlichen Igel« in ihrem eigenem Spiel zu besiegen!"
+
+A_Space_Adventure-final.name="Hauptmission: Der Urknall"
 A_Space_Adventure-final.desc="Igel Einsam muss ein paar Sprengkörper, die auf dem Meteriot platziert wurden, detonieren. Hilf ihm, diese Mission zu beenden, ohne verletzt zu werden!"
--- a/share/hedgewars/Data/Locale/campaigns_en.txt	Sat Nov 19 17:26:16 2016 -0500
+++ b/share/hedgewars/Data/Locale/campaigns_en.txt	Sun Nov 20 00:43:12 2016 +0100
@@ -1,34 +1,61 @@
+A_Classic_Fairytale.name="A Classic Fairytale"
+
+A_Classic_Fairytale-first_blood.name="Mission 1: First Blood"
 A_Classic_Fairytale-first_blood.desc="Help Leaks a lot to complete his training and become a proper hedgehog warrior. You will be trained in the art of rope, parachute, shoryuken and desert eagle."
 
+A_Classic_Fairytale-shadow.name="Mission 2: The Shadow Falls"
 A_Classic_Fairytale-shadow.desc="Leaks a lot and Dense Cloud are going for hunting. Be prepared for the dangers awaiting you at the forest. Remember, make your choices wisely."
 
+A_Classic_Fairytale-journey.name="Mission 3: The Journey Back"
 A_Classic_Fairytale-journey.desc="Leaks a lot has to go to the other side of the island. Be fast and cautious."
 
+A_Classic_Fairytale-united.name="Mission 4: United We Stand"
 A_Classic_Fairytale-united.desc="After his long journey Leaks a lot is finally back to the village. However, there isn't time to rest. You have to defend the village from the rage of the cannibals."
 
+A_Classic_Fairytale-backstab.name="Mission 5: Backstab"
 A_Classic_Fairytale-backstab.desc="The monstrous cannibals are hunting Leaks a lot and his friends. Defeat them once again and protect your allies. Use your resources accordingly to defeat the incoming enemies!"
 
+A_Classic_Fairytale-dragon.name="Mission 6: Dragon's Lair"
 A_Classic_Fairytale-dragon.desc="Leaks a lot has to get to the other side of the lake. Become a rope master and avoid get hit by the enemy shots."
 
+A_Classic_Fairytale-family.name="Mission 7: Family Reunion"
 A_Classic_Fairytale-family.desc="Leaks a lot has to save once more his allies. Eliminate the enemy hogs and free your comrades. Use your resources carefully as they are limited. Drill some holes in the right spot and go close to the princess."
 
+A_Classic_Fairytale-queen.name="Mission 8: Long Live The Queen"
 A_Classic_Fairytale-queen.desc="Leaks a lot has to fight once again. In order to win he'll have to fight the traitor and use all the resources available. Defeat the enemy!"
 
+A_Classic_Fairytale-enemy.name="Mission 9: The Enemy Of My Enemy"
 A_Classic_Fairytale-enemy.desc="What a great twist! Leaks a lot has to fight side by side with the… “cannibals” against the common enemy. The evil cyborgs!"
 
+A_Classic_Fairytale-epil.name="Mission 10: Epilogue"
 A_Classic_Fairytale-epil.desc="Congratulations! Leaks a lot can finally leave in peace and get praised by his new friends and his tribe. Be proud for what you succeed! You can play again previous missions and see the other possible endings."
 
+A_Space_Adventure.name="A Space Adventure"
+A_Space_Adventure-cosmos.name="Menu: Spacetrip"
 A_Space_Adventure-cosmos.desc="Hogera, the planet of hogs is about to be hit by a gigantic meteorite. In this race for survival you have to lead PAotH's best pilot, Hog Solo, in a space trip around the neighbor planets to collect all the 4 pieces of the long lost anti gravity device!"
+A_Space_Adventure-moon01.name="Main Mission: The first stop"
 A_Space_Adventure-moon01.desc="Hog Solo has landed on the moon to refuel his saucer but professor Hogevil has gone there first and set an ambush! Rescue the captured PAotH researchers and drive Professor Hogevil away!"
+A_Space_Adventure-moon02.name="Side Mission: Chasing the blue hog"
 A_Space_Adventure-moon02.desc="Hog Solo visits an hermit, old PAotH veteran, who lives on the moon in order to gather some intel about Prof. Hogevil. However, he has to beat the hermit, Soneek the Crazy Runner, in a chase game first!"
+A_Space_Adventure-ice01.name="Main Mission: A frozen adventure"
 A_Space_Adventure-ice01.desc="Welcome to the planet of ice. Here, it's so cold that most of Hog Solo's weapons won't work. You have to get the lost part from the bandit leader Thanta using the weapons that you'll find there!"
+A_Space_Adventure-ice02.name="Side Mission: Hard flying"
 A_Space_Adventure-ice02.desc="Hog Solo couldn't just visit the Ice Planet without visiting the Olympic Stadium of Saucer Flying! In this mission you can prove your flying skills and claim your place among the best!"
+A_Space_Adventure-desert01.name="Main Mission: Searching in the dust"
 A_Space_Adventure-desert01.desc="You have landed to the planet of sand! Hog Solo has to find the missing part in the underground tunnels. Be careful as vicious smugglers await to attack and rob you!"
+A_Space_Adventure-desert02.name="Side Mission: Running for survival"
 A_Space_Adventure-desert02.desc="Hog Solo was searching for the part in this tunnel when it unexpectedly start getting flooded! Get to the surface as soon as possible and be careful not to trigger a mine."
+A_Space_Adventure-desert03.name="Side Mission: Precise flying"
 A_Space_Adventure-desert03.desc="Hog Solo has some time to fly his RC plane and have some fun. Fly the RC plane and hit all the targets!"
+A_Space_Adventure-fruit01.name="Main Mission: Bad timing"
 A_Space_Adventure-fruit01.desc="In the fruit planet things aren't going so well. Hogs aren't collecting fruits but they are preparing for battle. You'll have to choose if you'll fight or if you'll flee."
+A_Space_Adventure-fruit02.name="Main Mission: Getting to the device"
 A_Space_Adventure-fruit02.desc="Hog Solo gets closer to the lost part on the Fruit Planet. Will Captain Lime help him acquire the part or not?"
+A_Space_Adventure-fruit03.name="Main Mission: Precise shooting"
 A_Space_Adventure-fruit03.desc="Hog Solo got lost and got ambushed by the Red Strawberries. Help him eliminate them and win some extra ammo for the mission “Getting to the device”."
+A_Space_Adventure-death01.name="Main Mission: The last encounter"
 A_Space_Adventure-death01.desc="On the Death Planet, the most infertile planet around, Hog Solo is very close to get the last part of the device! However, an unpleasant surprise awaits him ..."
+A_Space_Adventure-death02.name="Side Mission: Killing the specialists"
 A_Space_Adventure-death02.desc="Again Hog Solo has got himself in a difficult situation. Help him defeat the “5 Deadly Hogs“ in their own game!"
+A_Space_Adventure-final.name="Main Mission: The big bang"
 A_Space_Adventure-final.desc="Hog Solo has to detonate some explosives that have been placed on the meteorite. Help him complete his mission without getting hurt!"
--- a/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/epil.lua	Sat Nov 19 17:26:16 2016 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/epil.lua	Sun Nov 20 00:43:12 2016 +0100
@@ -83,6 +83,8 @@
   AddNewEvent(CheckCrateTaken, {}, DoCrateTaken, {}, 1)
   TurnTimeLeft = 0
   ShowMission("Epilogue", "That's all folks!", "You have successfully finished the campaign!|If you wish to replay, there are other possible endings, too!|You can practice moving around and using utilities in this mission.|However, it will never end!", 1, 0)
+  SaveCampaignVar("Progress", "10")
+  SaveCampaignVar("Won", "true")
 end
 
 ---------------------------Events-------------------------------------
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/campaign.ini	Sat Nov 19 17:26:16 2016 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/campaign.ini	Sun Nov 20 00:43:12 2016 +0100
@@ -1,4 +1,4 @@
-MissionNum=5
+MissionNum=14
 ResetRetry=1
 
 [Mission 1]
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/death02.lua	Sat Nov 19 17:26:16 2016 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/death02.lua	Sun Nov 20 00:43:12 2016 +0100
@@ -200,6 +200,7 @@
 	end
 	SendStat(siCustomAchievement, loc("The next 4 times you play the \"The last encounter\" mission you'll get 20 more hit points and a laser sight."))
 	SendStat(siPlayerKills,'1',teamA.name)
+	SaveCampaignVar("Mission11Won", "true")
 	EndGame()
 end
 
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/desert02.lua	Sat Nov 19 17:26:16 2016 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/desert02.lua	Sun Nov 20 00:43:12 2016 +0100
@@ -166,6 +166,7 @@
 		end
 	end
 	SendStat(siPlayerKills,'0',teamA.name)
+	SaveCampaignVar("Mission7Won", "true")
 	EndGame()
 end
 
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/desert03.lua	Sat Nov 19 17:26:16 2016 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/desert03.lua	Sun Nov 20 00:43:12 2016 +0100
@@ -213,6 +213,7 @@
 	SendStat(siCustomAchievement, loc("You are indeed the best PAotH pilot."))
 	SendStat(siCustomAchievement, loc("Next time you play \"Searching in the dust\" you'll have an RC plane available."))
 	SendStat(siPlayerKills,'1',teamA.name)
+	SaveCampaignVar("Mission12Won", "true")
 	EndGame()
 end
 
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/final.lua	Sat Nov 19 17:26:16 2016 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/final.lua	Sun Nov 20 00:43:12 2016 +0100
@@ -151,6 +151,8 @@
 
 function heroWin(gear)
 	saveCompletedStatus(7)
+	SaveCampaignVar("Mission1Won", "true")
+	SaveCampaignVar("Won", "true")
 	SendStat(siGameResult, loc("Congratulations, you have saved Hogera!"))
 	SendStat(siCustomAchievement, loc("Hogera is safe!"))
 	SendStat(siPlayerKills,'1',teamA.name)
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/fruit03.lua	Sat Nov 19 17:26:16 2016 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/fruit03.lua	Sun Nov 20 00:43:12 2016 +0100
@@ -226,6 +226,7 @@
 	end
 	SendStat(siCustomAchievement, loc("You will gain some extra ammo from the crates the next time you play the \"Getting to the device\" mission."))
 	SendStat(siPlayerKills,'1',teamA.name)
+	SaveCampaignVar("Mission10Won", "true")
 	EndGame()
 end
 
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/global_functions.lua	Sat Nov 19 17:26:16 2016 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/global_functions.lua	Sun Nov 20 00:43:12 2016 +0100
@@ -6,6 +6,21 @@
 		status = GetCampaignVar("MainMissionsStatus")
 	end
 
+	local planetToLevelMapping = {
+		[1] = 2,
+		[2] = 3,
+		[3] = 8,
+		[4] = 5,
+		[5] = 4,
+		[6] = 9,
+		[7] = 14
+	}
+
+	local level = planetToLevelMapping[planetNum]
+	if level ~= nil then
+		SaveCampaignVar("Mission"..level.."Won", "true")
+	end
+
 	if planetNum == 1 then
 		status = "1"..status:sub(2)
 	elseif planetNum == status:len() then
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/ice02.lua	Sat Nov 19 17:26:16 2016 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/ice02.lua	Sun Nov 20 00:43:12 2016 +0100
@@ -159,6 +159,7 @@
 			end
 
 			SendStat(siPlayerKills,'0',teamA.name)
+			SaveCampaignVar("Mission6Won", "true")
 			EndGame()
 		end
 	end
--- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/moon02.lua	Sat Nov 19 17:26:16 2016 -0500
+++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/moon02.lua	Sun Nov 20 00:43:12 2016 +0100
@@ -254,5 +254,6 @@
 	SendStat(siGameResult, loc("Congratulations, you are the fastest!"))
 	-- siCustomAchievements were added earlier
 	SendStat(siPlayerKills,'0',teamA.name)
+	SaveCampaignVar("Mission13Won", "true")
 	EndGame()
 end