a82136a4b8caeb39c32c85206501d26bb1695d3b
[supertux.git] / src / badguy / dispenser.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 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
20 #include <config.h>
21
22 #include "dispenser.hpp"
23
24 #include "object/bullet.hpp"
25 #include "random_generator.hpp"
26 #include "lisp/writer.hpp"
27 #include "object_factory.hpp"
28 #include "audio/sound_manager.hpp"
29 #include "sector.hpp"
30 #include "object/player.hpp"
31 #include "log.hpp"
32
33 #include <stdexcept>
34
35 Dispenser::Dispenser(const lisp::Lisp& reader)
36         : BadGuy(reader, "images/creatures/dispenser/dispenser.sprite")
37 {
38   set_colgroup_active(COLGROUP_MOVING_STATIC);
39   sound_manager->preload("sounds/squish.wav");
40   reader.get("cycle", cycle);
41   reader.get("badguy", badguys);
42   random = false; // default
43   reader.get("random", random);
44   type = "dropper"; //default
45   reader.get("type", type);
46   next_badguy = 0;
47   autotarget = false;
48   swivel = false;
49   broken = false;
50
51   if (badguys.size() <= 0)
52     throw std::runtime_error("No badguys in dispenser.");
53
54   if (type == "rocketlauncher") {
55     sprite->set_action(dir == LEFT ? "working-left" : "working-right");
56     set_colgroup_active(COLGROUP_MOVING); //if this were COLGROUP_MOVING_STATIC MrRocket would explode on launch.
57
58     if (start_dir == AUTO) {
59       autotarget = true;
60     }
61   } else if (type == "cannon") {
62     sprite->set_action("working");
63   } else {
64     sprite->set_action("dropper");
65   }
66
67   bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height());
68   countMe = false;
69 }
70
71 void
72 Dispenser::write(lisp::Writer& writer)
73 {
74   writer.start_list("dispenser");
75
76   writer.write("x", start_position.x);
77   writer.write("y", start_position.y);
78   writer.write("cycle", cycle);
79   writer.write("random", random);
80   writer.write("type", type);
81   writer.write("badguy", badguys);
82
83   writer.end_list("dispenser");
84 }
85
86 void
87 Dispenser::activate()
88 {
89    if( broken ){
90      return;
91    }
92    if( autotarget && !swivel ){ // auto cannon sprite might be wrong
93       Player* player = this->get_nearest_player();
94       if( player ){
95         dir = (player->get_pos().x > get_pos().x) ? RIGHT : LEFT;
96         sprite->set_action(dir == LEFT ? "working-left" : "working-right");
97       }
98    }
99    dispense_timer.start(cycle, true);
100    launch_badguy();
101 }
102
103 void
104 Dispenser::deactivate()
105 {
106    dispense_timer.stop();
107 }
108
109 //TODO: Add launching velocity to certain badguys
110 bool
111 Dispenser::collision_squished(GameObject& object)
112 {
113   //Cannon launching MrRocket can be broken by jumping on it
114   //other dispensers are not that fragile.
115   if (broken || type != "rocketlauncher") {
116     return false;
117   }
118
119   sprite->set_action(dir == LEFT ? "broken-left" : "broken-right");
120   dispense_timer.start(0);
121   set_colgroup_active(COLGROUP_MOVING_STATIC); // Tux can stand on broken cannon.
122   Player* player = dynamic_cast<Player*>(&object);
123   if (player){
124     player->bounce(*this);
125   }
126   sound_manager->play("sounds/squish.wav", get_pos());
127   broken = true;
128   return true;
129 }
130
131 HitResponse
132 Dispenser::collision(GameObject& other, const CollisionHit& hit)
133 {
134   Player* player = dynamic_cast<Player*> (&other);
135   if(player) {
136     // hit from above?
137     if (player->get_bbox().p2.y < (bbox.p1.y + 16)) {
138       collision_squished(*player);
139       return FORCE_MOVE;
140     }
141     if(frozen){
142       unfreeze();
143     }
144     return FORCE_MOVE;
145   }
146
147   Bullet* bullet = dynamic_cast<Bullet*> (&other);
148   if(bullet){
149     return collision_bullet(*bullet, hit);
150   }
151
152   return FORCE_MOVE;
153 }
154
155
156 void
157 Dispenser::active_update(float )
158 {
159   if (dispense_timer.check()) {
160     // auto always shoots in Tux's direction
161     if( autotarget ){ 
162       if( sprite->animation_done()) {
163         sprite->set_action(dir == LEFT ? "working-left" : "working-right");
164         swivel = false;
165       }
166
167       Player* player = this->get_nearest_player();
168       if( player && !swivel ){
169         Direction targetdir = (player->get_pos().x > get_pos().x) ? RIGHT : LEFT;
170         if( dir != targetdir ){ // no target: swivel cannon 
171           swivel = true;
172           dir = targetdir;
173           sprite->set_action(dir == LEFT ? "swivel-left" : "swivel-right", 1);
174         } else { // tux in sight: shoot
175           launch_badguy();
176         }
177       }
178     } else {
179       launch_badguy();
180     }
181   }
182 }
183
184 void
185 Dispenser::launch_badguy()
186 {
187   //FIXME: Does is_offscreen() work right here?
188   if (!is_offscreen()) {
189     Direction launchdir = dir;
190     if( !autotarget && start_dir == AUTO ){
191       Player* player = this->get_nearest_player();
192       if( player ){
193         launchdir = (player->get_pos().x > get_pos().x) ? RIGHT : LEFT;
194       } 
195     } 
196
197     if (badguys.size() > 1) {
198       if (random) {
199         next_badguy = systemRandom.rand(badguys.size());
200       }
201       else {
202         next_badguy++;
203
204         if (next_badguy >= badguys.size())
205           next_badguy = 0;
206       }
207     }
208
209     std::string badguy = badguys[next_badguy];
210
211     if(badguy == "random") {
212       log_warning << "random is outdated; use a list of badguys to select from." << std::endl;
213       return;
214     }
215
216     GameObject* badguy_object = NULL;
217
218     try {
219       Vector spawnpoint;
220
221       if (type == "dropper")
222         spawnpoint = Vector(get_pos().x, get_pos().y+32);
223       else if (type == "cannon")
224         spawnpoint = Vector(get_pos().x + (launchdir == LEFT ? -32 : 32), get_pos().y);
225       else if (type == "rocketlauncher")
226         spawnpoint = Vector(get_pos().x + (launchdir == LEFT ? -32 : 32), get_pos().y);
227
228       badguy_object = create_object(badguy, Vector(get_pos().x, get_pos().y+32), launchdir);
229
230       if (badguy_object)
231         Sector::current()->add_object(badguy_object);
232     } catch(std::exception& e) {
233       log_warning << "Error dispensing badguy: " << e.what() << std::endl;
234       return;
235     }
236   }
237 }
238
239 void
240 Dispenser::freeze()
241 {
242   BadGuy::freeze();
243   dispense_timer.stop();
244 }
245
246 void
247 Dispenser::unfreeze()
248 {
249   BadGuy::unfreeze();
250   activate();
251 }
252
253 bool
254 Dispenser::is_freezable() const
255 {
256   return true;
257 }
258 IMPLEMENT_FACTORY(Dispenser, "dispenser")