File indexing completed on 2024-10-13 03:43:40
0001 /* 0002 SPDX-FileCopyrightText: 2003 Marco Krüger <grisuji@gmx.de> 0003 SPDX-FileCopyrightText: 2003, 2009 Ian Wadham <iandw.au@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "kgrdebug.h" 0009 #include "kgrgame.h" 0010 0011 #include "kgrview.h" 0012 #include "kgrscene.h" 0013 #include "kgrselector.h" 0014 // KGoldrunner loads and plays .ogg files and requires OpenAL + SndFile > v0.21. 0015 // Fallback to Phonon by the KGameSound library does not give good results. 0016 #include <libkdegames_capabilities.h> 0017 #ifdef KGAUDIO_BACKEND_OPENAL 0018 #include "kgrsounds.h" 0019 #endif 0020 0021 #include "kgreditor.h" 0022 #include "kgrlevelplayer.h" 0023 #include "kgrdialog.h" 0024 #include "kgrgameio.h" 0025 0026 #include <iostream> 0027 #include <cstdlib> 0028 0029 #include <QByteArray> 0030 #include <QDate> 0031 #include <QDateTime> 0032 #include <QLabel> 0033 #include <QHeaderView> 0034 #include <QPushButton> 0035 #include <QSpacerItem> 0036 #include <QStandardPaths> 0037 #include <QStringList> 0038 #include <QTextStream> 0039 #include <QTimer> 0040 #include <QTreeWidget> 0041 #include <QTreeWidgetItem> 0042 #include <QVBoxLayout> 0043 #include <QFileInfo> 0044 #include <QRandomGenerator> 0045 0046 #include <KConfigGroup> 0047 #include <KGuiItem> 0048 #include <KSharedConfig> 0049 #include <KStandardGuiItem> 0050 #include <KLocalizedString> 0051 #include <KMessageBox> 0052 0053 #include "kgoldrunner_debug.h" 0054 0055 // TODO - Can we change over to KScoreDialog? 0056 0057 // Do NOT change KGoldrunner over to KScoreDialog until we have found a way 0058 // to preserve high-score data pre-existing from the KGr high-score methods. 0059 // #define USE_KSCOREDIALOG 1 // IDW - 11 Aug 07. 0060 0061 #ifdef USE_KSCOREDIALOG 0062 #include <KScoreDialog> 0063 #else 0064 0065 #endif 0066 0067 #define UserPause true 0068 #define ProgramPause false 0069 #define NewLevel true 0070 0071 /******************************************************************************/ 0072 /*********************** KGOLDRUNNER GAME CLASS *************************/ 0073 /******************************************************************************/ 0074 0075 KGrGame::KGrGame (KGrView * theView, 0076 const QString & theSystemDir, const QString & theUserDir) 0077 : 0078 QObject (theView), // Game is destroyed if view closes. 0079 levelPlayer (nullptr), 0080 recording (nullptr), 0081 playback (false), 0082 view (theView), 0083 scene (view->gameScene()), 0084 systemDataDir (theSystemDir), 0085 userDataDir (theUserDir), 0086 level (0), 0087 mainDemoName (QStringLiteral("demo")), 0088 // mainDemoName ("CM"), // IDW test. 0089 demoType (DEMO), 0090 startupDemo (false), 0091 programFreeze (false), 0092 effects (nullptr), 0093 fx (NumSounds), 0094 soundOn (false), 0095 stepsOn (false), 0096 editor (nullptr) 0097 { 0098 dbgLevel = 0; 0099 0100 gameFrozen = false; 0101 0102 dyingTimer = new QTimer (this); 0103 connect(dyingTimer, &QTimer::timeout, this, &KGrGame::finalBreath); 0104 0105 // Initialise random number generator. 0106 randomGen = new QRandomGenerator (QRandomGenerator::global()->generate()); 0107 //qCDebug(KGOLDRUNNER_LOG) << "RANDOM NUMBER GENERATOR INITIALISED"; 0108 0109 scene->setReplayMessage (i18n("Click anywhere to begin live play")); 0110 } 0111 0112 KGrGame::~KGrGame() 0113 { 0114 qDeleteAll(gameList); 0115 delete randomGen; 0116 delete levelPlayer; 0117 delete recording; 0118 #ifdef KGAUDIO_BACKEND_OPENAL 0119 delete effects; 0120 #endif 0121 } 0122 0123 // Flags to control author's debugging aids. 0124 bool KGrGame::bugFix = false; // Start game with dynamic bug-fix OFF. 0125 bool KGrGame::logging = false; // Start game with dynamic logging OFF. 0126 0127 bool KGrGame::modeSwitch (const int action, 0128 int & selectedGame, int & selectedLevel) 0129 { 0130 // If editing, check that the user has saved changes. 0131 // Note: KGoldrunner::setEditMenu disables/enables SAVE_GAME, PAUSE, 0132 // HIGH_SCORE, KILL_HERO, HINT and INSTANT_REPLAY, so they are not relevant 0133 // here. All the other GameActions require a save-check. 0134 if (! saveOK()) { 0135 return false; // The user needs to go on editing. 0136 } 0137 bool result = true; 0138 SelectAction slAction = SL_NONE; 0139 switch (action) { 0140 case NEW: 0141 slAction = SL_ANY; 0142 break; 0143 case SOLVE: 0144 slAction = SL_SOLVE; 0145 break; 0146 case SAVE_SOLUTION: 0147 slAction = SL_SAVE_SOLUTION; 0148 break; 0149 case REPLAY_ANY: 0150 slAction = SL_REPLAY; 0151 break; 0152 case LOAD: 0153 // Run the Load Game dialog. Return false if failed or cancelled. 0154 result = selectSavedGame (selectedGame, selectedLevel); 0155 break; 0156 case HIGH_SCORE: // (Disabled during demo/replay) 0157 case KILL_HERO: // (Disabled during demo/replay) 0158 case PAUSE: 0159 case HINT: 0160 return result; // If demo/replay, keep it going. 0161 break; 0162 default: 0163 break; 0164 } 0165 if (slAction != SL_NONE) { 0166 // Select game and level. Return false if failed or cancelled. 0167 result = selectGame (slAction, selectedGame, selectedLevel); 0168 } 0169 if (playback && (result == true)) { 0170 setPlayback (false); // If demo/replay, kill it. 0171 } 0172 return result; 0173 } 0174 0175 void KGrGame::gameActions (const int action) 0176 { 0177 int selectedGame = gameIndex; 0178 int selectedLevel = level; 0179 // For some actions, modeSwitch() calls the game and level selection dialog. 0180 if (! modeSwitch (action, selectedGame, selectedLevel)) { 0181 return; 0182 } 0183 switch (action) { 0184 case NEW: 0185 newGame (selectedLevel, selectedGame); 0186 showTutorialMessages (level); 0187 break; 0188 case NEXT_LEVEL: 0189 if (level >= levelMax) { 0190 KGrMessage::information (view, i18n ("Play Next Level"), 0191 i18n ("There are no more levels in this game.")); 0192 return; 0193 } 0194 level++; 0195 //qCDebug(KGOLDRUNNER_LOG) << "Game" << gameList.at(gameIndex)->name << "level" << level; 0196 newGame (level, gameIndex); 0197 showTutorialMessages (level); 0198 break; 0199 case LOAD: 0200 loadGame (selectedGame, selectedLevel); 0201 break; 0202 case SAVE_GAME: 0203 saveGame(); 0204 break; 0205 case PAUSE: 0206 freeze (UserPause, (! gameFrozen)); 0207 break; 0208 case HIGH_SCORE: 0209 // Stop the action during the high-scores dialog. 0210 freeze (ProgramPause, true); 0211 showHighScores(); 0212 freeze (ProgramPause, false); 0213 break; 0214 case KILL_HERO: 0215 if (levelPlayer && (! playback)) { 0216 // Record the KILL_HERO code, then emit signal endLevel (DEAD). 0217 levelPlayer->killHero(); 0218 } 0219 break; 0220 case HINT: 0221 showHint(); 0222 break; 0223 case DEMO: 0224 setPlayback (true); 0225 demoType = DEMO; 0226 if (! startDemo (SYSTEM, mainDemoName, 1)) { 0227 setPlayback (false); 0228 } 0229 break; 0230 case SOLVE: 0231 runReplay (SOLVE, selectedGame, selectedLevel); 0232 break; 0233 case SAVE_SOLUTION: 0234 saveSolution (gameList.at (selectedGame)->prefix, selectedLevel); 0235 break; 0236 case INSTANT_REPLAY: 0237 if (levelPlayer) { 0238 startInstantReplay(); 0239 } 0240 break; 0241 case REPLAY_LAST: 0242 replayLastLevel(); 0243 break; 0244 case REPLAY_ANY: 0245 runReplay (REPLAY_ANY, selectedGame, selectedLevel); 0246 break; 0247 default: 0248 break; 0249 } 0250 } 0251 0252 void KGrGame::editActions (const int action) 0253 { 0254 bool editOK = true; 0255 bool newEditor = (editor) ? false : true; 0256 int editLevel = level; 0257 // dbk << "Level" << level << prefix << gameIndex; 0258 if (newEditor) { 0259 if (action == SAVE_EDITS) { 0260 KGrMessage::information (view, i18n ("Save Level"), 0261 i18n ("Inappropriate action: you are not editing a level.")); 0262 return; 0263 } 0264 0265 // If there is no editor running, start one. 0266 freeze (ProgramPause, true); 0267 editor = new KGrEditor (view, systemDataDir, userDataDir, gameList); 0268 Q_EMIT setEditMenu (true); // Enable edit menu items and toolbar. 0269 } 0270 0271 switch (action) { 0272 case CREATE_LEVEL: 0273 editOK = editor->createLevel (gameIndex); 0274 break; 0275 case EDIT_ANY: 0276 editOK = editor->updateLevel (gameIndex, editLevel); 0277 break; 0278 case SAVE_EDITS: 0279 editOK = editor->saveLevelFile (); 0280 break; 0281 case MOVE_LEVEL: 0282 editOK = editor->moveLevelFile (gameIndex, editLevel); 0283 break; 0284 case DELETE_LEVEL: 0285 editOK = editor->deleteLevelFile (gameIndex, editLevel); 0286 break; 0287 case CREATE_GAME: 0288 editOK = editor->editGame (-1); 0289 break; 0290 case EDIT_GAME: 0291 editOK = editor->editGame (gameIndex); 0292 break; 0293 default: 0294 break; 0295 } 0296 0297 if (newEditor) { 0298 if (editOK) { 0299 // If a level or demo is running, close it, with no win/lose result. 0300 setPlayback (false); 0301 if (levelPlayer) { 0302 endLevel (NORMAL); 0303 scene->deleteAllSprites(); 0304 } 0305 0306 Q_EMIT showLives (0); 0307 Q_EMIT showScore (0); 0308 } 0309 else { 0310 // Edit failed or was cancelled, so close the editor. 0311 Q_EMIT setEditMenu (false); // Disable edit menu items and toolbar. 0312 delete editor; 0313 editor = nullptr; 0314 } 0315 freeze (ProgramPause, false); 0316 } 0317 0318 if (! editOK) { 0319 return; // Continue play, demo or previous edit. 0320 } 0321 0322 int game = gameIndex, lev = level; 0323 editor->getGameAndLevel (game, lev); 0324 0325 if (((game != gameIndex) || (lev != level)) && (lev != 0)) { 0326 gameIndex = game; 0327 prefix = gameList.at (gameIndex)->prefix; 0328 level = lev; 0329 0330 //qCDebug(KGOLDRUNNER_LOG) << "Saving to KConfigGroup"; 0331 KConfigGroup gameGroup (KSharedConfig::openConfig(), QStringLiteral("KDEGame")); 0332 gameGroup.writeEntry (QStringLiteral("GamePrefix"), prefix); 0333 gameGroup.writeEntry (QStringLiteral("Level_") + prefix, level); 0334 gameGroup.sync(); // Ensure that the entry goes to disk. 0335 } 0336 } 0337 0338 void KGrGame::editToolbarActions (const int action) 0339 { 0340 // If game-editor is inactive or action-code is not recognised, do nothing. 0341 if (editor) { 0342 switch (action) { 0343 case EDIT_HINT: 0344 // Edit the level-name or hint. 0345 editor->editNameAndHint(); 0346 break; 0347 case FREE: 0348 case ENEMY: 0349 case HERO: 0350 case CONCRETE: 0351 case BRICK: 0352 case FBRICK: 0353 case HLADDER: 0354 case LADDER: 0355 case NUGGET: 0356 case BAR: 0357 // Set the next object to be painted in the level-layout. 0358 editor->setEditObj (action); 0359 break; 0360 default: 0361 break; 0362 } 0363 } 0364 } 0365 0366 void KGrGame::settings (const int action) 0367 { 0368 // TODO - Bad - Configure Keys does not pause a demo. IDW 0369 KConfigGroup gameGroup (KSharedConfig::openConfig(), QStringLiteral("KDEGame")); 0370 bool onOff = false; 0371 switch (action) { 0372 case PLAY_SOUNDS: 0373 case PLAY_STEPS: 0374 toggleSoundsOnOff (action); 0375 break; 0376 case STARTUP_DEMO: 0377 // Toggle the startup demo to be on or off. 0378 onOff = (! gameGroup.readEntry ("StartingDemo", true)); 0379 gameGroup.writeEntry ("StartingDemo", onOff); 0380 break; 0381 case MOUSE: 0382 case KEYBOARD: 0383 case LAPTOP: 0384 setControlMode (action); 0385 gameGroup.writeEntry ("ControlMode", action); 0386 break; 0387 case CLICK_KEY: 0388 case HOLD_KEY: 0389 if (controlMode == KEYBOARD) { 0390 setHoldKeyOption (action); 0391 gameGroup.writeEntry ("HoldKeyOption", action); 0392 } 0393 break; 0394 case NORMAL_SPEED: 0395 case BEGINNER_SPEED: 0396 case CHAMPION_SPEED: 0397 setTimeScale (action); 0398 gameGroup.writeEntry ("SpeedLevel", action); 0399 gameGroup.writeEntry ("ActualSpeed", timeScale); 0400 break; 0401 case INC_SPEED: 0402 case DEC_SPEED: 0403 setTimeScale (action); 0404 gameGroup.writeEntry ("ActualSpeed", timeScale); 0405 break; 0406 default: 0407 break; 0408 } 0409 gameGroup.sync(); 0410 } 0411 0412 void KGrGame::setInitialTheme (const QString & themeFilepath) 0413 { 0414 initialThemeFilepath = themeFilepath; 0415 } 0416 0417 void KGrGame::initGame() 0418 { 0419 #ifndef KGAUDIO_BACKEND_OPENAL 0420 KGrMessage::information (view, i18n ("No Sound"), 0421 i18n ("Warning: This copy of KGoldrunner has no sound.\n" 0422 "\n" 0423 "This is because no development versions of the OpenAL and " 0424 "SndFile libraries were present when it was compiled and built."), 0425 "WarningNoSound"); 0426 #endif 0427 //qCDebug(KGOLDRUNNER_LOG) << "Entered, draw the initial graphics now ..."; 0428 0429 // Get the most recent collection and level that was played by this user. 0430 // If he/she has never played before, set it to Tutorial, level 1. 0431 KConfigGroup gameGroup (KSharedConfig::openConfig(), QStringLiteral("KDEGame")); 0432 QString prevGamePrefix = gameGroup.readEntry ("GamePrefix", "tute"); 0433 int prevLevel = gameGroup.readEntry (QLatin1String("Level_") + prevGamePrefix, 1); 0434 0435 //qCDebug(KGOLDRUNNER_LOG)<< "Config() Game and Level" << prevGamePrefix << prevLevel; 0436 0437 // Use that game and level, if it is among the current games. 0438 // Otherwise, use the first game in the list and level 1. 0439 gameIndex = 0; 0440 level = 1; 0441 int n = 0; 0442 dbk1 << gameIndex << level << "Search:" << prevGamePrefix << prevLevel; 0443 for (KGrGameData * gameData : std::as_const(gameList)) { 0444 dbk1 << "Trying:" << n << gameData->prefix; 0445 if (gameData->prefix == prevGamePrefix) { 0446 gameIndex = n; 0447 level = prevLevel; 0448 dbk1 << "FOUND:" << gameIndex << prevGamePrefix << level; 0449 break; 0450 } 0451 n++; 0452 } 0453 0454 // Set control-mode, hold-key option (for when K/B is used) and game-speed. 0455 settings (gameGroup.readEntry ("ControlMode", (int) MOUSE)); 0456 Q_EMIT setToggle (((controlMode == MOUSE) ? QStringLiteral("mouse_mode") : 0457 ((controlMode == KEYBOARD) ? QStringLiteral("keyboard_mode") : 0458 QStringLiteral("laptop_mode"))), true); 0459 0460 holdKeyOption = gameGroup.readEntry ("HoldKeyOption", (int) CLICK_KEY); 0461 Q_EMIT setToggle (((holdKeyOption == CLICK_KEY) ? QStringLiteral("click_key") : 0462 QStringLiteral("hold_key")), true); 0463 0464 int speedLevel = gameGroup.readEntry ("SpeedLevel", (int) NORMAL_SPEED); 0465 settings (speedLevel); 0466 Q_EMIT setToggle ((speedLevel == NORMAL_SPEED) ? QStringLiteral("normal_speed") : 0467 ((speedLevel == BEGINNER_SPEED) ? QStringLiteral("beginner_speed") : 0468 QStringLiteral("champion_speed")), true); 0469 timeScale = gameGroup.readEntry ("ActualSpeed", 10); 0470 0471 #ifdef KGAUDIO_BACKEND_OPENAL 0472 // Set up sounds, if required in config. 0473 soundOn = gameGroup.readEntry ("Sound", false); 0474 //qCDebug(KGOLDRUNNER_LOG) << "Sound" << soundOn; 0475 if (soundOn) { 0476 loadSounds(); 0477 effects->setMuted (false); 0478 } 0479 Q_EMIT setToggle (QStringLiteral("options_sounds"), soundOn); 0480 0481 stepsOn = gameGroup.readEntry ("StepSounds", false); 0482 //qCDebug(KGOLDRUNNER_LOG) << "StepSounds" << stepsOn; 0483 Q_EMIT setToggle (QStringLiteral("options_steps"), stepsOn); 0484 #endif 0485 0486 dbk1 << "Owner" << gameList.at (gameIndex)->owner 0487 << gameList.at (gameIndex)->name << level; 0488 0489 setPlayback (gameGroup.readEntry ("StartingDemo", true)); 0490 if (playback && (startDemo (SYSTEM, mainDemoName, 1))) { 0491 startupDemo = true; // Demo is starting. 0492 demoType = DEMO; 0493 } 0494 else { 0495 setPlayback (false); // Load previous session's level. 0496 newGame (level, gameIndex); 0497 quickStartDialog(); 0498 } 0499 Q_EMIT setToggle (QStringLiteral("options_demo"), startupDemo); 0500 0501 // Allow a short break, to display the graphics, then use the demo delay-time 0502 // or the reaction-time to the quick-start dialog to do some more rendering. 0503 QTimer::singleShot (10, scene, &KGrScene::preRenderSprites); 0504 0505 } // End KGrGame::initGame() 0506 0507 bool KGrGame::getRecordingName (const QString & dir, const QString & pPrefix, 0508 QString & filename) 0509 { 0510 QString recFile = dir + QLatin1String("rec_") + pPrefix + QLatin1String(".txt"); 0511 QFileInfo fileInfo (recFile); 0512 bool recOK = fileInfo.exists() && fileInfo.isReadable(); 0513 filename = QString (); 0514 0515 if (demoType == SOLVE) { 0516 // Look for a solution-file name in User or System area. 0517 QString solFile = dir + QLatin1String("sol_") + pPrefix + QLatin1String(".txt"); 0518 fileInfo.setFile (solFile); 0519 bool solOK = fileInfo.exists() && fileInfo.isReadable(); 0520 if (solOK) { 0521 filename = solFile; // Accept sol_* in User or System area. 0522 return true; 0523 } 0524 else if (recOK && (dir == systemDataDir)) { 0525 filename = recFile; // Accept rec_* (old name) in System area only. 0526 return true; 0527 } 0528 } 0529 else if (recOK) { 0530 filename = recFile; // Accept rec_* name for demo or recording. 0531 return true; 0532 } 0533 // File not found or not readable. 0534 return false; 0535 } 0536 0537 bool KGrGame::startDemo (const Owner demoOwner, const QString & pPrefix, 0538 const int levelNo) 0539 { 0540 // Find the relevant file and the list of levels it contains. 0541 QString dir = (demoOwner == SYSTEM) ? systemDataDir : userDataDir; 0542 QString filepath; 0543 if (! getRecordingName (dir, pPrefix, filepath)) { 0544 qCDebug(KGOLDRUNNER_LOG) << "No file found by getRecordingName() for" << dir << pPrefix; 0545 return false; 0546 } 0547 dbk1 << "Owner" << demoOwner << "type" << demoType 0548 << pPrefix << levelNo << "filepath" << filepath; 0549 KConfig config (filepath, KConfig::SimpleConfig); 0550 QStringList demoList = config.groupList(); 0551 dbk1 << "DEMO LIST" << demoList.count() << demoList; 0552 0553 // Find the required level (e.g. CM007) in the list available on the file. 0554 QString s = pPrefix + QString::number(levelNo).rightJustified(3,QLatin1Char('0')); 0555 int index = demoList.indexOf (s) + 1; 0556 dbk1 << "DEMO looking for" << s << "found at" << index; 0557 if (index <= 0) { 0558 setPlayback (false); 0559 qCDebug(KGOLDRUNNER_LOG) << "DEMO not found in" << filepath << s << pPrefix << levelNo; 0560 return false; 0561 } 0562 0563 // Load and run the recorded level(s). 0564 if (playLevel (demoOwner, pPrefix, levelNo, (! NewLevel))) { 0565 playbackOwner = demoOwner; 0566 playbackPrefix = pPrefix; 0567 playbackIndex = levelNo; 0568 0569 // Play back all levels in Main Demo or just one level in other replays. 0570 playbackMax = ((demoType == DEMO) && (playbackPrefix == mainDemoName)) ? 0571 demoList.count() : levelNo; 0572 if (levelPlayer) { 0573 levelPlayer->prepareToPlay(); 0574 } 0575 qCDebug(KGOLDRUNNER_LOG) << "DEMO started ..." << filepath << pPrefix << levelNo; 0576 return true; 0577 } 0578 else { 0579 setPlayback (false); 0580 qCDebug(KGOLDRUNNER_LOG) << "DEMO failed ..." << filepath << pPrefix << levelNo; 0581 return false; 0582 } 0583 } 0584 0585 void KGrGame::runNextDemoLevel() 0586 { 0587 // dbk << "index" << playbackIndex << "max" << playbackMax << playbackPrefix 0588 // << "owner" << playbackOwner; 0589 if (playbackIndex < playbackMax) { 0590 playbackIndex++; 0591 if (playLevel (playbackOwner, playbackPrefix, 0592 playbackIndex, (! NewLevel))) { 0593 if (levelPlayer) { 0594 levelPlayer->prepareToPlay(); 0595 } 0596 qCDebug(KGOLDRUNNER_LOG) << "DEMO continued ..." << playbackPrefix << playbackIndex; 0597 return; 0598 } 0599 } 0600 finishDemo(); 0601 } 0602 0603 void KGrGame::finishDemo() 0604 { 0605 setPlayback (false); 0606 newGame (level, gameIndex); 0607 if (startupDemo) { 0608 // The startup demo was running, so run the Quick Start Dialog now. 0609 startupDemo = false; 0610 quickStartDialog(); 0611 } 0612 else { 0613 // Otherwise, run the last level used. 0614 showTutorialMessages (level); 0615 } 0616 } 0617 0618 void KGrGame::interruptDemo() 0619 { 0620 qCDebug(KGOLDRUNNER_LOG) << "DEMO interrupted ..."; 0621 if ((demoType == INSTANT_REPLAY) || (demoType == REPLAY_LAST)) { 0622 setPlayback (false); 0623 levelMax = gameList.at (gameIndex)->nLevels; 0624 freeze (UserPause, true); 0625 KGrMessage::information (view, i18n ("Game Paused"), 0626 i18n ("The replay has stopped and the game is pausing while you " 0627 "prepare to go on playing. Please press the Pause key " 0628 "(default P or Esc) when you are ready."), 0629 QStringLiteral("Show_interruptDemo")); 0630 } 0631 else { 0632 finishDemo(); // Initial DEMO, main DEMO or SOLVE. 0633 } 0634 } 0635 0636 void KGrGame::startInstantReplay() 0637 { 0638 // dbk << "Start INSTANT_REPLAY"; 0639 demoType = INSTANT_REPLAY; 0640 0641 // Terminate current play. 0642 delete levelPlayer; 0643 levelPlayer = nullptr; 0644 scene->deleteAllSprites(); 0645 0646 // Redisplay the starting score and lives. 0647 lives = recording->lives; 0648 Q_EMIT showLives (lives); 0649 score = recording->score; 0650 Q_EMIT showScore (score); 0651 0652 // Restart the level in playback mode. 0653 setPlayback (true); 0654 setupLevelPlayer(); 0655 levelPlayer->prepareToPlay(); 0656 } 0657 0658 void KGrGame::replayLastLevel() 0659 { 0660 // Replay the last game and level completed by the player. 0661 KConfigGroup gameGroup (KSharedConfig::openConfig(), QStringLiteral("KDEGame")); 0662 QString lastPrefix = gameGroup.readEntry ("LastGamePrefix", ""); 0663 int lastLevel = gameGroup.readEntry ("LastLevel", -1); 0664 0665 if (lastLevel > 0) { 0666 setPlayback (true); 0667 demoType = REPLAY_LAST; 0668 if (! startDemo (USER, lastPrefix, lastLevel)) { 0669 setPlayback (false); 0670 KGrMessage::information (view, i18n ("Replay Last Level"), 0671 i18n ("ERROR: Could not find and replay a recording of " 0672 "the last level you played.")); 0673 } 0674 } 0675 else { 0676 KGrMessage::information (view, i18n ("Replay Last Level"), 0677 i18n ("There is no last level to replay. You need to play a " 0678 "level to completion, win or lose, before you can use " 0679 "the Replay Last Level action.")); 0680 } 0681 } 0682 0683 /******************************************************************************/ 0684 /********************** QUICK-START DIALOG AND SLOTS ************************/ 0685 /******************************************************************************/ 0686 0687 void KGrGame::quickStartDialog() 0688 { 0689 // Make sure the game does not start during the Quick Start dialog. 0690 freeze (ProgramPause, true); 0691 0692 qs = new QDialog (view); 0693 0694 // Modal dialog, 4 buttons, vertically: the PLAY button has the focus. 0695 qs->setWindowTitle (i18nc("@title:window", "Quick Start")); 0696 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 0697 0698 QPushButton *newGameButton = new QPushButton; 0699 buttonBox->addButton(newGameButton, QDialogButtonBox::ActionRole); 0700 QPushButton *useMenuButton = new QPushButton; 0701 buttonBox->addButton(useMenuButton, QDialogButtonBox::ActionRole); 0702 0703 QHBoxLayout *leftIconRightButtonsLayout = new QHBoxLayout; 0704 qs->setLayout(leftIconRightButtonsLayout); 0705 0706 QVBoxLayout *leftButtonLayout = new QVBoxLayout; 0707 buttonBox->button(QDialogButtonBox::Ok)->setFocus(); 0708 leftButtonLayout->addWidget(buttonBox); 0709 buttonBox->setOrientation (Qt::Vertical); 0710 0711 // Set up the PLAY button. 0712 buttonBox->button (QDialogButtonBox::Ok)->setText(i18nc ("Button text: start playing a game", "&PLAY")); 0713 buttonBox->button (QDialogButtonBox::Ok)->setToolTip(i18n ("Start playing this level")); 0714 buttonBox->button (QDialogButtonBox::Ok)->setWhatsThis( 0715 i18n ("Set up to start playing the game and level being shown, " 0716 "as soon as you click, move the mouse or press a key")); 0717 0718 // Set up the Quit button. 0719 buttonBox->button (QDialogButtonBox::Cancel)->setText(i18n ("&Quit")); 0720 buttonBox->button (QDialogButtonBox::Cancel)->setToolTip(i18n ("Close KGoldrunner")); 0721 0722 // Set up the New Game button. 0723 newGameButton->setText(i18n ("&New Game...")); 0724 newGameButton->setToolTip (i18n ("Start a different game or level")); 0725 newGameButton->setWhatsThis( 0726 i18n ("Use the Select Game dialog box to choose a " 0727 "different game or level and start playing it")); 0728 0729 // Set up the Use Menu button. 0730 useMenuButton->setText(i18n ("&Use Menu")); 0731 useMenuButton->setToolTip( 0732 i18n ("Use the menus to choose other actions")); 0733 useMenuButton->setWhatsThis( 0734 i18n ("Before playing, use the menus to choose other actions, " 0735 "such as loading a saved game or changing the theme")); 0736 0737 // Add the KGoldrunner application icon to the dialog box. 0738 QLabel * logo = new QLabel(); 0739 QIcon test = QIcon::fromTheme(QStringLiteral("kgoldrunner")); 0740 logo->setPixmap(test.pixmap(240)); 0741 logo->setAlignment (Qt::AlignTop | Qt::AlignHCenter); 0742 logo->setAlignment (Qt::AlignTop | Qt::AlignHCenter | Qt::AlignLeft); 0743 0744 leftIconRightButtonsLayout->addWidget(logo); 0745 leftIconRightButtonsLayout->addLayout(leftButtonLayout); 0746 0747 connect(buttonBox, &QDialogButtonBox::accepted, this, &KGrGame::quickStartPlay); 0748 connect(buttonBox, &QDialogButtonBox::rejected, this, &KGrGame::quickStartQuit); 0749 connect(newGameButton, &QPushButton::clicked, this, &KGrGame::quickStartNewGame); 0750 connect(useMenuButton, &QPushButton::clicked, this, &KGrGame::quickStartUseMenu); 0751 0752 qs->show(); 0753 } 0754 0755 void KGrGame::quickStartPlay() 0756 { 0757 // KDialog calls QDialog::accept() after the OK slot, so must hide it 0758 // now, to avoid interference with any tutorial messages there may be. 0759 qs->hide(); 0760 freeze (ProgramPause, false); 0761 showTutorialMessages (level); // Level has already been loaded. 0762 } 0763 0764 void KGrGame::quickStartNewGame() 0765 { 0766 qs->accept(); 0767 freeze (ProgramPause, false); 0768 0769 int selectedGame = gameIndex; 0770 int selectedLevel = level; 0771 if (modeSwitch (NEW, selectedGame, selectedLevel)) { 0772 newGame (selectedLevel, selectedGame); 0773 } 0774 showTutorialMessages (selectedLevel); 0775 } 0776 0777 void KGrGame::quickStartUseMenu() 0778 { 0779 qs->accept(); 0780 freeze (ProgramPause, false); 0781 freeze (UserPause, true); 0782 KGrMessage::information (view, i18n ("Game Paused"), 0783 i18n ("The game is halted. You will need to press the Pause key " 0784 "(default P or Esc) when you are ready to play.")); 0785 0786 if (levelPlayer) { 0787 levelPlayer->prepareToPlay(); 0788 } 0789 } 0790 0791 void KGrGame::quickStartQuit() 0792 { 0793 Q_EMIT quitGame(); 0794 } 0795 0796 /******************************************************************************/ 0797 /************************* LEVEL SELECTION PROCEDURE ************************/ 0798 /******************************************************************************/ 0799 0800 bool KGrGame::selectGame (const SelectAction slAction, 0801 int & selectedGame, int & selectedLevel) 0802 { 0803 // Halt the game during the dialog. 0804 freeze (ProgramPause, true); 0805 0806 // Run the game and level selection dialog. 0807 KGrSLDialog * sl = new KGrSLDialog (slAction, selectedLevel, selectedGame, 0808 gameList, systemDataDir, userDataDir, 0809 view); 0810 bool selected = sl->selectLevel (selectedGame, selectedLevel); 0811 delete sl; 0812 0813 //qCDebug(KGOLDRUNNER_LOG) << "After dialog - programFreeze" << programFreeze; 0814 //qCDebug(KGOLDRUNNER_LOG) << "selected" << selected << "gameFrozen" << gameFrozen; 0815 //qCDebug(KGOLDRUNNER_LOG) << "selectedGame" << selectedGame 0816 // << "prefix" << gameList.at(selectedGame)->prefix 0817 // << "selectedLevel" << selectedLevel; 0818 // Unfreeze the game, but only if it was previously unfrozen. 0819 freeze (ProgramPause, false); 0820 return selected; 0821 } 0822 0823 void KGrGame::runReplay (const int action, 0824 const int selectedGame, const int selectedLevel) 0825 { 0826 if (action == SOLVE) { 0827 setPlayback (true); 0828 demoType = SOLVE; 0829 if (startDemo // Has the user saved a solution to this level? 0830 (USER, gameList.at (selectedGame)->prefix, selectedLevel)) { 0831 } 0832 else { // If not, look for a released solution. 0833 setPlayback (true); // Set playback again (startDemo() cleared it). 0834 if (! startDemo 0835 (SYSTEM, gameList.at (selectedGame)->prefix, selectedLevel)) { 0836 KGrMessage::information (view, i18n ("Show a Solution"), 0837 i18n ("Sorry, although all levels of KGoldrunner can be " 0838 "solved, no solution has been recorded yet for the " 0839 "level you selected."), QStringLiteral("Show_noSolutionRecorded")); 0840 } 0841 } 0842 } 0843 else if (action == REPLAY_ANY) { 0844 setPlayback (true); 0845 demoType = REPLAY_ANY; 0846 if (! startDemo 0847 (USER, gameList.at (selectedGame)->prefix, selectedLevel)) { 0848 KGrMessage::information (view, i18n ("Replay Any Level"), 0849 i18n ("Sorry, you do not seem to have played and recorded " 0850 "the selected level before."), QStringLiteral("Show_noReplay")); 0851 } 0852 } 0853 } 0854 0855 /******************************************************************************/ 0856 /*************************** MAIN GAME PROCEDURES ***************************/ 0857 /******************************************************************************/ 0858 0859 void KGrGame::newGame (const int lev, const int newGameIndex) 0860 { 0861 scene->goToBlack(); 0862 0863 KGrGameData * gameData = gameList.at (newGameIndex); 0864 level = lev; 0865 gameIndex = newGameIndex; 0866 owner = gameData->owner; 0867 prefix = gameData->prefix; 0868 levelMax = gameData->nLevels; 0869 0870 lives = 5; // Start with 5 lives. 0871 score = 0; 0872 startScore = 0; 0873 0874 Q_EMIT showLives (lives); 0875 Q_EMIT showScore (score); 0876 0877 playLevel (owner, prefix, level, NewLevel); 0878 } 0879 0880 bool KGrGame::playLevel (const Owner fileOwner, const QString & prefix, 0881 const int levelNo, const bool newLevel) 0882 { 0883 // If the game-editor is active, terminate it. 0884 if (editor) { 0885 Q_EMIT setEditMenu (false); // Disable edit menu items and toolbar. 0886 delete editor; 0887 editor = nullptr; 0888 } 0889 0890 // If there is a level being played, kill it, with no win/lose result. 0891 if (levelPlayer) { 0892 endLevel (NORMAL); 0893 } 0894 0895 // Clean up any sprites remaining from a previous level. This is done late, 0896 // so that the player has a little time to observe how the level ended. 0897 scene->deleteAllSprites(); 0898 0899 // Set up to record or play back: load either level-data or recording-data. 0900 if (! initRecordingData (fileOwner, prefix, levelNo, playback)) { 0901 return false; 0902 } 0903 else if (playback) { 0904 // Set up and display the starting score and lives. 0905 lives = recording->lives; 0906 Q_EMIT showLives (lives); 0907 score = recording->score; 0908 Q_EMIT showScore (score); 0909 } 0910 0911 scene->setLevel (levelNo); // Switch and render background if reqd. 0912 scene->fadeIn (true); // Then run the fade-in animation. 0913 startScore = score; // The score we will save, if asked. 0914 0915 // Create a level player, initialised and ready for play or replay to start. 0916 setupLevelPlayer(); 0917 0918 levelName = recording->levelName; 0919 levelHint = recording->hint; 0920 0921 // Indicate on the menus whether there is a hint for this level. 0922 Q_EMIT hintAvailable (levelHint.length() > 0); 0923 0924 // Re-draw the playfield frame, level title and figures. 0925 scene->setTitle (getTitle()); 0926 0927 // If we are starting a new level, save it in the player's config file. 0928 if (newLevel && (level != 0)) { // But do not save the "ENDE" level. 0929 KConfigGroup gameGroup (KSharedConfig::openConfig(), QStringLiteral("KDEGame")); 0930 gameGroup.writeEntry ("GamePrefix", prefix); 0931 gameGroup.writeEntry (QStringLiteral("Level_") + prefix, level); 0932 gameGroup.sync(); // Ensure that the entry goes to disk. 0933 } 0934 0935 return true; 0936 } 0937 0938 void KGrGame::setupLevelPlayer() 0939 { 0940 levelPlayer = new KGrLevelPlayer (this, randomGen); 0941 0942 levelPlayer->init (view, recording, playback, gameFrozen); 0943 levelPlayer->setTimeScale (recording->speed); 0944 0945 // Use queued connections here, to ensure that levelPlayer has finished 0946 // executing and can be deleted when control goes to the relevant slot. 0947 connect(levelPlayer, &KGrLevelPlayer::endLevel, this, &KGrGame::endLevel, Qt::QueuedConnection); 0948 if (playback) { 0949 connect(levelPlayer, &KGrLevelPlayer::interruptDemo, this, &KGrGame::interruptDemo, Qt::QueuedConnection); 0950 } 0951 } 0952 0953 void KGrGame::incScore (const int n) 0954 { 0955 score = score + n; // SCORING: trap enemy 75, kill enemy 75, 0956 Q_EMIT showScore (score); // collect gold 250, complete the level 1500. 0957 } 0958 0959 void KGrGame::playSound (const int n, const bool onOff) 0960 { 0961 #ifdef KGAUDIO_BACKEND_OPENAL 0962 if (! effects) { 0963 return; // Sound is off and not yet loaded. 0964 } 0965 static int fallToken = -1; 0966 if (onOff) { 0967 int token = -1; 0968 if (stepsOn || ((n != StepSound) && (n != ClimbSound))) { 0969 token = effects->play (fx [n]); 0970 } 0971 if (n == FallSound) { 0972 fallToken = token; 0973 } 0974 } 0975 // Only the falling sound can get individually turned off. 0976 else if ((n == FallSound) && (fallToken >= 0)) { 0977 effects->stop (fallToken); 0978 fallToken = -1; 0979 } 0980 #endif 0981 } 0982 0983 void KGrGame::endLevel (const int result) 0984 { 0985 // dbk << "Return to KGrGame, result:" << result; 0986 0987 #ifdef KGAUDIO_BACKEND_OPENAL 0988 if (effects) { // If sounds have been loaded, cut off 0989 effects->stopAllSounds(); // all sounds that are in progress. 0990 } 0991 #endif 0992 0993 if (! levelPlayer) { 0994 return; // Not playing a level. 0995 } 0996 0997 if (playback && (result == UNEXPECTED_END)) { 0998 if (demoType == INSTANT_REPLAY) { 0999 interruptDemo(); // Reached end of recording in instant replay. 1000 } 1001 else { 1002 runNextDemoLevel(); // Finished replay unexpectedly. Error? 1003 } 1004 return; 1005 } 1006 1007 // dbk << "delete levelPlayer"; 1008 // Delete the level-player, hero, enemies, grid, rule-book, etc. 1009 // Delete sprites in the view later: the user may need to see them briefly. 1010 delete levelPlayer; 1011 levelPlayer = nullptr; 1012 1013 // If the player finished the level (won or lost), save the recording. 1014 if ((! playback) && ((result == WON_LEVEL) || (result == DEAD))) { 1015 // dbk << "saveRecording (QString ("rec_"))"; 1016 saveRecording (QStringLiteral("rec_")); 1017 1018 // Save the game and level, for use in the REPLAY_LAST action. 1019 KConfigGroup gameGroup (KSharedConfig::openConfig (), QStringLiteral("KDEGame")); 1020 gameGroup.writeEntry ("LastGamePrefix", prefix); 1021 gameGroup.writeEntry ("LastLevel", level); 1022 gameGroup.sync(); // Ensure that the entry goes to disk. 1023 } 1024 1025 if (result == WON_LEVEL) { 1026 // dbk << "Won level"; 1027 levelCompleted(); 1028 } 1029 else if (result == DEAD) { 1030 // dbk << "Lost level"; 1031 herosDead(); 1032 } 1033 } 1034 1035 void KGrGame::herosDead() 1036 { 1037 if ((level < 1) || (lives <= 0)) { 1038 return; // Game over: we are in the "ENDE" screen. 1039 } 1040 1041 // Lose a life. 1042 if ((--lives > 0) || playback) { 1043 // Demo mode or still some life left. 1044 Q_EMIT showLives (lives); 1045 1046 // Freeze the animation and let the player see what happened. 1047 freeze (ProgramPause, true); 1048 playSound (DeathSound); 1049 1050 dyingTimer->setSingleShot (true); 1051 dyingTimer->start (1000); 1052 } 1053 else { 1054 // Game over. 1055 Q_EMIT showLives (lives); 1056 1057 freeze (ProgramPause, true); 1058 playSound (GameOverSound); 1059 1060 checkHighScore(); // Check if there is a high score for this game. 1061 1062 // Offer the player a chance to start this level again with 5 new lives. 1063 QString gameOver = i18n ("<NOBR><B>GAME OVER !!!</B></NOBR><P>" 1064 "Would you like to try this level again?</P>"); 1065 switch (KGrMessage::warning (view, i18n ("Game Over"), gameOver, 1066 i18n ("&Try Again"), i18n ("&Finish"))) { 1067 case 0: 1068 freeze (ProgramPause, false); // Offer accepted. 1069 newGame (level, gameIndex); 1070 showTutorialMessages (level); 1071 return; 1072 break; 1073 case 1: 1074 break; // Offer rejected. 1075 } 1076 1077 // Game completely over. 1078 freeze (ProgramPause, false); // Unfreeze. 1079 level = 0; // Display the "ENDE" screen. 1080 if (playLevel (SYSTEM, QStringLiteral("ende"), level, (! NewLevel))) { 1081 levelPlayer->prepareToPlay(); // Activate the animation. 1082 } 1083 } 1084 } 1085 1086 void KGrGame::finalBreath() 1087 { 1088 //dbk << "Connecting fadeFinished()"; 1089 connect(scene, &KGrScene::fadeFinished, this, &KGrGame::repeatLevel); 1090 //dbk << "Calling scene->fadeOut()"; 1091 scene->fadeIn (false); 1092 } 1093 1094 void KGrGame::repeatLevel() 1095 { 1096 disconnect(scene, &KGrScene::fadeFinished, this, &KGrGame::repeatLevel); 1097 scene->goToBlack(); 1098 1099 // Avoid re-starting if the player selected edit before the time was up. 1100 if (! editor) { 1101 if (playback) { 1102 runNextDemoLevel(); 1103 } 1104 else if (playLevel (owner, prefix, level, (! NewLevel))) { 1105 levelPlayer->prepareToPlay(); 1106 } 1107 } 1108 freeze (ProgramPause, false); // Unfreeze, but don't move yet. 1109 } 1110 1111 void KGrGame::levelCompleted() 1112 { 1113 playSound (CompletedSound); 1114 1115 //dbk << "Connecting fadeFinished()"; 1116 connect(scene, &KGrScene::fadeFinished, this, &KGrGame::goUpOneLevel); 1117 //dbk << "Calling scene->fadeOut()"; 1118 scene->fadeIn (false); 1119 } 1120 1121 void KGrGame::goUpOneLevel() 1122 { 1123 disconnect(scene, &KGrScene::fadeFinished, this, &KGrGame::goUpOneLevel); 1124 scene->goToBlack(); 1125 1126 lives++; // Level completed: gain another life. 1127 Q_EMIT showLives (lives); 1128 incScore (1500); 1129 1130 if (playback) { 1131 runNextDemoLevel(); 1132 return; 1133 } 1134 if (level >= levelMax) { 1135 KGrGameData * gameData = gameList.at (gameIndex); 1136 freeze (ProgramPause, true); 1137 playSound (VictorySound); 1138 1139 KGrMessage::information (view, gameData->name, 1140 i18n ("<b>CONGRATULATIONS !!!!</b>" 1141 "<p>You have conquered the last level in the " 1142 "<b>\"%1\"</b> game !!</p>", gameData->name)); 1143 checkHighScore(); // Check if there is a high score for this game. 1144 1145 freeze (ProgramPause, false); 1146 level = 0; // Game completed: display the "ENDE" screen. 1147 } 1148 else { 1149 level++; // Go up one level. 1150 } 1151 1152 if (playLevel (owner, prefix, level, NewLevel)) { 1153 showTutorialMessages (level); 1154 } 1155 } 1156 1157 void KGrGame::setControlMode (const int mode) 1158 { 1159 // Enable/disable keyboard-mode options. 1160 bool enableDisable = (mode == KEYBOARD); 1161 Q_EMIT setAvail (QStringLiteral("click_key"), enableDisable); 1162 Q_EMIT setAvail (QStringLiteral("hold_key"), enableDisable); 1163 1164 controlMode = mode; 1165 if (levelPlayer && (! playback)) { 1166 // Change control during play, but not during a demo or replay. 1167 levelPlayer->setControlMode (mode); 1168 } 1169 } 1170 1171 void KGrGame::setHoldKeyOption (const int option) 1172 { 1173 holdKeyOption = option; 1174 if (levelPlayer && (! playback)) { 1175 // Change key-option during play, but not during a demo or replay. 1176 levelPlayer->setHoldKeyOption (option); 1177 } 1178 } 1179 1180 void KGrGame::setTimeScale (const int action) 1181 { 1182 switch (action) { 1183 case NORMAL_SPEED: 1184 timeScale = 10; 1185 break; 1186 case BEGINNER_SPEED: 1187 timeScale = 5; 1188 break; 1189 case CHAMPION_SPEED: 1190 timeScale = 15; 1191 break; 1192 case INC_SPEED: 1193 timeScale = (timeScale < 20) ? timeScale + 1 : 20; 1194 break; 1195 case DEC_SPEED: 1196 timeScale = (timeScale > 2) ? timeScale - 1 : 2; 1197 break; 1198 default: 1199 break; 1200 } 1201 1202 if (levelPlayer && (! playback)) { 1203 // Change speed during play, but not during a demo or replay. 1204 //qCDebug(KGOLDRUNNER_LOG) << "setTimeScale" << (timeScale); 1205 levelPlayer->setTimeScale (timeScale); 1206 } 1207 } 1208 1209 bool KGrGame::inEditMode() 1210 { 1211 return (editor != nullptr); // Return true if the game-editor is active. 1212 } 1213 1214 void KGrGame::toggleSoundsOnOff (const int action) 1215 { 1216 const char * setting = (action == PLAY_SOUNDS) ? "Sound" : "StepSounds"; 1217 KConfigGroup gameGroup (KSharedConfig::openConfig(), QStringLiteral("KDEGame")); 1218 bool soundOnOff = gameGroup.readEntry (setting, false); 1219 soundOnOff = (! soundOnOff); 1220 gameGroup.writeEntry (setting, soundOnOff); 1221 if (action == PLAY_SOUNDS) { 1222 soundOn = soundOnOff; 1223 } 1224 else { 1225 stepsOn = soundOnOff; 1226 } 1227 1228 #ifdef KGAUDIO_BACKEND_OPENAL 1229 if (action == PLAY_SOUNDS) { 1230 if (soundOn && (effects == nullptr)) { 1231 loadSounds(); // Sounds were not loaded when the game started. 1232 } 1233 effects->setMuted (! soundOn); 1234 } 1235 #endif 1236 } 1237 1238 void KGrGame::freeze (const bool userAction, const bool on_off) 1239 { 1240 QString type = userAction ? QStringLiteral("UserAction") : QStringLiteral("ProgramAction"); 1241 //qCDebug(KGOLDRUNNER_LOG) << "PAUSE:" << type << on_off; 1242 //qCDebug(KGOLDRUNNER_LOG) << "gameFrozen" << gameFrozen << "programFreeze" << programFreeze; 1243 1244 #ifdef KGAUDIO_BACKEND_OPENAL 1245 if (on_off && effects) { // If pausing and sounds are loaded, cut 1246 effects->stopAllSounds(); // off all sounds that are in progress. 1247 } 1248 #endif 1249 1250 if (! userAction) { 1251 // The program needs to freeze the game during a message, dialog, etc. 1252 if (on_off) { 1253 if (gameFrozen) { 1254 //if (programFreeze) { 1255 // qCDebug(KGOLDRUNNER_LOG) << "P: The program has already frozen the game."; 1256 //} 1257 //else { 1258 // qCDebug(KGOLDRUNNER_LOG) << "P: The user has already frozen the game."; 1259 //} 1260 return; // The game is already frozen. 1261 } 1262 programFreeze = false; 1263 } 1264 else if (! programFreeze) { 1265 //if (gameFrozen) { 1266 // qCDebug(KGOLDRUNNER_LOG) << "P: The user will keep the game frozen."; 1267 //} 1268 //else { 1269 // qCDebug(KGOLDRUNNER_LOG) << "P: The game is NOT frozen."; 1270 //} 1271 return; // The user will keep the game frozen. 1272 } 1273 // The program will succeed in freezing or unfreezing the game. 1274 programFreeze = on_off; 1275 } 1276 else if (programFreeze) { 1277 // If the user breaks through a program freeze somehow, take no action. 1278 qCDebug(KGOLDRUNNER_LOG) << "U: THE USER HAS BROKEN THROUGH SOMEHOW."; 1279 return; 1280 } 1281 else { 1282 // The user is freezing or unfreezing the game. Do visual feedback. 1283 Q_EMIT gameFreeze (on_off); 1284 } 1285 1286 gameFrozen = on_off; 1287 if (levelPlayer) { 1288 levelPlayer->pause (on_off); 1289 } 1290 //qCDebug(KGOLDRUNNER_LOG) << "RESULT: gameFrozen" << gameFrozen 1291 // << "programFreeze" << programFreeze; 1292 } 1293 1294 void KGrGame::showHint() 1295 { 1296 // Put out a hint for this level. 1297 QString caption = i18n ("Hint"); 1298 1299 if (levelHint.length() > 0) { 1300 freeze (ProgramPause, true); 1301 // TODO - IDW. Check if a solution exists BEFORE showing the extra button. 1302 switch (KGrMessage::warning (view, caption, levelHint, 1303 i18n ("&OK"), i18n ("&Show a Solution"))) { 1304 case 0: 1305 freeze (ProgramPause, false); // No replay requested. 1306 break; 1307 case 1: 1308 freeze (ProgramPause, false); // Replay a solution. 1309 // This deletes current KGrLevelPlayer and play, but no life is lost. 1310 runReplay (SOLVE, gameIndex, level); 1311 break; 1312 } 1313 } 1314 else 1315 myMessage (view, caption, 1316 i18n ("Sorry, there is no hint for this level.")); 1317 } 1318 1319 void KGrGame::showTutorialMessages (int levelNo) 1320 { 1321 // Halt the game during message displays and mouse pointer moves. 1322 freeze (ProgramPause, true); 1323 1324 // Check if this is a tutorial collection and not on the "ENDE" screen. 1325 if ((prefix.left (4) == QLatin1String("tute")) && (levelNo != 0)) { 1326 1327 // At the start of a tutorial, put out an introduction. 1328 if (levelNo == 1) { 1329 KGrMessage::information (view, gameList.at (gameIndex)->name, 1330 i18n (gameList.at (gameIndex)->about.constData())); 1331 } 1332 // Put out an explanation of this level. 1333 KGrMessage::information (view, getTitle(), levelHint); 1334 } 1335 1336 if (levelPlayer) { 1337 levelPlayer->prepareToPlay(); 1338 } 1339 freeze (ProgramPause, false); // Let the level begin. 1340 } 1341 1342 void KGrGame::setPlayback (const bool onOff) 1343 { 1344 if (playback != onOff) { 1345 // Disable high scores, kill hero and some settings during demo/replay. 1346 bool enableDisable = (! onOff); 1347 Q_EMIT setAvail (QStringLiteral("game_highscores"), enableDisable); 1348 Q_EMIT setAvail (QStringLiteral("kill_hero"), enableDisable); 1349 1350 Q_EMIT setAvail (QStringLiteral("mouse_mode"), enableDisable); 1351 Q_EMIT setAvail (QStringLiteral("keyboard_mode"), enableDisable); 1352 Q_EMIT setAvail (QStringLiteral("laptop_mode"), enableDisable); 1353 1354 Q_EMIT setAvail (QStringLiteral("click_key"), enableDisable); 1355 Q_EMIT setAvail (QStringLiteral("hold_key"), enableDisable); 1356 1357 Q_EMIT setAvail (QStringLiteral("normal_speed"), enableDisable); 1358 Q_EMIT setAvail (QStringLiteral("beginner_speed"), enableDisable); 1359 Q_EMIT setAvail (QStringLiteral("champion_speed"), enableDisable); 1360 Q_EMIT setAvail (QStringLiteral("increase_speed"), enableDisable); 1361 Q_EMIT setAvail (QStringLiteral("decrease_speed"), enableDisable); 1362 } 1363 scene->showReplayMessage (onOff); 1364 playback = onOff; 1365 } 1366 1367 QString KGrGame::getDirectory (Owner o) 1368 { 1369 return ((o == SYSTEM) ? systemDataDir : userDataDir); 1370 } 1371 1372 QString KGrGame::getTitle() 1373 { 1374 int lev = (playback) ? recording->level : level; 1375 KGrGameData * gameData = gameList.at (gameIndex); 1376 if (lev == 0) { 1377 // Generate a special title for end of game. 1378 return (i18n ("T H E E N D")); 1379 } 1380 1381 // Set title string to "Game-name - NNN" or "Game-name - NNN - Level-name". 1382 QString gameName = (playback) ? recording->gameName : gameData->name; 1383 QString levelNumber = QString::number(lev).rightJustified(3, QLatin1Char('0')); 1384 1385 QString levelTitle = (levelName.length() <= 0) 1386 ? 1387 i18nc ("Game name - level number.", 1388 "%1 - %2", gameName, levelNumber) 1389 : 1390 i18nc ("Game name - level number - level name.", 1391 "%1 - %2 - %3", gameName, levelNumber, levelName); 1392 return (levelTitle); 1393 } 1394 1395 void KGrGame::kbControl (const int dirn, const bool pressed) 1396 { 1397 dbk2 << "Keystroke setting direction" << dirn << "pressed" << pressed; 1398 1399 if (editor) { 1400 return; 1401 } 1402 if (playback && levelPlayer) { 1403 levelPlayer->interruptPlayback(); // Will emit interruptDemo(). 1404 return; 1405 } 1406 1407 // Using keyboard control can automatically disable mouse control. 1408 if (pressed && ((controlMode == MOUSE) || 1409 ((controlMode == LAPTOP) && (dirn != DIG_RIGHT) && (dirn != DIG_LEFT)))) 1410 { 1411 // Halt the game while a message is displayed. 1412 freeze (ProgramPause, true); 1413 1414 switch (KMessageBox::questionTwoActions (view, 1415 i18n ("You have pressed a key that can be used to control the " 1416 "Hero. Do you want to switch automatically to keyboard " 1417 "control? Pointer control is easier to use in the long term " 1418 "- like riding a bike rather than walking!"), 1419 i18n ("Switch to Keyboard Mode"), 1420 KGuiItem (i18n ("Switch to &Keyboard Mode")), 1421 KGuiItem (i18n ("Stay in &Mouse Mode")), 1422 i18n ("Keyboard Mode"))) 1423 { 1424 case KMessageBox::PrimaryAction: 1425 case KMessageBox::Ok: 1426 case KMessageBox::Continue: 1427 settings (KEYBOARD); 1428 Q_EMIT setToggle (QStringLiteral("keyboard_mode"), true); // Adjust Settings menu. 1429 break; 1430 case KMessageBox::SecondaryAction: 1431 case KMessageBox::Cancel: 1432 break; 1433 } 1434 1435 // Unfreeze the game, but only if it was previously unfrozen. 1436 freeze (ProgramPause, false); 1437 1438 if (controlMode != KEYBOARD) { 1439 return; // Stay in Mouse or Laptop Mode. 1440 } 1441 } 1442 1443 // Accept keystroke to set next direction, even when the game is frozen. 1444 if (levelPlayer) { 1445 levelPlayer->setDirectionByKey ((Direction) dirn, pressed); 1446 } 1447 } 1448 1449 /******************************************************************************/ 1450 /************************** SAVE AND RE-LOAD GAMES **************************/ 1451 /******************************************************************************/ 1452 1453 void KGrGame::saveGame() // Save game ID, score and level. 1454 { 1455 if (editor) { 1456 myMessage (view, i18n ("Save Game"), 1457 i18n ("Sorry, you cannot save your game play while you are editing. " 1458 "Please try menu item \"%1\".", 1459 i18n ("&Save Edits..."))); 1460 return; 1461 } 1462 if (playback) { 1463 return; // Avoid saving in playback mode. 1464 } 1465 1466 QDate today = QDate::currentDate(); 1467 QTime now = QTime::currentTime(); 1468 QString saved; 1469 QString day; 1470 day = QLocale().dayName(today.dayOfWeek(), QLocale::ShortFormat); 1471 saved = QString::asprintf 1472 ("%-6s %03d %03ld %7ld %s %04d-%02d-%02d %02d:%02d\n", 1473 qPrintable(prefix), level, lives, startScore, 1474 qPrintable(day), 1475 today.year(), today.month(), today.day(), 1476 now.hour(), now.minute()); 1477 1478 QFile file1 (userDataDir + QStringLiteral("savegame.dat")); 1479 QFile file2 (userDataDir + QStringLiteral("savegame.tmp")); 1480 1481 if (! file2.open (QIODevice::WriteOnly)) { 1482 KGrMessage::information (view, i18n ("Save Game"), 1483 i18n ("Cannot open file '%1' for output.", 1484 userDataDir + QStringLiteral("savegame.tmp"))); 1485 return; 1486 } 1487 QTextStream text2 (&file2); 1488 text2 << saved; 1489 1490 if (file1.exists()) { 1491 if (! file1.open (QIODevice::ReadOnly)) { 1492 KGrMessage::information (view, i18n ("Save Game"), 1493 i18n ("Cannot open file '%1' for read-only.", 1494 userDataDir + QStringLiteral("savegame.dat"))); 1495 return; 1496 } 1497 1498 QTextStream text1 (&file1); 1499 int n = 30; // Limit the file to the last 30 saves. 1500 while ((! text1.atEnd()) && (--n > 0)) { 1501 saved = text1.readLine() + QLatin1Char('\n'); 1502 text2 << saved; 1503 } 1504 file1.close(); 1505 } 1506 1507 file2.close(); 1508 1509 if (KGrGameIO::safeRename (view, userDataDir+QStringLiteral("savegame.tmp"), 1510 userDataDir+QStringLiteral("savegame.dat"))) { 1511 KGrMessage::information (view, i18n ("Save Game"), 1512 i18n ("Please note: for reasons of simplicity, your saved game " 1513 "position and score will be as they were at the start of this " 1514 "level, not as they are now.")); 1515 } 1516 else { 1517 KGrMessage::information (view, i18n ("Save Game"), 1518 i18n ("Error: Failed to save your game.")); 1519 } 1520 } 1521 1522 bool KGrGame::selectSavedGame (int & selectedGame, int & selectedLevel) 1523 { 1524 selectedGame = 0; 1525 selectedLevel = 1; 1526 1527 QFile savedGames (userDataDir + QStringLiteral("savegame.dat")); 1528 if (! savedGames.exists()) { 1529 // Use myMessage() because it stops the game while the message appears. 1530 myMessage (view, i18n ("Load Game"), 1531 i18n ("Sorry, there are no saved games.")); 1532 return false; 1533 } 1534 1535 if (! savedGames.open (QIODevice::ReadOnly)) { 1536 myMessage (view, i18n ("Load Game"), 1537 i18n ("Cannot open file '%1' for read-only.", 1538 userDataDir + QStringLiteral("savegame.dat"))); 1539 return false; 1540 } 1541 1542 // Halt the game during the loadGame() dialog. 1543 freeze (ProgramPause, true); 1544 1545 bool result = false; 1546 1547 loadedData = QString(); 1548 KGrLGDialog * lg = new KGrLGDialog (&savedGames, gameList, view); 1549 if (lg->exec() == QDialog::Accepted) { 1550 loadedData = lg->getCurrentText(); 1551 } 1552 delete lg; 1553 1554 QString pr; 1555 int index = -1; 1556 1557 selectedLevel = 0; 1558 if (! loadedData.isEmpty()) { 1559 pr = loadedData.mid (21, 7); // Get the game prefix. 1560 pr = pr.left (pr.indexOf(QLatin1Char(' '), 0, Qt::CaseInsensitive)); 1561 1562 for (int i = 0; i < gameList.count(); i++) { // Find the game. 1563 if (gameList.at (i)->prefix == pr) { 1564 index = i; 1565 break; 1566 } 1567 } 1568 if (index >= 0) { 1569 selectedGame = index; 1570 selectedLevel = QStringView(loadedData).mid(28, 3).toInt(); 1571 result = true; 1572 } 1573 else { 1574 KGrMessage::information (view, i18n ("Load Game"), 1575 i18n ("Cannot find the game with prefix '%1'.", pr)); 1576 } 1577 } 1578 1579 // Unfreeze the game, but only if it was previously unfrozen. 1580 freeze (ProgramPause, false); 1581 1582 return result; 1583 } 1584 1585 void KGrGame::loadGame (const int game, const int lev) 1586 { 1587 newGame (lev, game); // Re-start the selected game. 1588 showTutorialMessages (level); 1589 lives = loadedData.mid (32, 3).toLong(); // Update the lives. 1590 Q_EMIT showLives (lives); 1591 score = loadedData.mid (36, 7).toLong(); // Update the score. 1592 Q_EMIT showScore (score); 1593 } 1594 1595 bool KGrGame::saveOK() 1596 { 1597 return (editor ? (editor->saveOK()) : true); 1598 } 1599 1600 /******************************************************************************/ 1601 /************************** HIGH-SCORE PROCEDURES ***************************/ 1602 /******************************************************************************/ 1603 1604 void KGrGame::checkHighScore() 1605 { 1606 // Don't keep high scores for tutorial games. 1607 if ((prefix.left (4) == QLatin1String("tute")) || (playback)) { 1608 return; 1609 } 1610 1611 if (score <= 0) { 1612 return; 1613 } 1614 1615 #ifdef USE_KSCOREDIALOG 1616 KScoreDialog scoreDialog ( 1617 KScoreDialog::Name | KScoreDialog::Level | 1618 KScoreDialog::Date | KScoreDialog::Score, 1619 view); 1620 scoreDialog.setConfigGroup (prefix); 1621 KScoreDialog::FieldInfo scoreInfo; 1622 scoreInfo[KScoreDialog::Level].setNum (level); 1623 scoreInfo[KScoreDialog::Score].setNum (score); 1624 QDate today = QDate::currentDate(); 1625 scoreInfo[KScoreDialog::Date] = today.toString ("ddd yyyy MM dd"); 1626 if (scoreDialog.addScore (scoreInfo)) { 1627 scoreDialog.exec(); 1628 } 1629 #else 1630 bool prevHigh = true; 1631 qint16 prevLevel = 0; 1632 qint32 prevScore = 0; 1633 QString thisUser = i18n ("Unknown"); 1634 int highCount = 0; 1635 1636 // Look for user's high-score file or for a released high-score file. 1637 QFile high1 (userDataDir + QStringLiteral("hi_") + prefix + QStringLiteral(".dat")); 1638 QDataStream s1; 1639 1640 if (! high1.exists()) { 1641 high1.setFileName (systemDataDir + QStringLiteral("hi_") + prefix + QStringLiteral(".dat")); 1642 if (! high1.exists()) { 1643 prevHigh = false; 1644 } 1645 } 1646 1647 // If a previous high score file exists, check the current score against it. 1648 if (prevHigh) { 1649 if (! high1.open (QIODevice::ReadOnly)) { 1650 QString high1_name = high1.fileName(); 1651 KGrMessage::information (view, i18n ("Check for High Score"), 1652 i18n ("Cannot open file '%1' for read-only.", high1_name)); 1653 return; 1654 } 1655 1656 // Read previous users, levels and scores from the high score file. 1657 s1.setDevice (&high1); 1658 bool found = false; 1659 highCount = 0; 1660 while (! s1.atEnd()) { 1661 char * prevUser; 1662 char * prevDate; 1663 s1 >> prevUser; 1664 s1 >> prevLevel; 1665 s1 >> prevScore; 1666 s1 >> prevDate; 1667 delete prevUser; 1668 delete prevDate; 1669 highCount++; 1670 if (score > prevScore) { 1671 found = true; // We have a high score. 1672 break; 1673 } 1674 } 1675 1676 // Check if higher than one on file or fewer than 10 previous scores. 1677 if ((! found) && (highCount >= 10)) { 1678 return; // We did not have a high score. 1679 } 1680 } 1681 1682 /* ************************************************************* */ 1683 /* If we have come this far, we have a new high score to record. */ 1684 /* ************************************************************* */ 1685 1686 QFile high2 (userDataDir + QStringLiteral("hi_") + prefix + QStringLiteral(".tmp")); 1687 QDataStream s2; 1688 1689 if (! high2.open (QIODevice::WriteOnly)) { 1690 KGrMessage::information (view, i18n ("Check for High Score"), 1691 i18n ("Cannot open file '%1' for output.", 1692 userDataDir + QStringLiteral("hi_") + prefix + QStringLiteral(".tmp"))); 1693 return; 1694 } 1695 1696 // Dialog to ask the user to enter their name. 1697 QDialog * hsn = new QDialog (view, 1698 Qt::WindowTitleHint); 1699 hsn->setObjectName ( QStringLiteral("hsNameDialog" )); 1700 1701 int margin = 10; 1702 int spacing = 10; 1703 QVBoxLayout * mainLayout = new QVBoxLayout (hsn); 1704 mainLayout->setSpacing (spacing); 1705 mainLayout->setContentsMargins(margin, margin, margin, margin); 1706 1707 QLabel * hsnMessage = new QLabel ( 1708 i18n ("<html><b>Congratulations !!!</b><br/>" 1709 "You have achieved a high score in this game.<br/>" 1710 "Please enter your name " 1711 "so that it may be enshrined<br/>" 1712 "in the KGoldrunner Hall of Fame.</html>"), 1713 hsn); 1714 QLineEdit * hsnUser = new QLineEdit (hsn); 1715 QPushButton * OK = new QPushButton(hsn); 1716 KGuiItem::assign(OK,KStandardGuiItem::ok()); 1717 1718 mainLayout-> addWidget (hsnMessage); 1719 mainLayout-> addWidget (hsnUser); 1720 mainLayout-> addWidget (OK); 1721 1722 hsn-> setWindowTitle (i18nc("@title:window", "Save High Score")); 1723 1724 // QPoint p = view->mapToGlobal (QPoint (0,0)); 1725 // hsn-> move (p.x() + 50, p.y() + 50); 1726 1727 OK-> setShortcut (Qt::Key_Return); 1728 hsnUser-> setFocus(); // Set the keyboard input on. 1729 1730 connect(hsnUser, &QLineEdit::returnPressed, hsn, &QDialog::accept); 1731 connect(OK, &QPushButton::clicked, hsn, &QDialog::accept); 1732 1733 // Run the dialog to get the player's name. Use "-" if nothing is entered. 1734 hsn->exec(); 1735 thisUser = hsnUser->text(); 1736 if (thisUser.length() <= 0) 1737 thisUser = QLatin1Char('-'); 1738 delete hsn; 1739 1740 QDate today = QDate::currentDate(); 1741 QString hsDate; 1742 QString day = QLocale().dayName(today.dayOfWeek(), QLocale::ShortFormat); 1743 hsDate = QString::asprintf 1744 ("%s %04d-%02d-%02d", 1745 qPrintable(day), 1746 today.year(), today.month(), today.day()); 1747 1748 s2.setDevice (&high2); 1749 1750 if (prevHigh) { 1751 high1.reset(); 1752 bool scoreRecorded = false; 1753 highCount = 0; 1754 while ((! s1.atEnd()) && (highCount < 10)) { 1755 char * prevUser; 1756 char * prevDate; 1757 s1 >> prevUser; 1758 s1 >> prevLevel; 1759 s1 >> prevScore; 1760 s1 >> prevDate; 1761 if ((! scoreRecorded) && (score > prevScore)) { 1762 highCount++; 1763 // Recode the user's name as UTF-8, in case it contains 1764 // non-ASCII chars (e.g. "Krüger" is encoded as "Krüger"). 1765 s2 << thisUser.toUtf8().constData(); 1766 s2 << (qint16) level; 1767 s2 << (qint32) score; 1768 s2 << qPrintable(hsDate); 1769 scoreRecorded = true; 1770 } 1771 if (highCount < 10) { 1772 highCount++; 1773 s2 << prevUser; 1774 s2 << prevLevel; 1775 s2 << prevScore; 1776 s2 << prevDate; 1777 } 1778 delete prevUser; 1779 delete prevDate; 1780 } 1781 if ((! scoreRecorded) && (highCount < 10)) { 1782 // Recode the user's name as UTF-8, in case it contains 1783 // non-ASCII chars (e.g. "Krüger" is encoded as "Krüger"). 1784 s2 << thisUser.toUtf8().constData(); 1785 s2 << (qint16) level; 1786 s2 << (qint32) score; 1787 s2 << qPrintable(hsDate); 1788 } 1789 high1.close(); 1790 } 1791 else { 1792 // Recode the user's name as UTF-8, in case it contains 1793 // non-ASCII chars (e.g. "Krüger" is encoded as "Krüger"). 1794 s2 << thisUser.toUtf8().constData(); 1795 s2 << (qint16) level; 1796 s2 << (qint32) score; 1797 s2 << qPrintable(hsDate); 1798 } 1799 1800 high2.close(); 1801 1802 if (KGrGameIO::safeRename (view, high2.fileName(), 1803 userDataDir + QStringLiteral("hi_") + prefix + QStringLiteral(".dat"))) { 1804 // Remove a redundant popup message. 1805 // KGrMessage::information (view, i18n ("Save High Score"), 1806 // i18n ("Your high score has been saved.")); 1807 } 1808 else { 1809 KGrMessage::information (view, i18n ("Save High Score"), 1810 i18n ("Error: Failed to save your high score.")); 1811 } 1812 1813 showHighScores(); 1814 return; 1815 #endif 1816 } 1817 1818 void KGrGame::showHighScores() 1819 { 1820 // Don't keep high scores for tutorial games. 1821 if (prefix.left (4) == QLatin1String("tute")) { 1822 KGrMessage::information (view, i18n ("Show High Scores"), 1823 i18n ("Sorry, we do not keep high scores for tutorial games.")); 1824 return; 1825 } 1826 1827 #ifdef USE_KSCOREDIALOG 1828 KScoreDialog scoreDialog ( 1829 KScoreDialog::Name | KScoreDialog::Level | 1830 KScoreDialog::Date | KScoreDialog::Score, 1831 view); 1832 scoreDialog.exec(); 1833 #else 1834 qint16 prevLevel = 0; 1835 qint32 prevScore = 0; 1836 int n = 0; 1837 1838 // Look for user's high-score file or for a released high-score file. 1839 QFile high1 (userDataDir + QStringLiteral("hi_") + prefix +QStringLiteral( ".dat")); 1840 QDataStream s1; 1841 1842 if (! high1.exists()) { 1843 high1.setFileName (systemDataDir + QStringLiteral("hi_") + prefix + QStringLiteral(".dat")); 1844 if (! high1.exists()) { 1845 KGrMessage::information (view, i18n ("Show High Scores"), 1846 i18n("Sorry, there are no high scores for the \"%1\" game yet.", 1847 gameList.at (gameIndex)->name)); 1848 return; 1849 } 1850 } 1851 1852 if (! high1.open (QIODevice::ReadOnly)) { 1853 QString high1_name = high1.fileName(); 1854 KGrMessage::information (view, i18n ("Show High Scores"), 1855 i18n ("Cannot open file '%1' for read-only.", high1_name)); 1856 return; 1857 } 1858 1859 QDialog * hs = new QDialog (view, 1860 Qt::WindowTitleHint); 1861 hs->setObjectName ( QStringLiteral("hsDialog" )); 1862 1863 int margin = 10; 1864 int spacing = 10; 1865 QVBoxLayout * mainLayout = new QVBoxLayout (hs); 1866 mainLayout->setSpacing (spacing); 1867 mainLayout->setContentsMargins(margin, margin, margin, margin); 1868 1869 QLabel * hsHeader = new QLabel (i18n ( 1870 "<center><h2>KGoldrunner Hall of Fame</h2></center>" 1871 "<center><h3>\"%1\" Game</h3></center>", 1872 gameList.at (gameIndex)->name), 1873 hs); 1874 mainLayout->addWidget (hsHeader, 10); 1875 1876 QTreeWidget * scores = new QTreeWidget (hs); 1877 mainLayout->addWidget (scores, 50); 1878 scores->setColumnCount (5); 1879 scores->setHeaderLabels ({ 1880 i18nc ("1, 2, 3 etc.", "Rank"), 1881 i18nc ("Person", "Name"), 1882 i18nc ("Game level reached", "Level"), 1883 i18n ("Score"), 1884 i18n ("Date"), 1885 }); 1886 scores->setRootIsDecorated (false); 1887 1888 hs-> setWindowTitle (i18nc("@title:window", "High Scores")); 1889 1890 // Read and display the users, levels and scores from the high score file. 1891 scores->clear(); 1892 s1.setDevice (&high1); 1893 n = 0; 1894 while ((! s1.atEnd()) && (n < 10)) { 1895 char * prevUser; 1896 char * prevDate; 1897 s1 >> prevUser; 1898 s1 >> prevLevel; 1899 s1 >> prevScore; 1900 s1 >> prevDate; 1901 1902 // prevUser has been saved on file as UTF-8 to allow non=ASCII chars 1903 // in the user's name (e.g. "Krüger" is encoded as "Krüger" in UTF-8). 1904 const QStringList data{ 1905 QString().setNum (n+1), 1906 QString::fromUtf8 (prevUser), 1907 QString().setNum (prevLevel), 1908 QString().setNum (prevScore), 1909 QString::fromUtf8 (prevDate), 1910 }; 1911 QTreeWidgetItem * score = new QTreeWidgetItem (data); 1912 score->setTextAlignment (0, Qt::AlignRight); // Rank. 1913 score->setTextAlignment (1, Qt::AlignLeft); // Name. 1914 score->setTextAlignment (2, Qt::AlignRight); // Level. 1915 score->setTextAlignment (3, Qt::AlignRight); // Score. 1916 score->setTextAlignment (4, Qt::AlignLeft); // Date. 1917 if (prevScore > 0) { // Skip score 0 (bad file-data). 1918 scores->addTopLevelItem (score); // Show score > 0. 1919 if (n == 0) { 1920 scores->setCurrentItem (score); // Highlight the highest score. 1921 } 1922 n++; 1923 } 1924 1925 delete prevUser; 1926 delete prevDate; 1927 } 1928 1929 // Adjust the columns to fit the data. 1930 scores->header()->setSectionResizeMode (0, QHeaderView::ResizeToContents); 1931 scores->header()->setSectionResizeMode (1, QHeaderView::ResizeToContents); 1932 scores->header()->setSectionResizeMode (2, QHeaderView::ResizeToContents); 1933 scores->header()->setSectionResizeMode (3, QHeaderView::ResizeToContents); 1934 scores->header()->setSectionResizeMode (4, QHeaderView::ResizeToContents); 1935 scores->header()->setMinimumSectionSize (-1); // Font metrics size. 1936 1937 QFrame * separator = new QFrame (hs); 1938 separator->setFrameStyle (QFrame::HLine + QFrame::Sunken); 1939 mainLayout->addWidget (separator); 1940 1941 QHBoxLayout *hboxLayout1 = new QHBoxLayout(); 1942 hboxLayout1->setSpacing (spacing); 1943 QSpacerItem * spacerItem = new QSpacerItem (40, 20, QSizePolicy::Expanding, 1944 QSizePolicy::Minimum); 1945 hboxLayout1->addItem (spacerItem); 1946 QPushButton * OK = new QPushButton(hs); 1947 KGuiItem::assign(OK,KStandardGuiItem::close()); 1948 OK-> setShortcut (Qt::Key_Return); 1949 OK-> setMaximumWidth (100); 1950 hboxLayout1->addWidget (OK); 1951 mainLayout-> addLayout (hboxLayout1, 5); 1952 // int w = (view->size().width()*4)/10; 1953 // hs-> setMinimumSize (w, w); 1954 1955 // QPoint p = view->mapToGlobal (QPoint (0,0)); 1956 // hs-> move (p.x() + 50, p.y() + 50); 1957 1958 // Start up the dialog box. 1959 connect(OK, &QPushButton::clicked, hs, &QDialog::accept); 1960 hs-> exec(); 1961 1962 delete hs; 1963 #endif 1964 } 1965 1966 /******************************************************************************/ 1967 /************************** AUTHORS' DEBUGGING AIDS **************************/ 1968 /******************************************************************************/ 1969 1970 void KGrGame::dbgControl (const int code) 1971 { 1972 if (playback) { 1973 levelPlayer->interruptPlayback(); // Will emit interruptDemo(). 1974 return; 1975 } 1976 // qCDebug(KGOLDRUNNER_LOG) << "Debug code =" << code; 1977 if (levelPlayer && gameFrozen) { 1978 levelPlayer->dbgControl (code); 1979 } 1980 } 1981 1982 bool KGrGame::initGameLists() 1983 { 1984 // Initialise the lists of games (i.e. collections of levels). 1985 1986 // System games are the ones distributed with KDE Games. They cannot be 1987 // edited or deleted, but they can be copied, edited and saved into a user's 1988 // game. Users' games and levels can be freely created, edited and deleted. 1989 1990 owner = SYSTEM; // Use system levels initially. 1991 if (! loadGameData (SYSTEM)) // Load list of system games. 1992 return (false); // If no system games, abort. 1993 loadGameData (USER); // Load user's list of games. 1994 // If none, don't worry. 1995 for (int i = 0; i < gameList.count(); i++) { 1996 dbk1 << i << gameList.at(i)->prefix << gameList.at(i)->name; 1997 } 1998 return (true); 1999 } 2000 2001 bool KGrGame::loadGameData (Owner o) 2002 { 2003 KGrGameIO io (view); 2004 QList<KGrGameData *> gList; 2005 QString filePath; 2006 IOStatus status = io.fetchGameListData 2007 (o, getDirectory (o), gList, filePath); 2008 2009 bool result = false; 2010 switch (status) { 2011 case NotFound: 2012 // If the user has not yet created a collection, don't worry. 2013 if (o == SYSTEM) { 2014 KGrMessage::information (view, i18n ("Load Game Info"), 2015 i18n ("Cannot find game info file '%1'.", filePath)); 2016 } 2017 break; 2018 case NoRead: 2019 case NoWrite: 2020 KGrMessage::information (view, i18n ("Load Game Info"), 2021 i18n ("Cannot open file '%1' for read-only.", filePath)); 2022 break; 2023 case UnexpectedEOF: 2024 KGrMessage::information (view, i18n ("Load Game Info"), 2025 i18n ("Reached end of file '%1' before finding end of game-data.", 2026 filePath)); 2027 break; 2028 case OK: 2029 // Append this owner's list of games to the main list. 2030 gameList += gList; 2031 result = true; 2032 break; 2033 } 2034 2035 return (result); 2036 } 2037 2038 void KGrGame::saveSolution (const QString & prefix, const int levelNo) 2039 { 2040 // Save the game and level data that is currently displayed. 2041 KGrRecording * prevRecording = recording; 2042 recording = nullptr; 2043 demoType = REPLAY_ANY; // Must load a "rec_" file, not "sol_". 2044 2045 // Proceed as if we are going to replay the selected level. 2046 if (initRecordingData (USER, prefix, levelNo, true)) { 2047 // But instead just save the recording data on a solution file. 2048 saveRecording (QStringLiteral("sol_")); 2049 KGrMessage::information (view, i18n ("Save A Solution"), 2050 i18n ("Your solution to level %1 has been saved on file %2", 2051 levelNo, userDataDir + QStringLiteral("sol_") + prefix + QStringLiteral(".txt"))); 2052 } 2053 else { 2054 KGrMessage::information (view, i18n ("Save A Solution"), 2055 i18n ("Sorry, you do not seem to have played and recorded " 2056 "the selected level before."), QStringLiteral("Show_noRecording")); 2057 } 2058 2059 // Restore the game and level data that is currently displayed. 2060 delete recording; 2061 recording = prevRecording; 2062 2063 // TODO - Factor KGrRecording into separate files, with methods, etc. 2064 } 2065 2066 bool KGrGame::initRecordingData (const Owner fileOwner, const QString & prefix, 2067 const int levelNo, const bool pPlayback) 2068 { 2069 // Initialise the recording. 2070 delete recording; 2071 recording = new KGrRecording; 2072 recording->content.fill (0, 4000); 2073 recording->draws.fill (0, 400); 2074 2075 // If system game or ENDE, choose system dir, else choose user dir. 2076 const QString dir = ((fileOwner == SYSTEM) || (levelNo == 0)) ? 2077 systemDataDir : userDataDir; 2078 if (pPlayback) { 2079 if (! loadRecording (dir, prefix, levelNo)) { 2080 return false; 2081 } 2082 } 2083 else { 2084 KGrGameIO io (view); 2085 KGrLevelData levelData; 2086 KGrGameData * gameData = gameList.at (gameIndex); 2087 2088 // Set digWhileFalling same as game, by default: read the level data. 2089 // The dig-while-falling setting can be overridden for a single level. 2090 levelData.digWhileFalling = gameData->digWhileFalling; 2091 if (! io.readLevelData (dir, prefix, levelNo, levelData)) { 2092 return false; 2093 } 2094 2095 recording->dateTime = QDateTime::currentDateTime() 2096 .toUTC() 2097 .toString (Qt::ISODate); 2098 //qCDebug(KGOLDRUNNER_LOG) << "Recording at" << recording->dateTime; 2099 2100 recording->owner = gameData->owner; 2101 recording->rules = gameData->rules; 2102 recording->prefix = gameData->prefix; 2103 recording->gameName = gameData->name; 2104 2105 recording->level = levelNo; 2106 recording->width = levelData.width; 2107 recording->height = levelData.height; 2108 recording->layout = levelData.layout; 2109 2110 // Record whether this level will allow the hero to dig while falling. 2111 recording->digWhileFalling = levelData.digWhileFalling; 2112 2113 // If there is a name or hint, translate the UTF-8 code right now. 2114 recording->levelName = (levelData.name.size() > 0) ? 2115 i18n (levelData.name.constData()) : QString(); 2116 recording->hint = (levelData.hint.size() > 0) ? 2117 i18n (levelData.hint.constData()) : QString(); 2118 2119 recording->lives = lives; 2120 recording->score = score; 2121 recording->speed = timeScale; 2122 recording->controlMode = controlMode; 2123 recording->keyOption = holdKeyOption; 2124 recording->content [0] = static_cast<char>(0xff); 2125 } 2126 return true; 2127 } 2128 2129 void KGrGame::saveRecording (const QString & filetype) 2130 { 2131 QString filename = userDataDir + filetype + prefix + QStringLiteral(".txt"); 2132 QString groupName = prefix + 2133 QString::number(recording->level).rightJustified(3,QLatin1Char('0')); 2134 //qCDebug(KGOLDRUNNER_LOG) << filename << groupName; 2135 2136 KConfig config (filename, KConfig::SimpleConfig); 2137 KConfigGroup configGroup = config.group (groupName); 2138 configGroup.writeEntry ("DateTime", recording->dateTime); 2139 configGroup.writeEntry ("Owner", (int) recording->owner); 2140 configGroup.writeEntry ("Rules", (int) recording->rules); 2141 configGroup.writeEntry ("Prefix", recording->prefix); 2142 configGroup.writeEntry ("GameName", recording->gameName); 2143 configGroup.writeEntry ("Level", recording->level); 2144 configGroup.writeEntry ("Width", recording->width); 2145 configGroup.writeEntry ("Height", recording->height); 2146 configGroup.writeEntry ("Layout", recording->layout); 2147 configGroup.writeEntry ("Name", recording->levelName); 2148 configGroup.writeEntry ("Hint", recording->hint); 2149 configGroup.writeEntry ("DigWhileFalling", recording->digWhileFalling); 2150 configGroup.writeEntry ("Lives", (int) recording->lives); 2151 configGroup.writeEntry ("Score", (int) recording->score); 2152 configGroup.writeEntry ("Speed", (int) recording->speed); 2153 configGroup.writeEntry ("Mode", (int) recording->controlMode); 2154 configGroup.writeEntry ("KeyOption", (int)recording->keyOption); 2155 2156 QList<int> bytes; 2157 int ch = 0; 2158 int n = recording->content.size(); 2159 for (int i = 0; i < n; i++) { 2160 ch = (uchar)(recording->content.at(i)); 2161 bytes.append (ch); 2162 if (ch == 0) 2163 break; 2164 } 2165 configGroup.writeEntry ("Content", bytes); 2166 2167 bytes.clear(); 2168 ch = 0; 2169 n = recording->draws.size(); 2170 for (int i = 0; i < n; i++) { 2171 ch = (uchar)(recording->draws.at(i)); 2172 bytes.append (ch); 2173 if (ch == 0) 2174 break; 2175 } 2176 configGroup.writeEntry ("Draws", bytes); 2177 2178 configGroup.sync(); // Ensure that the entry goes to disk. 2179 } 2180 2181 bool KGrGame::loadRecording (const QString & dir, const QString & prefix, 2182 const int levelNo) 2183 { 2184 //qCDebug(KGOLDRUNNER_LOG) << prefix << levelNo; 2185 QString filename; 2186 if (! getRecordingName (dir, prefix, filename)) { 2187 qCDebug(KGOLDRUNNER_LOG) << "No file found by getRecordingName() for" << dir << prefix; 2188 return false; 2189 } 2190 QString groupName = prefix + QString::number(levelNo).rightJustified(3,QLatin1Char('0')); 2191 qCDebug(KGOLDRUNNER_LOG) << "loadRecording" << filename << prefix << levelNo << groupName; 2192 2193 KConfig config (filename, KConfig::SimpleConfig); 2194 if (! config.hasGroup (groupName)) { 2195 qCDebug(KGOLDRUNNER_LOG) << "Group" << groupName << "NOT FOUND"; 2196 return false; 2197 } 2198 2199 KConfigGroup configGroup = config.group (groupName); 2200 QString blank; 2201 recording->dateTime = configGroup.readEntry ("DateTime", ""); 2202 recording->owner = (Owner)(configGroup.readEntry 2203 ("Owner", (int)(USER))); 2204 recording->rules = configGroup.readEntry ("Rules", (int)('T')); 2205 recording->prefix = configGroup.readEntry ("Prefix", ""); 2206 recording->gameName = configGroup.readEntry ("GameName", blank); 2207 recording->level = configGroup.readEntry ("Level", 1); 2208 recording->width = configGroup.readEntry ("Width", FIELDWIDTH); 2209 recording->height = configGroup.readEntry ("Height", FIELDHEIGHT); 2210 recording->layout = configGroup.readEntry ("Layout", QByteArray()); 2211 recording->levelName = configGroup.readEntry ("Name", blank); 2212 recording->hint = configGroup.readEntry ("Hint", blank); 2213 recording->digWhileFalling = configGroup.readEntry ("DigWhileFalling", 2214 true); 2215 recording->lives = configGroup.readEntry ("Lives", 5); 2216 recording->score = configGroup.readEntry ("Score", 0); 2217 recording->speed = configGroup.readEntry ("Speed", 10); 2218 recording->controlMode = configGroup.readEntry ("Mode", (int)MOUSE); 2219 recording->keyOption = configGroup.readEntry ("KeyOption", 2220 (int)CLICK_KEY); 2221 2222 // If demoType is DEMO or SOLVE, get the TRANSLATED gameName, levelName and 2223 // hint from current data (other recordings have been translated already). 2224 // Also get the CURRENT setting of digWhileFalling for this game and level 2225 // (in case the demo or solution file contains out-of-date settings). 2226 if ((demoType == DEMO) || (demoType == SOLVE)) { 2227 int index = -1; 2228 for (int i = 0; i < gameList.count(); i++) { // Find the game. 2229 if (gameList.at (i)->prefix == recording->prefix) { 2230 index = i; 2231 break; 2232 } 2233 } 2234 if (index >= 0) { 2235 // Get digWhileFalling flag and current translation of name of game. 2236 recording->digWhileFalling = gameList.at (index)->digWhileFalling; 2237 recording->gameName = gameList.at (index)->name; 2238 // qCDebug(KGOLDRUNNER_LOG) << "GAME" << gameList.at (index)->name << levelNo 2239 // << "set digWhileFalling to" 2240 // << gameList.at (index)->digWhileFalling; 2241 2242 // Read the current level data. 2243 KGrGameIO io (view); 2244 KGrLevelData levelData; 2245 2246 QString levelDir = (gameList.at (index)->owner == USER) ? 2247 userDataDir : systemDataDir; 2248 // Set digWhileFalling same as game, by default. 2249 levelData.digWhileFalling = gameList.at (index)->digWhileFalling; 2250 if (io.readLevelData (levelDir, recording->prefix, recording->level, 2251 levelData)) { 2252 // If there is a level name or hint, translate it. 2253 recording->levelName = (levelData.name.size() > 0) ? 2254 i18n (levelData.name.constData()) : QString(); 2255 recording->hint = (levelData.hint.size() > 0) ? 2256 i18n (levelData.hint.constData()) : QString(); 2257 recording->digWhileFalling = levelData.digWhileFalling; 2258 // qCDebug(KGOLDRUNNER_LOG) << "LEVEL" << gameList.at (index)->name << levelNo 2259 // << "digWhileFalling is NOW" 2260 // << levelData.digWhileFalling; 2261 } 2262 } 2263 } 2264 2265 QList<int> bytes = configGroup.readEntry ("Content", QList<int>()); 2266 int n = bytes.count(); 2267 recording->content.fill (0, n + 1); 2268 for (int i = 0; i < n; i++) { 2269 recording->content [i] = bytes.at (i); 2270 } 2271 2272 bytes.clear(); 2273 bytes = configGroup.readEntry ("Draws", QList<int>()); 2274 n = bytes.count(); 2275 recording->draws.fill (0, n + 1); 2276 for (int i = 0; i < n; i++) { 2277 recording->draws [i] = bytes.at (i); 2278 } 2279 return true; 2280 } 2281 2282 void KGrGame::loadSounds() 2283 { 2284 #ifdef KGAUDIO_BACKEND_OPENAL 2285 const qreal volumes [NumSounds] = {0.6, 0.3, 0.3, 0.6, 0.6, 1.8, 1.0, 1.0, 1.0, 1.0}; 2286 effects = new KGrSounds(); 2287 effects->setParent (this); // Delete at end of KGrGame. 2288 2289 fx[GoldSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation, 2290 QStringLiteral("themes/default/gold.ogg"))); 2291 fx[StepSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation, 2292 QStringLiteral("themes/default/step.wav"))); 2293 fx[ClimbSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation, 2294 QStringLiteral("themes/default/climb.wav"))); 2295 fx[FallSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation, 2296 QStringLiteral("themes/default/falling.ogg"))); 2297 fx[DigSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation, 2298 QStringLiteral("themes/default/dig.ogg"))); 2299 fx[LadderSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation, 2300 QStringLiteral("themes/default/ladder.ogg"))); 2301 fx[CompletedSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation, 2302 QStringLiteral("themes/default/completed.ogg"))); 2303 fx[DeathSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation, 2304 QStringLiteral("themes/default/death.ogg"))); 2305 fx[GameOverSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation, 2306 QStringLiteral("themes/default/gameover.ogg"))); 2307 fx[VictorySound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation, 2308 QStringLiteral("themes/default/victory.ogg"))); 2309 2310 // Gold and dig sounds are timed and are allowed to play for at least one 2311 // second, so that rapid sequences of those sounds are heard as overlapping. 2312 effects->setTimedSound (fx[GoldSound]); 2313 effects->setTimedSound (fx[DigSound]); 2314 2315 // Adjust the relative volumes of sounds to improve the overall balance. 2316 for (int i = 0; i < NumSounds; i++) { 2317 effects->setVolume (fx [i], volumes [i]); 2318 } 2319 #endif 2320 } 2321 2322 /******************************************************************************/ 2323 /********************** MESSAGE BOX WITH FREEZE *************************/ 2324 /******************************************************************************/ 2325 2326 void KGrGame::myMessage (QWidget * parent, const QString &title, const QString &contents) 2327 { 2328 // Halt the game while the message is displayed, if not already halted. 2329 freeze (ProgramPause, true); 2330 2331 KGrMessage::information (parent, title, contents); 2332 2333 // Unfreeze the game, but only if it was previously unfrozen. 2334 freeze (ProgramPause, false); 2335 } 2336 2337 #include "moc_kgrgame.cpp" 2338 2339 // vi: set sw=4 :