1 /*
2  * Copyright (C) 2004 Ivo Danihelka (ivo@danihelka.net)
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  */
9 #include "Level.h"
10 
11 #include "StateManager.h"
12 #include "DescFinder.h"
13 #include "PhaseLocker.h"
14 #include "LevelInput.h"
15 #include "LevelScript.h"
16 #include "LevelLoading.h"
17 #include "LevelCountDown.h"
18 #include "CommandQueue.h"
19 #include "MultiDrawer.h"
20 
21 #include "Log.h"
22 #include "Room.h"
23 #include "StepCounter.h"
24 #include "View.h"
25 #include "OptionAgent.h"
26 #include "VideoAgent.h"
27 #include "ScriptException.h"
28 #include "LogicException.h"
29 #include "DemoMode.h"
30 #include "SoundAgent.h"
31 #include "SubTitleAgent.h"
32 #include "StepDecor.h"
33 #include "StatusDisplay.h"
34 #include "Picture.h"
35 #include "DialogStack.h"
36 #include "StringTool.h"
37 
38 #include <stdio.h>
39 #include <assert.h>
40 
41 //-----------------------------------------------------------------
42 /**
43  * Create new level.
44  */
Level(const std::string & codename,const Path & datafile,int depth)45     Level::Level(const std::string &codename, const Path &datafile, int depth)
46 : m_codename(codename), m_datafile(datafile)
47 {
48     m_desc = NULL;
49     m_restartCounter = 1;
50     m_undoSteps = 0;
51     m_wasDangerousMove = false;
52     m_depth = depth;
53     m_newRound = false;
54     m_locker = new PhaseLocker();
55     m_levelScript = new LevelScript(this);
56     m_loading = new LevelLoading(m_levelScript);
57     m_countdown = new LevelCountDown(m_levelScript);
58     m_show = new CommandQueue();
59     m_background = new MultiDrawer();
60     m_statusDisplay = new StatusDisplay();
61     takeHandler(new LevelInput(this));
62     registerDrawable(m_background);
63     registerDrawable(SubTitleAgent::agent());
64     registerDrawable(m_statusDisplay);
65 }
66 //-----------------------------------------------------------------
~Level()67 Level::~Level()
68 {
69     own_cleanState();
70     delete m_locker;
71     //NOTE: m_show must be removed before levelScript
72     // because it uses the same script
73     delete m_show;
74     delete m_countdown;
75     delete m_loading;
76     delete m_levelScript;
77     delete m_background;
78     delete m_statusDisplay;
79 }
80 //-----------------------------------------------------------------
81 void
fillStatus(LevelStatus * status)82 Level::fillStatus(LevelStatus *status)
83 {
84     m_countdown->fillStatus(status);
85 }
86 //-----------------------------------------------------------------
87 /**
88  * Start gameplay.
89  * fillDesc() and fillStatus() must be called before.
90  */
91     void
own_initState()92 Level::own_initState()
93 {
94     if (NULL == m_desc) {
95         throw LogicException(ExInfo("level description is NULL")
96                 .addInfo("codename", m_codename));
97     }
98     m_countdown->reset();
99     m_loading->reset();
100     //NOTE: let level first to draw and then play
101     m_locker->reset();
102     m_locker->ensurePhases(1);
103     if (!isUndoing()) {
104         SoundAgent::agent()->stopMusic();
105     }
106     //TODO: escape "codename"
107     m_levelScript->scriptDo("CODENAME = [[" + m_codename + "]]");
108     m_levelScript->scriptInclude(m_datafile);
109 }
110 //-----------------------------------------------------------------
111 /**
112  * Update level.
113  */
114     void
own_updateState()115 Level::own_updateState()
116 {
117     m_newRound = false;
118     if (m_locker->getLocked() == 0) {
119         m_newRound = true;
120         nextAction();
121     }
122     updateLevel();
123     m_locker->decLock();
124 
125     if (m_countdown->countDown(this)) {
126         finishLevel();
127     }
128 }
129 //-----------------------------------------------------------------
130     void
own_pauseState()131 Level::own_pauseState()
132 {
133     m_levelScript->killPlan();
134     action_undo_finish();
135 }
136 //-----------------------------------------------------------------
137     void
own_resumeState()138 Level::own_resumeState()
139 {
140     if (m_levelScript->isRoom()) {
141         initScreen();
142     }
143 }
144 //-----------------------------------------------------------------
145 /**
146  * Clean room after visit.
147  */
148     void
own_cleanState()149 Level::own_cleanState()
150 {
151     m_levelScript->cleanRoom();
152 }
153 //-----------------------------------------------------------------
154 /**
155  * Loading is paused on background.
156  */
157     void
own_noteBg()158 Level::own_noteBg()
159 {
160     if (m_loading->isLoading() && !m_loading->isPaused()) {
161         m_loading->togglePause();
162     }
163     action_undo_finish();
164 }
165 //-----------------------------------------------------------------
166     void
own_noteFg()167 Level::own_noteFg()
168 {
169     initScreen();
170     if (m_loading->isLoading() && m_loading->isPaused()) {
171         m_loading->togglePause();
172     }
173     //NOTE: ensure that an unwanted mouse press will not move a fish
174     m_locker->ensurePhases(3);
175 }
176 
177 //-----------------------------------------------------------------
178 bool
isUndoing() const179 Level::isUndoing() const
180 {
181     return m_undoSteps != 0;
182 }
183 //-----------------------------------------------------------------
184 bool
isActing() const185 Level::isActing() const
186 {
187     return isShowing() || isLoading() || isUndoing();
188 }
189 //-----------------------------------------------------------------
190 bool
isLoading() const191 Level::isLoading() const
192 {
193     return m_loading->isLoading();
194 }
195 //-----------------------------------------------------------------
196 void
togglePause()197 Level::togglePause()
198 {
199     return m_loading->togglePause();
200 }
201 //-----------------------------------------------------------------
202 /**
203  * Process next action.
204  */
205     void
nextAction()206 Level::nextAction()
207 {
208     if (isUndoing()) {
209         nextUndoAction();
210     }
211     else if (isLoading()) {
212         nextLoadAction();
213     }
214     else if (isShowing()) {
215         nextShowAction();
216     }
217     else {
218         nextPlayerAction();
219     }
220 }
221 //-----------------------------------------------------------------
222 /**
223  * Update level (plan dialogs, do anim, ...).
224  */
225     void
updateLevel()226 Level::updateLevel()
227 {
228     if (!isUndoing() && !isLoading()) {
229         m_levelScript->updateScript();
230     }
231 }
232 //-----------------------------------------------------------------
233 /**
234  * Save state for undo.
235  * Should be called after a player move,
236  * but still before level script update.
237  * @param oldMoves moves before the last move
238  */
239     void
saveUndo(const std::string & oldMoves)240 Level::saveUndo(const std::string &oldMoves)
241 {
242     if (m_levelScript->isRoom()) {
243         Room *room = m_levelScript->room();
244         bool keepLast = m_wasDangerousMove;
245         m_wasDangerousMove = room->stepCounter()->isDangerousMove();
246 
247         std::string keepLastValue = keepLast ? "true" : "false";
248         m_levelScript->scriptDo("script_saveUndo(\""
249                 + oldMoves + "\"," + keepLastValue + ")");
250     }
251 }
252 //-----------------------------------------------------------------
253 /**
254  * Finish complete level.
255  * Save solution.
256  */
257     void
finishLevel()258 Level::finishLevel()
259 {
260     if (m_countdown->isFinishedEnough()) {
261         m_countdown->saveSolution();
262         GameState *nextState = m_countdown->createNextState();
263         if (nextState) {
264             changeState(nextState);
265         }
266         else {
267             quitState();
268         }
269     }
270     else if (m_countdown->isWrongEnough()) {
271         action_restart(1);
272     }
273 }
274 //-----------------------------------------------------------------
275 /*
276  * Update room.
277  * Let objects to move.
278  */
279     void
nextPlayerAction()280 Level::nextPlayerAction()
281 {
282     if (m_levelScript->isRoom()) {
283         Room *room = m_levelScript->room();
284         std::string oldMoves = room->stepCounter()->getMoves();
285         room->nextRound(getInput());
286         // The old positions are now occupied, so check the isSolvable().
287         bool wasSolvable = room->isSolvable();
288         m_wasDangerousMove = m_wasDangerousMove || room->isFalling();
289 
290         if (wasSolvable && !room->isFalling()) {
291             saveUndo(oldMoves);
292         }
293     }
294 }
295 
296 //-----------------------------------------------------------------
297 /**
298  * Write save to the file.
299  * Save moves and models state.
300  * @param models saved models
301  */
302     void
saveGame(const std::string & models)303 Level::saveGame(const std::string &models)
304 {
305     if (m_levelScript->isRoom()) {
306         Path file = Path::dataWritePath("saves/" + m_codename + ".lua");
307         FILE *saveFile = fopen(file.getNative().c_str(), "w");
308         if (saveFile) {
309             std::string moves =
310                 m_levelScript->room()->stepCounter()->getMoves();
311             fputs("\nsaved_moves = '", saveFile);
312             fputs(moves.c_str(), saveFile);
313             fputs("'\n", saveFile);
314 
315             fputs("\nsaved_models = ", saveFile);
316             fputs(models.c_str(), saveFile);
317             fclose(saveFile);
318             displaySaveStatus();
319         }
320         else {
321             LOG_WARNING(ExInfo("cannot save game")
322                     .addInfo("file", file.getNative()));
323         }
324     }
325 }
326 //-----------------------------------------------------------------
327     void
displaySaveStatus()328 Level::displaySaveStatus()
329 {
330     static const int TIME = 3;
331     LOG_INFO(ExInfo("game is saved")
332             .addInfo("codename", m_codename));
333     m_statusDisplay->displayStatus(
334             new Picture(Path::dataReadPath("images/menu/status/saved.png"),
335                 V2(0, 0)), TIME);
336 }
337 //-----------------------------------------------------------------
338 /**
339  * Start loading mode.
340  * @param moves saved moves to load
341  */
342     void
loadGame(const std::string & moves)343 Level::loadGame(const std::string &moves)
344 {
345     if (isUndoing()) {
346         if (m_levelScript->isRoom()) {
347             m_levelScript->room()->setMoves(moves);
348         }
349     }
350     else {
351         m_loading->loadGame(moves);
352     }
353 }
354 //-----------------------------------------------------------------
355 /**
356  * Start replay mode.
357  * @param moves saved moves to load
358  */
359     void
loadReplay(const std::string & moves)360 Level::loadReplay(const std::string &moves)
361 {
362     m_loading->loadReplay(moves);
363 }
364 
365 //-----------------------------------------------------------------
366 /**
367  * Load next move.
368  */
369     void
nextLoadAction()370 Level::nextLoadAction()
371 {
372     m_loading->nextLoadAction();
373     if (!isLoading()) {
374         m_levelScript->scriptDo("script_loadState()");
375     }
376 }
377 //-----------------------------------------------------------------
378 /**
379  * Let show execute.
380  */
381     void
nextShowAction()382 Level::nextShowAction()
383 {
384     if (m_levelScript->isRoom()) {
385         m_levelScript->room()->beginFall();
386         m_show->executeFirst();
387         m_levelScript->room()->finishRound();
388     }
389 }
390 //-----------------------------------------------------------------
391 /**
392  * Do the next undo step.
393  */
394     void
nextUndoAction()395 Level::nextUndoAction()
396 {
397     if (m_levelScript->isRoom()) {
398         std::string moves = m_levelScript->room()->stepCounter()->getMoves();
399         std::string strSteps = StringTool::toString(m_undoSteps);
400         m_levelScript->scriptDo("script_loadUndo(\""
401                + moves + "\"," + strSteps + ")");
402     }
403 }
404 //-----------------------------------------------------------------
405 /**
406  * (re)start room.
407  * @return true
408  */
409     bool
action_restart(int increment)410 Level::action_restart(int increment)
411 {
412     if (increment > 0) {
413         m_undoSteps = 0;
414     }
415     own_cleanState();
416     m_restartCounter += increment;
417     //NOTE: The script is just overridden by itself,
418     // so planned shows and undo remain after restart.
419     own_initState();
420     return true;
421 }
422 //-----------------------------------------------------------------
423 /**
424  * Move a fish.
425  * @param symbol move symbol, e.g. 'U', 'D', 'L', 'R'
426  * @return true when move is done
427  */
428     bool
action_move(char symbol)429 Level::action_move(char symbol)
430 {
431     return m_levelScript->room()->makeMove(symbol);
432 }
433 //-----------------------------------------------------------------
434 /**
435  * Save position.
436  * @return true
437  */
438     bool
action_save()439 Level::action_save()
440 {
441     if (m_levelScript->room()->isSolvable()) {
442         m_levelScript->scriptDo("script_save()");
443     }
444     else {
445         LOG_INFO(ExInfo("bad level condition, level cannot be finished, "
446                     "no save is made"));
447     }
448     return true;
449 }
450 //-----------------------------------------------------------------
451 /**
452  * Load position.
453  * @return true
454  */
455     bool
action_load()456 Level::action_load()
457 {
458     Path file = Path::dataReadPath("saves/" + m_codename + ".lua");
459     if (file.exists()) {
460         m_undoSteps = 0;
461         m_restartCounter--;
462         action_restart(1);
463         m_levelScript->scriptInclude(file);
464         m_levelScript->scriptDo("script_load()");
465     }
466     else {
467         LOG_INFO(ExInfo("there is no file to load")
468                 .addInfo("file", file.getNative()));
469     }
470     return true;
471 }
472 //-----------------------------------------------------------------
473 /**
474  * Start the undoing.
475  * @param steps 1 for undo, -1 for redo
476  */
477     void
action_undo(int steps)478 Level::action_undo(int steps)
479 {
480     m_undoSteps = steps;
481     m_levelScript->killPlan();
482     m_countdown->reset();
483     nextUndoAction();
484 }
485 //-----------------------------------------------------------------
486 /**
487  * Restart the room at the current undo position.
488  */
489     void
action_undo_finish()490 Level::action_undo_finish()
491 {
492     if (!isUndoing()) {
493         return;
494     }
495 
496     action_restart(0);
497     m_levelScript->scriptDo("script_loadFinalUndo()");
498     m_undoSteps = 0;
499 }
500 //-----------------------------------------------------------------
501     void
switchFish()502 Level::switchFish()
503 {
504     if (m_levelScript->isRoom()) {
505         m_levelScript->room()->switchFish();
506     }
507 }
508 //-----------------------------------------------------------------
509     void
controlEvent(const KeyStroke & stroke)510 Level::controlEvent(const KeyStroke &stroke)
511 {
512     if (m_levelScript->isRoom()) {
513         m_levelScript->room()->controlEvent(stroke);
514     }
515 }
516 //-----------------------------------------------------------------
517     void
controlMouse(const MouseStroke & button)518 Level::controlMouse(const MouseStroke &button)
519 {
520     if (m_levelScript->isRoom()) {
521         m_levelScript->room()->controlMouse(button);
522     }
523 }
524 //-----------------------------------------------------------------
525 /**
526  * Create new room
527  * and change screen resolution.
528  */
529     void
createRoom(int w,int h,const std::string & picture)530 Level::createRoom(int w, int h, const std::string &picture)
531 {
532     Room *room = new Room(w, h, picture, m_locker, m_levelScript);
533     room->addDecor(new StepDecor(room->stepCounter()));
534     m_levelScript->takeRoom(room);
535     m_background->removeAll();
536     m_background->acceptDrawer(room);
537 
538     initScreen();
539 }
540 //-----------------------------------------------------------------
541 void
initScreen()542 Level::initScreen()
543 {
544     if (m_levelScript->isRoom()) {
545         std::string title = m_desc->findDesc(m_codename);
546         title.append(": " + m_desc->findLevelName(m_codename));
547 
548         OptionAgent *options = OptionAgent::agent();
549         options->setParam("caption", title);
550         options->setParam("screen_width",
551                 m_levelScript->room()->getW() * View::SCALE);
552         options->setParam("screen_height",
553                 m_levelScript->room()->getH() * View::SCALE);
554         VideoAgent::agent()->initVideoMode();
555     }
556 }
557 //-----------------------------------------------------------------
558     void
newDemo(const Path & demofile)559 Level::newDemo(const Path &demofile)
560 {
561     m_levelScript->interruptPlan();
562     DemoMode *demo = new DemoMode(demofile);
563     pushState(demo);
564 }
565 
566 //-----------------------------------------------------------------
567 bool
isShowing() const568 Level::isShowing() const
569 {
570     return !m_show->empty();
571 }
572 //-----------------------------------------------------------------
573 void
interruptShow()574 Level::interruptShow()
575 {
576     m_show->removeAll();
577 }
578 //-----------------------------------------------------------------
579 void
planShow(Command * new_command)580 Level::planShow(Command *new_command)
581 {
582     m_show->planCommand(new_command);
583 }
584 //-----------------------------------------------------------------
585 std::string
getLevelName() const586 Level::getLevelName() const
587 {
588     return m_desc->findLevelName(m_codename);
589 }
590 //-----------------------------------------------------------------
591 int
getCountForSolved() const592 Level::getCountForSolved() const
593 {
594     int countdown = 10;
595     if (isUndoing()) {
596         countdown = -1;
597     }
598     else if (isLoading()) {
599         countdown = 0;
600     }
601     else if (m_levelScript->dialogs()->areRunning()) {
602         countdown = 30;
603     }
604     return countdown;
605 }
606 //-----------------------------------------------------------------
607 int
getCountForWrong() const608 Level::getCountForWrong() const
609 {
610     //NOTE: don't forget to change briefcase_help_demo too
611     return 75;
612 }
613 
614