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