- more resolution fixes
[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 < 0)
331     scroll_y = 0;
332   else if(scroll_y > level->height * 32 - screen->h)
333     scroll_y = level->height * 32 - screen->h;
334
335   if (scroll_y < 0)
336   {
337     //std::cerr << "Level too short!!" << std::endl;
338     scroll_y = 0;
339   }
340
341   /* X-axis scrolling */
342
343   /* Auto scrolling */
344   if(level->hor_autoscroll_speed)
345   {
346     scroll_x += level->hor_autoscroll_speed * frame_ratio;
347     return;
348   }
349
350
351   /* Horizontal backscrolling */
352   float tux_pos_x = tux.base.x + (tux.base.width/2);
353
354   if(tux.old_dir != tux.dir && level->back_scrolling)
355     scrolling_timer.start(CHANGE_DIR_SCROLL_SPEED);
356
357   bool right = false;
358   bool left = false;
359   if (tux.physic.get_velocity_x() > 0)
360     right = true;
361   else if (tux.physic.get_velocity_x() < 0)
362     left = true;
363   else
364     {
365     if (tux.dir == RIGHT)
366       right = true;
367     else
368       left = true;
369     }
370
371   if(scrolling_timer.check())
372   {
373     float final_scroll_x;
374     if (right)
375       final_scroll_x = tux_pos_x - (screen->w - X_SPACE);
376     else
377       final_scroll_x = tux_pos_x - X_SPACE;
378
379     scroll_x +=   (final_scroll_x - scroll_x)
380                 / (frame_ratio * (CHANGE_DIR_SCROLL_SPEED / 100))
381                 + (tux.physic.get_velocity_x() * frame_ratio + tux.physic.get_acceleration_x() * frame_ratio * frame_ratio);
382   }
383   else
384   {
385     if (right && scroll_x < tux_pos_x - (screen->w - X_SPACE))
386       scroll_x = tux_pos_x - (screen->w - X_SPACE);
387     else if (left && scroll_x > tux_pos_x - X_SPACE && level->back_scrolling)
388       scroll_x = tux_pos_x - X_SPACE;
389   }
390
391   // this code prevent the screen to scroll before the start or after the level's end
392   if(scroll_x < 0)
393     scroll_x = 0;
394   else if(scroll_x > level->width * 32 - screen->w)
395     scroll_x = level->width * 32 - screen->w;
396 }
397
398 void
399 World::collision_handler()
400 {
401   // CO_BULLET & CO_BADGUY check
402   for(unsigned int i = 0; i < bullets.size(); ++i)
403     {
404       for (BadGuys::iterator j = bad_guys.begin(); j != bad_guys.end(); ++j)
405         {
406           if((*j)->dying != DYING_NOT)
407             continue;
408           
409           if(rectcollision(bullets[i].base, (*j)->base))
410             {
411               // We have detected a collision and now call the
412               // collision functions of the collided objects.
413               // collide with bad_guy first, since bullet_collision will
414               // delete the bullet
415               (*j)->collision(&bullets[i], CO_BULLET);
416               bullets[i].collision(CO_BADGUY);
417               break; // bullet is invalid now, so break
418             }
419         }
420     }
421
422   /* CO_BADGUY & CO_BADGUY check */
423   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
424     {
425       if((*i)->dying != DYING_NOT)
426         continue;
427       
428       BadGuys::iterator j = i;
429       ++j;
430       for (; j != bad_guys.end(); ++j)
431         {
432           if(j == i || (*j)->dying != DYING_NOT)
433             continue;
434
435           if(rectcollision((*i)->base, (*j)->base))
436             {
437               // We have detected a collision and now call the
438               // collision functions of the collided objects.
439               (*j)->collision(*i, CO_BADGUY);
440               (*i)->collision(*j, CO_BADGUY);
441             }
442         }
443     }
444
445   if(tux.dying != DYING_NOT) return;
446     
447   // CO_BADGUY & CO_PLAYER check 
448   for (BadGuys::iterator i = bad_guys.begin(); i != bad_guys.end(); ++i)
449     {
450       if((*i)->dying != DYING_NOT)
451         continue;
452       
453       if(rectcollision_offset((*i)->base, tux.base, 0, 0))
454         {
455           // We have detected a collision and now call the collision
456           // functions of the collided objects.
457           if (tux.previous_base.y < tux.base.y &&
458               tux.previous_base.y + tux.previous_base.height 
459               < (*i)->base.y + (*i)->base.height/2
460               && !tux.invincible_timer.started())
461             {
462               (*i)->collision(&tux, CO_PLAYER, COLLISION_SQUISH);
463             }
464           else
465             {
466               tux.collision(*i, CO_BADGUY);
467               (*i)->collision(&tux, CO_PLAYER, COLLISION_NORMAL);
468             }
469         }
470     }
471
472   // CO_UPGRADE & CO_PLAYER check
473   for(unsigned int i = 0; i < upgrades.size(); ++i)
474     {
475       if(rectcollision(upgrades[i].base, tux.base))
476         {
477           // We have detected a collision and now call the collision
478           // functions of the collided objects.
479           upgrades[i].collision(&tux, CO_PLAYER, COLLISION_NORMAL);
480         }
481     }
482 }
483
484 void
485 World::add_score(float x, float y, int s)
486 {
487   player_status.score += s;
488
489   FloatingScore* new_floating_score = new FloatingScore();
490   new_floating_score->init(x-scroll_x, y-scroll_y, s);
491   floating_scores.push_back(new_floating_score);
492 }
493
494 void
495 World::add_bouncy_distro(float x, float y)
496 {
497   BouncyDistro* new_bouncy_distro = new BouncyDistro();
498   new_bouncy_distro->init(x, y);
499   bouncy_distros.push_back(new_bouncy_distro);
500 }
501
502 void
503 World::add_broken_brick(Tile* tile, float x, float y)
504 {
505   add_broken_brick_piece(tile, x, y, -1, -4);
506   add_broken_brick_piece(tile, x, y + 16, -1.5, -3);
507
508   add_broken_brick_piece(tile, x + 16, y, 1, -4);
509   add_broken_brick_piece(tile, x + 16, y + 16, 1.5, -3);
510 }
511
512 void
513 World::add_broken_brick_piece(Tile* tile, float x, float y, float xm, float ym)
514 {
515   BrokenBrick* new_broken_brick = new BrokenBrick();
516   new_broken_brick->init(tile, x, y, xm, ym);
517   broken_bricks.push_back(new_broken_brick);
518 }
519
520 void
521 World::add_bouncy_brick(float x, float y)
522 {
523   BouncyBrick* new_bouncy_brick = new BouncyBrick();
524   new_bouncy_brick->init(x,y);
525   bouncy_bricks.push_back(new_bouncy_brick);
526 }
527
528 BadGuy*
529 World::add_bad_guy(float x, float y, BadGuyKind kind, bool stay_on_platform)
530 {
531   BadGuy* badguy = new BadGuy(x,y,kind, stay_on_platform);
532   bad_guys.push_back(badguy);
533   return badguy;
534 }
535
536 void
537 World::add_upgrade(float x, float y, Direction dir, UpgradeKind kind)
538 {
539   Upgrade new_upgrade;
540   new_upgrade.init(x,y,dir,kind);
541   upgrades.push_back(new_upgrade);
542 }
543
544 void 
545 World::add_bullet(float x, float y, float xm, Direction dir)
546 {
547   if(tux.got_power == tux.FIRE_POWER)
548     {
549     if(bullets.size() > MAX_FIRE_BULLETS-1)
550       return;
551     }
552   else if(tux.got_power == tux.ICE_POWER)
553     {
554     if(bullets.size() > MAX_ICE_BULLETS-1)
555       return;
556     }
557
558   Bullet new_bullet;
559   if(tux.got_power == tux.FIRE_POWER)
560     new_bullet.init(x,y,xm,dir, FIRE_BULLET);
561   else if(tux.got_power == tux.ICE_POWER)
562     new_bullet.init(x,y,xm,dir, ICE_BULLET);
563   bullets.push_back(new_bullet);
564   
565   play_sound(sounds[SND_SHOOT], SOUND_CENTER_SPEAKER);
566 }
567
568 void
569 World::play_music(int musictype)
570 {
571   currentmusic = musictype;
572   switch(currentmusic) {
573     case HURRYUP_MUSIC:
574       music_manager->play_music(get_level()->get_level_music_fast());
575       break;
576     case LEVEL_MUSIC:
577       music_manager->play_music(get_level()->get_level_music());
578       break;
579     case HERRING_MUSIC:
580       music_manager->play_music(herring_song);
581       break;
582     default:
583       music_manager->halt_music();
584       break;
585   }
586 }
587
588 int
589 World::get_music_type()
590 {
591   return currentmusic;
592 }
593
594 /* Break a brick: */
595 void
596 World::trybreakbrick(float x, float y, bool small)
597 {
598   Level* plevel = get_level();
599   
600   Tile* tile = gettile(x, y);
601   if (tile->brick)
602     {
603       if (tile->data > 0)
604         {
605           /* Get a distro from it: */
606           add_bouncy_distro(((int)(x + 1) / 32) * 32,
607                                   (int)(y / 32) * 32);
608
609           // TODO: don't handle this in a global way but per-tile...
610           if (!counting_distros)
611             {
612               counting_distros = true;
613               distro_counter = 5;
614             }
615           else
616             {
617               distro_counter--;
618             }
619
620           if (distro_counter <= 0)
621             {
622               counting_distros = false;
623               plevel->change(x, y, TM_IA, tile->next_tile);
624             }
625
626           play_sound(sounds[SND_DISTRO], SOUND_CENTER_SPEAKER);
627           player_status.score = player_status.score + SCORE_DISTRO;
628           player_status.distros++;
629         }
630       else if (!small)
631         {
632           /* Get rid of it: */
633           plevel->change(x, y, TM_IA, tile->next_tile);
634           
635           /* Replace it with broken bits: */
636           add_broken_brick(tile, 
637                                  ((int)(x + 1) / 32) * 32,
638                                  (int)(y / 32) * 32);
639           
640           /* Get some score: */
641           play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
642           player_status.score = player_status.score + SCORE_BRICK;
643         }
644     }
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(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(posx, posy, col_side, UPGRADE_GROWUP);
675       else     /* Tux is big, add a fireflower: */
676         add_upgrade(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(posx, posy, col_side, UPGRADE_GROWUP);
683       else     /* Tux is big, add an iceflower: */
684         add_upgrade(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(posx, posy, col_side, UPGRADE_HERRING);
690       break;
691
692     case 4: // Add a 1up extra
693       add_upgrade(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(((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