Made level editor to use the draw_bg() that Ryan added to level. This way the backgro...
[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(level->height > VISIBLE_TILES_Y-1)
325     {
326     if (scroll_y < tux_pos_y - (screen->h - Y_SPACE))
327       scroll_y = tux_pos_y - (screen->h - Y_SPACE);
328     else if (scroll_y > tux_pos_y - Y_SPACE)
329       scroll_y = tux_pos_y - Y_SPACE;
330     }
331
332   // this code prevent the screen to scroll before the start or after the level's end
333   if(scroll_y > level->height * 32 - screen->h)
334     scroll_y = level->height * 32 - screen->h;
335   if(scroll_y < 0)
336     scroll_y = 0;
337
338   /* X-axis scrolling */
339
340   /* Auto scrolling */
341   if(level->hor_autoscroll_speed)
342   {
343     scroll_x += level->hor_autoscroll_speed * frame_ratio;
344     return;
345   }
346
347
348   /* Horizontal backscrolling */
349   float tux_pos_x = tux.base.x + (tux.base.width/2);
350
351   if(tux.old_dir != tux.dir && level->back_scrolling)
352     scrolling_timer.start(CHANGE_DIR_SCROLL_SPEED);
353
354   bool right = false;
355   bool left = false;
356   if (tux.physic.get_velocity_x() > 0)
357     right = true;
358   else if (tux.physic.get_velocity_x() < 0)
359     left = true;
360   else
361     {
362     if (tux.dir == RIGHT)
363       right = true;
364     else
365       left = true;
366     }
367
368   if(scrolling_timer.check())
369   {
370     float final_scroll_x;
371     if (right)
372       final_scroll_x = tux_pos_x - (screen->w - X_SPACE);
373     else
374       final_scroll_x = tux_pos_x - X_SPACE;
375
376     scroll_x +=   (final_scroll_x - scroll_x)
377                 / (frame_ratio * (CHANGE_DIR_SCROLL_SPEED / 100))
378                 + (tux.physic.get_velocity_x() * frame_ratio + tux.physic.get_acceleration_x() * frame_ratio * frame_ratio);
379   }
380   else
381   {
382     if (right && scroll_x < tux_pos_x - (screen->w - X_SPACE))
383       scroll_x = tux_pos_x - (screen->w - X_SPACE);
384     else if (left && scroll_x > tux_pos_x - X_SPACE && level->back_scrolling)
385       scroll_x = tux_pos_x - X_SPACE;
386   }
387
388   // this code prevent the screen to scroll before the start or after the level's end
389   if(scroll_x > level->width * 32 - screen->w)
390     scroll_x = level->width * 32 - screen->w;
391   if(scroll_x < 0)
392     scroll_x = 0;
393 }
394
395 void
396 World::collision_handler()
397 {
398   // CO_BULLET & CO_BADGUY check
399   for(unsigned int i = 0; i < bullets.size(); ++i)
400     {
401       for (BadGuys::iterator j = bad_guys.begin(); j != bad_guys.end(); ++j)
402         {
403           if((*j)->dying != DYING_NOT)
404             continue;
405           
406           if(rectcollision(bullets[i].base, (*j)->base))
407             {
408               // We have detected a collision and now call the
409               // collision functions of the collided objects.
410               // collide with bad_guy first, since bullet_collision will
411               // delete the bullet
412               (*j)->collision(&bullets[i], CO_BULLET);
413               bullets[i].collision(CO_BADGUY);
414               break; // bullet is invalid now, so break
415             }
416         }
417     }
418
419   /* CO_BADGUY & CO_BADGUY check */
420   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
421     {
422       if((*i)->dying != DYING_NOT)
423         continue;
424       
425       BadGuys::iterator j = i;
426       ++j;
427       for (; j != bad_guys.end(); ++j)
428         {
429           if(j == i || (*j)->dying != DYING_NOT)
430             continue;
431
432           if(rectcollision((*i)->base, (*j)->base))
433             {
434               // We have detected a collision and now call the
435               // collision functions of the collided objects.
436               (*j)->collision(*i, CO_BADGUY);
437               (*i)->collision(*j, CO_BADGUY);
438             }
439         }
440     }
441
442   if(tux.dying != DYING_NOT) return;
443     
444   // CO_BADGUY & CO_PLAYER check 
445   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
446     {
447       if((*i)->dying != DYING_NOT)
448         continue;
449       
450       if(rectcollision_offset((*i)->base, tux.base, 0, 0))
451         {
452           // We have detected a collision and now call the collision
453           // functions of the collided objects.
454           if (tux.previous_base.y < tux.base.y &&
455               tux.previous_base.y + tux.previous_base.height 
456               < (*i)->base.y + (*i)->base.height/2
457               && !tux.invincible_timer.started())
458             {
459               (*i)->collision(&tux, CO_PLAYER, COLLISION_SQUISH);
460             }
461           else
462             {
463               tux.collision(*i, CO_BADGUY);
464               (*i)->collision(&tux, CO_PLAYER, COLLISION_NORMAL);
465             }
466         }
467     }
468
469   // CO_UPGRADE & CO_PLAYER check
470   for(unsigned int i = 0; i < upgrades.size(); ++i)
471     {
472       if(rectcollision(upgrades[i].base, tux.base))
473         {
474           // We have detected a collision and now call the collision
475           // functions of the collided objects.
476           upgrades[i].collision(&tux, CO_PLAYER, COLLISION_NORMAL);
477         }
478     }
479 }
480
481 void
482 World::add_score(float x, float y, int s)
483 {
484   player_status.score += s;
485
486   FloatingScore* new_floating_score = new FloatingScore();
487   new_floating_score->init(x-scroll_x, y-scroll_y, s);
488   floating_scores.push_back(new_floating_score);
489 }
490
491 void
492 World::add_bouncy_distro(float x, float y)
493 {
494   BouncyDistro* new_bouncy_distro = new BouncyDistro();
495   new_bouncy_distro->init(x, y);
496   bouncy_distros.push_back(new_bouncy_distro);
497 }
498
499 void
500 World::add_broken_brick(Tile* tile, float x, float y)
501 {
502   add_broken_brick_piece(tile, x, y, -1, -4);
503   add_broken_brick_piece(tile, x, y + 16, -1.5, -3);
504
505   add_broken_brick_piece(tile, x + 16, y, 1, -4);
506   add_broken_brick_piece(tile, x + 16, y + 16, 1.5, -3);
507 }
508
509 void
510 World::add_broken_brick_piece(Tile* tile, float x, float y, float xm, float ym)
511 {
512   BrokenBrick* new_broken_brick = new BrokenBrick();
513   new_broken_brick->init(tile, x, y, xm, ym);
514   broken_bricks.push_back(new_broken_brick);
515 }
516
517 void
518 World::add_bouncy_brick(float x, float y)
519 {
520   BouncyBrick* new_bouncy_brick = new BouncyBrick();
521   new_bouncy_brick->init(x,y);
522   bouncy_bricks.push_back(new_bouncy_brick);
523 }
524
525 BadGuy*
526 World::add_bad_guy(float x, float y, BadGuyKind kind, bool stay_on_platform)
527 {
528   BadGuy* badguy = new BadGuy(x,y,kind, stay_on_platform);
529   bad_guys.push_back(badguy);
530   return badguy;
531 }
532
533 void
534 World::add_upgrade(float x, float y, Direction dir, UpgradeKind kind)
535 {
536   Upgrade new_upgrade;
537   new_upgrade.init(x,y,dir,kind);
538   upgrades.push_back(new_upgrade);
539 }
540
541 void 
542 World::add_bullet(float x, float y, float xm, Direction dir)
543 {
544   if(tux.got_power == tux.FIRE_POWER)
545     {
546     if(bullets.size() > MAX_FIRE_BULLETS-1)
547       return;
548     }
549   else if(tux.got_power == tux.ICE_POWER)
550     {
551     if(bullets.size() > MAX_ICE_BULLETS-1)
552       return;
553     }
554
555   Bullet new_bullet;
556   if(tux.got_power == tux.FIRE_POWER)
557     new_bullet.init(x,y,xm,dir, FIRE_BULLET);
558   else if(tux.got_power == tux.ICE_POWER)
559     new_bullet.init(x,y,xm,dir, ICE_BULLET);
560   bullets.push_back(new_bullet);
561   
562   play_sound(sounds[SND_SHOOT], SOUND_CENTER_SPEAKER);
563 }
564
565 void
566 World::play_music(int musictype)
567 {
568   currentmusic = musictype;
569   switch(currentmusic) {
570     case HURRYUP_MUSIC:
571       music_manager->play_music(get_level()->get_level_music_fast());
572       break;
573     case LEVEL_MUSIC:
574       music_manager->play_music(get_level()->get_level_music());
575       break;
576     case HERRING_MUSIC:
577       music_manager->play_music(herring_song);
578       break;
579     default:
580       music_manager->halt_music();
581       break;
582   }
583 }
584
585 int
586 World::get_music_type()
587 {
588   return currentmusic;
589 }
590
591 /* Break a brick: */
592 void
593 World::trybreakbrick(float x, float y, bool small)
594 {
595   Level* plevel = get_level();
596   
597   Tile* tile = gettile(x, y);
598   if (tile->brick)
599     {
600       if (tile->data > 0)
601         {
602           /* Get a distro from it: */
603           add_bouncy_distro(((int)(x + 1) / 32) * 32,
604                                   (int)(y / 32) * 32);
605
606           // TODO: don't handle this in a global way but per-tile...
607           if (!counting_distros)
608             {
609               counting_distros = true;
610               distro_counter = 5;
611             }
612           else
613             {
614               distro_counter--;
615             }
616
617           if (distro_counter <= 0)
618             {
619               counting_distros = false;
620               plevel->change(x, y, TM_IA, tile->next_tile);
621             }
622
623           play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
624           player_status.score = player_status.score + SCORE_DISTRO;
625           player_status.distros++;
626         }
627       else if (!small)
628         {
629           /* Get rid of it: */
630           plevel->change(x, y, TM_IA, tile->next_tile);
631           
632           /* Replace it with broken bits: */
633           add_broken_brick(tile, 
634                                  ((int)(x + 1) / 32) * 32,
635                                  (int)(y / 32) * 32);
636           
637           /* Get some score: */
638           play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
639           player_status.score = player_status.score + SCORE_BRICK;
640         }
641     }
642 }
643
644 /* Empty a box: */
645 void
646 World::tryemptybox(float x, float y, Direction col_side)
647 {
648   Tile* tile = gettile(x,y);
649   if (!tile->fullbox)
650     return;
651
652   // according to the collision side, set the upgrade direction
653   if(col_side == LEFT)
654     col_side = RIGHT;
655   else
656     col_side = LEFT;
657
658   int posx = ((int)(x+1) / 32) * 32;
659   int posy = (int)(y/32) * 32 - 32;
660   switch(tile->data)
661     {
662     case 1: // Box with a distro!
663       add_bouncy_distro(posx, posy);
664       play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
665       player_status.score = player_status.score + SCORE_DISTRO;
666       player_status.distros++;
667       break;
668
669     case 2: // Add a fire flower upgrade!
670       if (tux.size == SMALL)     /* Tux is small, add mints! */
671         add_upgrade(posx, posy, col_side, UPGRADE_GROWUP);
672       else     /* Tux is big, add a fireflower: */
673         add_upgrade(posx, posy, col_side, UPGRADE_FIREFLOWER);
674       play_sound(sounds[SND_UPGRADE], SOUND_CENTER_SPEAKER);
675       break;
676     
677     case 5: // Add an ice flower upgrade!
678       if (tux.size == SMALL)     /* Tux is small, add mints! */
679         add_upgrade(posx, posy, col_side, UPGRADE_GROWUP);
680       else     /* Tux is big, add an iceflower: */
681         add_upgrade(posx, posy, col_side, UPGRADE_ICEFLOWER);
682       play_sound(sounds[SND_UPGRADE], SOUND_CENTER_SPEAKER);
683       break;
684
685     case 3: // Add a golden herring
686       add_upgrade(posx, posy, col_side, UPGRADE_HERRING);
687       break;
688
689     case 4: // Add a 1up extra
690       add_upgrade(posx, posy, col_side, UPGRADE_1UP);
691       break;
692     default:
693       break;
694     }
695
696   /* Empty the box: */
697   level->change(x, y, TM_IA, tile->next_tile);
698 }
699
700 /* Try to grab a distro: */
701 void
702 World::trygrabdistro(float x, float y, int bounciness)
703 {
704   Tile* tile = gettile(x, y);
705   if (tile && tile->distro)
706     {
707       level->change(x, y, TM_IA, tile->next_tile);
708       play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
709
710       if (bounciness == BOUNCE)
711         {
712           add_bouncy_distro(((int)(x + 1) / 32) * 32,
713                                   (int)(y / 32) * 32);
714         }
715
716       player_status.score = player_status.score + SCORE_DISTRO;
717       player_status.distros++;
718     }
719 }
720
721 /* Try to bump a bad guy from below: */
722 void
723 World::trybumpbadguy(float x, float y)
724 {
725   // Bad guys: 
726   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
727     {
728       if ((*i)->base.x >= x - 32 && (*i)->base.x <= x + 32 &&
729           (*i)->base.y >= y - 16 && (*i)->base.y <= y + 16)
730         {
731           (*i)->collision(&tux, CO_PLAYER, COLLISION_BUMP);
732         }
733     }
734
735   // Upgrades:
736   for (unsigned int i = 0; i < upgrades.size(); i++)
737     {
738       if (upgrades[i].base.height == 32 &&
739           upgrades[i].base.x >= x - 32 && upgrades[i].base.x <= x + 32 &&
740           upgrades[i].base.y >= y - 16 && upgrades[i].base.y <= y + 16)
741         {
742           upgrades[i].collision(&tux, CO_PLAYER, COLLISION_BUMP);
743         }
744     }
745 }
746
747 /* EOF */
748