generalgamesetuppanel.cpp
1 //------------------------------------------------------------------------------
2 // generalgamesetuppanel.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2014 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "generalgamesetuppanel.h"
24 #include "ui_generalgamesetuppanel.h"
25 
26 #include "configuration/doomseekerconfig.h"
27 #include "gui/createserver/hostmodetraits.h"
28 #include "gui/createserver/maplistpanel.h"
29 #include "gui/createserverdialog.h"
30 #include "ini/ini.h"
31 #include "plugins/engineplugin.h"
32 #include "plugins/pluginloader.h"
33 #include "serverapi/gamecreateparams.h"
34 #include "serverapi/gameexefactory.h"
35 #include "serverapi/gamefile.h"
36 #include "templatedpathresolver.h"
37 #include <cassert>
38 #include <QFileDialog>
39 #include <QFileInfo>
40 #include <QMessageBox>
41 #include <QTimer>
42 
43 DClass<GeneralGameSetupPanel> : public Ui::GeneralGameSetupPanel
44 {
45 public:
46  EnginePlugin *currentEngine;
47  HostModeTraits hostMode;
48  bool iwadSetExplicitly;
49  bool pwadsSetExplicitly;
50  CreateServerDialog *parentDialog;
51 
52  int demoRow;
53  int serverNameRow;
54  int portRow;
55  int hostModeSpacerRow;
56  int gamemodeRow;
57  int difficultyRow;
58  int mapRow;
59 
60  bool isValidExecutable(const QString &executablePath)
61  {
62  QFileInfo fileInfo(executablePath);
63  return !executablePath.isEmpty() && fileInfo.isFile();
64  }
65 };
66 
67 DPointered(GeneralGameSetupPanel)
68 
69 
71  : QWidget(parent)
72 {
73  d->setupUi(this);
74  d->iwadSetExplicitly = false;
75  d->pwadsSetExplicitly = false;
76  d->parentDialog = nullptr;
77 
78  d->demoPlayerExecutableInput->setAllowedExecutables(GameFile::Offline);
79  d->hostExecutableInput->setAllowedExecutables(GameFile::Server);
80  d->offlineExecutableInput->setAllowedExecutables(GameFile::Offline);
81  d->remoteExecutableInput->setAllowedExecutables(GameFile::Client);
82 
83  d->demoPicker->setTitleVisible(false);
84 
85  d->formLayout->getWidgetPosition(d->demoPicker, &d->demoRow, nullptr);
86  d->formLayout->getWidgetPosition(d->leServerName, &d->serverNameRow, nullptr);
87  d->formLayout->getWidgetPosition(d->portArea, &d->portRow, nullptr);
88  d->formLayout->getWidgetPosition(d->hostModeSpacer, &d->hostModeSpacerRow, nullptr);
89  d->formLayout->getWidgetPosition(d->cboGamemode, &d->gamemodeRow, nullptr);
90  d->formLayout->getWidgetPosition(d->cboDifficulty, &d->difficultyRow, nullptr);
91  d->formLayout->getWidgetPosition(d->mapPanel, &d->mapRow, nullptr);
92 
93  this->connect(d->cboEngine, SIGNAL(currentPluginChanged(EnginePlugin*)),
94  SIGNAL(pluginChanged(EnginePlugin*)));
95 }
96 
97 GeneralGameSetupPanel::~GeneralGameSetupPanel()
98 {
99 }
100 
101 QStringList GeneralGameSetupPanel::getAllWadPaths() const
102 {
103  QStringList paths;
104  paths << d->iwadPicker->currentIwad();
105  for (const PickedGameFile &file : d->wadsPicker->files())
106  {
107  paths << file.path;
108  }
109  return paths;
110 }
111 
112 void GeneralGameSetupPanel::fillInParams(GameCreateParams &params)
113 {
114  params.setDemoPath(gDoomseekerTemplatedPathResolver().resolve(d->demoPicker->path()));
115  params.setExecutablePath(gDoomseekerTemplatedPathResolver().resolve(pathToExe()));
116  params.setIwadPath(gDoomseekerTemplatedPathResolver().resolve(d->iwadPicker->currentIwad()));
117  params.setLoggingPath(gDoomseekerTemplatedPathResolver().resolve(d->logDirectoryPicker->validatedCurrentPath()));
118  QStringList filePaths;
119  QList<bool> optionalFiles;
120  for (const PickedGameFile &file : d->wadsPicker->files())
121  {
122  filePaths << file.path;
123  optionalFiles << file.optional;
124  }
125  params.setPwadsPaths(gDoomseekerTemplatedPathResolver().resolve(filePaths));
126  params.setPwadsOptional(optionalFiles);
127  params.setBroadcastToLan(d->cbBroadcastToLAN->isChecked());
128  params.setBroadcastToMaster(d->cbBroadcastToMaster->isChecked());
129  params.setMap(d->leMap->text());
130  params.setName(d->leServerName->text());
131  params.setPort(d->spinPort->isEnabled() ? d->spinPort->value() : 0);
132  params.setGameMode(currentGameMode());
133  params.setSkill(d->cboDifficulty->itemData(d->cboDifficulty->currentIndex()).toInt());
134  params.setUpnp(d->cbUpnp->isChecked());
135  params.setUpnpPort(d->spinUpnpPort->value());
136 }
137 
138 void GeneralGameSetupPanel::loadConfig(Ini &config, bool loadingPrevious)
139 {
140  IniSection general = config.section("General");
141 
142  // Engine
143  const EnginePlugin *prevEngine = d->currentEngine;
144  QString configEngineName = general["engine"];
145  bool engineChanged = false;
146  if (d->hostMode.canChangeGame())
147  {
148  if (!setEngine(configEngineName))
149  return;
150  engineChanged = (prevEngine != d->currentEngine);
151  }
152 
153  // Executables
154  bool changeExecutable = engineChanged || !d->cbLockExecutable->isChecked();
155  if (!d->hostMode.canChangeGame())
156  {
157  // If we can't change the engine, also don't substitute the executable
158  // when the config that is being loaded is for a different game.
159  auto *plugin = gPlugins->plugin(gPlugins->pluginIndexFromName(configEngineName));
160  if (plugin == nullptr || d->currentEngine != plugin->info())
161  changeExecutable = false;
162  }
163 
164  if (changeExecutable)
165  {
166  QString demoPlayerExecutablePath = *general["demoPlayerExecutable"];
167  if (d->isValidExecutable(demoPlayerExecutablePath))
168  d->demoPlayerExecutableInput->setPath(demoPlayerExecutablePath);
169 
170  QString hostExecutablePath = *general["hostExecutable"];
171  if (d->isValidExecutable(hostExecutablePath))
172  d->hostExecutableInput->setPath(hostExecutablePath);
173 
174  QString offlineExecutablePath = *general["offlineExecutable"];
175  if (d->isValidExecutable(offlineExecutablePath))
176  d->offlineExecutableInput->setPath(offlineExecutablePath);
177 
178  QString remoteExecutablePath = *general["remoteExecutable"];
179  if (d->isValidExecutable(remoteExecutablePath))
180  d->remoteExecutableInput->setPath(remoteExecutablePath);
181  }
182 
183  // Other
184  d->leServerName->setText(general["name"]);
185  d->spinPort->setValue(general["port"]);
186 
187  int gameModeIndex = d->cboGamemode->findData(static_cast<gamemode_id>(general["gamemode"]));
188  if (gameModeIndex >= 0)
189  d->cboGamemode->setCurrentIndex(gameModeIndex);
190 
191  int difficultyIndex = d->cboDifficulty->findData(static_cast<int>(general["difficulty"]));
192  d->cboDifficulty->setCurrentIndex(qMax(0, difficultyIndex));
193 
194  d->leMap->setText(general["map"]);
195 
196  if (!(loadingPrevious && d->iwadSetExplicitly))
197  d->iwadPicker->addIwad(general["iwad"]);
198 
199  d->logDirectoryPicker->setLoggingEnabled(general["logEnabled"]);
200  d->logDirectoryPicker->setPathAndUpdate(general["logDir"]);
201 
202  if (!(loadingPrevious && d->pwadsSetExplicitly))
203  {
204  QList<PickedGameFile> files;
205  QStringList filesPaths = general["pwads"].valueString().split(";");
206  QList<QString> optionalFilesTokens = general["pwadsOptional"].valueString().split(";");
207  for (int i = 0; i < filesPaths.length(); ++i)
208  {
209  PickedGameFile file;
210  file.path = filesPaths[i];
211  if (i < optionalFilesTokens.length())
212  {
213  file.optional = (optionalFilesTokens[i] != "0");
214  }
215  files << file;
216  }
217  d->wadsPicker->setFiles(files);
218  }
219 
220  d->cbBroadcastToLAN->setChecked(general["broadcastToLAN"]);
221  d->cbBroadcastToMaster->setChecked(general["broadcastToMaster"]);
222  d->cbUpnp->setChecked(general["upnp"]);
223  d->spinUpnpPort->setValue(general["upnpPort"]);
224 
225  // Timer triggers slot after config is fully loaded.
226  QTimer::singleShot(0, this, SLOT(updateMapWarningVisibility()));
227 }
228 
229 void GeneralGameSetupPanel::saveConfig(Ini &config)
230 {
231  IniSection general = config.section("General");
232  general["engine"] = d->cboEngine->currentText();
233  general["demoPlayerExecutable"] = d->demoPlayerExecutableInput->path();
234  general["hostExecutable"] = d->hostExecutableInput->path();
235  general["offlineExecutable"] = d->offlineExecutableInput->path();
236  general["remoteExecutable"] = d->remoteExecutableInput->path();
237  general["name"] = d->leServerName->text();
238  general["port"] = d->spinPort->value();
239  general["logDir"] = d->logDirectoryPicker->currentPath();
240  general["logEnabled"] = d->logDirectoryPicker->isLoggingEnabled();
241  general["gamemode"] = d->cboGamemode->itemData(d->cboGamemode->currentIndex()).toInt();
242  general["map"] = d->leMap->text();
243  general["difficulty"] = d->cboDifficulty->itemData(d->cboDifficulty->currentIndex()).toInt();
244  general["iwad"] = d->iwadPicker->currentIwad();
245 
246  QList<PickedGameFile> files = d->wadsPicker->files();
247  QStringList filePaths;
248  QStringList optionalFiles;
249  for (const auto &file : files)
250  {
251  filePaths << file.path;
252  optionalFiles << (file.optional ? "1" : "0");
253  }
254  general["pwads"] = filePaths.join(";");
255  general["pwadsOptional"] = optionalFiles.join(";");
256 
257  general["broadcastToLAN"] = d->cbBroadcastToLAN->isChecked();
258  general["broadcastToMaster"] = d->cbBroadcastToMaster->isChecked();
259  general["upnp"] = d->cbUpnp->isChecked();
260  general["upnpPort"] = d->spinUpnpPort->value();
261 }
262 
263 void GeneralGameSetupPanel::reloadAppConfig()
264 {
265  d->demoPlayerExecutableInput->reloadExecutables();
266  d->hostExecutableInput->reloadExecutables();
267  d->offlineExecutableInput->reloadExecutables();
268  d->remoteExecutableInput->reloadExecutables();
269  d->iwadPicker->loadIwads();
270 }
271 
272 void GeneralGameSetupPanel::setupDifficulty(const EnginePlugin *engine)
273 {
274  QVariant oldDifficulty = d->cboDifficulty->itemData(d->cboDifficulty->currentIndex());
275  d->cboDifficulty->clear();
276 
277  QList<GameCVar> levels = engine->data()->difficulty->get(QVariant());
278  d->labelDifficulty->setVisible(!levels.isEmpty());
279  d->cboDifficulty->setVisible(!levels.isEmpty());
280  d->cboDifficulty->addItem(tr("< NONE >"), Skill::UNDEFINED);
281  for (const GameCVar &level : levels)
282  {
283  d->cboDifficulty->addItem(level.name(), level.value());
284  }
285  int memorizedIndex = d->cboDifficulty->findData(oldDifficulty);
286  if (memorizedIndex >= 0)
287  d->cboDifficulty->setCurrentIndex(memorizedIndex);
288 }
289 
290 void GeneralGameSetupPanel::setupForEngine(EnginePlugin *engine)
291 {
292  d->currentEngine = engine;
293 
294  d->cboEngine->setPluginByName(engine->data()->name);
295  d->labelIwad->setVisible(engine->data()->hasIwad);
296  d->iwadPicker->setVisible(engine->data()->hasIwad);
297  d->labelLogging->setVisible(engine->data()->allowsLogging);
298  d->logDirectoryPicker->setVisible(engine->data()->allowsLogging);
299  d->upnpArea->setVisible(engine->data()->allowsUpnp);
300  d->spinUpnpPort->setVisible(engine->data()->allowsUpnpPort);
301 
302  d->demoPlayerExecutableInput->setPlugin(engine);
303  d->hostExecutableInput->setPlugin(engine);
304  d->offlineExecutableInput->setPlugin(engine);
305  d->remoteExecutableInput->setPlugin(engine);
306 
307  d->spinPort->setValue(d->currentEngine->data()->defaultServerPort);
308 
309  d->cboGamemode->clear();
310  d->cboGamemode->addItem(tr("< NONE >"), GameMode::SGM_Unknown);
311 
312  QList<GameMode> gameModes = d->currentEngine->gameModes();
313  for (int i = 0; i < gameModes.count(); ++i)
314  d->cboGamemode->addItem(gameModes[i].name(), gameModes[i].index());
315  setupDifficulty(engine);
316 }
317 
318 void GeneralGameSetupPanel::setupForHostMode(GameCreateParams::HostMode hostMode)
319 {
320  d->hostMode = hostMode;
321 
322  d->cboEngine->setDisabled(!d->hostMode.canChangeGame());
323 
324  d->demoPlayerExecutableInput->hide();
325  d->hostExecutableInput->hide();
326  d->offlineExecutableInput->hide();
327  d->remoteExecutableInput->hide();
328  switch (hostMode)
329  {
330  case GameCreateParams::Demo: d->demoPlayerExecutableInput->show(); break;
331  default:
332  case GameCreateParams::Host: d->hostExecutableInput->show(); break;
333  case GameCreateParams::Offline: d->offlineExecutableInput->show(); break;
334  case GameCreateParams::Remote: d->remoteExecutableInput->show(); break;
335  }
336 
337  const bool canSetupServer = d->hostMode.isCreatingServer() && d->hostMode.canChangeAnyGameRules();
338  d->labelServerName->setVisible(canSetupServer);
339  d->leServerName->setVisible(canSetupServer);
340  d->labelPort->setVisible(canSetupServer);
341  d->portArea->setVisible(canSetupServer);
342  d->hostModeSpacer->setVisible(canSetupServer);
343  d->broadcastOptionsArea->setVisible(canSetupServer);
344 
345  // Insert/remove operations are necessary to get rid of extra
346  // spacing remaining when hiding form elements.
347  if (canSetupServer)
348  {
349  d->formLayout->insertRow(d->serverNameRow, d->labelServerName, d->leServerName);
350  d->formLayout->insertRow(d->portRow, d->labelPort, d->portArea);
351  d->formLayout->insertRow(d->hostModeSpacerRow, d->hostModeSpacer,
352  static_cast<QWidget *>(nullptr));
353  }
354  else
355  {
356  d->formLayout->removeWidget(d->labelServerName);
357  d->formLayout->removeWidget(d->leServerName);
358  d->formLayout->removeWidget(d->labelPort);
359  d->formLayout->removeWidget(d->portArea);
360  d->formLayout->removeWidget(d->hostModeSpacer);
361  }
362 
363  d->labelDemo->setVisible(d->hostMode == GameCreateParams::Demo);
364  d->demoPicker->setVisible(d->hostMode == GameCreateParams::Demo);
365 
366  if (d->hostMode == GameCreateParams::Demo)
367  {
368  d->formLayout->insertRow(d->demoRow, d->labelDemo, d->demoPicker);
369  }
370  else
371  {
372  d->formLayout->removeWidget(d->labelDemo);
373  d->formLayout->removeWidget(d->demoPicker);
374  }
375 
376  d->labelGamemode->setVisible(d->hostMode.canChangeAnyGameRules());
377  d->cboGamemode->setVisible(d->hostMode.canChangeAnyGameRules());
378  d->labelDifficulty->setVisible(d->hostMode.canChangeAnyGameRules());;
379  d->cboDifficulty->setVisible(d->hostMode.canChangeAnyGameRules());
380  d->labelMap->setVisible(d->hostMode.canChangeAnyGameRules());
381  d->mapPanel->setVisible(d->hostMode.canChangeAnyGameRules());
382 
383  if (d->hostMode.canChangeAnyGameRules())
384  {
385  d->formLayout->insertRow(d->gamemodeRow, d->labelGamemode, d->cboGamemode);
386  d->formLayout->insertRow(d->difficultyRow, d->labelDifficulty, d->cboDifficulty);
387  d->formLayout->insertRow(d->mapRow, d->labelMap, d->mapPanel);
388  }
389  else
390  {
391  d->formLayout->removeWidget(d->labelGamemode);
392  d->formLayout->removeWidget(d->cboGamemode);
393  d->formLayout->removeWidget(d->labelDifficulty);
394  d->formLayout->removeWidget(d->cboDifficulty);
395  d->formLayout->removeWidget(d->labelMap);
396  d->formLayout->removeWidget(d->mapPanel);
397  }
398 }
399 
400 void GeneralGameSetupPanel::setCreateServerDialog(CreateServerDialog *dialog)
401 {
402  d->parentDialog = dialog;
403 }
404 
405 void GeneralGameSetupPanel::setDemoPath(const QString &demoPath)
406 {
407  d->demoPicker->setPath(demoPath);
408 }
409 
410 void GeneralGameSetupPanel::setIwadByName(const QString &iwad)
411 {
412  d->iwadSetExplicitly = true;
413  d->iwadPicker->setIwadByName(iwad);
414 }
415 
416 void GeneralGameSetupPanel::setIwadByPath(const QString &iwadPath)
417 {
418  d->iwadSetExplicitly = true;
419  d->iwadPicker->setIwadByPath(iwadPath);
420 }
421 
422 void GeneralGameSetupPanel::setPwads(const QList<PickedGameFile> &pwads)
423 {
424  d->pwadsSetExplicitly = true;
425  d->wadsPicker->setFiles(pwads);
426 }
427 
428 void GeneralGameSetupPanel::showEvent(QShowEvent *event)
429 {
430  Q_UNUSED(event);
431  updateMapWarningVisibility();
432 }
433 
434 QString GeneralGameSetupPanel::mapName() const
435 {
436  return d->leMap->text();
437 }
438 
439 QString GeneralGameSetupPanel::pathToExe()
440 {
441  switch (d->hostMode.mode)
442  {
443  case GameCreateParams::Demo: return d->demoPlayerExecutableInput->path();
444  default:
445  case GameCreateParams::Host: return d->hostExecutableInput->path();
446  case GameCreateParams::Offline: return d->offlineExecutableInput->path();
447  case GameCreateParams::Remote: return d->remoteExecutableInput->path();
448  }
449 }
450 
451 void GeneralGameSetupPanel::onGameModeChanged(int index)
452 {
453  if (index >= 0)
454  emit gameModeChanged(currentGameMode());
455 }
456 
457 GameMode GeneralGameSetupPanel::currentGameMode() const
458 {
459  QList<GameMode> gameModes = d->currentEngine->gameModes();
460  for (const GameMode &mode : gameModes)
461  {
462  if (mode.index() == d->cboGamemode->itemData(d->cboGamemode->currentIndex()).toInt())
463  return mode;
464  }
465  return GameMode::mkUnknown();
466 }
467 
468 EnginePlugin *GeneralGameSetupPanel::currentPlugin() const
469 {
470  return d->cboEngine->currentPlugin();
471 }
472 
473 bool GeneralGameSetupPanel::setEngine(const QString &engineName)
474 {
475  if (!d->cboEngine->setPluginByName(engineName))
476  {
477  QMessageBox::critical(this, tr("Doomseeker - load server config"),
478  tr("Plugin for engine \"%1\" is not present!").arg(engineName));
479  return false;
480  }
481  return true;
482 }
483 
484 void GeneralGameSetupPanel::updateMapWarningVisibility()
485 {
486  assert(d->parentDialog != nullptr);
487  MapListPanel *mapList = d->parentDialog->mapListPanel();
488  d->lblMapWarning->setVisible(mapList->hasMaps() && !mapList->isMapOnList(mapName()));
489 }