1 /*
2 * Luola - 2D multiplayer cave-flying game
3 * Copyright (C) 2003-2006 Calle Laakkonen
4 *
5 * File : special.c
6 * Description : Level special objects
7 * Author(s) : Calle Laakkonen
8 *
9 * Luola is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * Luola is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 */
23
24 #include <stdlib.h>
25 #include <math.h>
26
27 #include "SDL.h"
28
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
32
33 #include "list.h"
34 #include "fs.h"
35 #include "player.h"
36 #include "level.h"
37 #include "weapon.h"
38 #include "special.h"
39 #include "ship.h"
40 #include "audio.h"
41
42 /* List of special objects */
43 static struct dllist *special_list;
44
45 /* Special object graphics */
46 static SDL_Surface **jumpgate_gfx;
47 static int jumpgate_frames;
48 static SDL_Surface **turret_gfx[2]; /* Normal and missile turrets */
49 static int turret_frames[2];
50 static SDL_Surface **jumppoint_gfx[2]; /* Entry and exit points */
51 static int jumppoint_frames[2];
52
53 /* Check if there is any solid terrain inside a rectangle */
hitsolid_rect(int x,int y,int w,int h)54 static int hitsolid_rect (int x, int y, int w, int h)
55 {
56 int x2 = x+w, y2 = y+h;
57 for (; x < x2; x++) {
58 for (; y < y2; y++) {
59 if (is_solid(x,y))
60 return 1;
61 }
62 y-=h;
63 }
64 return 0;
65 }
66
67 /* Search for a good place for a turret */
68 /* If ground!=0, only y+ axis is searched */
find_turret_xy(int * tx,int * ty,int ground)69 static int find_turret_xy (int *tx, int *ty,int ground)
70 {
71 int x[4]={*tx,*tx,*tx,*tx}, y[4]={*ty,*ty,*ty,*ty};
72 int dx[4]={0,1,0,-1}, dy[4]={1,0,-1,0};
73 int r,loops=0;
74 while(++loops<400) {
75 for(r=0;r<4;r++) {
76 if((ground&&r!=0) || is_water(x[r],y[r])) continue;
77 x[r] += dx[r];
78 y[r] += dy[r];
79 if(is_solid(x[r],y[r])) {
80 *tx = x[r];
81 *ty = y[r];
82 return 1;
83 }
84 }
85 }
86 return 0;
87 }
88
89 /* Load level special object images */
init_specials(LDAT * specialfile)90 void init_specials (LDAT *specialfile) {
91 jumppoint_gfx[0] = load_image_array(specialfile, 0, T_ALPHA, "WARP",
92 &jumppoint_frames[0]);
93 jumppoint_gfx[1] = load_image_array(specialfile, 0, T_ALPHA, "WARPE",
94 &jumppoint_frames[1]);
95
96 turret_gfx[0] = load_image_array(specialfile, 0, T_ALPHA, "TURRET",
97 &turret_frames[0]);
98 turret_gfx[1] = load_image_array(specialfile, 0, T_ALPHA, "SAMSITE",
99 &turret_frames[1]);
100
101 jumpgate_gfx = load_image_array (specialfile, 0, T_ALPHA, "JUMPGATE",
102 &jumpgate_frames);
103 }
104
105 /* Clear all level specials at the end of the level */
clear_specials(void)106 void clear_specials (void) {
107 dllist_free(special_list,free);
108 special_list=NULL;
109 }
110
111 /* Transport a ship between two jumppoints */
jumppoint_hitship(struct SpecialObj * point,struct Ship * ship)112 static void jumppoint_hitship(struct SpecialObj *point, struct Ship *ship) {
113 if(point->timer==0 && point->link && point->frame>=point->frames/2) {
114 point->timer=0.8*GAME_SPEED;
115 point->link->timer=point->timer;
116 ship->physics.x = point->link->x;
117 ship->physics.y = point->link->y;
118 }
119 }
120
121 /* Transport a projectile between two jumppoints */
jumppoint_hitprojectile(struct SpecialObj * point,struct Projectile * p)122 static void jumppoint_hitprojectile(struct SpecialObj *point, struct Projectile *p) {
123 if(p->physics.radius>=3.5 && point->timer==0 && point->link &&
124 point->frame>=point->frames/2)
125 {
126 point->timer=0.1*GAME_SPEED;
127 point->link->timer=point->timer;
128 p->physics.x = point->link->x;
129 p->physics.y = point->link->y;
130 }
131 }
132
133 /* Jump-point animation */
jumppoint_animate(struct SpecialObj * point)134 static void jumppoint_animate(struct SpecialObj *point) {
135 point->frame++;
136 if(point->frame>=point->frames) /* Loop at the middle */
137 point->frame=point->frames/2;
138 }
139
140 /* Create a jump-point */
make_jumppoint(int x,int y,int owner,int exit)141 struct SpecialObj *make_jumppoint(int x,int y,int owner,int exit) {
142 struct SpecialObj *point= malloc(sizeof(struct SpecialObj));
143 if(!point) {
144 perror(__func__);
145 return NULL;
146 }
147 point->gfx = jumppoint_gfx[exit?1:0];
148 point->frames = jumppoint_frames[exit?1:0];
149 point->frame=0;
150 point->x = x;
151 point->y = y;
152 point->owner = owner;
153 point->secret = 0;
154 switch(game_settings.jumplife) {
155 case JLIFE_SHORT: point->life = point->frames*1.5; break;
156 case JLIFE_MEDIUM: point->life = point->frames*3.5; break;
157 case JLIFE_LONG: point->life = point->frames*6.5; break;
158 }
159 point->timer = 0;
160 point->link = NULL;
161 point->hitship = jumppoint_hitship;
162 point->hitprojectile = jumppoint_hitprojectile;
163 point->animate = jumppoint_animate;
164 point->destroy = NULL;
165
166 return point;
167 }
168
169 /* Open a wormhole between two jumpgates */
jumpgate_hitship(struct SpecialObj * gate,struct Ship * ship)170 static void jumpgate_hitship(struct SpecialObj *gate, struct Ship *ship) {
171 if(gate->timer==0 && gate->link) {
172 struct SpecialObj *exit = make_jumppoint(gate->link->x,gate->link->y,gate->owner,1);
173 struct SpecialObj *entry = make_jumppoint(gate->x,gate->y,gate->owner,0);
174
175 entry->link = exit;
176 exit->link = entry;
177 if(game_settings.onewayjp) {
178 exit->hitship = NULL;
179 exit->hitprojectile = NULL;
180 }
181 gate->timer=exit->life*1.5;
182 gate->link->timer=gate->timer;
183 add_special(exit);
184 add_special(entry);
185 }
186 }
187
188 /* Create a jumpgate */
make_jumpgate(int x,int y)189 static struct SpecialObj *make_jumpgate(int x,int y) {
190 struct SpecialObj *gate = malloc(sizeof(struct SpecialObj));
191 if(!gate) {
192 perror(__func__);
193 return NULL;
194 }
195 gate->gfx = jumpgate_gfx;
196 gate->frames = jumpgate_frames;
197 gate->frame=0;
198 gate->x = x;
199 gate->y = y;
200 gate->owner = -1;
201 gate->life = -1;
202 gate->timer = 0;
203 gate->secret = 0;
204 gate->link = NULL;
205 gate->hitship = jumpgate_hitship;
206 gate->hitprojectile = NULL;
207 gate->animate = NULL;
208 gate->destroy = NULL;
209
210 return gate;
211 }
212
213 /* Add random jumpgates to the level */
add_random_gates(int count)214 static void add_random_gates(int count) {
215 int w=jumpgate_gfx[0]->w;
216 int h=jumpgate_gfx[0]->h;
217 int r;
218 for(r=0;r<count;r++) {
219 struct SpecialObj *gate[2];
220 int g;
221 for(g=0;g<2;g++) {
222 int x,y,loops=0;
223 do {
224 x = rand()%lev_level.width;
225 y = rand()%lev_level.height;
226 } while((hitsolid_rect(x-w/2,y-h/2,w,h) ||
227 (g==1 && hypot(gate[0]->x-x,gate[0]->y-y)<500.0))
228 && ++loops<1000);
229 if(loops>=1000) {
230 fprintf(stderr,"Warning: Couldn't find place for a jumpgate!\n");
231 if(g>0)
232 free(gate[0]);
233 return;
234 }
235 gate[g] = make_jumpgate(x,y);
236 }
237 gate[0]->link = gate[1];
238 gate[1]->link = gate[0];
239 add_special(gate[0]);
240 add_special(gate[1]);
241 }
242 }
243
244 /* Turret fires a projectile */
turret_shoot(struct SpecialObj * turret)245 static void turret_shoot(struct SpecialObj *turret) {
246 struct Projectile *bullet;
247 double x = turret->x + cos(turret->angle)*6;
248 double y = turret->y - sin(turret->angle)*6;
249 Vector v = get_muzzle_vel(turret->angle);
250
251 switch(turret->type) {
252 case 2:
253 bullet = make_missile(x,y,v);
254 bullet->angle = 2*M_PI-turret->angle;
255 turret->timer = 1.5*GAME_SPEED;
256 playwave_3d (WAV_MISSILE, turret->x, turret->y);
257 break;
258 case 1:
259 bullet = make_grenade(x,y,v);
260 turret->timer = 0.9 * GAME_SPEED;
261 playwave_3d (WAV_NORMALWEAP, turret->x, turret->y);
262 break;
263 default:
264 bullet = make_bullet(x,y,v);
265 turret->timer = 0.5 * GAME_SPEED;
266 playwave_3d (WAV_NORMALWEAP, turret->x, turret->y);
267 break;
268 }
269 bullet->owner = turret->owner;
270 add_projectile(bullet);
271 }
272
273 /* Turret explodes when it is destroyed */
turret_explode(struct SpecialObj * turret)274 static void turret_explode(struct SpecialObj *turret) {
275 spawn_clusters(turret->x,turret->y, 5.6, 8, make_bullet);
276 spawn_clusters(turret->x,turret->y, 5.6, 16, make_firestarter);
277 add_explosion (turret->x,turret->y);
278 }
279
280 /* Turret animation */
turret_animate(struct SpecialObj * turret)281 static void turret_animate(struct SpecialObj *turret) {
282 double dist;
283 float targx=-1,targy=-1;
284 int targplr;
285 /* Get a target */
286 targplr = find_nearest_enemy(turret->x,turret->y,turret->owner, &dist);
287 if(dist<160.0) {
288 targx = players[targplr].ship->physics.x;
289 targy = players[targplr].ship->physics.y;
290 } else {
291 targplr = find_nearest_pilot(turret->x,turret->y,turret->owner, &dist);
292 if(dist<160.0) {
293 targx = players[targplr].pilot.walker.physics.x;
294 targy = players[targplr].pilot.walker.physics.y;
295 }
296 }
297 /* Aim at target if found */
298 if(targx>=0) {
299 double a = atan2(turret->y-targy, targx-turret->x);
300 double d;
301 if(a<0) a = 2*M_PI + a;
302 d = shortestRotation(turret->angle,a);
303 if(d<-0.2) {
304 turret->turn = -0.2;
305 } else if(d>0.2) {
306 turret->turn = 0.2;
307 } else {
308 turret->turn = d/2;
309 if(turret->timer==0)
310 turret_shoot(turret);
311 }
312 } else {
313 /* Restore normal turning speed */
314 if(turret->turn<0)
315 turret->turn=-0.05;
316 else
317 turret->turn=0.05;
318 }
319 /* Rotate turret */
320 turret->angle += turret->turn;
321 if(is_solid(turret->x+cos(turret->angle)*5,turret->y-sin(turret->angle)*5))
322 {
323 turret->angle -= turret->turn;
324 turret->turn = -turret->turn;
325 }
326 if(turret->angle>2*M_PI) turret->angle=0;
327 else if(turret->angle<0) turret->angle=2*M_PI;
328 turret->frame = Round(turret->angle/(2*M_PI)*(turret->frames-1));
329 }
330
331 /* Missile turret animation */
mturret_animate(struct SpecialObj * turret)332 static void mturret_animate(struct SpecialObj *turret) {
333 /* Find a target */
334 int targplr;
335 double dist;
336 if(turret->timer==0) {
337 targplr = find_nearest_enemy(turret->x,turret->y,turret->owner, &dist);
338 if(dist<250.0) {
339 turret->angle = (turret->frames-turret->frame)/(double)turret->frames*M_PI_2+M_PI_4;
340 turret_shoot(turret);
341 }
342 }
343
344 /* Animation */
345 if(turret->turn>0) {
346 if(++turret->frame>=turret->frames) {
347 turret->frame=turret->frames-1;
348 turret->turn=-1;
349 }
350 } else {
351 if(--turret->frame>turret->frames) {
352 turret->frame=0;
353 turret->turn=1;
354 }
355 }
356 }
357
358 /* Projectile hits a turret */
turret_hitprojectile(struct SpecialObj * turret,struct Projectile * p)359 static void turret_hitprojectile(struct SpecialObj *turret, struct Projectile *p) {
360 if(p->critter) { /* Collide only with the type of projectiles that
361 can hit critters and pilots */
362 turret->health -= p->damage;
363 if(turret->health<=0) turret->life=0;
364 p->life=0;
365 if(p->explode)
366 p->explode(p);
367 }
368 }
369
370 /* Create a turret */
make_turret(int x,int y,int type)371 static struct SpecialObj *make_turret(int x,int y,int type) {
372 double a;
373 struct SpecialObj *turret = malloc(sizeof(struct SpecialObj));
374 if(!turret) {
375 perror(__func__);
376 return NULL;
377 }
378 turret->gfx = turret_gfx[type==2];
379 turret->frames = turret_frames[type==2];
380 turret->type = type;
381 turret->frame=0;
382 turret->x = x;
383 turret->y = y;
384 turret->owner = -1;
385 turret->life = -1;
386 turret->timer = 0;
387 turret->secret = 0;
388 turret->health = 0.20;
389 turret->hitship = NULL;
390 turret->hitprojectile = turret_hitprojectile;
391 if(type==2)
392 turret->animate = mturret_animate;
393 else
394 turret->animate = turret_animate;
395 turret->destroy = turret_explode;
396 turret->turn = 0.05;
397
398 /* Find a good starting angle */
399 if(type<2)
400 for(a=0;a<2*M_PI;a++)
401 if(!is_solid(x+cos(a)*5,y-sin(a)*5)) {turret->angle = a; break;}
402
403 return turret;
404 }
405
406 /* Add random turrets to level */
add_random_turrets(int count)407 static void add_random_turrets(int count) {
408 int r;
409 for(r=0;r<count;r++) {
410 int x,y,loops=0;
411 int type=rand()%3;
412 do {
413 x = rand()%lev_level.width;
414 y = rand()%lev_level.height;
415 if(is_free(x,y)) {
416 if(find_turret_xy(&x,&y,type==2))
417 break;
418 }
419 } while(++loops<1000);
420 if(loops>=1000) {
421 fprintf(stderr,"Warning: Couldn't find place for a turret!\n");
422 return;
423 }
424 add_special(make_turret(x,y,type));
425 }
426 }
427
428 /* Place level specials at the start of the level */
prepare_specials(struct LevelSettings * settings)429 void prepare_specials (struct LevelSettings * settings) {
430 struct dllist *objects=NULL;
431
432 /* Add random objects */
433 add_random_gates(level_settings.jumpgates);
434 add_random_turrets(level_settings.turrets);
435
436 /* Add manually placed objects */
437 if(settings)
438 objects=settings->objects;
439 while(objects) {
440 struct LSB_Object *objdef = objects->data;
441 struct SpecialObj *obj=NULL;
442 switch(objdef->type) {
443 case OBJ_TURRET:
444 obj = make_turret(objdef->x,objdef->y,objdef->value);
445 break;
446 case OBJ_JUMPGATE:
447 obj = make_jumpgate(objdef->x,objdef->y);
448 obj->owner = objdef->id; /* Store id here temporarily */
449 obj->link = (struct SpecialObj*)objdef->link;
450 break;
451 default: break;
452 }
453 add_special(obj);
454 objects = objects->next;
455 }
456 /* Pair up the manually placed jumpgates */
457 objects = special_list;
458 while(objects) {
459 struct SpecialObj *obj = objects->data;
460 if(obj->gfx == jumpgate_gfx && (int)obj->link<=255) {
461 struct dllist *pair = objects->next;
462 while(pair) {
463 struct SpecialObj *p = pair->data;
464 if(p->gfx == jumpgate_gfx && (int)p->link == obj->owner &&
465 p->owner == (int)obj->link)
466 {
467 obj->owner = -1;
468 obj->link = p;
469 p->owner = -1;
470 p->link = obj;
471 break;
472 }
473 pair = pair->next;
474 }
475 if(pair==NULL) {
476 fprintf(stderr,"No pair (%d) found for jumpgate %d\n",
477 (int)obj->link,obj->owner);
478 obj->life=0; /* mark the jumpgate for deletion */
479 }
480 }
481 objects = objects->next;
482 }
483 }
484
485 /* Add a new level special */
add_special(struct SpecialObj * special)486 void add_special (struct SpecialObj *special) {
487 if(special) {
488 if(special_list)
489 dllist_append(special_list,special);
490 else
491 special_list=dllist_append(special_list,special);
492 }
493 }
494
495 /* Drop a jump point. The jump-point will wait for a pair to be dropped */
496 /* before it will open. */
drop_jumppoint(int x,int y,int player)497 void drop_jumppoint (int x, int y, int player) {
498 /* First search for a exit point to this jumppoint */
499 struct dllist *ptr=special_list;
500 struct SpecialObj *exit,*point;
501 while(ptr) {
502 exit = ptr->data;
503 if(exit->gfx==jumppoint_gfx[1] && exit->owner == player &&
504 exit->link==NULL)
505 break;
506 ptr=ptr->next;
507 }
508 if(ptr) { /* If exit point exists, create the wormhole */
509 point = make_jumppoint(x,y,player,0);
510 point->link = exit;
511 exit->link = point;
512
513 exit->life = point->life;
514 exit->animate = point->animate;
515 exit->secret = 0;
516 if(game_settings.onewayjp==0) {
517 exit->hitship = point->hitship;
518 exit->hitprojectile = point->hitprojectile;
519 }
520 playwave (WAV_JUMP);
521 } else { /* No exit point, create one */
522 point = make_jumppoint(x,y,player,1);
523 point->animate = NULL;
524 point->hitship = NULL;
525 point->hitprojectile = NULL;
526 point->life=-1;
527 point->secret = 1;
528 }
529 add_special(point);
530 }
531
532 /* Draw a special object on all viewports */
draw_special(struct SpecialObj * object)533 static void draw_special (struct SpecialObj *object)
534 {
535 int p;
536 for(p=0;p<4;p++) {
537 if (players[p].state==ALIVE||players[p].state==DEAD) {
538 SDL_Rect rect = {0,0, object->gfx[0]->w, object->gfx[0]->h};
539 SDL_Rect rect2;
540 if(object->secret && p!=object->owner) continue;
541 rect.x = object->x - rect.w/2 - cam_rects[p].x + viewport_rects[p].x;
542 rect.y = object->y - rect.h/2 - cam_rects[p].y + viewport_rects[p].y;
543 if ((rect.x > viewport_rects[p].x - rect.w
544 && rect.x < viewport_rects[p].x + cam_rects[p].w)
545 && (rect.y > viewport_rects[p].y - rect.h
546 && rect.y < viewport_rects[p].y + cam_rects[p].h)) {
547 rect2 =
548 cliprect (rect.x, rect.y, rect.w, rect.h, viewport_rects[p].x,
549 viewport_rects[p].y, viewport_rects[p].x + cam_rects[p].w,
550 viewport_rects[p].y + cam_rects[p].h);
551 if (rect.x < viewport_rects[p].x)
552 rect.x = viewport_rects[p].x;
553 if (rect.y < viewport_rects[p].y)
554 rect.y = viewport_rects[p].y;
555 SDL_BlitSurface (object->gfx[object->frame], &rect2, screen,
556 &rect);
557 }
558 }
559 }
560 }
561
562 /* Check if a ship hits a special object */
ship_hit_special(struct SpecialObj * obj)563 static void ship_hit_special(struct SpecialObj *obj) {
564 struct dllist *ptr = ship_list;
565 int w2 = obj->gfx[0]->w/2;
566 int h2 = obj->gfx[0]->h/2;
567 while(ptr) {
568 struct Ship *ship=ptr->data;
569
570 if(ship->physics.x>=obj->x-w2 && ship->physics.x<=obj->x+w2 &&
571 ship->physics.y>=obj->y-h2 && ship->physics.y<=obj->y+h2)
572 {
573 obj->hitship(obj,ship);
574 break;
575 }
576 ptr=ptr->next;
577 }
578 }
579
580 /* Check if a projectile hits a special object */
projectile_hit_special(struct SpecialObj * obj)581 static void projectile_hit_special (struct SpecialObj *obj) {
582 struct dllist *ptr = projectile_list;
583 int w2 = obj->gfx[0]->w/2;
584 int h2 = obj->gfx[0]->h/2;
585 while(ptr) {
586 struct Projectile *p = ptr->data;
587
588 if(p->physics.x>=obj->x-w2 && p->physics.x<=obj->x+w2 &&
589 p->physics.y>=obj->y-h2 && p->physics.y<=obj->y+h2)
590 {
591 obj->hitprojectile(obj,p);
592 }
593 ptr=ptr->next;
594 }
595 }
596
597 /* Animate special objects */
animate_specials(void)598 void animate_specials (void) {
599 struct dllist *list = special_list,*next;
600 while (list) {
601 struct SpecialObj *obj=list->data;
602
603 /* Check for collisions */
604 if(obj->hitship)
605 ship_hit_special (obj);
606 if(obj->hitprojectile)
607 projectile_hit_special(obj);
608
609 /* Animate */
610 if(obj->animate)
611 obj->animate(obj);
612 if (obj->timer > 0)
613 obj->timer--;
614
615 /* Draw the object on all viewports */
616 draw_special (obj);
617
618 /* Check if the object has expired.
619 * If life < 0, the object has no time limit */
620 next = list->next;
621 if(obj->life > 0) obj->life--;
622 else if (obj->life == 0) {
623 if(obj->destroy)
624 obj->destroy(obj);
625 free (obj);
626 if(list==special_list)
627 special_list=dllist_remove(list);
628 else
629 dllist_remove(list);
630 }
631 list = next;
632 }
633 }
634
635