9c83a7683352bad9dc9e39d9e4094ee9f77c2b90
[supertux.git] / src / sector.cpp
1 //  $Id$
2 //
3 //  SuperTux -  A Jump'n Run
4 //  Copyright (C) 2004 Matthias Braun <matze@braunis.de
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 #include <config.h>
20
21 #include <memory>
22 #include <algorithm>
23 #include <stdexcept>
24 #include <iostream>
25 #include <fstream>
26 #include <sstream>
27 #include <stdexcept>
28 #include <float.h>
29
30 #include "sector.hpp"
31 #include "player_status.hpp"
32 #include "object/gameobjs.hpp"
33 #include "object/camera.hpp"
34 #include "object/background.hpp"
35 #include "object/particlesystem.hpp"
36 #include "object/particlesystem_interactive.hpp"
37 #include "object/tilemap.hpp"
38 #include "lisp/parser.hpp"
39 #include "lisp/lisp.hpp"
40 #include "lisp/writer.hpp"
41 #include "lisp/list_iterator.hpp"
42 #include "tile.hpp"
43 #include "audio/sound_manager.hpp"
44 #include "game_session.hpp"
45 #include "resources.hpp"
46 #include "statistics.hpp"
47 #include "collision_grid.hpp"
48 #include "collision_grid_iterator.hpp"
49 #include "object_factory.hpp"
50 #include "collision.hpp"
51 #include "spawn_point.hpp"
52 #include "math/rect.hpp"
53 #include "math/aatriangle.hpp"
54 #include "object/coin.hpp"
55 #include "object/block.hpp"
56 #include "object/invisible_block.hpp"
57 #include "object/bullet.hpp"
58 #include "object/text_object.hpp"
59 #include "badguy/jumpy.hpp"
60 #include "trigger/sequence_trigger.hpp"
61 #include "player_status.hpp"
62 #include "scripting/script_interpreter.hpp"
63 #include "scripting/sound.hpp"
64 #include "scripting/scripted_object.hpp"
65 #include "scripting/text.hpp"
66
67 Sector* Sector::_current = 0;
68
69 Sector::Sector()
70   : gravity(10), player(0), solids(0), camera(0),
71     currentmusic(LEVEL_MUSIC)
72 {
73   player = new Player(player_status);
74   add_object(player);
75
76 #ifdef USE_GRID
77   grid = new CollisionGrid(32000, 32000);
78 #else
79   grid = 0;
80 #endif
81 }
82
83 Sector::~Sector()
84 {
85   update_game_objects();
86   assert(gameobjects_new.size() == 0);
87
88   delete grid;
89
90   for(GameObjects::iterator i = gameobjects.begin(); i != gameobjects.end();
91       ++i) {
92     delete *i;
93   }
94
95   for(SpawnPoints::iterator i = spawnpoints.begin(); i != spawnpoints.end();
96       ++i)
97     delete *i;
98     
99   if(_current == this)
100     _current = 0;
101 }
102
103 GameObject*
104 Sector::parse_object(const std::string& name, const lisp::Lisp& reader)
105 {
106   if(name == "camera") {
107     Camera* camera = new Camera(this);
108     camera->parse(reader);
109     return camera;
110   } else if(name == "particles-snow") {
111     SnowParticleSystem* partsys = new SnowParticleSystem();
112     partsys->parse(reader);
113     return partsys;
114   } else if(name == "particles-rain") {
115     RainParticleSystem* partsys = new RainParticleSystem();
116     partsys->parse(reader);
117     return partsys;
118   } else if(name == "particles-comets") {
119     CometParticleSystem* partsys = new CometParticleSystem();
120     partsys->parse(reader);
121     return partsys;
122   } else if(name == "particles-ghosts") {
123     GhostParticleSystem* partsys = new GhostParticleSystem();
124     partsys->parse(reader);
125     return partsys;
126   } else if(name == "particles-clouds") {
127     CloudParticleSystem* partsys = new CloudParticleSystem();
128     partsys->parse(reader);
129     return partsys;
130   } else if(name == "money") { // for compatibility with old maps
131     return new Jumpy(reader);
132   } 
133
134   try {
135     return create_object(name, reader);
136   } catch(std::exception& e) {
137     std::cerr << e.what() << "\n";
138   }
139   
140   return 0;
141 }
142
143 void
144 Sector::parse(const lisp::Lisp& sector)
145 {
146   _current = this;
147   
148   lisp::ListIterator iter(&sector);
149   while(iter.next()) {
150     const std::string& token = iter.item();
151     if(token == "name") {
152       iter.value()->get(name);
153     } else if(token == "gravity") {
154       iter.value()->get(gravity);
155     } else if(token == "music") {
156       iter.value()->get(music);
157     } else if(token == "spawnpoint") {
158       SpawnPoint* sp = new SpawnPoint(iter.lisp());
159       spawnpoints.push_back(sp);
160     } else if(token == "init-script") {
161       iter.value()->get(init_script);
162     } else {
163       GameObject* object = parse_object(token, *(iter.lisp()));
164       if(object) {
165         add_object(object);
166       }
167     }
168   }
169
170   update_game_objects();
171
172   if(!solids)
173     throw std::runtime_error("sector does not contain a solid tile layer.");
174
175   fix_old_tiles();
176   if(!camera) {
177     std::cerr << "sector '" << name << "' does not contain a camera.\n";
178     update_game_objects();
179     add_object(new Camera(this));
180   }
181
182   update_game_objects();
183 }
184
185 void
186 Sector::parse_old_format(const lisp::Lisp& reader)
187 {
188   _current = this;
189   
190   name = "main";
191   reader.get("gravity", gravity);
192
193   std::string backgroundimage;
194   reader.get("background", backgroundimage);
195   float bgspeed = .5;
196   reader.get("bkgd_speed", bgspeed);
197   bgspeed /= 100;
198
199   Color bkgd_top, bkgd_bottom;
200   int r = 0, g = 0, b = 128;
201   reader.get("bkgd_red_top", r);
202   reader.get("bkgd_green_top",  g);
203   reader.get("bkgd_blue_top",  b);
204   bkgd_top.red = static_cast<float> (r) / 255.0f;
205   bkgd_top.green = static_cast<float> (g) / 255.0f;
206   bkgd_top.blue = static_cast<float> (b) / 255.0f;
207   
208   reader.get("bkgd_red_bottom",  r);
209   reader.get("bkgd_green_bottom", g);
210   reader.get("bkgd_blue_bottom", b);
211   bkgd_bottom.red = static_cast<float> (r) / 255.0f;
212   bkgd_bottom.green = static_cast<float> (g) / 255.0f;
213   bkgd_bottom.blue = static_cast<float> (b) / 255.0f;
214   
215   if(backgroundimage != "") {
216     Background* background = new Background;
217     background->set_image(
218             std::string("images/background/") + backgroundimage, bgspeed);
219     add_object(background);
220   } else {
221     Background* background = new Background;
222     background->set_gradient(bkgd_top, bkgd_bottom);
223     add_object(background);
224   }
225
226   std::string particlesystem;
227   reader.get("particle_system", particlesystem);
228   if(particlesystem == "clouds")
229     add_object(new CloudParticleSystem());
230   else if(particlesystem == "snow")
231     add_object(new SnowParticleSystem());
232   else if(particlesystem == "rain")
233     add_object(new RainParticleSystem());
234
235   Vector startpos(100, 170);
236   reader.get("start_pos_x", startpos.x);
237   reader.get("start_pos_y", startpos.y);
238
239   SpawnPoint* spawn = new SpawnPoint;
240   spawn->pos = startpos;
241   spawn->name = "main";
242   spawnpoints.push_back(spawn);
243
244   music = "chipdisko.ogg";
245   reader.get("music", music);
246   music = "music/" + music;
247
248   int width = 30, height = 15;
249   reader.get("width", width);
250   reader.get("height", height);
251   
252   std::vector<unsigned int> tiles;
253   if(reader.get_vector("interactive-tm", tiles)
254       || reader.get_vector("tilemap", tiles)) {
255     TileMap* tilemap = new TileMap();
256     tilemap->set(width, height, tiles, LAYER_TILES, true);
257     add_object(tilemap);
258   }
259
260   if(reader.get_vector("background-tm", tiles)) {
261     TileMap* tilemap = new TileMap();
262     tilemap->set(width, height, tiles, LAYER_BACKGROUNDTILES, false);
263     add_object(tilemap);
264   }
265
266   if(reader.get_vector("foreground-tm", tiles)) {
267     TileMap* tilemap = new TileMap();
268     tilemap->set(width, height, tiles, LAYER_FOREGROUNDTILES, false);
269     add_object(tilemap);
270   }
271
272   // read reset-points (now spawn-points)
273   const lisp::Lisp* resetpoints = reader.get_lisp("reset-points");
274   if(resetpoints) {
275     lisp::ListIterator iter(resetpoints);
276     while(iter.next()) {
277       if(iter.item() == "point") {
278         Vector sp_pos;
279         if(reader.get("x", sp_pos.x) && reader.get("y", sp_pos.y))
280           {
281           SpawnPoint* sp = new SpawnPoint;
282           sp->name = "main";
283           sp->pos = sp_pos;
284           spawnpoints.push_back(sp);
285           }
286       } else {
287         std::cerr << "Unknown token '" << iter.item() << "' in reset-points.\n";
288       }
289     }
290   }
291
292   // read objects
293   const lisp::Lisp* objects = reader.get_lisp("objects");
294   if(objects) {
295     lisp::ListIterator iter(objects);
296     while(iter.next()) {
297       GameObject* object = parse_object(iter.item(), *(iter.lisp()));
298       if(object) {
299         add_object(object);
300       } else {
301         std::cerr << "Unknown object '" << iter.item() << "' in level.\n";
302       }
303     }
304   }
305
306   // add a camera
307   Camera* camera = new Camera(this);
308   add_object(camera);
309
310   update_game_objects();
311
312   if(solids == 0)
313     throw std::runtime_error("sector does not contain a solid tile layer.");
314
315   fix_old_tiles();
316   update_game_objects();
317 }
318
319 void
320 Sector::fix_old_tiles()
321 {
322   // hack for now...
323   for(size_t x=0; x < solids->get_width(); ++x) {
324     for(size_t y=0; y < solids->get_height(); ++y) {
325       const Tile* tile = solids->get_tile(x, y);
326       Vector pos(x*32, y*32);
327       
328       if(tile->getID() == 112) {
329         add_object(new InvisibleBlock(pos));
330         solids->change(x, y, 0);
331       } else if(tile->getAttributes() & Tile::COIN) {
332         add_object(new Coin(pos));
333         solids->change(x, y, 0);
334       } else if(tile->getAttributes() & Tile::FULLBOX) {
335         add_object(new BonusBlock(pos, tile->getData()));
336         solids->change(x, y, 0);
337       } else if(tile->getAttributes() & Tile::BRICK) {
338         add_object(new Brick(pos, tile->getData()));
339         solids->change(x, y, 0);
340       } else if(tile->getAttributes() & Tile::GOAL) {
341         std::string sequence = tile->getData() == 0 ? "endsequence" : "stoptux";
342         add_object(new SequenceTrigger(pos, sequence));
343         solids->change(x, y, 0);
344       }
345     }
346   }
347 }
348
349 void
350 Sector::write(lisp::Writer& writer)
351 {
352   writer.write_string("name", name);
353   writer.write_float("gravity", gravity);
354   writer.write_string("music", music);
355
356   // write spawnpoints
357   for(SpawnPoints::iterator i = spawnpoints.begin(); i != spawnpoints.end();
358       ++i) {
359     SpawnPoint* spawn = *i;
360     writer.start_list("spawn-points");
361     writer.write_string("name", spawn->name);
362     writer.write_float("x", spawn->pos.x);
363     writer.write_float("y", spawn->pos.y);
364     writer.end_list("spawn-points");
365   }
366
367   // write objects
368   for(GameObjects::iterator i = gameobjects.begin();
369       i != gameobjects.end(); ++i) {
370     Serializable* serializable = dynamic_cast<Serializable*> (*i);
371     if(serializable)
372       serializable->write(writer);
373   }
374 }
375
376 void
377 Sector::add_object(GameObject* object)
378 {
379   // make sure the object isn't already in the list
380 #ifdef DEBUG
381   for(GameObjects::iterator i = gameobjects.begin(); i != gameobjects.end();
382       ++i) {
383     if(*i == object) {
384       assert("object already added to sector" == 0);
385     }
386   }
387   for(GameObjects::iterator i = gameobjects_new.begin();
388       i != gameobjects_new.end(); ++i) {
389     if(*i == object) {
390       assert("object already added to sector" == 0);
391     }
392   }
393 #endif
394
395   gameobjects_new.push_back(object);
396 }
397
398 void
399 Sector::activate(const std::string& spawnpoint)
400 {
401   SpawnPoint* sp = 0;
402   for(SpawnPoints::iterator i = spawnpoints.begin(); i != spawnpoints.end();
403       ++i) {
404     if((*i)->name == spawnpoint) {
405       sp = *i;
406       break;
407     }
408   }                                                                           
409   if(!sp) {
410     std::cerr << "Spawnpoint '" << spawnpoint << "' not found.\n";
411     if(spawnpoint != "main") {
412       activate("main");
413     } else {
414       activate(Vector(0, 0));
415     }
416   } else {
417     activate(sp->pos);
418   }
419
420   // Run init script
421   if(init_script != "") {
422     ScriptInterpreter::add_script_object(this,
423         std::string("Sector(") + name + ") - init", init_script);
424   }
425 }
426
427 void
428 Sector::activate(const Vector& player_pos)
429 {
430   _current = this;
431
432   player->move(player_pos);
433   camera->reset(player->get_pos());
434 }
435
436 Rect
437 Sector::get_active_region()
438 {
439   return Rect(
440     camera->get_translation() - Vector(1600, 1200),
441     camera->get_translation() + Vector(1600, 1200));
442 }
443
444 void
445 Sector::update(float elapsed_time)
446 {
447   player->check_bounds(camera);
448
449 #if 0
450   CollisionGridIterator iter(*grid, get_active_region());
451   while(MovingObject* object = iter.next()) {
452     if(!object->is_valid())
453       continue;
454
455     object->update(elapsed_time);
456   }
457 #else
458   /* update objects */
459   for(GameObjects::iterator i = gameobjects.begin();
460           i != gameobjects.end(); ++i) {
461     GameObject* object = *i;
462     if(!object->is_valid())
463       continue;
464     
465     object->update(elapsed_time);
466   }
467 #endif
468   
469   /* Handle all possible collisions. */
470   handle_collisions();
471   update_game_objects();
472 }
473
474 void
475 Sector::update_game_objects()
476 {
477   /** cleanup marked objects */
478   for(std::vector<Bullet*>::iterator i = bullets.begin();
479       i != bullets.end(); /* nothing */) {
480     Bullet* bullet = *i;
481     if(bullet->is_valid()) {
482       ++i;
483       continue;
484     }
485
486     i = bullets.erase(i);
487   }
488   for(MovingObjects::iterator i = moving_objects.begin();
489       i != moving_objects.end(); /* nothing */) {
490     MovingObject* moving_object = *i;
491     if(moving_object->is_valid()) {
492       ++i;
493       continue;
494     }
495
496 #ifdef USE_GRID
497     grid->remove_object(moving_object);
498 #endif
499     
500     i = moving_objects.erase(i);
501   }
502   for(std::vector<GameObject*>::iterator i = gameobjects.begin();
503       i != gameobjects.end(); /* nothing */) {
504     GameObject* object = *i;
505     
506     if(object->is_valid()) {
507       ++i;
508       continue;
509     }
510     
511     delete *i;
512     i = gameobjects.erase(i);
513   }
514
515   /* add newly created objects */
516   for(std::vector<GameObject*>::iterator i = gameobjects_new.begin();
517       i != gameobjects_new.end(); ++i)
518   {
519     GameObject* object = *i;
520     
521     Bullet* bullet = dynamic_cast<Bullet*> (object);
522     if(bullet)
523       bullets.push_back(bullet);
524
525     MovingObject* movingobject = dynamic_cast<MovingObject*> (object);
526     if(movingobject) {
527       moving_objects.push_back(movingobject);
528  #ifdef USE_GRID
529       grid->add_object(movingobject);
530 #endif
531     }
532     
533     TileMap* tilemap = dynamic_cast<TileMap*> (object);
534     if(tilemap && tilemap->is_solid()) {
535       if(solids == 0) {
536         solids = tilemap;
537       } else {
538         std::cerr << "Another solid tilemaps added. Ignoring.";
539       }
540     }
541
542     Camera* camera = dynamic_cast<Camera*> (object);
543     if(camera) {
544       if(this->camera != 0) {
545         std::cerr << "Warning: Multiple cameras added. Ignoring.";
546         continue;
547       }
548       this->camera = camera;
549     }
550
551     gameobjects.push_back(object);
552   }
553   gameobjects_new.clear();
554 }
555
556 void
557 Sector::draw(DrawingContext& context)
558 {
559   context.push_transform();
560   context.set_translation(camera->get_translation());
561
562   for(GameObjects::iterator i = gameobjects.begin();
563       i != gameobjects.end(); ++i) {
564     GameObject* object = *i; 
565     if(!object->is_valid())
566       continue;
567     
568     object->draw(context);
569   }
570
571   context.pop_transform();
572 }
573
574 static const float DELTA = .001;
575
576 void
577 Sector::collision_tilemap(MovingObject* object, CollisionHit& hit) const
578 {
579   // calculate rectangle where the object will move
580   float x1, x2;
581   if(object->get_movement().x >= 0) {
582     x1 = object->get_bbox().p1.x;
583     x2 = object->get_bbox().p2.x + object->get_movement().x;
584   } else {
585     x1 = object->get_bbox().p1.x + object->get_movement().x;
586     x2 = object->get_bbox().p2.x;
587   }
588   float y1, y2;
589   if(object->get_movement().y >= 0) {
590     y1 = object->get_bbox().p1.y;
591     y2 = object->get_bbox().p2.y + object->get_movement().y;
592   } else {
593     y1 = object->get_bbox().p1.y + object->get_movement().y;
594     y2 = object->get_bbox().p2.y;
595   }
596
597   // test with all tiles in this rectangle
598   int starttilex = int(x1) / 32;
599   int starttiley = int(y1) / 32;
600   int max_x = int(x2);
601   // the +1 is somehow needed to make characters stay on the floor
602   int max_y = int(y2+1);
603
604   CollisionHit temphit;
605   Rect dest = object->get_bbox();
606   dest.move(object->movement);
607   for(int x = starttilex; x*32 < max_x; ++x) {
608     for(int y = starttiley; y*32 < max_y; ++y) {
609       const Tile* tile = solids->get_tile(x, y);
610       if(!tile)
611         continue;
612       // skip non-solid tiles
613       if(tile->getAttributes() == 0)
614         continue;
615       // only handle unisolid when the player is falling down and when he was
616       // above the tile before
617       if(tile->getAttributes() & Tile::UNISOLID) {
618         if(object->movement.y < 0 || object->get_bbox().p2.y > y*32)
619           continue;
620       }
621
622       if(tile->getAttributes() & Tile::SLOPE) { // slope tile
623         AATriangle triangle;
624         Vector p1(x*32, y*32);
625         Vector p2((x+1)*32, (y+1)*32);
626         triangle = AATriangle(p1, p2, tile->getData());
627
628         if(Collision::rectangle_aatriangle(temphit, dest, object->movement,
629               triangle)) {
630           if(temphit.time > hit.time && (tile->getAttributes() & Tile::SOLID)) {
631             hit = temphit;
632           }
633         }
634       } else { // normal rectangular tile
635         Rect rect(x*32, y*32, (x+1)*32, (y+1)*32);
636         if(Collision::rectangle_rectangle(temphit, dest,
637               object->movement, rect)) {
638           if(temphit.time > hit.time && (tile->getAttributes() & Tile::SOLID)) {
639             hit = temphit;
640           }
641         }
642       }
643     }
644   }
645 }
646
647 uint32_t
648 Sector::collision_tile_attributes(MovingObject* object) const
649 {
650   /** XXX This function doesn't work correctly as it will check all tiles
651    * in the bounding box of the object movement, this might include tiles
652    * that have actually never been touched by the object
653    * (though this only occures for very fast objects...)
654    */
655   
656   // calculate rectangle where the object will move
657   float x1, x2;
658   if(object->get_movement().x >= 0) {
659     x1 = object->get_bbox().p1.x;
660     x2 = object->get_bbox().p2.x + object->get_movement().x;
661   } else {
662     x1 = object->get_bbox().p1.x + object->get_movement().x;
663     x2 = object->get_bbox().p2.x;
664   }
665   float y1, y2;
666   if(object->get_movement().y >= 0) {
667     y1 = object->get_bbox().p1.y;
668     y2 = object->get_bbox().p2.y + object->get_movement().y;
669   } else {
670     y1 = object->get_bbox().p1.y + object->get_movement().y;
671     y2 = object->get_bbox().p2.y;
672   }
673
674   // test with all tiles in this rectangle
675   int starttilex = int(x1-1) / 32;
676   int starttiley = int(y1-1) / 32;
677   int max_x = int(x2+1);
678   int max_y = int(y2+1);
679
680   uint32_t result = 0;
681   for(int x = starttilex; x*32 < max_x; ++x) {
682     for(int y = starttiley; y*32 < max_y; ++y) {
683       const Tile* tile = solids->get_tile(x, y);
684       if(!tile)
685         continue;
686       result |= tile->getAttributes();
687     }
688   }
689
690   return result;
691 }
692
693 void
694 Sector::collision_object(MovingObject* object1, MovingObject* object2) const
695 {
696   CollisionHit hit;
697   Rect dest1 = object1->get_bbox();
698   dest1.move(object1->get_movement());
699   Rect dest2 = object2->get_bbox();
700   dest2.move(object2->get_movement());
701
702   Vector movement = object1->get_movement() - object2->get_movement();
703   if(Collision::rectangle_rectangle(hit, dest1, movement, dest2)) {
704     HitResponse response1 = object1->collision(*object2, hit);
705     hit.normal *= -1;
706     HitResponse response2 = object2->collision(*object1, hit);
707
708     if(response1 != CONTINUE) {
709       if(response1 == ABORT_MOVE)
710         object1->movement = Vector(0, 0);
711       if(response2 == CONTINUE)
712         object2->movement += hit.normal * (hit.depth + DELTA);
713     } else if(response2 != CONTINUE) {
714       if(response2 == ABORT_MOVE)
715         object2->movement = Vector(0, 0);
716       if(response1 == CONTINUE)
717         object1->movement += -hit.normal * (hit.depth + DELTA);
718     } else {
719       object1->movement += -hit.normal * (hit.depth/2 + DELTA);
720       object2->movement += hit.normal * (hit.depth/2 + DELTA);
721     }
722   }
723 }
724
725 void
726 Sector::handle_collisions()
727 {
728   // part1: COLGROUP_MOVING vs COLGROUP_STATIC and tilemap
729   //   we do this up to 4 times and have to sort all results for the smallest
730   //   one before we can continue here
731   for(MovingObjects::iterator i = moving_objects.begin();
732       i != moving_objects.end(); ++i) {
733     MovingObject* moving_object = *i;
734     if((moving_object->get_group() != COLGROUP_MOVING
735           && moving_object->get_group() != COLGROUP_MOVING_ONLY_STATIC)
736         || !moving_object->is_valid())
737       continue;
738
739     // up to 4 tries
740     for(int t = 0; t < 4; ++t) {
741       CollisionHit hit;
742       hit.time = -1;
743       MovingObject* collided_with = NULL; 
744       
745       // collision with tilemap
746       collision_tilemap(moving_object, hit);
747     
748       // collision with other objects
749       Rect dest1 = moving_object->get_bbox();
750       dest1.move(moving_object->get_movement());
751       CollisionHit temphit;
752       
753       for(MovingObjects::iterator i2 = moving_objects.begin();
754           i2 != moving_objects.end(); ++i2) {
755         MovingObject* moving_object_2 = *i2;
756         if(moving_object_2->get_group() != COLGROUP_STATIC
757            || !moving_object_2->is_valid())
758           continue;
759         
760         Rect dest2 = moving_object_2->get_bbox();
761         dest2.move(moving_object_2->get_movement());
762         Vector movement 
763           = moving_object->get_movement() - moving_object_2->get_movement();
764         if(Collision::rectangle_rectangle(temphit, dest1, movement, dest2)
765             && temphit.time > hit.time) {
766           hit = temphit;
767           collided_with = moving_object_2;
768         }
769       }
770
771       if(hit.time < 0)
772         break;
773
774       // call collision callbacks
775       HitResponse response;
776       if(collided_with != 0) {
777         response = moving_object->collision(*collided_with, hit);
778         hit.normal *= -1;
779         collided_with->collision(*moving_object, hit);
780       } else {
781         response = moving_object->collision(*solids, hit);
782         hit.normal *= -1;
783       }
784    
785       if(response == CONTINUE) {
786         moving_object->movement += -hit.normal * (hit.depth + DELTA);
787       } else if(response == ABORT_MOVE) {
788         moving_object->movement = Vector(0, 0);
789         break;
790       } else { // force move
791         break;
792       }
793     }
794   }
795
796   // part2: COLGROUP_MOVING vs tile attributes
797   for(MovingObjects::iterator i = moving_objects.begin();
798       i != moving_objects.end(); ++i) {
799     MovingObject* moving_object = *i;
800     if((moving_object->get_group() != COLGROUP_MOVING
801           && moving_object->get_group() != COLGROUP_MOVING_ONLY_STATIC)
802         || !moving_object->is_valid())
803       continue;
804
805     uint32_t tile_attributes = collision_tile_attributes(moving_object);
806     if(tile_attributes > Tile::FIRST_INTERESTING_FLAG) {
807       moving_object->collision_tile(tile_attributes);
808     }
809   }
810
811   // part2.5: COLGROUP_MOVING vs COLGROUP_TOUCHABLE
812   for(MovingObjects::iterator i = moving_objects.begin();
813       i != moving_objects.end(); ++i) {
814     MovingObject* moving_object = *i;
815     if(moving_object->get_group() != COLGROUP_MOVING
816         || !moving_object->is_valid())
817       continue;
818
819     for(MovingObjects::iterator i2 = moving_objects.begin();
820         i2 != moving_objects.end(); ++i2) {
821       MovingObject* moving_object_2 = *i2;
822       if(moving_object_2->get_group() != COLGROUP_TOUCHABLE
823          || !moving_object_2->is_valid())
824         continue;
825
826       collision_object(moving_object, moving_object_2);
827     } 
828   }
829
830   // part3: COLGROUP_MOVING vs COLGROUP_MOVING
831   for(MovingObjects::iterator i = moving_objects.begin();
832       i != moving_objects.end(); ++i) {
833     MovingObject* moving_object = *i;
834
835     if(moving_object->get_group() != COLGROUP_MOVING
836         || !moving_object->is_valid())
837       continue;
838
839     for(MovingObjects::iterator i2 = i+1;
840         i2 != moving_objects.end(); ++i2) {
841       MovingObject* moving_object_2 = *i2;
842       if(moving_object_2->get_group() != COLGROUP_MOVING
843          || !moving_object_2->is_valid())
844         continue;
845
846       collision_object(moving_object, moving_object_2);
847     }    
848   }
849
850   // apply object movement
851   for(MovingObjects::iterator i = moving_objects.begin();
852       i != moving_objects.end(); ++i) {
853     MovingObject* moving_object = *i;
854
855     moving_object->bbox.move(moving_object->get_movement());
856     moving_object->movement = Vector(0, 0);
857   }
858 }
859
860 bool
861 Sector::is_free_space(const Rect& rect) const
862 {
863   // test with all tiles in this rectangle
864   int starttilex = int(rect.p1.x) / 32;
865   int starttiley = int(rect.p1.y) / 32;
866   int max_x = int(rect.p2.x);
867   int max_y = int(rect.p2.y);
868
869   for(int x = starttilex; x*32 < max_x; ++x) {
870     for(int y = starttiley; y*32 < max_y; ++y) {
871       const Tile* tile = solids->get_tile(x, y);
872       if(!tile)
873         continue;
874       if(tile->getAttributes() & Tile::SOLID)
875         return false;
876     }
877   }
878
879   for(MovingObjects::const_iterator i = moving_objects.begin();
880       i != moving_objects.end(); ++i) {
881     const MovingObject* moving_object = *i;
882     if(moving_object->get_group() != COLGROUP_STATIC
883         || !moving_object->is_valid())
884       continue;
885
886     if(Collision::intersects(rect, moving_object->get_bbox()))
887       return false;
888   }
889
890   return true;
891 }
892
893 bool
894 Sector::add_bullet(const Vector& pos, float xm, Direction dir)
895 {
896   // TODO remove this function and move these checks elsewhere...
897   static const size_t MAX_FIRE_BULLETS = 2;
898   static const size_t MAX_ICE_BULLETS = 1;
899
900   Bullet* new_bullet = 0;
901   if(player_status->bonus == FIRE_BONUS) {
902     if(bullets.size() > MAX_FIRE_BULLETS-1)
903       return false;
904     new_bullet = new Bullet(pos, xm, dir, FIRE_BULLET);
905   } else if(player_status->bonus == ICE_BONUS) {
906     if(bullets.size() > MAX_ICE_BULLETS-1)
907       return false;
908     new_bullet = new Bullet(pos, xm, dir, ICE_BULLET);
909   } else {
910     return false;
911   }
912   add_object(new_bullet);
913
914   sound_manager->play("sounds/shoot.wav");
915
916   return true;
917 }
918
919 bool
920 Sector::add_smoke_cloud(const Vector& pos)
921 {
922   add_object(new SmokeCloud(pos));
923   return true;
924 }
925
926 void
927 Sector::add_floating_text(const Vector& pos, const std::string& text)
928 {
929   add_object(new FloatingText(pos, text));
930 }
931
932 void
933 Sector::play_music(MusicType type)
934 {
935   currentmusic = type;
936   switch(currentmusic) {
937     case LEVEL_MUSIC:
938       sound_manager->play_music(music);
939       break;
940     case HERRING_MUSIC:
941       sound_manager->play_music("music/salcon.ogg");
942       break;
943     default:
944       sound_manager->play_music("");
945       break;
946   }
947 }
948
949 MusicType
950 Sector::get_music_type()
951 {
952   return currentmusic;
953 }
954
955 int
956 Sector::get_total_badguys()
957 {
958   int total_badguys = 0;
959   for(GameObjects::iterator i = gameobjects.begin();
960       i != gameobjects.end(); ++i) {
961     BadGuy* badguy = dynamic_cast<BadGuy*> (*i);
962     if (badguy && badguy->countMe)
963       total_badguys++;
964   }
965
966   return total_badguys;
967 }
968
969 bool
970 Sector::inside(const Rect& rect) const
971 {
972   if(rect.p1.x > solids->get_width() * 32 
973       || rect.p1.y > solids->get_height() * 32
974       || rect.p2.x < 0 || rect.p2.y < 0)
975     return false;
976
977   return true;
978 }