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