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