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