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 "filefilter.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  GameCreateParams::HostMode hostMode;
48  bool iwadSetExplicitly;
49  CreateServerDialog *parentDialog;
50 
51  int serverNameRow;
52  int portRow;
53  int hostModeSpacerRow;
54 
55  bool isValidExecutable(const QString &executablePath)
56  {
57  QFileInfo fileInfo(executablePath);
58  return !executablePath.isEmpty() && fileInfo.isFile();
59  }
60 };
61 
62 DPointered(GeneralGameSetupPanel)
63 
64 
66  : QWidget(parent)
67 {
68  d->setupUi(this);
69  d->hostMode = GameCreateParams::Host;
70  d->iwadSetExplicitly = false;
71  d->parentDialog = nullptr;
72 
73  d->hostExecutableInput->setAllowedExecutables(GameFile::Server);
74  d->offlineExecutableInput->setAllowedExecutables(GameFile::Offline);
75  d->remoteExecutableInput->setAllowedExecutables(GameFile::Client);
76 
77  d->formLayout->getWidgetPosition(d->leServerName, &d->serverNameRow, nullptr);
78  d->formLayout->getWidgetPosition(d->portArea, &d->portRow, nullptr);
79  d->formLayout->getWidgetPosition(d->hostModeSpacer, &d->hostModeSpacerRow, nullptr);
80 
81  this->connect(d->cboEngine, SIGNAL(currentPluginChanged(EnginePlugin*)),
82  SIGNAL(pluginChanged(EnginePlugin*)));
83 }
84 
85 GeneralGameSetupPanel::~GeneralGameSetupPanel()
86 {
87 }
88 
89 QStringList GeneralGameSetupPanel::getAllWadPaths() const
90 {
91  QStringList paths;
92  paths << d->iwadPicker->currentIwad() << d->wadsPicker->filePaths();
93  return paths;
94 }
95 
96 void GeneralGameSetupPanel::fillInParams(GameCreateParams &params)
97 {
98  params.setExecutablePath(gDoomseekerTemplatedPathResolver().resolve(pathToExe()));
99  params.setIwadPath(gDoomseekerTemplatedPathResolver().resolve(d->iwadPicker->currentIwad()));
100  params.setLoggingPath(gDoomseekerTemplatedPathResolver().resolve(d->logDirectoryPicker->validatedCurrentPath()));
101  params.setPwadsPaths(gDoomseekerTemplatedPathResolver().resolve(d->wadsPicker->filePaths()));
102  params.setPwadsOptional(d->wadsPicker->fileOptional());
103  params.setBroadcastToLan(d->cbBroadcastToLAN->isChecked());
104  params.setBroadcastToMaster(d->cbBroadcastToMaster->isChecked());
105  params.setMap(d->leMap->text());
106  params.setName(d->leServerName->text());
107  params.setPort(d->spinPort->isEnabled() ? d->spinPort->value() : 0);
108  params.setGameMode(currentGameMode());
109  params.setSkill(d->cboDifficulty->itemData(d->cboDifficulty->currentIndex()).toInt());
110  params.setUpnp(d->cbUpnp->isChecked());
111  params.setUpnpPort(d->spinUpnpPort->value());
112 }
113 
114 void GeneralGameSetupPanel::loadConfig(Ini &config, bool loadingPrevious)
115 {
116  IniSection general = config.section("General");
117 
118  // Engine
119  const EnginePlugin *prevEngine = d->currentEngine;
120  QString configEngineName = general["engine"];
121  bool engineChanged = false;
122  if (d->hostMode != GameCreateParams::Remote)
123  {
124  if (!setEngine(configEngineName))
125  return;
126  engineChanged = (prevEngine != d->currentEngine);
127  }
128 
129  // Executables
130  bool changeExecutable = engineChanged || !d->cbLockExecutable->isChecked();
131  if (d->hostMode == GameCreateParams::Remote)
132  {
133  // If we're configuring a remote game the engine cannot be changed.
134  // Don't substitute the executable when the config that is being
135  // loaded is for a different game.
136  auto *plugin = gPlugins->plugin(gPlugins->pluginIndexFromName(configEngineName));
137  if (plugin == nullptr || d->currentEngine != plugin->info())
138  changeExecutable = false;
139  }
140 
141  if (changeExecutable)
142  {
143  QString hostExecutablePath = *general["hostExecutable"];
144  if (d->isValidExecutable(hostExecutablePath))
145  d->hostExecutableInput->setPath(hostExecutablePath);
146 
147  QString offlineExecutablePath = *general["offlineExecutable"];
148  if (d->isValidExecutable(offlineExecutablePath))
149  d->offlineExecutableInput->setPath(offlineExecutablePath);
150 
151  QString remoteExecutablePath = *general["remoteExecutable"];
152  if (d->isValidExecutable(remoteExecutablePath))
153  d->remoteExecutableInput->setPath(remoteExecutablePath);
154  }
155 
156  // Other
157  d->leServerName->setText(general["name"]);
158  d->spinPort->setValue(general["port"]);
159 
160  int gameModeIndex = d->cboGamemode->findData(static_cast<gamemode_id>(general["gamemode"]));
161  if (gameModeIndex >= 0)
162  d->cboGamemode->setCurrentIndex(gameModeIndex);
163 
164  int difficultyIndex = d->cboDifficulty->findData(static_cast<int>(general["difficulty"]));
165  d->cboDifficulty->setCurrentIndex(qMax(0, difficultyIndex));
166 
167  d->leMap->setText(general["map"]);
168 
169  if (!(loadingPrevious && d->iwadSetExplicitly))
170  d->iwadPicker->addIwad(general["iwad"]);
171 
172  d->logDirectoryPicker->setLoggingEnabled(general["logEnabled"]);
173  d->logDirectoryPicker->setPathAndUpdate(general["logDir"]);
174 
175  QList<bool> optionalWads;
176  for (QString value : general["pwadsOptional"].valueString().split(";"))
177  {
178  optionalWads << (value != "0");
179  }
180  d->wadsPicker->setFilePaths(general["pwads"].valueString().split(";"), optionalWads);
181 
182  d->cbBroadcastToLAN->setChecked(general["broadcastToLAN"]);
183  d->cbBroadcastToMaster->setChecked(general["broadcastToMaster"]);
184  d->cbUpnp->setChecked(general["upnp"]);
185  d->spinUpnpPort->setValue(general["upnpPort"]);
186 
187  // Timer triggers slot after config is fully loaded.
188  QTimer::singleShot(0, this, SLOT(updateMapWarningVisibility()));
189 }
190 
191 void GeneralGameSetupPanel::saveConfig(Ini &config)
192 {
193  IniSection general = config.section("General");
194  general["engine"] = d->cboEngine->currentText();
195  general["hostExecutable"] = d->hostExecutableInput->path();
196  general["offlineExecutable"] = d->offlineExecutableInput->path();
197  general["remoteExecutable"] = d->remoteExecutableInput->path();
198  general["name"] = d->leServerName->text();
199  general["port"] = d->spinPort->value();
200  general["logDir"] = d->logDirectoryPicker->currentPath();
201  general["logEnabled"] = d->logDirectoryPicker->isLoggingEnabled();
202  general["gamemode"] = d->cboGamemode->itemData(d->cboGamemode->currentIndex()).toInt();
203  general["map"] = d->leMap->text();
204  general["difficulty"] = d->cboDifficulty->itemData(d->cboDifficulty->currentIndex()).toInt();
205  general["iwad"] = d->iwadPicker->currentIwad();
206 
207  general["pwads"] = d->wadsPicker->filePaths().join(";");
208  QList<bool> optionalWads = d->wadsPicker->fileOptional();
209  QStringList optionalList;
210  for (bool optional : optionalWads)
211  {
212  optionalList << (optional ? "1" : "0");
213  }
214  general["pwadsOptional"] = optionalList.join(";");
215 
216  general["broadcastToLAN"] = d->cbBroadcastToLAN->isChecked();
217  general["broadcastToMaster"] = d->cbBroadcastToMaster->isChecked();
218  general["upnp"] = d->cbUpnp->isChecked();
219  general["upnpPort"] = d->spinUpnpPort->value();
220 }
221 
222 void GeneralGameSetupPanel::reloadAppConfig()
223 {
224  d->hostExecutableInput->reloadExecutables();
225  d->offlineExecutableInput->reloadExecutables();
226  d->remoteExecutableInput->reloadExecutables();
227  d->iwadPicker->loadIwads();
228 }
229 
230 void GeneralGameSetupPanel::setupDifficulty(const EnginePlugin *engine)
231 {
232  QVariant oldDifficulty = d->cboDifficulty->itemData(d->cboDifficulty->currentIndex());
233  d->cboDifficulty->clear();
234 
235  QList<GameCVar> levels = engine->data()->difficulty->get(QVariant());
236  d->labelDifficulty->setVisible(!levels.isEmpty());
237  d->cboDifficulty->setVisible(!levels.isEmpty());
238  d->cboDifficulty->addItem(tr("< NONE >"), Skill::UNDEFINED);
239  for (const GameCVar &level : levels)
240  {
241  d->cboDifficulty->addItem(level.name(), level.value());
242  }
243  int memorizedIndex = d->cboDifficulty->findData(oldDifficulty);
244  if (memorizedIndex >= 0)
245  d->cboDifficulty->setCurrentIndex(memorizedIndex);
246 }
247 
248 void GeneralGameSetupPanel::setupForEngine(EnginePlugin *engine)
249 {
250  d->currentEngine = engine;
251 
252  d->cboEngine->setPluginByName(engine->data()->name);
253  d->labelIwad->setVisible(engine->data()->hasIwad);
254  d->iwadPicker->setVisible(engine->data()->hasIwad);
255  d->labelLogging->setVisible(engine->data()->allowsLogging);
256  d->logDirectoryPicker->setVisible(engine->data()->allowsLogging);
257  d->upnpArea->setVisible(engine->data()->allowsUpnp);
258  d->spinUpnpPort->setVisible(engine->data()->allowsUpnpPort);
259 
260  d->hostExecutableInput->setPlugin(engine);
261  d->offlineExecutableInput->setPlugin(engine);
262  d->remoteExecutableInput->setPlugin(engine);
263 
264  d->spinPort->setValue(d->currentEngine->data()->defaultServerPort);
265 
266  d->cboGamemode->clear();
267  d->cboGamemode->addItem(tr("< NONE >"), GameMode::SGM_Unknown);
268 
269  QList<GameMode> gameModes = d->currentEngine->gameModes();
270  for (int i = 0; i < gameModes.count(); ++i)
271  d->cboGamemode->addItem(gameModes[i].name(), gameModes[i].index());
272  setupDifficulty(engine);
273 }
274 
275 void GeneralGameSetupPanel::setupForHostMode(GameCreateParams::HostMode hostMode)
276 {
277  d->hostMode = hostMode;
278 
279  d->cboEngine->setDisabled(hostMode == GameCreateParams::Remote);
280 
281  d->hostExecutableInput->hide();
282  d->offlineExecutableInput->hide();
283  d->remoteExecutableInput->hide();
284  switch (hostMode)
285  {
286  default:
287  case GameCreateParams::Host: d->hostExecutableInput->show(); break;
288  case GameCreateParams::Offline: d->offlineExecutableInput->show(); break;
289  case GameCreateParams::Remote: d->remoteExecutableInput->show(); break;
290  }
291 
292  d->labelServerName->setVisible(hostMode == GameCreateParams::Host);
293  d->leServerName->setVisible(hostMode == GameCreateParams::Host);
294  d->labelPort->setVisible(hostMode == GameCreateParams::Host);
295  d->portArea->setVisible(hostMode == GameCreateParams::Host);
296  d->hostModeSpacer->setVisible(hostMode == GameCreateParams::Host);
297  d->broadcastOptionsArea->setVisible(hostMode == GameCreateParams::Host);
298 
299  // Insert/remove operations are necessary to get rid of extra
300  // spacing remaining when hiding form elements.
301  if (hostMode == GameCreateParams::Host)
302  {
303  d->formLayout->insertRow(d->serverNameRow, d->labelServerName, d->leServerName);
304  d->formLayout->insertRow(d->portRow, d->labelPort, d->portArea);
305  d->formLayout->insertRow(d->hostModeSpacerRow, d->hostModeSpacer,
306  static_cast<QWidget *>(nullptr));
307  }
308  else
309  {
310  d->formLayout->removeWidget(d->labelServerName);
311  d->formLayout->removeWidget(d->leServerName);
312  d->formLayout->removeWidget(d->labelPort);
313  d->formLayout->removeWidget(d->portArea);
314  d->formLayout->removeWidget(d->hostModeSpacer);
315  }
316 }
317 
318 void GeneralGameSetupPanel::setCreateServerDialog(CreateServerDialog *dialog)
319 {
320  d->parentDialog = dialog;
321 }
322 
323 void GeneralGameSetupPanel::setIwadByName(const QString &iwad)
324 {
325  d->iwadSetExplicitly = true;
326  d->iwadPicker->setIwadByName(iwad);
327 }
328 
329 void GeneralGameSetupPanel::showEvent(QShowEvent *event)
330 {
331  Q_UNUSED(event);
332  updateMapWarningVisibility();
333 }
334 
335 QString GeneralGameSetupPanel::mapName() const
336 {
337  return d->leMap->text();
338 }
339 
340 QString GeneralGameSetupPanel::pathToExe()
341 {
342  switch (d->hostMode)
343  {
344  default:
345  case GameCreateParams::Host: return d->hostExecutableInput->path();
346  case GameCreateParams::Offline: return d->offlineExecutableInput->path();
347  case GameCreateParams::Remote: return d->remoteExecutableInput->path();
348  }
349 }
350 
351 void GeneralGameSetupPanel::onGameModeChanged(int index)
352 {
353  if (index >= 0)
354  emit gameModeChanged(currentGameMode());
355 }
356 
357 GameMode GeneralGameSetupPanel::currentGameMode() const
358 {
359  QList<GameMode> gameModes = d->currentEngine->gameModes();
360  for (const GameMode &mode : gameModes)
361  {
362  if (mode.index() == d->cboGamemode->itemData(d->cboGamemode->currentIndex()).toInt())
363  return mode;
364  }
365  return GameMode::mkUnknown();
366 }
367 
368 EnginePlugin *GeneralGameSetupPanel::currentPlugin() const
369 {
370  return d->cboEngine->currentPlugin();
371 }
372 
373 bool GeneralGameSetupPanel::setEngine(const QString &engineName)
374 {
375  if (!d->cboEngine->setPluginByName(engineName))
376  {
377  QMessageBox::critical(this, tr("Doomseeker - load server config"),
378  tr("Plugin for engine \"%1\" is not present!").arg(engineName));
379  return false;
380  }
381  return true;
382 }
383 
384 void GeneralGameSetupPanel::updateMapWarningVisibility()
385 {
386  assert(d->parentDialog != nullptr);
387  MapListPanel *mapList = d->parentDialog->mapListPanel();
388  d->lblMapWarning->setVisible(mapList->hasMaps() && !mapList->isMapOnList(mapName()));
389 }