3201eb285fe817eba0f1a07d3a33d3a1d7af195a
[supertux.git] / src / object / 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 #include <config.h>
22
23 #include <algorithm>
24 #include <iostream>
25 #include <cmath>
26
27 #include "app/globals.h"
28 #include "tile.h"
29 #include "tile_manager.h"
30 #include "gameloop.h"
31 #include "gameobjs.h"
32 #include "special/sprite_manager.h"
33 #include "resources.h"
34 #include "sector.h"
35 #include "tilemap.h"
36 #include "video/drawing_context.h"
37 #include "camera.h"
38
39 BouncyCoin::BouncyCoin(const Vector& pos)
40   : position(pos)
41 {
42   timer.start(.3);
43   sprite = sprite_manager->create("coin");
44   sprite->set_action("still");
45 }
46
47 BouncyCoin::~BouncyCoin()
48 {
49   delete sprite;
50 }
51
52 void
53 BouncyCoin::action(float elapsed_time)
54 {
55   position.y += -200 * elapsed_time;
56
57   if(timer.check())
58     remove_me();
59 }
60
61 void
62 BouncyCoin::draw(DrawingContext& context)
63 {
64   sprite->draw(context, position, LAYER_OBJECTS);
65 }
66
67 //---------------------------------------------------------------------------
68
69 BrokenBrick::BrokenBrick(Sprite* nsprite,
70     const Vector& pos, const Vector& nmovement)
71   : sprite(new Sprite(*nsprite)), position(pos), movement(nmovement)
72 {
73   timer.start(.2);
74 }
75
76 BrokenBrick::~BrokenBrick()
77 {
78   delete sprite;
79 }
80
81 void
82 BrokenBrick::action(float elapsed_time)
83 {
84   position += movement * elapsed_time;
85
86   if (timer.check())
87     remove_me();
88 }
89
90 void
91 BrokenBrick::draw(DrawingContext& context)
92 {
93   sprite->draw_part(context,
94       Vector(rand() % 16, rand() % 16), Vector(16, 16),
95       position, LAYER_OBJECTS + 1);
96 }
97
98 //---------------------------------------------------------------------------
99
100 FloatingText::FloatingText(const Vector& pos, const std::string& text_)
101   : position(pos), text(text_)
102 {
103   timer.start(.1);
104   position.x -= text.size() * 8;
105 }
106
107 FloatingText::FloatingText(const Vector& pos, int score)
108   : position(pos)
109 {
110   timer.start(.1);
111
112   // turn int into a string
113   char str[10];
114   snprintf(str, 10, "%d", score);
115   text = str;
116
117   position.x -= text.size() * 8;
118 }
119
120 void
121 FloatingText::action(float elapsed_time)
122 {
123   position.y -= 1.4 * elapsed_time;
124
125   if(timer.check())
126     remove_me();
127 }
128
129 #define FADING_TIME .350
130
131 void
132 FloatingText::draw(DrawingContext& context)
133 {
134   // make an alpha animation when disapearing
135   int alpha;
136   if(timer.get_timeleft() < FADING_TIME)
137     alpha = int(timer.get_timeleft() * 255 / FADING_TIME);
138   else
139     alpha = 255;
140
141   context.push_transform();
142   context.set_alpha(alpha);
143
144   context.draw_text(gold_text, text, position, LEFT_ALLIGN, LAYER_OBJECTS+1);
145
146   context.pop_transform();
147 }
148
149 /* Trampoline */
150
151 #if 0
152 Sprite *img_trampoline;
153
154 Trampoline::Trampoline(LispReader& reader)
155 {
156   reader.read_float("x", base.x);
157   reader.read_float("y", base.y); 
158   base.width = 32;
159   base.height = 32;
160   power = 7.5;
161   reader.read_float("power", power);
162
163   frame = 0;
164   mode = M_NORMAL;
165   physic.reset();
166 }
167
168 Trampoline::Trampoline(float x, float y)
169 {
170   base.x = x;
171   base.y = y;
172   base.width = 32;
173   base.height = 32;
174   power = 7.5;
175
176   frame = 0;
177   mode = M_NORMAL;
178   physic.reset();
179 }
180
181 void
182 Trampoline::write(LispWriter& writer)
183 {
184   writer.start_list("trampoline");
185
186   writer.write_float("x", base.x);
187   writer.write_float("y", base.y);
188   writer.write_float("power", power);
189
190   writer.end_list("trampoline");
191 }
192
193 void
194 Trampoline::draw(DrawingContext& context)
195 {
196   img_trampoline->set_frame(frame);
197   img_trampoline->draw(context, base, LAYER_OBJECTS);
198   frame = 0;
199 }
200
201 void
202 Trampoline::action(float frame_ratio)
203 {
204   // TODO: Remove if we're too far off the screen
205
206   // Falling
207   if (mode != M_HELD)
208   {
209     if (issolid(base.x + base.width/2, base.y + base.height))
210     {
211       base.y = int((base.y + base.height)/32) * 32 - base.height;
212
213       physic.enable_gravity(false);
214       physic.set_velocity_y(0.0f);
215
216       physic.set_velocity_x(0);
217     }
218     else
219     {
220       physic.enable_gravity(true);
221     }
222   }
223   else // Player is carrying us around
224   {
225     /* FIXME: The trampoline object shouldn't know about pplayer objects. */
226     /* If we're holding the iceblock */
227     Player& tux = *Sector::current()->player;
228     Direction dir = tux.dir;
229
230     if(dir == RIGHT)
231     {
232       base.x = tux.base.x + 16;
233       base.y = tux.base.y + tux.base.height/1.5 - base.height;
234     }
235     else /* facing left */
236     {
237       base.x = tux.base.x - 16;
238       base.y = tux.base.y + tux.base.height/1.5 - base.height;
239     }
240
241     if(collision_object_map(base))
242     {
243       base.x = tux.base.x;
244       base.y = tux.base.y + tux.base.height/1.5 - base.height;
245     }
246   }
247
248   physic.apply(frame_ratio, base.x, base.y, Sector::current()->gravity);
249   collision_swept_object_map(&old_base, &base);
250 }
251
252 void
253 Trampoline::collision(const MovingObject&, int)
254 {
255   // comes later
256 }
257
258 void
259 Trampoline::collision(void *p_c_object, int c_object, CollisionType type)
260 {
261   Player* pplayer_c = NULL;
262   switch (c_object)
263   {
264     case CO_PLAYER:
265       pplayer_c = (Player*) p_c_object;
266
267       if (type == COLLISION_NORMAL)
268       {
269         // Pick up if HELD (done in Player)
270       }
271
272       else if (type == COLLISION_SQUISH)
273       {
274         int squish_amount = (32 - (int)pplayer_c->base.y % 32);
275
276         if (squish_amount < 24)
277           frame = 3;
278         else if (squish_amount < 28)
279           frame = 2;
280         else if (squish_amount < 30)
281           frame = 1;
282         else
283           frame = 0;
284
285         if (squish_amount < 20) {
286           pplayer_c->physic.set_velocity_y(power);
287           pplayer_c->fall_mode = Player::TRAMPOLINE_JUMP;
288         }
289         else if (pplayer_c->physic.get_velocity_y() < 0)
290           pplayer_c->physic.set_velocity_y(-squish_amount/32);
291       }
292
293       break;
294
295     default:
296       break;
297     
298   }
299 }
300 #endif
301
302 /* Flying Platform */
303
304 #if 0
305 Sprite *img_flying_platform;
306
307 FlyingPlatform::FlyingPlatform(LispReader& reader)
308 {
309   reader.read_int_vector("x", pos_x);
310   reader.read_int_vector("y", pos_y);
311
312   velocity = 2.0;
313   reader.read_float("velocity", velocity);
314
315   base.x = pos_x[0];
316   base.y = pos_y[0];
317   base.width = 96;
318   base.height = 40;
319
320   point = 0;
321   move = false;
322
323   float x = pos_x[point+1] - pos_x[point];
324   float y = pos_y[point+1] - pos_y[point];
325   vel_x = x*velocity / sqrt(x*x + y*y);
326   vel_y = -(velocity - vel_x);
327
328   frame = 0;
329 }
330
331 FlyingPlatform::FlyingPlatform(int x, int y)
332 {
333 base.x = x;
334 base.y = y;
335 point = 0;
336 move = false;
337 }
338
339 void
340 FlyingPlatform::write(LispWriter& writer)
341 {
342   writer.start_list("flying-trampoline");
343
344   writer.write_int_vector("x", pos_x);
345   writer.write_int_vector("y", pos_y);
346   writer.write_float("velocity", velocity);
347
348   writer.end_list("flying-trampoline");
349 }
350
351 void
352 FlyingPlatform::draw(DrawingContext& context)
353 {
354   img_flying_platform->draw(context, base, LAYER_OBJECTS);
355 }
356
357 void
358 FlyingPlatform::action(float frame_ratio)
359 {
360   // TODO: Remove if we're too far off the screen
361
362 if(!move)
363   return;
364
365 if((unsigned)point+1 != pos_x.size())
366   {
367   if(((pos_x[point+1] > pos_x[point] && base.x >= pos_x[point+1]) ||
368       (pos_x[point+1] < pos_x[point] && base.x <= pos_x[point+1]) ||
369       pos_x[point] == pos_x[point+1]) &&
370     ((pos_y[point+1] > pos_y[point] && base.y >= pos_y[point+1]) ||
371       (pos_y[point+1] < pos_y[point] && base.y <= pos_y[point+1]) ||
372       pos_y[point] == pos_y[point+1]))
373     {
374     point++;
375
376     float x = pos_x[point+1] - pos_x[point];
377     float y = pos_y[point+1] - pos_y[point];
378     vel_x = x*velocity / sqrt(x*x + y*y);
379     vel_y = -(velocity - vel_x);
380     }
381   }
382 else   // last point
383   {
384   // point = 0;
385   // reverse vector
386   return;
387   }
388 /*
389 if(pos_x[point+1] > base.x)
390   base.x += velocity * frame_ratio;
391 else if(pos_x[point+1] < base.x)
392   base.x -= velocity * frame_ratio;
393
394 if(pos_y[point+1] > base.y)
395   base.y += velocity * frame_ratio;
396 else if(pos_y[point+1] < base.y)
397   base.y -= velocity * frame_ratio;
398 */
399
400 base.x += vel_x * frame_ratio;
401 base.y += vel_y * frame_ratio;
402 }
403
404 void
405 FlyingPlatform::collision(const MovingObject&, int)
406 {
407   // comes later
408 }
409
410 void
411 FlyingPlatform::collision(void *p_c_object, int c_object, CollisionType type)
412 {
413 (void) p_c_object;
414 (void) type;
415
416 //  Player* pplayer_c = NULL;
417   switch (c_object)
418   {
419     case CO_PLAYER:
420 //      pplayer_c = (Player*) p_c_object;
421       move = true;
422
423       break;
424
425     default:
426       break;
427     
428   }
429 }
430 #endif
431
432 Sprite *img_smoke_cloud = 0;
433
434 SmokeCloud::SmokeCloud(const Vector& pos)
435   : position(pos)
436 {
437   timer.start(.3);
438 }
439
440 void
441 SmokeCloud::action(float elapsed_time)
442 {
443   position.y -= 120 * elapsed_time;
444
445   if(timer.check())
446     remove_me();
447 }
448
449 void
450 SmokeCloud::draw(DrawingContext& context)
451 {
452   img_smoke_cloud->draw(context, position, LAYER_OBJECTS+1);
453 }
454
455 Particles::Particles(const Vector& epicenter, int min_angle, int max_angle,
456         const Vector& initial_velocity, const Vector& acceleration, int number,
457         Color color_, int size_, float life_time, int drawing_layer_)
458   : accel(acceleration), color(color_), size(size_), drawing_layer(drawing_layer_)
459 {
460   if(life_time == 0) {
461     live_forever = true;
462   } else {
463     live_forever = false;
464     timer.start(life_time);
465   }
466
467   // create particles
468   for(int p = 0; p < number; p++)
469     {
470     Particle* particle = new Particle;
471     particle->pos = epicenter;
472
473     float angle = ((rand() % (max_angle-min_angle))+min_angle)
474                       * (M_PI / 180);  // convert to radius
475     particle->vel.x = /*fabs*/(sin(angle)) * initial_velocity.x;
476 //    if(angle >= M_PI && angle < M_PI*2)
477 //      particle->vel.x *= -1;  // work around to fix signal
478     particle->vel.y = /*fabs*/(cos(angle)) * initial_velocity.y;
479 //    if(angle >= M_PI_2 && angle < 3*M_PI_2)
480 //      particle->vel.y *= -1;
481
482     particles.push_back(particle);
483     }
484 }
485
486 Particles::~Particles()
487 {
488   // free particles
489   for(std::vector<Particle*>::iterator i = particles.begin();
490       i < particles.end(); i++)
491     delete (*i);
492 }
493
494 void
495 Particles::action(float elapsed_time)
496 {
497   Vector camera = Sector::current()->camera->get_translation();
498
499   // update particles
500   for(std::vector<Particle*>::iterator i = particles.begin();
501       i != particles.end(); ) {
502     (*i)->pos.x += (*i)->vel.x * elapsed_time;
503     (*i)->pos.y += (*i)->vel.y * elapsed_time;
504
505     (*i)->vel.x += accel.x * elapsed_time;
506     (*i)->vel.y += accel.y * elapsed_time;
507
508     if((*i)->pos.x < camera.x || (*i)->pos.x > screen->w + camera.x ||
509        (*i)->pos.y < camera.y || (*i)->pos.y > screen->h + camera.y) {
510       delete (*i);
511       i = particles.erase(i);
512     } else {
513       ++i;
514     }
515   }
516
517   if((timer.check() && !live_forever) || particles.size() == 0)
518     remove_me();
519 }
520
521 void
522 Particles::draw(DrawingContext& context)
523 {
524   // draw particles
525   for(std::vector<Particle*>::iterator i = particles.begin();
526       i != particles.end(); i++) {
527     context.draw_filled_rect((*i)->pos, Vector(size,size), color,drawing_layer);
528   }
529 }
530
531 void load_object_gfx()
532 {
533 #if 0
534   img_trampoline = sprite_manager->load("trampoline");
535   img_trampoline->start_animation(0);
536   img_flying_platform = sprite_manager->load("flying_platform");
537 #endif
538   img_smoke_cloud = sprite_manager->create("stomp");
539 }
540
541 void free_object_gfx()
542 {
543   delete img_smoke_cloud;
544 }
545