diff -r 0135e64c6c66 -r c4fd2813b127 QTfrontend/game.cpp --- 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 lastGameStartArgs = QList(); 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); +} +