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