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