QTfrontend/game.cpp
branchui-scaling
changeset 15283 c4fd2813b127
parent 13389 24b531dcebe7
parent 15278 16f389fcd462
child 15663 d92eeb468dad
--- a/QTfrontend/game.cpp	Wed May 16 18:22:28 2018 +0200
+++ b/QTfrontend/game.cpp	Wed Jul 31 23:14:27 2019 +0200
@@ -29,6 +29,8 @@
 
 #include "hwform.h"
 #include "ui/page/pageoptions.h"
+#include "ui/page/pagetraining.h"
+#include "ui/page/pagecampaign.h"
 #include "game.h"
 #include "hwconsts.h"
 #include "gameuiconfig.h"
@@ -44,15 +46,14 @@
 // last game info
 QList<QVariant> lastGameStartArgs = QList<QVariant>();
 GameType lastGameType = gtNone;
-QString lastTrainingSubFolder = NULL;
 GameCFGWidget * lastGameCfg = NULL;
 QString lastGameAmmo = NULL;
 TeamSelWidget * lastGameTeamSel = NULL;
 
-QString training, campaign, campaignScript, campaignTeam; // TODO: Cleaner solution?
+QString trainingName, trainingScript, trainingTeam, campaign, campaignScript, campaignTeam; // TODO: Cleaner solution?
 
 HWGame::HWGame(GameUIConfig * config, GameCFGWidget * gamecfg, QString ammo, TeamSelWidget* pTeamSelWidget) :
-    TCPBase(true, 0),
+    TCPBase(true, !config->language().isEmpty(), 0),
     ammostr(ammo),
     m_pTeamSelWidget(pTeamSelWidget)
 {
@@ -75,22 +76,29 @@
 
 void HWGame::onClientDisconnect()
 {
-    switch (gameType)
+    if (demoIsPresent)
     {
-        case gtDemo:
-            // for video recording we need demo anyway
-            emit HaveRecord(rtNeither, demo);
-            break;
-        case gtNet:
-            emit HaveRecord(rtDemo, demo);
-            break;
-        default:
-            if (gameState == gsInterrupted || gameState == gsHalted)
-                emit HaveRecord(rtSave, demo);
-            else if (gameState == gsFinished)
+        switch (gameType)
+        {
+            case gtDemo:
+                // for video recording we need demo anyway
+                emit HaveRecord(rtNeither, demo);
+                break;
+            case gtNet:
                 emit HaveRecord(rtDemo, demo);
-            else
-                emit HaveRecord(rtNeither, demo);
+                break;
+            default:
+                if (gameState == gsInterrupted || gameState == gsHalted)
+                    emit HaveRecord(rtSave, demo);
+                else if (gameState == gsFinished)
+                    emit HaveRecord(rtDemo, demo);
+                else
+                    emit HaveRecord(rtNeither, demo);
+        }
+    }
+    else
+    {
+        emit HaveRecord(rtNeither, demo);
     }
     SetGameState(gsStopped);
 }
@@ -139,37 +147,196 @@
 
 void HWGame::SendQuickConfig()
 {
+    /* Load and increase Quick Game experience level.
+    Experience increases by 1 for each started game and maxes out
+    at 20. Low experience levels will introduce a "beginner's bias" to make
+    the first quick games easier and simpler. The max. possible difficulty
+    increases progressively the longer you play.
+    If experience is maxed out, the beginner's bias is gone and quick games
+    are completely random. */
+    int exp = config->quickGameExperience();
+    if(exp < 20)
+    {
+       config->setQuickGameExperience(exp + 1);
+    }
+    qDebug("Starting quick game ...");
+    qDebug("Quick Game experience level: %d", exp);
+
+    // Init stuff
     QByteArray teamscfg;
     QAbstractItemModel * themeModel = DataManager::instance().themeModel()->withoutHidden();
 
     HWProto::addStringToBuffer(teamscfg, "TL");
-    HWProto::addStringToBuffer(teamscfg, QString("etheme %1")
-                               .arg((themeModel->rowCount() > 0) ? themeModel->index(rand() % themeModel->rowCount(), 0).data(ThemeModel::ActualNameRole).toString() : "Nature"));
+
+    // Random seed
     HWProto::addStringToBuffer(teamscfg, "eseed " + QUuid::createUuid().toString());
 
-    HWProto::addStringToBuffer(teamscfg, "e$template_filter 2");
-    HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%18+4));
+    int r, minhogs, maxhogs;
 
+    // Random map type
+    r = rand() % 10000;
+    if(r < 3000) { // 30%
+        // Random
+        r = 0;
+    } else if(r < 5250) { // 22.5%
+        // Maze
+        if(exp <= 3)
+            r = 0;
+        else
+            r = 1;
+    } else if(r < 7490) { // 22.4%
+        // Perlin
+        if(exp <= 7)
+            r = 1;
+        else
+            r = 2;
+    } else if(r < 7500 && exp >= 5) { // 0.1%
+        // Floating Flowers (just for fun)
+        r = 5;
+    } else if(r < 8750) { // 12.5%
+        // Image map
+        r = 3;
+    } else { // 12.5%
+        // Forts
+        r = 4;
+    }
+    switch(r)
+    {
+        // Random map
+        default:
+        case 0: {
+            r = rand() % 3;
+            if(r == 0)
+            {
+                // small island
+                HWProto::addStringToBuffer(teamscfg, "e$template_filter 1");
+                minhogs = 3;
+                maxhogs = 4;
+            }
+            else if(r == 1 || exp <= 6)
+            {
+                // medium island
+                HWProto::addStringToBuffer(teamscfg, "e$template_filter 2");
+                minhogs = 4;
+                maxhogs = 5;
+            }
+            else
+            {
+                // cave (locked at low experience because these maps can be huge)
+                HWProto::addStringToBuffer(teamscfg, "e$template_filter 4");
+                minhogs = 4;
+                maxhogs = 6;
+            }
+            HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%18+4));
+            break;
+        }
+        // Maze
+        case 1: {
+            minhogs = 4;
+            maxhogs = 6;
+            HWProto::addStringToBuffer(teamscfg, "e$mapgen 1");
+            HWProto::addStringToBuffer(teamscfg, "e$template_filter "+QString::number(rand()%6));
+            HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%16+6));
+            break;
+        }
+        // Perlin
+        case 2: {
+            minhogs = 4;
+            maxhogs = 6;
+            HWProto::addStringToBuffer(teamscfg, "e$mapgen 2");
+            HWProto::addStringToBuffer(teamscfg, "e$template_filter "+QString::number(rand()%6));
+            HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%18+4));
+            break;
+        }
+        // Image map
+        case 3: {
+            minhogs = 4;
+            maxhogs = 6;
+            HWProto::addStringToBuffer(teamscfg, "e$mapgen 3");
+            // Select map from hardcoded list.
+            // TODO: find a more dynamic solution.
+            r = rand() % cQuickGameMaps.count();
+            HWProto::addStringToBuffer(teamscfg, "e$map " + cQuickGameMaps[r]);
+            break;
+        }
+        // Forts
+        case 4: {
+            minhogs = 4;
+            maxhogs = 6;
+            HWProto::addStringToBuffer(teamscfg, "e$mapgen 4");
+            HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%20+1));
+            break;
+        }
+        // Floating Flowers
+        // (actually empty map; this forces the engine to generate fallback structures to have
+        // something for hogs to stand on)
+        case 5: {
+            minhogs = 4;
+            maxhogs = 8;
+            HWProto::addStringToBuffer(teamscfg, "e$mapgen 3");
+            HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%4+3));
+            break;
+        }
+    }
+
+    // Theme
+    HWProto::addStringToBuffer(teamscfg, QString("etheme %1")
+        .arg((themeModel->rowCount() > 0) ? themeModel->index(rand() % themeModel->rowCount(), 0).data(ThemeModel::ActualNameRole).toString() : "Nature"));
+
+    int hogs = minhogs + rand() % (maxhogs-minhogs+1);
+    // Cap hog count at low experience
+    if((exp <= 8) && (hogs > 5))
+        hogs = 5;
+    else if((exp <= 5) && (hogs > 4))
+        hogs = 4;
+
+    // Teams
+    // Player team
     HWTeam team1;
     team1.setDifficulty(0);
     team1.setColor(0);
-    team1.setNumHedgehogs(4);
+    team1.setNumHedgehogs(hogs);
     HWNamegen::teamRandomEverything(team1);
-    team1.setVoicepack("Default");
-    HWProto::addStringListToBuffer(teamscfg,
-                                   team1.teamGameConfig(100));
+    team1.setVoicepack("Default_qau");
 
+    // Computer team
     HWTeam team2;
-    team2.setDifficulty(4);
+    // Random difficulty.
+    // Max. possible difficulty is capped at low experience levels.
+    if(exp >= 15) // very easy to very hard (full range)
+        r = 5 - rand() % 5;
+    else if(exp >= 9) // very easy to hard
+        r = 5 - rand() % 4;
+    else if(exp >= 6) // very easy to medium
+        r = 5 - rand() % 3;
+    else if(exp >= 2) // very easy to easy
+        r = 5 - rand() % 2;
+    else // very easy
+        r = 5;
+    team2.setDifficulty(r);
     team2.setColor(1);
-    team2.setNumHedgehogs(4);
+    team2.setNumHedgehogs(hogs);
+    // Make sure the team names are not equal
     do
         HWNamegen::teamRandomEverything(team2);
     while(!team2.name().compare(team1.name()) || !team2.hedgehog(0).Hat.compare(team1.hedgehog(0).Hat));
-    team2.setVoicepack("Default");
-    HWProto::addStringListToBuffer(teamscfg,
-                                   team2.teamGameConfig(100));
+    team2.setVoicepack("Default_qau");
 
+    // Team play order
+    r = rand() % 2;
+    if(r == 0 || exp <= 4) // player plays first
+    {
+        HWProto::addStringListToBuffer(teamscfg, team1.teamGameConfig(100));
+        HWProto::addStringListToBuffer(teamscfg, team2.teamGameConfig(100));
+    }
+    else // computer plays first
+    {
+        HWProto::addStringListToBuffer(teamscfg, team2.teamGameConfig(100));
+        HWProto::addStringListToBuffer(teamscfg, team1.teamGameConfig(100));
+    }
+
+    // Ammo scheme "Default"
+    // TODO: Random schemes
     HWProto::addStringToBuffer(teamscfg, QString("eammloadt %1").arg(cDefaultAmmoStore->mid(0, cAmmoNumber)));
     HWProto::addStringToBuffer(teamscfg, QString("eammprob %1").arg(cDefaultAmmoStore->mid(cAmmoNumber, cAmmoNumber)));
     HWProto::addStringToBuffer(teamscfg, QString("eammdelay %1").arg(cDefaultAmmoStore->mid(2 * cAmmoNumber, cAmmoNumber)));
@@ -184,8 +351,16 @@
 {
     QByteArray traincfg;
     HWProto::addStringToBuffer(traincfg, "TL");
+
+    HWTeam missionTeam = HWTeam();
+    missionTeam.setName(config->Form->ui.pageTraining->CBTeam->currentText());
+    missionTeam.loadFromFile();
+    missionTeam.setNumHedgehogs(HEDGEHOGS_PER_TEAM);
+    missionTeam.setMissionTeam(true);
+    HWProto::addStringListToBuffer(traincfg, missionTeam.teamGameConfig(100));
+
     HWProto::addStringToBuffer(traincfg, "eseed " + QUuid::createUuid().toString());
-    HWProto::addStringToBuffer(traincfg, "escript " + training);
+    HWProto::addStringToBuffer(traincfg, "escript " + trainingScript);
 
     RawSendIPC(traincfg);
 }
@@ -194,8 +369,15 @@
 {
     QByteArray campaigncfg;
     HWProto::addStringToBuffer(campaigncfg, "TL");
+
+    HWTeam missionTeam = HWTeam();
+    missionTeam.setName(config->Form->ui.pageCampaign->CBTeam->currentText());
+    missionTeam.loadFromFile();
+    missionTeam.setNumHedgehogs(HEDGEHOGS_PER_TEAM);
+    missionTeam.setMissionTeam(true);
+    HWProto::addStringListToBuffer(campaigncfg, missionTeam.teamGameConfig(100));
+
     HWProto::addStringToBuffer(campaigncfg, "eseed " + QUuid::createUuid().toString());
-
     HWProto::addStringToBuffer(campaigncfg, "escript " + campaignScript);
 
     RawSendIPC(campaigncfg);
@@ -277,6 +459,11 @@
             SetGameState(gsFinished);
             break;
         }
+        case 'm':
+        {
+            SetDemoPresence(false);
+            break;
+        }
         case 'H':
         {
             SetGameState(gsHalted);
@@ -307,6 +494,14 @@
                 writeCampaignVar(msg.right(msg.size() - 3));
             break;
         }
+        case 'v':
+        {
+            if (msg.at(2) == '?')
+                sendMissionVar(msg.right(msg.size() - 3));
+            else if (msg.at(2) == '!')
+                writeMissionVar(msg.right(msg.size() - 3));
+            break;
+        }
         case 'W':
         {
             // fetch new window resolution via IPC and save it in the settings
@@ -346,6 +541,20 @@
     RawSendIPC(buf);
 }
 
+void HWGame::FromNetWarning(const QString & msg)
+{
+    QByteArray buf;
+    HWProto::addStringToBuffer(buf, "s\x00" + msg + "\x20\x20");
+    RawSendIPC(buf);
+}
+
+void HWGame::FromNetError(const QString & msg)
+{
+    QByteArray buf;
+    HWProto::addStringToBuffer(buf, "s\x05" + msg + "\x20\x20");
+    RawSendIPC(buf);
+}
+
 void HWGame::onClientRead()
 {
     quint8 msglen;
@@ -400,6 +609,10 @@
     arguments << QString::number(resolutions.second.width());
     arguments << "--height";
     arguments << QString::number(resolutions.second.height());
+    if (config->zoom() != 100) {
+        arguments << "--zoom";
+        arguments << QString::number(config->zoom());
+    }
     arguments << "--raw-quality";
     arguments << QString::number(config->translateQuality());
     arguments << "--stereo";
@@ -414,6 +627,8 @@
         arguments << "--nosound";
     if (!config->isMusicEnabled())
         arguments << "--nomusic";
+    if (!config->isAudioDampenEnabled())
+        arguments << "--nodampen";
     if (!nick.isEmpty()) {
         arguments << "--nick";
         arguments << nick;
@@ -427,6 +642,8 @@
         arguments << "--no-healthtag";
     if (config->Form->ui.pageOptions->CBTagOpacity->isChecked())
         arguments << "--translucent-tags";
+    if (!config->isHolidaySillinessEnabled())
+        arguments << "--no-holiday-silliness";
     arguments << "--chat-size";
     arguments << QString::number(config->chatSize());
 
@@ -453,6 +670,19 @@
     SetGameState(gsStarted);
 }
 
+void HWGame::PlayOfficialServerDemo()
+{
+    // TODO: Use gtDemo so fast-forward is available.
+    // Needs engine support first.
+    lastGameStartArgs.clear();
+    lastGameType = gtLocal;
+
+    gameType = gtLocal;
+    demo.clear();
+    Start(false);
+    SetGameState(gsStarted);
+}
+
 void HWGame::StartNet()
 {
     lastGameStartArgs.clear();
@@ -486,16 +716,19 @@
     SetGameState(gsStarted);
 }
 
-void HWGame::StartTraining(const QString & file, const QString & subFolder)
+void HWGame::StartTraining(const QString & file, const QString & subFolder, const QString & trainTeam)
 {
     lastGameStartArgs.clear();
     lastGameStartArgs.append(file);
+    lastGameStartArgs.append(subFolder);
+    lastGameStartArgs.append(trainTeam);
     lastGameType = gtTraining;
-    lastTrainingSubFolder = subFolder;
 
     gameType = gtTraining;
 
-    training = "Missions/" + subFolder + "/" + file + ".lua";
+    trainingScript  = "Missions/" + subFolder + "/" + file + ".lua";
+    trainingName = file;
+    trainingTeam = trainTeam;
     demo.clear();
     Start(false);
     SetGameState(gsStarted);
@@ -524,14 +757,23 @@
     emit GameStateChanged(state);
     if (gameType == gtCampaign)
     {
-      emit CampStateChanged(1);
+        emit CampStateChanged(state);
+    }
+    else if (gameType == gtTraining)
+    {
+        emit TrainingStateChanged(1);
     }
 }
 
+void HWGame::SetDemoPresence(bool hasDemo)
+{
+    emit DemoPresenceChanged(hasDemo);
+}
+
 void HWGame::abort()
 {
     QByteArray buf;
-    HWProto::addStringToBuffer(buf, QString("efinish"));
+    HWProto::addStringToBuffer(buf, QString("eforcequit"));
     RawSendIPC(buf);
 }
 
@@ -560,3 +802,28 @@
     teamfile.setValue("Campaign " + campaign + "/" + varToWrite, varValue);
 }
 
+void HWGame::sendMissionVar(const QByteArray &varToSend)
+{
+    QString varToFind = QString::fromUtf8(varToSend);
+    QSettings teamfile(QString(cfgdir->absolutePath() + "/Teams/%1.hwt").arg(trainingTeam), QSettings::IniFormat, 0);
+    teamfile.setIniCodec("UTF-8");
+    QString varValue = teamfile.value("Mission " + trainingName + "/" + varToFind, "").toString();
+    QByteArray command;
+    HWProto::addStringToBuffer(command, "v." + varValue);
+    RawSendIPC(command);
+}
+
+void HWGame::writeMissionVar(const QByteArray & varVal)
+{
+    int i = varVal.indexOf(" ");
+    if(i < 0)
+        return;
+
+    QString varToWrite = QString::fromUtf8(varVal.left(i));
+    QString varValue = QString::fromUtf8(varVal.mid(i + 1));
+
+    QSettings teamfile(QString(cfgdir->absolutePath() + "/Teams/%1.hwt").arg(trainingTeam), QSettings::IniFormat, 0);
+    teamfile.setIniCodec("UTF-8");
+    teamfile.setValue("Mission " + trainingName + "/" + varToWrite, varValue);
+}
+