4ede2c7d10439052c239466b4a7f3fa1634b9a80
[supertux.git] / src / gameobjs.cpp
1 //  $Id$
2 // 
3 //  SuperTux
4 //  Copyright (C) 2000 Bill Kendrick <bill@newbreedsoftware.com>
5 //  Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
6 //
7 //  This program is free software; you can redistribute it and/or
8 //  modify it under the terms of the GNU General Public License
9 //  as published by the Free Software Foundation; either version 2
10 //  of the License, or (at your option) any later version.
11 //
12 //  This program is distributed in the hope that it will be useful,
13 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 //  GNU General Public License for more details.
16 // 
17 //  You should have received a copy of the GNU General Public License
18 //  along with this program; if not, write to the Free Software
19 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 //  02111-1307, USA.
21
22 #include <algorithm>
23 #include <iostream>
24 #include <cmath>
25
26 #include "app/globals.h"
27 #include "tile.h"
28 #include "tile_manager.h"
29 #include "gameloop.h"
30 #include "gameobjs.h"
31 #include "special/sprite_manager.h"
32 #include "resources.h"
33 #include "sector.h"
34 #include "tilemap.h"
35 #include "video/drawing_context.h"
36
37 BouncyDistro::BouncyDistro(const Vector& pos)
38   : position(pos)
39 {
40   ym = -2;
41 }
42
43 void
44 BouncyDistro::action(float elapsed_time)
45 {
46   position.y += ym * elapsed_time;
47
48   ym += 0.1 * elapsed_time; // not framerate independent... but who really cares
49   if(ym >= 0)
50     remove_me();
51 }
52
53 void
54 BouncyDistro::draw(DrawingContext& context)
55 {
56   context.draw_surface(img_distro[0], position, LAYER_OBJECTS);
57 }
58
59
60 BrokenBrick::BrokenBrick(Tile* ntile,const Vector& pos, const Vector& nmovement)
61   : tile(ntile), position(pos), movement(nmovement)
62 {
63   timer.start(200);
64 }
65
66 void
67 BrokenBrick::action(float elapsed_time)
68 {
69   position += movement * elapsed_time;
70
71   if (!timer.check())
72     remove_me();
73 }
74
75 void
76 BrokenBrick::draw(DrawingContext& context)
77 {
78   if (tile->images.size() > 0)
79     context.draw_surface_part(tile->images[0],
80         Vector(rand() % 16, rand() % 16),
81         Vector(16, 16),
82         position, LAYER_OBJECTS + 1);
83 }
84
85 BouncyBrick::BouncyBrick(const Vector& pos)
86   : position(pos), offset(0), offset_m(-BOUNCY_BRICK_SPEED), 
87     shape(Sector::current()->solids->get_tile_id_at(pos))
88
89   shape.hidden = true;
90 }
91
92 void
93 BouncyBrick::action(float elapsed_time)
94 {
95   offset += offset_m * elapsed_time;
96
97   /* Go back down? */
98   if (offset < -BOUNCY_BRICK_MAX_OFFSET)
99     offset_m = BOUNCY_BRICK_SPEED;
100
101   /* Stop bouncing? */
102   if (offset >= 0)
103     {
104       shape.hidden = false;
105       remove_me();
106     }
107 }
108
109 void
110 BouncyBrick::draw(DrawingContext& context)
111 {
112   TileManager::instance()->
113     draw_tile(context, shape.id, position + Vector(0, offset), LAYER_TILES+1);
114 }
115
116 FloatingScore::FloatingScore(const Vector& pos, int score)
117   : position(pos)
118 {
119   timer.start(1000);
120   snprintf(str, 10, "%d", score);
121   position.x -= strlen(str) * 8;
122 }
123
124 void
125 FloatingScore::action(float elapsed_time)
126 {
127   position.y -= 2 * elapsed_time;
128
129   if(!timer.check())
130     remove_me();
131 }
132
133 void
134 FloatingScore::draw(DrawingContext& context)
135 {
136   context.draw_text(gold_text, str, position, LEFT_ALLIGN, LAYER_OBJECTS);
137 }
138
139 /* Trampoline */
140
141 Sprite *img_trampoline;
142
143 Trampoline::Trampoline(LispReader& reader)
144 {
145   reader.read_float("x", base.x);
146   reader.read_float("y", base.y); 
147   base.width = 32;
148   base.height = 32;
149   power = 7.5;
150   reader.read_float("power", power);
151
152   frame = 0;
153   mode = M_NORMAL;
154   physic.reset();
155 }
156
157 Trampoline::Trampoline(float x, float y)
158 {
159   base.x = x;
160   base.y = y;
161   base.width = 32;
162   base.height = 32;
163   power = 7.5;
164
165   frame = 0;
166   mode = M_NORMAL;
167   physic.reset();
168 }
169
170 void
171 Trampoline::write(LispWriter& writer)
172 {
173   writer.start_list("trampoline");
174
175   writer.write_float("x", base.x);
176   writer.write_float("y", base.y);
177   writer.write_float("power", power);
178
179   writer.end_list("trampoline");
180 }
181
182 void
183 Trampoline::draw(DrawingContext& context)
184 {
185   img_trampoline->set_frame(frame);
186   img_trampoline->draw(context, base, LAYER_OBJECTS);
187   frame = 0;
188 }
189
190 void
191 Trampoline::action(float frame_ratio)
192 {
193   // TODO: Remove if we're too far off the screen
194
195   // Falling
196   if (mode != M_HELD)
197   {
198     if (issolid(base.x + base.width/2, base.y + base.height))
199     {
200       base.y = int((base.y + base.height)/32) * 32 - base.height;
201
202       physic.enable_gravity(false);
203       physic.set_velocity_y(0.0f);
204
205       physic.set_velocity_x(0);
206     }
207     else
208     {
209       physic.enable_gravity(true);
210     }
211   }
212   else // Player is carrying us around
213   {
214     /* FIXME: The trampoline object shouldn't know about pplayer objects. */
215     /* If we're holding the iceblock */
216     Player& tux = *Sector::current()->player;
217     Direction dir = tux.dir;
218
219     if(dir == RIGHT)
220     {
221       base.x = tux.base.x + 16;
222       base.y = tux.base.y + tux.base.height/1.5 - base.height;
223     }
224     else /* facing left */
225     {
226       base.x = tux.base.x - 16;
227       base.y = tux.base.y + tux.base.height/1.5 - base.height;
228     }
229
230     if(collision_object_map(base))
231     {
232       base.x = tux.base.x;
233       base.y = tux.base.y + tux.base.height/1.5 - base.height;
234     }
235   }
236
237   physic.apply(frame_ratio, base.x, base.y, Sector::current()->gravity);
238   collision_swept_object_map(&old_base, &base);
239 }
240
241 void
242 Trampoline::collision(const MovingObject&, int)
243 {
244   // comes later
245 }
246
247 void
248 Trampoline::collision(void *p_c_object, int c_object, CollisionType type)
249 {
250   Player* pplayer_c = NULL;
251   switch (c_object)
252   {
253     case CO_PLAYER:
254       pplayer_c = (Player*) p_c_object;
255
256       if (type == COLLISION_NORMAL)
257       {
258         // Pick up if HELD (done in Player)
259       }
260
261       else if (type == COLLISION_SQUISH)
262       {
263         int squish_amount = (32 - (int)pplayer_c->base.y % 32);
264
265         if (squish_amount < 24)
266           frame = 3;
267         else if (squish_amount < 28)
268           frame = 2;
269         else if (squish_amount < 30)
270           frame = 1;
271         else
272           frame = 0;
273
274         if (squish_amount < 20) {
275           pplayer_c->physic.set_velocity_y(power);
276           pplayer_c->fall_mode = Player::TRAMPOLINE_JUMP;
277         }
278         else if (pplayer_c->physic.get_velocity_y() < 0)
279           pplayer_c->physic.set_velocity_y(-squish_amount/32);
280       }
281
282       break;
283
284     default:
285       break;
286     
287   }
288 }
289
290 /* Flying Platform */
291
292 Sprite *img_flying_platform;
293
294 FlyingPlatform::FlyingPlatform(LispReader& reader)
295 {
296   reader.read_int_vector("x", pos_x);
297   reader.read_int_vector("y", pos_y);
298
299   velocity = 2.0;
300   reader.read_float("velocity", velocity);
301
302   base.x = pos_x[0];
303   base.y = pos_y[0];
304   base.width = 96;
305   base.height = 40;
306
307   point = 0;
308   move = false;
309
310   float x = pos_x[point+1] - pos_x[point];
311   float y = pos_y[point+1] - pos_y[point];
312   vel_x = x*velocity / sqrt(x*x + y*y);
313   vel_y = -(velocity - vel_x);
314
315   frame = 0;
316 }
317
318 FlyingPlatform::FlyingPlatform(int x, int y)
319 {
320 base.x = x;
321 base.y = y;
322 point = 0;
323 move = false;
324 }
325
326 void
327 FlyingPlatform::write(LispWriter& writer)
328 {
329   writer.start_list("flying-trampoline");
330
331   writer.write_int_vector("x", pos_x);
332   writer.write_int_vector("y", pos_y);
333   writer.write_float("velocity", velocity);
334
335   writer.end_list("flying-trampoline");
336 }
337
338 void
339 FlyingPlatform::draw(DrawingContext& context)
340 {
341   img_flying_platform->draw(context, base, LAYER_OBJECTS);
342 }
343
344 void
345 FlyingPlatform::action(float frame_ratio)
346 {
347   // TODO: Remove if we're too far off the screen
348
349 if(!move)
350   return;
351
352 if((unsigned)point+1 != pos_x.size())
353   {
354   if(((pos_x[point+1] > pos_x[point] && base.x >= pos_x[point+1]) ||
355       (pos_x[point+1] < pos_x[point] && base.x <= pos_x[point+1]) ||
356       pos_x[point] == pos_x[point+1]) &&
357     ((pos_y[point+1] > pos_y[point] && base.y >= pos_y[point+1]) ||
358       (pos_y[point+1] < pos_y[point] && base.y <= pos_y[point+1]) ||
359       pos_y[point] == pos_y[point+1]))
360     {
361     point++;
362
363     float x = pos_x[point+1] - pos_x[point];
364     float y = pos_y[point+1] - pos_y[point];
365     vel_x = x*velocity / sqrt(x*x + y*y);
366     vel_y = -(velocity - vel_x);
367     }
368   }
369 else   // last point
370   {
371   // point = 0;
372   // reverse vector
373   return;
374   }
375 /*
376 if(pos_x[point+1] > base.x)
377   base.x += velocity * frame_ratio;
378 else if(pos_x[point+1] < base.x)
379   base.x -= velocity * frame_ratio;
380
381 if(pos_y[point+1] > base.y)
382   base.y += velocity * frame_ratio;
383 else if(pos_y[point+1] < base.y)
384   base.y -= velocity * frame_ratio;
385 */
386
387 base.x += vel_x * frame_ratio;
388 base.y += vel_y * frame_ratio;
389 }
390
391 void
392 FlyingPlatform::collision(const MovingObject&, int)
393 {
394   // comes later
395 }
396
397 void
398 FlyingPlatform::collision(void *p_c_object, int c_object, CollisionType type)
399 {
400 (void) p_c_object;
401 (void) type;
402
403 //  Player* pplayer_c = NULL;
404   switch (c_object)
405   {
406     case CO_PLAYER:
407 //      pplayer_c = (Player*) p_c_object;
408       move = true;
409
410       break;
411
412     default:
413       break;
414     
415   }
416 }
417
418 Sprite *img_smoke_cloud;
419
420 SmokeCloud::SmokeCloud(const Vector& pos)
421   : position(pos)
422 {
423   timer.start(300);
424 }
425
426 void
427 SmokeCloud::action(float elapsed_time)
428 {
429   position.y -= 1.2 * elapsed_time;
430
431   if(!timer.check())
432     remove_me();
433 }
434
435 void
436 SmokeCloud::draw(DrawingContext& context)
437 {
438   img_smoke_cloud->draw(context, position, LAYER_OBJECTS+1);
439 }
440
441 Particles::Particles(const Vector& epicenter, const Vector& velocity, const Vector& acceleration, int number, Color color_, int size_, int life_time)
442   : color(color_), size(size_), vel(velocity), accel(acceleration)
443 {
444   timer.start(life_time);
445
446   // create particles
447   for(int p = 0; p < number; p++)
448     {
449     Particle* particle = new Particle;
450     particle->pos = epicenter;
451     particle->angle = (rand() % 360) * (M_PI / 180);  // in radius
452
453     particles.push_back(particle);
454     }
455 }
456
457 Particles::~Particles()
458 {
459   // free particles
460   for(std::vector<Particle*>::iterator i = particles.begin(); i < particles.end(); i++)
461     delete (*i);
462 }
463
464 void
465 Particles::action(float elapsed_time)
466 {
467   vel.x += accel.x * elapsed_time;
468   vel.y += accel.y * elapsed_time;
469
470   // update particles
471   for(int p = 0; p < particles.size(); p++)
472     {
473     particles[p]->pos.x += sin(particles[p]->angle) * vel.x * elapsed_time;
474     particles[p]->pos.y += cos(particles[p]->angle) * vel.y * elapsed_time;
475     }
476
477   if(!timer.check())
478     remove_me();
479 }
480
481 void
482 Particles::draw(DrawingContext& context)
483 {
484   // draw particles
485   for(int p = 0; p < particles.size(); p++)
486     {
487     context.draw_filled_rect(particles[p]->pos, Vector(size,size), color, LAYER_OBJECTS+10);
488     }
489 }
490
491 void load_object_gfx()
492 {
493   img_trampoline = sprite_manager->load("trampoline");
494   img_trampoline->start_animation(0);
495   img_flying_platform = sprite_manager->load("flying_platform");
496   img_smoke_cloud = sprite_manager->load("stomp");
497 }