a3db52a59ab5902a270cf027540592fdc6bacdad
[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   sound_manager->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   sound_manager->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
203     try {
204       GameObject *game_object;
205       MovingObject *moving_object;
206       Vector spawnpoint;
207       Rectf object_bbox;
208
209       /* Need to allocate the badguy first to figure out its bounding box. */
210       game_object = ObjectFactory::instance().create(badguy, get_pos(), launchdir);
211       if (game_object == NULL)
212         throw std::runtime_error("Creating " + badguy + " object failed.");
213
214       moving_object = dynamic_cast<MovingObject *> (game_object);
215       if (moving_object == NULL)
216         throw std::runtime_error(badguy + " is not a moving object.");
217
218       object_bbox = moving_object->get_bbox ();
219
220       if (type == "dropper") {
221         spawnpoint = get_anchor_pos (get_bbox (), ANCHOR_BOTTOM);
222         spawnpoint.x -= 0.5 * object_bbox.get_width ();
223       }
224       else if ((type == "cannon") || (type == "rocketlauncher")) {
225         spawnpoint = get_pos (); /* top-left corner of the cannon */
226         if (launchdir == LEFT)
227           spawnpoint.x -= object_bbox.get_width () + 1;
228         else
229           spawnpoint.x += get_bbox ().get_width () + 1;
230       }
231
232       /* Now we set the real spawn position */
233       log_debug << "Cannong bbox: " << get_bbox () << std::endl;
234       log_debug << "Badguy width: " << object_bbox.get_width () << std::endl;
235       log_debug << "New badguy's spawnpoint: " << spawnpoint << std::endl;
236       moving_object->set_pos (spawnpoint);
237
238       Sector::current()->add_object(moving_object);
239     } catch(std::exception& e) {
240       log_warning << "Error dispensing badguy: " << e.what() << std::endl;
241       return;
242     }
243   }
244 }
245
246 void
247 Dispenser::freeze()
248 {
249   BadGuy::freeze();
250   dispense_timer.stop();
251 }
252
253 void
254 Dispenser::unfreeze()
255 {
256   BadGuy::unfreeze();
257   activate();
258 }
259
260 bool
261 Dispenser::is_freezable() const
262 {
263   return true;
264 }
265
266 /* EOF */