-converted remaining classes to GameObject
[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 "viewport.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)
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   level->load(filename, this);
53
54   tux = new Player(displaymanager);
55   gameobjects.push_back(tux);
56
57   set_defaults();
58
59   get_level()->load_gfx();
60   // add background
61   activate_particle_systems();
62   Background* bg = new Background(displaymanager);
63   if(level->img_bkgd) {
64     bg->set_image(level->img_bkgd, level->bkgd_speed);
65   } else {
66     bg->set_gradient(level->bkgd_top, level->bkgd_bottom);
67   }
68   gameobjects.push_back(bg);
69
70   // add tilemap
71   gameobjects.push_back(new TileMap(displaymanager, get_level()));
72   get_level()->load_song();
73
74   apply_bonuses();
75
76   scrolling_timer.init(true);
77 }
78
79 World::World(const std::string& subset, int level_nr)
80 {
81   // FIXME: Move this to action and draw and everywhere else where the
82   // world calls child functions
83   current_ = this;
84
85   level = new Level();
86   level->load(subset, level_nr, this);
87
88   tux = new Player(displaymanager);
89   gameobjects.push_back(tux);        
90
91   set_defaults();
92
93   get_level()->load_gfx();
94   activate_particle_systems();
95   Background* bg = new Background(displaymanager);
96   if(level->img_bkgd) {
97     bg->set_image(level->img_bkgd, level->bkgd_speed);
98   } else {
99     bg->set_gradient(level->bkgd_top, level->bkgd_bottom);
100   }
101   gameobjects.push_back(bg);
102   // add tilemap
103   gameobjects.push_back(new TileMap(displaymanager, get_level()));  
104   get_level()->load_song();
105
106   apply_bonuses();
107
108   scrolling_timer.init(true);
109 }
110
111 void
112 World::apply_bonuses()
113 {
114   // Apply bonuses from former levels
115   switch (player_status.bonus)
116     {
117     case PlayerStatus::NO_BONUS:
118       break;
119                                                                                 
120     case PlayerStatus::FLOWER_BONUS:
121       tux->got_power = Player::FIRE_POWER;  // FIXME: add ice power to here
122       // fall through
123                                                                                 
124     case PlayerStatus::GROWUP_BONUS:
125       tux->grow();
126       break;
127     }
128 }
129
130 World::~World()
131 {
132   for (Trampolines::iterator i = trampolines.begin(); i != trampolines.end(); ++i)
133     delete *i;
134
135   for (std::vector<GameObject*>::iterator i = gameobjects.begin();
136           i != gameobjects.end(); ++i) {
137     Drawable* drawable = dynamic_cast<Drawable*> (*i);
138     if(drawable)
139       displaymanager.remove_drawable(drawable);
140     delete *i;
141   }
142   bad_guys.clear();
143
144   delete level;
145 }
146
147 void
148 World::set_defaults()
149 {
150   // Set defaults: 
151   scroll_x = 0;
152
153   player_status.score_multiplier = 1;
154
155   counting_distros = false;
156   distro_counter = 0;
157
158   /* set current song/music */
159   currentmusic = LEVEL_MUSIC;
160 }
161
162 void
163 World::add_object(GameObject* object)
164 {
165   // XXX hack for now until new collision code is ready
166   BadGuy* badguy = dynamic_cast<BadGuy*> (object);
167   if(badguy)
168     bad_guys.push_back(badguy);
169   Bullet* bullet = dynamic_cast<Bullet*> (object);
170   if(bullet)
171     bullets.push_back(bullet);
172   Upgrade* upgrade = dynamic_cast<Upgrade*> (object);
173   if(upgrade)
174     upgrades.push_back(upgrade);
175   Trampoline* trampoline = dynamic_cast<Trampoline*> (object);
176   if(trampoline)
177     trampolines.push_back(trampoline);
178
179   gameobjects.push_back(object);
180 }
181
182 void
183 World::parse_objects(lisp_object_t* cur)
184 {
185   while(!lisp_nil_p(cur)) {
186     lisp_object_t* data = lisp_car(cur);
187     std::string object_type = lisp_symbol(lisp_car(data));
188     
189     LispReader reader(lisp_cdr(data));
190
191     if(object_type == "trampoline") {
192       add_object(new Trampoline(displaymanager, reader));
193     } else {
194       BadGuyKind kind = badguykind_from_string(object_type);
195       add_object(new BadGuy(displaymanager, kind, reader));
196     }
197       
198     cur = lisp_cdr(cur);
199   } 
200 }
201
202 void
203 World::activate_particle_systems()
204 {
205   if (level->particle_system == "clouds")
206     {
207       add_object(new CloudParticleSystem(displaymanager));
208     }
209   else if (level->particle_system == "snow")
210     {
211       add_object(new SnowParticleSystem(displaymanager));
212     }
213   else if (level->particle_system != "")
214     {
215       st_abort("unknown particle system specified in level", "");
216     }
217 }
218
219 void
220 World::draw()
221 {
222   /* Draw objects */
223   displaymanager.get_viewport().set_translation(Vector(scroll_x, scroll_y));
224   displaymanager.draw();
225 }
226
227 void
228 World::action(double frame_ratio)
229 {
230   tux->check_bounds(level->back_scrolling, (bool)level->hor_autoscroll_speed);
231   scrolling(frame_ratio);
232
233   /* update objects (don't use iterators here, because the list might change
234    * during the iteration)
235    */
236   for(size_t i = 0; i < gameobjects.size(); ++i)
237     gameobjects[i]->action(frame_ratio);
238
239   /* Handle all possible collisions. */
240   collision_handler();
241  
242   /** cleanup marked objects */
243   for(std::vector<GameObject*>::iterator i = gameobjects.begin();
244       i != gameobjects.end(); /* nothing */) {
245     if((*i)->is_valid() == false) {
246       Drawable* drawable = dynamic_cast<Drawable*> (*i);
247       if(drawable)
248         displaymanager.remove_drawable(drawable);
249       BadGuy* badguy = dynamic_cast<BadGuy*> (*i);
250       if(badguy) {
251         bad_guys.erase(std::remove(bad_guys.begin(), bad_guys.end(), badguy),
252             bad_guys.end());
253       }
254       Bullet* bullet = dynamic_cast<Bullet*> (*i);
255       if(bullet) {
256         bullets.erase(
257             std::remove(bullets.begin(), bullets.end(), bullet),
258             bullets.end());
259       }
260       Upgrade* upgrade = dynamic_cast<Upgrade*> (*i);
261       if(upgrade) {
262         upgrades.erase(
263             std::remove(upgrades.begin(), upgrades.end(), upgrade),
264             upgrades.end());
265       }
266       Trampoline* trampoline = dynamic_cast<Trampoline*> (*i);
267       if(trampoline) {
268         trampolines.erase(
269             std::remove(trampolines.begin(), trampolines.end(), trampoline),
270             trampolines.end());
271       }
272       
273       delete *i;
274       i = gameobjects.erase(i);
275     } else {
276       ++i;
277     }
278   }
279 }
280
281 /* the space that it takes for the screen to start scrolling, regarding */
282 /* screen bounds (in pixels) */
283 // should be higher than screen->w/2 (400)
284 #define X_SPACE (500-16)
285 // should be less than screen->h/2 (300)
286 #define Y_SPACE 250
287
288 // the time it takes to move the camera (in ms)
289 #define CHANGE_DIR_SCROLL_SPEED 2000
290
291 /* This functions takes cares of the scrolling */
292 void World::scrolling(double frame_ratio)
293 {
294   /* Y-axis scrolling */
295
296   float tux_pos_y = tux->base.y + (tux->base.height/2);
297
298   if(level->height > VISIBLE_TILES_Y-1 && !tux->dying)
299     {
300     if (scroll_y < tux_pos_y - (screen->h - Y_SPACE))
301       scroll_y = tux_pos_y - (screen->h - Y_SPACE);
302     else if (scroll_y > tux_pos_y - Y_SPACE)
303       scroll_y = tux_pos_y - Y_SPACE;
304     }
305
306   // this code prevent the screen to scroll before the start or after the level's end
307   if(scroll_y > level->height * 32 - screen->h)
308     scroll_y = level->height * 32 - screen->h;
309   if(scroll_y < 0)
310     scroll_y = 0;
311
312   /* X-axis scrolling */
313
314   /* Auto scrolling */
315   if(level->hor_autoscroll_speed)
316   {
317     scroll_x += level->hor_autoscroll_speed * frame_ratio;
318     return;
319   }
320
321
322   /* Horizontal backscrolling */
323   float tux_pos_x = tux->base.x + (tux->base.width/2);
324
325   if(tux->old_dir != tux->dir && level->back_scrolling)
326     scrolling_timer.start(CHANGE_DIR_SCROLL_SPEED);
327
328   bool right = false;
329   bool left = false;
330   if (tux->physic.get_velocity_x() > 0)
331     right = true;
332   else if (tux->physic.get_velocity_x() < 0)
333     left = true;
334   else
335     {
336     if (tux->dir == RIGHT)
337       right = true;
338     else
339       left = true;
340     }
341
342   if(scrolling_timer.check())
343   {
344     float final_scroll_x;
345     float constant1;
346     float constant2;
347     if (right)
348       final_scroll_x = tux_pos_x - (screen->w - X_SPACE);
349     else
350       final_scroll_x = tux_pos_x - X_SPACE;
351
352     if((tux->physic.get_velocity_x() > 0 && tux->dir == RIGHT)
353         || (tux->physic.get_velocity_x() < 0 && tux->dir == LEFT))
354     {
355       constant1 = 1.0;
356       constant2 = .4;
357     }
358     else
359     {
360       constant1 = 0.;
361       constant2 = 0.;
362     }
363     
364     float number = 2.5/(frame_ratio * CHANGE_DIR_SCROLL_SPEED/1000)*exp((CHANGE_DIR_SCROLL_SPEED-scrolling_timer.get_left())/1400.);
365     if(left) number *= -1.;
366
367     scroll_x += number
368             + constant1 * tux->physic.get_velocity_x() * frame_ratio
369             + constant2 * tux->physic.get_acceleration_x() * frame_ratio * frame_ratio;
370
371     if ((right && final_scroll_x - scroll_x < 0) || (left && final_scroll_x - scroll_x > 0))
372       scroll_x = final_scroll_x;
373     
374   }
375   else
376   {
377     if (right && scroll_x < tux_pos_x - (screen->w - X_SPACE))
378       scroll_x = tux_pos_x - (screen->w - X_SPACE);
379     else if (left && scroll_x > tux_pos_x - X_SPACE && level->back_scrolling)
380       scroll_x = tux_pos_x - X_SPACE;
381   }
382
383   // this code prevent the screen to scroll before the start or after the level's end
384   if(scroll_x > level->width * 32 - screen->w)
385     scroll_x = level->width * 32 - screen->w;
386   if(scroll_x < 0)
387     scroll_x = 0;
388 }
389
390 void
391 World::collision_handler()
392 {
393   // CO_BULLET & CO_BADGUY check
394   for(unsigned int i = 0; i < bullets.size(); ++i)
395     {
396       for (BadGuys::iterator j = bad_guys.begin(); j != bad_guys.end(); ++j)
397         {
398           if((*j)->dying != DYING_NOT)
399             continue;
400           
401           if(rectcollision(bullets[i]->base, (*j)->base))
402             {
403               // We have detected a collision and now call the
404               // collision functions of the collided objects.
405               (*j)->collision(&bullets[i], CO_BULLET, COLLISION_NORMAL);
406               bullets[i]->collision(CO_BADGUY);
407               break; // bullet is invalid now, so break
408             }
409         }
410     }
411
412   /* CO_BADGUY & CO_BADGUY check */
413   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
414     {
415       if((*i)->dying != DYING_NOT)
416         continue;
417       
418       BadGuys::iterator j = i;
419       ++j;
420       for (; j != bad_guys.end(); ++j)
421         {
422           if(j == i || (*j)->dying != DYING_NOT)
423             continue;
424
425           if(rectcollision((*i)->base, (*j)->base))
426             {
427               // We have detected a collision and now call the
428               // collision functions of the collided objects.
429               (*j)->collision(*i, CO_BADGUY);
430               (*i)->collision(*j, CO_BADGUY);
431             }
432         }
433     }
434
435   if(tux->dying != DYING_NOT) return;
436     
437   // CO_BADGUY & CO_PLAYER check 
438   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
439     {
440       if((*i)->dying != DYING_NOT)
441         continue;
442       
443       if(rectcollision_offset((*i)->base, tux->base, 0, 0))
444         {
445           // We have detected a collision and now call the collision
446           // functions of the collided objects.
447           if (tux->previous_base.y < tux->base.y &&
448               tux->previous_base.y + tux->previous_base.height 
449               < (*i)->base.y + (*i)->base.height/2
450               && !tux->invincible_timer.started())
451             {
452               (*i)->collision(tux, CO_PLAYER, COLLISION_SQUISH);
453             }
454           else
455             {
456               tux->collision(*i, CO_BADGUY);
457               (*i)->collision(tux, CO_PLAYER, COLLISION_NORMAL);
458             }
459         }
460     }
461
462   // CO_UPGRADE & CO_PLAYER check
463   for(unsigned int i = 0; i < upgrades.size(); ++i)
464     {
465       if(rectcollision(upgrades[i]->base, tux->base))
466         {
467           // We have detected a collision and now call the collision
468           // functions of the collided objects.
469           upgrades[i]->collision(tux, CO_PLAYER, COLLISION_NORMAL);
470         }
471     }
472
473   // CO_TRAMPOLINE & (CO_PLAYER or CO_BADGUY)
474   for (Trampolines::iterator i = trampolines.begin(); i != trampolines.end(); ++i)
475   {
476     if (rectcollision((*i)->base, tux->base))
477     {
478       if (tux->previous_base.y < tux->base.y &&
479           tux->previous_base.y + tux->previous_base.height 
480           < (*i)->base.y + (*i)->base.height/2)
481       {
482         (*i)->collision(tux, CO_PLAYER, COLLISION_SQUISH);
483       }
484       else if (tux->previous_base.y <= tux->base.y)
485       {
486         tux->collision(*i, CO_TRAMPOLINE);
487         (*i)->collision(tux, CO_PLAYER, COLLISION_NORMAL);
488       }
489     }
490   }
491 }
492
493 void
494 World::add_score(const Vector& pos, int s)
495 {
496   player_status.score += s;
497
498   add_object(new FloatingScore(displaymanager, pos, s));
499 }
500
501 void
502 World::add_bouncy_distro(const Vector& pos)
503 {
504   add_object(new BouncyDistro(displaymanager, pos));
505 }
506
507 void
508 World::add_broken_brick(const Vector& pos, Tile* tile)
509 {
510   add_broken_brick_piece(pos, Vector(-1, -4), tile);
511   add_broken_brick_piece(pos + Vector(0, 16), Vector(-1.5, -3), tile);
512
513   add_broken_brick_piece(pos + Vector(16, 0), Vector(1, -4), tile);
514   add_broken_brick_piece(pos + Vector(16, 16), Vector(1.5, -3), tile);
515 }
516
517 void
518 World::add_broken_brick_piece(const Vector& pos, const Vector& movement,
519     Tile* tile)
520 {
521   add_object(new BrokenBrick(displaymanager, tile, pos, movement));
522 }
523
524 void
525 World::add_bouncy_brick(const Vector& pos)
526 {
527   add_object(new BouncyBrick(displaymanager, pos));
528 }
529
530 BadGuy*
531 World::add_bad_guy(float x, float y, BadGuyKind kind)
532 {
533   BadGuy* badguy = new BadGuy(displaymanager, kind, x, y);
534   add_object(badguy);
535   return badguy;
536 }
537
538 void
539 World::add_upgrade(const Vector& pos, Direction dir, UpgradeKind kind)
540 {
541   add_object(new Upgrade(displaymanager, pos, dir, kind));
542 }
543
544 void 
545 World::add_bullet(const Vector& pos, float xm, Direction dir)
546 {
547   if(tux->got_power == Player::FIRE_POWER)
548     {
549     if(bullets.size() > MAX_FIRE_BULLETS-1)
550       return;
551     }
552   else if(tux->got_power == Player::ICE_POWER)
553     {
554     if(bullets.size() > MAX_ICE_BULLETS-1)
555       return;
556     }
557
558   Bullet* new_bullet = 0;
559   if(tux->got_power == Player::FIRE_POWER)
560     new_bullet = new Bullet(displaymanager, pos, xm, dir, FIRE_BULLET);
561   else if(tux->got_power == Player::ICE_POWER)
562     new_bullet = new Bullet(displaymanager, pos, xm, dir, ICE_BULLET);
563   else
564     st_abort("wrong bullet type.", "");
565   add_object(new_bullet);
566   
567   play_sound(sounds[SND_SHOOT], SOUND_CENTER_SPEAKER);
568 }
569
570 void
571 World::play_music(int musictype)
572 {
573   currentmusic = musictype;
574   switch(currentmusic) {
575     case HURRYUP_MUSIC:
576       music_manager->play_music(get_level()->get_level_music_fast());
577       break;
578     case LEVEL_MUSIC:
579       music_manager->play_music(get_level()->get_level_music());
580       break;
581     case HERRING_MUSIC:
582       music_manager->play_music(herring_song);
583       break;
584     default:
585       music_manager->halt_music();
586       break;
587   }
588 }
589
590 int
591 World::get_music_type()
592 {
593   return currentmusic;
594 }
595
596 /* Break a brick: */
597 bool
598 World::trybreakbrick(float x, float y, bool small)
599 {
600   Level* plevel = get_level();
601   
602   Tile* tile = gettile(x, y);
603   if (tile->brick)
604     {
605       if (tile->data > 0)
606         {
607           /* Get a distro from it: */
608           add_bouncy_distro(
609               Vector(((int)(x + 1) / 32) * 32, (int)(y / 32) * 32));
610
611           // TODO: don't handle this in a global way but per-tile...
612           if (!counting_distros)
613             {
614               counting_distros = true;
615               distro_counter = 5;
616             }
617           else
618             {
619               distro_counter--;
620             }
621
622           if (distro_counter <= 0)
623             {
624               counting_distros = false;
625               plevel->change(x, y, TM_IA, tile->next_tile);
626             }
627
628           play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
629           player_status.score = player_status.score + SCORE_DISTRO;
630           player_status.distros++;
631           return true;
632         }
633       else if (!small)
634         {
635           /* Get rid of it: */
636           plevel->change(x, y, TM_IA, tile->next_tile);
637           
638           /* Replace it with broken bits: */
639           add_broken_brick(Vector(
640                                  ((int)(x + 1) / 32) * 32,
641                                  (int)(y / 32) * 32), tile);
642           
643           /* Get some score: */
644           play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
645           player_status.score = player_status.score + SCORE_BRICK;
646           
647           return true;
648         }
649     }
650
651   return false;
652 }
653
654 /* Empty a box: */
655 void
656 World::tryemptybox(float x, float y, Direction col_side)
657 {
658   Tile* tile = gettile(x,y);
659   if (!tile->fullbox)
660     return;
661
662   // according to the collision side, set the upgrade direction
663   if(col_side == LEFT)
664     col_side = RIGHT;
665   else
666     col_side = LEFT;
667
668   int posx = ((int)(x+1) / 32) * 32;
669   int posy = (int)(y/32) * 32 - 32;
670   switch(tile->data)
671     {
672     case 1: // Box with a distro!
673       add_bouncy_distro(Vector(posx, posy));
674       play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
675       player_status.score = player_status.score + SCORE_DISTRO;
676       player_status.distros++;
677       break;
678
679     case 2: // Add a fire flower upgrade!
680       if (tux->size == SMALL)     /* Tux is small, add mints! */
681         add_upgrade(Vector(posx, posy), col_side, UPGRADE_GROWUP);
682       else     /* Tux is big, add a fireflower: */
683         add_upgrade(Vector(posx, posy), col_side, UPGRADE_FIREFLOWER);
684       play_sound(sounds[SND_UPGRADE], SOUND_CENTER_SPEAKER);
685       break;
686     
687     case 5: // Add an ice flower upgrade!
688       if (tux->size == SMALL)     /* Tux is small, add mints! */
689         add_upgrade(Vector(posx, posy), col_side, UPGRADE_GROWUP);
690       else     /* Tux is big, add an iceflower: */
691         add_upgrade(Vector(posx, posy), col_side, UPGRADE_ICEFLOWER);
692       play_sound(sounds[SND_UPGRADE], SOUND_CENTER_SPEAKER);
693       break;
694
695     case 3: // Add a golden herring
696       add_upgrade(Vector(posx, posy), col_side, UPGRADE_HERRING);
697       break;
698
699     case 4: // Add a 1up extra
700       add_upgrade(Vector(posx, posy), col_side, UPGRADE_1UP);
701       break;
702     default:
703       break;
704     }
705
706   /* Empty the box: */
707   level->change(x, y, TM_IA, tile->next_tile);
708 }
709
710 /* Try to grab a distro: */
711 void
712 World::trygrabdistro(float x, float y, int bounciness)
713 {
714   Tile* tile = gettile(x, y);
715   if (tile && tile->distro)
716     {
717       level->change(x, y, TM_IA, tile->next_tile);
718       play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
719
720       if (bounciness == BOUNCE)
721         {
722           add_bouncy_distro(Vector(((int)(x + 1) / 32) * 32,
723                                   (int)(y / 32) * 32));
724         }
725
726       player_status.score = player_status.score + SCORE_DISTRO;
727       player_status.distros++;
728     }
729 }
730
731 /* Try to bump a bad guy from below: */
732 void
733 World::trybumpbadguy(float x, float y)
734 {
735   // Bad guys: 
736   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
737     {
738       if ((*i)->base.x >= x - 32 && (*i)->base.x <= x + 32 &&
739           (*i)->base.y >= y - 16 && (*i)->base.y <= y + 16)
740         {
741           (*i)->collision(tux, CO_PLAYER, COLLISION_BUMP);
742         }
743     }
744
745   // Upgrades:
746   for (unsigned int i = 0; i < upgrades.size(); i++)
747     {
748       if (upgrades[i]->base.height == 32 &&
749           upgrades[i]->base.x >= x - 32 && upgrades[i]->base.x <= x + 32 &&
750           upgrades[i]->base.y >= y - 16 && upgrades[i]->base.y <= y + 16)
751         {
752           upgrades[i]->collision(tux, CO_PLAYER, COLLISION_BUMP);
753         }
754     }
755 }
756
757 /* EOF */
758