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