first very unfinished and unpolished version of the intro
[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(song_title);
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(backgroundimage, bgspeed);
218     add_object(background);
219   } else {
220     Background* background = new Background;
221     background->set_gradient(bkgd_top, bkgd_bottom);
222     add_object(background);
223   }
224
225   std::string particlesystem;
226   reader.get("particle_system", particlesystem);
227   if(particlesystem == "clouds")
228     add_object(new CloudParticleSystem());
229   else if(particlesystem == "snow")
230     add_object(new SnowParticleSystem());
231   else if(particlesystem == "rain")
232     add_object(new RainParticleSystem());
233
234   Vector startpos(100, 170);
235   reader.get("start_pos_x", startpos.x);
236   reader.get("start_pos_y", startpos.y);
237
238   SpawnPoint* spawn = new SpawnPoint;
239   spawn->pos = startpos;
240   spawn->name = "main";
241   spawnpoints.push_back(spawn);
242
243   song_title = "chipdisko.ogg";
244   reader.get("music", song_title);
245
246   int width = 30, height = 15;
247   reader.get("width", width);
248   reader.get("height", height);
249   
250   std::vector<unsigned int> tiles;
251   if(reader.get_vector("interactive-tm", tiles)
252       || reader.get_vector("tilemap", tiles)) {
253     TileMap* tilemap = new TileMap();
254     tilemap->set(width, height, tiles, LAYER_TILES, true);
255     add_object(tilemap);
256   }
257
258   if(reader.get_vector("background-tm", tiles)) {
259     TileMap* tilemap = new TileMap();
260     tilemap->set(width, height, tiles, LAYER_BACKGROUNDTILES, false);
261     add_object(tilemap);
262   }
263
264   if(reader.get_vector("foreground-tm", tiles)) {
265     TileMap* tilemap = new TileMap();
266     tilemap->set(width, height, tiles, LAYER_FOREGROUNDTILES, false);
267     add_object(tilemap);
268   }
269
270   // read reset-points (now spawn-points)
271   const lisp::Lisp* resetpoints = reader.get_lisp("reset-points");
272   if(resetpoints) {
273     lisp::ListIterator iter(resetpoints);
274     while(iter.next()) {
275       if(iter.item() == "point") {
276         Vector sp_pos;
277         if(reader.get("x", sp_pos.x) && reader.get("y", sp_pos.y))
278           {
279           SpawnPoint* sp = new SpawnPoint;
280           sp->name = "main";
281           sp->pos = sp_pos;
282           spawnpoints.push_back(sp);
283           }
284       } else {
285         std::cerr << "Unknown token '" << iter.item() << "' in reset-points.\n";
286       }
287     }
288   }
289
290   // read objects
291   const lisp::Lisp* objects = reader.get_lisp("objects");
292   if(objects) {
293     lisp::ListIterator iter(objects);
294     while(iter.next()) {
295       GameObject* object = parse_object(iter.item(), *(iter.lisp()));
296       if(object) {
297         add_object(object);
298       } else {
299         std::cerr << "Unknown object '" << iter.item() << "' in level.\n";
300       }
301     }
302   }
303
304   // add a camera
305   Camera* camera = new Camera(this);
306   add_object(camera);
307
308   update_game_objects();
309
310   if(solids == 0)
311     throw std::runtime_error("sector does not contain a solid tile layer.");
312
313   fix_old_tiles();
314   update_game_objects();
315 }
316
317 void
318 Sector::fix_old_tiles()
319 {
320   // hack for now...
321   for(size_t x=0; x < solids->get_width(); ++x) {
322     for(size_t y=0; y < solids->get_height(); ++y) {
323       const Tile* tile = solids->get_tile(x, y);
324       Vector pos(x*32, y*32);
325       
326       if(tile->getID() == 112) {
327         add_object(new InvisibleBlock(pos));
328         solids->change(x, y, 0);
329       } else if(tile->getAttributes() & Tile::COIN) {
330         add_object(new Coin(pos));
331         solids->change(x, y, 0);
332       } else if(tile->getAttributes() & Tile::FULLBOX) {
333         add_object(new BonusBlock(pos, tile->getData()));
334         solids->change(x, y, 0);
335       } else if(tile->getAttributes() & Tile::BRICK) {
336         add_object(new Brick(pos, tile->getData()));
337         solids->change(x, y, 0);
338       } else if(tile->getAttributes() & Tile::GOAL) {
339         std::string sequence = tile->getData() == 0 ? "endsequence" : "stoptux";
340         add_object(new SequenceTrigger(pos, sequence));
341         solids->change(x, y, 0);
342       }
343     }
344   }
345 }
346
347 void
348 Sector::write(lisp::Writer& writer)
349 {
350   writer.write_string("name", name);
351   writer.write_float("gravity", gravity);
352   writer.write_string("music", song_title);
353
354   // write spawnpoints
355   for(SpawnPoints::iterator i = spawnpoints.begin(); i != spawnpoints.end();
356       ++i) {
357     SpawnPoint* spawn = *i;
358     writer.start_list("spawn-points");
359     writer.write_string("name", spawn->name);
360     writer.write_float("x", spawn->pos.x);
361     writer.write_float("y", spawn->pos.y);
362     writer.end_list("spawn-points");
363   }
364
365   // write objects
366   for(GameObjects::iterator i = gameobjects.begin();
367       i != gameobjects.end(); ++i) {
368     Serializable* serializable = dynamic_cast<Serializable*> (*i);
369     if(serializable)
370       serializable->write(writer);
371   }
372 }
373
374 void
375 Sector::add_object(GameObject* object)
376 {
377   // make sure the object isn't already in the list
378 #ifdef DEBUG
379   for(GameObjects::iterator i = gameobjects.begin(); i != gameobjects.end();
380       ++i) {
381     if(*i == object) {
382       assert("object already added to sector" == 0);
383     }
384   }
385   for(GameObjects::iterator i = gameobjects_new.begin();
386       i != gameobjects_new.end(); ++i) {
387     if(*i == object) {
388       assert("object already added to sector" == 0);
389     }
390   }
391 #endif
392
393   gameobjects_new.push_back(object);
394 }
395
396 void
397 Sector::activate(const std::string& spawnpoint)
398 {
399   SpawnPoint* sp = 0;
400   for(SpawnPoints::iterator i = spawnpoints.begin(); i != spawnpoints.end();
401       ++i) {
402     if((*i)->name == spawnpoint) {
403       sp = *i;
404       break;
405     }
406   }                                                                           
407   if(!sp) {
408     std::cerr << "Spawnpoint '" << spawnpoint << "' not found.\n";
409     if(spawnpoint != "main") {
410       activate("main");
411     } else {
412       activate(Vector(0, 0));
413     }
414   } else {
415     activate(sp->pos);
416   }
417
418   // Run init script
419   if(init_script != "") {
420     ScriptInterpreter::add_script_object(this,
421         std::string("Sector(") + name + ") - init", init_script);
422   }
423 }
424
425 void
426 Sector::activate(const Vector& player_pos)
427 {
428   _current = this;
429
430   player->move(player_pos);
431   camera->reset(player->get_pos());
432 }
433
434 Rect
435 Sector::get_active_region()
436 {
437   return Rect(
438     camera->get_translation() - Vector(1600, 1200),
439     camera->get_translation() + Vector(1600, 1200));
440 }
441
442 void
443 Sector::update(float elapsed_time)
444 {
445   player->check_bounds(camera);
446
447 #if 0
448   CollisionGridIterator iter(*grid, get_active_region());
449   while(MovingObject* object = iter.next()) {
450     if(!object->is_valid())
451       continue;
452
453     object->update(elapsed_time);
454   }
455 #else
456   /* update objects */
457   for(GameObjects::iterator i = gameobjects.begin();
458           i != gameobjects.end(); ++i) {
459     GameObject* object = *i;
460     if(!object->is_valid())
461       continue;
462     
463     object->update(elapsed_time);
464   }
465 #endif
466   
467   /* Handle all possible collisions. */
468   handle_collisions();
469   update_game_objects();
470 }
471
472 void
473 Sector::update_game_objects()
474 {
475   /** cleanup marked objects */
476   for(std::vector<Bullet*>::iterator i = bullets.begin();
477       i != bullets.end(); /* nothing */) {
478     Bullet* bullet = *i;
479     if(bullet->is_valid()) {
480       ++i;
481       continue;
482     }
483
484     i = bullets.erase(i);
485   }
486   for(MovingObjects::iterator i = moving_objects.begin();
487       i != moving_objects.end(); /* nothing */) {
488     MovingObject* moving_object = *i;
489     if(moving_object->is_valid()) {
490       ++i;
491       continue;
492     }
493
494 #ifdef USE_GRID
495     grid->remove_object(moving_object);
496 #endif
497     
498     i = moving_objects.erase(i);
499   }
500   for(std::vector<GameObject*>::iterator i = gameobjects.begin();
501       i != gameobjects.end(); /* nothing */) {
502     GameObject* object = *i;
503     
504     if(object->is_valid()) {
505       ++i;
506       continue;
507     }
508     
509     delete *i;
510     i = gameobjects.erase(i);
511   }
512
513   /* add newly created objects */
514   for(std::vector<GameObject*>::iterator i = gameobjects_new.begin();
515       i != gameobjects_new.end(); ++i)
516   {
517     GameObject* object = *i;
518     
519     Bullet* bullet = dynamic_cast<Bullet*> (object);
520     if(bullet)
521       bullets.push_back(bullet);
522
523     MovingObject* movingobject = dynamic_cast<MovingObject*> (object);
524     if(movingobject) {
525       moving_objects.push_back(movingobject);
526  #ifdef USE_GRID
527       grid->add_object(movingobject);
528 #endif
529     }
530     
531     TileMap* tilemap = dynamic_cast<TileMap*> (object);
532     if(tilemap && tilemap->is_solid()) {
533       if(solids == 0) {
534         solids = tilemap;
535       } else {
536         std::cerr << "Another solid tilemaps added. Ignoring.";
537       }
538     }
539
540     Camera* camera = dynamic_cast<Camera*> (object);
541     if(camera) {
542       if(this->camera != 0) {
543         std::cerr << "Warning: Multiple cameras added. Ignoring.";
544         continue;
545       }
546       this->camera = camera;
547     }
548
549     gameobjects.push_back(object);
550   }
551   gameobjects_new.clear();
552 }
553
554 void
555 Sector::draw(DrawingContext& context)
556 {
557   context.push_transform();
558   context.set_translation(camera->get_translation());
559
560   for(GameObjects::iterator i = gameobjects.begin();
561       i != gameobjects.end(); ++i) {
562     GameObject* object = *i; 
563     if(!object->is_valid())
564       continue;
565     
566     object->draw(context);
567   }
568
569   context.pop_transform();
570 }
571
572 static const float DELTA = .001;
573
574 void
575 Sector::collision_tilemap(MovingObject* object, CollisionHit& hit) const
576 {
577   // calculate rectangle where the object will move
578   float x1, x2;
579   if(object->get_movement().x >= 0) {
580     x1 = object->get_bbox().p1.x;
581     x2 = object->get_bbox().p2.x + object->get_movement().x;
582   } else {
583     x1 = object->get_bbox().p1.x + object->get_movement().x;
584     x2 = object->get_bbox().p2.x;
585   }
586   float y1, y2;
587   if(object->get_movement().y >= 0) {
588     y1 = object->get_bbox().p1.y;
589     y2 = object->get_bbox().p2.y + object->get_movement().y;
590   } else {
591     y1 = object->get_bbox().p1.y + object->get_movement().y;
592     y2 = object->get_bbox().p2.y;
593   }
594
595   // test with all tiles in this rectangle
596   int starttilex = int(x1) / 32;
597   int starttiley = int(y1) / 32;
598   int max_x = int(x2);
599   // the +1 is somehow needed to make characters stay on the floor
600   int max_y = int(y2+1);
601
602   CollisionHit temphit;
603   Rect dest = object->get_bbox();
604   dest.move(object->movement);
605   for(int x = starttilex; x*32 < max_x; ++x) {
606     for(int y = starttiley; y*32 < max_y; ++y) {
607       const Tile* tile = solids->get_tile(x, y);
608       if(!tile)
609         continue;
610       // skip non-solid tiles
611       if(tile->getAttributes() == 0)
612         continue;
613       // only handle unisolid when the player is falling down and when he was
614       // above the tile before
615       if(tile->getAttributes() & Tile::UNISOLID) {
616         if(object->movement.y < 0 || object->get_bbox().p2.y > y*32)
617           continue;
618       }
619
620       if(tile->getAttributes() & Tile::SLOPE) { // slope tile
621         AATriangle triangle;
622         Vector p1(x*32, y*32);
623         Vector p2((x+1)*32, (y+1)*32);
624         triangle = AATriangle(p1, p2, tile->getData());
625
626         if(Collision::rectangle_aatriangle(temphit, dest, object->movement,
627               triangle)) {
628           if(temphit.time > hit.time && (tile->getAttributes() & Tile::SOLID)) {
629             hit = temphit;
630           }
631         }
632       } else { // normal rectangular tile
633         Rect rect(x*32, y*32, (x+1)*32, (y+1)*32);
634         if(Collision::rectangle_rectangle(temphit, dest,
635               object->movement, rect)) {
636           if(temphit.time > hit.time && (tile->getAttributes() & Tile::SOLID)) {
637             hit = temphit;
638           }
639         }
640       }
641     }
642   }
643 }
644
645 uint32_t
646 Sector::collision_tile_attributes(MovingObject* object) const
647 {
648   /** XXX This function doesn't work correctly as it will check all tiles
649    * in the bounding box of the object movement, this might include tiles
650    * that have actually never been touched by the object
651    * (though this only occures for very fast objects...)
652    */
653   
654   // calculate rectangle where the object will move
655   float x1, x2;
656   if(object->get_movement().x >= 0) {
657     x1 = object->get_bbox().p1.x;
658     x2 = object->get_bbox().p2.x + object->get_movement().x;
659   } else {
660     x1 = object->get_bbox().p1.x + object->get_movement().x;
661     x2 = object->get_bbox().p2.x;
662   }
663   float y1, y2;
664   if(object->get_movement().y >= 0) {
665     y1 = object->get_bbox().p1.y;
666     y2 = object->get_bbox().p2.y + object->get_movement().y;
667   } else {
668     y1 = object->get_bbox().p1.y + object->get_movement().y;
669     y2 = object->get_bbox().p2.y;
670   }
671
672   // test with all tiles in this rectangle
673   int starttilex = int(x1-1) / 32;
674   int starttiley = int(y1-1) / 32;
675   int max_x = int(x2+1);
676   int max_y = int(y2+1);
677
678   uint32_t result = 0;
679   for(int x = starttilex; x*32 < max_x; ++x) {
680     for(int y = starttiley; y*32 < max_y; ++y) {
681       const Tile* tile = solids->get_tile(x, y);
682       if(!tile)
683         continue;
684       result |= tile->getAttributes();
685     }
686   }
687
688   return result;
689 }
690
691 void
692 Sector::collision_object(MovingObject* object1, MovingObject* object2) const
693 {
694   CollisionHit hit;
695   Rect dest1 = object1->get_bbox();
696   dest1.move(object1->get_movement());
697   Rect dest2 = object2->get_bbox();
698   dest2.move(object2->get_movement());
699
700   Vector movement = object1->get_movement() - object2->get_movement();
701   if(Collision::rectangle_rectangle(hit, dest1, movement, dest2)) {
702     HitResponse response1 = object1->collision(*object2, hit);
703     hit.normal *= -1;
704     HitResponse response2 = object2->collision(*object1, hit);
705
706     if(response1 != CONTINUE) {
707       if(response1 == ABORT_MOVE)
708         object1->movement = Vector(0, 0);
709       if(response2 == CONTINUE)
710         object2->movement += hit.normal * (hit.depth + DELTA);
711     } else if(response2 != CONTINUE) {
712       if(response2 == ABORT_MOVE)
713         object2->movement = Vector(0, 0);
714       if(response1 == CONTINUE)
715         object1->movement += -hit.normal * (hit.depth + DELTA);
716     } else {
717       object1->movement += -hit.normal * (hit.depth/2 + DELTA);
718       object2->movement += hit.normal * (hit.depth/2 + DELTA);
719     }
720   }
721 }
722
723 void
724 Sector::handle_collisions()
725 {
726   // part1: COLGROUP_MOVING vs COLGROUP_STATIC and tilemap
727   //   we do this up to 4 times and have to sort all results for the smallest
728   //   one before we can continue here
729   for(MovingObjects::iterator i = moving_objects.begin();
730       i != moving_objects.end(); ++i) {
731     MovingObject* moving_object = *i;
732     if((moving_object->get_group() != COLGROUP_MOVING
733           && moving_object->get_group() != COLGROUP_MOVING_ONLY_STATIC)
734         || !moving_object->is_valid())
735       continue;
736
737     // up to 4 tries
738     for(int t = 0; t < 4; ++t) {
739       CollisionHit hit;
740       hit.time = -1;
741       MovingObject* collided_with = NULL; 
742       
743       // collision with tilemap
744       collision_tilemap(moving_object, hit);
745     
746       // collision with other objects
747       Rect dest1 = moving_object->get_bbox();
748       dest1.move(moving_object->get_movement());
749       CollisionHit temphit;
750       
751       for(MovingObjects::iterator i2 = moving_objects.begin();
752           i2 != moving_objects.end(); ++i2) {
753         MovingObject* moving_object_2 = *i2;
754         if(moving_object_2->get_group() != COLGROUP_STATIC
755            || !moving_object_2->is_valid())
756           continue;
757         
758         Rect dest2 = moving_object_2->get_bbox();
759         dest2.move(moving_object_2->get_movement());
760         Vector movement 
761           = moving_object->get_movement() - moving_object_2->get_movement();
762         if(Collision::rectangle_rectangle(temphit, dest1, movement, dest2)
763             && temphit.time > hit.time) {
764           hit = temphit;
765           collided_with = moving_object_2;
766         }
767       }
768
769       if(hit.time < 0)
770         break;
771
772       // call collision callbacks
773       HitResponse response;
774       if(collided_with != 0) {
775         response = moving_object->collision(*collided_with, hit);
776         hit.normal *= -1;
777         collided_with->collision(*moving_object, hit);
778       } else {
779         response = moving_object->collision(*solids, hit);
780         hit.normal *= -1;
781       }
782    
783       if(response == CONTINUE) {
784         moving_object->movement += -hit.normal * (hit.depth + DELTA);
785       } else if(response == ABORT_MOVE) {
786         moving_object->movement = Vector(0, 0);
787         break;
788       } else { // force move
789         break;
790       }
791     }
792   }
793
794   // part2: COLGROUP_MOVING vs tile attributes
795   for(MovingObjects::iterator i = moving_objects.begin();
796       i != moving_objects.end(); ++i) {
797     MovingObject* moving_object = *i;
798     if((moving_object->get_group() != COLGROUP_MOVING
799           && moving_object->get_group() != COLGROUP_MOVING_ONLY_STATIC)
800         || !moving_object->is_valid())
801       continue;
802
803     uint32_t tile_attributes = collision_tile_attributes(moving_object);
804     if(tile_attributes > Tile::FIRST_INTERESTING_FLAG) {
805       moving_object->collision_tile(tile_attributes);
806     }
807   }
808
809   // part2.5: COLGROUP_MOVING vs COLGROUP_TOUCHABLE
810   for(MovingObjects::iterator i = moving_objects.begin();
811       i != moving_objects.end(); ++i) {
812     MovingObject* moving_object = *i;
813     if(moving_object->get_group() != COLGROUP_MOVING
814         || !moving_object->is_valid())
815       continue;
816
817     for(MovingObjects::iterator i2 = moving_objects.begin();
818         i2 != moving_objects.end(); ++i2) {
819       MovingObject* moving_object_2 = *i2;
820       if(moving_object_2->get_group() != COLGROUP_TOUCHABLE
821          || !moving_object_2->is_valid())
822         continue;
823
824       collision_object(moving_object, moving_object_2);
825     } 
826   }
827
828   // part3: COLGROUP_MOVING vs COLGROUP_MOVING
829   for(MovingObjects::iterator i = moving_objects.begin();
830       i != moving_objects.end(); ++i) {
831     MovingObject* moving_object = *i;
832
833     if(moving_object->get_group() != COLGROUP_MOVING
834         || !moving_object->is_valid())
835       continue;
836
837     for(MovingObjects::iterator i2 = i+1;
838         i2 != moving_objects.end(); ++i2) {
839       MovingObject* moving_object_2 = *i2;
840       if(moving_object_2->get_group() != COLGROUP_MOVING
841          || !moving_object_2->is_valid())
842         continue;
843
844       collision_object(moving_object, moving_object_2);
845     }    
846   }
847
848   // apply object movement
849   for(MovingObjects::iterator i = moving_objects.begin();
850       i != moving_objects.end(); ++i) {
851     MovingObject* moving_object = *i;
852
853     moving_object->bbox.move(moving_object->get_movement());
854     moving_object->movement = Vector(0, 0);
855   }
856 }
857
858 bool
859 Sector::is_free_space(const Rect& rect) const
860 {
861   // test with all tiles in this rectangle
862   int starttilex = int(rect.p1.x) / 32;
863   int starttiley = int(rect.p1.y) / 32;
864   int max_x = int(rect.p2.x);
865   int max_y = int(rect.p2.y);
866
867   for(int x = starttilex; x*32 < max_x; ++x) {
868     for(int y = starttiley; y*32 < max_y; ++y) {
869       const Tile* tile = solids->get_tile(x, y);
870       if(!tile)
871         continue;
872       if(tile->getAttributes() & Tile::SOLID)
873         return false;
874     }
875   }
876
877   for(MovingObjects::const_iterator i = moving_objects.begin();
878       i != moving_objects.end(); ++i) {
879     const MovingObject* moving_object = *i;
880     if(moving_object->get_group() != COLGROUP_STATIC
881         || !moving_object->is_valid())
882       continue;
883
884     if(Collision::intersects(rect, moving_object->get_bbox()))
885       return false;
886   }
887
888   return true;
889 }
890
891 bool
892 Sector::add_bullet(const Vector& pos, float xm, Direction dir)
893 {
894   // TODO remove this function and move these checks elsewhere...
895   static const size_t MAX_FIRE_BULLETS = 2;
896   static const size_t MAX_ICE_BULLETS = 1;
897
898   Bullet* new_bullet = 0;
899   if(player_status->bonus == FIRE_BONUS) {
900     if(bullets.size() > MAX_FIRE_BULLETS-1)
901       return false;
902     new_bullet = new Bullet(pos, xm, dir, FIRE_BULLET);
903   } else if(player_status->bonus == ICE_BONUS) {
904     if(bullets.size() > MAX_ICE_BULLETS-1)
905       return false;
906     new_bullet = new Bullet(pos, xm, dir, ICE_BULLET);
907   } else {
908     return false;
909   }
910   add_object(new_bullet);
911
912   sound_manager->play("sounds/shoot.wav");
913
914   return true;
915 }
916
917 bool
918 Sector::add_smoke_cloud(const Vector& pos)
919 {
920   add_object(new SmokeCloud(pos));
921   return true;
922 }
923
924 void
925 Sector::add_floating_text(const Vector& pos, const std::string& text)
926 {
927   add_object(new FloatingText(pos, text));
928 }
929
930 void
931 Sector::play_music(MusicType type)
932 {
933   currentmusic = type;
934   switch(currentmusic) {
935     case LEVEL_MUSIC:
936       sound_manager->play_music(std::string("music/") + song_title);
937       break;
938     case HERRING_MUSIC:
939       sound_manager->play_music("music/salcon.ogg");
940       break;
941     default:
942       sound_manager->play_music("");
943       break;
944   }
945 }
946
947 MusicType
948 Sector::get_music_type()
949 {
950   return currentmusic;
951 }
952
953 int
954 Sector::get_total_badguys()
955 {
956   int total_badguys = 0;
957   for(GameObjects::iterator i = gameobjects.begin();
958       i != gameobjects.end(); ++i) {
959     BadGuy* badguy = dynamic_cast<BadGuy*> (*i);
960     if (badguy && badguy->countMe)
961       total_badguys++;
962   }
963
964   return total_badguys;
965 }
966
967 bool
968 Sector::inside(const Rect& rect) const
969 {
970   if(rect.p1.x > solids->get_width() * 32 
971       || rect.p1.y > solids->get_height() * 32
972       || rect.p2.x < 0 || rect.p2.y < 0)
973     return false;
974
975   return true;
976 }