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 "Room.h"
10 
11 #include "WavyPicture.h"
12 #include "Field.h"
13 #include "FinderAlg.h"
14 #include "ResSoundPack.h"
15 #include "Controls.h"
16 #include "PhaseLocker.h"
17 #include "Planner.h"
18 #include "View.h"
19 
20 #include "Log.h"
21 #include "Rules.h"
22 #include "LogicException.h"
23 #include "LoadException.h"
24 #include "Unit.h"
25 #include "TimerAgent.h"
26 #include "SubTitleAgent.h"
27 #include "DialogStack.h"
28 #include "SoundAgent.h"
29 #include "OptionAgent.h"
30 #include "ModelList.h"
31 #include "Landslip.h"
32 #include "MouseStroke.h"
33 #include "MouseControl.h"
34 #include "Path.h"
35 
36 #include <assert.h>
37 
38 //-----------------------------------------------------------------
39 /**
40  * Create room holder.
41  * @param w room width
42  * @param h room height
43  * @param picture room background
44  * @param locker shared locker for anim
45  * @param levelScript shared planner to interrupt
46  */
Room(int w,int h,const std::string & picture,PhaseLocker * locker,Planner * levelScript)47 Room::Room(int w, int h, const std::string &picture,
48         PhaseLocker *locker, Planner *levelScript)
49 {
50     m_locker = locker;
51     m_levelScript = levelScript;
52     m_fastFalling = false;
53     m_bg = new WavyPicture(Path::dataReadPath(picture), V2(0, 0));
54     m_bgFilename = picture;
55     m_field = new Field(w, h);
56     m_finder = new FinderAlg(w, h);
57     m_controls = new Controls(m_locker);
58     m_view = new View(ModelList(&m_models));
59     m_lastAction = Cube::ACTION_NO;
60     m_soundPack = new ResSoundPack();
61     m_startTime = TimerAgent::agent()->getCycles();
62 }
63 //-----------------------------------------------------------------
64 /**
65  * Delete field and models.
66  */
~Room()67 Room::~Room()
68 {
69     m_soundPack->removeAll();
70     delete m_soundPack;
71     m_levelScript->killPlan();
72     m_levelScript->dialogs()->removeAll();
73     SubTitleAgent::agent()->removeAll();
74     delete m_controls;
75     delete m_view;
76 
77     //NOTE: models must be removed before field because they unmask self
78     Cube::t_models::iterator end = m_models.end();
79     for (Cube::t_models::iterator i = m_models.begin(); i != end; ++i) {
80         delete (*i);
81     }
82 
83     delete m_finder;
84     delete m_field;
85     delete m_bg;
86 }
87 //-----------------------------------------------------------------
88 /**
89  * Set waves on background.
90  */
91     void
setWaves(float amplitude,float periode,float speed)92 Room::setWaves(float amplitude, float periode, float speed)
93 {
94     m_bg->setWamp(amplitude);
95     m_bg->setWperiode(periode);
96     m_bg->setWspeed(speed);
97 }
98 //-----------------------------------------------------------------
99     void
addDecor(Decor * new_decor)100 Room::addDecor(Decor *new_decor)
101 {
102     m_view->addDecor(new_decor);
103 }
104 //-----------------------------------------------------------------
105 /**
106  * Add model at scene.
107  * @param new_model new object
108  * @param new_unit driver for the object or NULL
109  * @return model index
110  */
111     int
addModel(Cube * new_model,Unit * new_unit)112 Room::addModel(Cube *new_model, Unit *new_unit)
113 {
114     new_model->rules()->takeField(m_field);
115     m_models.push_back(new_model);
116 
117     if (new_unit) {
118         new_unit->takeModel(new_model);
119         m_controls->addUnit(new_unit);
120     }
121 
122     int model_index = m_models.size() - 1;
123     new_model->setIndex(model_index);
124     return model_index;
125 }
126 //-----------------------------------------------------------------
127 /**
128  * Return model at index.
129  * @throws LogicException when model_index is out of range
130  */
131     Cube *
getModel(int model_index)132 Room::getModel(int model_index)
133 {
134     Cube *result = NULL;
135     if (0 <= model_index && model_index < (int)m_models.size()) {
136         result = m_models[model_index];
137     }
138     else {
139         throw LogicException(ExInfo("bad model index")
140                 .addInfo("model_index", model_index));
141     }
142 
143     return result;
144 }
145 //-----------------------------------------------------------------
146 /**
147  * Return model at location.
148  */
149     Cube *
askField(const V2 & loc)150 Room::askField(const V2 &loc)
151 {
152     return m_field->getModel(loc);
153 }
154 //-----------------------------------------------------------------
155 /**
156  * Update all models.
157  * Prepare new move, let models fall, let models drive, release old position.
158  */
159     void
nextRound(const InputProvider * input)160 Room::nextRound(const InputProvider *input)
161 {
162     if (m_fastFalling) {
163         while (beginFall()) {
164             finishRound();
165         }
166     }
167     else {
168         beginFall();
169     }
170 
171     if (isFresh()) {
172         if (m_controls->driving(input)) {
173             m_lastAction = Cube::ACTION_MOVE;
174         }
175         else {
176             MouseControl rat(m_controls, m_view, m_finder);
177             if (rat.mouseDrive(input)) {
178                 m_lastAction = Cube::ACTION_MOVE;
179             }
180         }
181     }
182     finishRound();
183 }
184 //-----------------------------------------------------------------
185 /**
186  * Play sound like some object has fall.
187  * NOTE: only one sound is played even more objects have fall
188  */
189     void
playImpact(Cube::eWeight impact)190 Room::playImpact(Cube::eWeight impact)
191 {
192     switch (impact) {
193         case Cube::NONE:
194             break;
195         case Cube::LIGHT:
196             playSound("impact_light", 50);
197             break;
198         case Cube::HEAVY:
199             playSound("impact_heavy", 50);
200             break;
201         default:
202             assert(!"unknown impact weight");
203     }
204 }
205 //-----------------------------------------------------------------
206 /**
207  * Play sound like a fish die.
208  * @param model fresh dead fish
209  */
210     void
playDead(Cube * model)211 Room::playDead(Cube *model)
212 {
213     m_levelScript->dialogs()->killSound(model->getIndex());
214     switch (model->getPower()) {
215         case Cube::LIGHT:
216             playSound("dead_small");
217             break;
218         case Cube::HEAVY:
219             playSound("dead_big");
220             break;
221         default:
222             LOG_WARNING(ExInfo("curious power of dead fish")
223                     .addInfo("power", model->getPower()));
224             break;
225     }
226 }
227 //-----------------------------------------------------------------
228 /**
229  * Move all models to new position
230  * and check dead fihes.
231  */
232     void
prepareRound()233 Room::prepareRound()
234 {
235     bool interrupt = false;
236 
237     //NOTE: we must call this functions sequential for all objects
238     Cube::t_models::iterator end = m_models.end();
239     for (Cube::t_models::iterator i = m_models.begin(); i != end; ++i) {
240         (*i)->rules()->freeOldPos();
241     }
242     for (Cube::t_models::iterator i = m_models.begin(); i != end; ++i) {
243         (*i)->rules()->occupyNewPos();
244     }
245     for (Cube::t_models::iterator j = m_models.begin(); j != end; ++j) {
246         bool die = (*j)->rules()->checkDead(m_lastAction);
247         interrupt |= die;
248         if (die) {
249             playDead(*j);
250         }
251     }
252     for (Cube::t_models::iterator l = m_models.begin(); l != end; ++l) {
253         (*l)->rules()->changeState();
254     }
255 
256     if (interrupt) {
257         m_levelScript->interruptPlan();
258     }
259 }
260 //-----------------------------------------------------------------
261 /**
262  * Let models to go out of screen.
263  * @param interactive whether do anim
264  * @return true when a model went out
265  */
266     bool
fallout(bool interactive)267 Room::fallout(bool interactive)
268 {
269     bool wentOut = false;
270     Cube::t_models::iterator end = m_models.end();
271     for (Cube::t_models::iterator i = m_models.begin(); i != end; ++i) {
272         if (!(*i)->isLost()) {
273             int outDepth = (*i)->rules()->actionOut();
274             if (outDepth > 0) {
275                 wentOut = true;
276                 if (interactive) {
277                     m_locker->ensurePhases(3);
278                 }
279             }
280             else if (outDepth == -1) {
281                 m_levelScript->interruptPlan();
282             }
283         }
284     }
285 
286     return wentOut;
287 }
288 //-----------------------------------------------------------------
289 /**
290  * Let things fall.
291  * @return true when something is falling.
292  */
293     bool
falldown(bool interactive)294 Room::falldown(bool interactive)
295 {
296     ModelList models(&m_models);
297     Landslip slip(models);
298 
299     bool falling = slip.computeFall();
300     if (interactive) {
301         playImpact(slip.getImpact());
302     }
303     return falling;
304 }
305 //-----------------------------------------------------------------
306 /**
307  * Let models to release their old position.
308  * @param interactive whether ensure phases for motion animation
309  */
310     void
finishRound(bool interactive)311 Room::finishRound(bool interactive)
312 {
313     if (interactive) {
314         m_controls->lockPhases();
315     }
316     m_view->noteNewRound(m_locker->getLocked());
317 }
318 
319 //-----------------------------------------------------------------
320     void
switchFish()321 Room::switchFish()
322 {
323     m_controls->switchActive();
324 }
325 //-----------------------------------------------------------------
326     void
controlEvent(const KeyStroke & stroke)327 Room::controlEvent(const KeyStroke &stroke)
328 {
329     m_controls->controlEvent(stroke);
330 }
331 //-----------------------------------------------------------------
332     void
controlMouse(const MouseStroke & button)333 Room::controlMouse(const MouseStroke &button)
334 {
335     if (button.isLeft()) {
336         V2 fieldPos = m_view->getFieldPos(button.getLoc());
337         Cube *model = askField(fieldPos);
338         m_controls->activateSelected(model);
339     }
340 }
341 
342 //-----------------------------------------------------------------
343 const StepCounter *
stepCounter() const344 Room::stepCounter() const
345 {
346     return m_controls;
347 }
348 //-----------------------------------------------------------------
349     void
setMoves(const std::string & moves)350 Room::setMoves(const std::string &moves)
351 {
352     m_controls->setMoves(moves);
353 }
354 //-----------------------------------------------------------------
355     void
checkActive()356 Room::checkActive()
357 {
358     return m_controls->checkActive();
359 }
360 //-----------------------------------------------------------------
361     void
unBusyUnits()362 Room::unBusyUnits()
363 {
364     Cube::t_models::iterator end = m_models.end();
365     for (Cube::t_models::iterator i = m_models.begin(); i != end; ++i) {
366         (*i)->setBusy(false);
367     }
368 }
369 //-----------------------------------------------------------------
370 /**
371  * Load this move, let object to fall fast.
372  * Don't play sound.
373  * @throws LoadException for bad moves
374  */
375     void
loadMove(char move)376 Room::loadMove(char move)
377 {
378     static const bool NO_INTERACTIVE = false;
379     bool falling = true;
380     while (falling) {
381         falling = beginFall(NO_INTERACTIVE);
382         makeMove(move);
383 
384         finishRound(NO_INTERACTIVE);
385     }
386 }
387 //-----------------------------------------------------------------
388 /**
389  * Begin round.
390  * Let objects fall.
391  * First objects can fall out of room (even upward),
392  * when nothing is going out, then objects can fall down by gravity.
393  *
394  * @param interactive whether play sound and do anim
395  * @return true when something was falling
396  */
397     bool
beginFall(bool interactive)398 Room::beginFall(bool interactive)
399 {
400     prepareRound();
401     m_lastAction = Cube::ACTION_NO;
402 
403     if (fallout(interactive)) {
404         m_lastAction = Cube::ACTION_MOVE;
405     }
406     else {
407         if (falldown(interactive)) {
408             m_lastAction = Cube::ACTION_FALL;
409         }
410     }
411     return m_lastAction != Cube::ACTION_NO;
412 }
413 //-----------------------------------------------------------------
414 /**
415  * Try make single move.
416  * @return true for success or false when something has moved before
417  * @throws LoadException for bad moves
418  */
419     bool
makeMove(char move)420 Room::makeMove(char move)
421 {
422     bool result = false;
423     if (isFresh()) {
424         if (!m_controls->makeMove(move)) {
425             throw LoadException(ExInfo("load error - bad move")
426                     .addInfo("move", std::string(1, move)));
427         }
428         m_lastAction = Cube::ACTION_MOVE;
429         result = true;
430     }
431     return result;
432 }
433 //-----------------------------------------------------------------
434 /**
435  * Returns true when there is no unit which will be able to move.
436  */
437 bool
cannotMove() const438 Room::cannotMove() const
439 {
440     return m_controls->cannotMove();
441 }
442 //-----------------------------------------------------------------
443 /**
444  * Returns true when all goals can be solved.
445  */
446 bool
isSolvable() const447 Room::isSolvable() const
448 {
449     Cube::t_models::const_iterator end = m_models.end();
450     for (Cube::t_models::const_iterator i = m_models.begin(); i != end; ++i) {
451         if ((*i)->isWrong()) {
452             return false;
453         }
454     }
455     return true;
456 }
457 //-----------------------------------------------------------------
458 /**
459  * Returns true when all goal all satisfied.
460  * Right time to ask is after finishRound.
461  * NOTE: room is not solved when somethig is still falling
462  */
463 bool
isSolved() const464 Room::isSolved() const
465 {
466     if (!isFresh()) {
467         return false;
468     }
469     Cube::t_models::const_iterator end = m_models.end();
470     for (Cube::t_models::const_iterator i = m_models.begin(); i != end; ++i) {
471         if (!(*i)->isSatisfy()) {
472             return false;
473         }
474     }
475     return true;
476 }
477 
478 //-----------------------------------------------------------------
479 int
getW() const480 Room::getW() const
481 {
482     return m_field->getW();
483 }
484 //-----------------------------------------------------------------
485 int
getH() const486 Room::getH() const
487 {
488     return m_field->getH();
489 }
490 //-----------------------------------------------------------------
491 int
getCycles() const492 Room::getCycles() const
493 {
494     return TimerAgent::agent()->getCycles() - m_startTime;
495 }
496 //-----------------------------------------------------------------
497     void
addSound(const std::string & name,const Path & file)498 Room::addSound(const std::string &name, const Path &file)
499 {
500     m_soundPack->addSound(name, file);
501 }
502 //-----------------------------------------------------------------
503     void
playSound(const std::string & name,int volume)504 Room::playSound(const std::string &name, int volume)
505 {
506     if (OptionAgent::agent()->getAsBool("sound", true)) {
507         SoundAgent::agent()->playSound(
508             m_soundPack->getRandomRes(name), volume);
509     }
510 }
511 //-----------------------------------------------------------------
512 /**
513  * Shift room content.
514  * NOTE: background is not shifted
515  */
516     void
setScreenShift(const V2 & shift)517 Room::setScreenShift(const V2 &shift)
518 {
519     m_view->setScreenShift(shift);
520 }
521 //-----------------------------------------------------------------
522 void
changeBg(const std::string & picture)523 Room::changeBg(const std::string &picture)
524 {
525     if (picture != m_bgFilename) {
526         m_bg->changePicture(Path::dataReadPath(picture));
527         m_bgFilename = picture;
528     }
529 }
530 //-----------------------------------------------------------------
531     void
drawOn(SDL_Surface * screen)532 Room::drawOn(SDL_Surface *screen)
533 {
534     m_bg->drawOn(screen);
535     m_view->drawOn(screen);
536 }
537 
538