1 /*
2 SPDX-FileCopyrightText: 2003 Marco Krüger <grisuji@gmx.de>
3 SPDX-FileCopyrightText: 2003, 2009 Ian Wadham <iandw.au@gmail.com>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "kgrdebug.h"
9 #include "kgrgame.h"
10
11 #include "kgrview.h"
12 #include "kgrscene.h"
13 #include "kgrselector.h"
14 // KGoldrunner loads and plays .ogg files and requires OpenAL + SndFile > v0.21.
15 // Fallback to Phonon by the KgSound library does not give good results.
16 #include <libkdegames_capabilities.h>
17 #ifdef KGAUDIO_BACKEND_OPENAL
18 #include "kgrsounds.h"
19 #endif
20
21 #include "kgreditor.h"
22 #include "kgrlevelplayer.h"
23 #include "kgrdialog.h"
24 #include "kgrgameio.h"
25
26 #include <iostream>
27 #include <cstdlib>
28
29 #include <QByteArray>
30 #include <QDate>
31 #include <QDateTime>
32 #include <QLabel>
33 #include <QHeaderView>
34 #include <QPushButton>
35 #include <QSpacerItem>
36 #include <QStandardPaths>
37 #include <QStringList>
38 #include <QTextStream>
39 #include <QTimer>
40 #include <QTreeWidget>
41 #include <QTreeWidgetItem>
42 #include <QVBoxLayout>
43 #include <QFileInfo>
44 #include <QRandomGenerator>
45
46 #include <KConfigGroup>
47 #include <KGuiItem>
48 #include <KSharedConfig>
49 #include <KStandardGuiItem>
50 #include <KLocalizedString>
51 #include <KMessageBox>
52
53 #include "kgoldrunner_debug.h"
54
55 // TODO - Can we change over to KScoreDialog?
56
57 // Do NOT change KGoldrunner over to KScoreDialog until we have found a way
58 // to preserve high-score data pre-existing from the KGr high-score methods.
59 // #define USE_KSCOREDIALOG 1 // IDW - 11 Aug 07.
60
61 #ifdef USE_KSCOREDIALOG
62 #include <KScoreDialog>
63 #else
64
65 #endif
66
67 #define UserPause true
68 #define ProgramPause false
69 #define NewLevel true
70
71 /******************************************************************************/
72 /*********************** KGOLDRUNNER GAME CLASS *************************/
73 /******************************************************************************/
74
KGrGame(KGrView * theView,const QString & theSystemDir,const QString & theUserDir)75 KGrGame::KGrGame (KGrView * theView,
76 const QString & theSystemDir, const QString & theUserDir)
77 :
78 QObject (theView), // Game is destroyed if view closes.
79 levelPlayer (nullptr),
80 recording (nullptr),
81 playback (false),
82 view (theView),
83 scene (view->gameScene()),
84 systemDataDir (theSystemDir),
85 userDataDir (theUserDir),
86 level (0),
87 mainDemoName (QStringLiteral("demo")),
88 // mainDemoName ("CM"), // IDW test.
89 demoType (DEMO),
90 startupDemo (false),
91 programFreeze (false),
92 effects (nullptr),
93 fx (NumSounds),
94 soundOn (false),
95 stepsOn (false),
96 editor (nullptr)
97 {
98 dbgLevel = 0;
99
100 gameFrozen = false;
101
102 dyingTimer = new QTimer (this);
103 connect(dyingTimer, &QTimer::timeout, this, &KGrGame::finalBreath);
104
105 // Initialise random number generator.
106 randomGen = new QRandomGenerator (QRandomGenerator::global()->generate());
107 //qCDebug(KGOLDRUNNER_LOG) << "RANDOM NUMBER GENERATOR INITIALISED";
108
109 scene->setReplayMessage (i18n("Click anywhere to begin live play"));
110 }
111
~KGrGame()112 KGrGame::~KGrGame()
113 {
114 qDeleteAll(gameList);
115 delete randomGen;
116 delete levelPlayer;
117 delete recording;
118 #ifdef KGAUDIO_BACKEND_OPENAL
119 delete effects;
120 #endif
121 }
122
123 // Flags to control author's debugging aids.
124 bool KGrGame::bugFix = false; // Start game with dynamic bug-fix OFF.
125 bool KGrGame::logging = false; // Start game with dynamic logging OFF.
126
modeSwitch(const int action,int & selectedGame,int & selectedLevel)127 bool KGrGame::modeSwitch (const int action,
128 int & selectedGame, int & selectedLevel)
129 {
130 // If editing, check that the user has saved changes.
131 // Note: KGoldrunner::setEditMenu disables/enables SAVE_GAME, PAUSE,
132 // HIGH_SCORE, KILL_HERO, HINT and INSTANT_REPLAY, so they are not relevant
133 // here. All the other GameActions require a save-check.
134 if (! saveOK()) {
135 return false; // The user needs to go on editing.
136 }
137 bool result = true;
138 SelectAction slAction = SL_NONE;
139 switch (action) {
140 case NEW:
141 slAction = SL_ANY;
142 break;
143 case SOLVE:
144 slAction = SL_SOLVE;
145 break;
146 case SAVE_SOLUTION:
147 slAction = SL_SAVE_SOLUTION;
148 break;
149 case REPLAY_ANY:
150 slAction = SL_REPLAY;
151 break;
152 case LOAD:
153 // Run the Load Game dialog. Return false if failed or cancelled.
154 result = selectSavedGame (selectedGame, selectedLevel);
155 break;
156 case HIGH_SCORE: // (Disabled during demo/replay)
157 case KILL_HERO: // (Disabled during demo/replay)
158 case PAUSE:
159 case HINT:
160 return result; // If demo/replay, keep it going.
161 break;
162 default:
163 break;
164 }
165 if (slAction != SL_NONE) {
166 // Select game and level. Return false if failed or cancelled.
167 result = selectGame (slAction, selectedGame, selectedLevel);
168 }
169 if (playback && (result == true)) {
170 setPlayback (false); // If demo/replay, kill it.
171 }
172 return result;
173 }
174
gameActions(const int action)175 void KGrGame::gameActions (const int action)
176 {
177 int selectedGame = gameIndex;
178 int selectedLevel = level;
179 // For some actions, modeSwitch() calls the game and level selection dialog.
180 if (! modeSwitch (action, selectedGame, selectedLevel)) {
181 return;
182 }
183 switch (action) {
184 case NEW:
185 newGame (selectedLevel, selectedGame);
186 showTutorialMessages (level);
187 break;
188 case NEXT_LEVEL:
189 if (level >= levelMax) {
190 KGrMessage::information (view, i18n ("Play Next Level"),
191 i18n ("There are no more levels in this game."));
192 return;
193 }
194 level++;
195 //qCDebug(KGOLDRUNNER_LOG) << "Game" << gameList.at(gameIndex)->name << "level" << level;
196 newGame (level, gameIndex);
197 showTutorialMessages (level);
198 break;
199 case LOAD:
200 loadGame (selectedGame, selectedLevel);
201 break;
202 case SAVE_GAME:
203 saveGame();
204 break;
205 case PAUSE:
206 freeze (UserPause, (! gameFrozen));
207 break;
208 case HIGH_SCORE:
209 // Stop the action during the high-scores dialog.
210 freeze (ProgramPause, true);
211 showHighScores();
212 freeze (ProgramPause, false);
213 break;
214 case KILL_HERO:
215 if (levelPlayer && (! playback)) {
216 // Record the KILL_HERO code, then emit signal endLevel (DEAD).
217 levelPlayer->killHero();
218 }
219 break;
220 case HINT:
221 showHint();
222 break;
223 case DEMO:
224 setPlayback (true);
225 demoType = DEMO;
226 if (! startDemo (SYSTEM, mainDemoName, 1)) {
227 setPlayback (false);
228 }
229 break;
230 case SOLVE:
231 runReplay (SOLVE, selectedGame, selectedLevel);
232 break;
233 case SAVE_SOLUTION:
234 saveSolution (gameList.at (selectedGame)->prefix, selectedLevel);
235 break;
236 case INSTANT_REPLAY:
237 if (levelPlayer) {
238 startInstantReplay();
239 }
240 break;
241 case REPLAY_LAST:
242 replayLastLevel();
243 break;
244 case REPLAY_ANY:
245 runReplay (REPLAY_ANY, selectedGame, selectedLevel);
246 break;
247 default:
248 break;
249 }
250 }
251
editActions(const int action)252 void KGrGame::editActions (const int action)
253 {
254 bool editOK = true;
255 bool newEditor = (editor) ? false : true;
256 int editLevel = level;
257 // dbk << "Level" << level << prefix << gameIndex;
258 if (newEditor) {
259 if (action == SAVE_EDITS) {
260 KGrMessage::information (view, i18n ("Save Level"),
261 i18n ("Inappropriate action: you are not editing a level."));
262 return;
263 }
264
265 // If there is no editor running, start one.
266 freeze (ProgramPause, true);
267 editor = new KGrEditor (view, systemDataDir, userDataDir, gameList);
268 Q_EMIT setEditMenu (true); // Enable edit menu items and toolbar.
269 }
270
271 switch (action) {
272 case CREATE_LEVEL:
273 editOK = editor->createLevel (gameIndex);
274 break;
275 case EDIT_ANY:
276 editOK = editor->updateLevel (gameIndex, editLevel);
277 break;
278 case SAVE_EDITS:
279 editOK = editor->saveLevelFile ();
280 break;
281 case MOVE_LEVEL:
282 editOK = editor->moveLevelFile (gameIndex, editLevel);
283 break;
284 case DELETE_LEVEL:
285 editOK = editor->deleteLevelFile (gameIndex, editLevel);
286 break;
287 case CREATE_GAME:
288 editOK = editor->editGame (-1);
289 break;
290 case EDIT_GAME:
291 editOK = editor->editGame (gameIndex);
292 break;
293 default:
294 break;
295 }
296
297 if (newEditor) {
298 if (editOK) {
299 // If a level or demo is running, close it, with no win/lose result.
300 setPlayback (false);
301 if (levelPlayer) {
302 endLevel (NORMAL);
303 scene->deleteAllSprites();
304 }
305
306 Q_EMIT showLives (0);
307 Q_EMIT showScore (0);
308 }
309 else {
310 // Edit failed or was cancelled, so close the editor.
311 Q_EMIT setEditMenu (false); // Disable edit menu items and toolbar.
312 delete editor;
313 editor = nullptr;
314 }
315 freeze (ProgramPause, false);
316 }
317
318 if (! editOK) {
319 return; // Continue play, demo or previous edit.
320 }
321
322 int game = gameIndex, lev = level;
323 editor->getGameAndLevel (game, lev);
324
325 if (((game != gameIndex) || (lev != level)) && (lev != 0)) {
326 gameIndex = game;
327 prefix = gameList.at (gameIndex)->prefix;
328 level = lev;
329
330 //qCDebug(KGOLDRUNNER_LOG) << "Saving to KConfigGroup";
331 KConfigGroup gameGroup (KSharedConfig::openConfig(), "KDEGame");
332 gameGroup.writeEntry (QStringLiteral("GamePrefix"), prefix);
333 gameGroup.writeEntry (QStringLiteral("Level_") + prefix, level);
334 gameGroup.sync(); // Ensure that the entry goes to disk.
335 }
336 }
337
editToolbarActions(const int action)338 void KGrGame::editToolbarActions (const int action)
339 {
340 // If game-editor is inactive or action-code is not recognised, do nothing.
341 if (editor) {
342 switch (action) {
343 case EDIT_HINT:
344 // Edit the level-name or hint.
345 editor->editNameAndHint();
346 break;
347 case FREE:
348 case ENEMY:
349 case HERO:
350 case CONCRETE:
351 case BRICK:
352 case FBRICK:
353 case HLADDER:
354 case LADDER:
355 case NUGGET:
356 case BAR:
357 // Set the next object to be painted in the level-layout.
358 editor->setEditObj (action);
359 break;
360 default:
361 break;
362 }
363 }
364 }
365
settings(const int action)366 void KGrGame::settings (const int action)
367 {
368 // TODO - Bad - Configure Keys does not pause a demo. IDW
369 KConfigGroup gameGroup (KSharedConfig::openConfig(), "KDEGame");
370 bool onOff = false;
371 switch (action) {
372 case PLAY_SOUNDS:
373 case PLAY_STEPS:
374 toggleSoundsOnOff (action);
375 break;
376 case STARTUP_DEMO:
377 // Toggle the startup demo to be on or off.
378 onOff = (! gameGroup.readEntry ("StartingDemo", true));
379 gameGroup.writeEntry ("StartingDemo", onOff);
380 break;
381 case MOUSE:
382 case KEYBOARD:
383 case LAPTOP:
384 setControlMode (action);
385 gameGroup.writeEntry ("ControlMode", action);
386 break;
387 case CLICK_KEY:
388 case HOLD_KEY:
389 if (controlMode == KEYBOARD) {
390 setHoldKeyOption (action);
391 gameGroup.writeEntry ("HoldKeyOption", action);
392 }
393 break;
394 case NORMAL_SPEED:
395 case BEGINNER_SPEED:
396 case CHAMPION_SPEED:
397 setTimeScale (action);
398 gameGroup.writeEntry ("SpeedLevel", action);
399 gameGroup.writeEntry ("ActualSpeed", timeScale);
400 break;
401 case INC_SPEED:
402 case DEC_SPEED:
403 setTimeScale (action);
404 gameGroup.writeEntry ("ActualSpeed", timeScale);
405 break;
406 default:
407 break;
408 }
409 gameGroup.sync();
410 }
411
setInitialTheme(const QString & themeFilepath)412 void KGrGame::setInitialTheme (const QString & themeFilepath)
413 {
414 initialThemeFilepath = themeFilepath;
415 }
416
initGame()417 void KGrGame::initGame()
418 {
419 #ifndef KGAUDIO_BACKEND_OPENAL
420 KGrMessage::information (view, i18n ("No Sound"),
421 i18n ("Warning: This copy of KGoldrunner has no sound.\n"
422 "\n"
423 "This is because no development versions of the OpenAL and "
424 "SndFile libraries were present when it was compiled and built."),
425 "WarningNoSound");
426 #endif
427 //qCDebug(KGOLDRUNNER_LOG) << "Entered, draw the initial graphics now ...";
428
429 // Get the most recent collection and level that was played by this user.
430 // If he/she has never played before, set it to Tutorial, level 1.
431 KConfigGroup gameGroup (KSharedConfig::openConfig(), "KDEGame");
432 QString prevGamePrefix = gameGroup.readEntry ("GamePrefix", "tute");
433 int prevLevel = gameGroup.readEntry (QLatin1String("Level_") + prevGamePrefix, 1);
434
435 //qCDebug(KGOLDRUNNER_LOG)<< "Config() Game and Level" << prevGamePrefix << prevLevel;
436
437 // Use that game and level, if it is among the current games.
438 // Otherwise, use the first game in the list and level 1.
439 gameIndex = 0;
440 level = 1;
441 int n = 0;
442 dbk1 << gameIndex << level << "Search:" << prevGamePrefix << prevLevel;
443 for (KGrGameData * gameData : std::as_const(gameList)) {
444 dbk1 << "Trying:" << n << gameData->prefix;
445 if (gameData->prefix == prevGamePrefix) {
446 gameIndex = n;
447 level = prevLevel;
448 dbk1 << "FOUND:" << gameIndex << prevGamePrefix << level;
449 break;
450 }
451 n++;
452 }
453
454 // Set control-mode, hold-key option (for when K/B is used) and game-speed.
455 settings (gameGroup.readEntry ("ControlMode", (int) MOUSE));
456 Q_EMIT setToggle (((controlMode == MOUSE) ? "mouse_mode" :
457 ((controlMode == KEYBOARD) ? "keyboard_mode" :
458 "laptop_mode")), true);
459
460 holdKeyOption = gameGroup.readEntry ("HoldKeyOption", (int) CLICK_KEY);
461 Q_EMIT setToggle (((holdKeyOption == CLICK_KEY) ? "click_key" :
462 "hold_key"), true);
463
464 int speedLevel = gameGroup.readEntry ("SpeedLevel", (int) NORMAL_SPEED);
465 settings (speedLevel);
466 Q_EMIT setToggle ((speedLevel == NORMAL_SPEED) ? "normal_speed" :
467 ((speedLevel == BEGINNER_SPEED) ? "beginner_speed" :
468 "champion_speed"), true);
469 timeScale = gameGroup.readEntry ("ActualSpeed", 10);
470
471 #ifdef KGAUDIO_BACKEND_OPENAL
472 // Set up sounds, if required in config.
473 soundOn = gameGroup.readEntry ("Sound", false);
474 //qCDebug(KGOLDRUNNER_LOG) << "Sound" << soundOn;
475 if (soundOn) {
476 loadSounds();
477 effects->setMuted (false);
478 }
479 Q_EMIT setToggle ("options_sounds", soundOn);
480
481 stepsOn = gameGroup.readEntry ("StepSounds", false);
482 //qCDebug(KGOLDRUNNER_LOG) << "StepSounds" << stepsOn;
483 Q_EMIT setToggle ("options_steps", stepsOn);
484 #endif
485
486 dbk1 << "Owner" << gameList.at (gameIndex)->owner
487 << gameList.at (gameIndex)->name << level;
488
489 setPlayback (gameGroup.readEntry ("StartingDemo", true));
490 if (playback && (startDemo (SYSTEM, mainDemoName, 1))) {
491 startupDemo = true; // Demo is starting.
492 demoType = DEMO;
493 }
494 else {
495 setPlayback (false); // Load previous session's level.
496 newGame (level, gameIndex);
497 quickStartDialog();
498 }
499 Q_EMIT setToggle ("options_demo", startupDemo);
500
501 // Allow a short break, to display the graphics, then use the demo delay-time
502 // or the reaction-time to the quick-start dialog to do some more rendering.
503 QTimer::singleShot (10, scene, &KGrScene::preRenderSprites);
504
505 } // End KGrGame::initGame()
506
getRecordingName(const QString & dir,const QString & pPrefix,QString & filename)507 bool KGrGame::getRecordingName (const QString & dir, const QString & pPrefix,
508 QString & filename)
509 {
510 QString recFile = dir + QLatin1String("rec_") + pPrefix + QLatin1String(".txt");
511 QFileInfo fileInfo (recFile);
512 bool recOK = fileInfo.exists() && fileInfo.isReadable();
513 filename = QString ();
514
515 if (demoType == SOLVE) {
516 // Look for a solution-file name in User or System area.
517 QString solFile = dir + QLatin1String("sol_") + pPrefix + QLatin1String(".txt");
518 fileInfo.setFile (solFile);
519 bool solOK = fileInfo.exists() && fileInfo.isReadable();
520 if (solOK) {
521 filename = solFile; // Accept sol_* in User or System area.
522 return true;
523 }
524 else if (recOK && (dir == systemDataDir)) {
525 filename = recFile; // Accept rec_* (old name) in System area only.
526 return true;
527 }
528 }
529 else if (recOK) {
530 filename = recFile; // Accept rec_* name for demo or recording.
531 return true;
532 }
533 // File not found or not readable.
534 return false;
535 }
536
startDemo(const Owner demoOwner,const QString & pPrefix,const int levelNo)537 bool KGrGame::startDemo (const Owner demoOwner, const QString & pPrefix,
538 const int levelNo)
539 {
540 // Find the relevant file and the list of levels it contains.
541 QString dir = (demoOwner == SYSTEM) ? systemDataDir : userDataDir;
542 QString filepath;
543 if (! getRecordingName (dir, pPrefix, filepath)) {
544 qCDebug(KGOLDRUNNER_LOG) << "No file found by getRecordingName() for" << dir << pPrefix;
545 return false;
546 }
547 dbk1 << "Owner" << demoOwner << "type" << demoType
548 << pPrefix << levelNo << "filepath" << filepath;
549 KConfig config (filepath, KConfig::SimpleConfig);
550 QStringList demoList = config.groupList();
551 dbk1 << "DEMO LIST" << demoList.count() << demoList;
552
553 // Find the required level (e.g. CM007) in the list available on the file.
554 QString s = pPrefix + QString::number(levelNo).rightJustified(3,QLatin1Char('0'));
555 int index = demoList.indexOf (s) + 1;
556 dbk1 << "DEMO looking for" << s << "found at" << index;
557 if (index <= 0) {
558 setPlayback (false);
559 qCDebug(KGOLDRUNNER_LOG) << "DEMO not found in" << filepath << s << pPrefix << levelNo;
560 return false;
561 }
562
563 // Load and run the recorded level(s).
564 if (playLevel (demoOwner, pPrefix, levelNo, (! NewLevel))) {
565 playbackOwner = demoOwner;
566 playbackPrefix = pPrefix;
567 playbackIndex = levelNo;
568
569 // Play back all levels in Main Demo or just one level in other replays.
570 playbackMax = ((demoType == DEMO) && (playbackPrefix == mainDemoName)) ?
571 demoList.count() : levelNo;
572 if (levelPlayer) {
573 levelPlayer->prepareToPlay();
574 }
575 qCDebug(KGOLDRUNNER_LOG) << "DEMO started ..." << filepath << pPrefix << levelNo;
576 return true;
577 }
578 else {
579 setPlayback (false);
580 qCDebug(KGOLDRUNNER_LOG) << "DEMO failed ..." << filepath << pPrefix << levelNo;
581 return false;
582 }
583 }
584
runNextDemoLevel()585 void KGrGame::runNextDemoLevel()
586 {
587 // dbk << "index" << playbackIndex << "max" << playbackMax << playbackPrefix
588 // << "owner" << playbackOwner;
589 if (playbackIndex < playbackMax) {
590 playbackIndex++;
591 if (playLevel (playbackOwner, playbackPrefix,
592 playbackIndex, (! NewLevel))) {
593 if (levelPlayer) {
594 levelPlayer->prepareToPlay();
595 }
596 qCDebug(KGOLDRUNNER_LOG) << "DEMO continued ..." << playbackPrefix << playbackIndex;
597 return;
598 }
599 }
600 finishDemo();
601 }
602
finishDemo()603 void KGrGame::finishDemo()
604 {
605 setPlayback (false);
606 newGame (level, gameIndex);
607 if (startupDemo) {
608 // The startup demo was running, so run the Quick Start Dialog now.
609 startupDemo = false;
610 quickStartDialog();
611 }
612 else {
613 // Otherwise, run the last level used.
614 showTutorialMessages (level);
615 }
616 }
617
interruptDemo()618 void KGrGame::interruptDemo()
619 {
620 qCDebug(KGOLDRUNNER_LOG) << "DEMO interrupted ...";
621 if ((demoType == INSTANT_REPLAY) || (demoType == REPLAY_LAST)) {
622 setPlayback (false);
623 levelMax = gameList.at (gameIndex)->nLevels;
624 freeze (UserPause, true);
625 KGrMessage::information (view, i18n ("Game Paused"),
626 i18n ("The replay has stopped and the game is pausing while you "
627 "prepare to go on playing. Please press the Pause key "
628 "(default P or Esc) when you are ready."),
629 QStringLiteral("Show_interruptDemo"));
630 }
631 else {
632 finishDemo(); // Initial DEMO, main DEMO or SOLVE.
633 }
634 }
635
startInstantReplay()636 void KGrGame::startInstantReplay()
637 {
638 // dbk << "Start INSTANT_REPLAY";
639 demoType = INSTANT_REPLAY;
640
641 // Terminate current play.
642 delete levelPlayer;
643 levelPlayer = nullptr;
644 scene->deleteAllSprites();
645
646 // Redisplay the starting score and lives.
647 lives = recording->lives;
648 Q_EMIT showLives (lives);
649 score = recording->score;
650 Q_EMIT showScore (score);
651
652 // Restart the level in playback mode.
653 setPlayback (true);
654 setupLevelPlayer();
655 levelPlayer->prepareToPlay();
656 }
657
replayLastLevel()658 void KGrGame::replayLastLevel()
659 {
660 // Replay the last game and level completed by the player.
661 KConfigGroup gameGroup (KSharedConfig::openConfig(), "KDEGame");
662 QString lastPrefix = gameGroup.readEntry ("LastGamePrefix", "");
663 int lastLevel = gameGroup.readEntry ("LastLevel", -1);
664
665 if (lastLevel > 0) {
666 setPlayback (true);
667 demoType = REPLAY_LAST;
668 if (! startDemo (USER, lastPrefix, lastLevel)) {
669 setPlayback (false);
670 KGrMessage::information (view, i18n ("Replay Last Level"),
671 i18n ("ERROR: Could not find and replay a recording of "
672 "the last level you played."));
673 }
674 }
675 else {
676 KGrMessage::information (view, i18n ("Replay Last Level"),
677 i18n ("There is no last level to replay. You need to play a "
678 "level to completion, win or lose, before you can use "
679 "the Replay Last Level action."));
680 }
681 }
682
683 /******************************************************************************/
684 /********************** QUICK-START DIALOG AND SLOTS ************************/
685 /******************************************************************************/
686
quickStartDialog()687 void KGrGame::quickStartDialog()
688 {
689 // Make sure the game does not start during the Quick Start dialog.
690 freeze (ProgramPause, true);
691
692 qs = new QDialog (view);
693
694 // Modal dialog, 4 buttons, vertically: the PLAY button has the focus.
695 qs->setWindowTitle (i18nc("@title:window", "Quick Start"));
696 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
697
698 QPushButton *newGameButton = new QPushButton;
699 buttonBox->addButton(newGameButton, QDialogButtonBox::ActionRole);
700 QPushButton *useMenuButton = new QPushButton;
701 buttonBox->addButton(useMenuButton, QDialogButtonBox::ActionRole);
702
703 QHBoxLayout *leftIconRightButtonsLayout = new QHBoxLayout;
704 qs->setLayout(leftIconRightButtonsLayout);
705
706 QVBoxLayout *leftButtonLayout = new QVBoxLayout;
707 buttonBox->button(QDialogButtonBox::Ok)->setFocus();
708 leftButtonLayout->addWidget(buttonBox);
709 buttonBox->setOrientation (Qt::Vertical);
710
711 // Set up the PLAY button.
712 buttonBox->button (QDialogButtonBox::Ok)->setText(i18nc ("Button text: start playing a game", "&PLAY"));
713 buttonBox->button (QDialogButtonBox::Ok)->setToolTip(i18n ("Start playing this level"));
714 buttonBox->button (QDialogButtonBox::Ok)->setWhatsThis(
715 i18n ("Set up to start playing the game and level being shown, "
716 "as soon as you click, move the mouse or press a key"));
717
718 // Set up the Quit button.
719 buttonBox->button (QDialogButtonBox::Cancel)->setText(i18n ("&Quit"));
720 buttonBox->button (QDialogButtonBox::Cancel)->setToolTip(i18n ("Close KGoldrunner"));
721
722 // Set up the New Game button.
723 newGameButton->setText(i18n ("&New Game..."));
724 newGameButton->setToolTip (i18n ("Start a different game or level"));
725 newGameButton->setWhatsThis(
726 i18n ("Use the Select Game dialog box to choose a "
727 "different game or level and start playing it"));
728
729 // Set up the Use Menu button.
730 useMenuButton->setText(i18n ("&Use Menu"));
731 useMenuButton->setToolTip(
732 i18n ("Use the menus to choose other actions"));
733 useMenuButton->setWhatsThis(
734 i18n ("Before playing, use the menus to choose other actions, "
735 "such as loading a saved game or changing the theme"));
736
737 // Add the KGoldrunner application icon to the dialog box.
738 QLabel * logo = new QLabel();
739 QIcon test = QIcon::fromTheme(QStringLiteral("kgoldrunner"));
740 logo->setPixmap(test.pixmap(240));
741 logo->setAlignment (Qt::AlignTop | Qt::AlignHCenter);
742 logo->setAlignment (Qt::AlignTop | Qt::AlignHCenter | Qt::AlignLeft);
743
744 leftIconRightButtonsLayout->addWidget(logo);
745 leftIconRightButtonsLayout->addLayout(leftButtonLayout);
746
747 connect(buttonBox, &QDialogButtonBox::accepted, this, &KGrGame::quickStartPlay);
748 connect(buttonBox, &QDialogButtonBox::rejected, this, &KGrGame::quickStartQuit);
749 connect(newGameButton, &QPushButton::clicked, this, &KGrGame::quickStartNewGame);
750 connect(useMenuButton, &QPushButton::clicked, this, &KGrGame::quickStartUseMenu);
751
752 qs->show();
753 }
754
quickStartPlay()755 void KGrGame::quickStartPlay()
756 {
757 // KDialog calls QDialog::accept() after the OK slot, so must hide it
758 // now, to avoid interference with any tutorial messages there may be.
759 qs->hide();
760 freeze (ProgramPause, false);
761 showTutorialMessages (level); // Level has already been loaded.
762 }
763
quickStartNewGame()764 void KGrGame::quickStartNewGame()
765 {
766 qs->accept();
767 freeze (ProgramPause, false);
768
769 int selectedGame = gameIndex;
770 int selectedLevel = level;
771 if (modeSwitch (NEW, selectedGame, selectedLevel)) {
772 newGame (selectedLevel, selectedGame);
773 }
774 showTutorialMessages (selectedLevel);
775 }
776
quickStartUseMenu()777 void KGrGame::quickStartUseMenu()
778 {
779 qs->accept();
780 freeze (ProgramPause, false);
781 freeze (UserPause, true);
782 KGrMessage::information (view, i18n ("Game Paused"),
783 i18n ("The game is halted. You will need to press the Pause key "
784 "(default P or Esc) when you are ready to play."));
785
786 if (levelPlayer) {
787 levelPlayer->prepareToPlay();
788 }
789 }
790
quickStartQuit()791 void KGrGame::quickStartQuit()
792 {
793 Q_EMIT quitGame();
794 }
795
796 /******************************************************************************/
797 /************************* LEVEL SELECTION PROCEDURE ************************/
798 /******************************************************************************/
799
selectGame(const SelectAction slAction,int & selectedGame,int & selectedLevel)800 bool KGrGame::selectGame (const SelectAction slAction,
801 int & selectedGame, int & selectedLevel)
802 {
803 // Halt the game during the dialog.
804 freeze (ProgramPause, true);
805
806 // Run the game and level selection dialog.
807 KGrSLDialog * sl = new KGrSLDialog (slAction, selectedLevel, selectedGame,
808 gameList, systemDataDir, userDataDir,
809 view);
810 bool selected = sl->selectLevel (selectedGame, selectedLevel);
811 delete sl;
812
813 //qCDebug(KGOLDRUNNER_LOG) << "After dialog - programFreeze" << programFreeze;
814 //qCDebug(KGOLDRUNNER_LOG) << "selected" << selected << "gameFrozen" << gameFrozen;
815 //qCDebug(KGOLDRUNNER_LOG) << "selectedGame" << selectedGame
816 // << "prefix" << gameList.at(selectedGame)->prefix
817 // << "selectedLevel" << selectedLevel;
818 // Unfreeze the game, but only if it was previously unfrozen.
819 freeze (ProgramPause, false);
820 return selected;
821 }
822
runReplay(const int action,const int selectedGame,const int selectedLevel)823 void KGrGame::runReplay (const int action,
824 const int selectedGame, const int selectedLevel)
825 {
826 if (action == SOLVE) {
827 setPlayback (true);
828 demoType = SOLVE;
829 if (startDemo // Has the user saved a solution to this level?
830 (USER, gameList.at (selectedGame)->prefix, selectedLevel)) {
831 }
832 else { // If not, look for a released solution.
833 setPlayback (true); // Set playback again (startDemo() cleared it).
834 if (! startDemo
835 (SYSTEM, gameList.at (selectedGame)->prefix, selectedLevel)) {
836 KGrMessage::information (view, i18n ("Show a Solution"),
837 i18n ("Sorry, although all levels of KGoldrunner can be "
838 "solved, no solution has been recorded yet for the "
839 "level you selected."), QStringLiteral("Show_noSolutionRecorded"));
840 }
841 }
842 }
843 else if (action == REPLAY_ANY) {
844 setPlayback (true);
845 demoType = REPLAY_ANY;
846 if (! startDemo
847 (USER, gameList.at (selectedGame)->prefix, selectedLevel)) {
848 KGrMessage::information (view, i18n ("Replay Any Level"),
849 i18n ("Sorry, you do not seem to have played and recorded "
850 "the selected level before."), QStringLiteral("Show_noReplay"));
851 }
852 }
853 }
854
855 /******************************************************************************/
856 /*************************** MAIN GAME PROCEDURES ***************************/
857 /******************************************************************************/
858
newGame(const int lev,const int newGameIndex)859 void KGrGame::newGame (const int lev, const int newGameIndex)
860 {
861 scene->goToBlack();
862
863 KGrGameData * gameData = gameList.at (newGameIndex);
864 level = lev;
865 gameIndex = newGameIndex;
866 owner = gameData->owner;
867 prefix = gameData->prefix;
868 levelMax = gameData->nLevels;
869
870 lives = 5; // Start with 5 lives.
871 score = 0;
872 startScore = 0;
873
874 Q_EMIT showLives (lives);
875 Q_EMIT showScore (score);
876
877 playLevel (owner, prefix, level, NewLevel);
878 }
879
playLevel(const Owner fileOwner,const QString & prefix,const int levelNo,const bool newLevel)880 bool KGrGame::playLevel (const Owner fileOwner, const QString & prefix,
881 const int levelNo, const bool newLevel)
882 {
883 // If the game-editor is active, terminate it.
884 if (editor) {
885 Q_EMIT setEditMenu (false); // Disable edit menu items and toolbar.
886 delete editor;
887 editor = nullptr;
888 }
889
890 // If there is a level being played, kill it, with no win/lose result.
891 if (levelPlayer) {
892 endLevel (NORMAL);
893 }
894
895 // Clean up any sprites remaining from a previous level. This is done late,
896 // so that the player has a little time to observe how the level ended.
897 scene->deleteAllSprites();
898
899 // Set up to record or play back: load either level-data or recording-data.
900 if (! initRecordingData (fileOwner, prefix, levelNo, playback)) {
901 return false;
902 }
903 else if (playback) {
904 // Set up and display the starting score and lives.
905 lives = recording->lives;
906 Q_EMIT showLives (lives);
907 score = recording->score;
908 Q_EMIT showScore (score);
909 }
910
911 scene->setLevel (levelNo); // Switch and render background if reqd.
912 scene->fadeIn (true); // Then run the fade-in animation.
913 startScore = score; // The score we will save, if asked.
914
915 // Create a level player, initialised and ready for play or replay to start.
916 setupLevelPlayer();
917
918 levelName = recording->levelName;
919 levelHint = recording->hint;
920
921 // Indicate on the menus whether there is a hint for this level.
922 Q_EMIT hintAvailable (levelHint.length() > 0);
923
924 // Re-draw the playfield frame, level title and figures.
925 scene->setTitle (getTitle());
926
927 // If we are starting a new level, save it in the player's config file.
928 if (newLevel && (level != 0)) { // But do not save the "ENDE" level.
929 KConfigGroup gameGroup (KSharedConfig::openConfig(), "KDEGame");
930 gameGroup.writeEntry ("GamePrefix", prefix);
931 gameGroup.writeEntry (QStringLiteral("Level_") + prefix, level);
932 gameGroup.sync(); // Ensure that the entry goes to disk.
933 }
934
935 return true;
936 }
937
setupLevelPlayer()938 void KGrGame::setupLevelPlayer()
939 {
940 levelPlayer = new KGrLevelPlayer (this, randomGen);
941
942 levelPlayer->init (view, recording, playback, gameFrozen);
943 levelPlayer->setTimeScale (recording->speed);
944
945 // Use queued connections here, to ensure that levelPlayer has finished
946 // executing and can be deleted when control goes to the relevant slot.
947 connect(levelPlayer, &KGrLevelPlayer::endLevel, this, &KGrGame::endLevel, Qt::QueuedConnection);
948 if (playback) {
949 connect(levelPlayer, &KGrLevelPlayer::interruptDemo, this, &KGrGame::interruptDemo, Qt::QueuedConnection);
950 }
951 }
952
incScore(const int n)953 void KGrGame::incScore (const int n)
954 {
955 score = score + n; // SCORING: trap enemy 75, kill enemy 75,
956 Q_EMIT showScore (score); // collect gold 250, complete the level 1500.
957 }
958
playSound(const int n,const bool onOff)959 void KGrGame::playSound (const int n, const bool onOff)
960 {
961 #ifdef KGAUDIO_BACKEND_OPENAL
962 if (! effects) {
963 return; // Sound is off and not yet loaded.
964 }
965 static int fallToken = -1;
966 if (onOff) {
967 int token = -1;
968 if (stepsOn || ((n != StepSound) && (n != ClimbSound))) {
969 token = effects->play (fx [n]);
970 }
971 if (n == FallSound) {
972 fallToken = token;
973 }
974 }
975 // Only the falling sound can get individually turned off.
976 else if ((n == FallSound) && (fallToken >= 0)) {
977 effects->stop (fallToken);
978 fallToken = -1;
979 }
980 #endif
981 }
982
endLevel(const int result)983 void KGrGame::endLevel (const int result)
984 {
985 // dbk << "Return to KGrGame, result:" << result;
986
987 #ifdef KGAUDIO_BACKEND_OPENAL
988 if (effects) { // If sounds have been loaded, cut off
989 effects->stopAllSounds(); // all sounds that are in progress.
990 }
991 #endif
992
993 if (! levelPlayer) {
994 return; // Not playing a level.
995 }
996
997 if (playback && (result == UNEXPECTED_END)) {
998 if (demoType == INSTANT_REPLAY) {
999 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 (), "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
herosDead()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
finalBreath()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
repeatLevel()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
levelCompleted()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
goUpOneLevel()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
setControlMode(const int mode)1157 void KGrGame::setControlMode (const int mode)
1158 {
1159 // Enable/disable keyboard-mode options.
1160 bool enableDisable = (mode == KEYBOARD);
1161 Q_EMIT setAvail ("click_key", enableDisable);
1162 Q_EMIT setAvail ("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
setHoldKeyOption(const int option)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
setTimeScale(const int action)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
inEditMode()1209 bool KGrGame::inEditMode()
1210 {
1211 return (editor != nullptr); // Return true if the game-editor is active.
1212 }
1213
toggleSoundsOnOff(const int action)1214 void KGrGame::toggleSoundsOnOff (const int action)
1215 {
1216 const char * setting = (action == PLAY_SOUNDS) ? "Sound" : "StepSounds";
1217 KConfigGroup gameGroup (KSharedConfig::openConfig(), "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
freeze(const bool userAction,const bool on_off)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
showHint()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
showTutorialMessages(int levelNo)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
setPlayback(const bool onOff)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 ("game_highscores", enableDisable);
1348 Q_EMIT setAvail ("kill_hero", enableDisable);
1349
1350 Q_EMIT setAvail ("mouse_mode", enableDisable);
1351 Q_EMIT setAvail ("keyboard_mode", enableDisable);
1352 Q_EMIT setAvail ("laptop_mode", enableDisable);
1353
1354 Q_EMIT setAvail ("click_key", enableDisable);
1355 Q_EMIT setAvail ("hold_key", enableDisable);
1356
1357 Q_EMIT setAvail ("normal_speed", enableDisable);
1358 Q_EMIT setAvail ("beginner_speed", enableDisable);
1359 Q_EMIT setAvail ("champion_speed", enableDisable);
1360 Q_EMIT setAvail ("increase_speed", enableDisable);
1361 Q_EMIT setAvail ("decrease_speed", enableDisable);
1362 }
1363 scene->showReplayMessage (onOff);
1364 playback = onOff;
1365 }
1366
getDirectory(Owner o)1367 QString KGrGame::getDirectory (Owner o)
1368 {
1369 return ((o == SYSTEM) ? systemDataDir : userDataDir);
1370 }
1371
getTitle()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
kbControl(const int dirn,const bool pressed)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::questionYesNo (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::Yes:
1425 case KMessageBox::Ok:
1426 case KMessageBox::Continue:
1427 settings (KEYBOARD);
1428 Q_EMIT setToggle ("keyboard_mode", true); // Adjust Settings menu.
1429 break;
1430 case KMessageBox::No:
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
saveGame()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
selectSavedGame(int & selectedGame,int & selectedLevel)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 = loadedData.midRef (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
loadGame(const int game,const int lev)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
saveOK()1595 bool KGrGame::saveOK()
1596 {
1597 return (editor ? (editor->saveOK()) : true);
1598 }
1599
1600 /******************************************************************************/
1601 /************************** HIGH-SCORE PROCEDURES ***************************/
1602 /******************************************************************************/
1603
checkHighScore()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
showHighScores()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 (QStringList() <<
1880 i18nc ("1, 2, 3 etc.", "Rank") <<
1881 i18nc ("Person", "Name") <<
1882 i18nc ("Game level reached", "Level") <<
1883 i18n ("Score") <<
1884 i18n ("Date"));
1885 scores->setRootIsDecorated (false);
1886
1887 hs-> setWindowTitle (i18nc("@title:window", "High Scores"));
1888
1889 // Read and display the users, levels and scores from the high score file.
1890 scores->clear();
1891 s1.setDevice (&high1);
1892 n = 0;
1893 while ((! s1.atEnd()) && (n < 10)) {
1894 char * prevUser;
1895 char * prevDate;
1896 s1 >> prevUser;
1897 s1 >> prevLevel;
1898 s1 >> prevScore;
1899 s1 >> prevDate;
1900
1901 // prevUser has been saved on file as UTF-8 to allow non=ASCII chars
1902 // in the user's name (e.g. "Krüger" is encoded as "Krüger" in UTF-8).
1903 QStringList data;
1904 data << QString().setNum (n+1)
1905 << QString().fromUtf8 (prevUser)
1906 << QString().setNum (prevLevel)
1907 << QString().setNum (prevScore)
1908 << QString().fromUtf8 (prevDate);
1909 QTreeWidgetItem * score = new QTreeWidgetItem (data);
1910 score->setTextAlignment (0, Qt::AlignRight); // Rank.
1911 score->setTextAlignment (1, Qt::AlignLeft); // Name.
1912 score->setTextAlignment (2, Qt::AlignRight); // Level.
1913 score->setTextAlignment (3, Qt::AlignRight); // Score.
1914 score->setTextAlignment (4, Qt::AlignLeft); // Date.
1915 if (prevScore > 0) { // Skip score 0 (bad file-data).
1916 scores->addTopLevelItem (score); // Show score > 0.
1917 if (n == 0) {
1918 scores->setCurrentItem (score); // Highlight the highest score.
1919 }
1920 n++;
1921 }
1922
1923 delete prevUser;
1924 delete prevDate;
1925 }
1926
1927 // Adjust the columns to fit the data.
1928 scores->header()->setSectionResizeMode (0, QHeaderView::ResizeToContents);
1929 scores->header()->setSectionResizeMode (1, QHeaderView::ResizeToContents);
1930 scores->header()->setSectionResizeMode (2, QHeaderView::ResizeToContents);
1931 scores->header()->setSectionResizeMode (3, QHeaderView::ResizeToContents);
1932 scores->header()->setSectionResizeMode (4, QHeaderView::ResizeToContents);
1933 scores->header()->setMinimumSectionSize (-1); // Font metrics size.
1934
1935 QFrame * separator = new QFrame (hs);
1936 separator->setFrameStyle (QFrame::HLine + QFrame::Sunken);
1937 mainLayout->addWidget (separator);
1938
1939 QHBoxLayout *hboxLayout1 = new QHBoxLayout();
1940 hboxLayout1->setSpacing (spacing);
1941 QSpacerItem * spacerItem = new QSpacerItem (40, 20, QSizePolicy::Expanding,
1942 QSizePolicy::Minimum);
1943 hboxLayout1->addItem (spacerItem);
1944 QPushButton * OK = new QPushButton(hs);
1945 KGuiItem::assign(OK,KStandardGuiItem::close());
1946 OK-> setShortcut (Qt::Key_Return);
1947 OK-> setMaximumWidth (100);
1948 hboxLayout1->addWidget (OK);
1949 mainLayout-> addLayout (hboxLayout1, 5);
1950 // int w = (view->size().width()*4)/10;
1951 // hs-> setMinimumSize (w, w);
1952
1953 // QPoint p = view->mapToGlobal (QPoint (0,0));
1954 // hs-> move (p.x() + 50, p.y() + 50);
1955
1956 // Start up the dialog box.
1957 connect(OK, &QPushButton::clicked, hs, &QDialog::accept);
1958 hs-> exec();
1959
1960 delete hs;
1961 #endif
1962 }
1963
1964 /******************************************************************************/
1965 /************************** AUTHORS' DEBUGGING AIDS **************************/
1966 /******************************************************************************/
1967
dbgControl(const int code)1968 void KGrGame::dbgControl (const int code)
1969 {
1970 if (playback) {
1971 levelPlayer->interruptPlayback(); // Will emit interruptDemo().
1972 return;
1973 }
1974 // qCDebug(KGOLDRUNNER_LOG) << "Debug code =" << code;
1975 if (levelPlayer && gameFrozen) {
1976 levelPlayer->dbgControl (code);
1977 }
1978 }
1979
initGameLists()1980 bool KGrGame::initGameLists()
1981 {
1982 // Initialise the lists of games (i.e. collections of levels).
1983
1984 // System games are the ones distributed with KDE Games. They cannot be
1985 // edited or deleted, but they can be copied, edited and saved into a user's
1986 // game. Users' games and levels can be freely created, edited and deleted.
1987
1988 owner = SYSTEM; // Use system levels initially.
1989 if (! loadGameData (SYSTEM)) // Load list of system games.
1990 return (false); // If no system games, abort.
1991 loadGameData (USER); // Load user's list of games.
1992 // If none, don't worry.
1993 for (int i = 0; i < gameList.count(); i++) {
1994 dbk1 << i << gameList.at(i)->prefix << gameList.at(i)->name;
1995 }
1996 return (true);
1997 }
1998
loadGameData(Owner o)1999 bool KGrGame::loadGameData (Owner o)
2000 {
2001 KGrGameIO io (view);
2002 QList<KGrGameData *> gList;
2003 QString filePath;
2004 IOStatus status = io.fetchGameListData
2005 (o, getDirectory (o), gList, filePath);
2006
2007 bool result = false;
2008 switch (status) {
2009 case NotFound:
2010 // If the user has not yet created a collection, don't worry.
2011 if (o == SYSTEM) {
2012 KGrMessage::information (view, i18n ("Load Game Info"),
2013 i18n ("Cannot find game info file '%1'.", filePath));
2014 }
2015 break;
2016 case NoRead:
2017 case NoWrite:
2018 KGrMessage::information (view, i18n ("Load Game Info"),
2019 i18n ("Cannot open file '%1' for read-only.", filePath));
2020 break;
2021 case UnexpectedEOF:
2022 KGrMessage::information (view, i18n ("Load Game Info"),
2023 i18n ("Reached end of file '%1' before finding end of game-data.",
2024 filePath));
2025 break;
2026 case OK:
2027 // Append this owner's list of games to the main list.
2028 gameList += gList;
2029 result = true;
2030 break;
2031 }
2032
2033 return (result);
2034 }
2035
saveSolution(const QString & prefix,const int levelNo)2036 void KGrGame::saveSolution (const QString & prefix, const int levelNo)
2037 {
2038 // Save the game and level data that is currently displayed.
2039 KGrRecording * prevRecording = recording;
2040 recording = nullptr;
2041 demoType = REPLAY_ANY; // Must load a "rec_" file, not "sol_".
2042
2043 // Proceed as if we are going to replay the selected level.
2044 if (initRecordingData (USER, prefix, levelNo, true)) {
2045 // But instead just save the recording data on a solution file.
2046 saveRecording (QStringLiteral("sol_"));
2047 KGrMessage::information (view, i18n ("Save A Solution"),
2048 i18n ("Your solution to level %1 has been saved on file %2",
2049 levelNo, userDataDir + QStringLiteral("sol_") + prefix + QStringLiteral(".txt")));
2050 }
2051 else {
2052 KGrMessage::information (view, i18n ("Save A Solution"),
2053 i18n ("Sorry, you do not seem to have played and recorded "
2054 "the selected level before."), QStringLiteral("Show_noRecording"));
2055 }
2056
2057 // Restore the game and level data that is currently displayed.
2058 delete recording;
2059 recording = prevRecording;
2060
2061 // TODO - Factor KGrRecording into separate files, with methods, etc.
2062 }
2063
initRecordingData(const Owner fileOwner,const QString & prefix,const int levelNo,const bool pPlayback)2064 bool KGrGame::initRecordingData (const Owner fileOwner, const QString & prefix,
2065 const int levelNo, const bool pPlayback)
2066 {
2067 // Initialise the recording.
2068 delete recording;
2069 recording = new KGrRecording;
2070 recording->content.fill (0, 4000);
2071 recording->draws.fill (0, 400);
2072
2073 // If system game or ENDE, choose system dir, else choose user dir.
2074 const QString dir = ((fileOwner == SYSTEM) || (levelNo == 0)) ?
2075 systemDataDir : userDataDir;
2076 if (pPlayback) {
2077 if (! loadRecording (dir, prefix, levelNo)) {
2078 return false;
2079 }
2080 }
2081 else {
2082 KGrGameIO io (view);
2083 KGrLevelData levelData;
2084 KGrGameData * gameData = gameList.at (gameIndex);
2085
2086 // Set digWhileFalling same as game, by default: read the level data.
2087 // The dig-while-falling setting can be overridden for a single level.
2088 levelData.digWhileFalling = gameData->digWhileFalling;
2089 if (! io.readLevelData (dir, prefix, levelNo, levelData)) {
2090 return false;
2091 }
2092
2093 recording->dateTime = QDateTime::currentDateTime()
2094 .toUTC()
2095 .toString (Qt::ISODate);
2096 //qCDebug(KGOLDRUNNER_LOG) << "Recording at" << recording->dateTime;
2097
2098 recording->owner = gameData->owner;
2099 recording->rules = gameData->rules;
2100 recording->prefix = gameData->prefix;
2101 recording->gameName = gameData->name;
2102
2103 recording->level = levelNo;
2104 recording->width = levelData.width;
2105 recording->height = levelData.height;
2106 recording->layout = levelData.layout;
2107
2108 // Record whether this level will allow the hero to dig while falling.
2109 recording->digWhileFalling = levelData.digWhileFalling;
2110
2111 // If there is a name or hint, translate the UTF-8 code right now.
2112 recording->levelName = (levelData.name.size() > 0) ?
2113 i18n (levelData.name.constData()) : QString();
2114 recording->hint = (levelData.hint.size() > 0) ?
2115 i18n (levelData.hint.constData()) : QString();
2116
2117 recording->lives = lives;
2118 recording->score = score;
2119 recording->speed = timeScale;
2120 recording->controlMode = controlMode;
2121 recording->keyOption = holdKeyOption;
2122 recording->content [0] = 0xff;
2123 }
2124 return true;
2125 }
2126
saveRecording(const QString & filetype)2127 void KGrGame::saveRecording (const QString & filetype)
2128 {
2129 QString filename = userDataDir + filetype + prefix + QStringLiteral(".txt");
2130 QString groupName = prefix +
2131 QString::number(recording->level).rightJustified(3,QLatin1Char('0'));
2132 //qCDebug(KGOLDRUNNER_LOG) << filename << groupName;
2133
2134 KConfig config (filename, KConfig::SimpleConfig);
2135 KConfigGroup configGroup = config.group (groupName);
2136 configGroup.writeEntry ("DateTime", recording->dateTime);
2137 configGroup.writeEntry ("Owner", (int) recording->owner);
2138 configGroup.writeEntry ("Rules", (int) recording->rules);
2139 configGroup.writeEntry ("Prefix", recording->prefix);
2140 configGroup.writeEntry ("GameName", recording->gameName);
2141 configGroup.writeEntry ("Level", recording->level);
2142 configGroup.writeEntry ("Width", recording->width);
2143 configGroup.writeEntry ("Height", recording->height);
2144 configGroup.writeEntry ("Layout", recording->layout);
2145 configGroup.writeEntry ("Name", recording->levelName);
2146 configGroup.writeEntry ("Hint", recording->hint);
2147 configGroup.writeEntry ("DigWhileFalling", recording->digWhileFalling);
2148 configGroup.writeEntry ("Lives", (int) recording->lives);
2149 configGroup.writeEntry ("Score", (int) recording->score);
2150 configGroup.writeEntry ("Speed", (int) recording->speed);
2151 configGroup.writeEntry ("Mode", (int) recording->controlMode);
2152 configGroup.writeEntry ("KeyOption", (int)recording->keyOption);
2153
2154 QList<int> bytes;
2155 int ch = 0;
2156 int n = recording->content.size();
2157 for (int i = 0; i < n; i++) {
2158 ch = (uchar)(recording->content.at(i));
2159 bytes.append (ch);
2160 if (ch == 0)
2161 break;
2162 }
2163 configGroup.writeEntry ("Content", bytes);
2164
2165 bytes.clear();
2166 ch = 0;
2167 n = recording->draws.size();
2168 for (int i = 0; i < n; i++) {
2169 ch = (uchar)(recording->draws.at(i));
2170 bytes.append (ch);
2171 if (ch == 0)
2172 break;
2173 }
2174 configGroup.writeEntry ("Draws", bytes);
2175
2176 configGroup.sync(); // Ensure that the entry goes to disk.
2177 }
2178
loadRecording(const QString & dir,const QString & prefix,const int levelNo)2179 bool KGrGame::loadRecording (const QString & dir, const QString & prefix,
2180 const int levelNo)
2181 {
2182 //qCDebug(KGOLDRUNNER_LOG) << prefix << levelNo;
2183 QString filename;
2184 if (! getRecordingName (dir, prefix, filename)) {
2185 qCDebug(KGOLDRUNNER_LOG) << "No file found by getRecordingName() for" << dir << prefix;
2186 return false;
2187 }
2188 QString groupName = prefix + QString::number(levelNo).rightJustified(3,QLatin1Char('0'));
2189 qCDebug(KGOLDRUNNER_LOG) << "loadRecording" << filename << prefix << levelNo << groupName;
2190
2191 KConfig config (filename, KConfig::SimpleConfig);
2192 if (! config.hasGroup (groupName)) {
2193 qCDebug(KGOLDRUNNER_LOG) << "Group" << groupName << "NOT FOUND";
2194 return false;
2195 }
2196
2197 KConfigGroup configGroup = config.group (groupName);
2198 QString blank;
2199 recording->dateTime = configGroup.readEntry ("DateTime", "");
2200 recording->owner = (Owner)(configGroup.readEntry
2201 ("Owner", (int)(USER)));
2202 recording->rules = configGroup.readEntry ("Rules", (int)('T'));
2203 recording->prefix = configGroup.readEntry ("Prefix", "");
2204 recording->gameName = configGroup.readEntry ("GameName", blank);
2205 recording->level = configGroup.readEntry ("Level", 1);
2206 recording->width = configGroup.readEntry ("Width", FIELDWIDTH);
2207 recording->height = configGroup.readEntry ("Height", FIELDHEIGHT);
2208 recording->layout = configGroup.readEntry ("Layout", QByteArray());
2209 recording->levelName = configGroup.readEntry ("Name", blank);
2210 recording->hint = configGroup.readEntry ("Hint", blank);
2211 recording->digWhileFalling = configGroup.readEntry ("DigWhileFalling",
2212 true);
2213 recording->lives = configGroup.readEntry ("Lives", 5);
2214 recording->score = configGroup.readEntry ("Score", 0);
2215 recording->speed = configGroup.readEntry ("Speed", 10);
2216 recording->controlMode = configGroup.readEntry ("Mode", (int)MOUSE);
2217 recording->keyOption = configGroup.readEntry ("KeyOption",
2218 (int)CLICK_KEY);
2219
2220 // If demoType is DEMO or SOLVE, get the TRANSLATED gameName, levelName and
2221 // hint from current data (other recordings have been translated already).
2222 // Also get the CURRENT setting of digWhileFalling for this game and level
2223 // (in case the demo or solution file contains out-of-date settings).
2224 if ((demoType == DEMO) || (demoType == SOLVE)) {
2225 int index = -1;
2226 for (int i = 0; i < gameList.count(); i++) { // Find the game.
2227 if (gameList.at (i)->prefix == recording->prefix) {
2228 index = i;
2229 break;
2230 }
2231 }
2232 if (index >= 0) {
2233 // Get digWhileFalling flag and current translation of name of game.
2234 recording->digWhileFalling = gameList.at (index)->digWhileFalling;
2235 recording->gameName = gameList.at (index)->name;
2236 // qCDebug(KGOLDRUNNER_LOG) << "GAME" << gameList.at (index)->name << levelNo
2237 // << "set digWhileFalling to"
2238 // << gameList.at (index)->digWhileFalling;
2239
2240 // Read the current level data.
2241 KGrGameIO io (view);
2242 KGrLevelData levelData;
2243
2244 QString levelDir = (gameList.at (index)->owner == USER) ?
2245 userDataDir : systemDataDir;
2246 // Set digWhileFalling same as game, by default.
2247 levelData.digWhileFalling = gameList.at (index)->digWhileFalling;
2248 if (io.readLevelData (levelDir, recording->prefix, recording->level,
2249 levelData)) {
2250 // If there is a level name or hint, translate it.
2251 recording->levelName = (levelData.name.size() > 0) ?
2252 i18n (levelData.name.constData()) : QString();
2253 recording->hint = (levelData.hint.size() > 0) ?
2254 i18n (levelData.hint.constData()) : QString();
2255 recording->digWhileFalling = levelData.digWhileFalling;
2256 // qCDebug(KGOLDRUNNER_LOG) << "LEVEL" << gameList.at (index)->name << levelNo
2257 // << "digWhileFalling is NOW"
2258 // << levelData.digWhileFalling;
2259 }
2260 }
2261 }
2262
2263 QList<int> bytes = configGroup.readEntry ("Content", QList<int>());
2264 int n = bytes.count();
2265 recording->content.fill (0, n + 1);
2266 for (int i = 0; i < n; i++) {
2267 recording->content [i] = bytes.at (i);
2268 }
2269
2270 bytes.clear();
2271 bytes = configGroup.readEntry ("Draws", QList<int>());
2272 n = bytes.count();
2273 recording->draws.fill (0, n + 1);
2274 for (int i = 0; i < n; i++) {
2275 recording->draws [i] = bytes.at (i);
2276 }
2277 return true;
2278 }
2279
loadSounds()2280 void KGrGame::loadSounds()
2281 {
2282 #ifdef KGAUDIO_BACKEND_OPENAL
2283 const qreal volumes [NumSounds] = {0.6, 0.3, 0.3, 0.6, 0.6, 1.8, 1.0, 1.0, 1.0, 1.0};
2284 effects = new KGrSounds();
2285 effects->setParent (this); // Delete at end of KGrGame.
2286
2287 fx[GoldSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2288 QStringLiteral("themes/default/gold.ogg")));
2289 fx[StepSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2290 QStringLiteral("themes/default/step.wav")));
2291 fx[ClimbSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2292 QStringLiteral("themes/default/climb.wav")));
2293 fx[FallSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2294 QStringLiteral("themes/default/falling.ogg")));
2295 fx[DigSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2296 QStringLiteral("themes/default/dig.ogg")));
2297 fx[LadderSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2298 QStringLiteral("themes/default/ladder.ogg")));
2299 fx[CompletedSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2300 QStringLiteral("themes/default/completed.ogg")));
2301 fx[DeathSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2302 QStringLiteral("themes/default/death.ogg")));
2303 fx[GameOverSound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2304 QStringLiteral("themes/default/gameover.ogg")));
2305 fx[VictorySound] = effects->loadSound (QStandardPaths::locate (QStandardPaths::AppDataLocation,
2306 QStringLiteral("themes/default/victory.ogg")));
2307
2308 // Gold and dig sounds are timed and are allowed to play for at least one
2309 // second, so that rapid sequences of those sounds are heard as overlapping.
2310 effects->setTimedSound (fx[GoldSound]);
2311 effects->setTimedSound (fx[DigSound]);
2312
2313 // Adjust the relative volumes of sounds to improve the overall balance.
2314 for (int i = 0; i < NumSounds; i++) {
2315 effects->setVolume (fx [i], volumes [i]);
2316 }
2317 #endif
2318 }
2319
2320 /******************************************************************************/
2321 /********************** MESSAGE BOX WITH FREEZE *************************/
2322 /******************************************************************************/
2323
myMessage(QWidget * parent,const QString & title,const QString & contents)2324 void KGrGame::myMessage (QWidget * parent, const QString &title, const QString &contents)
2325 {
2326 // Halt the game while the message is displayed, if not already halted.
2327 freeze (ProgramPause, true);
2328
2329 KGrMessage::information (parent, title, contents);
2330
2331 // Unfreeze the game, but only if it was previously unfrozen.
2332 freeze (ProgramPause, false);
2333 }
2334
2335
2336 // vi: set sw=4 :
2337