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