Stay-on-platform Badguys no longer go crazy when falling down
[supertux.git] / src / badguy / totem.cpp
1 //  $Id$
2 // 
3 //  SuperTux - "Totem" Badguy
4 //  Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.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
19 //  02111-1307, USA.
20
21 #include <config.h>
22
23 #include "totem.hpp"
24 #include "log.hpp"
25
26 static const float WALKSPEED = 100;
27 static const float JUMP_ON_SPEED_Y = -400;
28 static const float JUMP_OFF_SPEED_Y = -500;
29
30 Totem::Totem(const lisp::Lisp& reader)
31         : BadGuy(reader, "images/creatures/totem/totem.sprite")
32 {
33   carrying = 0;
34   carried_by = 0;
35 }
36
37 Totem::Totem(const Totem& other)
38         : BadGuy(other), carrying(other.carrying), carried_by(other.carried_by)
39 {
40 }
41
42 Totem::~Totem() 
43 {
44   if (carrying) carrying->jump_off();
45   if (carried_by) jump_off();
46 }
47
48 bool 
49 Totem::updatePointers(const GameObject* from_object, GameObject* to_object)
50 {
51   if (from_object == carrying) {
52     carrying = dynamic_cast<Totem*>(to_object);
53     return true;
54   }
55   if (from_object == carried_by) {
56     carried_by = dynamic_cast<Totem*>(to_object);
57     return true;
58   }
59   return false;
60 }
61
62 void
63 Totem::write(lisp::Writer& writer)
64 {
65   writer.start_list("totem");
66
67   writer.write_float("x", start_position.x);
68   writer.write_float("y", start_position.y);
69
70   writer.end_list("totem");
71 }
72
73 void
74 Totem::activate()
75 {
76   if (!carried_by) {
77     physic.set_velocity_x(dir == LEFT ? -WALKSPEED : WALKSPEED);
78     sprite->set_action(dir == LEFT ? "walking-left" : "walking-right");
79     return;
80   } else {
81     synchronize_with(carried_by);
82     sprite->set_action(dir == LEFT ? "stacked-left" : "stacked-right");
83     return;
84   }
85 }
86
87 void
88 Totem::active_update(float elapsed_time)
89 {
90   BadGuy::active_update(elapsed_time);
91
92   if (!carried_by) {
93     if (on_ground() && might_fall())
94     {
95       dir = (dir == LEFT ? RIGHT : LEFT);
96       activate();
97     }
98
99     Sector* s = Sector::current();
100     if (s) {
101       // jump a bit if we find a suitable totem 
102       for (std::vector<MovingObject*>::iterator i = s->moving_objects.begin(); i != s->moving_objects.end(); i++) {
103         Totem* t = dynamic_cast<Totem*>(*i);
104         if (!t) continue;
105         
106         // skip if we are not approaching each other
107         if (!((this->dir == LEFT) && (t->dir == RIGHT))) continue;
108         
109         Vector p1 = this->get_pos();
110         Vector p2 = t->get_pos();
111
112         // skip if not on same height
113         float dy = (p1.y - p2.y);
114         if (fabsf(dy - 0) > 2) continue;
115
116         // skip if too far away
117         float dx = (p1.x - p2.x);
118         if (fabsf(dx - 128) > 2) continue;
119
120         physic.set_velocity_y(JUMP_ON_SPEED_Y);
121         p1.y -= 1;
122         this->set_pos(p1);
123         break;
124       }
125     }
126   }
127
128   if (carried_by) {
129     this->synchronize_with(carried_by);
130   }
131
132   if (carrying) {
133     carrying->synchronize_with(this);
134   }
135
136 }
137
138 bool
139 Totem::collision_squished(Player& player)
140 {
141   if (carrying) carrying->jump_off();
142   if (carried_by) {
143     player.bounce(*this);
144     jump_off();
145   }
146
147   sprite->set_action(dir == LEFT ? "squished-left" : "squished-right");
148   bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height());
149
150   kill_squished(player);
151   return true;
152 }
153
154 void
155 Totem::collision_solid(const CollisionHit& hit)
156 {
157   update_on_ground_flag(hit);
158
159   // if we are being carried around, pass event to bottom of stack and ignore it
160   if (carried_by) {
161     carried_by->collision_solid(hit);
162     return;
163   }
164
165   // If we hit something from above or below: stop moving in this direction 
166   if (hit.top || hit.bottom) {
167     physic.set_velocity_y(0);
168   }
169
170   // If we are hit from the direction we are facing: turn around
171   if (hit.left && (dir == LEFT)) {
172     dir = RIGHT;
173     activate();
174   }
175   if (hit.right && (dir == RIGHT)) {
176     dir = LEFT;
177     activate();
178   }
179 }
180
181 HitResponse
182 Totem::collision_badguy(BadGuy& badguy, const CollisionHit& hit)
183 {
184   // if we are being carried around, pass event to bottom of stack and ignore it
185   if (carried_by) {
186     carried_by->collision_badguy(badguy, hit);
187     return CONTINUE;
188   }
189  
190   // if we hit a Totem that is not from our stack: have our base jump on its top
191   Totem* totem = dynamic_cast<Totem*>(&badguy);
192   if (totem) {
193     Totem* thisBase = this; while (thisBase->carried_by) thisBase=thisBase->carried_by;
194     Totem* srcBase = totem; while (srcBase->carried_by)  srcBase=srcBase->carried_by;
195     Totem* thisTop = this;  while (thisTop->carrying)    thisTop=thisTop->carrying;
196     if (srcBase != thisBase) {
197       srcBase->jump_on(thisTop);
198     }
199   }
200
201   // If we are hit from the direction we are facing: turn around
202   if(hit.left && (dir == LEFT)) {
203     dir = RIGHT;
204     activate();
205   }
206   if(hit.right && (dir == RIGHT)) {
207     dir = LEFT;
208     activate();
209   }
210
211   return CONTINUE;
212 }
213
214 void
215 Totem::kill_fall()
216 {
217   if (carrying) carrying->jump_off();
218   if (carried_by) jump_off();
219
220   BadGuy::kill_fall();
221 }
222
223 void 
224 Totem::jump_on(Totem* target)
225 {
226   if (target->carrying) {
227     log_warning << "target is already carrying someone" << std::endl;
228     return;
229   }
230   
231   target->carrying = this;
232
233   this->carried_by = target;
234   this->activate();
235   bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height());
236   
237   this->synchronize_with(target);
238 }
239
240 void
241 Totem::jump_off() {
242   if (!carried_by) {
243     log_warning << "not carried by anyone" << std::endl;
244     return;
245   }
246
247   carried_by->carrying = 0;
248
249   this->carried_by = 0;
250
251   this->activate();
252   bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height());
253
254
255   physic.set_velocity_y(JUMP_OFF_SPEED_Y);
256 }
257
258 void 
259 Totem::synchronize_with(Totem* base)
260 {
261
262   if (dir != base->dir) {
263     dir = base->dir;
264     sprite->set_action(dir == LEFT ? "stacked-left" : "stacked-right");
265   }
266   
267   Vector pos = base->get_pos();
268   pos.y -= sprite->get_current_hitbox_height();
269   set_pos(pos);
270
271   physic.set_velocity_x(base->physic.get_velocity_x());
272   physic.set_velocity_y(base->physic.get_velocity_y());
273 }
274
275
276 IMPLEMENT_FACTORY(Totem, "totem")
277