New sound effects
[supertux.git] / src / badguy / dispenser.cpp
1 //  SuperTux
2 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 #include "badguy/dispenser.hpp"
18
19 #include "audio/sound_manager.hpp"
20 #include "math/random_generator.hpp"
21 #include "object/bullet.hpp"
22 #include "object/player.hpp"
23 #include "supertux/object_factory.hpp"
24 #include "supertux/sector.hpp"
25 #include "util/reader.hpp"
26
27 #include <stdexcept>
28
29 Dispenser::Dispenser(const Reader& reader) :
30   BadGuy(reader, "images/creatures/dispenser/dispenser.sprite"),
31   cycle(),
32   badguys(),
33   next_badguy(),
34   dispense_timer(),
35   autotarget(),
36   swivel(),
37   broken(),
38   random(),
39   type()
40 {
41   set_colgroup_active(COLGROUP_MOVING_STATIC);
42   SoundManager::current()->preload("sounds/squish.wav");
43   reader.get("cycle", cycle);
44   reader.get("badguy", badguys);
45   random = false; // default
46   reader.get("random", random);
47   type = "dropper"; //default
48   reader.get("type", type);
49   next_badguy = 0;
50   autotarget = false;
51   swivel = false;
52   broken = false;
53
54   if (badguys.size() <= 0)
55     throw std::runtime_error("No badguys in dispenser.");
56
57   if (type == "rocketlauncher") {
58     sprite->set_action(dir == LEFT ? "working-left" : "working-right");
59     set_colgroup_active(COLGROUP_MOVING); //if this were COLGROUP_MOVING_STATIC MrRocket would explode on launch.
60
61     if (start_dir == AUTO) {
62       autotarget = true;
63     }
64   } else if (type == "cannon") {
65     sprite->set_action("working");
66   } else {
67     sprite->set_action("dropper");
68   }
69
70   bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height());
71   countMe = false;
72 }
73
74 void
75 Dispenser::activate()
76 {
77   if( broken ){
78     return;
79   }
80   if( autotarget && !swivel ){ // auto cannon sprite might be wrong
81     Player* player = this->get_nearest_player();
82     if( player ){
83       dir = (player->get_pos().x > get_pos().x) ? RIGHT : LEFT;
84       sprite->set_action(dir == LEFT ? "working-left" : "working-right");
85     }
86   }
87   dispense_timer.start(cycle, true);
88   launch_badguy();
89 }
90
91 void
92 Dispenser::deactivate()
93 {
94   dispense_timer.stop();
95 }
96
97 //TODO: Add launching velocity to certain badguys
98 bool
99 Dispenser::collision_squished(GameObject& object)
100 {
101   //Cannon launching MrRocket can be broken by jumping on it
102   //other dispensers are not that fragile.
103   if (broken || type != "rocketlauncher") {
104     return false;
105   }
106
107   sprite->set_action(dir == LEFT ? "broken-left" : "broken-right");
108   dispense_timer.start(0);
109   set_colgroup_active(COLGROUP_MOVING_STATIC); // Tux can stand on broken cannon.
110   Player* player = dynamic_cast<Player*>(&object);
111   if (player){
112     player->bounce(*this);
113   }
114   SoundManager::current()->play("sounds/squish.wav", get_pos());
115   broken = true;
116   return true;
117 }
118
119 HitResponse
120 Dispenser::collision(GameObject& other, const CollisionHit& hit)
121 {
122   Player* player = dynamic_cast<Player*> (&other);
123   if(player) {
124     // hit from above?
125     if (player->get_bbox().p2.y < (bbox.p1.y + 16)) {
126       collision_squished(*player);
127       return FORCE_MOVE;
128     }
129     if(frozen){
130       unfreeze();
131     }
132     return FORCE_MOVE;
133   }
134
135   Bullet* bullet = dynamic_cast<Bullet*> (&other);
136   if(bullet){
137     return collision_bullet(*bullet, hit);
138   }
139
140   return FORCE_MOVE;
141 }
142
143 void
144 Dispenser::active_update(float )
145 {
146   if (dispense_timer.check()) {
147     // auto always shoots in Tux's direction
148     if( autotarget ){
149       if( sprite->animation_done()) {
150         sprite->set_action(dir == LEFT ? "working-left" : "working-right");
151         swivel = false;
152       }
153
154       Player* player = this->get_nearest_player();
155       if( player && !swivel ){
156         Direction targetdir = (player->get_pos().x > get_pos().x) ? RIGHT : LEFT;
157         if( dir != targetdir ){ // no target: swivel cannon
158           swivel = true;
159           dir = targetdir;
160           sprite->set_action(dir == LEFT ? "swivel-left" : "swivel-right", 1);
161         } else { // tux in sight: shoot
162           launch_badguy();
163         }
164       }
165     } else {
166       launch_badguy();
167     }
168   }
169 }
170
171 void
172 Dispenser::launch_badguy()
173 {
174   //FIXME: Does is_offscreen() work right here?
175   if (!is_offscreen()) {
176     Direction launchdir = dir;
177     if( !autotarget && start_dir == AUTO ){
178       Player* player = this->get_nearest_player();
179       if( player ){
180         launchdir = (player->get_pos().x > get_pos().x) ? RIGHT : LEFT;
181       }
182     }
183
184     if (badguys.size() > 1) {
185       if (random) {
186         next_badguy = gameRandom.rand(badguys.size());
187       }
188       else {
189         next_badguy++;
190
191         if (next_badguy >= badguys.size())
192           next_badguy = 0;
193       }
194     }
195
196     std::string badguy = badguys[next_badguy];
197
198     if(badguy == "random") {
199       log_warning << "random is outdated; use a list of badguys to select from." << std::endl;
200       return;
201     }
202     if(badguy == "goldbomb") {
203       log_warning << "goldbomb is not allowed to be dispensed" << std::endl;
204       return;
205     }
206
207     try {
208       GameObjectPtr game_object;
209       Vector spawnpoint;
210       Rectf object_bbox;
211
212       /* Need to allocate the badguy first to figure out its bounding box. */
213       game_object = ObjectFactory::instance().create(badguy, get_pos(), launchdir);
214       if (game_object == NULL)
215         throw std::runtime_error("Creating " + badguy + " object failed.");
216
217       BadGuy& bad_guy = dynamic_cast<BadGuy&>(*game_object);
218
219       object_bbox = bad_guy.get_bbox();
220
221       if (type == "dropper")
222       {
223         spawnpoint = get_anchor_pos (get_bbox(), ANCHOR_BOTTOM);
224         spawnpoint.x -= 0.5 * object_bbox.get_width();
225       }
226       else if ((type == "cannon") || (type == "rocketlauncher"))
227       {
228         spawnpoint = get_pos(); /* top-left corner of the cannon */
229         if (launchdir == LEFT)
230           spawnpoint.x -= object_bbox.get_width() + 1;
231         else
232           spawnpoint.x += get_bbox().get_width() + 1;
233       }
234
235       /* Now we set the real spawn position */
236       bad_guy.set_pos(spawnpoint);
237
238       /* We don't want to count dispensed badguys in level stats */
239       if(bad_guy.countMe)
240         bad_guy.countMe = false;
241
242       Sector::current()->add_object(game_object);
243     } catch(const std::exception& e) {
244       log_warning << "Error dispensing badguy: " << e.what() << std::endl;
245       return;
246     }
247   }
248 }
249
250 void
251 Dispenser::freeze()
252 {
253   BadGuy::freeze();
254   dispense_timer.stop();
255 }
256
257 void
258 Dispenser::unfreeze()
259 {
260   BadGuy::unfreeze();
261   activate();
262 }
263
264 bool
265 Dispenser::is_freezable() const
266 {
267   return true;
268 }
269
270 /* EOF */