make badguys reappear when scrolling back
[supertux.git] / src / world.cpp
1 //  $Id$
2 // 
3 //  SuperTux
4 //  Copyright (C) 2000 Bill Kendrick <bill@newbreedsoftware.com>
5 //  Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
6 //  Copyright (C) 2004 Ingo Ruhnke <grumbel@gmx.de>
7 //
8 //  This program is free software; you can redistribute it and/or
9 //  modify it under the terms of the GNU General Public License
10 //  as published by the Free Software Foundation; either version 2
11 //  of the License, or (at your option) any later version.
12 //
13 //  This program is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 //  GNU General Public License for more details.
17 // 
18 //  You should have received a copy of the GNU General Public License
19 //  along with this program; if not, write to the Free Software
20 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
21 //  02111-1307, USA.
22
23 #include <iostream>
24 #include <math.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include "globals.h"
28 #include "scene.h"
29 #include "screen.h"
30 #include "defines.h"
31 #include "world.h"
32 #include "level.h"
33 #include "tile.h"
34 #include "resources.h"
35 #include "gameobjs.h"
36 #include "camera.h"
37 #include "display_manager.h"
38 #include "background.h"
39 #include "tilemap.h"
40
41 Surface* img_distro[4];
42
43 World* World::current_ = 0;
44
45 World::World(const std::string& filename, int level_nr)
46 {
47   // FIXME: Move this to action and draw and everywhere else where the
48   // world calls child functions
49   current_ = this;
50
51   level = new Level();
52   if(level_nr >= 0) {
53     level->load(filename, level_nr, this);
54   } else {
55     level->load(filename, this);
56   }
57
58   tux = new Player(displaymanager);
59   add_object(tux);
60
61   set_defaults();
62
63   level->load_gfx();
64   // add background
65   activate_particle_systems();
66   background = new Background(displaymanager);
67   if(level->img_bkgd) {
68     background->set_image(level->img_bkgd, level->bkgd_speed);
69   } else {
70     background->set_gradient(level->bkgd_top, level->bkgd_bottom);
71   }
72   add_object(background);
73
74   // add tilemap
75   add_object(new TileMap(displaymanager, level));
76   level->load_song();
77
78   camera = new Camera(tux, level);
79   add_object(camera);               
80
81   apply_bonuses();
82 }
83
84 void
85 World::apply_bonuses()
86 {
87   // Apply bonuses from former levels
88   switch (player_status.bonus)
89     {
90     case PlayerStatus::NO_BONUS:
91       break;
92                                                                                 
93     case PlayerStatus::FLOWER_BONUS:
94       tux->got_power = Player::FIRE_POWER;  // FIXME: add ice power to here
95       // fall through
96                                                                                 
97     case PlayerStatus::GROWUP_BONUS:
98       tux->grow();
99       break;
100     }
101 }
102
103 World::~World()
104 {
105   for (std::vector<GameObject*>::iterator i = gameobjects.begin();
106           i != gameobjects.end(); ++i) {
107     delete *i;
108   }
109
110   delete level;
111 }
112
113 void
114 World::set_defaults()
115 {
116   player_status.score_multiplier = 1;
117
118   counting_distros = false;
119   distro_counter = 0;
120
121   /* set current song/music */
122   currentmusic = LEVEL_MUSIC;
123 }
124
125 void
126 World::add_object(GameObject* object)
127 {
128   // XXX hack for now until new collision code is ready
129   BadGuy* badguy = dynamic_cast<BadGuy*> (object);
130   if(badguy)
131     bad_guys.push_back(badguy);
132   Bullet* bullet = dynamic_cast<Bullet*> (object);
133   if(bullet)
134     bullets.push_back(bullet);
135   Upgrade* upgrade = dynamic_cast<Upgrade*> (object);
136   if(upgrade)
137     upgrades.push_back(upgrade);
138   Trampoline* trampoline = dynamic_cast<Trampoline*> (object);
139   if(trampoline)
140     trampolines.push_back(trampoline);
141   FlyingPlatform* flying_platform = dynamic_cast<FlyingPlatform*> (object);
142   if(flying_platform)
143     flying_platforms.push_back(flying_platform);
144
145   gameobjects.push_back(object);
146 }
147
148 void
149 World::parse_objects(lisp_object_t* cur)
150 {
151   while(!lisp_nil_p(cur)) {
152     lisp_object_t* data = lisp_car(cur);
153     std::string object_type = lisp_symbol(lisp_car(data));
154     
155     LispReader reader(lisp_cdr(data));
156
157     if(object_type == "trampoline") {
158       add_object(new Trampoline(displaymanager, reader));
159     }
160     else if(object_type == "flying-platform") {
161       add_object(new FlyingPlatform(displaymanager, reader));
162     }
163     else {
164       BadGuyKind kind = badguykind_from_string(object_type);
165       add_object(new BadGuy(displaymanager, kind, reader));
166     }
167       
168     cur = lisp_cdr(cur);
169   } 
170 }
171
172 void
173 World::activate_particle_systems()
174 {
175   if (level->particle_system == "clouds")
176     {
177       add_object(new CloudParticleSystem(displaymanager));
178     }
179   else if (level->particle_system == "snow")
180     {
181       add_object(new SnowParticleSystem(displaymanager));
182     }
183   else if (level->particle_system != "")
184     {
185       st_abort("unknown particle system specified in level", "");
186     }
187 }
188
189 void
190 World::draw()
191 {
192   /* Draw objects */
193   displaymanager.draw(*camera);
194 }
195
196 void
197 World::action(float elapsed_time)
198 {
199   tux->check_bounds(*camera,
200       level->back_scrolling, (bool)level->hor_autoscroll_speed);
201     
202   /* update objects (don't use iterators here, because the list might change
203    * during the iteration)
204    */
205   for(size_t i = 0; i < gameobjects.size(); ++i)
206     if(gameobjects[i]->is_valid())
207       gameobjects[i]->action(elapsed_time);
208
209   /* Handle all possible collisions. */
210   collision_handler();
211  
212   /** cleanup marked objects */
213   for(std::vector<GameObject*>::iterator i = gameobjects.begin();
214       i != gameobjects.end(); /* nothing */) {
215     if((*i)->is_valid() == false) {
216       Drawable* drawable = dynamic_cast<Drawable*> (*i);
217       if(drawable)
218         displaymanager.remove_drawable(drawable);
219       BadGuy* badguy = dynamic_cast<BadGuy*> (*i);
220       if(badguy) {
221         bad_guys.erase(std::remove(bad_guys.begin(), bad_guys.end(), badguy),
222             bad_guys.end());
223       }
224       Bullet* bullet = dynamic_cast<Bullet*> (*i);
225       if(bullet) {
226         bullets.erase(
227             std::remove(bullets.begin(), bullets.end(), bullet),
228             bullets.end());
229       }
230       Upgrade* upgrade = dynamic_cast<Upgrade*> (*i);
231       if(upgrade) {
232         upgrades.erase(
233             std::remove(upgrades.begin(), upgrades.end(), upgrade),
234             upgrades.end());
235       }
236       Trampoline* trampoline = dynamic_cast<Trampoline*> (*i);
237       if(trampoline) {
238         trampolines.erase(
239             std::remove(trampolines.begin(), trampolines.end(), trampoline),
240             trampolines.end());
241       }
242       FlyingPlatform* flying_platform= dynamic_cast<FlyingPlatform*> (*i);
243       if(flying_platform) {
244         flying_platforms.erase(
245             std::remove(flying_platforms.begin(), flying_platforms.end(), flying_platform),
246             flying_platforms.end());
247       }
248       
249       delete *i;
250       i = gameobjects.erase(i);
251     } else {
252       ++i;
253     }
254   }
255 }
256
257 void
258 World::collision_handler()
259 {
260   // CO_BULLET & CO_BADGUY check
261   for(unsigned int i = 0; i < bullets.size(); ++i)
262     {
263       for (BadGuys::iterator j = bad_guys.begin(); j != bad_guys.end(); ++j)
264         {
265           if((*j)->dying != DYING_NOT)
266             continue;
267           
268           if(rectcollision(bullets[i]->base, (*j)->base))
269             {
270               // We have detected a collision and now call the
271               // collision functions of the collided objects.
272               (*j)->collision(bullets[i], CO_BULLET, COLLISION_NORMAL);
273               bullets[i]->collision(CO_BADGUY);
274               break; // bullet is invalid now, so break
275             }
276         }
277     }
278
279   /* CO_BADGUY & CO_BADGUY check */
280   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
281     {
282       if((*i)->dying != DYING_NOT)
283         continue;
284       
285       BadGuys::iterator j = i;
286       ++j;
287       for (; j != bad_guys.end(); ++j)
288         {
289           if(j == i || (*j)->dying != DYING_NOT)
290             continue;
291
292           if(rectcollision((*i)->base, (*j)->base))
293             {
294               // We have detected a collision and now call the
295               // collision functions of the collided objects.
296               (*j)->collision(*i, CO_BADGUY);
297               (*i)->collision(*j, CO_BADGUY);
298             }
299         }
300     }
301
302   if(tux->dying != DYING_NOT) return;
303     
304   // CO_BADGUY & CO_PLAYER check 
305   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
306     {
307       if((*i)->dying != DYING_NOT)
308         continue;
309       
310       if(rectcollision_offset((*i)->base, tux->base, 0, 0))
311         {
312           // We have detected a collision and now call the collision
313           // functions of the collided objects.
314           if (tux->previous_base.y < tux->base.y &&
315               tux->previous_base.y + tux->previous_base.height 
316               < (*i)->base.y + (*i)->base.height/2
317               && !tux->invincible_timer.started())
318             {
319               (*i)->collision(tux, CO_PLAYER, COLLISION_SQUISH);
320             }
321           else
322             {
323               tux->collision(*i, CO_BADGUY);
324               (*i)->collision(tux, CO_PLAYER, COLLISION_NORMAL);
325             }
326         }
327     }
328
329   // CO_UPGRADE & CO_PLAYER check
330   for(unsigned int i = 0; i < upgrades.size(); ++i)
331     {
332       if(rectcollision(upgrades[i]->base, tux->base))
333         {
334           // We have detected a collision and now call the collision
335           // functions of the collided objects.
336           upgrades[i]->collision(tux, CO_PLAYER, COLLISION_NORMAL);
337         }
338     }
339
340   // CO_TRAMPOLINE & (CO_PLAYER or CO_BADGUY)
341   for (Trampolines::iterator i = trampolines.begin(); i != trampolines.end(); ++i)
342   {
343     if (rectcollision((*i)->base, tux->base))
344     {
345       if (tux->previous_base.y < tux->base.y &&
346           tux->previous_base.y + tux->previous_base.height 
347           < (*i)->base.y + (*i)->base.height/2)
348       {
349         (*i)->collision(tux, CO_PLAYER, COLLISION_SQUISH);
350       }
351       else if (tux->previous_base.y <= tux->base.y)
352       {
353         tux->collision(*i, CO_TRAMPOLINE);
354         (*i)->collision(tux, CO_PLAYER, COLLISION_NORMAL);
355       }
356     }
357   }
358
359   // CO_FLYING_PLATFORM & (CO_PLAYER or CO_BADGUY)
360   for (FlyingPlatforms::iterator i = flying_platforms.begin(); i != flying_platforms.end(); ++i)
361   {
362     if (rectcollision((*i)->base, tux->base))
363     {
364       if (tux->previous_base.y < tux->base.y &&
365           tux->previous_base.y + tux->previous_base.height 
366           < (*i)->base.y + (*i)->base.height/2)
367       {
368         (*i)->collision(tux, CO_PLAYER, COLLISION_SQUISH);
369         tux->collision(*i, CO_FLYING_PLATFORM);
370       }
371 /*      else if (tux->previous_base.y <= tux->base.y)
372       {
373       }*/
374     }
375   }
376 }
377
378 void
379 World::add_score(const Vector& pos, int s)
380 {
381   player_status.score += s;
382
383   add_object(new FloatingScore(displaymanager, pos, s));
384 }
385
386 void
387 World::add_bouncy_distro(const Vector& pos)
388 {
389   add_object(new BouncyDistro(displaymanager, pos));
390 }
391
392 void
393 World::add_broken_brick(const Vector& pos, Tile* tile)
394 {
395   add_broken_brick_piece(pos, Vector(-1, -4), tile);
396   add_broken_brick_piece(pos + Vector(0, 16), Vector(-1.5, -3), tile);
397
398   add_broken_brick_piece(pos + Vector(16, 0), Vector(1, -4), tile);
399   add_broken_brick_piece(pos + Vector(16, 16), Vector(1.5, -3), tile);
400 }
401
402 void
403 World::add_broken_brick_piece(const Vector& pos, const Vector& movement,
404     Tile* tile)
405 {
406   add_object(new BrokenBrick(displaymanager, tile, pos, movement));
407 }
408
409 void
410 World::add_bouncy_brick(const Vector& pos)
411 {
412   add_object(new BouncyBrick(displaymanager, pos));
413 }
414
415 BadGuy*
416 World::add_bad_guy(float x, float y, BadGuyKind kind)
417 {
418   BadGuy* badguy = new BadGuy(displaymanager, kind, x, y);
419   add_object(badguy);
420   return badguy;
421 }
422
423 void
424 World::add_upgrade(const Vector& pos, Direction dir, UpgradeKind kind)
425 {
426   add_object(new Upgrade(displaymanager, pos, dir, kind));
427 }
428
429 bool
430 World::add_bullet(const Vector& pos, float xm, Direction dir)
431 {
432   if(tux->got_power == Player::FIRE_POWER)
433     {
434     if(bullets.size() > MAX_FIRE_BULLETS-1)
435       return false;
436     }
437   else if(tux->got_power == Player::ICE_POWER)
438     {
439     if(bullets.size() > MAX_ICE_BULLETS-1)
440       return false;
441     }
442
443   Bullet* new_bullet = 0;
444   if(tux->got_power == Player::FIRE_POWER)
445     new_bullet = new Bullet(displaymanager, pos, xm, dir, FIRE_BULLET);
446   else if(tux->got_power == Player::ICE_POWER)
447     new_bullet = new Bullet(displaymanager, pos, xm, dir, ICE_BULLET);
448   else
449     st_abort("wrong bullet type.", "");
450   add_object(new_bullet);
451   
452   play_sound(sounds[SND_SHOOT], SOUND_CENTER_SPEAKER);
453
454   return true;
455 }
456
457 void
458 World::play_music(int musictype)
459 {
460   currentmusic = musictype;
461   switch(currentmusic) {
462     case HURRYUP_MUSIC:
463       music_manager->play_music(get_level()->get_level_music_fast());
464       break;
465     case LEVEL_MUSIC:
466       music_manager->play_music(get_level()->get_level_music());
467       break;
468     case HERRING_MUSIC:
469       music_manager->play_music(herring_song);
470       break;
471     default:
472       music_manager->halt_music();
473       break;
474   }
475 }
476
477 int
478 World::get_music_type()
479 {
480   return currentmusic;
481 }
482
483 /* Break a brick: */
484 bool
485 World::trybreakbrick(float x, float y, bool small)
486 {
487   Level* plevel = get_level();
488   
489   Tile* tile = gettile(x, y);
490   if (tile->brick)
491     {
492       if (tile->data > 0)
493         {
494           /* Get a distro from it: */
495           add_bouncy_distro(
496               Vector(((int)(x + 1) / 32) * 32, (int)(y / 32) * 32));
497
498           // TODO: don't handle this in a global way but per-tile...
499           if (!counting_distros)
500             {
501               counting_distros = true;
502               distro_counter = 5;
503             }
504           else
505             {
506               distro_counter--;
507             }
508
509           if (distro_counter <= 0)
510             {
511               counting_distros = false;
512               plevel->change(x, y, TM_IA, tile->next_tile);
513             }
514
515           play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
516           player_status.score = player_status.score + SCORE_DISTRO;
517           player_status.distros++;
518           return true;
519         }
520       else if (!small)
521         {
522           /* Get rid of it: */
523           plevel->change(x, y, TM_IA, tile->next_tile);
524           
525           /* Replace it with broken bits: */
526           add_broken_brick(Vector(
527                                  ((int)(x + 1) / 32) * 32,
528                                  (int)(y / 32) * 32), tile);
529           
530           /* Get some score: */
531           play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
532           player_status.score = player_status.score + SCORE_BRICK;
533           
534           return true;
535         }
536     }
537
538   return false;
539 }
540
541 /* Empty a box: */
542 void
543 World::tryemptybox(float x, float y, Direction col_side)
544 {
545   Tile* tile = gettile(x,y);
546   if (!tile->fullbox)
547     return;
548
549   // according to the collision side, set the upgrade direction
550   if(col_side == LEFT)
551     col_side = RIGHT;
552   else
553     col_side = LEFT;
554
555   int posx = ((int)(x+1) / 32) * 32;
556   int posy = (int)(y/32) * 32 - 32;
557   switch(tile->data)
558     {
559     case 1: // Box with a distro!
560       add_bouncy_distro(Vector(posx, posy));
561       play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
562       player_status.score = player_status.score + SCORE_DISTRO;
563       player_status.distros++;
564       break;
565
566     case 2: // Add a fire flower upgrade!
567       if (tux->size == SMALL)     /* Tux is small, add mints! */
568         add_upgrade(Vector(posx, posy), col_side, UPGRADE_GROWUP);
569       else     /* Tux is big, add a fireflower: */
570         add_upgrade(Vector(posx, posy), col_side, UPGRADE_FIREFLOWER);
571       play_sound(sounds[SND_UPGRADE], SOUND_CENTER_SPEAKER);
572       break;
573     
574     case 5: // Add an ice flower upgrade!
575       if (tux->size == SMALL)     /* Tux is small, add mints! */
576         add_upgrade(Vector(posx, posy), col_side, UPGRADE_GROWUP);
577       else     /* Tux is big, add an iceflower: */
578         add_upgrade(Vector(posx, posy), col_side, UPGRADE_ICEFLOWER);
579       play_sound(sounds[SND_UPGRADE], SOUND_CENTER_SPEAKER);
580       break;
581
582     case 3: // Add a golden herring
583       add_upgrade(Vector(posx, posy), col_side, UPGRADE_HERRING);
584       break;
585
586     case 4: // Add a 1up extra
587       add_upgrade(Vector(posx, posy), col_side, UPGRADE_1UP);
588       break;
589     default:
590       break;
591     }
592
593   /* Empty the box: */
594   level->change(x, y, TM_IA, tile->next_tile);
595 }
596
597 /* Try to grab a distro: */
598 void
599 World::trygrabdistro(float x, float y, int bounciness)
600 {
601   Tile* tile = gettile(x, y);
602   if (tile && tile->distro)
603     {
604       level->change(x, y, TM_IA, tile->next_tile);
605       play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
606
607       if (bounciness == BOUNCE)
608         {
609           add_bouncy_distro(Vector(((int)(x + 1) / 32) * 32,
610                                   (int)(y / 32) * 32));
611         }
612
613       player_status.score = player_status.score + SCORE_DISTRO;
614       player_status.distros++;
615     }
616 }
617
618 /* Try to bump a bad guy from below: */
619 void
620 World::trybumpbadguy(float x, float y)
621 {
622   // Bad guys: 
623   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
624     {
625       if ((*i)->base.x >= x - 32 && (*i)->base.x <= x + 32 &&
626           (*i)->base.y >= y - 16 && (*i)->base.y <= y + 16)
627         {
628           (*i)->collision(tux, CO_PLAYER, COLLISION_BUMP);
629         }
630     }
631
632   // Upgrades:
633   for (unsigned int i = 0; i < upgrades.size(); i++)
634     {
635       if (upgrades[i]->base.height == 32 &&
636           upgrades[i]->base.x >= x - 32 && upgrades[i]->base.x <= x + 32 &&
637           upgrades[i]->base.y >= y - 16 && upgrades[i]->base.y <= y + 16)
638         {
639           upgrades[i]->collision(tux, CO_PLAYER, COLLISION_BUMP);
640         }
641     }
642 }
643
644 /* EOF */
645