File indexing completed on 2024-04-21 04:02:28

0001 /*
0002     This file is part of Killbots.
0003 
0004     SPDX-FileCopyrightText: 2006-2009 Parker Coates <coates@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "mainwindow.h"
0010 
0011 #include "coordinator.h"
0012 #include "engine.h"
0013 #include "optionspage.h"
0014 #include "renderer.h"
0015 #include "rulesetselector.h"
0016 #include "scene.h"
0017 #include "settings.h"
0018 #include "view.h"
0019 
0020 #include <KGameHighScoreDialog>
0021 #include <KGameStandardAction>
0022 #include <KGameThemeProvider>
0023 #include <KGameThemeSelector>
0024 
0025 #include <KActionCollection>
0026 #include <KConfigDialog>
0027 #include <KLocalizedString>
0028 #include <KMessageBox>
0029 #include <KShortcutsDialog>
0030 #include <KStandardAction>
0031 
0032 #include <QAction>
0033 #include <QDir>
0034 #include <QIcon>
0035 #include <QTimer>
0036 
0037 #include "killbots_debug.h"
0038 
0039 Killbots::MainWindow::MainWindow(QWidget *parent)
0040     : KXmlGuiWindow(parent),
0041       m_scoreDialog(nullptr),
0042       m_rulesetChanged(false)
0043 {
0044     setAcceptDrops(false);
0045 
0046     m_coordinator = new Coordinator(this);
0047     m_coordinator->setAnimationSpeed(Settings::animationSpeed());
0048 
0049     m_engine = new Engine(m_coordinator, this);
0050     m_coordinator->setEngine(m_engine);
0051     connect(m_engine, &Engine::gameOver, this, &MainWindow::onGameOver);
0052 
0053     m_scene = new Scene(this);
0054     m_coordinator->setScene(m_scene);
0055     connect(m_scene, &Scene::clicked, m_coordinator, &Coordinator::requestAction);
0056     connect(Renderer::self()->themeProvider(), &KGameThemeProvider::currentThemeChanged, m_scene, &Scene::doLayout);
0057 
0058     m_view = new View(m_scene, this);
0059     m_view->setMinimumSize(400, 280);
0060     m_view->setWhatsThis(i18n("This is the main game area used to interact with Killbots. It shows the current state of the game grid and allows one to control the hero using the mouse. It also displays certain statistics about the game in progress."));
0061     setCentralWidget(m_view);
0062     connect(m_view, &View::sizeChanged, m_scene, &Scene::doLayout);
0063 
0064     setupActions();
0065     connect(m_engine, &Engine::teleportAllowed,       actionCollection()->action(QStringLiteral("teleport")),        &QAction::setEnabled);
0066     connect(m_engine, &Engine::teleportAllowed,       actionCollection()->action(QStringLiteral("teleport_sip")),    &QAction::setEnabled);
0067     connect(m_engine, &Engine::teleportSafelyAllowed, actionCollection()->action(QStringLiteral("teleport_safely")), &QAction::setEnabled);
0068     connect(m_engine, &Engine::vaporizerAllowed,      actionCollection()->action(QStringLiteral("vaporizer")),       &QAction::setEnabled);
0069     connect(m_engine, &Engine::waitOutRoundAllowed,   actionCollection()->action(QStringLiteral("wait_out_round")),  &QAction::setEnabled);
0070 
0071     setupGUI(Save | Create | ToolBar | Keys);
0072 
0073     // Delaying the start of the first game by 50ms to avoid resize events after
0074     // the game has been started. Delaying by 0ms doesn't seem to be enough.
0075     QTimer::singleShot(50, m_coordinator, &Coordinator::requestNewGame);
0076 }
0077 
0078 Killbots::MainWindow::~MainWindow()
0079 {
0080     Killbots::Renderer::cleanup();
0081 }
0082 
0083 void Killbots::MainWindow::configurePreferences()
0084 {
0085     // An instance of the dialog could be already created and could be cached,
0086     // in which case we want to display the cached dialog instead of creating
0087     // another one
0088     if (!KConfigDialog::showDialog(QStringLiteral("configurePreferencesDialog"))) {
0089         // Creating a dialog because KConfigDialog didn't find an instance of it
0090         KConfigDialog *configDialog = new KConfigDialog(this, QStringLiteral("configurePreferencesDialog"), Settings::self());
0091 
0092         // Creating setting pages and adding them to the dialog
0093         configDialog->addPage(new OptionsPage(this),
0094                               i18nc("@title", "General"),
0095                               QStringLiteral("configure"),
0096                               i18nc("@info", "Configure general settings")
0097                              );
0098         configDialog->addPage(new RulesetSelector(this),
0099                               i18nc("@title", "Game Type"),
0100                               QStringLiteral("games-config-custom"),
0101                               i18nc("@info", "Select a game type")
0102                              );
0103         configDialog->addPage(new KGameThemeSelector(Renderer::self()->themeProvider()),
0104                               i18nc("@title", "Appearance"),
0105                               QStringLiteral("games-config-theme"),
0106                               i18nc("@info", "Select a graphical theme")
0107                              );
0108 
0109         configDialog->setMaximumSize(800, 600);
0110 
0111         // Update the sprite style if it has changed
0112         connect(configDialog, &KConfigDialog::settingsChanged, this, &MainWindow::onSettingsChanged);
0113 
0114         configDialog->show();
0115     }
0116 }
0117 
0118 void Killbots::MainWindow::onSettingsChanged()
0119 {
0120     m_coordinator->setAnimationSpeed(Settings::animationSpeed());
0121 
0122     if (m_engine->ruleset()->fileName() != Settings::ruleset()) {
0123         qCDebug(KILLBOTS_LOG) << "Detected a changed in ruleset. From" << m_engine->ruleset()->fileName() << "to" << Settings::ruleset();
0124 
0125         // We don't act on the changed ruleset here because the config dialog
0126         // is still visible. We want the game in progress to be visible when
0127         // we display the message box. We also don't want a new game to start
0128         // while hidden behind the config dialog. So we wait until the config
0129         // dialog is closed. See onConfigDialogClosed below.
0130         m_rulesetChanged = true;
0131     }
0132     onConfigDialogClosed();
0133 }
0134 
0135 void Killbots::MainWindow::onConfigDialogClosed()
0136 {
0137     if (m_rulesetChanged) {
0138         if (!m_engine->gameHasStarted()
0139                 || KMessageBox::questionTwoActions(this,
0140                                               i18n("A new game type has been selected, but there is already a game in progress."),
0141                                               i18nc("@title:window", "Game Type Changed"),
0142                                               KGuiItem(i18nc("@action:button", "Continue Current Game")),
0143                                               KGuiItem(i18nc("@action:button", "Start a New Game"))
0144                                              ) == KMessageBox::SecondaryAction
0145            ) {
0146             m_coordinator->requestNewGame();
0147         }
0148 
0149         m_rulesetChanged = false;
0150     }
0151 }
0152 
0153 void Killbots::MainWindow::createScoreDialog()
0154 {
0155     m_scoreDialog = new KGameHighScoreDialog(KGameHighScoreDialog::Name, this);
0156     m_scoreDialog->addField(KGameHighScoreDialog::Level, i18nc("@title:column round of the game", "Round"), QStringLiteral("round"));
0157     m_scoreDialog->setModal(false);
0158     QStringList fileList;
0159     const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("killbots/ruleset"), QStandardPaths::LocateDirectory);
0160     for (const QString &dir : dirs) {
0161         const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.desktop"));
0162         for (const QString &file : fileNames) {
0163             fileList.append(dir + QLatin1Char('/') + file);
0164         }
0165     }
0166     for (const QString &fileName : std::as_const(fileList)) {
0167         const Ruleset *ruleset = Ruleset::load(fileName);
0168         if (ruleset) {
0169             m_scoreDialog->addLocalizedConfigGroupName(qMakePair(ruleset->scoreGroupKey(), ruleset->name()));
0170         }
0171         delete ruleset;
0172     }
0173 }
0174 
0175 void Killbots::MainWindow::onGameOver(int score, int round)
0176 {
0177     if (score && m_engine->ruleset()) {
0178         if (!m_scoreDialog) {
0179             createScoreDialog();
0180         }
0181 
0182         m_scoreDialog->setConfigGroup(qMakePair(m_engine->ruleset()->scoreGroupKey(), m_engine->ruleset()->name()));
0183 
0184         KGameHighScoreDialog::FieldInfo scoreEntry;
0185         scoreEntry[ KGameHighScoreDialog::Score ].setNum(score);
0186         scoreEntry[ KGameHighScoreDialog::Level ].setNum(round);
0187 
0188         if (m_scoreDialog->addScore(scoreEntry)) {
0189             QTimer::singleShot(1000, this, &MainWindow::showHighscores);
0190         }
0191     }
0192 }
0193 
0194 void Killbots::MainWindow::showHighscores()
0195 {
0196     if (!m_scoreDialog) {
0197         createScoreDialog();
0198     }
0199 
0200     // TODO: Find out why this line breaks adding high scores.
0201     //m_scoreDialog->setConfigGroup(qMakePair(m_engine->ruleset()->scoreGroupKey(), m_engine->ruleset()->name()));
0202     m_scoreDialog->exec();
0203 }
0204 
0205 QAction *Killbots::MainWindow::createMappedAction(int mapping,
0206         const QString &internalName,
0207         const QString &displayName,
0208         const QString &translatedShortcut,
0209         const int alternateShortcut,
0210         const QString &helpText,
0211         const QString &icon
0212                                                  )
0213 {
0214     QAction *action = new QAction(displayName, actionCollection());
0215     action->setObjectName(internalName);
0216     KActionCollection::setDefaultShortcuts(action, {QKeySequence(translatedShortcut), QKeySequence(alternateShortcut), QKeySequence(alternateShortcut | Qt::KeypadModifier)});
0217     if (!helpText.isEmpty()) {
0218         action->setWhatsThis(helpText);
0219         action->setToolTip(helpText);
0220     }
0221     if (!icon.isEmpty()) {
0222         action->setIcon(QIcon::fromTheme(icon));
0223     }
0224 
0225     connect(action, &QAction::triggered, this, [this, mapping] { m_coordinator->requestAction(mapping); });
0226     actionCollection()->addAction(internalName, action);
0227 
0228     return action;
0229 }
0230 
0231 void Killbots::MainWindow::setupActions()
0232 {
0233     KGameStandardAction::gameNew(m_coordinator, &Coordinator::requestNewGame, actionCollection());
0234     KGameStandardAction::highscores(this, &MainWindow::showHighscores, actionCollection());
0235     KGameStandardAction::quit(qApp, &QCoreApplication::quit, actionCollection());
0236     KStandardAction::preferences(this, &MainWindow::configurePreferences, actionCollection());
0237 
0238     createMappedAction(TeleportSafely,
0239                        QStringLiteral("teleport_safely"),
0240                        i18nc("@action", "Teleport Safely"),
0241                        i18nc("Shortcut for teleport safely. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "T"),
0242                        Qt::Key_Plus,
0243                        i18nc("@info:tooltip", "Teleport to a safe location"),
0244                        QStringLiteral("games-solve")
0245                       );
0246     createMappedAction(Teleport,
0247                        QStringLiteral("teleport"),
0248                        i18nc("@action", "Teleport"),
0249                        i18nc("Shortcut for teleport. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "R"),
0250                        Qt::Key_Minus,
0251                        i18nc("@info:tooltip", "Teleport to a random location"),
0252                        QStringLiteral("roll")
0253                       );
0254     createMappedAction(TeleportSafelyIfPossible,
0255                        QStringLiteral("teleport_sip"),
0256                        i18nc("@action", "Teleport (Safely if Possible)"),
0257                        i18nc("Shortcut for teleport safely if possible. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "Space"),
0258                        Qt::Key_0,
0259                        i18nc("@info:tooltip", "Teleport safely if that action is enabled, otherwise teleport randomly")
0260                       );
0261     createMappedAction(Vaporizer,
0262                        QStringLiteral("vaporizer"),
0263                        i18nc("@action", "Vaporizer"),
0264                        i18nc("Shortcut for vaporizer. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "F"),
0265                        Qt::Key_Period,
0266                        i18nc("@info:tooltip", "Destroy all enemies in neighboring cells"),
0267                        QStringLiteral("edit-bomb")
0268                       );
0269     createMappedAction(WaitOutRound,
0270                        QStringLiteral("wait_out_round"),
0271                        i18nc("@action", "Wait Out Round"),
0272                        i18nc("Shortcut for wait out round. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "V"),
0273                        Qt::Key_Asterisk,
0274                        i18nc("@info:tooltip", "Risk remaining in place until the end of the round for bonuses"),
0275                        QStringLiteral("process-stop")
0276                       );
0277     createMappedAction(UpLeft,
0278                        QStringLiteral("move_up_left"),
0279                        i18nc("@action", "Move Up and Left"),
0280                        i18nc("Shortcut for move up and left. https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "Q"),
0281                        Qt::Key_7
0282                       );
0283     createMappedAction(Up,
0284                        QStringLiteral("move_up"),
0285                        i18nc("@action", "Move Up"),
0286                        i18nc("Shortcut for move up. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "W"),
0287                        Qt::Key_8
0288                       );
0289     createMappedAction(UpRight,
0290                        QStringLiteral("move_up_right"),
0291                        i18nc("@action", "Move Up and Right"),
0292                        i18nc("Shortcut for move up and right. https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "E"),
0293                        Qt::Key_9
0294                       );
0295     createMappedAction(Left,
0296                        QStringLiteral("move_left"),
0297                        i18nc("@action", "Move Left"),
0298                        i18nc("Shortcut for move left. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "A"),
0299                        Qt::Key_4
0300                       );
0301     createMappedAction(Hold,
0302                        QStringLiteral("stand_still"),
0303                        i18nc("@action", "Stand Still"),
0304                        i18nc("Shortcut for stand still. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "S"),
0305                        Qt::Key_5
0306                       );
0307     createMappedAction(Right,
0308                        QStringLiteral("move_right"),
0309                        i18nc("@action", "Move Right"),
0310                        i18nc("Shortcut for move right. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "D"),
0311                        Qt::Key_6
0312                       );
0313     createMappedAction(DownLeft,
0314                        QStringLiteral("move_down_left"),
0315                        i18nc("@action", "Move Down and Left"),
0316                        i18nc("Shortcut for move down and left. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "Z"),
0317                        Qt::Key_1
0318                       );
0319     createMappedAction(Down,
0320                        QStringLiteral("move_down"),
0321                        i18nc("@action", "Move Down"),
0322                        i18nc("Shortcut for move down. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "X"),
0323                        Qt::Key_2
0324                       );
0325     createMappedAction(DownRight,
0326                        QStringLiteral("move_down_right"),
0327                        i18nc("@action", "Move Down and Right"),
0328                        i18nc("Shortcut for move down and right. https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "C"),
0329                        Qt::Key_3
0330                       );
0331 }
0332 
0333 #include "moc_mainwindow.cpp"